aiwcli 0.11.1 → 0.12.1

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 (117) hide show
  1. package/dist/commands/clear.d.ts +8 -0
  2. package/dist/commands/clear.js +86 -0
  3. package/dist/lib/bmad-installer.d.ts +2 -27
  4. package/dist/lib/bmad-installer.js +3 -43
  5. package/dist/lib/claude-settings-types.d.ts +2 -1
  6. package/dist/lib/env-compat.d.ts +0 -8
  7. package/dist/lib/env-compat.js +0 -12
  8. package/dist/lib/git/index.d.ts +0 -1
  9. package/dist/lib/gitignore-manager.d.ts +0 -2
  10. package/dist/lib/gitignore-manager.js +1 -1
  11. package/dist/lib/hooks-merger.d.ts +1 -15
  12. package/dist/lib/hooks-merger.js +1 -1
  13. package/dist/lib/index.d.ts +3 -7
  14. package/dist/lib/index.js +3 -11
  15. package/dist/lib/output.d.ts +2 -1
  16. package/dist/lib/settings-hierarchy.d.ts +1 -13
  17. package/dist/lib/settings-hierarchy.js +1 -1
  18. package/dist/lib/template-installer.d.ts +5 -9
  19. package/dist/lib/template-installer.js +3 -13
  20. package/dist/lib/template-linter.d.ts +3 -10
  21. package/dist/lib/template-linter.js +2 -2
  22. package/dist/lib/template-resolver.d.ts +6 -0
  23. package/dist/lib/template-resolver.js +10 -0
  24. package/dist/lib/template-settings-reconstructor.d.ts +1 -1
  25. package/dist/lib/template-settings-reconstructor.js +17 -24
  26. package/dist/lib/terminal.d.ts +3 -14
  27. package/dist/lib/terminal.js +0 -4
  28. package/dist/lib/version.d.ts +2 -11
  29. package/dist/lib/version.js +3 -3
  30. package/dist/lib/windsurf-hooks-merger.d.ts +1 -15
  31. package/dist/lib/windsurf-hooks-merger.js +1 -1
  32. package/dist/templates/_shared/.codex/workflows/handoff.md +1 -1
  33. package/dist/templates/_shared/.windsurf/workflows/handoff.md +1 -1
  34. package/dist/templates/_shared/hooks-ts/session_end.ts +75 -4
  35. package/dist/templates/_shared/hooks-ts/session_start.ts +11 -13
  36. package/dist/templates/_shared/hooks-ts/user_prompt_submit.ts +6 -8
  37. package/dist/templates/_shared/lib-ts/CLAUDE.md +56 -7
  38. package/dist/templates/_shared/lib-ts/base/hook-utils.ts +176 -29
  39. package/dist/templates/_shared/lib-ts/base/logger.ts +1 -1
  40. package/dist/templates/_shared/lib-ts/base/state-io.ts +11 -2
  41. package/dist/templates/_shared/lib-ts/base/subprocess-utils.ts +181 -165
  42. package/dist/templates/_shared/lib-ts/context/plan-manager.ts +14 -13
  43. package/dist/templates/_shared/lib-ts/handoff/handoff-reader.ts +3 -2
  44. package/dist/templates/_shared/lib-ts/package.json +1 -2
  45. package/dist/templates/_shared/lib-ts/templates/plan-context.ts +27 -34
  46. package/dist/templates/_shared/lib-ts/types.ts +17 -2
  47. package/dist/templates/_shared/scripts/resume_handoff.ts +4 -4
  48. package/dist/templates/_shared/scripts/save_handoff.ts +7 -7
  49. package/dist/templates/_shared/scripts/status_line.ts +104 -71
  50. package/dist/templates/_shared/workflows/handoff.md +1 -1
  51. package/dist/templates/cc-native/.claude/settings.json +182 -175
  52. package/dist/templates/cc-native/_cc-native/agents/CLAUDE.md +23 -1
  53. package/dist/templates/cc-native/_cc-native/agents/plan-questions/PLAN-QUESTIONER.md +70 -0
  54. package/dist/templates/cc-native/_cc-native/hooks/CLAUDE.md +6 -1
  55. package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.ts +142 -111
  56. package/dist/templates/cc-native/_cc-native/hooks/enhance_plan_post_subagent.ts +54 -0
  57. package/dist/templates/cc-native/_cc-native/hooks/enhance_plan_post_write.ts +52 -0
  58. package/dist/templates/cc-native/_cc-native/hooks/mark_questions_asked.ts +53 -0
  59. package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.ts +19 -19
  60. package/dist/templates/cc-native/_cc-native/lib-ts/aggregate-agents.ts +6 -5
  61. package/dist/templates/cc-native/_cc-native/lib-ts/artifacts.ts +114 -83
  62. package/dist/templates/cc-native/_cc-native/lib-ts/cc-native-state.ts +107 -10
  63. package/dist/templates/cc-native/_cc-native/lib-ts/cli-output-parser.ts +1 -1
  64. package/dist/templates/cc-native/_cc-native/lib-ts/corroboration.ts +6 -2
  65. package/dist/templates/cc-native/_cc-native/lib-ts/index.ts +0 -4
  66. package/dist/templates/cc-native/_cc-native/lib-ts/orchestrator.ts +40 -219
  67. package/dist/templates/cc-native/_cc-native/lib-ts/plan-enhancement.ts +41 -0
  68. package/dist/templates/cc-native/_cc-native/lib-ts/plan-questions.ts +102 -0
  69. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/agent.ts +26 -227
  70. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/base/base-agent.ts +217 -0
  71. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/index.ts +4 -2
  72. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/claude-agent.ts +65 -0
  73. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/codex-agent.ts +185 -0
  74. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/gemini-agent.ts +39 -0
  75. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/orchestrator-claude-agent.ts +195 -0
  76. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/schemas.ts +201 -0
  77. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/types.ts +2 -2
  78. package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +17 -16
  79. package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +13 -108
  80. package/dist/templates/cc-native/_cc-native/lib-ts/verdict.ts +3 -3
  81. package/dist/templates/cc-native/_cc-native/plan-review.config.json +2 -14
  82. package/oclif.manifest.json +1 -1
  83. package/package.json +1 -2
  84. package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.ts +0 -119
  85. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/codex.ts +0 -130
  86. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/gemini.ts +0 -107
  87. /package/dist/templates/cc-native/_cc-native/agents/{ARCH-EVOLUTION.md → plan-review/ARCH-EVOLUTION.md} +0 -0
  88. /package/dist/templates/cc-native/_cc-native/agents/{ARCH-PATTERNS.md → plan-review/ARCH-PATTERNS.md} +0 -0
  89. /package/dist/templates/cc-native/_cc-native/agents/{ARCH-STRUCTURE.md → plan-review/ARCH-STRUCTURE.md} +0 -0
  90. /package/dist/templates/cc-native/_cc-native/agents/{ASSUMPTION-TRACER.md → plan-review/ASSUMPTION-TRACER.md} +0 -0
  91. /package/dist/templates/cc-native/_cc-native/agents/{CLARITY-AUDITOR.md → plan-review/CLARITY-AUDITOR.md} +0 -0
  92. /package/dist/templates/cc-native/_cc-native/agents/{COMPLETENESS-FEASIBILITY.md → plan-review/COMPLETENESS-FEASIBILITY.md} +0 -0
  93. /package/dist/templates/cc-native/_cc-native/agents/{COMPLETENESS-GAPS.md → plan-review/COMPLETENESS-GAPS.md} +0 -0
  94. /package/dist/templates/cc-native/_cc-native/agents/{COMPLETENESS-ORDERING.md → plan-review/COMPLETENESS-ORDERING.md} +0 -0
  95. /package/dist/templates/cc-native/_cc-native/agents/{CONSTRAINT-VALIDATOR.md → plan-review/CONSTRAINT-VALIDATOR.md} +0 -0
  96. /package/dist/templates/cc-native/_cc-native/agents/{DESIGN-ADR-VALIDATOR.md → plan-review/DESIGN-ADR-VALIDATOR.md} +0 -0
  97. /package/dist/templates/cc-native/_cc-native/agents/{DESIGN-SCALE-MATCHER.md → plan-review/DESIGN-SCALE-MATCHER.md} +0 -0
  98. /package/dist/templates/cc-native/_cc-native/agents/{DEVILS-ADVOCATE.md → plan-review/DEVILS-ADVOCATE.md} +0 -0
  99. /package/dist/templates/cc-native/_cc-native/agents/{DOCUMENTATION-PHILOSOPHY.md → plan-review/DOCUMENTATION-PHILOSOPHY.md} +0 -0
  100. /package/dist/templates/cc-native/_cc-native/agents/{HANDOFF-READINESS.md → plan-review/HANDOFF-READINESS.md} +0 -0
  101. /package/dist/templates/cc-native/_cc-native/agents/{HIDDEN-COMPLEXITY.md → plan-review/HIDDEN-COMPLEXITY.md} +0 -0
  102. /package/dist/templates/cc-native/_cc-native/agents/{INCREMENTAL-DELIVERY.md → plan-review/INCREMENTAL-DELIVERY.md} +0 -0
  103. /package/dist/templates/cc-native/_cc-native/agents/{RISK-DEPENDENCY.md → plan-review/RISK-DEPENDENCY.md} +0 -0
  104. /package/dist/templates/cc-native/_cc-native/agents/{RISK-FMEA.md → plan-review/RISK-FMEA.md} +0 -0
  105. /package/dist/templates/cc-native/_cc-native/agents/{RISK-PREMORTEM.md → plan-review/RISK-PREMORTEM.md} +0 -0
  106. /package/dist/templates/cc-native/_cc-native/agents/{RISK-REVERSIBILITY.md → plan-review/RISK-REVERSIBILITY.md} +0 -0
  107. /package/dist/templates/cc-native/_cc-native/agents/{SCOPE-BOUNDARY.md → plan-review/SCOPE-BOUNDARY.md} +0 -0
  108. /package/dist/templates/cc-native/_cc-native/agents/{SIMPLICITY-GUARDIAN.md → plan-review/SIMPLICITY-GUARDIAN.md} +0 -0
  109. /package/dist/templates/cc-native/_cc-native/agents/{SKEPTIC.md → plan-review/SKEPTIC.md} +0 -0
  110. /package/dist/templates/cc-native/_cc-native/agents/{TESTDRIVEN-BEHAVIOR-AUDITOR.md → plan-review/TESTDRIVEN-BEHAVIOR-AUDITOR.md} +0 -0
  111. /package/dist/templates/cc-native/_cc-native/agents/{TESTDRIVEN-CHARACTERIZATION.md → plan-review/TESTDRIVEN-CHARACTERIZATION.md} +0 -0
  112. /package/dist/templates/cc-native/_cc-native/agents/{TESTDRIVEN-FIRST-VALIDATOR.md → plan-review/TESTDRIVEN-FIRST-VALIDATOR.md} +0 -0
  113. /package/dist/templates/cc-native/_cc-native/agents/{TESTDRIVEN-PYRAMID-ANALYZER.md → plan-review/TESTDRIVEN-PYRAMID-ANALYZER.md} +0 -0
  114. /package/dist/templates/cc-native/_cc-native/agents/{TRADEOFF-COSTS.md → plan-review/TRADEOFF-COSTS.md} +0 -0
  115. /package/dist/templates/cc-native/_cc-native/agents/{TRADEOFF-STAKEHOLDERS.md → plan-review/TRADEOFF-STAKEHOLDERS.md} +0 -0
  116. /package/dist/templates/cc-native/_cc-native/agents/{VERIFY-COVERAGE.md → plan-review/VERIFY-COVERAGE.md} +0 -0
  117. /package/dist/templates/cc-native/_cc-native/agents/{VERIFY-STRENGTH.md → plan-review/VERIFY-STRENGTH.md} +0 -0
