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.
Files changed (134) hide show
  1. package/dist/commands/clean.d.ts +7 -0
  2. package/dist/commands/clean.js +17 -8
  3. package/dist/commands/clear.d.ts +85 -0
  4. package/dist/commands/clear.js +455 -347
  5. package/dist/commands/init/index.d.ts +15 -0
  6. package/dist/commands/init/index.js +79 -38
  7. package/dist/lib/gitignore-manager.js +12 -13
  8. package/dist/lib/settings-hierarchy.d.ts +13 -1
  9. package/dist/lib/settings-hierarchy.js +1 -1
  10. package/dist/lib/template-linter.d.ts +4 -0
  11. package/dist/lib/template-linter.js +1 -1
  12. package/dist/lib/tty-detection.d.ts +1 -0
  13. package/dist/lib/tty-detection.js +1 -0
  14. package/dist/templates/CLAUDE.md +27 -0
  15. package/dist/templates/_shared/.claude/settings.json +7 -7
  16. package/dist/templates/_shared/.claude/{commands/handoff.md → skills/handoff/SKILL.md} +4 -3
  17. package/dist/templates/_shared/.claude/{commands/handoff-resume.md → skills/handoff-resume/SKILL.md} +3 -2
  18. package/dist/templates/_shared/.claude/skills/meta-plan/SKILL.md +43 -0
  19. package/dist/templates/_shared/.codex/workflows/handoff.md +1 -1
  20. package/dist/templates/_shared/.codex/workflows/meta-plan.md +347 -0
  21. package/dist/templates/_shared/.windsurf/workflows/handoff.md +1 -1
  22. package/dist/templates/_shared/.windsurf/workflows/meta-plan.md +347 -0
  23. package/dist/templates/_shared/hooks-ts/lint_after_edit.ts +59 -0
  24. package/dist/templates/_shared/hooks-ts/session_end.ts +11 -10
  25. package/dist/templates/_shared/hooks-ts/session_start.ts +15 -12
  26. package/dist/templates/_shared/hooks-ts/user_prompt_submit.ts +12 -12
  27. package/dist/templates/_shared/lib-ts/CLAUDE.md +3 -3
  28. package/dist/templates/_shared/lib-ts/base/constants.ts +324 -306
  29. package/dist/templates/_shared/lib-ts/base/hook-utils.ts +26 -7
  30. package/dist/templates/_shared/lib-ts/base/inference.ts +19 -19
  31. package/dist/templates/_shared/lib-ts/base/lint-dispatch.ts +287 -0
  32. package/dist/templates/_shared/lib-ts/base/state-io.ts +4 -3
  33. package/dist/templates/_shared/lib-ts/base/subprocess-utils.ts +3 -3
  34. package/dist/templates/_shared/lib-ts/context/CLAUDE.md +134 -0
  35. package/dist/templates/_shared/lib-ts/context/context-formatter.ts +16 -15
  36. package/dist/templates/_shared/lib-ts/context/context-selector.ts +16 -16
  37. package/dist/templates/_shared/lib-ts/context/context-store.ts +15 -14
  38. package/dist/templates/_shared/lib-ts/context/plan-manager.ts +2 -2
  39. package/dist/templates/_shared/scripts/resolve-run.ts +61 -0
  40. package/dist/templates/_shared/scripts/resolve_context.ts +1 -1
  41. package/dist/templates/_shared/scripts/status_line.ts +100 -94
  42. package/dist/templates/_shared/{handoff-system → skills/handoff-system}/CLAUDE.md +433 -421
  43. package/dist/templates/_shared/{handoff-system → skills/handoff-system}/lib/document-generator.ts +5 -4
  44. package/dist/templates/_shared/{handoff-system → skills/handoff-system}/lib/handoff-reader.ts +2 -1
  45. package/dist/templates/_shared/{handoff-system → skills/handoff-system}/scripts/resume_handoff.ts +6 -6
  46. package/dist/templates/_shared/{handoff-system → skills/handoff-system}/scripts/save_handoff.ts +16 -17
  47. package/dist/templates/_shared/{handoff-system → skills/handoff-system}/workflows/handoff-resume.md +2 -2
  48. package/dist/templates/_shared/{handoff-system → skills/handoff-system}/workflows/handoff.md +3 -3
  49. package/dist/templates/_shared/skills/meta-plan/CLAUDE.md +44 -0
  50. package/dist/templates/_shared/skills/meta-plan/workflows/meta-plan.md +347 -0
  51. package/dist/templates/cc-native/.claude/commands/cc-native/specdev.md +1 -1
  52. package/dist/templates/cc-native/.claude/settings.json +86 -57
  53. package/dist/templates/cc-native/_cc-native/artifacts/CLAUDE.md +64 -0
  54. package/dist/templates/cc-native/_cc-native/{lib-ts/artifacts → artifacts/lib}/format.ts +599 -597
  55. package/dist/templates/cc-native/_cc-native/{lib-ts/artifacts → artifacts/lib}/index.ts +26 -26
  56. package/dist/templates/cc-native/_cc-native/{lib-ts/artifacts → artifacts/lib}/tracker.ts +107 -106
  57. package/dist/templates/cc-native/_cc-native/{lib-ts/artifacts → artifacts/lib}/write.ts +119 -118
  58. package/dist/templates/cc-native/_cc-native/hooks/CLAUDE.md +237 -247
  59. package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.ts +76 -74
  60. package/dist/templates/cc-native/_cc-native/hooks/validate_task_prompt.ts +76 -0
  61. package/dist/templates/cc-native/_cc-native/lib-ts/aggregate-agents.ts +163 -156
  62. package/dist/templates/cc-native/_cc-native/lib-ts/cc-native-state.ts +15 -16
  63. package/dist/templates/cc-native/_cc-native/lib-ts/index.ts +116 -116
  64. package/dist/templates/cc-native/_cc-native/lib-ts/plan-discovery.ts +3 -3
  65. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/embedding-indexer.ts +16 -12
  66. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/hyde.ts +2 -3
  67. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/index.ts +31 -31
  68. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/logger.ts +7 -6
  69. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/ollama-client.ts +9 -7
  70. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/retrieval-pipeline.ts +17 -14
  71. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/transcript-indexer.ts +41 -37
  72. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/transcript-loader.ts +43 -33
  73. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/transcript-searcher.ts +20 -20
  74. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/types.ts +9 -8
  75. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/vector-store.ts +4 -3
  76. package/dist/templates/cc-native/_cc-native/lib-ts/settings.ts +9 -10
  77. package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +20 -19
  78. package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +329 -329
  79. package/dist/templates/cc-native/_cc-native/plan-review/CLAUDE.md +149 -0
  80. package/dist/templates/cc-native/_cc-native/plan-review/agents/CLAUDE.md +143 -0
  81. package/dist/templates/cc-native/_cc-native/plan-review/agents/PLAN-ORCHESTRATOR.md +213 -0
  82. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-questions/PLAN-QUESTIONER.md +70 -0
  83. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/ARCH-EVOLUTION.md +62 -0
  84. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/ARCH-PATTERNS.md +61 -0
  85. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/ARCH-STRUCTURE.md +62 -0
  86. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/ASSUMPTION-TRACER.md +56 -0
  87. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/CLARITY-AUDITOR.md +53 -0
  88. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/COMPLETENESS-FEASIBILITY.md +66 -0
  89. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/COMPLETENESS-GAPS.md +70 -0
  90. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/COMPLETENESS-ORDERING.md +62 -0
  91. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/CONSTRAINT-VALIDATOR.md +72 -0
  92. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/DESIGN-ADR-VALIDATOR.md +61 -0
  93. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/DESIGN-SCALE-MATCHER.md +64 -0
  94. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/DEVILS-ADVOCATE.md +56 -0
  95. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/DOCUMENTATION-PHILOSOPHY.md +86 -0
  96. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/HANDOFF-READINESS.md +59 -0
  97. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/HIDDEN-COMPLEXITY.md +58 -0
  98. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/INCREMENTAL-DELIVERY.md +66 -0
  99. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/RISK-DEPENDENCY.md +62 -0
  100. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/RISK-FMEA.md +66 -0
  101. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/RISK-PREMORTEM.md +71 -0
  102. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/RISK-REVERSIBILITY.md +74 -0
  103. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/SCOPE-BOUNDARY.md +77 -0
  104. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/SIMPLICITY-GUARDIAN.md +62 -0
  105. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/SKEPTIC.md +68 -0
  106. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/TESTDRIVEN-BEHAVIOR-AUDITOR.md +61 -0
  107. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/TESTDRIVEN-CHARACTERIZATION.md +71 -0
  108. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/TESTDRIVEN-FIRST-VALIDATOR.md +61 -0
  109. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/TESTDRIVEN-PYRAMID-ANALYZER.md +61 -0
  110. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/TRADEOFF-COSTS.md +67 -0
  111. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/TRADEOFF-STAKEHOLDERS.md +65 -0
  112. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/VERIFY-COVERAGE.md +74 -0
  113. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/VERIFY-STRENGTH.md +69 -0
  114. package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/agent-selection.ts +162 -163
  115. package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/corroboration.ts +119 -119
  116. package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/graduation.ts +132 -132
  117. package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/orchestrator.ts +70 -70
  118. package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/output-builder.ts +121 -130
  119. package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/plan-questions.ts +101 -102
  120. package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/review-pipeline.ts +507 -511
  121. package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/reviewers/agent.ts +73 -74
  122. package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/reviewers/base/base-agent.ts +217 -217
  123. package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/reviewers/index.ts +12 -12
  124. package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/reviewers/providers/claude-agent.ts +66 -66
  125. package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/reviewers/providers/codex-agent.ts +185 -185
  126. package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/reviewers/providers/gemini-agent.ts +39 -39
  127. package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/reviewers/providers/orchestrator-claude-agent.ts +196 -196
  128. package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/reviewers/schemas.ts +201 -201
  129. package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/reviewers/types.ts +23 -23
  130. package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/verdict.ts +72 -72
  131. package/dist/templates/cc-native/_cc-native/{workflows → plan-review/workflows}/specdev.md +9 -9
  132. package/oclif.manifest.json +1 -1
  133. package/package.json +6 -5
  134. 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
