aiwcli 0.12.7 → 0.13.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/dist/commands/clean.d.ts +7 -0
- package/dist/commands/clean.js +17 -8
- package/dist/commands/clear.d.ts +85 -0
- package/dist/commands/clear.js +455 -347
- package/dist/commands/init/index.d.ts +15 -0
- package/dist/commands/init/index.js +79 -38
- package/dist/lib/gitignore-manager.js +12 -13
- package/dist/lib/settings-hierarchy.d.ts +13 -1
- package/dist/lib/settings-hierarchy.js +1 -1
- package/dist/lib/template-linter.d.ts +4 -0
- package/dist/lib/template-linter.js +1 -1
- package/dist/lib/tty-detection.d.ts +1 -0
- package/dist/lib/tty-detection.js +1 -0
- package/dist/templates/CLAUDE.md +27 -0
- package/dist/templates/_shared/.claude/settings.json +7 -7
- package/dist/templates/_shared/.claude/{commands/handoff.md → skills/handoff/SKILL.md} +4 -3
- package/dist/templates/_shared/.claude/{commands/handoff-resume.md → skills/handoff-resume/SKILL.md} +3 -2
- package/dist/templates/_shared/.claude/skills/meta-plan/SKILL.md +43 -0
- package/dist/templates/_shared/.codex/workflows/handoff.md +1 -1
- package/dist/templates/_shared/.codex/workflows/meta-plan.md +347 -0
- package/dist/templates/_shared/.windsurf/workflows/handoff.md +1 -1
- package/dist/templates/_shared/.windsurf/workflows/meta-plan.md +347 -0
- package/dist/templates/_shared/hooks-ts/lint_after_edit.ts +59 -0
- package/dist/templates/_shared/hooks-ts/session_end.ts +11 -10
- package/dist/templates/_shared/hooks-ts/session_start.ts +15 -12
- package/dist/templates/_shared/hooks-ts/user_prompt_submit.ts +12 -12
- package/dist/templates/_shared/lib-ts/CLAUDE.md +3 -3
- package/dist/templates/_shared/lib-ts/base/constants.ts +324 -306
- package/dist/templates/_shared/lib-ts/base/hook-utils.ts +26 -7
- package/dist/templates/_shared/lib-ts/base/inference.ts +19 -19
- package/dist/templates/_shared/lib-ts/base/lint-dispatch.ts +287 -0
- package/dist/templates/_shared/lib-ts/base/state-io.ts +4 -3
- package/dist/templates/_shared/lib-ts/base/subprocess-utils.ts +3 -3
- package/dist/templates/_shared/lib-ts/context/CLAUDE.md +134 -0
- package/dist/templates/_shared/lib-ts/context/context-formatter.ts +16 -15
- package/dist/templates/_shared/lib-ts/context/context-selector.ts +16 -16
- package/dist/templates/_shared/lib-ts/context/context-store.ts +15 -14
- package/dist/templates/_shared/lib-ts/context/plan-manager.ts +2 -2
- package/dist/templates/_shared/scripts/resolve-run.ts +61 -0
- package/dist/templates/_shared/scripts/resolve_context.ts +1 -1
- package/dist/templates/_shared/scripts/status_line.ts +100 -94
- package/dist/templates/_shared/{handoff-system → skills/handoff-system}/CLAUDE.md +433 -421
- package/dist/templates/_shared/{handoff-system → skills/handoff-system}/lib/document-generator.ts +5 -4
- package/dist/templates/_shared/{handoff-system → skills/handoff-system}/lib/handoff-reader.ts +2 -1
- package/dist/templates/_shared/{handoff-system → skills/handoff-system}/scripts/resume_handoff.ts +6 -6
- package/dist/templates/_shared/{handoff-system → skills/handoff-system}/scripts/save_handoff.ts +16 -17
- package/dist/templates/_shared/{handoff-system → skills/handoff-system}/workflows/handoff-resume.md +2 -2
- package/dist/templates/_shared/{handoff-system → skills/handoff-system}/workflows/handoff.md +3 -3
- package/dist/templates/_shared/skills/meta-plan/CLAUDE.md +44 -0
- package/dist/templates/_shared/skills/meta-plan/workflows/meta-plan.md +347 -0
- package/dist/templates/cc-native/.claude/commands/cc-native/specdev.md +1 -1
- package/dist/templates/cc-native/.claude/settings.json +86 -57
- package/dist/templates/cc-native/_cc-native/artifacts/CLAUDE.md +64 -0
- package/dist/templates/cc-native/_cc-native/{lib-ts/artifacts → artifacts/lib}/format.ts +599 -597
- package/dist/templates/cc-native/_cc-native/{lib-ts/artifacts → artifacts/lib}/index.ts +26 -26
- package/dist/templates/cc-native/_cc-native/{lib-ts/artifacts → artifacts/lib}/tracker.ts +107 -106
- package/dist/templates/cc-native/_cc-native/{lib-ts/artifacts → artifacts/lib}/write.ts +119 -118
- package/dist/templates/cc-native/_cc-native/hooks/CLAUDE.md +237 -247
- package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.ts +76 -74
- package/dist/templates/cc-native/_cc-native/hooks/validate_task_prompt.ts +76 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/aggregate-agents.ts +163 -156
- package/dist/templates/cc-native/_cc-native/lib-ts/cc-native-state.ts +15 -16
- package/dist/templates/cc-native/_cc-native/lib-ts/index.ts +116 -116
- package/dist/templates/cc-native/_cc-native/lib-ts/plan-discovery.ts +3 -3
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/embedding-indexer.ts +16 -12
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/hyde.ts +2 -3
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/index.ts +31 -31
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/logger.ts +7 -6
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/ollama-client.ts +9 -7
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/retrieval-pipeline.ts +17 -14
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/transcript-indexer.ts +41 -37
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/transcript-loader.ts +43 -33
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/transcript-searcher.ts +20 -20
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/types.ts +9 -8
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/vector-store.ts +4 -3
- package/dist/templates/cc-native/_cc-native/lib-ts/settings.ts +9 -10
- package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +20 -19
- package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +329 -329
- package/dist/templates/cc-native/_cc-native/plan-review/CLAUDE.md +149 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/CLAUDE.md +143 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/PLAN-ORCHESTRATOR.md +213 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-questions/PLAN-QUESTIONER.md +70 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/ARCH-EVOLUTION.md +62 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/ARCH-PATTERNS.md +61 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/ARCH-STRUCTURE.md +62 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/ASSUMPTION-TRACER.md +56 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/CLARITY-AUDITOR.md +53 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/COMPLETENESS-FEASIBILITY.md +66 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/COMPLETENESS-GAPS.md +70 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/COMPLETENESS-ORDERING.md +62 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/CONSTRAINT-VALIDATOR.md +72 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/DESIGN-ADR-VALIDATOR.md +61 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/DESIGN-SCALE-MATCHER.md +64 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/DEVILS-ADVOCATE.md +56 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/DOCUMENTATION-PHILOSOPHY.md +86 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/HANDOFF-READINESS.md +59 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/HIDDEN-COMPLEXITY.md +58 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/INCREMENTAL-DELIVERY.md +66 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/RISK-DEPENDENCY.md +62 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/RISK-FMEA.md +66 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/RISK-PREMORTEM.md +71 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/RISK-REVERSIBILITY.md +74 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/SCOPE-BOUNDARY.md +77 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/SIMPLICITY-GUARDIAN.md +62 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/SKEPTIC.md +68 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/TESTDRIVEN-BEHAVIOR-AUDITOR.md +61 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/TESTDRIVEN-CHARACTERIZATION.md +71 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/TESTDRIVEN-FIRST-VALIDATOR.md +61 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/TESTDRIVEN-PYRAMID-ANALYZER.md +61 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/TRADEOFF-COSTS.md +67 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/TRADEOFF-STAKEHOLDERS.md +65 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/VERIFY-COVERAGE.md +74 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/VERIFY-STRENGTH.md +69 -0
- package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/agent-selection.ts +162 -163
- package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/corroboration.ts +119 -119
- package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/graduation.ts +132 -132
- package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/orchestrator.ts +70 -70
- package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/output-builder.ts +121 -130
- package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/plan-questions.ts +101 -102
- package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/review-pipeline.ts +507 -511
- package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/reviewers/agent.ts +73 -74
- package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/reviewers/base/base-agent.ts +217 -217
- package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/reviewers/index.ts +12 -12
- package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/reviewers/providers/claude-agent.ts +66 -66
- package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/reviewers/providers/codex-agent.ts +185 -185
- package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/reviewers/providers/gemini-agent.ts +39 -39
- package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/reviewers/providers/orchestrator-claude-agent.ts +196 -196
- package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/reviewers/schemas.ts +201 -201
- package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/reviewers/types.ts +23 -23
- package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/verdict.ts +72 -72
- package/dist/templates/cc-native/_cc-native/{workflows → plan-review/workflows}/specdev.md +9 -9
- package/oclif.manifest.json +1 -1
- package/package.json +6 -5
- package/dist/templates/cc-native/_cc-native/lib-ts/artifacts.ts +0 -21
|
@@ -12,7 +12,7 @@ import { getContextBySessionId } from "../context/context-store.js";
|
|
|
12
12
|
import type { HookInput, HookOutput, PermissionRequestOutput } from "../types.js";
|
|
13
13
|
|
|
14
14
|
// Re-export logger functions for convenience (matches Python hook_utils re-exports)
|
|
15
|
-
|
|
15
|
+
|
|
16
16
|
|
|
17
17
|
// Context window baseline: tokens not visible in hook data §5.9
|
|
18
18
|
export const CONTEXT_BASELINE_TOKENS = 22_600;
|
|
@@ -281,23 +281,28 @@ export function emitPermissionDecision(
|
|
|
281
281
|
export function emitBlock(reason: string, context?: string): void {
|
|
282
282
|
const event = _lastHookEvent;
|
|
283
283
|
switch (event) {
|
|
284
|
-
case "PermissionRequest":
|
|
284
|
+
case "PermissionRequest": {
|
|
285
285
|
emitPermissionDecision("deny", { message: reason });
|
|
286
286
|
break;
|
|
287
|
+
}
|
|
287
288
|
case "PostToolUse":
|
|
288
|
-
case "PostToolUseFailure":
|
|
289
|
+
case "PostToolUseFailure": {
|
|
289
290
|
emitBlockViaExit(reason, context);
|
|
290
291
|
break;
|
|
291
|
-
|
|
292
|
+
}
|
|
293
|
+
case "PreToolUse": {
|
|
292
294
|
emitContextAndBlock(context ?? reason, reason);
|
|
293
295
|
break;
|
|
296
|
+
}
|
|
294
297
|
case "Stop":
|
|
295
|
-
case "SubagentStop":
|
|
298
|
+
case "SubagentStop": {
|
|
296
299
|
emitBlockTopLevel(reason);
|
|
297
300
|
break;
|
|
298
|
-
|
|
301
|
+
}
|
|
302
|
+
case "UserPromptSubmit": {
|
|
299
303
|
emitBlockPrompt(reason, context);
|
|
300
304
|
break;
|
|
305
|
+
}
|
|
301
306
|
default: {
|
|
302
307
|
logWarn(_cachedHookName ?? "unknown",
|
|
303
308
|
`emitBlock() called from ${event ?? "unknown"} — no blocking mechanism exists for this event type, ignoring`);
|
|
@@ -431,6 +436,13 @@ export function runHook(
|
|
|
431
436
|
_earlyReadInput(prefetchedInput);
|
|
432
437
|
_cachedHookName = hookName;
|
|
433
438
|
|
|
439
|
+
// Ensure cwd is project root so relative paths in hooks resolve correctly,
|
|
440
|
+
// even when cwd has drifted via `cd` in a Bash tool call.
|
|
441
|
+
try {
|
|
442
|
+
const projectRoot = getProjectRoot(_prefetchedInput?.cwd);
|
|
443
|
+
if (process.cwd() !== projectRoot) process.chdir(projectRoot);
|
|
444
|
+
} catch { /* non-fatal — proceed with current cwd */ }
|
|
445
|
+
|
|
434
446
|
const startTime = performance.now();
|
|
435
447
|
const template = detectTemplate();
|
|
436
448
|
const event = _lastHookEvent ?? "unknown";
|
|
@@ -482,6 +494,13 @@ export function runHookAsync(
|
|
|
482
494
|
_earlyReadInput(prefetchedInput);
|
|
483
495
|
_cachedHookName = hookName;
|
|
484
496
|
|
|
497
|
+
// Ensure cwd is project root so relative paths in hooks resolve correctly,
|
|
498
|
+
// even when cwd has drifted via `cd` in a Bash tool call.
|
|
499
|
+
try {
|
|
500
|
+
const projectRoot = getProjectRoot(_prefetchedInput?.cwd);
|
|
501
|
+
if (process.cwd() !== projectRoot) process.chdir(projectRoot);
|
|
502
|
+
} catch { /* non-fatal — proceed with current cwd */ }
|
|
503
|
+
|
|
485
504
|
const startTime = performance.now();
|
|
486
505
|
const template = detectTemplate();
|
|
487
506
|
const event = _lastHookEvent ?? "unknown";
|
|
@@ -583,4 +602,4 @@ function _drainAndExit(code: number): void {
|
|
|
583
602
|
});
|
|
584
603
|
}
|
|
585
604
|
|
|
586
|
-
export {
|
|
605
|
+
export {hookLog, logBlocking, logDebug, logDiagnostic, logError, logHookError, logInfo, logWarn, setContextPath, setSessionId} from "./logger.js";
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { execFileSync } from "node:child_process";
|
|
8
|
+
|
|
8
9
|
import { logDebug, logWarn } from "./logger.js";
|
|
9
10
|
import { STOP_WORDS } from "./stop-words.js";
|
|
10
11
|
import type { InferenceResult } from "../types.js";
|
|
@@ -12,9 +13,9 @@ import { execFileAsync, getInternalSubprocessEnv, shellQuoteWin } from "./subpro
|
|
|
12
13
|
|
|
13
14
|
// Model configurations §6.1
|
|
14
15
|
const MODELS: Record<string, string> = {
|
|
15
|
-
fast: "claude-
|
|
16
|
-
standard: "claude-sonnet-4-
|
|
17
|
-
smart: "claude-opus-4-
|
|
16
|
+
fast: "claude-haiku-4-5-20251001",
|
|
17
|
+
standard: "claude-sonnet-4-6",
|
|
18
|
+
smart: "claude-opus-4-6",
|
|
18
19
|
};
|
|
19
20
|
|
|
20
21
|
const TIMEOUTS: Record<string, number> = {
|
|
@@ -44,7 +45,6 @@ export function inference(
|
|
|
44
45
|
|
|
45
46
|
try {
|
|
46
47
|
const isWin = process.platform === "win32";
|
|
47
|
-
let stdout: string;
|
|
48
48
|
|
|
49
49
|
// On Windows with shell:true, Node.js sets windowsVerbatimArguments —
|
|
50
50
|
// args are joined with spaces, NOT individually quoted. We must manually
|
|
@@ -53,10 +53,10 @@ export function inference(
|
|
|
53
53
|
const empty = isWin ? '""' : "";
|
|
54
54
|
let promptArg = fullPrompt;
|
|
55
55
|
if (isWin) {
|
|
56
|
-
promptArg = '"' + fullPrompt.
|
|
56
|
+
promptArg = '"' + fullPrompt.replaceAll(/\r?\n/g, " ").replaceAll('"', '""') + '"';
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
stdout = execFileSync(
|
|
59
|
+
const stdout = execFileSync(
|
|
60
60
|
"claude",
|
|
61
61
|
["--model", model, "--print", "--setting-sources", empty, "-p", "--no-session-persistence", promptArg],
|
|
62
62
|
{
|
|
@@ -74,10 +74,10 @@ export function inference(
|
|
|
74
74
|
output: stdout.trim(),
|
|
75
75
|
latency_ms: latencyMs,
|
|
76
76
|
};
|
|
77
|
-
} catch (
|
|
77
|
+
} catch (error: any) {
|
|
78
78
|
const latencyMs = Date.now() - startTime;
|
|
79
79
|
|
|
80
|
-
if (
|
|
80
|
+
if (error.code === "ETIMEDOUT" || error.killed) {
|
|
81
81
|
return {
|
|
82
82
|
success: false,
|
|
83
83
|
output: "",
|
|
@@ -86,7 +86,7 @@ export function inference(
|
|
|
86
86
|
};
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
if (
|
|
89
|
+
if (error.code === "ENOENT") {
|
|
90
90
|
return {
|
|
91
91
|
success: false,
|
|
92
92
|
output: "",
|
|
@@ -96,11 +96,11 @@ export function inference(
|
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
// Non-zero exit code
|
|
99
|
-
if (
|
|
99
|
+
if (error.status !== undefined && error.status !== 0) {
|
|
100
100
|
return {
|
|
101
101
|
success: false,
|
|
102
|
-
output: (
|
|
103
|
-
error: (
|
|
102
|
+
output: (error.stdout ?? "").toString().trim(),
|
|
103
|
+
error: (error.stderr ?? "").toString().trim() || `Exit code: ${error.status}`,
|
|
104
104
|
latency_ms: latencyMs,
|
|
105
105
|
};
|
|
106
106
|
}
|
|
@@ -108,7 +108,7 @@ export function inference(
|
|
|
108
108
|
return {
|
|
109
109
|
success: false,
|
|
110
110
|
output: "",
|
|
111
|
-
error: String(
|
|
111
|
+
error: String(error),
|
|
112
112
|
latency_ms: latencyMs,
|
|
113
113
|
};
|
|
114
114
|
}
|
|
@@ -140,7 +140,7 @@ export function generateSemanticSummary(
|
|
|
140
140
|
if (!result.success || !result.output) return null;
|
|
141
141
|
|
|
142
142
|
let summary = result.output.trim();
|
|
143
|
-
summary = summary.
|
|
143
|
+
summary = summary.replaceAll(/^["']+|["']+$/g, "");
|
|
144
144
|
summary = summary.replace(/[.!?]+$/, "");
|
|
145
145
|
|
|
146
146
|
// Filter stop words
|
|
@@ -221,11 +221,11 @@ export function generateContextIdSlug(
|
|
|
221
221
|
if (!slug) slug = raw;
|
|
222
222
|
|
|
223
223
|
// Clean up
|
|
224
|
-
slug = slug.
|
|
224
|
+
slug = slug.replaceAll(/^["'`]+|["'`]+$/g, "");
|
|
225
225
|
slug = slug.replace(/[.!?]+$/, "");
|
|
226
|
-
slug = slug.
|
|
227
|
-
slug = slug.
|
|
228
|
-
slug = slug.
|
|
226
|
+
slug = slug.replaceAll('-', " ");
|
|
227
|
+
slug = slug.replaceAll(/[^a-zA-Z0-9 ]/g, "");
|
|
228
|
+
slug = slug.replaceAll(/\s+/g, " ").trim();
|
|
229
229
|
|
|
230
230
|
const words = slug.split(" ");
|
|
231
231
|
|
|
@@ -263,7 +263,7 @@ export async function inferenceAsync(
|
|
|
263
263
|
const isWin = process.platform === "win32";
|
|
264
264
|
const empty = isWin ? '""' : "";
|
|
265
265
|
const promptArg = isWin
|
|
266
|
-
? shellQuoteWin(fullPrompt.
|
|
266
|
+
? shellQuoteWin(fullPrompt.replaceAll(/\r?\n/g, " "))
|
|
267
267
|
: fullPrompt;
|
|
268
268
|
|
|
269
269
|
const result = await execFileAsync(
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Linter dispatch table and runner for PostToolUse lint-after-edit hook.
|
|
3
|
+
* Maps file extensions to linter configs, runs linters, parses output.
|
|
4
|
+
* See root CLAUDE.md for template sync targets.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as fs from "node:fs";
|
|
8
|
+
import * as path from "node:path";
|
|
9
|
+
|
|
10
|
+
import { logDebug, logWarn } from "./logger.js";
|
|
11
|
+
import { findExecutable } from "./subprocess-utils.js";
|
|
12
|
+
|
|
13
|
+
// ─── Types ──────────────────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
export interface LinterConfig {
|
|
16
|
+
name: string;
|
|
17
|
+
extensions: string[];
|
|
18
|
+
source: "bundled" | "system";
|
|
19
|
+
binaryName: string;
|
|
20
|
+
buildArgs: (filePath: string) => string[];
|
|
21
|
+
parseOutput: (stdout: string, stderr: string, exitCode: number) => LintError[];
|
|
22
|
+
/** Exit codes that mean "lint errors found" (parse output). Other non-zero = crash (skip). */
|
|
23
|
+
lintExitCodes: number[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface LintError {
|
|
27
|
+
line: number;
|
|
28
|
+
column?: number;
|
|
29
|
+
severity: "error" | "warning";
|
|
30
|
+
message: string;
|
|
31
|
+
rule?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ─── Output Parsers ─────────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
function parseBiomeOutput(stdout: string, _stderr: string, _exitCode: number): LintError[] {
|
|
37
|
+
try {
|
|
38
|
+
const data = JSON.parse(stdout);
|
|
39
|
+
const diagnostics: any[] = data?.diagnostics ?? [];
|
|
40
|
+
return diagnostics.map((d) => ({
|
|
41
|
+
line: d.location?.span?.start?.line ?? d.location?.sourceCode?.lineIndex ?? 0,
|
|
42
|
+
column: d.location?.span?.start?.character ?? undefined,
|
|
43
|
+
severity: d.severity === "error" || d.severity === "fatal" ? "error" as const : "warning" as const,
|
|
44
|
+
message: typeof d.description === "string" ? d.description : (d.message ?? "Unknown issue"),
|
|
45
|
+
rule: d.category ?? undefined,
|
|
46
|
+
}));
|
|
47
|
+
} catch {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function parseRuffOutput(stdout: string, _stderr: string, _exitCode: number): LintError[] {
|
|
53
|
+
try {
|
|
54
|
+
const items: any[] = JSON.parse(stdout);
|
|
55
|
+
return items.map((item) => ({
|
|
56
|
+
line: item.location?.row ?? 0,
|
|
57
|
+
column: item.location?.column ?? undefined,
|
|
58
|
+
severity: "error" as const,
|
|
59
|
+
message: item.message ?? "Unknown issue",
|
|
60
|
+
rule: item.code ?? undefined,
|
|
61
|
+
}));
|
|
62
|
+
} catch {
|
|
63
|
+
return [];
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function parseShellcheckOutput(stdout: string, _stderr: string, _exitCode: number): LintError[] {
|
|
68
|
+
try {
|
|
69
|
+
const data = JSON.parse(stdout);
|
|
70
|
+
const comments: any[] = data?.comments ?? [];
|
|
71
|
+
return comments.map((c) => ({
|
|
72
|
+
line: c.line ?? 0,
|
|
73
|
+
column: c.column ?? undefined,
|
|
74
|
+
severity: c.level === "error" ? "error" as const : "warning" as const,
|
|
75
|
+
message: c.message ?? "Unknown issue",
|
|
76
|
+
rule: c.code ? `SC${c.code}` : undefined,
|
|
77
|
+
}));
|
|
78
|
+
} catch {
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function parseRubocopOutput(stdout: string, _stderr: string, _exitCode: number): LintError[] {
|
|
84
|
+
try {
|
|
85
|
+
const data = JSON.parse(stdout);
|
|
86
|
+
const offenses: any[] = data?.files?.[0]?.offenses ?? [];
|
|
87
|
+
return offenses.map((o) => ({
|
|
88
|
+
line: o.location?.line ?? 0,
|
|
89
|
+
column: o.location?.column ?? undefined,
|
|
90
|
+
severity: o.severity === "error" || o.severity === "fatal" ? "error" as const : "warning" as const,
|
|
91
|
+
message: o.message ?? "Unknown issue",
|
|
92
|
+
rule: o.cop_name ?? undefined,
|
|
93
|
+
}));
|
|
94
|
+
} catch {
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const CPPCHECK_RE = /^(.+):(\d+):(\d+): (\w+): (.+) \[(.+)\]$/;
|
|
100
|
+
|
|
101
|
+
function parseCppcheckOutput(_stdout: string, stderr: string, _exitCode: number): LintError[] {
|
|
102
|
+
const errors: LintError[] = [];
|
|
103
|
+
for (const line of stderr.split("\n")) {
|
|
104
|
+
const m = CPPCHECK_RE.exec(line.trim());
|
|
105
|
+
if (m) {
|
|
106
|
+
errors.push({
|
|
107
|
+
line: parseInt(m[2]!, 10),
|
|
108
|
+
column: parseInt(m[3]!, 10),
|
|
109
|
+
severity: m[4] === "error" ? "error" : "warning",
|
|
110
|
+
message: m[5]!,
|
|
111
|
+
rule: m[6],
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return errors;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ─── Dispatch Table ─────────────────────────────────────────────────────────
|
|
119
|
+
|
|
120
|
+
const LINTERS: LinterConfig[] = [
|
|
121
|
+
{
|
|
122
|
+
name: "biome",
|
|
123
|
+
extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".json", ".jsonc", ".css"],
|
|
124
|
+
source: "bundled",
|
|
125
|
+
binaryName: "biome",
|
|
126
|
+
buildArgs: (filePath) => ["lint", "--reporter=json", "--max-diagnostics=20", filePath],
|
|
127
|
+
parseOutput: parseBiomeOutput,
|
|
128
|
+
lintExitCodes: [1],
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
name: "ruff",
|
|
132
|
+
extensions: [".py", ".pyi"],
|
|
133
|
+
source: "system",
|
|
134
|
+
binaryName: "ruff",
|
|
135
|
+
buildArgs: (filePath) => ["check", "--no-fix", "--output-format=json", filePath],
|
|
136
|
+
parseOutput: parseRuffOutput,
|
|
137
|
+
lintExitCodes: [1],
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
name: "shellcheck",
|
|
141
|
+
extensions: [".sh", ".bash"],
|
|
142
|
+
source: "system",
|
|
143
|
+
binaryName: "shellcheck",
|
|
144
|
+
buildArgs: (filePath) => ["-f", "json1", filePath],
|
|
145
|
+
parseOutput: parseShellcheckOutput,
|
|
146
|
+
lintExitCodes: [1],
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
name: "rubocop",
|
|
150
|
+
extensions: [".rb"],
|
|
151
|
+
source: "system",
|
|
152
|
+
binaryName: "rubocop",
|
|
153
|
+
buildArgs: (filePath) => ["--format", "json", "--no-autocorrect", filePath],
|
|
154
|
+
parseOutput: parseRubocopOutput,
|
|
155
|
+
lintExitCodes: [1, 2],
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
name: "cppcheck",
|
|
159
|
+
extensions: [".c", ".cpp", ".h", ".hpp"],
|
|
160
|
+
source: "system",
|
|
161
|
+
binaryName: "cppcheck",
|
|
162
|
+
buildArgs: (filePath) => ["--enable=warning,style", "--template=gcc", filePath],
|
|
163
|
+
parseOutput: parseCppcheckOutput,
|
|
164
|
+
lintExitCodes: [1],
|
|
165
|
+
},
|
|
166
|
+
];
|
|
167
|
+
|
|
168
|
+
/** Extension → LinterConfig lookup map (built once). */
|
|
169
|
+
const EXT_MAP = new Map<string, LinterConfig>();
|
|
170
|
+
for (const linter of LINTERS) {
|
|
171
|
+
for (const ext of linter.extensions) {
|
|
172
|
+
EXT_MAP.set(ext, linter);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// ─── Public API ─────────────────────────────────────────────────────────────
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Look up the linter config for a file by extension.
|
|
180
|
+
* Returns null if no linter is configured for this file type.
|
|
181
|
+
*/
|
|
182
|
+
export function getLinterForFile(filePath: string): LinterConfig | null {
|
|
183
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
184
|
+
return EXT_MAP.get(ext) ?? null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Resolve the binary path for a linter.
|
|
189
|
+
* Bundled linters: check project node_modules/.bin first, then PATH.
|
|
190
|
+
* System linters: check PATH only.
|
|
191
|
+
* Returns null if binary not found.
|
|
192
|
+
*/
|
|
193
|
+
function resolveBinary(config: LinterConfig, projectRoot: string): string | null {
|
|
194
|
+
if (config.source === "bundled") {
|
|
195
|
+
// 1. Project-local node_modules
|
|
196
|
+
const localBin = path.join(projectRoot, "node_modules", ".bin", config.binaryName);
|
|
197
|
+
if (fs.existsSync(localBin)) return localBin;
|
|
198
|
+
|
|
199
|
+
// 2. PATH (covers global npm install of aiwcli)
|
|
200
|
+
return findExecutable(config.binaryName);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// System linters: PATH only
|
|
204
|
+
return findExecutable(config.binaryName);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Run a linter on a file.
|
|
209
|
+
* Returns null if the linter binary is not found.
|
|
210
|
+
* Returns { errors: [] } if the file passes lint.
|
|
211
|
+
*/
|
|
212
|
+
export function runLinter(
|
|
213
|
+
config: LinterConfig,
|
|
214
|
+
filePath: string,
|
|
215
|
+
projectRoot: string,
|
|
216
|
+
): { errors: LintError[] } | null {
|
|
217
|
+
const binary = resolveBinary(config, projectRoot);
|
|
218
|
+
if (!binary) {
|
|
219
|
+
logDebug("lint-dispatch", `${config.name} binary not found, skipping`);
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const args = config.buildArgs(filePath);
|
|
224
|
+
|
|
225
|
+
try {
|
|
226
|
+
// eslint-disable-next-line no-undef -- Bun runtime global
|
|
227
|
+
const result = (Bun as any).spawnSync([binary, ...args], {
|
|
228
|
+
cwd: projectRoot,
|
|
229
|
+
timeout: 8000, // 8s soft limit (10s hook timeout is the hard kill)
|
|
230
|
+
stdout: "pipe",
|
|
231
|
+
stderr: "pipe",
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
const {exitCode} = result;
|
|
235
|
+
const stdout = result.stdout?.toString() ?? "";
|
|
236
|
+
const stderr = result.stderr?.toString() ?? "";
|
|
237
|
+
|
|
238
|
+
// Exit 0 = clean
|
|
239
|
+
if (exitCode === 0) return { errors: [] };
|
|
240
|
+
|
|
241
|
+
// Known lint-error exit codes → parse output
|
|
242
|
+
if (config.lintExitCodes.includes(exitCode)) {
|
|
243
|
+
const errors = config.parseOutput(stdout, stderr, exitCode);
|
|
244
|
+
return { errors };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Unknown exit code = crash/timeout → skip
|
|
248
|
+
logWarn("lint-dispatch", `${config.name} exited with unexpected code ${exitCode} on ${filePath}`);
|
|
249
|
+
return { errors: [] };
|
|
250
|
+
} catch (error) {
|
|
251
|
+
logWarn("lint-dispatch", `${config.name} execution failed: ${error}`);
|
|
252
|
+
return { errors: [] };
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Format lint errors for Claude's context injection.
|
|
258
|
+
* Returns a human-readable string suitable for emitContext().
|
|
259
|
+
*/
|
|
260
|
+
export function formatLintErrors(
|
|
261
|
+
filePath: string,
|
|
262
|
+
linterName: string,
|
|
263
|
+
errors: LintError[],
|
|
264
|
+
maxShown = 15,
|
|
265
|
+
): string {
|
|
266
|
+
const shown = errors.slice(0, maxShown);
|
|
267
|
+
const lines = [
|
|
268
|
+
`Lint: ${errors.length} issue(s) in \`${filePath}\` (${linterName})`,
|
|
269
|
+
"",
|
|
270
|
+
];
|
|
271
|
+
|
|
272
|
+
for (const err of shown) {
|
|
273
|
+
const loc = err.column ? `L${err.line}:${err.column}` : `L${err.line}`;
|
|
274
|
+
const sev = err.severity === "error" ? "error" : "warn";
|
|
275
|
+
const rule = err.rule ? ` [${err.rule}]` : "";
|
|
276
|
+
lines.push(`- **${sev}** ${loc}: ${err.message}${rule}`);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (errors.length > maxShown) {
|
|
280
|
+
lines.push(`- ... and ${errors.length - maxShown} more`);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
lines.push("");
|
|
284
|
+
lines.push("Fix these lint errors in the file you just edited.");
|
|
285
|
+
|
|
286
|
+
return lines.join("\n");
|
|
287
|
+
}
|
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
|
|
7
7
|
import * as fs from "node:fs";
|
|
8
8
|
import * as path from "node:path";
|
|
9
|
-
|
|
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
|
|
|
@@ -121,8 +122,8 @@ export function readStateJson(
|
|
|
121
122
|
const data = JSON.parse(raw) as Record<string, any>;
|
|
122
123
|
migrateConsumedFlags(data); // Migrate before dictToState
|
|
123
124
|
return dictToState(data);
|
|
124
|
-
} catch (
|
|
125
|
-
logWarn("state_io", `Failed to read state.json for '${contextId}': ${
|
|
125
|
+
} catch (error: any) {
|
|
126
|
+
logWarn("state_io", `Failed to read state.json for '${contextId}': ${error}`);
|
|
126
127
|
return null;
|
|
127
128
|
}
|
|
128
129
|
}
|
|
@@ -26,14 +26,14 @@ const childProcesses = new Set<ChildProcess>();
|
|
|
26
26
|
* Called by exit and signal handlers.
|
|
27
27
|
*/
|
|
28
28
|
function cleanupChildren(): void {
|
|
29
|
-
|
|
29
|
+
for (const child of childProcesses) {
|
|
30
30
|
try {
|
|
31
31
|
// Use SIGKILL for forceful termination (important on Windows with shell)
|
|
32
32
|
child.kill("SIGKILL");
|
|
33
33
|
} catch {
|
|
34
34
|
// Ignore errors (child may have already exited)
|
|
35
35
|
}
|
|
36
|
-
}
|
|
36
|
+
}
|
|
37
37
|
childProcesses.clear();
|
|
38
38
|
}
|
|
39
39
|
|
|
@@ -229,7 +229,7 @@ export function execFileAsync(
|
|
|
229
229
|
});
|
|
230
230
|
|
|
231
231
|
// Pipe input to stdin if provided
|
|
232
|
-
if (options?.input
|
|
232
|
+
if (options?.input !== null && options?.input !== undefined && child.stdin) {
|
|
233
233
|
child.stdin.write(options.input);
|
|
234
234
|
child.stdin.end();
|
|
235
235
|
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# Context Management System
|
|
2
|
+
|
|
3
|
+
Shared library for context lifecycle management: state machine, session binding, plan tracking, and artifact routing.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
A "context" is a named work session with a state machine that tracks mode transitions, staged artifacts, and session history. Contexts persist to disk as `state.json` files under `_output/contexts/{id}/`. They are the backbone of the handoff and session-restore system.
|
|
8
|
+
|
|
9
|
+
## File Structure
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
context/
|
|
13
|
+
├── CLAUDE.md ← This file
|
|
14
|
+
├── context-store.ts ← Core CRUD + state machine transitions
|
|
15
|
+
├── context-formatter.ts ← Context → human-readable text for injection
|
|
16
|
+
├── context-selector.ts ← Find active/relevant contexts by criteria
|
|
17
|
+
├── plan-manager.ts ← Plan file archive, discovery, hash, path extraction
|
|
18
|
+
└── task-tracker.ts ← Task progress tracking within a context
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## State Machine
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
┌─────────────┐
|
|
25
|
+
│ created │
|
|
26
|
+
└──────┬──────┘
|
|
27
|
+
│ first user prompt / maybeActivate()
|
|
28
|
+
▼
|
|
29
|
+
┌─────────────┐
|
|
30
|
+
┌──▶│ active │◀──────────────────┐
|
|
31
|
+
│ └──────┬──────┘ │
|
|
32
|
+
│ │ session_end + artifact │ session_start (clear)
|
|
33
|
+
│ ▼ │
|
|
34
|
+
│ ┌─────────────────┐ │
|
|
35
|
+
│ │ has_staged_work │ ──────────────┘
|
|
36
|
+
│ └──────┬──────────┘
|
|
37
|
+
│ │ work_consumed = true
|
|
38
|
+
│ ▼
|
|
39
|
+
│ ┌─────────────┐
|
|
40
|
+
│ │ archived │──▶ (terminal)
|
|
41
|
+
│ └─────────────┘
|
|
42
|
+
│
|
|
43
|
+
└─── reopenContext()
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Modes:**
|
|
47
|
+
- `created` — context initialized, not yet bound to a session
|
|
48
|
+
- `active` — session in progress, context is current
|
|
49
|
+
- `has_staged_work` — session ended with staged artifact (plan or handoff)
|
|
50
|
+
- `archived` — context complete and closed
|
|
51
|
+
|
|
52
|
+
## API Reference
|
|
53
|
+
|
|
54
|
+
### `context-store.ts`
|
|
55
|
+
|
|
56
|
+
| Function | Purpose |
|
|
57
|
+
|----------|---------|
|
|
58
|
+
| `createContext(id, opts)` | Create new context with initial state |
|
|
59
|
+
| `getContext(contextId)` | Read context by ID |
|
|
60
|
+
| `getAllContexts(mode?, root?)` | List all contexts, optionally filtered by mode |
|
|
61
|
+
| `getContextBySessionId(sessionId, root?)` | Find context that owns a session ID |
|
|
62
|
+
| `updateContext(contextId, patch)` | Partial state update |
|
|
63
|
+
| `bindSession(contextId, sessionId)` | Attach session ID to context |
|
|
64
|
+
| `updateMode(contextId, mode)` | Transition state machine mode |
|
|
65
|
+
| `maybeActivate(contextId)` | Activate if in `created` mode (idempotent) |
|
|
66
|
+
| `completeContext(contextId)` | Mark complete, archive |
|
|
67
|
+
| `archiveContext(contextId)` | Archive without completing |
|
|
68
|
+
| `reopenContext(contextId)` | Transition `archived` → `active` |
|
|
69
|
+
| `createContextFromPrompt(prompt)` | Create context from user prompt text |
|
|
70
|
+
| `loadState(contextId)` | Raw state.json read |
|
|
71
|
+
| `saveState(contextId, state)` | Raw state.json write |
|
|
72
|
+
| `determineArtifactType(context)` | Detect `"plan"` or `"handoff"` from staged state |
|
|
73
|
+
|
|
74
|
+
### `context-formatter.ts`
|
|
75
|
+
|
|
76
|
+
Formats context state for injection into Claude's context window.
|
|
77
|
+
|
|
78
|
+
| Function | Purpose |
|
|
79
|
+
|----------|---------|
|
|
80
|
+
| `formatContext(context)` | Full context as human-readable markdown |
|
|
81
|
+
| `formatContextSummary(context)` | Short one-line summary |
|
|
82
|
+
|
|
83
|
+
### `context-selector.ts`
|
|
84
|
+
|
|
85
|
+
Finds contexts by various criteria.
|
|
86
|
+
|
|
87
|
+
| Function | Purpose |
|
|
88
|
+
|----------|---------|
|
|
89
|
+
| `selectActiveContext(root?)` | Find the single active context (errors if multiple) |
|
|
90
|
+
| `findContextByMode(mode, root?)` | Find contexts in a given mode |
|
|
91
|
+
| `findStagedWorkContext(root?)` | Find context with `has_staged_work` mode |
|
|
92
|
+
|
|
93
|
+
### `plan-manager.ts`
|
|
94
|
+
|
|
95
|
+
Manages plan file lifecycle within a context.
|
|
96
|
+
|
|
97
|
+
| Function | Purpose |
|
|
98
|
+
|----------|---------|
|
|
99
|
+
| `archivePlan(contextId, planPath)` | Copy plan to context's `plans/` folder |
|
|
100
|
+
| `findLatestPlan(contextId)` | Find most recent archived plan |
|
|
101
|
+
| `generatePlanId()` | Generate unique plan ID |
|
|
102
|
+
| `normalizePlanContent(text)` | Strip metadata for hashing |
|
|
103
|
+
| `extractPlanAnchors(content)` | Extract key phrases from plan for matching |
|
|
104
|
+
| `findPlanPathInTranscript(transcriptPath)` | Parse plan path from JSONL transcript |
|
|
105
|
+
| `extractPlanPathFromResult(toolResult)` | Extract plan path from tool result JSON |
|
|
106
|
+
|
|
107
|
+
### `task-tracker.ts`
|
|
108
|
+
|
|
109
|
+
Tracks task progress (ISC criteria) within a context.
|
|
110
|
+
|
|
111
|
+
| Function | Purpose |
|
|
112
|
+
|----------|---------|
|
|
113
|
+
| `initTaskTracker(contextId)` | Create task tracker for context |
|
|
114
|
+
| `addTask(contextId, task)` | Add tracked task |
|
|
115
|
+
| `updateTask(contextId, taskId, patch)` | Update task status |
|
|
116
|
+
| `getTaskSummary(contextId)` | Progress summary |
|
|
117
|
+
|
|
118
|
+
## Which Hooks Use This System
|
|
119
|
+
|
|
120
|
+
| Hook | Usage |
|
|
121
|
+
|------|-------|
|
|
122
|
+
| `session_start.ts` | `getContextBySessionId()`, `bindSession()`, `updateMode()`, `getAllContexts()` |
|
|
123
|
+
| `session_end.ts` | `getContextBySessionId()`, `updateMode()`, `saveState()` |
|
|
124
|
+
| `user_prompt_submit.ts` | `getAllContexts()`, `maybeActivate()`, `determineArtifactType()` |
|
|
125
|
+
| `archive_plan.ts` | `getContextBySessionId()`, `archivePlan()` |
|
|
126
|
+
| `save_handoff.ts` | `getContextBySessionId()`, `updateContext()` |
|
|
127
|
+
| `cc-native-plan-review.ts` | `getContextBySessionId()`, `getAllContexts()` |
|
|
128
|
+
|
|
129
|
+
## Design Decisions
|
|
130
|
+
|
|
131
|
+
- **Single state.json per context:** All state is in one file. No distributed state. Atomic writes prevent corruption.
|
|
132
|
+
- **No moves out of lib-ts:** Context is pure library code imported by ~8 shared hooks. Moving it would require updating all those import paths for no structural gain. The subfolder is already co-located; it just needed documentation.
|
|
133
|
+
- **`maybeActivate()` is idempotent:** Can be called from any hook without checking current mode — safe to call repeatedly.
|
|
134
|
+
- **`determineArtifactType()` drives session restore:** Returns `"plan"` or `"handoff"` to dispatch the correct restoration path in `session_start.ts`.
|