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.
- package/bin/dev.cmd +3 -3
- package/bin/dev.js +16 -16
- package/bin/run.cmd +3 -3
- package/bin/run.js +21 -21
- package/dist/commands/branch.js +7 -2
- package/dist/lib/bmad-installer.js +37 -37
- package/dist/lib/terminal.d.ts +2 -0
- package/dist/lib/terminal.js +57 -7
- package/dist/templates/CLAUDE.md +205 -205
- package/dist/templates/_shared/.claude/commands/handoff-resume.md +12 -64
- package/dist/templates/_shared/.claude/commands/handoff.md +12 -198
- package/dist/templates/_shared/.claude/settings.json +65 -65
- package/dist/templates/_shared/.codex/workflows/handoff.md +226 -226
- package/dist/templates/_shared/.windsurf/workflows/handoff.md +226 -226
- package/dist/templates/_shared/handoff-system/CLAUDE.md +421 -0
- package/dist/templates/_shared/{lib-ts/handoff → handoff-system/lib}/document-generator.ts +215 -216
- package/dist/templates/_shared/{lib-ts/handoff → handoff-system/lib}/handoff-reader.ts +157 -158
- package/dist/templates/_shared/{scripts → handoff-system/scripts}/resume_handoff.ts +373 -373
- package/dist/templates/_shared/{scripts → handoff-system/scripts}/save_handoff.ts +469 -358
- package/dist/templates/_shared/handoff-system/workflows/handoff-resume.md +66 -0
- package/dist/templates/_shared/{workflows → handoff-system/workflows}/handoff.md +254 -254
- package/dist/templates/_shared/hooks-ts/_utils/git-state.ts +2 -2
- package/dist/templates/_shared/hooks-ts/archive_plan.ts +159 -159
- package/dist/templates/_shared/hooks-ts/context_monitor.ts +147 -147
- package/dist/templates/_shared/hooks-ts/file-suggestion.ts +128 -128
- package/dist/templates/_shared/hooks-ts/pre_compact.ts +49 -49
- package/dist/templates/_shared/hooks-ts/session_end.ts +196 -183
- package/dist/templates/_shared/hooks-ts/session_start.ts +163 -151
- package/dist/templates/_shared/hooks-ts/task_create_capture.ts +48 -48
- package/dist/templates/_shared/hooks-ts/task_update_capture.ts +74 -74
- package/dist/templates/_shared/hooks-ts/user_prompt_submit.ts +93 -93
- package/dist/templates/_shared/lib-ts/CLAUDE.md +367 -367
- package/dist/templates/_shared/lib-ts/base/atomic-write.ts +138 -138
- package/dist/templates/_shared/lib-ts/base/constants.ts +303 -303
- package/dist/templates/_shared/lib-ts/base/git-state.ts +58 -58
- package/dist/templates/_shared/lib-ts/base/hook-utils.ts +582 -582
- package/dist/templates/_shared/lib-ts/base/inference.ts +301 -301
- package/dist/templates/_shared/lib-ts/base/logger.ts +247 -247
- package/dist/templates/_shared/lib-ts/base/state-io.ts +202 -130
- package/dist/templates/_shared/lib-ts/base/stop-words.ts +184 -184
- package/dist/templates/_shared/lib-ts/base/subprocess-utils.ts +56 -0
- package/dist/templates/_shared/lib-ts/base/utils.ts +184 -184
- package/dist/templates/_shared/lib-ts/context/context-formatter.ts +566 -560
- package/dist/templates/_shared/lib-ts/context/context-selector.ts +524 -515
- package/dist/templates/_shared/lib-ts/context/context-store.ts +712 -668
- package/dist/templates/_shared/lib-ts/context/plan-manager.ts +312 -312
- package/dist/templates/_shared/lib-ts/context/task-tracker.ts +185 -185
- package/dist/templates/_shared/lib-ts/package.json +20 -20
- package/dist/templates/_shared/lib-ts/templates/formatters.ts +102 -102
- package/dist/templates/_shared/lib-ts/templates/plan-context.ts +58 -58
- package/dist/templates/_shared/lib-ts/tsconfig.json +13 -13
- package/dist/templates/_shared/lib-ts/types.ts +186 -180
- package/dist/templates/_shared/scripts/resolve_context.ts +33 -33
- package/dist/templates/_shared/scripts/status_line.ts +690 -690
- package/dist/templates/cc-native/.claude/commands/{rlm → cc-native/rlm}/ask.md +136 -136
- package/dist/templates/cc-native/.claude/commands/{rlm → cc-native/rlm}/index.md +21 -21
- package/dist/templates/cc-native/.claude/commands/{rlm → cc-native/rlm}/overview.md +56 -56
- package/dist/templates/cc-native/.claude/commands/cc-native/specdev.md +10 -10
- package/dist/templates/cc-native/.windsurf/workflows/cc-native/fix.md +8 -8
- package/dist/templates/cc-native/.windsurf/workflows/cc-native/implement.md +8 -8
- package/dist/templates/cc-native/.windsurf/workflows/cc-native/research.md +8 -8
- package/dist/templates/cc-native/CC-NATIVE-README.md +189 -189
- package/dist/templates/cc-native/TEMPLATE-SCHEMA.md +304 -304
- package/dist/templates/cc-native/_cc-native/agents/CLAUDE.md +143 -143
- package/dist/templates/cc-native/_cc-native/agents/PLAN-ORCHESTRATOR.md +213 -213
- package/dist/templates/cc-native/_cc-native/agents/plan-questions/PLAN-QUESTIONER.md +70 -70
- package/dist/templates/cc-native/_cc-native/cc-native.config.json +96 -96
- package/dist/templates/cc-native/_cc-native/hooks/CLAUDE.md +247 -247
- package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.ts +76 -76
- package/dist/templates/cc-native/_cc-native/hooks/enhance_plan_post_subagent.ts +54 -54
- package/dist/templates/cc-native/_cc-native/hooks/enhance_plan_post_write.ts +51 -51
- package/dist/templates/cc-native/_cc-native/hooks/mark_questions_asked.ts +53 -53
- package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.ts +61 -61
- package/dist/templates/cc-native/_cc-native/lib-ts/agent-selection.ts +163 -163
- package/dist/templates/cc-native/_cc-native/lib-ts/aggregate-agents.ts +156 -156
- package/dist/templates/cc-native/_cc-native/lib-ts/artifacts/format.ts +597 -597
- package/dist/templates/cc-native/_cc-native/lib-ts/artifacts/index.ts +26 -26
- package/dist/templates/cc-native/_cc-native/lib-ts/artifacts/tracker.ts +107 -107
- package/dist/templates/cc-native/_cc-native/lib-ts/artifacts/write.ts +119 -119
- package/dist/templates/cc-native/_cc-native/lib-ts/artifacts.ts +21 -21
- package/dist/templates/cc-native/_cc-native/lib-ts/cc-native-state.ts +319 -319
- package/dist/templates/cc-native/_cc-native/lib-ts/cli-output-parser.ts +144 -144
- package/dist/templates/cc-native/_cc-native/lib-ts/config.ts +57 -57
- package/dist/templates/cc-native/_cc-native/lib-ts/constants.ts +83 -83
- package/dist/templates/cc-native/_cc-native/lib-ts/corroboration.ts +119 -119
- package/dist/templates/cc-native/_cc-native/lib-ts/debug.ts +79 -79
- package/dist/templates/cc-native/_cc-native/lib-ts/graduation.ts +132 -132
- package/dist/templates/cc-native/_cc-native/lib-ts/index.ts +116 -116
- package/dist/templates/cc-native/_cc-native/lib-ts/json-parser.ts +168 -168
- package/dist/templates/cc-native/_cc-native/lib-ts/orchestrator.ts +70 -70
- package/dist/templates/cc-native/_cc-native/lib-ts/output-builder.ts +130 -130
- package/dist/templates/cc-native/_cc-native/lib-ts/plan-discovery.ts +80 -80
- package/dist/templates/cc-native/_cc-native/lib-ts/plan-enhancement.ts +41 -41
- package/dist/templates/cc-native/_cc-native/lib-ts/plan-questions.ts +101 -101
- package/dist/templates/cc-native/_cc-native/lib-ts/review-pipeline.ts +511 -511
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/agent.ts +71 -71
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/base/base-agent.ts +217 -217
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/index.ts +12 -12
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/claude-agent.ts +66 -65
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/codex-agent.ts +184 -184
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/gemini-agent.ts +39 -39
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/orchestrator-claude-agent.ts +196 -195
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/schemas.ts +201 -201
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/types.ts +21 -21
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/CLAUDE.md +480 -480
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/embedding-indexer.ts +287 -287
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/hyde.ts +148 -148
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/index.ts +54 -54
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/logger.ts +58 -58
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/ollama-client.ts +208 -208
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/retrieval-pipeline.ts +460 -460
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/transcript-indexer.ts +446 -447
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/transcript-loader.ts +280 -280
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/transcript-searcher.ts +274 -274
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/types.ts +201 -201
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/vector-store.ts +278 -278
- package/dist/templates/cc-native/_cc-native/lib-ts/settings.ts +184 -184
- package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +275 -275
- package/dist/templates/cc-native/_cc-native/lib-ts/tsconfig.json +18 -18
- package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +329 -329
- package/dist/templates/cc-native/_cc-native/lib-ts/verdict.ts +72 -72
- package/dist/templates/cc-native/_cc-native/workflows/specdev.md +9 -9
- package/oclif.manifest.json +1 -1
- package/package.json +108 -108
- package/dist/templates/cc-native/_cc-native/lib-ts/nul +0 -3
|
@@ -1,247 +1,247 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unified logging for all hooks and libraries.
|
|
3
|
-
*
|
|
4
|
-
* Log format: JSONL (one JSON object per line)
|
|
5
|
-
* Log location: _output/hook-log.jsonl (global, all sessions)
|
|
6
|
-
* Filter by session using the "sid" field.
|
|
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
|
-
*
|
|
13
|
-
* Environment variables:
|
|
14
|
-
* - HOOK_LOG_DISABLE=1: Disable all file logging
|
|
15
|
-
* - HOOK_LOG_LEVEL=warn: Minimum level to log (default: debug)
|
|
16
|
-
* - HOOK_ERROR_LOG_DISABLE=1: Legacy alias for HOOK_LOG_DISABLE
|
|
17
|
-
*
|
|
18
|
-
* Never throws. No buffering. Stdlib only.
|
|
19
|
-
* See SPEC.md §3
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
import * as fs from "node:fs";
|
|
23
|
-
import * as path from "node:path";
|
|
24
|
-
|
|
25
|
-
const LEVELS: Record<string, number> = {
|
|
26
|
-
debug: 0,
|
|
27
|
-
info: 1,
|
|
28
|
-
warn: 2,
|
|
29
|
-
error: 3,
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
const MAX_LOG_LINES = 10_000; // Max lines in global log before pruning
|
|
33
|
-
|
|
34
|
-
// Module-level session ID cache
|
|
35
|
-
let _cachedSessionId: string | null = null;
|
|
36
|
-
|
|
37
|
-
// Module-level context path cache (kept for external callers)
|
|
38
|
-
let _cachedContextPath: string | null = null;
|
|
39
|
-
let _contextResolved = false;
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Set the session ID for this process. All subsequent log calls include it.
|
|
43
|
-
*/
|
|
44
|
-
export function setSessionId(sessionId: string | null): void {
|
|
45
|
-
_cachedSessionId = sessionId;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Set the context path for this process. Kept for external callers.
|
|
50
|
-
*/
|
|
51
|
-
export function setContextPath(contextPath: string | null): void {
|
|
52
|
-
_cachedContextPath = contextPath;
|
|
53
|
-
_contextResolved = true;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export function getContextPath(): string | null {
|
|
57
|
-
if (!_contextResolved) {
|
|
58
|
-
_contextResolved = true; // Don't retry
|
|
59
|
-
}
|
|
60
|
-
return _cachedContextPath;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function getMinLevel(): number {
|
|
64
|
-
const env = (process.env.HOOK_LOG_LEVEL ?? "debug").toLowerCase();
|
|
65
|
-
return LEVELS[env] ?? 0;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function isDisabled(): boolean {
|
|
69
|
-
return (
|
|
70
|
-
process.env.HOOK_LOG_DISABLE === "1" ||
|
|
71
|
-
process.env.HOOK_ERROR_LOG_DISABLE === "1" ||
|
|
72
|
-
process.env.CCNATIVE_DEBUG_DISABLE === "1"
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function getProjectRoot(): string {
|
|
77
|
-
return process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Write a structured log entry to the global hook log.
|
|
82
|
-
*
|
|
83
|
-
* All entries go to _output/hook-log.jsonl. Use the "sid" field
|
|
84
|
-
* (set via setSessionId) to filter by session.
|
|
85
|
-
*/
|
|
86
|
-
export function hookLog(
|
|
87
|
-
level: string,
|
|
88
|
-
hookName: string,
|
|
89
|
-
message: string,
|
|
90
|
-
opts?: {
|
|
91
|
-
component?: string;
|
|
92
|
-
data?: any;
|
|
93
|
-
traceback_str?: string;
|
|
94
|
-
stderr?: boolean;
|
|
95
|
-
},
|
|
96
|
-
): void {
|
|
97
|
-
try {
|
|
98
|
-
const levelLower = level.toLowerCase();
|
|
99
|
-
const levelNum = LEVELS[levelLower] ?? 0;
|
|
100
|
-
const component = opts?.component ?? "";
|
|
101
|
-
const tracebackStr = opts?.traceback_str ?? "";
|
|
102
|
-
const stderrEnabled = opts?.stderr === true;
|
|
103
|
-
|
|
104
|
-
// Write to stderr
|
|
105
|
-
if (stderrEnabled) {
|
|
106
|
-
const prefix = component
|
|
107
|
-
? `[${hookName}:${component}]`
|
|
108
|
-
: `[${hookName}]`;
|
|
109
|
-
process.stderr.write(`${prefix} ${message}\n`);
|
|
110
|
-
if (tracebackStr) {
|
|
111
|
-
process.stderr.write(tracebackStr + "\n");
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Check if file logging is enabled
|
|
116
|
-
if (isDisabled()) return;
|
|
117
|
-
|
|
118
|
-
// Check minimum level
|
|
119
|
-
if (levelNum < getMinLevel()) return;
|
|
120
|
-
|
|
121
|
-
// Build JSONL entry
|
|
122
|
-
const now = new Date();
|
|
123
|
-
const ts = now.toISOString().replace("Z", "").slice(0, 23);
|
|
124
|
-
const entry: Record<string, any> = {
|
|
125
|
-
ts,
|
|
126
|
-
level: levelLower,
|
|
127
|
-
hook: hookName,
|
|
128
|
-
msg: message,
|
|
129
|
-
};
|
|
130
|
-
if (_cachedSessionId) entry.sid = _cachedSessionId;
|
|
131
|
-
if (component) entry.component = component;
|
|
132
|
-
if (opts?.data !== undefined && opts.data !== null) {
|
|
133
|
-
try {
|
|
134
|
-
JSON.stringify(opts.data);
|
|
135
|
-
entry.data = opts.data;
|
|
136
|
-
} catch {
|
|
137
|
-
entry.data = String(opts.data);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
if (tracebackStr) entry.tb = tracebackStr.trimEnd();
|
|
141
|
-
|
|
142
|
-
const line = JSON.stringify(entry) + "\n";
|
|
143
|
-
|
|
144
|
-
// Always write to global log
|
|
145
|
-
const logPath = path.join(getProjectRoot(), "_output", "hook-log.jsonl");
|
|
146
|
-
|
|
147
|
-
// Ensure directory exists
|
|
148
|
-
const dir = path.dirname(logPath);
|
|
149
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
150
|
-
|
|
151
|
-
// Line-count guard: prune to last MAX_LOG_LINES
|
|
152
|
-
try {
|
|
153
|
-
if (fs.existsSync(logPath)) {
|
|
154
|
-
const content = fs.readFileSync(logPath, "utf-8");
|
|
155
|
-
const lines = content.split(/\r?\n/);
|
|
156
|
-
if (lines.length > MAX_LOG_LINES) {
|
|
157
|
-
fs.writeFileSync(
|
|
158
|
-
logPath,
|
|
159
|
-
lines.slice(lines.length - MAX_LOG_LINES).join("\n"),
|
|
160
|
-
"utf-8",
|
|
161
|
-
);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
} catch {
|
|
165
|
-
// ignore
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
fs.appendFileSync(logPath, line, "utf-8");
|
|
169
|
-
} catch {
|
|
170
|
-
// Never crash
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
export function logDebug(hookName: string, message: string, opts?: Record<string, any>): void {
|
|
175
|
-
hookLog("debug", hookName, message, opts);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
export function logInfo(hookName: string, message: string, opts?: Record<string, any>): void {
|
|
179
|
-
hookLog("info", hookName, message, opts);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
export function logWarn(hookName: string, message: string, opts?: Record<string, any>): void {
|
|
183
|
-
hookLog("warn", hookName, message, opts);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
export function logError(hookName: string, message: string, opts?: Record<string, any>): void {
|
|
187
|
-
hookLog("error", hookName, message, opts);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Log an error that SHOULD be visible to user/model via stderr.
|
|
192
|
-
* Use for real problems needing attention, not routine diagnostics.
|
|
193
|
-
*/
|
|
194
|
-
export function logBlocking(hookName: string, message: string, opts?: Record<string, any>): void {
|
|
195
|
-
hookLog("error", hookName, message, { ...opts, stderr: true });
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Log a structured diagnostic entry at a hook decision point.
|
|
200
|
-
* See SPEC.md §3.8
|
|
201
|
-
*/
|
|
202
|
-
export function logDiagnostic(
|
|
203
|
-
hookName: string,
|
|
204
|
-
phase: string,
|
|
205
|
-
summary: string,
|
|
206
|
-
opts?: {
|
|
207
|
-
inputs?: any;
|
|
208
|
-
decision?: any;
|
|
209
|
-
reasoning?: any;
|
|
210
|
-
component?: string;
|
|
211
|
-
data?: any;
|
|
212
|
-
},
|
|
213
|
-
): void {
|
|
214
|
-
const diagData: Record<string, any> = { phase };
|
|
215
|
-
if (opts?.inputs !== undefined) diagData.inputs = opts.inputs;
|
|
216
|
-
if (opts?.decision !== undefined) diagData.decision = opts.decision;
|
|
217
|
-
if (opts?.reasoning !== undefined) diagData.reasoning = opts.reasoning;
|
|
218
|
-
if (opts?.data && typeof opts.data === "object") {
|
|
219
|
-
Object.assign(diagData, opts.data);
|
|
220
|
-
}
|
|
221
|
-
hookLog("debug", hookName, `[DIAG:${phase}] ${summary}`, {
|
|
222
|
-
component: opts?.component ?? "diag",
|
|
223
|
-
data: diagData,
|
|
224
|
-
});
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Backward-compatible wrapper matching old hook_utils.log_hook_error signature.
|
|
229
|
-
* See SPEC.md §3.6
|
|
230
|
-
*/
|
|
231
|
-
export function logHookError(
|
|
232
|
-
hookName: string,
|
|
233
|
-
error: Error | string,
|
|
234
|
-
hookEvent = "unknown",
|
|
235
|
-
tracebackStr = "",
|
|
236
|
-
): void {
|
|
237
|
-
const errStr = typeof error === "string" ? error : String(error);
|
|
238
|
-
const msg = errStr.replaceAll(/[\n\r]/g, " ").slice(0, 200);
|
|
239
|
-
const errType =
|
|
240
|
-
typeof error === "object" && error !== null
|
|
241
|
-
? error.constructor.name
|
|
242
|
-
: "Error";
|
|
243
|
-
hookLog("error", hookName, `[${hookEvent}] ${errType}: ${msg}`, {
|
|
244
|
-
traceback_str: tracebackStr,
|
|
245
|
-
stderr: true,
|
|
246
|
-
});
|
|
247
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Unified logging for all hooks and libraries.
|
|
3
|
+
*
|
|
4
|
+
* Log format: JSONL (one JSON object per line)
|
|
5
|
+
* Log location: _output/hook-log.jsonl (global, all sessions)
|
|
6
|
+
* Filter by session using the "sid" field.
|
|
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
|
+
*
|
|
13
|
+
* Environment variables:
|
|
14
|
+
* - HOOK_LOG_DISABLE=1: Disable all file logging
|
|
15
|
+
* - HOOK_LOG_LEVEL=warn: Minimum level to log (default: debug)
|
|
16
|
+
* - HOOK_ERROR_LOG_DISABLE=1: Legacy alias for HOOK_LOG_DISABLE
|
|
17
|
+
*
|
|
18
|
+
* Never throws. No buffering. Stdlib only.
|
|
19
|
+
* See SPEC.md §3
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import * as fs from "node:fs";
|
|
23
|
+
import * as path from "node:path";
|
|
24
|
+
|
|
25
|
+
const LEVELS: Record<string, number> = {
|
|
26
|
+
debug: 0,
|
|
27
|
+
info: 1,
|
|
28
|
+
warn: 2,
|
|
29
|
+
error: 3,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const MAX_LOG_LINES = 10_000; // Max lines in global log before pruning
|
|
33
|
+
|
|
34
|
+
// Module-level session ID cache
|
|
35
|
+
let _cachedSessionId: string | null = null;
|
|
36
|
+
|
|
37
|
+
// Module-level context path cache (kept for external callers)
|
|
38
|
+
let _cachedContextPath: string | null = null;
|
|
39
|
+
let _contextResolved = false;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Set the session ID for this process. All subsequent log calls include it.
|
|
43
|
+
*/
|
|
44
|
+
export function setSessionId(sessionId: string | null): void {
|
|
45
|
+
_cachedSessionId = sessionId;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Set the context path for this process. Kept for external callers.
|
|
50
|
+
*/
|
|
51
|
+
export function setContextPath(contextPath: string | null): void {
|
|
52
|
+
_cachedContextPath = contextPath;
|
|
53
|
+
_contextResolved = true;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function getContextPath(): string | null {
|
|
57
|
+
if (!_contextResolved) {
|
|
58
|
+
_contextResolved = true; // Don't retry
|
|
59
|
+
}
|
|
60
|
+
return _cachedContextPath;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function getMinLevel(): number {
|
|
64
|
+
const env = (process.env.HOOK_LOG_LEVEL ?? "debug").toLowerCase();
|
|
65
|
+
return LEVELS[env] ?? 0;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function isDisabled(): boolean {
|
|
69
|
+
return (
|
|
70
|
+
process.env.HOOK_LOG_DISABLE === "1" ||
|
|
71
|
+
process.env.HOOK_ERROR_LOG_DISABLE === "1" ||
|
|
72
|
+
process.env.CCNATIVE_DEBUG_DISABLE === "1"
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function getProjectRoot(): string {
|
|
77
|
+
return process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Write a structured log entry to the global hook log.
|
|
82
|
+
*
|
|
83
|
+
* All entries go to _output/hook-log.jsonl. Use the "sid" field
|
|
84
|
+
* (set via setSessionId) to filter by session.
|
|
85
|
+
*/
|
|
86
|
+
export function hookLog(
|
|
87
|
+
level: string,
|
|
88
|
+
hookName: string,
|
|
89
|
+
message: string,
|
|
90
|
+
opts?: {
|
|
91
|
+
component?: string;
|
|
92
|
+
data?: any;
|
|
93
|
+
traceback_str?: string;
|
|
94
|
+
stderr?: boolean;
|
|
95
|
+
},
|
|
96
|
+
): void {
|
|
97
|
+
try {
|
|
98
|
+
const levelLower = level.toLowerCase();
|
|
99
|
+
const levelNum = LEVELS[levelLower] ?? 0;
|
|
100
|
+
const component = opts?.component ?? "";
|
|
101
|
+
const tracebackStr = opts?.traceback_str ?? "";
|
|
102
|
+
const stderrEnabled = opts?.stderr === true;
|
|
103
|
+
|
|
104
|
+
// Write to stderr
|
|
105
|
+
if (stderrEnabled) {
|
|
106
|
+
const prefix = component
|
|
107
|
+
? `[${hookName}:${component}]`
|
|
108
|
+
: `[${hookName}]`;
|
|
109
|
+
process.stderr.write(`${prefix} ${message}\n`);
|
|
110
|
+
if (tracebackStr) {
|
|
111
|
+
process.stderr.write(tracebackStr + "\n");
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Check if file logging is enabled
|
|
116
|
+
if (isDisabled()) return;
|
|
117
|
+
|
|
118
|
+
// Check minimum level
|
|
119
|
+
if (levelNum < getMinLevel()) return;
|
|
120
|
+
|
|
121
|
+
// Build JSONL entry
|
|
122
|
+
const now = new Date();
|
|
123
|
+
const ts = now.toISOString().replace("Z", "").slice(0, 23);
|
|
124
|
+
const entry: Record<string, any> = {
|
|
125
|
+
ts,
|
|
126
|
+
level: levelLower,
|
|
127
|
+
hook: hookName,
|
|
128
|
+
msg: message,
|
|
129
|
+
};
|
|
130
|
+
if (_cachedSessionId) entry.sid = _cachedSessionId;
|
|
131
|
+
if (component) entry.component = component;
|
|
132
|
+
if (opts?.data !== undefined && opts.data !== null) {
|
|
133
|
+
try {
|
|
134
|
+
JSON.stringify(opts.data);
|
|
135
|
+
entry.data = opts.data;
|
|
136
|
+
} catch {
|
|
137
|
+
entry.data = String(opts.data);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (tracebackStr) entry.tb = tracebackStr.trimEnd();
|
|
141
|
+
|
|
142
|
+
const line = JSON.stringify(entry) + "\n";
|
|
143
|
+
|
|
144
|
+
// Always write to global log
|
|
145
|
+
const logPath = path.join(getProjectRoot(), "_output", "hook-log.jsonl");
|
|
146
|
+
|
|
147
|
+
// Ensure directory exists
|
|
148
|
+
const dir = path.dirname(logPath);
|
|
149
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
150
|
+
|
|
151
|
+
// Line-count guard: prune to last MAX_LOG_LINES
|
|
152
|
+
try {
|
|
153
|
+
if (fs.existsSync(logPath)) {
|
|
154
|
+
const content = fs.readFileSync(logPath, "utf-8");
|
|
155
|
+
const lines = content.split(/\r?\n/);
|
|
156
|
+
if (lines.length > MAX_LOG_LINES) {
|
|
157
|
+
fs.writeFileSync(
|
|
158
|
+
logPath,
|
|
159
|
+
lines.slice(lines.length - MAX_LOG_LINES).join("\n"),
|
|
160
|
+
"utf-8",
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
} catch {
|
|
165
|
+
// ignore
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
fs.appendFileSync(logPath, line, "utf-8");
|
|
169
|
+
} catch {
|
|
170
|
+
// Never crash
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function logDebug(hookName: string, message: string, opts?: Record<string, any>): void {
|
|
175
|
+
hookLog("debug", hookName, message, opts);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function logInfo(hookName: string, message: string, opts?: Record<string, any>): void {
|
|
179
|
+
hookLog("info", hookName, message, opts);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function logWarn(hookName: string, message: string, opts?: Record<string, any>): void {
|
|
183
|
+
hookLog("warn", hookName, message, opts);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function logError(hookName: string, message: string, opts?: Record<string, any>): void {
|
|
187
|
+
hookLog("error", hookName, message, opts);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Log an error that SHOULD be visible to user/model via stderr.
|
|
192
|
+
* Use for real problems needing attention, not routine diagnostics.
|
|
193
|
+
*/
|
|
194
|
+
export function logBlocking(hookName: string, message: string, opts?: Record<string, any>): void {
|
|
195
|
+
hookLog("error", hookName, message, { ...opts, stderr: true });
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Log a structured diagnostic entry at a hook decision point.
|
|
200
|
+
* See SPEC.md §3.8
|
|
201
|
+
*/
|
|
202
|
+
export function logDiagnostic(
|
|
203
|
+
hookName: string,
|
|
204
|
+
phase: string,
|
|
205
|
+
summary: string,
|
|
206
|
+
opts?: {
|
|
207
|
+
inputs?: any;
|
|
208
|
+
decision?: any;
|
|
209
|
+
reasoning?: any;
|
|
210
|
+
component?: string;
|
|
211
|
+
data?: any;
|
|
212
|
+
},
|
|
213
|
+
): void {
|
|
214
|
+
const diagData: Record<string, any> = { phase };
|
|
215
|
+
if (opts?.inputs !== undefined) diagData.inputs = opts.inputs;
|
|
216
|
+
if (opts?.decision !== undefined) diagData.decision = opts.decision;
|
|
217
|
+
if (opts?.reasoning !== undefined) diagData.reasoning = opts.reasoning;
|
|
218
|
+
if (opts?.data && typeof opts.data === "object") {
|
|
219
|
+
Object.assign(diagData, opts.data);
|
|
220
|
+
}
|
|
221
|
+
hookLog("debug", hookName, `[DIAG:${phase}] ${summary}`, {
|
|
222
|
+
component: opts?.component ?? "diag",
|
|
223
|
+
data: diagData,
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Backward-compatible wrapper matching old hook_utils.log_hook_error signature.
|
|
229
|
+
* See SPEC.md §3.6
|
|
230
|
+
*/
|
|
231
|
+
export function logHookError(
|
|
232
|
+
hookName: string,
|
|
233
|
+
error: Error | string,
|
|
234
|
+
hookEvent = "unknown",
|
|
235
|
+
tracebackStr = "",
|
|
236
|
+
): void {
|
|
237
|
+
const errStr = typeof error === "string" ? error : String(error);
|
|
238
|
+
const msg = errStr.replaceAll(/[\n\r]/g, " ").slice(0, 200);
|
|
239
|
+
const errType =
|
|
240
|
+
typeof error === "object" && error !== null
|
|
241
|
+
? error.constructor.name
|
|
242
|
+
: "Error";
|
|
243
|
+
hookLog("error", hookName, `[${hookEvent}] ${errType}: ${msg}`, {
|
|
244
|
+
traceback_str: tracebackStr,
|
|
245
|
+
stderr: true,
|
|
246
|
+
});
|
|
247
|
+
}
|