@@ -1,165 +1,181 @@
1
- /**
2
- * Subprocess environment utilities.
3
- * See SPEC.md §5.10
4
- */
5
-
6
- import { execSync, execFile } from "node:child_process";
7
-
8
- /**
9
- * Check if this is an internal subprocess call.
10
- * All hooks should check this and return early to prevent recursion.
11
- */
12
- export function isInternalCall(): boolean {
13
- return process.env.AIWCLI_INTERNAL_CALL === "true";
14
- }
15
-
16
- /**
17
- * Get environment for internal subprocess calls.
18
- * Returns a copy of process.env with AIWCLI_INTERNAL_CALL=true.
19
- */
20
- export function getInternalSubprocessEnv(): Record<string, string | undefined> {
21
- return {
22
- ...process.env,
23
- AIWCLI_INTERNAL_CALL: "true",
24
- };
25
- }
26
-
27
- /**
28
- * Find an executable on the system PATH.
29
- * Uses `where` on Windows, `which` on Unix.
30
- * On Windows, prefers .cmd/.exe over extensionless shims since
31
- * execFileSync cannot spawn extensionless shell scripts.
32
- * Returns the first match or null if not found.
33
- */
34
- export function findExecutable(name: string): string | null {
35
- try {
36
- const cmd = process.platform === "win32" ? `where ${name}` : `which ${name}`;
37
- const lines = execSync(cmd, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], shell: true })
38
- .trim()
39
- .split(/\r?\n/)
40
- .map((l) => l.trim())
41
- .filter(Boolean);
42
-
43
- if (lines.length === 0) return null;
44
-
45
- // On Windows, `where` may return an extensionless shim first (e.g. npm creates
46
- // both `claude` and `claude.cmd`). execFileSync can't spawn the extensionless
47
- // one, so prefer .cmd or .exe.
48
- if (process.platform === "win32") {
49
- const preferred = lines.find((l) => /\.(cmd|exe)$/i.test(l));
50
- return preferred ?? lines[0] ?? null;
51
- }
52
-
53
- return lines[0] ?? null;
54
- } catch {
55
- return null;
56
- }
57
- }
58
-
59
- /**
60
- * Type guard for Node.js child_process exec errors.
61
- * ExecSync throws objects with these extra properties on non-zero exit or timeout.
62
- */
63
- export interface ExecSyncError {
64
- killed: boolean;
65
- signal: string | null;
66
- stdout: Buffer | string;
67
- stderr: Buffer | string;
68
- status: number | null;
69
- message: string;
70
- }
71
-
72
- /** Check if an unknown error is an ExecSync error with process info. */
73
- export function isExecSyncError(e: unknown): e is ExecSyncError {
74
- return (
75
- typeof e === "object" &&
76
- e !== null &&
77
- "killed" in e &&
78
- "signal" in e
79
- );
80
- }
81
-
82
- // ---------------------------------------------------------------------------
83
- // Async Subprocess Execution
84
- // ---------------------------------------------------------------------------
85
-
86
- /**
87
- * Result from an async subprocess execution.
88
- * Never throws callers inspect fields to determine outcome.
89
- */
90
- export interface ExecResult {
91
- stdout: string;
92
- stderr: string;
93
- exitCode: number;
94
- killed: boolean;
95
- signal: string | null;
96
- }
97
-
98
- /** Options for execFileAsync. */
99
- export interface ExecAsyncOptions {
100
- /** Data piped to the child's stdin. */
101
- input?: string;
102
- /** Timeout in milliseconds (not seconds). */
103
- timeout?: number;
104
- /** Environment variables for the child process. */
105
- env?: Record<string, string | undefined>;
106
- /** Maximum bytes on stdout/stderr. Default: 10 MB. */
107
- maxBuffer?: number;
108
- /** Use shell for execution. Required on Windows for .cmd files. */
109
- shell?: boolean;
110
- }
111
-
112
- /**
113
- * Async subprocess execution that does NOT block the event loop.
114
- * Drop-in replacement for execFileSync in Promise-based parallel patterns.
115
- *
116
- * Returns ExecResult on both success and non-zero exit.
117
- * On timeout: result.killed = true, result.signal = "SIGTERM".
118
- * On spawn failure: result.exitCode = -1, result.stderr contains error.
119
- */
120
- export function execFileAsync(
121
- file: string,
122
- args: string[],
123
- options?: ExecAsyncOptions,
124
- ): Promise<ExecResult> {
125
- return new Promise((resolve) => {
126
- const child = execFile(
127
- file,
128
- args,
129
- {
130
- encoding: "utf-8",
131
- timeout: options?.timeout ?? 0,
132
- env: options?.env as NodeJS.ProcessEnv,
133
- maxBuffer: options?.maxBuffer ?? 10 * 1024 * 1024,
134
- shell: options?.shell,
135
- },
136
- (error, stdout, stderr) => {
137
- if (error) {
138
- // execFile callback error includes process exit info
139
- const errObj = error as unknown as Record<string, unknown>;
140
- resolve({
141
- stdout: String(stdout ?? ""),
142
- stderr: String(stderr ?? ""),
143
- exitCode: typeof errObj.code === "number" ? errObj.code : (error as any).status ?? 1,
144
- killed: Boolean(errObj.killed),
145
- signal: typeof errObj.signal === "string" ? errObj.signal : null,
146
- });
147
- } else {
148
- resolve({
149
- stdout: String(stdout ?? ""),
150
- stderr: String(stderr ?? ""),
151
- exitCode: 0,
152
- killed: false,
153
- signal: null,
154
- });
155
- }
156
- },
157
- );
158
-
159
- // Pipe input to stdin if provided
160
- if (options?.input != null && child.stdin) {
161
- child.stdin.write(options.input);
162
- child.stdin.end();
163
- }
164
- });
165
- }
1
+ /**
2
+ * Subprocess environment utilities.
3
+ * See SPEC.md §5.10
4
+ */
5
+
6
+ import { execSync, execFile } from "node:child_process";
7
+
8
+ /**
9
+ * Check if this is an internal subprocess call.
10
+ * All hooks should check this and return early to prevent recursion.
11
+ */
12
+ export function isInternalCall(): boolean {
13
+ return process.env.AIWCLI_INTERNAL_CALL === "true";
14
+ }
15
+
16
+ /**
17
+ * Get environment for internal subprocess calls.
18
+ * Returns a copy of process.env with AIWCLI_INTERNAL_CALL=true and
19
+ * Claude Code nesting-detection env vars removed so subprocess
20
+ * claude instances can run without being blocked.
21
+ */
22
+ export function getInternalSubprocessEnv(): Record<string, string | undefined> {
23
+ const env = {
24
+ ...process.env,
25
+ AIWCLI_INTERNAL_CALL: "true",
26
+ };
27
+ // Explicitly delete vars that block subprocess calls (set to undefined does not work)
28
+ delete env.CLAUDECODE;
29
+ delete env.CLAUDE_CODE_ENTRYPOINT;
30
+ return env;
31
+ }
32
+ /**
33
+ * Find an executable on the system PATH.
34
+ * Uses `where` on Windows, `which` on Unix.
35
+ * On Windows, prefers .cmd/.exe over extensionless shims since
36
+ * execFileSync cannot spawn extensionless shell scripts.
37
+ * Returns the first match or null if not found.
38
+ */
39
+ export function findExecutable(name: string): string | null {
40
+ try {
41
+ const cmd = process.platform === "win32" ? `where ${name}` : `which ${name}`;
42
+ const lines = execSync(cmd, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], shell: true })
43
+ .trim()
44
+ .split(/\r?\n/)
45
+ .map((l) => l.trim())
46
+ .filter(Boolean);
47
+
48
+ if (lines.length === 0) return null;
49
+
50
+ // On Windows, `where` may return an extensionless shim first (e.g. npm creates
51
+ // both `claude` and `claude.cmd`). execFileSync can't spawn the extensionless
52
+ // one, so prefer .cmd or .exe.
53
+ if (process.platform === "win32") {
54
+ const preferred = lines.find((l) => /\.(cmd|exe)$/i.test(l));
55
+ return preferred ?? lines[0] ?? null;
56
+ }
57
+
58
+ return lines[0] ?? null;
59
+ } catch {
60
+ return null;
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Type guard for Node.js child_process exec errors.
66
+ * ExecSync throws objects with these extra properties on non-zero exit or timeout.
67
+ */
68
+ export interface ExecSyncError {
69
+ killed: boolean;
70
+ signal: string | null;
71
+ stdout: Buffer | string;
72
+ stderr: Buffer | string;
73
+ status: number | null;
74
+ message: string;
75
+ }
76
+
77
+ /** Check if an unknown error is an ExecSync error with process info. */
78
+ export function isExecSyncError(e: unknown): e is ExecSyncError {
79
+ return (
80
+ typeof e === "object" &&
81
+ e !== null &&
82
+ "killed" in e &&
83
+ "signal" in e
84
+ );
85
+ }
86
+
87
+ /**
88
+ * Quote a string for use as a cmd.exe argument when shell: true.
89
+ * Wraps in double quotes and escapes inner double quotes as "".
90
+ * On non-Windows platforms, returns the string unchanged (execFile
91
+ * handles quoting automatically without shell).
92
+ */
93
+ export function shellQuoteWin(arg: string): string {
94
+ if (process.platform !== "win32") return arg;
95
+ return '"' + arg.replaceAll('"', '""') + '"';
96
+ }
97
+
98
+ // ---------------------------------------------------------------------------
99
+ // Async Subprocess Execution
100
+ // ---------------------------------------------------------------------------
101
+
102
+ /**
103
+ * Result from an async subprocess execution.
104
+ * Never throws callers inspect fields to determine outcome.
105
+ */
106
+ export interface ExecResult {
107
+ stdout: string;
108
+ stderr: string;
109
+ exitCode: number;
110
+ killed: boolean;
111
+ signal: string | null;
112
+ }
113
+
114
+ /** Options for execFileAsync. */
115
+ export interface ExecAsyncOptions {
116
+ /** Data piped to the child's stdin. */
117
+ input?: string;
118
+ /** Timeout in milliseconds (not seconds). */
119
+ timeout?: number;
120
+ /** Environment variables for the child process. */
121
+ env?: Record<string, string | undefined>;
122
+ /** Maximum bytes on stdout/stderr. Default: 10 MB. */
123
+ maxBuffer?: number;
124
+ /** Use shell for execution. Required on Windows for .cmd files. */
125
+ shell?: boolean;
126
+ }
127
+
128
+ /**
129
+ * Async subprocess execution that does NOT block the event loop.
130
+ * Drop-in replacement for execFileSync in Promise-based parallel patterns.
131
+ *
132
+ * Returns ExecResult on both success and non-zero exit.
133
+ * On timeout: result.killed = true, result.signal = "SIGTERM".
134
+ * On spawn failure: result.exitCode = -1, result.stderr contains error.
135
+ */
136
+ export function execFileAsync(
137
+ file: string,
138
+ args: string[],
139
+ options?: ExecAsyncOptions,
140
+ ): Promise<ExecResult> {
141
+ return new Promise((resolve) => {
142
+ const child = execFile(
143
+ file,
144
+ args,
145
+ {
146
+ encoding: "utf-8",
147
+ timeout: options?.timeout ?? 0,
148
+ env: options?.env as NodeJS.ProcessEnv,
149
+ maxBuffer: options?.maxBuffer ?? 10 * 1024 * 1024,
150
+ shell: options?.shell,
151
+ },
152
+ (error, stdout, stderr) => {
153
+ if (error) {
154
+ // execFile callback error includes process exit info
155
+ const errObj = error as unknown as Record<string, unknown>;
156
+ resolve({
157
+ stdout: String(stdout ?? ""),
158
+ stderr: String(stderr ?? ""),
159
+ exitCode: typeof errObj.code === "number" ? errObj.code : (error as any).status ?? 1,
160
+ killed: Boolean(errObj.killed),
161
+ signal: typeof errObj.signal === "string" ? errObj.signal : null,
162
+ });
163
+ } else {
164
+ resolve({
165
+ stdout: String(stdout ?? ""),
166
+ stderr: String(stderr ?? ""),
167
+ exitCode: 0,
168
+ killed: false,
169
+ signal: null,
170
+ });
171
+ }
172
+ },
173
+ );
174
+
175
+ // Pipe input to stdin if provided
176
+ if (options?.input != null && child.stdin) {
177
+ child.stdin.write(options.input);
178
+ child.stdin.end();
179
+ }
180
+ });
181
+ }
@@ -8,11 +8,12 @@
8
8
  * - extractPlanPathFromResult: parse plan path from ExitPlanMode output
9
9
  */
10
10
 
11
- import * as fs from "node:fs";
12
- import * as path from "node:path";
13
11
  import * as crypto from "node:crypto";
14
- import { getContextDir, getContextPlansDir, sanitizeTitle } from "../base/constants.js";
12
+ import * as fs from "node:fs";
13
+ import * as path from "node:path";
14
+
15
15
  import { atomicWrite } from "../base/atomic-write.js";
16
+ import { getContextDir, getContextPlansDir, sanitizeTitle } from "../base/constants.js";
16
17
  import { logDebug, logInfo, logWarn, logError } from "../base/logger.js";
17
18
  import { generateSlug } from "../base/utils.js";
18
19
  import type { ContextState } from "../types.js";
@@ -43,8 +44,8 @@ export function archivePlan(
43
44
  let content: string;
44
45
  try {
45
46
  content = fs.readFileSync(planPath, "utf-8");
46
- } catch (e: any) {
47
- logError("plan_manager", `Failed to read plan: ${e}`);
47
+ } catch (error_: any) {
48
+ logError("plan_manager", `Failed to read plan: ${error_}`);
48
49
  return [null, null, null];
49
50
  }
50
51
 
@@ -148,8 +149,8 @@ export function findLatestPlan(
148
149
  if (state?.plan_path && fs.existsSync(state.plan_path)) {
149
150
  return state.plan_path;
150
151
  }
151
- } catch (e: any) {
152
- logWarn("plan_manager", `Failed to check state.json plan_path: ${e}`);
152
+ } catch (error: any) {
153
+ logWarn("plan_manager", `Failed to check state.json plan_path: ${error}`);
153
154
  }
154
155
 
155
156
  // 2. Fall back to most recent .md in plans/ dir
@@ -182,7 +183,7 @@ export function findLatestPlan(
182
183
  * See SPEC.md §9.4
183
184
  */
184
185
  export function generatePlanId(): string {
185
- return crypto.randomUUID().replace(/-/g, "").slice(0, 8);
186
+ return crypto.randomUUID().replaceAll('-', "").slice(0, 8);
186
187
  }
187
188
 
188
189
  /**
@@ -191,8 +192,8 @@ export function generatePlanId(): string {
191
192
  * See SPEC.md §9.5
192
193
  */
193
194
  export function normalizePlanContent(text: string): string {
194
- let result = text.replace(/<[^>]+>/g, "");
195
- result = result.replace(/\s+/g, " ").trim();
195
+ let result = text.replaceAll(/<[^>]+>/g, "");
196
+ result = result.replaceAll(/\s+/g, " ").trim();
196
197
  return result;
197
198
  }
198
199
 
@@ -249,8 +250,8 @@ export function findPlanPathInTranscript(transcriptPath: string): string | null
249
250
  let lines: string[];
250
251
  try {
251
252
  lines = fs.readFileSync(transcriptPath, "utf-8").split(/\r?\n/);
252
- } catch (e: any) {
253
- logWarn("plan_manager", `Failed to read transcript: ${e}`);
253
+ } catch (error: any) {
254
+ logWarn("plan_manager", `Failed to read transcript: ${error}`);
254
255
  return null;
255
256
  }
256
257
 
@@ -282,7 +283,7 @@ export function findPlanPathInTranscript(transcriptPath: string): string | null
282
283
  if (!filePath) continue;
283
284
 
284
285
  // Check if path contains .claude/plans/ as consecutive parts
285
- const parts = filePath.replace(/\\/g, "/").split("/");
286
+ const parts = filePath.replaceAll('\\', "/").split("/");
286
287
  for (let j = 0; j < parts.length - 1; j++) {
287
288
  if (parts[j] === ".claude" && parts[j + 1] === "plans") {
288
289
  logInfo("plan_manager", `Extracted plan path from transcript: ${filePath}`);
@@ -7,7 +7,8 @@
7
7
  */
8
8
 
9
9
  import * as fs from "node:fs";
10
- import * as path from "node:path";
10
+ import * as path from "node:path";
11
+
11
12
  import { getContextHandoffsDir } from "../base/constants.js";
12
13
  import { getContext } from "../context/context-store.js";
13
14
  import type { HandoffSections } from "../types.js";
@@ -29,7 +30,7 @@ export function findLatestHandoff(contextId: string, projectRoot?: string): stri
29
30
  .sort();
30
31
 
31
32
  if (entries.length === 0) return null;
32
- return path.join(handoffsDir, entries[entries.length - 1]!);
33
+ return path.join(handoffsDir, entries.at(-1)!);
33
34
  } catch {
34
35
  return null;
35
36
  }
@@ -7,8 +7,7 @@
7
7
  "test:unit": "mocha '__tests__/base/**/*.test.ts' '__tests__/templates/**/*.test.ts'",
8
8
  "test:contract": "mocha '__tests__/context/**/*.test.ts' '__tests__/handoff/**/*.test.ts'",
9
9
  "test:integration": "mocha '__tests__/integration/**/*.test.ts'",
10
- "test:parity": "mocha '__tests__/integration/python-parity.test.ts'",
11
- "fixtures": "python __tests__/fixtures/generate_fixtures.py"
10
+ "test:parity": "mocha '__tests__/integration/python-parity.test.ts'"
12
11
  },
13
12
  "devDependencies": {
14
13
  "mocha": "^10.0.0",
@@ -1,65 +1,58 @@
1
1
  /**
2
- * Plan context templates for add_plan_context hook.
3
- * See SPEC.md §13.6
2
+ * Plan evaluation guidance template.
3
+ * Injected as context to guide the Plan agent during plan creation.
4
4
  */
5
5
 
6
6
  export function getEvaluationContextReminder(): string {
7
- return `## CRITICAL: Write This Plan for a Different Agent
7
+ return `## Write This Plan for a Different Agent
8
8
 
9
- The agent executing this plan has ZERO context from this conversation — no chat history, no memory of files you explored or research you did.
9
+ The agent executing this plan has zero context from this conversation — no chat history, no memory of files explored or decisions made.
10
10
 
11
- **Write as if YOU are that agent. What would you need?**
11
+ Write as if you are that agent. What would you need?
12
12
 
13
- ### Required Structure
13
+ ### Structure
14
14
 
15
15
  \`\`\`
16
- # Plan: <descriptive title>
16
+ # Plan: [descriptive title]
17
17
 
18
18
  ## Background
19
- Why this change is needed (2-3 sentences)
19
+ Why this change is needed (2-3 sentences of motivation)
20
20
 
21
21
  ## Task
22
- What exactly to build/change
22
+ What exactly to build or change
23
23
 
24
24
  ## Files
25
25
  **Modify:**
26
- - \`exact/path/to/file.py\` - What changes (reference line numbers or patterns)
26
+ - \`exact/path/to/file.ext\` What changes and why
27
27
 
28
28
  **Reference:**
29
- - \`exact/path/to/reference.py\` - Why relevant (e.g., "pattern to follow at lines 12-30")
29
+ - \`exact/path/to/reference.ext\` Why relevant (e.g., "pattern to follow at lines 12-30")
30
30
 
31
31
  ## Steps
32
- 1. [Specific steps with function names, patterns, or code snippets]
32
+ Numbered steps with specific details. For each step, consider whether any of the skills available in your system-reminder messages would help the implementation agent — if so, reference the skill inline at the point of use.
33
+
34
+ 1. [Specific action with function names, patterns, or code snippets]
33
35
  2. [Enough detail for someone who never saw this conversation]
34
36
 
35
37
  ## Constraints
36
- - Technical requirements, preferences, or limitations
37
-
38
- ## Documentation
39
- Decisions not written down are lost when this session ends. Update the nearest CLAUDE.md and MEMORY.md so the next session inherits what you learned.
40
-
41
- **CLAUDE.md** (nearest to changed code — cascades to subdirectories):
42
- - \`exact/path/to/CLAUDE.md\` — What to document
43
-
44
- **What to write:**
45
- - Architectural choices and why alternatives were rejected
46
- - Non-obvious constraints (what breaks if this changes)
47
- - Workarounds with context on the underlying issue
48
- - Patterns that prevent future mistakes
38
+ Technical requirements, preferences, or limitations discovered during planning
49
39
 
50
- **Format:** \`## Topic\` / \`**Decision:** ...\` / \`**Rationale:** ...\`
40
+ ## Verification
41
+ Binary-testable checks the implementation agent runs to confirm success. Reference relevant skills inline where they aid verification.
51
42
 
52
- **MEMORY.md** (cross-session learning for the AI agent):
53
- - Insight that would prevent a future mistake (e.g., "hook X silently drops field Y")
43
+ ## Decisions Worth Preserving
44
+ Decisions made during this session that would be lost without documentation. Focus on:
45
+ - What was chosen and why the alternatives were rejected
46
+ - Constraints that aren't obvious from the code itself
47
+ - Patterns discovered that prevent future mistakes
54
48
 
55
- **Include when:** Architectural decisions, non-obvious constraints, workarounds, or patterns discovered during implementation.
56
- **Omit entries for:** Routine changes with no decisions (rename, formatting, dependency bump).
57
- When in doubt, write it — a lean entry is better than a lost decision.
49
+ The implementation agent should document these so the next session inherits what this session learned.
58
50
  \`\`\`
59
51
 
60
52
  ### Self-Check
61
- - [ ] Could I execute this if I forgot our entire conversation?
62
- - [ ] Are file paths exact (not "the auth file")?
53
+ - [ ] Could I execute this plan having never seen this conversation?
54
+ - [ ] Are all file paths exact (not "the auth file")?
63
55
  - [ ] Are implementation details specific (not "use the approach we discussed")?
64
- - [ ] Do documentation entries capture decisions the next session would otherwise lose?`;
56
+ - [ ] Are relevant skills referenced where they add value?
57
+ - [ ] Are key decisions captured so they survive this session?`;
65
58
  }
@@ -108,13 +108,28 @@ export interface HookInput {
108
108
  transcript_path?: string;
109
109
  }
110
110
 
111
- // §1.7
111
+ // §1.7 — Three hook output patterns (see hook-utils.ts for emit functions)
112
112
  export interface HookOutput {
113
+ // Pattern 1: hookSpecificOutput (PreToolUse, PostToolUse, UserPromptSubmit, etc.)
113
114
  hookSpecificOutput?: {
114
115
  additionalContext?: string;
115
116
  hookEventName?: string;
116
- permissionDecision?: "allow" | "deny";
117
+ permissionDecision?: "allow" | "deny" | "ask";
117
118
  permissionDecisionReason?: string;
119
+ updatedInput?: Record<string, unknown>;
120
+ };
121
+ // Pattern 2: Top-level decision (UserPromptSubmit, Stop, SubagentStop)
122
+ decision?: "block";
123
+ reason?: string;
124
+ }
125
+
126
+ // §1.7b — PermissionRequest output (structurally different from HookOutput)
127
+ export interface PermissionRequestOutput {
128
+ decision: {
129
+ behavior: "allow" | "deny";
130
+ message?: string;
131
+ updatedInput?: Record<string, unknown>;
132
+ updatedPermissions?: Record<string, unknown>;
118
133
  };
119
134
  }
120
135
 
@@ -15,16 +15,16 @@
15
15
  import * as fs from "node:fs";
16
16
  import * as path from "node:path";
17
17
 
18
+ import { getProjectRoot } from "../lib-ts/base/constants.js";
19
+ import { getGitStatusShort } from "../lib-ts/base/git-state.js";
20
+ import { eprint } from "../lib-ts/base/utils.js";
21
+ import { findActiveContextId } from "../lib-ts/context/context-store.js";
18
22
  import {
19
23
  findLatestHandoff,
20
24
  readHandoffSections,
21
25
  getHandoffTimestamp,
22
26
  getHandoffPlanReference,
23
27
  } from "../lib-ts/handoff/handoff-reader.js";
24
- import { getProjectRoot } from "../lib-ts/base/constants.js";
25
- import { findActiveContextId } from "../lib-ts/context/context-store.js";
26
- import { getGitStatusShort } from "../lib-ts/base/git-state.js";
27
- import { eprint } from "../lib-ts/base/utils.js";
28
28
 
29
29
  // ---------------------------------------------------------------------------
30
30
  // Helpers
@@ -22,12 +22,12 @@
22
22
  import * as fs from "node:fs";
23
23
  import * as path from "node:path";
24
24
 
25
- import { getContext, saveState } from "../lib-ts/context/context-store.js";
26
- import { getHandoffFolderPath, getProjectRoot } from "../lib-ts/base/constants.js";
27
25
  import { atomicWrite } from "../lib-ts/base/atomic-write.js";
28
- import { logInfo, logWarn, logError } from "../lib-ts/base/logger.js";
26
+ import { getHandoffFolderPath, getProjectRoot } from "../lib-ts/base/constants.js";
29
27
  import { getGitStatusShort } from "../lib-ts/base/git-state.js";
28
+ import { logInfo, logWarn, logError } from "../lib-ts/base/logger.js";
30
29
  import { eprint } from "../lib-ts/base/utils.js";
30
+ import { getContext, saveState } from "../lib-ts/context/context-store.js";
31
31
 
32
32
  // ---------------------------------------------------------------------------
33
33
  // Parsing helpers
@@ -250,8 +250,8 @@ function main(): void {
250
250
  } else {
251
251
  logWarn("save_handoff", `Failed to copy plan: ${error}`);
252
252
  }
253
- } catch (e) {
254
- logWarn("save_handoff", `Failed to read plan: ${e}`);
253
+ } catch (error) {
254
+ logWarn("save_handoff", `Failed to read plan: ${error}`);
255
255
  }
256
256
  }
257
257
 
@@ -339,8 +339,8 @@ function main(): void {
339
339
  } else {
340
340
  logWarn("save_handoff", `Could not load context state for ${contextId}`);
341
341
  }
342
- } catch (e) {
343
- logWarn("save_handoff", `Handoff saved but auto-resume won't work (context update failed): ${e}`);
342
+ } catch (error) {
343
+ logWarn("save_handoff", `Handoff saved but auto-resume won't work (context update failed): ${error}`);
344
344
  }
345
345
 
346
346
  // Output success message