Skip to content

feat: v0.2.13 release - auth, IP restriction, skills loader, auto-yes refactor#351

Merged
Kewton merged 35 commits intomainfrom
develop
Feb 22, 2026
Merged

feat: v0.2.13 release - auth, IP restriction, skills loader, auto-yes refactor#351
Kewton merged 35 commits intomainfrom
develop

Conversation

@Kewton
Copy link
Owner

@Kewton Kewton commented Feb 22, 2026

Summary

Changes

Issue Type Description
#323 refactor auto-yes-manager の pollAutoYes() を focused functions に分割
#331 feat トークン認証・HTTPS・ログインUI・AuthContext・ミドルウェア
#332 feat IP/CIDR制限(HTTP + WebSocket)、認証リダイレクト処理
#343 feat skills ローダー追加、YAML fallback パーサー、コマンドマージャー拡張
- fix SKILL.md YAML frontmatter のクォート修正

Test plan

  • npm run lint パス確認
  • npx tsc --noEmit 型チェックパス確認
  • npm run test:unit ユニットテストパス確認
  • npm run build ビルド成功確認
  • トークン認証のログイン/ログアウトフロー動作確認
  • IP制限設定時のアクセス制御動作確認
  • / 入力時にスキルがセレクターに表示されることを確認
  • SKILL.md のフロントマターが正常にパースされることを確認

🤖 Generated with Claude Code

Kewton and others added 30 commits February 21, 2026 09:26
…tions

Extract 4 @internal export functions from pollAutoYes() (~139 lines)
to improve testability and SRP compliance:
- validatePollingContext(): Pre-condition checks
- captureAndCleanOutput(): tmux output capture + ANSI cleanup
- processStopConditionDelta(): Stop condition delta-based check
- detectAndRespondToPrompt(): Prompt detection + auto-response

Also add getPollerState() internal helper for consistency with
existing getAutoYesState() accessor pattern.

pollAutoYes() is now a ~30-line orchestrator.
No functional changes - all 82 existing tests pass unchanged.
20 new timer-independent unit tests added (102 total).

Refs #323

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace 'manual' as 'expired' type cast with direct 'expired' value
in validatePollingContext test. The original cast was misleading since
AutoYesStopReason only accepts 'expired' | 'stop_pattern_matched'.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Issue #323 (pollAutoYes() refactoring) の pm-auto-issue2dev 全工程の
成果物を追加。Issueレビュー(8段階)→設計方針書→設計レビュー(4段階)
→作業計画→TDD自動開発のレポートファイル一式。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
refactor(auto-yes-manager): decompose pollAutoYes() into single-responsibility functions
- Add src/lib/auth.ts core module (generateToken, hashToken, verifyToken
  with timingSafeEqual, parseDuration, parseCookies, createRateLimiter)
- Add src/middleware.ts for Next.js request authentication
- Add auth API routes (login, logout, status)
- Add login page with i18n, rate limit display, and redirect logic
- Add WebSocket authentication via Cookie header in ws-server.ts
- Add HTTPS support in server.ts with certificate validation
- Add CLI options: --auth, --auth-expire, --cert, --key, --allow-http
- Add LogoutButton component in sidebar (desktop + mobile drawer)
- Add auth i18n namespace (en/ja)
- Update .env.example with auth/HTTPS documentation
- All 89 new tests pass (unit + integration)
- All 3724 existing unit tests pass

Resolves #331

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Apply DRY, readability, and maintainability improvements to Issue #331 code:

- Extract buildAuthCookieOptions() to centralize cookie security settings (login/logout DRY)
- Extract isHttpsEnabled() helper for CM_HTTPS_CERT environment check
- Extract DEFAULT_COOKIE_MAX_AGE_SECONDS constant (remove magic number 86400)
- Extract MS_PER_MINUTE/MS_PER_HOUR/MS_PER_DAY time constants (eliminate magic numbers)
- Replace switch-case in parseDuration() with data-driven unitMultipliers lookup
- Simplify rate limiter cleanup condition (remove redundant null check)
- Remove dead code (empty if-body) in daemon.ts auth env forwarding
- Add 'as const' to authEnvKeys array in daemon.ts for type narrowing
- Extract displayAuthToken() in start.ts (remove duplicated token display logic)
- Extract isExpectedWebSocketError() in ws-server.ts (remove duplicated error patterns)
- Add JSDoc to getClientIp(), displayAuthToken(), isExpectedWebSocketError()
- Add AuthCookieOptions interface with C001 constraint documentation

Security constraints maintained:
- S001: crypto.timingSafeEqual() for token verification
- S002: AUTH_EXCLUDED_PATHS exact match (===)
- C001: No Next.js module dependencies in auth.ts

