aiwcli 0.10.3 → 0.11.0

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 (189) hide show
  1. package/bin/run.js +1 -1
  2. package/dist/commands/clear.js +28 -131
  3. package/dist/commands/init/index.js +3 -3
  4. package/dist/lib/gitignore-manager.d.ts +32 -0
  5. package/dist/lib/gitignore-manager.js +141 -2
  6. package/dist/templates/CLAUDE.md +8 -8
  7. package/dist/templates/_shared/.claude/commands/handoff-resume.md +64 -0
  8. package/dist/templates/_shared/.claude/commands/handoff.md +16 -10
  9. package/dist/templates/_shared/.claude/settings.json +7 -7
  10. package/dist/templates/_shared/hooks-ts/_utils/git-state.ts +2 -0
  11. package/dist/templates/_shared/hooks-ts/archive_plan.ts +159 -0
  12. package/dist/templates/_shared/hooks-ts/context_monitor.ts +147 -0
  13. package/dist/templates/_shared/hooks-ts/file-suggestion.ts +130 -0
  14. package/dist/templates/_shared/hooks-ts/pre_compact.ts +49 -0
  15. package/dist/templates/_shared/hooks-ts/session_end.ts +104 -0
  16. package/dist/templates/_shared/hooks-ts/session_start.ts +144 -0
  17. package/dist/templates/_shared/hooks-ts/task_create_capture.ts +48 -0
  18. package/dist/templates/_shared/hooks-ts/task_update_capture.ts +74 -0
  19. package/dist/templates/_shared/hooks-ts/user_prompt_submit.ts +83 -0
  20. package/dist/templates/_shared/lib-ts/CLAUDE.md +318 -0
  21. package/dist/templates/_shared/lib-ts/base/atomic-write.ts +12 -12
  22. package/dist/templates/_shared/lib-ts/base/constants.ts +22 -15
  23. package/dist/templates/_shared/lib-ts/base/hook-utils.ts +129 -50
  24. package/dist/templates/_shared/lib-ts/base/inference.ts +28 -21
  25. package/dist/templates/_shared/lib-ts/base/logger.ts +31 -15
  26. package/dist/templates/_shared/lib-ts/base/state-io.ts +9 -7
  27. package/dist/templates/_shared/lib-ts/base/stop-words.ts +131 -131
  28. package/dist/templates/_shared/lib-ts/base/subprocess-utils.ts +139 -0
  29. package/dist/templates/_shared/lib-ts/base/utils.ts +69 -69
  30. package/dist/templates/_shared/lib-ts/context/context-formatter.ts +30 -24
  31. package/dist/templates/_shared/lib-ts/context/context-selector.ts +50 -32
  32. package/dist/templates/_shared/lib-ts/context/context-store.ts +76 -48
  33. package/dist/templates/_shared/lib-ts/context/plan-manager.ts +61 -37
  34. package/dist/templates/_shared/lib-ts/context/task-tracker.ts +10 -6
  35. package/dist/templates/_shared/lib-ts/handoff/document-generator.ts +11 -10
  36. package/dist/templates/_shared/lib-ts/handoff/handoff-reader.ts +159 -0
  37. package/dist/templates/_shared/lib-ts/templates/formatters.ts +6 -4
  38. package/dist/templates/_shared/lib-ts/types.ts +68 -55
  39. package/dist/templates/_shared/scripts/resolve_context.ts +24 -0
  40. package/dist/templates/_shared/scripts/resume_handoff.ts +321 -0
  41. package/dist/templates/_shared/scripts/save_handoff.ts +21 -21
  42. package/dist/templates/_shared/scripts/status_line.ts +733 -0
  43. package/dist/templates/cc-native/.claude/settings.json +175 -185
  44. package/dist/templates/cc-native/TEMPLATE-SCHEMA.md +15 -17
  45. package/dist/templates/cc-native/_cc-native/agents/CLAUDE.md +0 -2
  46. package/dist/templates/cc-native/_cc-native/hooks/CLAUDE.md +109 -135
  47. package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.ts +119 -0
  48. package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.ts +921 -0
  49. package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.ts +61 -0
  50. package/dist/templates/cc-native/_cc-native/lib-ts/aggregate-agents.ts +157 -0
  51. package/dist/templates/cc-native/_cc-native/lib-ts/artifacts.ts +709 -0
  52. package/dist/templates/cc-native/_cc-native/lib-ts/cc-native-state.ts +199 -0
  53. package/dist/templates/cc-native/_cc-native/lib-ts/cli-output-parser.ts +124 -0
  54. package/dist/templates/cc-native/_cc-native/lib-ts/config.ts +57 -0
  55. package/dist/templates/cc-native/_cc-native/lib-ts/constants.ts +83 -0
  56. package/dist/templates/cc-native/_cc-native/lib-ts/debug.ts +80 -0
  57. package/dist/templates/cc-native/_cc-native/lib-ts/index.ts +119 -0
  58. package/dist/templates/cc-native/_cc-native/lib-ts/json-parser.ts +162 -0
  59. package/dist/templates/cc-native/_cc-native/lib-ts/nul +3 -0
  60. package/dist/templates/cc-native/_cc-native/lib-ts/orchestrator.ts +249 -0
  61. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/agent.ts +155 -0
  62. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/codex.ts +130 -0
  63. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/gemini.ts +106 -0
  64. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/index.ts +10 -0
  65. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/types.ts +23 -0
  66. package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +243 -0
  67. package/dist/templates/cc-native/_cc-native/lib-ts/tsconfig.json +18 -0
  68. package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +310 -0
  69. package/dist/templates/cc-native/_cc-native/lib-ts/verdict.ts +72 -0
  70. package/dist/templates/cc-native/_cc-native/plan-review.config.json +1 -9
  71. package/oclif.manifest.json +1 -1
  72. package/package.json +1 -1
  73. package/dist/templates/_shared/hooks/__init__.py +0 -16
  74. package/dist/templates/_shared/hooks/__pycache__/__init__.cpython-313.pyc +0 -0
  75. package/dist/templates/_shared/hooks/__pycache__/archive_plan.cpython-313.pyc +0 -0
  76. package/dist/templates/_shared/hooks/__pycache__/context_enforcer.cpython-313.pyc +0 -0
  77. package/dist/templates/_shared/hooks/__pycache__/context_monitor.cpython-313.pyc +0 -0
  78. package/dist/templates/_shared/hooks/__pycache__/file-suggestion.cpython-313.pyc +0 -0
  79. package/dist/templates/_shared/hooks/__pycache__/pre_compact.cpython-313.pyc +0 -0
  80. package/dist/templates/_shared/hooks/__pycache__/session_end.cpython-313.pyc +0 -0
  81. package/dist/templates/_shared/hooks/__pycache__/session_start.cpython-313.pyc +0 -0
  82. package/dist/templates/_shared/hooks/__pycache__/task_create_atomicity.cpython-313.pyc +0 -0
  83. package/dist/templates/_shared/hooks/__pycache__/task_create_capture.cpython-313.pyc +0 -0
  84. package/dist/templates/_shared/hooks/__pycache__/task_update_capture.cpython-313.pyc +0 -0
  85. package/dist/templates/_shared/hooks/__pycache__/user_prompt_submit.cpython-313.pyc +0 -0
  86. package/dist/templates/_shared/hooks/archive_plan.py +0 -177
  87. package/dist/templates/_shared/hooks/context_monitor.py +0 -270
  88. package/dist/templates/_shared/hooks/file-suggestion.py +0 -215
  89. package/dist/templates/_shared/hooks/pre_compact.py +0 -104
  90. package/dist/templates/_shared/hooks/session_end.py +0 -173
  91. package/dist/templates/_shared/hooks/session_start.py +0 -206
  92. package/dist/templates/_shared/hooks/task_create_capture.py +0 -108
  93. package/dist/templates/_shared/hooks/task_update_capture.py +0 -145
  94. package/dist/templates/_shared/hooks/user_prompt_submit.py +0 -139
  95. package/dist/templates/_shared/lib/__init__.py +0 -1
  96. package/dist/templates/_shared/lib/__pycache__/__init__.cpython-313.pyc +0 -0
  97. package/dist/templates/_shared/lib/base/__init__.py +0 -65
  98. package/dist/templates/_shared/lib/base/__pycache__/__init__.cpython-313.pyc +0 -0
  99. package/dist/templates/_shared/lib/base/__pycache__/atomic_write.cpython-313.pyc +0 -0
  100. package/dist/templates/_shared/lib/base/__pycache__/constants.cpython-313.pyc +0 -0
  101. package/dist/templates/_shared/lib/base/__pycache__/hook_utils.cpython-313.pyc +0 -0
  102. package/dist/templates/_shared/lib/base/__pycache__/inference.cpython-313.pyc +0 -0
  103. package/dist/templates/_shared/lib/base/__pycache__/logger.cpython-313.pyc +0 -0
  104. package/dist/templates/_shared/lib/base/__pycache__/stop_words.cpython-313.pyc +0 -0
  105. package/dist/templates/_shared/lib/base/__pycache__/subprocess_utils.cpython-313.pyc +0 -0
  106. package/dist/templates/_shared/lib/base/__pycache__/utils.cpython-313.pyc +0 -0
  107. package/dist/templates/_shared/lib/base/atomic_write.py +0 -180
  108. package/dist/templates/_shared/lib/base/constants.py +0 -358
  109. package/dist/templates/_shared/lib/base/hook_utils.py +0 -339
  110. package/dist/templates/_shared/lib/base/inference.py +0 -307
  111. package/dist/templates/_shared/lib/base/logger.py +0 -305
  112. package/dist/templates/_shared/lib/base/stop_words.py +0 -221
  113. package/dist/templates/_shared/lib/base/subprocess_utils.py +0 -46
  114. package/dist/templates/_shared/lib/base/utils.py +0 -263
  115. package/dist/templates/_shared/lib/context/__init__.py +0 -102
  116. package/dist/templates/_shared/lib/context/__pycache__/__init__.cpython-313.pyc +0 -0
  117. package/dist/templates/_shared/lib/context/__pycache__/cache.cpython-313.pyc +0 -0
  118. package/dist/templates/_shared/lib/context/__pycache__/context_extractor.cpython-313.pyc +0 -0
  119. package/dist/templates/_shared/lib/context/__pycache__/context_formatter.cpython-313.pyc +0 -0
  120. package/dist/templates/_shared/lib/context/__pycache__/context_manager.cpython-313.pyc +0 -0
  121. package/dist/templates/_shared/lib/context/__pycache__/context_selector.cpython-313.pyc +0 -0
  122. package/dist/templates/_shared/lib/context/__pycache__/context_store.cpython-313.pyc +0 -0
  123. package/dist/templates/_shared/lib/context/__pycache__/discovery.cpython-313.pyc +0 -0
  124. package/dist/templates/_shared/lib/context/__pycache__/event_log.cpython-313.pyc +0 -0
  125. package/dist/templates/_shared/lib/context/__pycache__/plan_archive.cpython-313.pyc +0 -0
  126. package/dist/templates/_shared/lib/context/__pycache__/plan_manager.cpython-313.pyc +0 -0
  127. package/dist/templates/_shared/lib/context/__pycache__/task_sync.cpython-313.pyc +0 -0
  128. package/dist/templates/_shared/lib/context/__pycache__/task_tracker.cpython-313.pyc +0 -0
  129. package/dist/templates/_shared/lib/context/context_formatter.py +0 -317
  130. package/dist/templates/_shared/lib/context/context_selector.py +0 -508
  131. package/dist/templates/_shared/lib/context/context_store.py +0 -653
  132. package/dist/templates/_shared/lib/context/plan_manager.py +0 -303
  133. package/dist/templates/_shared/lib/context/task_tracker.py +0 -188
  134. package/dist/templates/_shared/lib/handoff/__init__.py +0 -22
  135. package/dist/templates/_shared/lib/handoff/__pycache__/__init__.cpython-313.pyc +0 -0
  136. package/dist/templates/_shared/lib/handoff/__pycache__/document_generator.cpython-313.pyc +0 -0
  137. package/dist/templates/_shared/lib/handoff/document_generator.py +0 -278
  138. package/dist/templates/_shared/lib/templates/README.md +0 -206
  139. package/dist/templates/_shared/lib/templates/__init__.py +0 -36
  140. package/dist/templates/_shared/lib/templates/__pycache__/__init__.cpython-313.pyc +0 -0
  141. package/dist/templates/_shared/lib/templates/__pycache__/formatters.cpython-313.pyc +0 -0
  142. package/dist/templates/_shared/lib/templates/__pycache__/persona_questions.cpython-313.pyc +0 -0
  143. package/dist/templates/_shared/lib/templates/__pycache__/plan_context.cpython-313.pyc +0 -0
  144. package/dist/templates/_shared/lib/templates/formatters.py +0 -146
  145. package/dist/templates/_shared/lib/templates/plan_context.py +0 -73
  146. package/dist/templates/_shared/scripts/__pycache__/save_handoff.cpython-313.pyc +0 -0
  147. package/dist/templates/_shared/scripts/__pycache__/status_line.cpython-313.pyc +0 -0
  148. package/dist/templates/_shared/scripts/save_handoff.py +0 -357
  149. package/dist/templates/_shared/scripts/status_line.py +0 -716
  150. package/dist/templates/cc-native/.claude/commands/cc-native/fresh-perspective.md +0 -8
  151. package/dist/templates/cc-native/.windsurf/workflows/cc-native/fresh-perspective.md +0 -8
  152. package/dist/templates/cc-native/MIGRATION.md +0 -86
  153. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/add_plan_context.cpython-313.pyc +0 -0
  154. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/cc-native-plan-review.cpython-313.pyc +0 -0
  155. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/mark_questions_asked.cpython-313.pyc +0 -0
  156. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/plan_accepted.cpython-313.pyc +0 -0
  157. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/plan_questions_early.cpython-313.pyc +0 -0
  158. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/suggest-fresh-perspective.cpython-313.pyc +0 -0
  159. package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.py +0 -130
  160. package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.py +0 -954
  161. package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.py +0 -81
  162. package/dist/templates/cc-native/_cc-native/hooks/suggest-fresh-perspective.py +0 -340
  163. package/dist/templates/cc-native/_cc-native/lib/CLAUDE.md +0 -265
  164. package/dist/templates/cc-native/_cc-native/lib/__init__.py +0 -53
  165. package/dist/templates/cc-native/_cc-native/lib/__pycache__/__init__.cpython-313.pyc +0 -0
  166. package/dist/templates/cc-native/_cc-native/lib/__pycache__/atomic_write.cpython-313.pyc +0 -0
  167. package/dist/templates/cc-native/_cc-native/lib/__pycache__/constants.cpython-313.pyc +0 -0
  168. package/dist/templates/cc-native/_cc-native/lib/__pycache__/debug.cpython-313.pyc +0 -0
  169. package/dist/templates/cc-native/_cc-native/lib/__pycache__/orchestrator.cpython-313.pyc +0 -0
  170. package/dist/templates/cc-native/_cc-native/lib/__pycache__/state.cpython-313.pyc +0 -0
  171. package/dist/templates/cc-native/_cc-native/lib/__pycache__/utils.cpython-313.pyc +0 -0
  172. package/dist/templates/cc-native/_cc-native/lib/constants.py +0 -45
  173. package/dist/templates/cc-native/_cc-native/lib/debug.py +0 -139
  174. package/dist/templates/cc-native/_cc-native/lib/orchestrator.py +0 -362
  175. package/dist/templates/cc-native/_cc-native/lib/reviewers/__init__.py +0 -28
  176. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/__init__.cpython-313.pyc +0 -0
  177. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/agent.cpython-313.pyc +0 -0
  178. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/base.cpython-313.pyc +0 -0
  179. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/codex.cpython-313.pyc +0 -0
  180. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/gemini.cpython-313.pyc +0 -0
  181. package/dist/templates/cc-native/_cc-native/lib/reviewers/agent.py +0 -215
  182. package/dist/templates/cc-native/_cc-native/lib/reviewers/base.py +0 -88
  183. package/dist/templates/cc-native/_cc-native/lib/reviewers/codex.py +0 -124
  184. package/dist/templates/cc-native/_cc-native/lib/reviewers/gemini.py +0 -108
  185. package/dist/templates/cc-native/_cc-native/lib/state.py +0 -268
  186. package/dist/templates/cc-native/_cc-native/lib/utils.py +0 -1071
  187. package/dist/templates/cc-native/_cc-native/scripts/__pycache__/aggregate_agents.cpython-313.pyc +0 -0
  188. package/dist/templates/cc-native/_cc-native/scripts/aggregate_agents.py +0 -168
  189. package/dist/templates/cc-native/_cc-native/workflows/fresh-perspective.md +0 -134
