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.
- package/bin/run.js +1 -1
- package/dist/commands/clear.js +28 -131
- package/dist/commands/init/index.js +3 -3
- package/dist/lib/gitignore-manager.d.ts +32 -0
- package/dist/lib/gitignore-manager.js +141 -2
- package/dist/templates/CLAUDE.md +8 -8
- package/dist/templates/_shared/.claude/commands/handoff-resume.md +64 -0
- package/dist/templates/_shared/.claude/commands/handoff.md +16 -10
- package/dist/templates/_shared/.claude/settings.json +7 -7
- package/dist/templates/_shared/hooks-ts/_utils/git-state.ts +2 -0
- package/dist/templates/_shared/hooks-ts/archive_plan.ts +159 -0
- package/dist/templates/_shared/hooks-ts/context_monitor.ts +147 -0
- package/dist/templates/_shared/hooks-ts/file-suggestion.ts +130 -0
- package/dist/templates/_shared/hooks-ts/pre_compact.ts +49 -0
- package/dist/templates/_shared/hooks-ts/session_end.ts +104 -0
- package/dist/templates/_shared/hooks-ts/session_start.ts +144 -0
- package/dist/templates/_shared/hooks-ts/task_create_capture.ts +48 -0
- package/dist/templates/_shared/hooks-ts/task_update_capture.ts +74 -0
- package/dist/templates/_shared/hooks-ts/user_prompt_submit.ts +83 -0
- package/dist/templates/_shared/lib-ts/CLAUDE.md +318 -0
- package/dist/templates/_shared/lib-ts/base/atomic-write.ts +12 -12
- package/dist/templates/_shared/lib-ts/base/constants.ts +22 -15
- package/dist/templates/_shared/lib-ts/base/hook-utils.ts +129 -50
- package/dist/templates/_shared/lib-ts/base/inference.ts +28 -21
- package/dist/templates/_shared/lib-ts/base/logger.ts +31 -15
- package/dist/templates/_shared/lib-ts/base/state-io.ts +9 -7
- package/dist/templates/_shared/lib-ts/base/stop-words.ts +131 -131
- package/dist/templates/_shared/lib-ts/base/subprocess-utils.ts +139 -0
- package/dist/templates/_shared/lib-ts/base/utils.ts +69 -69
- package/dist/templates/_shared/lib-ts/context/context-formatter.ts +30 -24
- package/dist/templates/_shared/lib-ts/context/context-selector.ts +50 -32
- package/dist/templates/_shared/lib-ts/context/context-store.ts +76 -48
- package/dist/templates/_shared/lib-ts/context/plan-manager.ts +61 -37
- package/dist/templates/_shared/lib-ts/context/task-tracker.ts +10 -6
- package/dist/templates/_shared/lib-ts/handoff/document-generator.ts +11 -10
- package/dist/templates/_shared/lib-ts/handoff/handoff-reader.ts +159 -0
- package/dist/templates/_shared/lib-ts/templates/formatters.ts +6 -4
- package/dist/templates/_shared/lib-ts/types.ts +68 -55
- package/dist/templates/_shared/scripts/resolve_context.ts +24 -0
- package/dist/templates/_shared/scripts/resume_handoff.ts +321 -0
- package/dist/templates/_shared/scripts/save_handoff.ts +21 -21
- package/dist/templates/_shared/scripts/status_line.ts +733 -0
- package/dist/templates/cc-native/.claude/settings.json +175 -185
- package/dist/templates/cc-native/TEMPLATE-SCHEMA.md +15 -17
- package/dist/templates/cc-native/_cc-native/agents/CLAUDE.md +0 -2
- package/dist/templates/cc-native/_cc-native/hooks/CLAUDE.md +109 -135
- package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.ts +119 -0
- package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.ts +921 -0
- package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.ts +61 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/aggregate-agents.ts +157 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/artifacts.ts +709 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/cc-native-state.ts +199 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/cli-output-parser.ts +124 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/config.ts +57 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/constants.ts +83 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/debug.ts +80 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/index.ts +119 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/json-parser.ts +162 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/nul +3 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/orchestrator.ts +249 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/agent.ts +155 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/codex.ts +130 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/gemini.ts +106 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/index.ts +10 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/types.ts +23 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +243 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/tsconfig.json +18 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +310 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/verdict.ts +72 -0
- package/dist/templates/cc-native/_cc-native/plan-review.config.json +1 -9
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
- package/dist/templates/_shared/hooks/__init__.py +0 -16
- package/dist/templates/_shared/hooks/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/archive_plan.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/context_enforcer.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/context_monitor.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/file-suggestion.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/pre_compact.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/session_end.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/session_start.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/task_create_atomicity.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/task_create_capture.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/task_update_capture.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/user_prompt_submit.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/archive_plan.py +0 -177
- package/dist/templates/_shared/hooks/context_monitor.py +0 -270
- package/dist/templates/_shared/hooks/file-suggestion.py +0 -215
- package/dist/templates/_shared/hooks/pre_compact.py +0 -104
- package/dist/templates/_shared/hooks/session_end.py +0 -173
- package/dist/templates/_shared/hooks/session_start.py +0 -206
- package/dist/templates/_shared/hooks/task_create_capture.py +0 -108
- package/dist/templates/_shared/hooks/task_update_capture.py +0 -145
- package/dist/templates/_shared/hooks/user_prompt_submit.py +0 -139
- package/dist/templates/_shared/lib/__init__.py +0 -1
- package/dist/templates/_shared/lib/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__init__.py +0 -65
- package/dist/templates/_shared/lib/base/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/atomic_write.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/constants.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/hook_utils.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/inference.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/logger.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/stop_words.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/subprocess_utils.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/utils.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/atomic_write.py +0 -180
- package/dist/templates/_shared/lib/base/constants.py +0 -358
- package/dist/templates/_shared/lib/base/hook_utils.py +0 -339
- package/dist/templates/_shared/lib/base/inference.py +0 -307
- package/dist/templates/_shared/lib/base/logger.py +0 -305
- package/dist/templates/_shared/lib/base/stop_words.py +0 -221
- package/dist/templates/_shared/lib/base/subprocess_utils.py +0 -46
- package/dist/templates/_shared/lib/base/utils.py +0 -263
- package/dist/templates/_shared/lib/context/__init__.py +0 -102
- package/dist/templates/_shared/lib/context/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/cache.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/context_extractor.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/context_formatter.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/context_manager.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/context_selector.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/context_store.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/discovery.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/event_log.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/plan_archive.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/plan_manager.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/task_sync.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/task_tracker.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/context_formatter.py +0 -317
- package/dist/templates/_shared/lib/context/context_selector.py +0 -508
- package/dist/templates/_shared/lib/context/context_store.py +0 -653
- package/dist/templates/_shared/lib/context/plan_manager.py +0 -303
- package/dist/templates/_shared/lib/context/task_tracker.py +0 -188
- package/dist/templates/_shared/lib/handoff/__init__.py +0 -22
- package/dist/templates/_shared/lib/handoff/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/handoff/__pycache__/document_generator.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/handoff/document_generator.py +0 -278
- package/dist/templates/_shared/lib/templates/README.md +0 -206
- package/dist/templates/_shared/lib/templates/__init__.py +0 -36
- package/dist/templates/_shared/lib/templates/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/templates/__pycache__/formatters.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/templates/__pycache__/persona_questions.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/templates/__pycache__/plan_context.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/templates/formatters.py +0 -146
- package/dist/templates/_shared/lib/templates/plan_context.py +0 -73
- package/dist/templates/_shared/scripts/__pycache__/save_handoff.cpython-313.pyc +0 -0
- package/dist/templates/_shared/scripts/__pycache__/status_line.cpython-313.pyc +0 -0
- package/dist/templates/_shared/scripts/save_handoff.py +0 -357
- package/dist/templates/_shared/scripts/status_line.py +0 -716
- package/dist/templates/cc-native/.claude/commands/cc-native/fresh-perspective.md +0 -8
- package/dist/templates/cc-native/.windsurf/workflows/cc-native/fresh-perspective.md +0 -8
- package/dist/templates/cc-native/MIGRATION.md +0 -86
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/add_plan_context.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/cc-native-plan-review.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/mark_questions_asked.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/plan_accepted.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/plan_questions_early.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/suggest-fresh-perspective.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.py +0 -130
- package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.py +0 -954
- package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.py +0 -81
- package/dist/templates/cc-native/_cc-native/hooks/suggest-fresh-perspective.py +0 -340
- package/dist/templates/cc-native/_cc-native/lib/CLAUDE.md +0 -265
- package/dist/templates/cc-native/_cc-native/lib/__init__.py +0 -53
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/atomic_write.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/constants.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/debug.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/orchestrator.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/state.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/utils.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/constants.py +0 -45
- package/dist/templates/cc-native/_cc-native/lib/debug.py +0 -139
- package/dist/templates/cc-native/_cc-native/lib/orchestrator.py +0 -362
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__init__.py +0 -28
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/agent.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/base.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/codex.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/gemini.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/agent.py +0 -215
- package/dist/templates/cc-native/_cc-native/lib/reviewers/base.py +0 -88
- package/dist/templates/cc-native/_cc-native/lib/reviewers/codex.py +0 -124
- package/dist/templates/cc-native/_cc-native/lib/reviewers/gemini.py +0 -108
- package/dist/templates/cc-native/_cc-native/lib/state.py +0 -268
- package/dist/templates/cc-native/_cc-native/lib/utils.py +0 -1071
- package/dist/templates/cc-native/_cc-native/scripts/__pycache__/aggregate_agents.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/scripts/aggregate_agents.py +0 -168
- 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:
|
|
35
|
+
let _cachedSessionId: null | string = null;
|
|
31
36
|
|
|
32
37
|
// Module-level context path cache (kept for external callers)
|
|
33
|
-
let _cachedContextPath:
|
|
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:
|
|
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:
|
|
51
|
+
export function setContextPath(contextPath: null | string): void {
|
|
47
52
|
_cachedContextPath = contextPath;
|
|
48
53
|
_contextResolved = true;
|
|
49
54
|
}
|
|
50
55
|
|
|
51
|
-
export function getContextPath():
|
|
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
|
|
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, "
|
|
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.
|
|
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
|
-
|
|
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, "
|
|
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 (
|
|
59
|
-
logWarn("state_io", `Failed to read state.json for '${contextId}': ${
|
|
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,
|
|
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", "
|
|
17
|
+
"a", "about", "above",
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
33
|
-
"
|
|
34
|
-
"
|
|
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
|
-
|
|
37
|
-
"
|
|
33
|
+
"both", "but", "by", "c", "can", "cant", "case",
|
|
34
|
+
"cases", "cause", "cc", "certainly", "chunk",
|
|
38
35
|
|
|
39
|
-
|
|
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
|
-
|
|
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
|
-
|
|
96
|
-
"
|
|
97
|
-
"
|
|
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
|
-
|
|
103
|
-
"
|
|
104
|
-
"
|
|
105
|
-
"
|
|
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
|
-
|
|
108
|
-
"
|
|
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
|
-
|
|
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
|
-
|
|
119
|
-
"
|
|
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
|
-
//
|
|
122
|
-
"
|
|
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
|
-
//
|
|
125
|
-
"
|
|
126
|
-
"
|
|
127
|
-
"
|
|
128
|
-
"
|
|
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
|
-
//
|
|
132
|
-
"
|
|
133
|
-
"
|
|
134
|
-
"
|
|
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
|
-
//
|
|
137
|
-
"
|
|
138
|
-
"
|
|
139
|
-
"
|
|
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
|
-
|
|
142
|
-
"
|
|
143
|
-
"
|
|
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
|
-
//
|
|
147
|
-
"
|
|
148
|
-
"
|
|
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", "
|
|
116
|
+
"re", "real", "really", "recently",
|
|
117
|
+
|
|
118
|
+
"region", "remain", "require",
|
|
152
119
|
|
|
153
|
-
|
|
154
|
-
"
|
|
155
|
-
"
|
|
156
|
-
|
|
157
|
-
"
|
|
158
|
-
"
|
|
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
|
-
//
|
|
161
|
-
"
|
|
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", "
|
|
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
|
-
|
|
167
|
-
"
|
|
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
|
-
|
|
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
|
+
}
|