Quality: tsc 0 errors, ESLint 0 errors, 3724/3724 tests pass

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add Quick Start section for token auth + HTTPS to security-guide.md
- Add mkcert certificate generation instructions for macOS and Linux
- Add Linux CA certificate distribution procedures
- Update Security Checklist with built-in auth options
- Update Migration from CM_AUTH_TOKEN section (--auth warning note)
- Add src/lib/auth.ts and src/middleware.ts to CLAUDE.md module list

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add design policy: issue-331-token-auth-design-policy.md
- Add multi-stage design review reports (stage1-4)
- Add multi-stage issue review reports
- Add work plan: work-plan.md
- Add pm-auto-dev iteration-1 reports

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
feat(auth): トークン認証・HTTPS対応 #331
middleware.ts imported auth.ts which uses Node.js crypto.timingSafeEqual,
causing ENVIRONMENT_FALLBACK errors since Next.js middleware runs in Edge Runtime.

Changes:
- Remove auth.ts import from middleware.ts
- Inline Edge Runtime compatible token verification using Web Crypto API
  (crypto.subtle.digest for SHA-256, XOR loop for constant-time comparison)
- Make middleware function async for Web Crypto API support
- Update auth-middleware.test.ts to await async middleware calls

Security maintained:
- S001: XOR over fixed-length SHA-256 hex is equivalent to timingSafeEqual
- S002: AUTH_EXCLUDED_PATHS exact match preserved

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
next-intl v4 throws ENVIRONMENT_FALLBACK during SSR of client components
when timeZone is not set in NextIntlClientProvider. The root cause is that
v4 validates the SSR environment by checking for timeZone on the server side.

Fix: propagate timeZone through i18n.ts -> layout.tsx -> AppProviders.tsx
so that useTranslations() in LogoutButton and other client components works
correctly during server-side rendering.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously, LogoutButton fetched /api/auth/status on every mount, causing
a visible layout shift (null -> button) that made the UI flicker.

Fix: pass authEnabled from layout.tsx (server component reads
CM_AUTH_TOKEN_HASH at SSR time) through AuthContext to LogoutButton.
The button now renders correctly on first paint with no client-side fetch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…c status fetch

Remove checkingAuth state and HEAD / request from login page.
Auth status is now read synchronously from AuthContext (set server-side
in layout.tsx), so the form renders immediately without a Loading...
intermediate state.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
On Node.js 19+, WebSocket upgrade requests can trigger the 'request'
event (and middleware) even when an 'upgrade' listener is registered,
causing TypeError in Next.js handleRequestImpl because the response
object for upgrade requests lacks setHeader.