- export { setSessionId };
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
- case "PreToolUse":
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
- case "UserPromptSubmit":
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 {logInfo, logError, logBlocking, logHookError, logDiagnostic, setContextPath, hookLog, logDebug, logWarn} from "./logger.js";
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-3-haiku-20240307",
16
- standard: "claude-sonnet-4-20250514",
17
- smart: "claude-opus-4-20250514",
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.replace(/\r?\n/g, " ").replace(/"/g, '""') + '"';
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 (e: any) {
77
+ } catch (error: any) {
78
78
  const latencyMs = Date.now() - startTime;
79
79
 
80
- if (e.code === "ETIMEDOUT" || e.killed) {
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 (e.code === "ENOENT") {
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 (e.status !== undefined && e.status !== 0) {
99
+ if (error.status !== undefined && error.status !== 0) {
100
100
  return {
101
101
  success: false,
102
- output: (e.stdout ?? "").toString().trim(),
103
- error: (e.stderr ?? "").toString().trim() || `Exit code: ${e.status}`,
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(e),
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.replace(/^["']+|["']+$/g, "");
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.replace(/^["'`]+|["'`]+$/g, "");
224
+ slug = slug.replaceAll(/^["'`]+|["'`]+$/g, "");
225
225
  slug = slug.replace(/[.!?]+$/, "");
226
- slug = slug.replace(/-/g, " ");
227
- slug = slug.replace(/[^a-zA-Z0-9 ]/g, "");
228
- slug = slug.replace(/\s+/g, " ").trim();
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.replace(/\r?\n/g, " "))
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
- import { getContextDir } from "./constants.js";
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 (e: any) {
125
- logWarn("state_io", `Failed to read state.json for '${contextId}': ${e}`);
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
- childProcesses.forEach((child) => {
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 != null && child.stdin) {
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`.