Skip to content

fix: Claude v2.1.142 スキル承認プロンプトの検出失敗を修正 (#704)#705

Merged
Kewton merged 1 commit into
developfrom
feature/704-worktree
May 15, 2026
Merged

fix: Claude v2.1.142 スキル承認プロンプトの検出失敗を修正 (#704)#705
Kewton merged 1 commit into
developfrom
feature/704-worktree

Conversation

@Kewton
Copy link
Copy Markdown
Owner

@Kewton Kewton commented May 15, 2026

Summary

Claude Code v2.1.142 の「Use skill "X"?」スキル承認プロンプトが CommandMate の prompt-detector で取れず、Yes/No ウィンドウが表示されない、Auto-Yes も発火しない不具合を修正します。

根本原因は src/lib/detection/prompt-detect-multiple-choice.tsNORMAL_OPTION_PATTERN が承認プロンプト直後のサマリ行 … +1 pending を「option 1 / label='pending'」と誤マッチし、isValidPrecedingOption() が連鎖して本物の 1./2./3. を全て弾いてしまうことでした(Layer 4 で options.length < 2no_prompt 返却)。

Closes #704

Changes

Added (src/lib/detection/prompt-detect-multiple-choice.ts, +83 lines)

  • S1: SUMMARY_LINE_PATTERN — Pass 2 オプション収集ループの先頭で … +N pending / +N pending / ↑N more / ↓N more 系のサマリ行を early-continue で除外。FP 抑止のため行頭の数字+メタキーワード(pending|more)のみに制限し、(.+) のフリーマッチを含めない設計。ReDoS safe。
  • S2: CLAUDE_PROMPT_FOOTER_PATTERN — Pass 2 開始前に Esc to cancel · Tab to amend フッターを発見したら effectiveEnd をその行 index に切り詰める。フッター未発見時は effectiveEnd を維持(fallback-safe)し、Codex/Gemini/OpenCode/Copilot への影響をゼロに保証。
  • S1 と S2 は独立に修正完了能力を持つ二重防御。Claude 上流のフッター文言・サマリ行文言いずれかが将来変わっても片方のガードで吸収できる構造。

Added (Tests, +156 lines)

  • tests/unit/prompt-detector.test.ts (+94): Issue fix: Claude Code v2.1.142「Use skill "X"?」プロンプトが Yes/No UI として検出されない #704 実 fixture(Use skill 完全プロンプト)で multiple_choice / options=[Yes, Yes2nd, No] / default=Yes 検証、末尾サマリ行単独で誤検出が出ないことの回帰防御、pending を含むラベルが正しく option として収集されることの FP 抑止検証。
  • tests/unit/lib/status-detector.test.ts (+38): 同 fixture で sessionStatus='waiting' / reason='prompt_detected' を返すことを検証。
  • tests/unit/lib/auto-yes-resolver.test.ts (+24): Yes/Yes2nd/No 構造プロンプトに対し resolveAutoAnswer'1'(Yes)を返すことを検証。

Added (Dev Reports, 13 files)

  • dev-reports/bug-fix/issue-704-20260515_173223/ 配下に Phase 1〜6 の context/result JSON、test evidence ログ、最終 progress report を保存。

Test Results

Unit Tests

npm run test:unit
Test Files  343 passed (343)
     Tests  6486 passed | 7 skipped (6493)

ベースライン 6481 + 新規 5 ケース = 6486 と完全一致。

Lint & Type Check

  • ESLint: 0 errors / 0 warnings
  • TypeScript (tsc --noEmit): 0 errors

Build

npm run build
✓ Compiled successfully
✓ Generating static pages (32/32)

Issue #704 ターゲットテスト

テスト 結果
prompt-detector.test.ts -t "Issue #704" 3/3 PASSED
status-detector.test.ts -t "Issue #704" 1/1 PASSED
auto-yes-resolver.test.ts -t "Issue #704" 1/1 PASSED

カバレッジ

src/lib/detection/prompt-detect-multiple-choice.ts:

  • Statements: 95.6%
  • Branch: 90.66%
  • Functions: 100%
  • Lines: 96.95%

(目標 80% を全項目で大幅クリア)

回帰防御

関連既存テスト 結果
prompt-detector.test.ts 全体 203/203 PASSED
status-detector.test.ts 全体 28/28 PASSED
auto-yes-resolver.test.ts 全体 9/9 PASSED
Bash/Edit 承認プロンプト 5/5 PASSED
Codex 関連 13/13 PASSED
Gemini 関連 6/6 PASSED

NORMAL_OPTION_PATTERN 自体は触らず追加ガードのみの実装のため、Codex/Gemini/OpenCode/Copilot 検出パスへの副作用なし。

受入条件チェック(Issue #704 本文より)

  • Use skill "X"? を含む実出力 fixture で prompt-detector が multiple_choice を返す
  • status-detector が sessionStatus='waiting' / reason='prompt_detected' を返す(API レスポンス isPromptWaiting=true / promptData!=null を担保。current-output route.ts の素通し伝播も併せて確認)
  • Auto-Yes 有効時にスキル承認プロンプトに対して resolveAutoAnswer が Yes 応答を返す
  • 既存の Bash/Edit 承認プロンプトの検出が壊れていない(回帰なし)
  • 単体テストが追加され npm run test:unit でパスする

UI レイヤーの Yes/No ウィンドウ DOM レンダリングは /uat で別途確認予定。

Checklist

  • Unit tests pass
  • Lint check passes
  • Type check passes
  • Build succeeds
  • No console.log in production code
  • ReDoS safe regex patterns (両端アンカー / linear / no nested quantifiers)
  • 回帰防御テスト追加(FP 抑止 + サマリ行単独 FP 防御 + 3 層回帰テスト)

🤖 Generated with Claude Code

Co-Authored-By: Claude noreply@anthropic.com

…line (#704)

Claude Code v2.1.142 renders trailing summary lines like "… +1 pending"
below the real options in skill approval prompts ("Use skill ...?").
NORMAL_OPTION_PATTERN was matching these as option N with label="pending",
which poisoned isValidPrecedingOption() and caused every real option
above (1./2./3.) to be rejected — collectedOptions ended up with length
< 2, returning no_prompt. As a result Yes/No UI never appeared and
Auto-Yes did not fire.

Defense-in-depth fix:
- S1: SUMMARY_LINE_PATTERN early-continue in Pass 2 loop (tight
  anchored pattern, no free `(.+)` capture, keyword whitelist
  pending|more) so legitimate labels mentioning these words are
  preserved.
- S2: CLAUDE_PROMPT_FOOTER_PATTERN trims effectiveEnd to the
  "Esc to cancel · Tab to amend" footer, putting the summary line
  outside the scan window entirely. Fallback-safe: no footer found =>
  effectiveEnd untouched, so Codex/Gemini/OpenCode/Copilot detection
  paths are unaffected.
- S3: 3-layer regression tests across prompt-detector, status-detector,
  and auto-yes-resolver covering the real Claude v2.1.142 fixture plus
  FP-suppression for option labels containing "pending"/"more".

Quality gates: lint 0/0, tsc 0, unit 6486 passed / 7 skipped, build OK.
@Kewton Kewton added the bug Something isn't working label May 15, 2026
@Kewton Kewton merged commit 2374f4c into develop May 15, 2026
5 checks passed
@Kewton Kewton deleted the feature/704-worktree branch May 15, 2026 09:11
Kewton added a commit that referenced this pull request May 17, 2026
… (#718)

* feat: 履歴(History)表示件数を50〜250件で選択可能にする (#701) (#702)

* feat(history): allow selecting history display limit (50-250)

Issue #701

- Add HistoryDisplayLimit selector (50/100/150/200/250) to HistoryPane header
- Persist selection in localStorage (commandmate:historyDisplayLimit)
- Raise messages API upper bound from 100 to MAX_MESSAGES_LIMIT (250)
- Centralize options/MAX/DEFAULT in src/config/history-display-config.ts
- Propagate selection through WorktreeDetailRefactored (PC + Mobile)
- worktreeApi.getMessages: optional limit argument
- useInfiniteMessages: default pageSize references DEFAULT_MESSAGES_LIMIT
- Tests: boundary (1/250/251/0/abc), DB getMessages limit=250,
  useInfiniteMessages hasMore at pageSize=250, config Single Source of Truth

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(history-display-config): derive MAX_MESSAGES_LIMIT from union type

- Replace brittle `as 250` literal cast with `HistoryDisplayLimit` type
  derivation so extending HISTORY_DISPLAY_LIMIT_OPTIONS no longer
  requires touching MAX_MESSAGES_LIMIT.
- Tighten comments and adjust wording for clarity.

Issue #701

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(claude-md): add history-display-config.ts to module reference (#701)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(detection): handle Claude v2.1.142 skill approval prompt summary line (#704) (#705)

Claude Code v2.1.142 renders trailing summary lines like "… +1 pending"
below the real options in skill approval prompts ("Use skill ...?").
NORMAL_OPTION_PATTERN was matching these as option N with label="pending",
which poisoned isValidPrecedingOption() and caused every real option
above (1./2./3.) to be rejected — collectedOptions ended up with length
< 2, returning no_prompt. As a result Yes/No UI never appeared and
Auto-Yes did not fire.

Defense-in-depth fix:
- S1: SUMMARY_LINE_PATTERN early-continue in Pass 2 loop (tight
  anchored pattern, no free `(.+)` capture, keyword whitelist
  pending|more) so legitimate labels mentioning these words are
  preserved.
- S2: CLAUDE_PROMPT_FOOTER_PATTERN trims effectiveEnd to the
  "Esc to cancel · Tab to amend" footer, putting the summary line
  outside the scan window entirely. Fallback-safe: no footer found =>
  effectiveEnd untouched, so Codex/Gemini/OpenCode/Copilot detection
  paths are unaffected.
- S3: 3-layer regression tests across prompt-detector, status-detector,
  and auto-yes-resolver covering the real Claude v2.1.142 fixture plus
  FP-suppression for option labels containing "pending"/"more".

Quality gates: lint 0/0, tsc 0, unit 6486 passed / 7 skipped, build OK.

* fix(files): preserve scroll position during tree refetch (#706) (#707)

- FileTreeView: limit full-screen loading/error to initial mount (rootItems empty)
- FileTreeView: add non-destructive refetch indicator (aria-live=polite) and error banner with retry button
- FileTreeView: extract reloadTreeWithExpandedDirs via useCallback + mountedRef guard
- WorktreeDetailRefactored: skip first-poll false-positive refresh by treating null prevTreeHashRef as baseline-only
- tests: add 5 new tests for refetch indicator / non-destructive error / retry path

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* perf(sync): parallelize scanMultipleRepositories with Promise.allSettled (#711) (#712)

Each `git worktree list` spawns an independent child process, so running
them sequentially via `for ... await` made the sync endpoint scale linearly
in the number of repositories. Switch to `Promise.allSettled(...map(...))`
so all repository scans run concurrently while preserving the prior "one
failure does not abort the rest" semantics. Logs continue to include
`repoPath` so the (now unordered) output can still be correlated.

The unit test mock for `child_process` had to be switched from auto-mock to
a factory mock: vitest's auto-mock preserves `util.promisify.custom` from
the real `exec`, which made `promisify(exec)` bypass `mockImplementation`
entirely. The factory returns a fresh `vi.fn()` without that symbol so
callback-style mocking works.

Closes #711

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* perf(sidebar): adaptive polling interval for active/idle transitions (#710) (#713)

useWorktreesCache のポーリング間隔(active=5s/idle=30s)が startPolling() 呼び出し時点で固定され、worktrees の active/idle 状態が遷移しても interval が更新されない問題を修正。

- currentIntervalRef: useRef<number | null> を追加し活動中の interval を追跡
- startPolling/stopPolling/hasActiveSession を useCallback でフック直下にリフトアップ
- worktrees-change useEffect で desired interval を再計算し差分時のみ startPolling を再実行
- document.hidden 中・初期化前(ref === null)はガードして no-op
- 既存の visibilitychange ハンドラの責務は維持

Closes #710

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* perf(sidebar): consolidate useWorktreesCache via Provider Context (#709) (#714)

Sessions ページが useWorktreesCache() を直接呼び出していたため、
WorktreesCacheProvider と合わせて /api/worktrees ポーリングが2系統
並行で動いていた問題を解消。

- WorktreesCacheProvider に WorktreesCacheContext を新設し
  useWorktreesCacheContext() フックを export
- src/app/sessions/page.tsx を Context 経由参照に変更
- useWorktreesCache フック自体は不変 (テスト/単体利用は維持)
- 新規テスト tests/unit/components/providers/WorktreesCacheProvider.test.tsx

Closes #709

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* perf(db): add composite index including role column on chat_messages (#708) (#715)

chat_messages の相関サブクエリ (role='assistant'/'user' AND archived=0 の
MAX(timestamp)/ORDER BY timestamp DESC LIMIT 1) が、既存
idx_messages_archived(worktree_id, archived, timestamp DESC) では role 列を
含まないため行スキャンに陥り、件数増加に伴い線形劣化していた。

Migration v32 で複合インデックス
  idx_messages_worktree_role_archived_time(worktree_id, role, archived, timestamp DESC)
を新設し、既存 idx_messages_archived を DROP。
他の role なしクエリ (getMessages, getLastMessage, deleteAllMessages) は
idx_messages_worktree_time(worktree_id, timestamp DESC) で従来同等に動作する。

- src/lib/db/migrations/v32-add-messages-role-composite-index.ts: 新規
- src/lib/db/migrations/index.ts / runner.ts: v32 登録、CURRENT_SCHEMA_VERSION = 32
- src/lib/db/init-db.ts: 初期化パスを新インデックスへ置換
- tests/unit/lib/db-migrations.test.ts: v32 describe ブロック追加、
  v22 旧インデックスアサーションを rollback to 31 経由に修正
- tests/unit/lib/db/chat-db-explain-plan.test.ts: EXPLAIN QUERY PLAN
  で対象4クエリが新インデックスを使うことを検証

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: Worktree詳細 HistoryPaneにメッセージテキスト検索機能を追加 (#716) (#717)

* feat(history): add text search to HistoryPane (#716)

Adds a per-pane text search bar to Worktree HistoryPane with namespace-isolated
CSS Custom Highlight API rendering so it can coexist with the TerminalPane
search (Issue #47).

Key design choices (3-stage reviewed):
- Preserve OCP: existing applyTerminalHighlights / clearTerminalHighlights
  signatures unchanged. New applyHistoryHighlights / clearHistoryHighlights
  share the internal engine via HighlightNamespace.
- ConversationPairCard receives no new props; only data-message-id is added so
  memo is preserved. useConversationHistory is untouched.
- HistoryPane manages autoExpandedIds internally to force-expand truncated
  assistant messages that contain hits, then applies highlights with a strict
  useLayoutEffect order (save scroll → restore scroll (skipped during search)
  → autoExpandedIds → applyHistoryHighlights) so textContent stays aligned
  with message.content.
- Debounce (300ms), min-query length (2), and max-matches (500) are shared
  via newly exported SEARCH_DEBOUNCE_MS / SEARCH_MIN_QUERY_LENGTH /
  TERMINAL_SEARCH_MAX_MATCHES from useTerminalSearch.

Tests: 47 new cases across useHistorySearch, HistorySearchBar, terminal-
highlight, and HistoryPane. Full unit suite: 6558 passed / 0 failed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(history-search): DRY debounce timer + unify selector escape (#716)

Small post-implementation cleanups identified during the refactoring phase.
No behavior changes; all 6558 unit tests still pass.

useHistorySearch.ts:
- Extract clearDebounceTimer() helper (DRY: clearTimeout/null pattern was
  duplicated across scheduleSearch / closeSearch / onCompositionStart /
  unmount cleanup).

HistoryPane.tsx:
- Replace custom escapeAttrValue() with native CSS.escape() via a small
  findMessageElement() helper. Removes a homemade escape function that
  duplicated standards-track functionality and unifies the two
  inconsistent selector-building sites (one used CSS.escape, the other
  the custom helper).
- Capture the current match element during the highlight loop so the
  scrollIntoView path no longer issues a second querySelector for the
  same node.

Constraints preserved:
- terminal-highlight.ts public API untouched (OCP).
- ConversationPairCard props unchanged (memo preserved).
- Effect declaration order (1)->(2)->(3)->(4) and HISTORY_SEARCH_NAMESPACE
  isolation unchanged (design policy v1.2 core).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(history): update CLAUDE.md and implementation-history for #716

Add HistoryPane text search feature entry and document new modules:
useHistorySearch hook, HistorySearchBar component, terminal-highlight
namespace separation (HISTORY_SEARCH_NAMESPACE), and data-message-id
attribution in ConversationPairCard.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Kewton added a commit that referenced this pull request May 27, 2026
* feat: 履歴(History)表示件数を50〜250件で選択可能にする (#701) (#702)

* feat(history): allow selecting history display limit (50-250)

Issue #701

- Add HistoryDisplayLimit selector (50/100/150/200/250) to HistoryPane header
- Persist selection in localStorage (commandmate:historyDisplayLimit)
- Raise messages API upper bound from 100 to MAX_MESSAGES_LIMIT (250)
- Centralize options/MAX/DEFAULT in src/config/history-display-config.ts
- Propagate selection through WorktreeDetailRefactored (PC + Mobile)
- worktreeApi.getMessages: optional limit argument
- useInfiniteMessages: default pageSize references DEFAULT_MESSAGES_LIMIT
- Tests: boundary (1/250/251/0/abc), DB getMessages limit=250,
  useInfiniteMessages hasMore at pageSize=250, config Single Source of Truth

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(history-display-config): derive MAX_MESSAGES_LIMIT from union type

- Replace brittle `as 250` literal cast with `HistoryDisplayLimit` type
  derivation so extending HISTORY_DISPLAY_LIMIT_OPTIONS no longer
  requires touching MAX_MESSAGES_LIMIT.
- Tighten comments and adjust wording for clarity.

Issue #701

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(claude-md): add history-display-config.ts to module reference (#701)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(detection): handle Claude v2.1.142 skill approval prompt summary line (#704) (#705)

Claude Code v2.1.142 renders trailing summary lines like "… +1 pending"
below the real options in skill approval prompts ("Use skill ...?").
NORMAL_OPTION_PATTERN was matching these as option N with label="pending",
which poisoned isValidPrecedingOption() and caused every real option
above (1./2./3.) to be rejected — collectedOptions ended up with length
< 2, returning no_prompt. As a result Yes/No UI never appeared and
Auto-Yes did not fire.

Defense-in-depth fix:
- S1: SUMMARY_LINE_PATTERN early-continue in Pass 2 loop (tight
  anchored pattern, no free `(.+)` capture, keyword whitelist
  pending|more) so legitimate labels mentioning these words are
  preserved.
- S2: CLAUDE_PROMPT_FOOTER_PATTERN trims effectiveEnd to the
  "Esc to cancel · Tab to amend" footer, putting the summary line
  outside the scan window entirely. Fallback-safe: no footer found =>
  effectiveEnd untouched, so Codex/Gemini/OpenCode/Copilot detection
  paths are unaffected.
- S3: 3-layer regression tests across prompt-detector, status-detector,
  and auto-yes-resolver covering the real Claude v2.1.142 fixture plus
  FP-suppression for option labels containing "pending"/"more".

Quality gates: lint 0/0, tsc 0, unit 6486 passed / 7 skipped, build OK.

* fix(files): preserve scroll position during tree refetch (#706) (#707)

- FileTreeView: limit full-screen loading/error to initial mount (rootItems empty)
- FileTreeView: add non-destructive refetch indicator (aria-live=polite) and error banner with retry button
- FileTreeView: extract reloadTreeWithExpandedDirs via useCallback + mountedRef guard
- WorktreeDetailRefactored: skip first-poll false-positive refresh by treating null prevTreeHashRef as baseline-only
- tests: add 5 new tests for refetch indicator / non-destructive error / retry path

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* perf(sync): parallelize scanMultipleRepositories with Promise.allSettled (#711) (#712)

Each `git worktree list` spawns an independent child process, so running
them sequentially via `for ... await` made the sync endpoint scale linearly
in the number of repositories. Switch to `Promise.allSettled(...map(...))`
so all repository scans run concurrently while preserving the prior "one
failure does not abort the rest" semantics. Logs continue to include
`repoPath` so the (now unordered) output can still be correlated.

The unit test mock for `child_process` had to be switched from auto-mock to
a factory mock: vitest's auto-mock preserves `util.promisify.custom` from
the real `exec`, which made `promisify(exec)` bypass `mockImplementation`
entirely. The factory returns a fresh `vi.fn()` without that symbol so
callback-style mocking works.

Closes #711

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* perf(sidebar): adaptive polling interval for active/idle transitions (#710) (#713)

useWorktreesCache のポーリング間隔(active=5s/idle=30s)が startPolling() 呼び出し時点で固定され、worktrees の active/idle 状態が遷移しても interval が更新されない問題を修正。

- currentIntervalRef: useRef<number | null> を追加し活動中の interval を追跡
- startPolling/stopPolling/hasActiveSession を useCallback でフック直下にリフトアップ
- worktrees-change useEffect で desired interval を再計算し差分時のみ startPolling を再実行
- document.hidden 中・初期化前(ref === null)はガードして no-op
- 既存の visibilitychange ハンドラの責務は維持

Closes #710

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* perf(sidebar): consolidate useWorktreesCache via Provider Context (#709) (#714)

Sessions ページが useWorktreesCache() を直接呼び出していたため、
WorktreesCacheProvider と合わせて /api/worktrees ポーリングが2系統
並行で動いていた問題を解消。

- WorktreesCacheProvider に WorktreesCacheContext を新設し
  useWorktreesCacheContext() フックを export
- src/app/sessions/page.tsx を Context 経由参照に変更
- useWorktreesCache フック自体は不変 (テスト/単体利用は維持)
- 新規テスト tests/unit/components/providers/WorktreesCacheProvider.test.tsx

Closes #709

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* perf(db): add composite index including role column on chat_messages (#708) (#715)

chat_messages の相関サブクエリ (role='assistant'/'user' AND archived=0 の
MAX(timestamp)/ORDER BY timestamp DESC LIMIT 1) が、既存
idx_messages_archived(worktree_id, archived, timestamp DESC) では role 列を
含まないため行スキャンに陥り、件数増加に伴い線形劣化していた。

Migration v32 で複合インデックス
  idx_messages_worktree_role_archived_time(worktree_id, role, archived, timestamp DESC)
を新設し、既存 idx_messages_archived を DROP。
他の role なしクエリ (getMessages, getLastMessage, deleteAllMessages) は
idx_messages_worktree_time(worktree_id, timestamp DESC) で従来同等に動作する。

- src/lib/db/migrations/v32-add-messages-role-composite-index.ts: 新規
- src/lib/db/migrations/index.ts / runner.ts: v32 登録、CURRENT_SCHEMA_VERSION = 32
- src/lib/db/init-db.ts: 初期化パスを新インデックスへ置換
- tests/unit/lib/db-migrations.test.ts: v32 describe ブロック追加、
  v22 旧インデックスアサーションを rollback to 31 経由に修正
- tests/unit/lib/db/chat-db-explain-plan.test.ts: EXPLAIN QUERY PLAN
  で対象4クエリが新インデックスを使うことを検証

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: Worktree詳細 HistoryPaneにメッセージテキスト検索機能を追加 (#716) (#717)

* feat(history): add text search to HistoryPane (#716)

Adds a per-pane text search bar to Worktree HistoryPane with namespace-isolated
CSS Custom Highlight API rendering so it can coexist with the TerminalPane
search (Issue #47).

Key design choices (3-stage reviewed):
- Preserve OCP: existing applyTerminalHighlights / clearTerminalHighlights
  signatures unchanged. New applyHistoryHighlights / clearHistoryHighlights
  share the internal engine via HighlightNamespace.
- ConversationPairCard receives no new props; only data-message-id is added so
  memo is preserved. useConversationHistory is untouched.
- HistoryPane manages autoExpandedIds internally to force-expand truncated
  assistant messages that contain hits, then applies highlights with a strict
  useLayoutEffect order (save scroll → restore scroll (skipped during search)
  → autoExpandedIds → applyHistoryHighlights) so textContent stays aligned
  with message.content.
- Debounce (300ms), min-query length (2), and max-matches (500) are shared
  via newly exported SEARCH_DEBOUNCE_MS / SEARCH_MIN_QUERY_LENGTH /
  TERMINAL_SEARCH_MAX_MATCHES from useTerminalSearch.

Tests: 47 new cases across useHistorySearch, HistorySearchBar, terminal-
highlight, and HistoryPane. Full unit suite: 6558 passed / 0 failed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(history-search): DRY debounce timer + unify selector escape (#716)

Small post-implementation cleanups identified during the refactoring phase.
No behavior changes; all 6558 unit tests still pass.

useHistorySearch.ts:
- Extract clearDebounceTimer() helper (DRY: clearTimeout/null pattern was
  duplicated across scheduleSearch / closeSearch / onCompositionStart /
  unmount cleanup).

HistoryPane.tsx:
- Replace custom escapeAttrValue() with native CSS.escape() via a small
  findMessageElement() helper. Removes a homemade escape function that
  duplicated standards-track functionality and unifies the two
  inconsistent selector-building sites (one used CSS.escape, the other
  the custom helper).
- Capture the current match element during the highlight loop so the
  scrollIntoView path no longer issues a second querySelector for the
  same node.

Constraints preserved:
- terminal-highlight.ts public API untouched (OCP).
- ConversationPairCard props unchanged (memo preserved).
- Effect declaration order (1)->(2)->(3)->(4) and HISTORY_SEARCH_NAMESPACE
  isolation unchanged (design policy v1.2 core).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(history): update CLAUDE.md and implementation-history for #716

Add HistoryPane text search feature entry and document new modules:
useHistorySearch hook, HistorySearchBar component, terminal-highlight
namespace separation (HISTORY_SEARCH_NAMESPACE), and data-message-id
attribution in ConversationPairCard.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(executor): diagnose execFile maxBuffer errors and bump limit to 10MB (#719) (#720)

Node v24 で execFile の `ERR_CHILD_PROCESS_STDIO_MAXBUFFER` が
`failed/exit_code=null` のまま原因不明で終了していた問題を修正。

- error.code が文字列のとき parseInt で NaN → null になっていたのを
  `typeof errCode === 'number'` での厳密判定に変更(型安全)。
- `stdout || stderr || error.message` の優先順位で巨大 stdout が
  error.message を呑み込んでいたため、必ず先頭に Error/Code/Signal/Reason
  サマリを置き、続けて --- stdout --- / --- stderr --- セクションで
  両方を保存するように変更。
- ERR_CHILD_PROCESS_STDIO_MAXBUFFER の場合は専用 Reason 行を付加し、
  timeout ではなく failed として扱う(時間切れと出力過多は意味が異なる)。
- MAX_OUTPUT_SIZE を 1MB → 10MB に暫定緩和(spawn + rolling buffer 化は
  別 Issue で根本対策)。

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant