aiwcli 0.12.3 → 0.12.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/bin/dev.cmd +3 -3
  2. package/bin/dev.js +16 -16
  3. package/bin/run.cmd +3 -3
  4. package/bin/run.js +21 -21
  5. package/dist/commands/branch.js +7 -2
  6. package/dist/lib/bmad-installer.js +37 -37
  7. package/dist/lib/terminal.d.ts +2 -0
  8. package/dist/lib/terminal.js +57 -7
  9. package/dist/templates/CLAUDE.md +205 -205
  10. package/dist/templates/_shared/.claude/commands/handoff-resume.md +12 -64
  11. package/dist/templates/_shared/.claude/commands/handoff.md +12 -198
  12. package/dist/templates/_shared/.claude/settings.json +65 -65
  13. package/dist/templates/_shared/.codex/workflows/handoff.md +226 -226
  14. package/dist/templates/_shared/.windsurf/workflows/handoff.md +226 -226
  15. package/dist/templates/_shared/handoff-system/CLAUDE.md +421 -0
  16. package/dist/templates/_shared/{lib-ts/handoff → handoff-system/lib}/document-generator.ts +215 -216
  17. package/dist/templates/_shared/{lib-ts/handoff → handoff-system/lib}/handoff-reader.ts +157 -158
  18. package/dist/templates/_shared/{scripts → handoff-system/scripts}/resume_handoff.ts +373 -373
  19. package/dist/templates/_shared/{scripts → handoff-system/scripts}/save_handoff.ts +469 -358
  20. package/dist/templates/_shared/handoff-system/workflows/handoff-resume.md +66 -0
  21. package/dist/templates/_shared/{workflows → handoff-system/workflows}/handoff.md +254 -254
  22. package/dist/templates/_shared/hooks-ts/_utils/git-state.ts +2 -2
  23. package/dist/templates/_shared/hooks-ts/archive_plan.ts +159 -159
  24. package/dist/templates/_shared/hooks-ts/context_monitor.ts +147 -147
  25. package/dist/templates/_shared/hooks-ts/file-suggestion.ts +128 -128
  26. package/dist/templates/_shared/hooks-ts/pre_compact.ts +49 -49
  27. package/dist/templates/_shared/hooks-ts/session_end.ts +196 -183
  28. package/dist/templates/_shared/hooks-ts/session_start.ts +163 -151
  29. package/dist/templates/_shared/hooks-ts/task_create_capture.ts +48 -48
  30. package/dist/templates/_shared/hooks-ts/task_update_capture.ts +74 -74
  31. package/dist/templates/_shared/hooks-ts/user_prompt_submit.ts +93 -93
  32. package/dist/templates/_shared/lib-ts/CLAUDE.md +367 -367
  33. package/dist/templates/_shared/lib-ts/base/atomic-write.ts +138 -138
  34. package/dist/templates/_shared/lib-ts/base/constants.ts +303 -303
  35. package/dist/templates/_shared/lib-ts/base/git-state.ts +58 -58
  36. package/dist/templates/_shared/lib-ts/base/hook-utils.ts +582 -582
  37. package/dist/templates/_shared/lib-ts/base/inference.ts +301 -301
  38. package/dist/templates/_shared/lib-ts/base/logger.ts +247 -247
  39. package/dist/templates/_shared/lib-ts/base/state-io.ts +202 -130
  40. package/dist/templates/_shared/lib-ts/base/stop-words.ts +184 -184
  41. package/dist/templates/_shared/lib-ts/base/subprocess-utils.ts +56 -0
  42. package/dist/templates/_shared/lib-ts/base/utils.ts +184 -184
  43. package/dist/templates/_shared/lib-ts/context/context-formatter.ts +566 -560
  44. package/dist/templates/_shared/lib-ts/context/context-selector.ts +524 -515
  45. package/dist/templates/_shared/lib-ts/context/context-store.ts +712 -668
  46. package/dist/templates/_shared/lib-ts/context/plan-manager.ts +312 -312
  47. package/dist/templates/_shared/lib-ts/context/task-tracker.ts +185 -185
  48. package/dist/templates/_shared/lib-ts/package.json +20 -20
  49. package/dist/templates/_shared/lib-ts/templates/formatters.ts +102 -102
  50. package/dist/templates/_shared/lib-ts/templates/plan-context.ts +58 -58
  51. package/dist/templates/_shared/lib-ts/tsconfig.json +13 -13
  52. package/dist/templates/_shared/lib-ts/types.ts +186 -180
  53. package/dist/templates/_shared/scripts/resolve_context.ts +33 -33
  54. package/dist/templates/_shared/scripts/status_line.ts +690 -690
  55. package/dist/templates/cc-native/.claude/commands/{rlm → cc-native/rlm}/ask.md +136 -136
  56. package/dist/templates/cc-native/.claude/commands/{rlm → cc-native/rlm}/index.md +21 -21
  57. package/dist/templates/cc-native/.claude/commands/{rlm → cc-native/rlm}/overview.md +56 -56
  58. package/dist/templates/cc-native/.claude/commands/cc-native/specdev.md +10 -10
  59. package/dist/templates/cc-native/.windsurf/workflows/cc-native/fix.md +8 -8
  60. package/dist/templates/cc-native/.windsurf/workflows/cc-native/implement.md +8 -8
  61. package/dist/templates/cc-native/.windsurf/workflows/cc-native/research.md +8 -8
  62. package/dist/templates/cc-native/CC-NATIVE-README.md +189 -189
  63. package/dist/templates/cc-native/TEMPLATE-SCHEMA.md +304 -304
  64. package/dist/templates/cc-native/_cc-native/agents/CLAUDE.md +143 -143
  65. package/dist/templates/cc-native/_cc-native/agents/PLAN-ORCHESTRATOR.md +213 -213
  66. package/dist/templates/cc-native/_cc-native/agents/plan-questions/PLAN-QUESTIONER.md +70 -70
  67. package/dist/templates/cc-native/_cc-native/cc-native.config.json +96 -96
  68. package/dist/templates/cc-native/_cc-native/hooks/CLAUDE.md +247 -247
  69. package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.ts +76 -76
  70. package/dist/templates/cc-native/_cc-native/hooks/enhance_plan_post_subagent.ts +54 -54
  71. package/dist/templates/cc-native/_cc-native/hooks/enhance_plan_post_write.ts +51 -51
  72. package/dist/templates/cc-native/_cc-native/hooks/mark_questions_asked.ts +53 -53
  73. package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.ts +61 -61
  74. package/dist/templates/cc-native/_cc-native/lib-ts/agent-selection.ts +163 -163
  75. package/dist/templates/cc-native/_cc-native/lib-ts/aggregate-agents.ts +156 -156
  76. package/dist/templates/cc-native/_cc-native/lib-ts/artifacts/format.ts +597 -597
  77. package/dist/templates/cc-native/_cc-native/lib-ts/artifacts/index.ts +26 -26
  78. package/dist/templates/cc-native/_cc-native/lib-ts/artifacts/tracker.ts +107 -107
  79. package/dist/templates/cc-native/_cc-native/lib-ts/artifacts/write.ts +119 -119
  80. package/dist/templates/cc-native/_cc-native/lib-ts/artifacts.ts +21 -21
  81. package/dist/templates/cc-native/_cc-native/lib-ts/cc-native-state.ts +319 -319
  82. package/dist/templates/cc-native/_cc-native/lib-ts/cli-output-parser.ts +144 -144
  83. package/dist/templates/cc-native/_cc-native/lib-ts/config.ts +57 -57
  84. package/dist/templates/cc-native/_cc-native/lib-ts/constants.ts +83 -83
  85. package/dist/templates/cc-native/_cc-native/lib-ts/corroboration.ts +119 -119
  86. package/dist/templates/cc-native/_cc-native/lib-ts/debug.ts +79 -79
  87. package/dist/templates/cc-native/_cc-native/lib-ts/graduation.ts +132 -132
  88. package/dist/templates/cc-native/_cc-native/lib-ts/index.ts +116 -116
  89. package/dist/templates/cc-native/_cc-native/lib-ts/json-parser.ts +168 -168
  90. package/dist/templates/cc-native/_cc-native/lib-ts/orchestrator.ts +70 -70
  91. package/dist/templates/cc-native/_cc-native/lib-ts/output-builder.ts +130 -130
  92. package/dist/templates/cc-native/_cc-native/lib-ts/plan-discovery.ts +80 -80
  93. package/dist/templates/cc-native/_cc-native/lib-ts/plan-enhancement.ts +41 -41
  94. package/dist/templates/cc-native/_cc-native/lib-ts/plan-questions.ts +101 -101
  95. package/dist/templates/cc-native/_cc-native/lib-ts/review-pipeline.ts +511 -511
  96. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/agent.ts +71 -71
  97. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/base/base-agent.ts +217 -217
  98. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/index.ts +12 -12
  99. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/claude-agent.ts +66 -65
  100. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/codex-agent.ts +184 -184
  101. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/gemini-agent.ts +39 -39
  102. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/orchestrator-claude-agent.ts +196 -195
  103. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/schemas.ts +201 -201
  104. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/types.ts +21 -21
  105. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/CLAUDE.md +480 -480
  106. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/embedding-indexer.ts +287 -287
  107. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/hyde.ts +148 -148
  108. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/index.ts +54 -54
  109. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/logger.ts +58 -58
  110. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/ollama-client.ts +208 -208
  111. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/retrieval-pipeline.ts +460 -460
  112. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/transcript-indexer.ts +446 -447
  113. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/transcript-loader.ts +280 -280
  114. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/transcript-searcher.ts +274 -274
  115. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/types.ts +201 -201
  116. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/vector-store.ts +278 -278
  117. package/dist/templates/cc-native/_cc-native/lib-ts/settings.ts +184 -184
  118. package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +275 -275
  119. package/dist/templates/cc-native/_cc-native/lib-ts/tsconfig.json +18 -18
  120. package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +329 -329
  121. package/dist/templates/cc-native/_cc-native/lib-ts/verdict.ts +72 -72
  122. package/dist/templates/cc-native/_cc-native/workflows/specdev.md +9 -9
  123. package/oclif.manifest.json +1 -1
  124. package/package.json +108 -108
  125. package/dist/templates/cc-native/_cc-native/lib-ts/nul +0 -3