Three-layer fix (Issue #331):
- server.ts: early return for WebSocket upgrade in requestHandler
- src/middleware.ts: early NextResponse.next() for upgrade requests;
  matcher updated to exclude all _next/ paths (not just static/image)
- src/lib/ws-server.ts: properly close /_next/ sockets in production
  instead of leaving them unhandled (which triggers the fallback)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Bug 1: /api/auth/status always returned authEnabled:false
- Add `export const dynamic = 'force-dynamic'` to auth/status route
- CM_AUTH_TOKEN_HASH is set at runtime, so static caching caused
  isAuthEnabled() to always evaluate with the build-time env (undefined)

Bug 2: TypeError: Cannot read properties of undefined (reading 'bind')
- Root cause: next() creates NextCustomServer which lazily calls
  setupWebSocketHandler() on the first HTTP request, registering
  its own 'upgrade' listener that passes the raw TCP socket as the
  HTTP response object, causing handleRequestImpl to fail at
  _res.setHeader.bind(_res)
- Fix: make setupWebSocketHandler a no-op after next() call so
  Next.js never registers its upgrade listener; all WebSocket
  upgrades are handled exclusively by ws-server.ts
- Add defense-in-depth guard: skip requestHandler if res has no setHeader
- Forward auth env vars (CM_AUTH_TOKEN_HASH etc.) to daemon process
  via process.env so start --daemon passes them to the child

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… docs

Extract AUTH_COOKIE_NAME and AUTH_EXCLUDED_PATHS from auth.ts/middleware.ts
into a shared Edge Runtime-compatible module (src/config/auth-config.ts) to
eliminate DRY violation. Add Issue #331 entry to implementation-history docs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- C1: Add token expiry check to middleware.ts (via shared computeExpireAt)
- H1: Verify auth cookie on WebSocket upgrade requests in middleware
- H2: Replace X-Forwarded-For-based rate limiting with global key
  to prevent IP spoofing bypass
- H3: Pass hostname to server.listen() so CM_BIND is respected;
  add server 'error' event handler for EADDRINUSE
- M1: Validate CM_AUTH_TOKEN_HASH is 64-char hex at startup
- M3: Add 256-char token length limit on login endpoint
- L1: Add Content-Length/Connection headers to WebSocket 401 response
- Consolidate parseDuration/computeExpireAt into auth-config.ts (DRY)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix isAuthEnabled() inconsistency: use storedTokenHash instead of
  raw env var, preventing auth-enabled-but-login-impossible state when
  CM_AUTH_TOKEN_HASH is malformed (#2 Medium)
- Add isValidTokenHash() type predicate to auth-config.ts for shared
  validation across auth.ts and middleware.ts
- Fix CLI HTTPS protocol display: align with server.ts logic so
  --cert --key always shows https:// URL (#3 Medium)
- Add --cert/--key mutual requirement validation (#3 Medium)
- Fix auth-middleware test mocks: add headers.get() mock and
  WebSocket upgrade auth test cases (#4 Low)
- Document global rate limit trade-off in login route (#1 High)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
feat(auth): add token authentication and HTTPS support (#331)
- Add 'skill' to SlashCommandCategory and SlashCommandSource types
- Implement loadSkills() to read SKILL.md frontmatter (name, description)
- Add safeParseFrontmatter() to disable gray-matter JS engine (S001)
- Implement deduplicateByName() with command priority over skills
- Add skillsCache independent from commandsCache, cleared together
- Add 'skill' to CATEGORY_ORDER between workflow and standard-session
- Update API route to include skill count in sources response
- Add comprehensive tests (31 unit + 19 command-merger + 3 integration)
- Coverage: slash-commands.ts 93%, command-merger.ts 93%, route.ts 84%

Resolves #343

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add documentation comments to improve code maintainability for Issue #343:

- filterCommands(): note that it only searches commandsCache, not skills
- loadSlashCommands(): document dual commandsCache assignment pattern
- loadSkills(): explain async declaration rationale (consistency)
- parseSkillFile(): [D009] clarify cliTools vs allowed-tools distinction
- route.ts: TODO for SlashCommandsResponse type unification with api-client.ts

No logic changes. All tests pass.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…orts

- Add slash-commands module entries to CLAUDE.md
- Add Issue #343 to implementation-history.md
- Add design policy, issue review, design review, and pm-auto-dev reports

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
feat: スラッシュコマンドセレクターで .claude/skills も表示する (#343)
…frontmatter

SKILL.md files with unquoted YAML special characters (colons, brackets)
in fields like argument-hint cause gray-matter's js-yaml parser to throw
YAMLException, silently dropping those skills. Add extractFrontmatterFields()
as a regex-based fallback to extract name and description when full YAML
parsing fails. Fixes skills like release and release-post not appearing
in the slash command selector.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fix(#343): add regex fallback for YAML-unfriendly SKILL.md frontmatter
…cess

- Add src/lib/ip-restriction.ts with Edge Runtime compatible CIDR matching
- Inject X-Real-IP header in server.ts for trusted IP identification
- Add IP restriction check in middleware.ts (Step 1, before auth)
- Add IP restriction check in ws-server.ts (defense-in-depth)
- Extend CLI with --allowed-ips and --trust-proxy options
- Add daemon.ts authEnvKeys for CM_ALLOWED_IPS/CM_TRUST_PROXY
- Add 45 unit tests and 6 integration tests for IP restriction
- All static analysis checks passed (tsc, ESLint)

Resolves #332

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…le.log

Reorganize ip-restriction.ts module-scope initialization before dependent
functions for better readability. Enhance JSDoc with S4-001 future extension
notes and S4-002/S4-005 rationale. Remove console.log statements from
ws-server.ts per CLAUDE.md production rules. Fix daemon.ts REVERSE_PROXY_WARNING
to account for CM_ALLOWED_IPS (consistent with start.ts).

Improvements:
- ip-restriction.ts: module-scope vars moved before getClientIp() that references them
- ip-restriction.ts: enhanced JSDoc for S1-004, S4-001, S4-002, S4-005, S4-006
- ws-server.ts: removed 8 console.log calls, kept console.warn/error for security/errors
- daemon.ts: added CM_ALLOWED_IPS check to suppress REVERSE_PROXY_WARNING

Quality Metrics:
- TypeScript errors: 0
- ESLint errors: 0
- Unit tests: 3796 passed (185 files)
- Integration auth-middleware tests: 17 passed

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…orts

- Add ip-restriction.ts entry to CLAUDE.md module list
- Add Issue #332 to docs/implementation-history.md
- Add design policy, issue review, design review, and TDD reports

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Detect auth redirect (307 → /login) in fetchApi and throw ApiError(401)
- Check content-type before parsing response as JSON
- Stop WorktreeSelectionContext polling on 401 errors to prevent
  console spam on login page
- Update test mocks to use importOriginal pattern for ApiError export

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Kewton and others added 5 commits February 22, 2026 22:50
feat(#332): アクセス元IP制限オプションの追加
fix(auth): handle auth redirects and stop polling on 401
…rors

Values containing colons, parentheses, and long strings caused gray-matter
YAML parsing failures during build.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@Kewton Kewton merged commit 6c7f93c into main Feb 22, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant