aiwcli 0.10.3 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. package/bin/run.js +1 -1
  2. package/dist/commands/clear.js +28 -131
  3. package/dist/commands/init/index.js +3 -3
  4. package/dist/lib/gitignore-manager.d.ts +32 -0
  5. package/dist/lib/gitignore-manager.js +141 -2
  6. package/dist/templates/CLAUDE.md +8 -8
  7. package/dist/templates/_shared/.claude/commands/handoff-resume.md +64 -0
  8. package/dist/templates/_shared/.claude/commands/handoff.md +16 -10
  9. package/dist/templates/_shared/.claude/settings.json +7 -7
  10. package/dist/templates/_shared/hooks-ts/_utils/git-state.ts +2 -0
  11. package/dist/templates/_shared/hooks-ts/archive_plan.ts +159 -0
  12. package/dist/templates/_shared/hooks-ts/context_monitor.ts +147 -0
  13. package/dist/templates/_shared/hooks-ts/file-suggestion.ts +130 -0
  14. package/dist/templates/_shared/hooks-ts/pre_compact.ts +49 -0
  15. package/dist/templates/_shared/hooks-ts/session_end.ts +104 -0
  16. package/dist/templates/_shared/hooks-ts/session_start.ts +144 -0
  17. package/dist/templates/_shared/hooks-ts/task_create_capture.ts +48 -0
  18. package/dist/templates/_shared/hooks-ts/task_update_capture.ts +74 -0
  19. package/dist/templates/_shared/hooks-ts/user_prompt_submit.ts +83 -0
  20. package/dist/templates/_shared/lib-ts/CLAUDE.md +318 -0
  21. package/dist/templates/_shared/lib-ts/base/atomic-write.ts +12 -12
  22. package/dist/templates/_shared/lib-ts/base/constants.ts +22 -15
  23. package/dist/templates/_shared/lib-ts/base/hook-utils.ts +129 -50
  24. package/dist/templates/_shared/lib-ts/base/inference.ts +28 -21
  25. package/dist/templates/_shared/lib-ts/base/logger.ts +31 -15
  26. package/dist/templates/_shared/lib-ts/base/state-io.ts +9 -7
  27. package/dist/templates/_shared/lib-ts/base/stop-words.ts +131 -131
  28. package/dist/templates/_shared/lib-ts/base/subprocess-utils.ts +139 -0
  29. package/dist/templates/_shared/lib-ts/base/utils.ts +69 -69
  30. package/dist/templates/_shared/lib-ts/context/context-formatter.ts +30 -24
  31. package/dist/templates/_shared/lib-ts/context/context-selector.ts +50 -32
  32. package/dist/templates/_shared/lib-ts/context/context-store.ts +76 -48
  33. package/dist/templates/_shared/lib-ts/context/plan-manager.ts +61 -37
  34. package/dist/templates/_shared/lib-ts/context/task-tracker.ts +10 -6
  35. package/dist/templates/_shared/lib-ts/handoff/document-generator.ts +11 -10
  36. package/dist/templates/_shared/lib-ts/handoff/handoff-reader.ts +159 -0
  37. package/dist/templates/_shared/lib-ts/templates/formatters.ts +6 -4
  38. package/dist/templates/_shared/lib-ts/types.ts +68 -55
  39. package/dist/templates/_shared/scripts/resolve_context.ts +24 -0
  40. package/dist/templates/_shared/scripts/resume_handoff.ts +321 -0
  41. package/dist/templates/_shared/scripts/save_handoff.ts +21 -21
  42. package/dist/templates/_shared/scripts/status_line.ts +733 -0
  43. package/dist/templates/cc-native/.claude/settings.json +175 -185
  44. package/dist/templates/cc-native/TEMPLATE-SCHEMA.md +15 -17
  45. package/dist/templates/cc-native/_cc-native/agents/CLAUDE.md +0 -2
  46. package/dist/templates/cc-native/_cc-native/hooks/CLAUDE.md +109 -135
  47. package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.ts +119 -0
  48. package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.ts +921 -0
  49. package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.ts +61 -0
  50. package/dist/templates/cc-native/_cc-native/lib-ts/aggregate-agents.ts +157 -0
  51. package/dist/templates/cc-native/_cc-native/lib-ts/artifacts.ts +709 -0
  52. package/dist/templates/cc-native/_cc-native/lib-ts/cc-native-state.ts +199 -0
  53. package/dist/templates/cc-native/_cc-native/lib-ts/cli-output-parser.ts +124 -0
  54. package/dist/templates/cc-native/_cc-native/lib-ts/config.ts +57 -0
  55. package/dist/templates/cc-native/_cc-native/lib-ts/constants.ts +83 -0
  56. package/dist/templates/cc-native/_cc-native/lib-ts/debug.ts +80 -0
  57. package/dist/templates/cc-native/_cc-native/lib-ts/index.ts +119 -0
  58. package/dist/templates/cc-native/_cc-native/lib-ts/json-parser.ts +162 -0
  59. package/dist/templates/cc-native/_cc-native/lib-ts/nul +3 -0
  60. package/dist/templates/cc-native/_cc-native/lib-ts/orchestrator.ts +249 -0
  61. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/agent.ts +155 -0
  62. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/codex.ts +130 -0
  63. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/gemini.ts +106 -0
  64. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/index.ts +10 -0
  65. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/types.ts +23 -0
  66. package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +243 -0
  67. package/dist/templates/cc-native/_cc-native/lib-ts/tsconfig.json +18 -0
  68. package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +310 -0
  69. package/dist/templates/cc-native/_cc-native/lib-ts/verdict.ts +72 -0
  70. package/dist/templates/cc-native/_cc-native/plan-review.config.json +1 -9
  71. package/oclif.manifest.json +1 -1
  72. package/package.json +1 -1
  73. package/dist/templates/_shared/hooks/__init__.py +0 -16
  74. package/dist/templates/_shared/hooks/__pycache__/__init__.cpython-313.pyc +0 -0
  75. package/dist/templates/_shared/hooks/__pycache__/archive_plan.cpython-313.pyc +0 -0
  76. package/dist/templates/_shared/hooks/__pycache__/context_enforcer.cpython-313.pyc +0 -0
  77. package/dist/templates/_shared/hooks/__pycache__/context_monitor.cpython-313.pyc +0 -0
  78. package/dist/templates/_shared/hooks/__pycache__/file-suggestion.cpython-313.pyc +0 -0
  79. package/dist/templates/_shared/hooks/__pycache__/pre_compact.cpython-313.pyc +0 -0
  80. package/dist/templates/_shared/hooks/__pycache__/session_end.cpython-313.pyc +0 -0
  81. package/dist/templates/_shared/hooks/__pycache__/session_start.cpython-313.pyc +0 -0
  82. package/dist/templates/_shared/hooks/__pycache__/task_create_atomicity.cpython-313.pyc +0 -0
  83. package/dist/templates/_shared/hooks/__pycache__/task_create_capture.cpython-313.pyc +0 -0
  84. package/dist/templates/_shared/hooks/__pycache__/task_update_capture.cpython-313.pyc +0 -0
  85. package/dist/templates/_shared/hooks/__pycache__/user_prompt_submit.cpython-313.pyc +0 -0
  86. package/dist/templates/_shared/hooks/archive_plan.py +0 -177
  87. package/dist/templates/_shared/hooks/context_monitor.py +0 -270
  88. package/dist/templates/_shared/hooks/file-suggestion.py +0 -215
  89. package/dist/templates/_shared/hooks/pre_compact.py +0 -104
  90. package/dist/templates/_shared/hooks/session_end.py +0 -173
  91. package/dist/templates/_shared/hooks/session_start.py +0 -206
  92. package/dist/templates/_shared/hooks/task_create_capture.py +0 -108
  93. package/dist/templates/_shared/hooks/task_update_capture.py +0 -145
  94. package/dist/templates/_shared/hooks/user_prompt_submit.py +0 -139
  95. package/dist/templates/_shared/lib/__init__.py +0 -1
  96. package/dist/templates/_shared/lib/__pycache__/__init__.cpython-313.pyc +0 -0
  97. package/dist/templates/_shared/lib/base/__init__.py +0 -65
  98. package/dist/templates/_shared/lib/base/__pycache__/__init__.cpython-313.pyc +0 -0
  99. package/dist/templates/_shared/lib/base/__pycache__/atomic_write.cpython-313.pyc +0 -0
  100. package/dist/templates/_shared/lib/base/__pycache__/constants.cpython-313.pyc +0 -0
  101. package/dist/templates/_shared/lib/base/__pycache__/hook_utils.cpython-313.pyc +0 -0
  102. package/dist/templates/_shared/lib/base/__pycache__/inference.cpython-313.pyc +0 -0
  103. package/dist/templates/_shared/lib/base/__pycache__/logger.cpython-313.pyc +0 -0
  104. package/dist/templates/_shared/lib/base/__pycache__/stop_words.cpython-313.pyc +0 -0
  105. package/dist/templates/_shared/lib/base/__pycache__/subprocess_utils.cpython-313.pyc +0 -0
  106. package/dist/templates/_shared/lib/base/__pycache__/utils.cpython-313.pyc +0 -0
  107. package/dist/templates/_shared/lib/base/atomic_write.py +0 -180
  108. package/dist/templates/_shared/lib/base/constants.py +0 -358
  109. package/dist/templates/_shared/lib/base/hook_utils.py +0 -339
  110. package/dist/templates/_shared/lib/base/inference.py +0 -307
  111. package/dist/templates/_shared/lib/base/logger.py +0 -305
  112. package/dist/templates/_shared/lib/base/stop_words.py +0 -221
  113. package/dist/templates/_shared/lib/base/subprocess_utils.py +0 -46
  114. package/dist/templates/_shared/lib/base/utils.py +0 -263
  115. package/dist/templates/_shared/lib/context/__init__.py +0 -102
  116. package/dist/templates/_shared/lib/context/__pycache__/__init__.cpython-313.pyc +0 -0
  117. package/dist/templates/_shared/lib/context/__pycache__/cache.cpython-313.pyc +0 -0
  118. package/dist/templates/_shared/lib/context/__pycache__/context_extractor.cpython-313.pyc +0 -0
  119. package/dist/templates/_shared/lib/context/__pycache__/context_formatter.cpython-313.pyc +0 -0
  120. package/dist/templates/_shared/lib/context/__pycache__/context_manager.cpython-313.pyc +0 -0
  121. package/dist/templates/_shared/lib/context/__pycache__/context_selector.cpython-313.pyc +0 -0
  122. package/dist/templates/_shared/lib/context/__pycache__/context_store.cpython-313.pyc +0 -0
  123. package/dist/templates/_shared/lib/context/__pycache__/discovery.cpython-313.pyc +0 -0
  124. package/dist/templates/_shared/lib/context/__pycache__/event_log.cpython-313.pyc +0 -0
  125. package/dist/templates/_shared/lib/context/__pycache__/plan_archive.cpython-313.pyc +0 -0
  126. package/dist/templates/_shared/lib/context/__pycache__/plan_manager.cpython-313.pyc +0 -0
  127. package/dist/templates/_shared/lib/context/__pycache__/task_sync.cpython-313.pyc +0 -0
  128. package/dist/templates/_shared/lib/context/__pycache__/task_tracker.cpython-313.pyc +0 -0
  129. package/dist/templates/_shared/lib/context/context_formatter.py +0 -317
  130. package/dist/templates/_shared/lib/context/context_selector.py +0 -508
  131. package/dist/templates/_shared/lib/context/context_store.py +0 -653
  132. package/dist/templates/_shared/lib/context/plan_manager.py +0 -303
  133. package/dist/templates/_shared/lib/context/task_tracker.py +0 -188
  134. package/dist/templates/_shared/lib/handoff/__init__.py +0 -22
  135. package/dist/templates/_shared/lib/handoff/__pycache__/__init__.cpython-313.pyc +0 -0
  136. package/dist/templates/_shared/lib/handoff/__pycache__/document_generator.cpython-313.pyc +0 -0
  137. package/dist/templates/_shared/lib/handoff/document_generator.py +0 -278
  138. package/dist/templates/_shared/lib/templates/README.md +0 -206
  139. package/dist/templates/_shared/lib/templates/__init__.py +0 -36
  140. package/dist/templates/_shared/lib/templates/__pycache__/__init__.cpython-313.pyc +0 -0
  141. package/dist/templates/_shared/lib/templates/__pycache__/formatters.cpython-313.pyc +0 -0
  142. package/dist/templates/_shared/lib/templates/__pycache__/persona_questions.cpython-313.pyc +0 -0
  143. package/dist/templates/_shared/lib/templates/__pycache__/plan_context.cpython-313.pyc +0 -0
  144. package/dist/templates/_shared/lib/templates/formatters.py +0 -146
  145. package/dist/templates/_shared/lib/templates/plan_context.py +0 -73
  146. package/dist/templates/_shared/scripts/__pycache__/save_handoff.cpython-313.pyc +0 -0
  147. package/dist/templates/_shared/scripts/__pycache__/status_line.cpython-313.pyc +0 -0
  148. package/dist/templates/_shared/scripts/save_handoff.py +0 -357
  149. package/dist/templates/_shared/scripts/status_line.py +0 -716
  150. package/dist/templates/cc-native/.claude/commands/cc-native/fresh-perspective.md +0 -8
  151. package/dist/templates/cc-native/.windsurf/workflows/cc-native/fresh-perspective.md +0 -8
  152. package/dist/templates/cc-native/MIGRATION.md +0 -86
  153. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/add_plan_context.cpython-313.pyc +0 -0
  154. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/cc-native-plan-review.cpython-313.pyc +0 -0
  155. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/mark_questions_asked.cpython-313.pyc +0 -0
  156. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/plan_accepted.cpython-313.pyc +0 -0
  157. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/plan_questions_early.cpython-313.pyc +0 -0
  158. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/suggest-fresh-perspective.cpython-313.pyc +0 -0
  159. package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.py +0 -130
  160. package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.py +0 -954
  161. package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.py +0 -81
  162. package/dist/templates/cc-native/_cc-native/hooks/suggest-fresh-perspective.py +0 -340
  163. package/dist/templates/cc-native/_cc-native/lib/CLAUDE.md +0 -265
  164. package/dist/templates/cc-native/_cc-native/lib/__init__.py +0 -53
  165. package/dist/templates/cc-native/_cc-native/lib/__pycache__/__init__.cpython-313.pyc +0 -0
  166. package/dist/templates/cc-native/_cc-native/lib/__pycache__/atomic_write.cpython-313.pyc +0 -0
  167. package/dist/templates/cc-native/_cc-native/lib/__pycache__/constants.cpython-313.pyc +0 -0
  168. package/dist/templates/cc-native/_cc-native/lib/__pycache__/debug.cpython-313.pyc +0 -0
  169. package/dist/templates/cc-native/_cc-native/lib/__pycache__/orchestrator.cpython-313.pyc +0 -0
  170. package/dist/templates/cc-native/_cc-native/lib/__pycache__/state.cpython-313.pyc +0 -0
  171. package/dist/templates/cc-native/_cc-native/lib/__pycache__/utils.cpython-313.pyc +0 -0
  172. package/dist/templates/cc-native/_cc-native/lib/constants.py +0 -45
  173. package/dist/templates/cc-native/_cc-native/lib/debug.py +0 -139
  174. package/dist/templates/cc-native/_cc-native/lib/orchestrator.py +0 -362
  175. package/dist/templates/cc-native/_cc-native/lib/reviewers/__init__.py +0 -28
  176. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/__init__.cpython-313.pyc +0 -0
  177. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/agent.cpython-313.pyc +0 -0
  178. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/base.cpython-313.pyc +0 -0
  179. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/codex.cpython-313.pyc +0 -0
  180. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/gemini.cpython-313.pyc +0 -0
  181. package/dist/templates/cc-native/_cc-native/lib/reviewers/agent.py +0 -215
  182. package/dist/templates/cc-native/_cc-native/lib/reviewers/base.py +0 -88
  183. package/dist/templates/cc-native/_cc-native/lib/reviewers/codex.py +0 -124
  184. package/dist/templates/cc-native/_cc-native/lib/reviewers/gemini.py +0 -108
  185. package/dist/templates/cc-native/_cc-native/lib/state.py +0 -268
  186. package/dist/templates/cc-native/_cc-native/lib/utils.py +0 -1071
  187. package/dist/templates/cc-native/_cc-native/scripts/__pycache__/aggregate_agents.cpython-313.pyc +0 -0
  188. package/dist/templates/cc-native/_cc-native/scripts/aggregate_agents.py +0 -168
  189. package/dist/templates/cc-native/_cc-native/workflows/fresh-perspective.md +0 -134
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Gemini CLI plan reviewer.
3
+ * Runs Gemini CLI in YOLO mode (auto-approve).
4
+ * See cc-native-plan-review-spec.md §4.12
5
+ */
6
+
7
+ import { logDebug, logError, logInfo as _logInfo, logWarn } from "../../../_shared/lib-ts/base/logger.js";
8
+ import { execFileAsync, findExecutable } from "../../../_shared/lib-ts/base/subprocess-utils.js";
9
+ import { coerceToReview, parseJsonMaybe } from "../json-parser.js";
10
+ import type { ReviewerResult, ReviewOptions } from "../types.js";
11
+ import { makeResult } from "./types.js";
12
+ import type { Reviewer } from "./types.js";
13
+
14
+ /**
15
+ * Gemini reviewer — runs gemini -y -p <instruction>.
16
+ */
17
+ export class GeminiReviewer implements Reviewer {
18
+ private settings: Record<string, unknown>;
19
+
20
+ constructor(settings: Record<string, unknown>) {
21
+ this.settings = settings;
22
+ }
23
+
24
+ async review(
25
+ plan: string,
26
+ schema: Record<string, unknown>,
27
+ _options: ReviewOptions,
28
+ ): Promise<ReviewerResult> {
29
+ return runGeminiReview(plan, schema, this.settings);
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Run Gemini CLI to review the plan.
35
+ * Never throws — returns error ReviewerResult on failure.
36
+ */
37
+ export async function runGeminiReview(
38
+ plan: string,
39
+ schema: Record<string, unknown>,
40
+ settings: Record<string, unknown>,
41
+ ): Promise<ReviewerResult> {
42
+ const geminiSettings =
43
+ ((settings.reviewers as Record<string, unknown> | undefined)?.gemini as
44
+ | Record<string, unknown>
45
+ | undefined) ?? {};
46
+ const timeout = (geminiSettings.timeout as number) ?? 120;
47
+ const model = (geminiSettings.model as string) ?? "";
48
+
49
+ const geminiPath = findExecutable("gemini");
50
+ if (!geminiPath) {
51
+ logWarn("gemini", "CLI not found on PATH");
52
+ return makeResult("gemini", false, "skip", {}, "", "gemini CLI not found on PATH");
53
+ }
54
+
55
+ logDebug("gemini", `Found CLI at: ${geminiPath}`);
56
+
57
+ const instruction = `
58
+
59
+ Review the PLAN above as a senior staff software engineer. Focus on:
60
+ - missing steps, unclear assumptions, edge cases
61
+ - security/privacy concerns
62
+ - testing/rollout/rollback completeness
63
+ - operational concerns (observability, failure modes)
64
+
65
+ Return ONLY a JSON object that matches this JSON Schema (no markdown, no code fences):
66
+ ${JSON.stringify(schema)}
67
+ `;
68
+
69
+ const cmdArgs = ["-y", "-p", instruction];
70
+
71
+ if (model) {
72
+ cmdArgs.push("--model", model);
73
+ }
74
+
75
+ logDebug("gemini", "Running command: gemini -y -p <instruction>");
76
+
77
+ const result = await execFileAsync(geminiPath, cmdArgs, {
78
+ input: plan,
79
+ timeout: timeout * 1000,
80
+ maxBuffer: 10 * 1024 * 1024,
81
+ });
82
+
83
+ if (result.killed || result.signal === "SIGTERM") {
84
+ logWarn("gemini", `TIMEOUT after ${timeout}s`);
85
+ return makeResult("gemini", false, "error", {}, "", `gemini timed out after ${timeout}s`);
86
+ }
87
+
88
+ if (!result.stdout && !result.stderr && result.exitCode !== 0) {
89
+ logError("gemini", `Process exited with code ${result.exitCode}`);
90
+ return makeResult("gemini", false, "error", {}, "", `gemini failed to run (exit ${result.exitCode})`);
91
+ }
92
+
93
+ logDebug("gemini", `Exit code: ${result.exitCode}`);
94
+
95
+ const raw = result.stdout.trim();
96
+ const err = result.stderr.trim();
97
+
98
+ const obj = parseJsonMaybe(raw);
99
+ const [ok, verdict, norm] = coerceToReview(
100
+ obj,
101
+ "Retry or check CLI auth/config.",
102
+ );
103
+
104
+ return makeResult("gemini", ok, verdict, norm, raw, err);
105
+ }
106
+
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Reviewers package — re-exports all reviewer implementations.
3
+ * See cc-native-plan-review-spec.md §4.9
4
+ */
5
+
6
+ export { AgentReviewer, runAgentReview } from "./agent.js";
7
+ export { CodexReviewer, runCodexReview } from "./codex.js";
8
+ export { GeminiReviewer, runGeminiReview } from "./gemini.js";
9
+ export type { Reviewer, ReviewerResult, ReviewOptions } from "./types.js";
10
+ export { makeResult } from "./types.js";
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Reviewer interface and options for plan review implementations.
3
+ * See cc-native-plan-review-spec.md §4.9
4
+ */
5
+
6
+ import type { ReviewData, ReviewerResult, Verdict } from "../types.js";
7
+
8
+ // Re-export for convenience
9
+
10
+
11
+ /** Create a standard ReviewerResult. Shared by all reviewer implementations. */
12
+ export function makeResult(
13
+ name: string,
14
+ ok: boolean,
15
+ verdict: Verdict,
16
+ data: Record<string, unknown> | ReviewData,
17
+ raw: string,
18
+ err: string,
19
+ ): ReviewerResult {
20
+ return { name, ok, verdict, data: data as Record<string, unknown>, raw, err };
21
+ }
22
+
23
+ export {type Reviewer, type ReviewerResult, type ReviewOptions} from "../types.js";
@@ -0,0 +1,243 @@
1
+ /**
2
+ * Iteration state management for plan review cycles.
3
+ * State files are stored adjacent to plan files (e.g., foo.md → foo.state.json).
4
+ * See cc-native-plan-review-spec.md §4.7
5
+ */
6
+
7
+ import * as fs from "node:fs";
8
+ import * as path from "node:path";
9
+
10
+ import { validatePlanPath } from "./constants.js";
11
+ import type { IterationEntry, IterationState } from "./types.js";
12
+ import { atomicWrite } from "../../_shared/lib-ts/base/atomic-write.js";
13
+ import { logError, logInfo, logWarn } from "../../_shared/lib-ts/base/logger.js";
14
+ import { nowIso } from "../../_shared/lib-ts/base/utils.js";
15
+
16
+ // ---------------------------------------------------------------------------
17
+ // Constants
18
+ // ---------------------------------------------------------------------------
19
+
20
+ const STATE_SCHEMA_VERSION = "1.0.0";
21
+
22
+ const DEFAULT_REVIEW_ITERATIONS: Record<string, number> = {
23
+ simple: 1,
24
+ medium: 1,
25
+ high: 2,
26
+ };
27
+
28
+ // ---------------------------------------------------------------------------
29
+ // State File Management
30
+ // ---------------------------------------------------------------------------
31
+
32
+ /**
33
+ * Derive state file path from plan file path with security validation.
34
+ * Example: ~/.claude/plans/foo.md → ~/.claude/plans/foo.state.json
35
+ *
36
+ * @throws Error if planPath is invalid or insecure
37
+ */
38
+ export function getStateFilePath(planPath: string): string {
39
+ const validated = validatePlanPath(planPath);
40
+ const parsed = path.parse(validated);
41
+ return path.join(parsed.dir, `${parsed.name}.state.json`);
42
+ }
43
+
44
+ /**
45
+ * Load state file with schema validation and migration.
46
+ */
47
+ export function loadState(planPath: string): null | Record<string, unknown> {
48
+ try {
49
+ const stateFile = getStateFilePath(planPath);
50
+
51
+ if (!fs.existsSync(stateFile)) {
52
+ return null;
53
+ }
54
+
55
+ const state = JSON.parse(
56
+ fs.readFileSync(stateFile, "utf8"),
57
+ ) as Record<string, unknown>;
58
+
59
+ // Handle schema version (backward compatible)
60
+ const schemaVersion = state.schema_version as string | undefined;
61
+
62
+ if (schemaVersion === undefined) {
63
+ state.schema_version = STATE_SCHEMA_VERSION;
64
+ logInfo(
65
+ "state",
66
+ `Migrated state file to schema v${STATE_SCHEMA_VERSION}`,
67
+ );
68
+ } else if (schemaVersion !== STATE_SCHEMA_VERSION) {
69
+ logWarn(
70
+ "state",
71
+ `Schema mismatch (expected ${STATE_SCHEMA_VERSION}, got ${schemaVersion})`,
72
+ );
73
+ }
74
+
75
+ return state;
76
+ } catch (error: unknown) {
77
+ if (error instanceof Error && error.message.includes("Invalid plan path")) {
78
+ logError("state", `SECURITY: Invalid plan path: ${error}`);
79
+ } else {
80
+ logError("state", `Failed to load state: ${error}`);
81
+ }
82
+
83
+ return null;
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Save state file with schema version and validation.
89
+ * Returns true on success, false on failure.
90
+ */
91
+ export function saveStateToPlan(
92
+ planPath: string,
93
+ state: Record<string, unknown>,
94
+ ): boolean {
95
+ try {
96
+ const stateFile = getStateFilePath(planPath);
97
+
98
+ const stateWithVersion = {
99
+ schema_version: STATE_SCHEMA_VERSION,
100
+ ...state,
101
+ };
102
+
103
+ const [success, error] = atomicWrite(
104
+ stateFile,
105
+ JSON.stringify(stateWithVersion, null, 2),
106
+ );
107
+
108
+ if (!success) {
109
+ logError("state", `Failed to save state: ${error}`);
110
+ return false;
111
+ }
112
+
113
+ return true;
114
+ } catch (error: unknown) {
115
+ if (error instanceof Error && error.message.includes("Invalid plan path")) {
116
+ logError("state", `SECURITY: Invalid plan path: ${error}`);
117
+ } else {
118
+ logError("state", String(error));
119
+ }
120
+
121
+ return false;
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Delete state file after successful archive.
127
+ * Returns true if deleted or didn't exist, false on error.
128
+ */
129
+ export function deleteState(planPath: string): boolean {
130
+ try {
131
+ const stateFile = getStateFilePath(planPath);
132
+ if (fs.existsSync(stateFile)) {
133
+ fs.unlinkSync(stateFile);
134
+ logInfo("state", `Deleted state file: ${stateFile}`);
135
+ }
136
+
137
+ return true;
138
+ } catch (error: unknown) {
139
+ if (error instanceof Error && error.message.includes("Invalid plan path")) {
140
+ logError("state", `SECURITY: Invalid plan path in delete: ${error}`);
141
+ return false;
142
+ }
143
+
144
+ logWarn("state", `Failed to delete state file: ${error}`);
145
+ return false;
146
+ }
147
+ }
148
+
149
+ // ---------------------------------------------------------------------------
150
+ // Iteration State Management
151
+ // ---------------------------------------------------------------------------
152
+
153
+ /**
154
+ * Get or initialize iteration state based on complexity.
155
+ */
156
+ export function getIterationState(
157
+ state: Record<string, unknown>,
158
+ complexity: string,
159
+ config?: Record<string, unknown>,
160
+ ): IterationState {
161
+ if (state.iteration) {
162
+ return state.iteration as IterationState;
163
+ }
164
+
165
+ // Initialize new iteration state
166
+ const reviewIterations = { ...DEFAULT_REVIEW_ITERATIONS };
167
+ if (config) {
168
+ const overrides = config.reviewIterations as
169
+ | Record<string, number>
170
+ | undefined;
171
+ if (overrides) {
172
+ Object.assign(reviewIterations, overrides);
173
+ }
174
+ }
175
+
176
+ return {
177
+ current: 1,
178
+ max: reviewIterations[complexity] ?? 1,
179
+ complexity,
180
+ history: [],
181
+ };
182
+ }
183
+
184
+ /**
185
+ * Record review result in iteration history and update state.
186
+ */
187
+ export function updateIterationState(
188
+ state: Record<string, unknown>,
189
+ iteration: IterationState,
190
+ planHash: string,
191
+ verdict: string,
192
+ ): Record<string, unknown> {
193
+ const entry: IterationEntry = {
194
+ hash: planHash,
195
+ verdict,
196
+ timestamp: nowIso(),
197
+ };
198
+
199
+ iteration.history.push(entry);
200
+ state.iteration = iteration;
201
+ return state;
202
+ }
203
+
204
+ /**
205
+ * Determine if more review iterations are needed.
206
+ */
207
+ export function shouldContinueIterating(
208
+ iteration: IterationState,
209
+ verdict: string,
210
+ config?: Record<string, unknown>,
211
+ ): boolean {
212
+ const {current} = iteration;
213
+ const maxIter = iteration.max;
214
+
215
+ // At or past max iterations
216
+ if (current >= maxIter) {
217
+ logInfo(
218
+ "state",
219
+ `At max iterations (${current}/${maxIter}), no more iterations`,
220
+ );
221
+ return false;
222
+ }
223
+
224
+ // Check early exit on all pass
225
+ let earlyExit = true;
226
+ if (config) {
227
+ earlyExit = (config.earlyExitOnAllPass as boolean) ?? true;
228
+ }
229
+
230
+ if (earlyExit && verdict === "pass") {
231
+ logInfo(
232
+ "state",
233
+ "All reviewers passed and earlyExitOnAllPass=true, exiting early",
234
+ );
235
+ return false;
236
+ }
237
+
238
+ logInfo(
239
+ "state",
240
+ `Continuing to next iteration (${current + 1}/${maxIter}), verdict=${verdict}`,
241
+ );
242
+ return true;
243
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "strict": true,
7
+ "noUncheckedIndexedAccess": true,
8
+ "noFallthroughCasesInSwitch": true,
9
+ "outDir": "dist",
10
+ "rootDir": "../..",
11
+ "declaration": true,
12
+ "noEmit": true,
13
+ "types": ["bun-types"],
14
+ "skipLibCheck": true
15
+ },
16
+ "include": ["./**/*.ts", "../../_shared/lib-ts/**/*.ts"],
17
+ "exclude": ["../../_shared/lib-ts/__tests__/**"]
18
+ }