@@ -1,367 +1,367 @@
1
- # Shared TypeScript Library
2
-
3
- **Location:** `_shared/lib-ts/` — cross-method infrastructure used by ALL templates.
4
-
5
- **One import gets you started:**
6
- ```typescript
7
- import { loadHookInput, runHook, logInfo, emitContext } from "../lib-ts/base/hook-utils.js";
8
- ```
9
-
10
- `hook-utils.ts` re-exports the most-used functions from `logger.ts`, `constants.ts`, and `context-store.ts`. Start here. Only import from deeper modules when you need specific capabilities.
11
-
12
- **Import direction:** Hooks --> method lib --> `_shared/lib-ts/`. Never the reverse.
13
-
14
- ---
15
-
16
- ## Critical Rules
17
-
18
- These cause silent failures or UI noise when violated:
19
-
20
- - **Entry point:** Every hook MUST use `runHook()` or `runHookAsync()` — never bare `main()` or `process.exit()`
21
- - **stdout is sacred:** Only hook JSON output goes to stdout. Use logger functions for diagnostics, never `console.log()` or `print()`
22
- - **stderr is opt-in:** `logDebug/logInfo/logWarn/logError` write to file only. Use `logBlocking()` when you NEED stderr visibility
23
- - **Catch non-critical errors locally:** Uncaught errors bubble to `runHook` which writes to stderr, showing "hook error" in the UI even on exit 0
24
- - **No reverse imports:** Never import from method lib (e.g., `_cc-native/lib/`) into shared lib
25
-
26
- ---
27
-
28
- ## Hook Skeleton
29
-
30
- Copy this for new hooks:
31
-
32
- ```typescript
33
- #!/usr/bin/env bun
34
- import { loadHookInput, runHook, logDebug, logInfo, emitContext } from "../lib-ts/base/hook-utils.js";
35
-
36
- function main(): void {
37
- const payload = loadHookInput();
38
- if (!payload) return;
39
-
40
- const sessionId = payload.session_id;
41
- if (!sessionId) return;
42
-
43
- // Your hook logic here...
44
-
45
- emitContext("Context visible to Claude");
46
- }
47
-
48
- runHook(main, "my_hook_name");
49
- ```
50
-
51
- For async hooks (AI inference, network calls):
52
-
53
- ```typescript
54
- import { runHookAsync } from "../lib-ts/base/hook-utils.js";
55
-
56
- async function asyncMain(): Promise<void> {
57
- // await something...
58
- }
59
-
60
- runHookAsync(asyncMain, "my_async_hook");
61
- ```
62
-
63
- ---
64
-
65
- ## Logging
66
-
67
- All logging goes to `_output/hook-log.jsonl`. stderr visibility is opt-in.
68
-
69
- | Tier | Function | Visible in UI? | Use When |
70
- |------|----------|---------------|----------|
71
- | File-only | `logDebug()` / `logInfo()` / `logWarn()` / `logError()` | No | 99% of logging: diagnostics, state changes, non-critical errors |
72
- | Blocking | `logBlocking()` | Yes (stderr) | The hook found a real problem the user or Claude must see |
73
- | Unhandled | `logHookError()` | Yes (stderr) | Reserved for `runHook` crash handler — do not call directly |
74
- | Terminal | `eprint()` | Yes (raw stderr) | Usage help, progress indicators — not logged to JSONL |
75
-
76
- ```typescript
77
- import { logDebug, logInfo, logWarn, logBlocking } from "../lib-ts/base/hook-utils.js";
78
-
79
- logInfo("my_hook", "Session started"); // file only
80
- logWarn("my_hook", `Fallback used: ${reason}`); // file only
81
- logBlocking("my_hook", "Critical: state corrupt"); // shows in UI
82
- ```
83
-
84
- ---
85
-
86
- ## Hook Output — Communication Channels
87
-
88
- Hooks have multiple channels back to the session. Pick the right one:
89
-
90
- | Want to... | Function | Who sees it |
91
- |------------|----------|-------------|
92
- | **Block (any event)** | `emitBlock(reason, context?)` | Claude + user — auto-dispatches to correct mechanism |
93
- | Block tool (PreToolUse only) | `emitContextAndBlock(context, reason)` | Claude + user (denial reason prominent) |
94
- | Return message, don't block | `emitContext(context)` | Claude + user (in transcript) |
95
- | Log only (diagnostics) | `logInfo()` / `logWarn()` / etc. | Nobody in session — file only |
96
-
97
- **There is no way to show something to the user but hide it from Claude, or vice versa.** Both `emitContext()` and `emitContextAndBlock()` produce output visible to both.
98
-
99
- ### Universal Blocking: `emitBlock()` (RECOMMENDED)
100
-
101
- ```typescript
102
- emitBlock("Reason for blocking", "Optional detailed context");
103
- ```
104
-
105
- Auto-detects the correct blocking mechanism from the current hook event:
106
-
107
- | Event | Mechanism | Dispatches to |
108
- |-------|-----------|---------------|
109
- | **PreToolUse** | `permissionDecision: "deny"` in hookSpecificOutput | `emitContextAndBlock()` |
110
- | **UserPromptSubmit** | Top-level `{ decision: "block", reason }` | `emitBlockPrompt()` |
111
- | **PostToolUse / PostToolUseFailure** | Exit code 2 + stderr | `emitBlockViaExit()` |
112
- | **Stop / SubagentStop** | Top-level `{ decision: "block", reason }` | `emitBlockTopLevel()` |
113
- | **PermissionRequest** | `{ decision: { behavior: "deny" } }` | `emitPermissionDecision()` |
114
- | **SessionStart / Notification / SubagentStart / SessionEnd** | No blocking mechanism — warns and no-ops | — |
115
-
116
- **Use `emitBlock()` for all new hooks.** The per-pattern functions below exist for advanced use cases only.
117
-
118
- ### Channel 1: Block + Context (PreToolUse only)
119
-
120
- ```typescript
121
- emitContextAndBlock(
122
- "Detailed feedback Claude sees", // additionalContext
123
- "Short reason for the block" // permissionDecisionReason
124
- );
125
- // No SystemExit needed — permissionDecision:"deny" with exit 0 is sufficient.
126
- // Warns if called from non-PreToolUse events (output will be silently rejected by Claude Code).
127
- ```
128
-
129
- The tool call is **prevented from executing**. Only works for PreToolUse hooks.
130
-
131
- ### Channel 2: Block Prompt Submission (UserPromptSubmit only)
132
-
133
- ```typescript
134
- emitBlockPrompt("Reason the prompt was blocked", "Optional context for Claude");
135
- ```
136
-
137
- Emits top-level `{ decision: "block", reason }`. The prompt is rejected before Claude processes it.
138
-
139
- ### Channel 3: Block via Exit Code (PostToolUse / PostToolUseFailure)
140
-
141
- ```typescript
142
- emitBlockViaExit("Reason for blocking", "Optional context prepended to stderr");
143
- // Throws SystemExit:2 — handled by runHook/runHookAsync
144
- // NOTE: Exit 2 causes Claude Code to ignore all JSON stdout — only stderr matters
145
- ```
146
-
147
- ### Channel 4: Block Stop/SubagentStop
148
-
149
- ```typescript
150
- emitBlockTopLevel("Reason to prevent stopping");
151
- ```
152
-
153
- ### Channel 5: PermissionRequest Decision
154
-
155
- ```typescript
156
- emitPermissionDecision("deny", { message: "Why denied" });
157
- emitPermissionDecision("allow", { updatedInput: { /* modified input */ } });
158
- ```
159
-
160
- ### Channel 6: Non-blocking Context (any hook event)
161
-
162
- ```typescript
163
- emitContext("Information added to Claude's context");
164
- ```
165
-
166
- The tool call / session continues normally. Works for PreToolUse, PostToolUse, UserPromptSubmit, SessionStart, Notification, SubagentStart.
167
-
168
- ### Channel 7: Log-only (diagnostics)
169
-
170
- ```typescript
171
- logInfo("my_hook", "Processing started"); // File only
172
- logWarn("my_hook", `Fallback used: ${why}`); // File only
173
- ```
174
-
175
- Nobody in the session sees this. Written to `_output/hook-log.jsonl` for debugging.
176
-
177
- ### Hook Output Logging
178
-
179
- Both `emitContext()` and `emitContextAndBlock()` automatically log their output to `_output/hook-log.jsonl` as `HOOK_OUTPUT` entries. This captures exactly what was sent to Claude via stdout, closing the visibility gap where the agent sees injected context the user doesn't.
180
-
181
- - **Log level:** `info` — visible unless `HOOK_LOG_LEVEL=warn`
182
- - **`msg` field:** Scannable summary: `HOOK_OUTPUT [context] 842 chars` or `HOOK_OUTPUT [block] 340 chars, reason="..."`
183
- - **`data` field:** Full payload including `additionalContext` and `blockReason` (for blocks)
184
- - **Controlled by:** Existing `HOOK_LOG_LEVEL` and `HOOK_LOG_DISABLE` env vars
185
- - **No hook changes needed:** Logging happens inside the emit functions themselves
186
-
187
- ### Exit codes and JSON
188
-
189
- | Exit Code | JSON Parsed? | Effect |
190
- |-----------|-------------|--------|
191
- | **0** | Yes | Normal — `hookSpecificOutput` processed |
192
- | **2** | No | Blocking error — JSON ignored, stderr fed to Claude |
193
- | **Other** | No | Non-blocking error — stderr shown in verbose mode |
194
-
195
- You cannot mix exit 2 with JSON decisions. Pick one: exit 0 + JSON, or exit 2 + stderr.
196
-
197
- ### hookSpecificOutput fields by event type
198
-
199
- | Event | `additionalContext` | `permissionDecision` | `permissionDecisionReason` | Other |
200
- |-------|:--:|:--:|:--:|-------|
201
- | **PreToolUse** | Y | Y (allow/deny/ask) | Y | `updatedInput` |
202
- | **PostToolUse** | Y | - | - | `updatedMCPToolOutput` (MCP only) |
203
- | **UserPromptSubmit** | Y | - | - | top-level `decision: "block"` |
204
- | **SessionStart** | Y | - | - | — |
205
- | **Notification** | Y | - | - | — |
206
- | **SubagentStart** | Y | - | - | — |
207
- | **Stop** | - | - | - | top-level `decision`, `reason` |
208
- | **SessionEnd** | - | - | - | — |
209
-
210
- **Invalid fields cause silent rejection of the entire output.** No error, no feedback. Conversely, **missing `hookEventName` also causes silent rejection** — see "Hook API: Critical Learnings" below.
211
-
212
- ### Special case: fileSuggestion
213
-
214
- The `fileSuggestion` settings command is NOT a hook — it uses a different protocol. It outputs a plain JSON array to stdout (e.g., `console.log(JSON.stringify(paths))`). Do not use `emitContext()` for fileSuggestion.
215
-
216
- ---
217
-
218
- ## Hook API: Critical Learnings (Verified 2026-02-11)
219
-
220
- These findings were verified through systematic testing. They document Claude Code's actual behavior, which sometimes differs from what the docs suggest.
221
-
222
- ### hookEventName is REQUIRED (CC 2.1.39+)
223
-
224
- Claude Code validates `hookSpecificOutput` using a Zod discriminated union keyed on `hookEventName`. If this field is missing:
225
-
226
- - The entire hook output is silently rejected — no error, no feedback
227
- - `permissionDecision: "deny"` is never processed
228
- - The hook appears to "not work" even though it runs successfully
229
-
230
- **You don't need to handle this manually.** `emitContext()` and `emitContextAndBlock()` auto-detect `hookEventName` from the stdin payload (via `_lastHookEvent`, set by `loadHookInput()`/`runHook()`). This works because hooks are synchronous single-process executions — each `bun` process has its own memory, so there's no concurrency risk between sessions.
231
-
232
- **If auto-detection fails** (e.g., `loadHookInput()` wasn't called), `hookEventName` is omitted and the output will be silently rejected. This is why `runHook()`/`runHookAsync()` is mandatory — it calls `_earlyReadInput()` first, guaranteeing `_lastHookEvent` is populated.
233
-
234
- ### Exit Code Behavior (Tested)
235
-
236
- | Exit Code | JSON Parsed? | Blocks Tool? | What Claude Sees | Tested? |
237
- |-----------|-------------|-------------|------------------|---------|
238
- | **0** + deny JSON | Yes | Yes (PreToolUse only) | `additionalContext` + denial reason | Yes |
239
- | **0** + context JSON | Yes | No | `additionalContext` in transcript | Yes |
240
- | **1** | No | No | stderr in verbose mode only | Yes |
241
- | **2** | No | Yes (any event) | stderr fed as system-reminder | Yes |
242
-
243
- **Key insight:** Exit 0 + `permissionDecision: "deny"` is the correct way to block a tool. Exit 2 is a blunt instrument — it ignores your JSON and feeds raw stderr to Claude. Use exit 0 + deny for clean blocking with structured feedback.
244
-
245
- ### ExitPlanMode: Not Special-Cased
246
-
247
- Early testing suggested ExitPlanMode was "immune" to PreToolUse deny. **This was wrong.** The actual issue was missing `hookEventName` — the Zod validator silently rejected the deny output.
248
-
249
- **With `hookEventName` included:**
250
- - PreToolUse `permissionDecision: "deny"` (exit 0) → **blocks ExitPlanMode**, no dialog appears, session stays in plan mode
251
- - `emitContextAndBlock()` handles this automatically via auto-detection
252
-
253
- **Without `hookEventName` (the bug):**
254
- - Deny silently rejected → dialog appeared → looked like ExitPlanMode was special-cased
255
- - Exit 2 also appeared to "not work" for PreToolUse (JSON was ignored as expected, but the blocking was via stderr, not deny)
256
- - PostToolUse with exit 2 appeared to work because it used stderr (not JSON), bypassing the Zod issue
257
-
258
- **Lesson:** When a hook output seems to be "silently ignored," check the JSON schema first. The Zod validator rejects malformed output without any error message.
259
-
260
- ### Debugging Checklist
261
-
262
- When a hook's deny/context isn't working:
263
-
264
- 1. **Is `hookEventName` in the JSON output?** Check `_output/hook-log.jsonl` for `HOOK_OUTPUT` entries
265
- 2. **Is the hook using `runHook()`/`runHookAsync()`?** Required for auto-detection
266
- 3. **Is `loadHookInput()` called before `emitContext()`?** It populates `_lastHookEvent`
267
- 4. **Is the exit code 0?** Exit 1/2 cause JSON to be ignored
268
- 5. **Are there extra fields in `hookSpecificOutput`?** Invalid fields cause silent rejection of the entire output
269
-
270
- ---
271
-
272
- ## Context Store
273
-
274
- 2-layer CRUD: per-context `state.json` + global `_output/index.json`.
275
-
276
- ```typescript
277
- import { getContextBySessionId, bindSession, maybeActivate, saveState } from "../lib-ts/context/context-store.js";
278
-
279
- const state = getContextBySessionId(sessionId, projectRoot);
280
- if (state) {
281
- // ALWAYS wrap non-critical operations — uncaught errors become UI "hook error"
282
- try {
283
- maybeActivate(state.id, permissionMode, projectRoot, "hook_name");
284
- } catch (e) {
285
- logWarn("hook_name", `maybeActivate failed (non-critical): ${e}`);
286
- }
287
- }
288
- ```
289
-
290
- **Valid modes:** `idle` | `has_plan` | `has_handoff` | `active`
291
-
292
- Transitions: `idle`/`has_plan`/`has_handoff` --> `active` (via `maybeActivate`). `active` --> `has_plan`/`has_handoff` (via `session_end`).
293
-
294
- ---
295
-
296
- ## Module Reference
297
-
298
- Use this table to find the right file. Read the source for full API details.
299
-
300
- ### `base/` — Core Infrastructure
301
-
302
- | File | Purpose | Key Exports |
303
- |------|---------|-------------|
304
- | `hook-utils.ts` | Hook lifecycle, stdin parsing, output emit, re-exports | `runHook`, `runHookAsync`, `loadHookInput`, `emitContext`, `emitContextAndBlock`, `emitBlock`, `emitBlockPrompt`, `emitBlockViaExit`, `emitBlockTopLevel`, `emitPermissionDecision`, `logDebug`...`logBlocking` |
305
- | `logger.ts` | JSONL logging engine | `hookLog`, `logDebug`, `logInfo`, `logWarn`, `logError`, `logBlocking`, `logHookError`, `logDiagnostic` |
306
- | `constants.ts` | Path resolution, limits | `getProjectRoot()`, `getContextDir()`, `MAX_FILE_SIZE` |
307
- | `atomic-write.ts` | Crash-safe file writes | `atomicWriteFileSync()` |
308
- | `state-io.ts` | State serialization with mode migration | `readState()`, `writeState()` |
309
- | `inference.ts` | Claude CLI subprocess calls | `inferText()` |
310
- | `utils.ts` | Formatting, ID generation | `nowIso()`, `generateContextId()`, `slugify()` |
311
- | `git-state.ts` | Git snapshot | `captureGitState()` |
312
- | `subprocess-utils.ts` | Recursive call guard | `isInternalCall()` |
313
- | `stop-words.ts` | Word list for ID generation | Used by `utils.ts` internally |
314
-
315
- ### `context/` — Context State Management
316
-
317
- | File | Purpose | Key Exports |
318
- |------|---------|-------------|
319
- | `context-store.ts` | CRUD for context state + index | `getContextBySessionId`, `bindSession`, `maybeActivate`, `saveState`, `createContext` |
320
- | `context-selector.ts` | Route prompts to contexts | `determineContext()`, `BlockRequest` |
321
- | `context-formatter.ts` | Display formatting | `formatContextSummary()` |
322
- | `plan-manager.ts` | Plan lifecycle (archive, hash, sign) | `archivePlan()`, `computePlanHash()` |
323
- | `task-tracker.ts` | Task CRUD on state.json | `addTask()`, `updateTask()`, `getTasks()` |
324
-
325
- ### `handoff/` and `templates/`
326
-
327
- | File | Purpose | Key Exports |
328
- |------|---------|-------------|
329
- | `handoff/document-generator.ts` | Handoff document generation | `generateHandoffDocument()` |
330
- | `templates/formatters.ts` | Display constants, mode maps, icons | `MODE_MAP`, `STATUS_ICONS` |
331
- | `templates/plan-context.ts` | Plan evaluation templates | `PLAN_EVALUATION_REMINDER` |
332
-
333
- ### Root
334
-
335
- | File | Purpose |
336
- |------|---------|
337
- | `types.ts` | All shared types: `Mode`, `ContextState`, `Task`, `HookInput`, `HookOutput` |
338
-
339
- ---
340
-
341
- ## Shared Hooks (`_shared/hooks-ts/`)
342
-
343
- These run for ALL templates. Method-specific hooks live in `_{method}/hooks/`.
344
-
345
- | Hook | Event | Purpose |
346
- |------|-------|---------|
347
- | `user_prompt_submit.ts` | UserPromptSubmit | Context enforcement — binds prompts to tracked contexts |
348
- | `context_monitor.ts` | PostToolUse:* | Context window tracking, handoff warnings at 30/20/10% |
349
- | `session_start.ts` | SessionStart | Restores plan/handoff context after `/clear` or compaction |
350
- | `session_end.ts` | SessionEnd | Stages `active` --> `has_plan`/`has_handoff` for next session |
351
- | `archive_plan.ts` | PreToolUse:ExitPlanMode | Archives plan file before accept/reject decision |
352
- | `pre_compact.ts` | PreToolUse:Compact | Pre-compaction state snapshot |
353
- | `task_create_capture.ts` | PostToolUse:TaskCreate | Persists task creation to context state |
354
- | `task_update_capture.ts` | PostToolUse:TaskUpdate | Persists task updates to context state |
355
- | `file-suggestion.ts` | PostToolUse:Write | Suggests file organization improvements |
356
-
357
- ---
358
-
359
- ## Environment Variables
360
-
361
- | Variable | Effect |
362
- |----------|--------|
363
- | `CLAUDE_PROJECT_DIR` | Override project root detection |
364
- | `HOOK_LOG_DISABLE=1` | Disable all file logging |
365
- | `HOOK_LOG_LEVEL=warn` | Minimum log level (default: `debug`) |
366
- | `HOOK_ERROR_LOG_DISABLE=1` | Legacy alias for `HOOK_LOG_DISABLE` |
367
- | `_CC_INTERNAL=1` | Marks subprocess calls (checked by `isInternalCall()`) |
1
+ # Shared TypeScript Library
2
+
3
+ **Location:** `_shared/lib-ts/` — cross-method infrastructure used by ALL templates.
4
+
5
+ **One import gets you started:**
6
+ ```typescript
7
+ import { loadHookInput, runHook, logInfo, emitContext } from "../lib-ts/base/hook-utils.js";
8
+ ```
9
+
10
+ `hook-utils.ts` re-exports the most-used functions from `logger.ts`, `constants.ts`, and `context-store.ts`. Start here. Only import from deeper modules when you need specific capabilities.
11
+
12
+ **Import direction:** Hooks --> method lib --> `_shared/lib-ts/`. Never the reverse.
13
+
14
+ ---
15
+
16
+ ## Critical Rules
17
+
18
+ These cause silent failures or UI noise when violated:
19
+
20
+ - **Entry point:** Every hook MUST use `runHook()` or `runHookAsync()` — never bare `main()` or `process.exit()`
21
+ - **stdout is sacred:** Only hook JSON output goes to stdout. Use logger functions for diagnostics, never `console.log()` or `print()`
22
+ - **stderr is opt-in:** `logDebug/logInfo/logWarn/logError` write to file only. Use `logBlocking()` when you NEED stderr visibility
23
+ - **Catch non-critical errors locally:** Uncaught errors bubble to `runHook` which writes to stderr, showing "hook error" in the UI even on exit 0
24
+ - **No reverse imports:** Never import from method lib (e.g., `_cc-native/lib/`) into shared lib
25
+
26
+ ---
27
+
28
+ ## Hook Skeleton
29
+
30
+ Copy this for new hooks:
31
+
32
+ ```typescript
33
+ #!/usr/bin/env bun
34
+ import { loadHookInput, runHook, logDebug, logInfo, emitContext } from "../lib-ts/base/hook-utils.js";
35
+
36
+ function main(): void {
37
+ const payload = loadHookInput();
38
+ if (!payload) return;
39
+
40
+ const sessionId = payload.session_id;
41
+ if (!sessionId) return;
42
+
43
+ // Your hook logic here...
44
+
45
+ emitContext("Context visible to Claude");
46
+ }
47
+
48
+ runHook(main, "my_hook_name");
49
+ ```
50
+
51
+ For async hooks (AI inference, network calls):
52
+
53
+ ```typescript
54
+ import { runHookAsync } from "../lib-ts/base/hook-utils.js";
55
+
56
+ async function asyncMain(): Promise<void> {
57
+ // await something...
58
+ }
59
+
60
+ runHookAsync(asyncMain, "my_async_hook");
61
+ ```
62
+
63
+ ---
64
+
65
+ ## Logging
66
+
67
+ All logging goes to `_output/hook-log.jsonl`. stderr visibility is opt-in.
68
+
69
+ | Tier | Function | Visible in UI? | Use When |
70
+ |------|----------|---------------|----------|
71
+ | File-only | `logDebug()` / `logInfo()` / `logWarn()` / `logError()` | No | 99% of logging: diagnostics, state changes, non-critical errors |
72
+ | Blocking | `logBlocking()` | Yes (stderr) | The hook found a real problem the user or Claude must see |
73
+ | Unhandled | `logHookError()` | Yes (stderr) | Reserved for `runHook` crash handler — do not call directly |
74
+ | Terminal | `eprint()` | Yes (raw stderr) | Usage help, progress indicators — not logged to JSONL |
75
+
76
+ ```typescript
77
+ import { logDebug, logInfo, logWarn, logBlocking } from "../lib-ts/base/hook-utils.js";
78
+
79
+ logInfo("my_hook", "Session started"); // file only
80
+ logWarn("my_hook", `Fallback used: ${reason}`); // file only
81
+ logBlocking("my_hook", "Critical: state corrupt"); // shows in UI
82
+ ```
83
+
84
+ ---
85
+
86
+ ## Hook Output — Communication Channels
87
+
88
+ Hooks have multiple channels back to the session. Pick the right one:
89
+
90
+ | Want to... | Function | Who sees it |
91
+ |------------|----------|-------------|
92
+ | **Block (any event)** | `emitBlock(reason, context?)` | Claude + user — auto-dispatches to correct mechanism |
93
+ | Block tool (PreToolUse only) | `emitContextAndBlock(context, reason)` | Claude + user (denial reason prominent) |
94
+ | Return message, don't block | `emitContext(context)` | Claude + user (in transcript) |
95
+ | Log only (diagnostics) | `logInfo()` / `logWarn()` / etc. | Nobody in session — file only |
96
+
97
+ **There is no way to show something to the user but hide it from Claude, or vice versa.** Both `emitContext()` and `emitContextAndBlock()` produce output visible to both.
98
+
99
+ ### Universal Blocking: `emitBlock()` (RECOMMENDED)
100
+
101
+ ```typescript
102
+ emitBlock("Reason for blocking", "Optional detailed context");
103
+ ```
104
+
105
+ Auto-detects the correct blocking mechanism from the current hook event:
106
+
107
+ | Event | Mechanism | Dispatches to |
108
+ |-------|-----------|---------------|
109
+ | **PreToolUse** | `permissionDecision: "deny"` in hookSpecificOutput | `emitContextAndBlock()` |
110
+ | **UserPromptSubmit** | Top-level `{ decision: "block", reason }` | `emitBlockPrompt()` |
111
+ | **PostToolUse / PostToolUseFailure** | Exit code 2 + stderr | `emitBlockViaExit()` |
112
+ | **Stop / SubagentStop** | Top-level `{ decision: "block", reason }` | `emitBlockTopLevel()` |
113
+ | **PermissionRequest** | `{ decision: { behavior: "deny" } }` | `emitPermissionDecision()` |
114
+ | **SessionStart / Notification / SubagentStart / SessionEnd** | No blocking mechanism — warns and no-ops | — |
115
+
116
+ **Use `emitBlock()` for all new hooks.** The per-pattern functions below exist for advanced use cases only.
117
+
118
+ ### Channel 1: Block + Context (PreToolUse only)
119
+
120
+ ```typescript
121
+ emitContextAndBlock(
122
+ "Detailed feedback Claude sees", // additionalContext
123
+ "Short reason for the block" // permissionDecisionReason
124
+ );
125
+ // No SystemExit needed — permissionDecision:"deny" with exit 0 is sufficient.
126
+ // Warns if called from non-PreToolUse events (output will be silently rejected by Claude Code).
127
+ ```
128
+
129
+ The tool call is **prevented from executing**. Only works for PreToolUse hooks.
130
+
131
+ ### Channel 2: Block Prompt Submission (UserPromptSubmit only)
132
+
133
+ ```typescript
134
+ emitBlockPrompt("Reason the prompt was blocked", "Optional context for Claude");
135
+ ```
136
+
137
+ Emits top-level `{ decision: "block", reason }`. The prompt is rejected before Claude processes it.
138
+
139
+ ### Channel 3: Block via Exit Code (PostToolUse / PostToolUseFailure)
140
+
141
+ ```typescript
142
+ emitBlockViaExit("Reason for blocking", "Optional context prepended to stderr");
143
+ // Throws SystemExit:2 — handled by runHook/runHookAsync
144
+ // NOTE: Exit 2 causes Claude Code to ignore all JSON stdout — only stderr matters
145
+ ```
146
+
147
+ ### Channel 4: Block Stop/SubagentStop
148
+
149
+ ```typescript
150
+ emitBlockTopLevel("Reason to prevent stopping");
151
+ ```
152
+
153
+ ### Channel 5: PermissionRequest Decision
154
+
155
+ ```typescript
156
+ emitPermissionDecision("deny", { message: "Why denied" });
157
+ emitPermissionDecision("allow", { updatedInput: { /* modified input */ } });
158
+ ```
159
+
160
+ ### Channel 6: Non-blocking Context (any hook event)
161
+
162
+ ```typescript
163
+ emitContext("Information added to Claude's context");
164
+ ```
165
+
166
+ The tool call / session continues normally. Works for PreToolUse, PostToolUse, UserPromptSubmit, SessionStart, Notification, SubagentStart.
167
+
168
+ ### Channel 7: Log-only (diagnostics)
169
+
170
+ ```typescript
171
+ logInfo("my_hook", "Processing started"); // File only
172
+ logWarn("my_hook", `Fallback used: ${why}`); // File only
173
+ ```
174
+
175
+ Nobody in the session sees this. Written to `_output/hook-log.jsonl` for debugging.
176
+
177
+ ### Hook Output Logging
178
+
179
+ Both `emitContext()` and `emitContextAndBlock()` automatically log their output to `_output/hook-log.jsonl` as `HOOK_OUTPUT` entries. This captures exactly what was sent to Claude via stdout, closing the visibility gap where the agent sees injected context the user doesn't.
180
+
181
+ - **Log level:** `info` — visible unless `HOOK_LOG_LEVEL=warn`
182
+ - **`msg` field:** Scannable summary: `HOOK_OUTPUT [context] 842 chars` or `HOOK_OUTPUT [block] 340 chars, reason="..."`
183
+ - **`data` field:** Full payload including `additionalContext` and `blockReason` (for blocks)
184
+ - **Controlled by:** Existing `HOOK_LOG_LEVEL` and `HOOK_LOG_DISABLE` env vars
185
+ - **No hook changes needed:** Logging happens inside the emit functions themselves
186
+
187
+ ### Exit codes and JSON
188
+
189
+ | Exit Code | JSON Parsed? | Effect |
190
+ |-----------|-------------|--------|
191
+ | **0** | Yes | Normal — `hookSpecificOutput` processed |
192
+ | **2** | No | Blocking error — JSON ignored, stderr fed to Claude |
193
+ | **Other** | No | Non-blocking error — stderr shown in verbose mode |
194
+
195
+ You cannot mix exit 2 with JSON decisions. Pick one: exit 0 + JSON, or exit 2 + stderr.
196
+
197
+ ### hookSpecificOutput fields by event type
198
+
199
+ | Event | `additionalContext` | `permissionDecision` | `permissionDecisionReason` | Other |
200
+ |-------|:--:|:--:|:--:|-------|
201
+ | **PreToolUse** | Y | Y (allow/deny/ask) | Y | `updatedInput` |
202
+ | **PostToolUse** | Y | - | - | `updatedMCPToolOutput` (MCP only) |
203
+ | **UserPromptSubmit** | Y | - | - | top-level `decision: "block"` |
204
+ | **SessionStart** | Y | - | - | — |
205
+ | **Notification** | Y | - | - | — |
206
+ | **SubagentStart** | Y | - | - | — |
207
+ | **Stop** | - | - | - | top-level `decision`, `reason` |
208
+ | **SessionEnd** | - | - | - | — |
209
+
210
+ **Invalid fields cause silent rejection of the entire output.** No error, no feedback. Conversely, **missing `hookEventName` also causes silent rejection** — see "Hook API: Critical Learnings" below.
211
+
212
+ ### Special case: fileSuggestion
213
+
214
+ The `fileSuggestion` settings command is NOT a hook — it uses a different protocol. It outputs a plain JSON array to stdout (e.g., `console.log(JSON.stringify(paths))`). Do not use `emitContext()` for fileSuggestion.
215
+
216
+ ---
217
+
218
+ ## Hook API: Critical Learnings (Verified 2026-02-11)
219
+
220
+ These findings were verified through systematic testing. They document Claude Code's actual behavior, which sometimes differs from what the docs suggest.
221
+
222
+ ### hookEventName is REQUIRED (CC 2.1.39+)
223
+
224
+ Claude Code validates `hookSpecificOutput` using a Zod discriminated union keyed on `hookEventName`. If this field is missing:
225
+
226
+ - The entire hook output is silently rejected — no error, no feedback
227
+ - `permissionDecision: "deny"` is never processed
228
+ - The hook appears to "not work" even though it runs successfully
229
+
230
+ **You don't need to handle this manually.** `emitContext()` and `emitContextAndBlock()` auto-detect `hookEventName` from the stdin payload (via `_lastHookEvent`, set by `loadHookInput()`/`runHook()`). This works because hooks are synchronous single-process executions — each `bun` process has its own memory, so there's no concurrency risk between sessions.
231
+
232
+ **If auto-detection fails** (e.g., `loadHookInput()` wasn't called), `hookEventName` is omitted and the output will be silently rejected. This is why `runHook()`/`runHookAsync()` is mandatory — it calls `_earlyReadInput()` first, guaranteeing `_lastHookEvent` is populated.
233
+
234
+ ### Exit Code Behavior (Tested)
235
+
236
+ | Exit Code | JSON Parsed? | Blocks Tool? | What Claude Sees | Tested? |
237
+ |-----------|-------------|-------------|------------------|---------|
238
+ | **0** + deny JSON | Yes | Yes (PreToolUse only) | `additionalContext` + denial reason | Yes |
239
+ | **0** + context JSON | Yes | No | `additionalContext` in transcript | Yes |
240
+ | **1** | No | No | stderr in verbose mode only | Yes |
241
+ | **2** | No | Yes (any event) | stderr fed as system-reminder | Yes |
242
+
243
+ **Key insight:** Exit 0 + `permissionDecision: "deny"` is the correct way to block a tool. Exit 2 is a blunt instrument — it ignores your JSON and feeds raw stderr to Claude. Use exit 0 + deny for clean blocking with structured feedback.
244
+
245
+ ### ExitPlanMode: Not Special-Cased
246
+
247
+ Early testing suggested ExitPlanMode was "immune" to PreToolUse deny. **This was wrong.** The actual issue was missing `hookEventName` — the Zod validator silently rejected the deny output.
248
+
249
+ **With `hookEventName` included:**
250
+ - PreToolUse `permissionDecision: "deny"` (exit 0) → **blocks ExitPlanMode**, no dialog appears, session stays in plan mode
251
+ - `emitContextAndBlock()` handles this automatically via auto-detection
252
+
253
+ **Without `hookEventName` (the bug):**
254
+ - Deny silently rejected → dialog appeared → looked like ExitPlanMode was special-cased
255
+ - Exit 2 also appeared to "not work" for PreToolUse (JSON was ignored as expected, but the blocking was via stderr, not deny)
256
+ - PostToolUse with exit 2 appeared to work because it used stderr (not JSON), bypassing the Zod issue
257
+
258
+ **Lesson:** When a hook output seems to be "silently ignored," check the JSON schema first. The Zod validator rejects malformed output without any error message.
259
+
260
+ ### Debugging Checklist
261
+
262
+ When a hook's deny/context isn't working:
263
+
264
+ 1. **Is `hookEventName` in the JSON output?** Check `_output/hook-log.jsonl` for `HOOK_OUTPUT` entries
265
+ 2. **Is the hook using `runHook()`/`runHookAsync()`?** Required for auto-detection
266
+ 3. **Is `loadHookInput()` called before `emitContext()`?** It populates `_lastHookEvent`
267
+ 4. **Is the exit code 0?** Exit 1/2 cause JSON to be ignored
268
+ 5. **Are there extra fields in `hookSpecificOutput`?** Invalid fields cause silent rejection of the entire output
269
+
270
+ ---
271
+
272
+ ## Context Store
273
+
274
+ 2-layer CRUD: per-context `state.json` + global `_output/index.json`.
275
+
276
+ ```typescript
277
+ import { getContextBySessionId, bindSession, maybeActivate, saveState } from "../lib-ts/context/context-store.js";
278
+
279
+ const state = getContextBySessionId(sessionId, projectRoot);
280
+ if (state) {
281
+ // ALWAYS wrap non-critical operations — uncaught errors become UI "hook error"
282
+ try {
283
+ maybeActivate(state.id, permissionMode, projectRoot, "hook_name");
284
+ } catch (e) {
285
+ logWarn("hook_name", `maybeActivate failed (non-critical): ${e}`);
286
+ }
287
+ }
288
+ ```
289
+
290
+ **Valid modes:** `idle` | `has_plan` | `has_handoff` | `active`
291
+
292
+ Transitions: `idle`/`has_plan`/`has_handoff` --> `active` (via `maybeActivate`). `active` --> `has_plan`/`has_handoff` (via `session_end`).
293
+
294
+ ---
295
+
296
+ ## Module Reference
297
+
298
+ Use this table to find the right file. Read the source for full API details.
299
+
300
+ ### `base/` — Core Infrastructure
301
+
302
+ | File | Purpose | Key Exports |
303
+ |------|---------|-------------|
304
+ | `hook-utils.ts` | Hook lifecycle, stdin parsing, output emit, re-exports | `runHook`, `runHookAsync`, `loadHookInput`, `emitContext`, `emitContextAndBlock`, `emitBlock`, `emitBlockPrompt`, `emitBlockViaExit`, `emitBlockTopLevel`, `emitPermissionDecision`, `logDebug`...`logBlocking` |
305
+ | `logger.ts` | JSONL logging engine | `hookLog`, `logDebug`, `logInfo`, `logWarn`, `logError`, `logBlocking`, `logHookError`, `logDiagnostic` |
306
+ | `constants.ts` | Path resolution, limits | `getProjectRoot()`, `getContextDir()`, `MAX_FILE_SIZE` |
307
+ | `atomic-write.ts` | Crash-safe file writes | `atomicWriteFileSync()` |
308
+ | `state-io.ts` | State serialization with mode migration | `readState()`, `writeState()` |
309
+ | `inference.ts` | Claude CLI subprocess calls | `inferText()` |
310
+ | `utils.ts` | Formatting, ID generation | `nowIso()`, `generateContextId()`, `slugify()` |
311
+ | `git-state.ts` | Git snapshot | `captureGitState()` |
312
+ | `subprocess-utils.ts` | Recursive call guard | `isInternalCall()` |
313
+ | `stop-words.ts` | Word list for ID generation | Used by `utils.ts` internally |
314
+
315
+ ### `context/` — Context State Management
316
+
317
+ | File | Purpose | Key Exports |
318
+ |------|---------|-------------|
319
+ | `context-store.ts` | CRUD for context state + index | `getContextBySessionId`, `bindSession`, `maybeActivate`, `saveState`, `createContext` |
320
+ | `context-selector.ts` | Route prompts to contexts | `determineContext()`, `BlockRequest` |
321
+ | `context-formatter.ts` | Display formatting | `formatContextSummary()` |
322
+ | `plan-manager.ts` | Plan lifecycle (archive, hash, sign) | `archivePlan()`, `computePlanHash()` |
323
+ | `task-tracker.ts` | Task CRUD on state.json | `addTask()`, `updateTask()`, `getTasks()` |
324
+
325
+ ### `handoff/` and `templates/`
326
+
327
+ | File | Purpose | Key Exports |
328
+ |------|---------|-------------|
329
+ | `handoff/document-generator.ts` | Handoff document generation | `generateHandoffDocument()` |
330
+ | `templates/formatters.ts` | Display constants, mode maps, icons | `MODE_MAP`, `STATUS_ICONS` |
331
+ | `templates/plan-context.ts` | Plan evaluation templates | `PLAN_EVALUATION_REMINDER` |
332
+
333
+ ### Root
334
+
335
+ | File | Purpose |
336
+ |------|---------|
337
+ | `types.ts` | All shared types: `Mode`, `ContextState`, `Task`, `HookInput`, `HookOutput` |
338
+
339
+ ---
340
+
341
+ ## Shared Hooks (`_shared/hooks-ts/`)
342
+
343
+ These run for ALL templates. Method-specific hooks live in `_{method}/hooks/`.
344
+
345
+ | Hook | Event | Purpose |
346
+ |------|-------|---------|
347
+ | `user_prompt_submit.ts` | UserPromptSubmit | Context enforcement — binds prompts to tracked contexts |
348
+ | `context_monitor.ts` | PostToolUse:* | Context window tracking, handoff warnings at 30/20/10% |
349
+ | `session_start.ts` | SessionStart | Restores plan/handoff context after `/clear` or compaction |
350
+ | `session_end.ts` | SessionEnd | Stages `active` --> `has_plan`/`has_handoff` for next session |
351
+ | `archive_plan.ts` | PreToolUse:ExitPlanMode | Archives plan file before accept/reject decision |
352
+ | `pre_compact.ts` | PreToolUse:Compact | Pre-compaction state snapshot |
353
+ | `task_create_capture.ts` | PostToolUse:TaskCreate | Persists task creation to context state |
354
+ | `task_update_capture.ts` | PostToolUse:TaskUpdate | Persists task updates to context state |
355
+ | `file-suggestion.ts` | PostToolUse:Write | Suggests file organization improvements |
356
+
357
+ ---
358
+
359
+ ## Environment Variables
360
+
361
+ | Variable | Effect |
362
+ |----------|--------|
363
+ | `CLAUDE_PROJECT_DIR` | Override project root detection |
364
+ | `HOOK_LOG_DISABLE=1` | Disable all file logging |
365
+ | `HOOK_LOG_LEVEL=warn` | Minimum log level (default: `debug`) |
366
+ | `HOOK_ERROR_LOG_DISABLE=1` | Legacy alias for `HOOK_LOG_DISABLE` |
367
+ | `_CC_INTERNAL=1` | Marks subprocess calls (checked by `isInternalCall()`) |