@@ -5,6 +5,11 @@
5
5
  * Log location: _output/hook-log.jsonl (global, all sessions)
6
6
  * Filter by session using the "sid" field.
7
7
  *
8
+ * stderr is OPT-IN: convenience functions (logDebug, logInfo, logWarn, logError)
9
+ * write to file only by default. To also write to stderr (visible to Claude Code
10
+ * as "hook error"), pass { stderr: true } or use logBlocking().
11
+ * logHookError() always writes to stderr (unhandled errors must be visible).
12
+ *
8
13
  * Environment variables:
9
14
  * - HOOK_LOG_DISABLE=1: Disable all file logging
10
15
  * - HOOK_LOG_LEVEL=warn: Minimum level to log (default: debug)
@@ -27,31 +32,32 @@ const LEVELS: Record<string, number> = {
27
32
  const MAX_LOG_LINES = 10_000; // Max lines in global log before pruning
28
33
 
29
34
  // Module-level session ID cache
30
- let _cachedSessionId: string | null = null;
35
+ let _cachedSessionId: null | string = null;
31
36
 
32
37
  // Module-level context path cache (kept for external callers)
33
- let _cachedContextPath: string | null = null;
38
+ let _cachedContextPath: null | string = null;
34
39
  let _contextResolved = false;
35
40
 
36
41
  /**
37
42
  * Set the session ID for this process. All subsequent log calls include it.
38
43
  */
39
- export function setSessionId(sessionId: string | null): void {
44
+ export function setSessionId(sessionId: null | string): void {
40
45
  _cachedSessionId = sessionId;
41
46
  }
42
47
 
43
48
  /**
44
49
  * Set the context path for this process. Kept for external callers.
45
50
  */
46
- export function setContextPath(contextPath: string | null): void {
51
+ export function setContextPath(contextPath: null | string): void {
47
52
  _cachedContextPath = contextPath;
48
53
  _contextResolved = true;
49
54
  }
50
55
 
51
- export function getContextPath(): string | null {
56
+ export function getContextPath(): null | string {
52
57
  if (!_contextResolved) {
53
58
  _contextResolved = true; // Don't retry
54
- }
59
+ }
60
+
55
61
  return _cachedContextPath;
56
62
  }
57
63
 
@@ -85,8 +91,8 @@ export function hookLog(
85
91
  opts?: {
86
92
  component?: string;
87
93
  data?: any;
88
- traceback_str?: string;
89
94
  stderr?: boolean;
95
+ traceback_str?: string;
90
96
  },
91
97
  ): void {
92
98
  try {
@@ -94,7 +100,7 @@ export function hookLog(
94
100
  const levelNum = LEVELS[levelLower] ?? 0;
95
101
  const component = opts?.component ?? "";
96
102
  const tracebackStr = opts?.traceback_str ?? "";
97
- const stderrEnabled = opts?.stderr !== false;
103
+ const stderrEnabled = opts?.stderr === true;
98
104
 
99
105
  // Write to stderr
100
106
  if (stderrEnabled) {
@@ -131,7 +137,8 @@ export function hookLog(
131
137
  } catch {
132
138
  entry.data = String(opts.data);
133
139
  }
134
- }
140
+ }
141
+
135
142
  if (tracebackStr) entry.tb = tracebackStr.trimEnd();
136
143
 
137
144
  const line = JSON.stringify(entry) + "\n";
@@ -146,7 +153,7 @@ export function hookLog(
146
153
  // Line-count guard: prune to last MAX_LOG_LINES
147
154
  try {
148
155
  if (fs.existsSync(logPath)) {
149
- const content = fs.readFileSync(logPath, "utf-8");
156
+ const content = fs.readFileSync(logPath, "utf8");
150
157
  const lines = content.split("\n");
151
158
  if (lines.length > MAX_LOG_LINES) {
152
159
  fs.writeFileSync(
@@ -182,6 +189,14 @@ export function logError(hookName: string, message: string, opts?: Record<string
182
189
  hookLog("error", hookName, message, opts);
183
190
  }
184
191
 
192
+ /**
193
+ * Log an error that SHOULD be visible to user/model via stderr.
194
+ * Use for real problems needing attention, not routine diagnostics.
195
+ */
196
+ export function logBlocking(hookName: string, message: string, opts?: Record<string, any>): void {
197
+ hookLog("error", hookName, message, { ...opts, stderr: true });
198
+ }
199
+
185
200
  /**
186
201
  * Log a structured diagnostic entry at a hook decision point.
187
202
  * See SPEC.md §3.8
@@ -191,11 +206,11 @@ export function logDiagnostic(
191
206
  phase: string,
192
207
  summary: string,
193
208
  opts?: {
194
- inputs?: any;
195
- decision?: any;
196
- reasoning?: any;
197
209
  component?: string;
198
210
  data?: any;
211
+ decision?: any;
212
+ inputs?: any;
213
+ reasoning?: any;
199
214
  },
200
215
  ): void {
201
216
  const diagData: Record<string, any> = { phase };
@@ -204,7 +219,8 @@ export function logDiagnostic(
204
219
  if (opts?.reasoning !== undefined) diagData.reasoning = opts.reasoning;
205
220
  if (opts?.data && typeof opts.data === "object") {
206
221
  Object.assign(diagData, opts.data);
207
- }
222
+ }
223
+
208
224
  hookLog("debug", hookName, `[DIAG:${phase}] ${summary}`, {
209
225
  component: opts?.component ?? "diag",
210
226
  data: diagData,
@@ -222,7 +238,7 @@ export function logHookError(
222
238
  tracebackStr = "",
223
239
  ): void {
224
240
  const errStr = typeof error === "string" ? error : String(error);
225
- const msg = errStr.replace(/[\n\r]/g, " ").slice(0, 200);
241
+ const msg = errStr.replaceAll(/[\n\r]/g, " ").slice(0, 200);
226
242
  const errType =
227
243
  typeof error === "object" && error !== null
228
244
  ? error.constructor.name
@@ -5,9 +5,10 @@
5
5
  */
6
6
 
7
7
  import * as fs from "node:fs";
8
- import * as path from "node:path";
9
- import { getContextDir } from "./constants.js";
8
+ import * as path from "node:path";
9
+
10
10
  import { atomicWrite } from "./atomic-write.js";
11
+ import { getContextDir } from "./constants.js";
11
12
  import { logWarn } from "./logger.js";
12
13
  import type { ContextState, Mode } from "../types.js";
13
14
 
@@ -29,7 +30,8 @@ export function toDict(state: ContextState): Record<string, unknown> {
29
30
  if (value !== null && value !== undefined) {
30
31
  result[key] = value;
31
32
  }
32
- }
33
+ }
34
+
33
35
  return result;
34
36
  }
35
37
 
@@ -52,11 +54,11 @@ export function readStateJson(
52
54
  if (!fs.existsSync(sp)) return null;
53
55
 
54
56
  try {
55
- const raw = fs.readFileSync(sp, "utf-8");
57
+ const raw = fs.readFileSync(sp, "utf8");
56
58
  const data = JSON.parse(raw) as Record<string, any>;
57
59
  return dictToState(data);
58
- } catch (e: any) {
59
- logWarn("state_io", `Failed to read state.json for '${contextId}': ${e}`);
60
+ } catch (error: any) {
61
+ logWarn("state_io", `Failed to read state.json for '${contextId}': ${error}`);
60
62
  return null;
61
63
  }
62
64
  }
@@ -69,7 +71,7 @@ export function writeStateJson(
69
71
  contextId: string,
70
72
  state: ContextState,
71
73
  projectRoot?: string,
72
- ): [boolean, string | null] {
74
+ ): [boolean, null | string] {
73
75
  const sp = statePath(contextId, projectRoot);
74
76
  const dir = path.dirname(sp);
75
77
  fs.mkdirSync(dir, { recursive: true });
@@ -14,161 +14,161 @@
14
14
 
15
15
  export const STOP_WORDS: ReadonlySet<string> = new Set([
16
16
  // ARTICLES
17
- "a", "an", "the",
17
+ "a", "about", "above",
18
18
 
19
- // PREPOSITIONS
20
- "to", "for", "in", "on", "at", "by", "with", "from", "of", "about",
21
- "into", "over", "under", "between", "through", "during", "before", "after",
22
- "above", "below", "against", "among", "around", "behind", "beside", "besides",
23
- "beyond", "down", "inside", "outside", "near", "off", "onto", "out",
24
- "since", "toward", "towards", "until", "upon", "within", "without",
25
- "across", "along", "via", "per",
26
-
27
- // PRONOUNS - Personal
28
- "i", "you", "he", "she", "it", "we", "they",
29
- "me", "him", "her", "us", "them",
30
- "myself", "yourself", "himself", "herself", "itself", "ourselves", "themselves",
19
+ "absolutely", "accordingly", "across", "active", "actually", "after", "afterwards", "against", "ago", "ah",
20
+ "aiw", "all", "allow", "almost", "along", "already", "alright", "also",
21
+ "alternatively", "although", "always", // AUXILIARY/MODAL VERBS
22
+ "am", "among", "an", // CONJUNCTIONS
23
+ "and", "another",
24
+ "any", "anybody", "anyone", "anything", "anyway", "anyways", "apparently", "appear",
25
+ "are", "aren", "arent", "args", "around", "as", "ask",
26
+ "assert", "async", "at", // SINGLE LETTERS
27
+ "b",
31
28
 
32
- // PRONOUNS - Possessive
33
- "my", "your", "his", "her", "its", "our", "their",
34
- "mine", "yours", "hers", "ours", "theirs",
29
+ "based", "basic", "basically", "bat", "be", "because", "become",
30
+ "been", "before", "begin", "behind", "being",
31
+ "below", "below", "beside", "besides", "between", "beyond", "block",
35
32
 
36
- // PRONOUNS - Demonstrative
37
- "this", "that", "these", "those",
33
+ "both", "but", "by", "c", "can", "cant", "case",
34
+ "cases", "cause", "cc", "certainly", "chunk",
38
35
 
39
- // PRONOUNS - Relative
40
- "who", "whom", "whose", "which",
41
-
42
- // PRONOUNS - Indefinite
43
- "someone", "somebody", "something", "anyone", "anybody", "anything",
44
- "everyone", "everybody", "everything", "no one", "nobody", "nothing",
45
- "one", "ones",
46
-
47
- // AUXILIARY/MODAL VERBS
48
- "am", "is", "are", "was", "were", "be", "been", "being",
49
- "have", "has", "had", "having",
50
- "do", "does", "did", "doing", "done",
51
- "can", "could", "will", "would", "shall", "should", "may", "might", "must",
52
-
53
- // CONJUNCTIONS
54
- "and", "or", "but", "nor", "so", "yet",
55
- "if", "then", "else", "whether", "unless", "although", "though",
56
- "because", "while", "whereas", "whenever", "wherever",
57
-
58
- // QUESTION WORDS
59
- "what", "when", "where", "why", "how",
60
-
61
- // ADVERBS OF PLACE/TIME
62
- "here", "there", "now", "always", "never", "often", "sometimes",
63
- "already", "still", "soon", "later", "ago", "today", "tomorrow",
64
- "yesterday", "currently", "previously", "recently", "immediately",
65
- "finally", "eventually", "meanwhile", "afterwards",
66
-
67
- // NEGATION
68
- "no", "not", "none", "neither",
69
- "don", "doesn", "didn", "won", "wouldn", "couldn", "shouldn",
70
- "isn", "aren", "wasn", "weren", "hasn", "haven", "hadn",
71
-
72
- // QUANTIFIERS
73
- "some", "any", "all", "each", "every", "both", "few", "more", "most",
74
- "many", "much", "several", "other", "another", "enough", "less", "least",
75
- "either", "such",
36
+ "clarification", "class", "clearly", "come",
76
37
 
77
- // FILLER/HEDGE WORDS
78
- "just", "also", "only", "really", "actually", "basically", "simply",
79
- "very", "quite", "rather", "pretty", "somewhat", "almost", "nearly",
80
- "exactly", "completely", "entirely", "totally", "absolutely",
81
- "probably", "possibly", "maybe", "perhaps", "definitely", "certainly",
82
- "apparently", "obviously", "clearly", "literally", "essentially",
83
-
84
- // SPEECH-TO-TEXT FILLERS (STT artifacts from voice input)
85
- "um", "uh", "ah", "oh", "hmm", "hm", "er", "eh", "huh",
86
- "hey", "hi", "hello", "yeah", "yep", "yup", "nah", "nope",
87
- "gonna", "gotta", "wanna", "kinda", "sorta",
88
- "stuff", "anyway", "anyways", "alright", "right", "well",
89
-
90
- // COMMON REQUEST PHRASES
91
- "want", "need", "help", "please", "like", "let", "get",
92
- "think", "know", "see", "try", "make", "give", "take",
93
- "look", "looking", "trying", "going", "getting", "making",
38
+ "complete", "completely", "consequently", "consider",
94
39
 
95
- // COMMON NON-ACTION VERBS
96
- "go", "come", "put", "say", "tell", "ask", "find", "keep",
97
- "seem", "appear", "become", "remain", "stay", "feel", "show", "mean",
98
- "include", "provide", "require", "allow", "expect", "cause",
99
- "follow", "consider", "continue", "start", "begin", "end",
100
- "contain", "contains",
40
+ "const", "contain", "contains", "continue", "conversely", "correct",
41
+ "correctly", "could", "couldn", "couldnt", "critical", "current",
42
+ "currently", "d",
101
43
 
102
- // LINKING/TRANSITION WORDS
103
- "however", "therefore", "thus", "hence", "otherwise", "instead",
104
- "moreover", "furthermore", "nevertheless", "nonetheless", "accordingly",
105
- "consequently", "similarly", "likewise", "conversely", "alternatively",
44
+ "def", "definitely", "dict", "did", "didn", "didnt", "different", "directory",
45
+ "do", "does", "doesn", "doesn",
46
+ "doesnt", "doing", "don", "done", "dont",
47
+ "down", "during", "e", "each", "eh", "eight", "either", "elif", "else",
106
48
 
107
- // FILE EXTENSIONS
108
- "py", "md", "ts", "json", "js", "yaml", "toml", "exe", "bat",
49
+ "empty", "end", "enough", "entirely", "eprint", "er",
50
+ "essentially", // SHORT NOISE
51
+ "etc", "eventually", "every", "everybody", "everyone", "everything",
52
+ "exactly", "example", "examples", "except", "exe",
109
53
 
110
- // COMMON CODING TERMS
111
- "using", "used", "uses",
112
- "based", "following",
113
- "same", "different", "specific", "existing", "new", "current", "first",
114
- "full", "complete", "single", "multiple", "simple",
115
- "needed", "required", "provided", "expected", "correctly",
116
- "works", "working", "work",
54
+ "existing", "expect", "expected", "f", "false",
117
55
 
118
- // STRUCTURAL WORDS
119
- "step", "steps", "phase", "below",
56
+ "feature", "features", "feel", "few", "finally", "find", "first",
57
+ "five", "folder", "follow", "following", "footer", "for", "format",
58
+ "four", "from", "full", "furthermore", "g",
59
+ "general", "get", "getting", "give",
120
60
 
121
- // QUERY LANGUAGE
122
- "questions", "question", "clarification",
61
+ // COMMON NON-ACTION VERBS
62
+ "go", "going", "gonna", "gotta",
63
+ "group", "h", "had", "hadn", "hadnt", "has", "hasn",
64
+ "hasnt", "have", "haven", "havent", "having", "he", "header",
123
65
 
124
- // OVERLY GENERIC TERMS
125
- "thing", "things", "way", "ways", "kind", "type", "types",
126
- "example", "examples", "case", "cases",
127
- "part", "parts", "point", "points",
128
- "time", "times", "next", "last",
129
- "set", "list", "group", "item", "items",
66
+ "hello", "help", "hence", "her", "her", // ADVERBS OF PLACE/TIME
67
+ "here", "heres", "hers", "herself",
68
+ "hes", "hey", "hi", // GENERIC ADJECTIVES
69
+ "high", "him", "himself", "his", "hm",
70
+ "hmm", "how",
130
71
 
131
- // PROGRAMMING KEYWORDS
132
- "self", "def", "return", "import", "true", "false", "none", "str",
133
- "const", "async", "class", "assert", "except", "dict", "len", "args",
134
- "sys", "eprint", "elif", "lambda", "yield", "pass",
72
+ // LINKING/TRANSITION WORDS
73
+ "however", "huh", // PRONOUNS - Personal
74
+ "i", "id", "if", "ill", // CONTRACTED FORMS
75
+ "im",
76
+ "immediately", "import", "important", "in", "include", "index", // GENERIC TECHNICAL NOUNS
77
+ "information",
78
+ "inside", "instead", "into", "is", "isn",
79
+ "isnt", "issue", "it", "item", "items", "its",
80
+ "itself", "ive", "j", "js", "json",
135
81
 
136
- // GENERIC ADJECTIVES
137
- "high", "low", "important", "critical", "optional", "manual",
138
- "real", "empty", "stable", "active", "proper", "correct",
139
- "basic", "main", "primary", "secondary", "general", "overall",
82
+ // FILLER/HEDGE WORDS
83
+ "just", "k", "keep", "kind", "kinda", "know", "l", "lambda", "last",
84
+ "later", "least", "len", "less", "let", "lets", "level", "like",
85
+ "likewise", "line", "lines", "list", "literally",
86
+ "ll", "look", "looking", "low", "m", "main",
140
87
 
141
- // GENERIC TECHNICAL NOUNS
142
- "information", "format", "status", "method", "purpose", "result",
143
- "source", "value", "option", "options", "feature", "features", "issue",
144
- "process", "version", "mode", "state",
88
+ "make", "making", "manual", "many", "may", "maybe", "md",
89
+ "me", "mean", "meanwhile", "method", "might", "mine", "mode",
90
+ "more", "moreover", "most", "much", "multiple", "must",
145
91
 
146
- // DOCUMENT/CODE STRUCTURE
147
- "section", "lines", "line", "folder", "directory", "index",
148
- "level", "block", "chunk", "region", "header", "footer",
92
+ // PRONOUNS - Possessive
93
+ "my", "myself", "n", "nah", "near", "nearly", "need", "needed",
94
+ "neither", "never", "nevertheless", "new", "next", "nine", // NEGATION
95
+ "no", "nobody",
96
+ "none", "none", "nonetheless", "no one", "nope", "nor",
97
+ "not", "nothing", "now", "o", "obviously", "of",
98
+ "off", "often",
99
+
100
+ "oh", "ok", "okay", "on", "one", "ones",
101
+ "only", "onto", "option", "optional", "options",
102
+ "or", "other", "otherwise", "our", "ours",
103
+
104
+ "ourselves", "out", "outside", "over", "overall", "p", "part", "parts", "pass",
105
+
106
+ "per", "perhaps", "phase",
107
+ "pl", "please",
108
+ "point", "points", "possibly", "pretty", "previously", "primary", "probably",
109
+ "process", "proper", "provide", "provided", "purpose",
110
+ "put", // FILE EXTENSIONS
111
+ "py", "q", "question", // QUERY LANGUAGE
112
+ "questions",
113
+ "quite", "r", "rather",
149
114
 
150
115
  // FRAGMENT WORDS
151
- "re", "pl", "aiw", "ve", "ll", "doesn", "t", "s",
116
+ "re", "real", "really", "recently",
117
+
118
+ "region", "remain", "require",
152
119
 
153
- // CONTRACTED FORMS
154
- "im", "ive", "id", "ill", "youre", "youve", "youll",
155
- "hes", "shes", "weve", "theyre", "theyve", "dont", "doesnt",
156
- "didnt", "wont", "wouldnt", "cant", "couldnt", "shouldnt", "isnt",
157
- "arent", "wasnt", "werent", "hasnt", "havent", "hadnt", "lets",
158
- "thats", "whats", "heres", "theres", "whos",
120
+ "required", "result", "return", "right", "s", "same", "say",
121
+ "secondary", // DOCUMENT/CODE STRUCTURE
122
+ "section", "see", "seem",
123
+ // PROGRAMMING KEYWORDS
124
+ "self", "set", "seven", "several",
125
+ "shall", "she", "shes", "should",
126
+ "shouldn", "shouldnt", "show", "similarly", "simple",
127
+
128
+ "simply", "since", "single", "six", "so", // QUANTIFIERS
129
+ "some", "somebody", // PRONOUNS - Indefinite
130
+ "someone",
131
+ "something", "sometimes", "somewhat", "soon", "sorta", "source", "specific", "stable",
132
+ "start", "state", "status", "stay", // STRUCTURAL WORDS
133
+ "step", "steps",
134
+
135
+ "still", "str", "stuff", "such", "sys", "t",
136
+ "take", "tell", "ten", "that", "thats", "the",
137
+ "their", "theirs", "them", "themselves", "then", "there",
138
+
139
+ "therefore", "theres", "these", "they", "theyre", "theyve",
140
+ // OVERLY GENERIC TERMS
141
+ "thing", "things", "think", // PRONOUNS - Demonstrative
142
+ "this", "those", "though", "three",
143
+ "through", "thus", "time", "times",
159
144
 
160
- // SHORT NOISE
161
- "etc", "up", "as", "cc",
145
+ // PREPOSITIONS
146
+ "to", "today", "toml", "tomorrow", // SHORT FILLER
147
+ "too", "totally",
148
+ "toward", "towards", "true", "try", "trying", "ts",
162
149
 
163
150
  // NUMBER WORDS
164
- "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten",
151
+ "two", "type", "types", "u", "uh", // SPEECH-TO-TEXT FILLERS (STT artifacts from voice input)
152
+ "um", "under", "unless",
153
+
154
+ "until", "up", "upon", "us", "used", "uses", // COMMON CODING TERMS
155
+ "using",
156
+ "v", "value", "ve", "version", "very", "via", "w",
157
+ "wanna", // COMMON REQUEST PHRASES
158
+ "want", "was", "wasn", "wasnt", "way", "ways",
159
+ "we", "well", "were", "weren", "werent", "weve", // QUESTION WORDS
160
+ "what",
161
+ "whats", "when", "whenever", "where", "whereas",
162
+
163
+ "wherever", "whether", "which", "while",
164
+
165
+ // PRONOUNS - Relative
166
+ "who", "whom", "whos", "whose", "why", "will", "with", "within", "without",
165
167
 
166
- // SINGLE LETTERS
167
- "b", "c", "d", "e", "f", "g", "h", "j", "k", "l", "m", "n", "o", "p",
168
- "q", "r", "u", "v", "w", "x", "y", "z",
168
+ "won", "wont", "work", "working", "works", "would", "wouldn", "wouldnt", "x", "y", "yaml", "yeah", "yep", "yes",
169
+ "yesterday", "yet", "yield", "you", "youll", "your", "youre", "yours",
169
170
 
170
- // SHORT FILLER
171
- "too", "yes", "ok", "okay",
171
+ "yourself", "youve", "yup", "z",
172
172
  ]);
173
173
 
174
174
  /**
@@ -3,6 +3,8 @@
3
3
  * See SPEC.md §5.10
4
4
  */
5
5
 
6
+ import { execFile, execSync } from "node:child_process";
7
+
6
8
  /**
7
9
  * Check if this is an internal subprocess call.
8
10
  * All hooks should check this and return early to prevent recursion.
@@ -21,3 +23,140 @@ export function getInternalSubprocessEnv(): Record<string, string | undefined> {
21
23
  AIWCLI_INTERNAL_CALL: "true",
22
24
  };
23
25
  }
26
+
27
+ /**
28
+ * Find an executable on the system PATH.
29
+ * Uses `where` on Windows, `which` on Unix.
30
+ * On Windows, prefers .cmd/.exe over extensionless shims since
31
+ * execFileSync cannot spawn extensionless shell scripts.
32
+ * Returns the first match or null if not found.
33
+ */
34
+ export function findExecutable(name: string): null | string {
35
+ try {
36
+ const cmd = process.platform === "win32" ? `where ${name}` : `which ${name}`;
37
+ const lines = execSync(cmd, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] })
38
+ .trim()
39
+ .split(/\r?\n/)
40
+ .map((l) => l.trim())
41
+ .filter(Boolean);
42
+
43
+ if (lines.length === 0) return null;
44
+
45
+ // On Windows, `where` may return an extensionless shim first (e.g. npm creates
46
+ // both `claude` and `claude.cmd`). execFileSync can't spawn the extensionless
47
+ // one, so prefer .cmd or .exe.
48
+ if (process.platform === "win32") {
49
+ const preferred = lines.find((l) => /\.(cmd|exe)$/i.test(l));
50
+ return preferred ?? lines[0] ?? null;
51
+ }
52
+
53
+ return lines[0] ?? null;
54
+ } catch {
55
+ return null;
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Type guard for Node.js child_process exec errors.
61
+ * ExecSync throws objects with these extra properties on non-zero exit or timeout.
62
+ */
63
+ export interface ExecSyncError {
64
+ killed: boolean;
65
+ message: string;
66
+ signal: null | string;
67
+ status: null | number;
68
+ stderr: Buffer | string;
69
+ stdout: Buffer | string;
70
+ }
71
+
72
+ /** Check if an unknown error is an ExecSync error with process info. */
73
+ export function isExecSyncError(e: unknown): e is ExecSyncError {
74
+ return (
75
+ typeof e === "object" &&
76
+ e !== null &&
77
+ "killed" in e &&
78
+ "signal" in e
79
+ );
80
+ }
81
+
82
+ // ---------------------------------------------------------------------------
83
+ // Async Subprocess Execution
84
+ // ---------------------------------------------------------------------------
85
+
86
+ /**
87
+ * Result from an async subprocess execution.
88
+ * Never throws — callers inspect fields to determine outcome.
89
+ */
90
+ export interface ExecResult {
91
+ exitCode: number;
92
+ killed: boolean;
93
+ signal: null | string;
94
+ stderr: string;
95
+ stdout: string;
96
+ }
97
+
98
+ /** Options for execFileAsync. */
99
+ export interface ExecAsyncOptions {
100
+ /** Environment variables for the child process. */
101
+ env?: Record<string, string | undefined>;
102
+ /** Data piped to the child's stdin. */
103
+ input?: string;
104
+ /** Maximum bytes on stdout/stderr. Default: 10 MB. */
105
+ maxBuffer?: number;
106
+ /** Timeout in milliseconds (not seconds). */
107
+ timeout?: number;
108
+ }
109
+
110
+ /**
111
+ * Async subprocess execution that does NOT block the event loop.
112
+ * Drop-in replacement for execFileSync in Promise-based parallel patterns.
113
+ *
114
+ * Returns ExecResult on both success and non-zero exit.
115
+ * On timeout: result.killed = true, result.signal = "SIGTERM".
116
+ * On spawn failure: result.exitCode = -1, result.stderr contains error.
117
+ */
118
+ export function execFileAsync(
119
+ file: string,
120
+ args: string[],
121
+ options?: ExecAsyncOptions,
122
+ ): Promise<ExecResult> {
123
+ return new Promise((resolve) => {
124
+ const child = execFile(
125
+ file,
126
+ args,
127
+ {
128
+ encoding: "utf-8",
129
+ timeout: options?.timeout ?? 0,
130
+ env: options?.env as NodeJS.ProcessEnv,
131
+ maxBuffer: options?.maxBuffer ?? 10 * 1024 * 1024,
132
+ },
133
+ (error, stdout, stderr) => {
134
+ if (error) {
135
+ // execFile callback error includes process exit info
136
+ const errObj = error as unknown as Record<string, unknown>;
137
+ resolve({
138
+ stdout: String(stdout ?? ""),
139
+ stderr: String(stderr ?? ""),
140
+ exitCode: typeof errObj.code === "number" ? errObj.code : (error as any).status ?? 1,
141
+ killed: Boolean(errObj.killed),
142
+ signal: typeof errObj.signal === "string" ? errObj.signal : null,
143
+ });
144
+ } else {
145
+ resolve({
146
+ stdout: String(stdout ?? ""),
147
+ stderr: String(stderr ?? ""),
148
+ exitCode: 0,
149
+ killed: false,
150
+ signal: null,
151
+ });
152
+ }
153
+ },
154
+ );
155
+
156
+ // Pipe input to stdin if provided
157
+ if (options?.input !== null && options?.input !== undefined && child.stdin) {
158
+ child.stdin.write(options.input);
159
+ child.stdin.end();
160
+ }
161
+ });
162
+ }