claude-all-hands 1.0.1 → 1.0.3

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 (170) hide show
  1. package/.claude/agents/code-simplifier.md +52 -0
  2. package/.claude/agents/curator.md +186 -246
  3. package/.claude/agents/documentation-taxonomist.md +255 -0
  4. package/.claude/agents/documentation-writer.md +366 -0
  5. package/.claude/agents/planner.md +123 -166
  6. package/.claude/agents/researcher.md +58 -41
  7. package/.claude/agents/surveyor.md +81 -0
  8. package/.claude/agents/worker.md +74 -0
  9. package/.claude/commands/continue.md +122 -0
  10. package/.claude/commands/create-skill.md +107 -0
  11. package/.claude/commands/create-specialist.md +111 -0
  12. package/.claude/commands/curator-audit.md +4 -0
  13. package/.claude/commands/debug.md +183 -0
  14. package/.claude/commands/docs-adjust.md +214 -0
  15. package/.claude/commands/docs-audit.md +172 -0
  16. package/.claude/commands/docs-init.md +210 -0
  17. package/.claude/commands/plan.md +199 -102
  18. package/.claude/commands/validate.md +11 -0
  19. package/.claude/commands/whats-next.md +106 -134
  20. package/.claude/envoy/README.md +5 -5
  21. package/.claude/envoy/envoy +11 -14
  22. package/.claude/envoy/package-lock.json +1594 -0
  23. package/.claude/envoy/package.json +38 -0
  24. package/.claude/envoy/src/cli.ts +126 -0
  25. package/.claude/envoy/src/commands/base.ts +216 -0
  26. package/.claude/envoy/src/commands/docs.ts +881 -0
  27. package/.claude/envoy/src/commands/gemini.ts +999 -0
  28. package/.claude/envoy/src/commands/git.ts +639 -0
  29. package/.claude/envoy/src/commands/index.ts +73 -0
  30. package/.claude/envoy/src/commands/knowledge.ts +178 -0
  31. package/.claude/envoy/src/commands/perplexity.ts +129 -0
  32. package/.claude/envoy/src/commands/plan/core.ts +134 -0
  33. package/.claude/envoy/src/commands/plan/findings.ts +446 -0
  34. package/.claude/envoy/src/commands/plan/gates.ts +672 -0
  35. package/.claude/envoy/src/commands/plan/index.ts +135 -0
  36. package/.claude/envoy/src/commands/plan/lifecycle.ts +648 -0
  37. package/.claude/envoy/src/commands/plan/plan-file.ts +138 -0
  38. package/.claude/envoy/src/commands/plan/prompts.ts +285 -0
  39. package/.claude/envoy/src/commands/plan/protocols.ts +166 -0
  40. package/.claude/envoy/src/commands/repomix.ts +99 -0
  41. package/.claude/envoy/src/commands/tavily.ts +220 -0
  42. package/.claude/envoy/src/commands/xai.ts +168 -0
  43. package/.claude/envoy/src/lib/ast-queries.ts +261 -0
  44. package/.claude/envoy/src/lib/design.ts +41 -0
  45. package/.claude/envoy/src/lib/feedback-schemas.ts +154 -0
  46. package/.claude/envoy/src/lib/findings.ts +215 -0
  47. package/.claude/envoy/src/lib/gates.ts +572 -0
  48. package/.claude/envoy/src/lib/git.ts +132 -0
  49. package/.claude/envoy/src/lib/index.ts +188 -0
  50. package/.claude/envoy/src/lib/knowledge.ts +646 -0
  51. package/.claude/envoy/src/lib/markdown.ts +75 -0
  52. package/.claude/envoy/src/lib/observability.ts +262 -0
  53. package/.claude/envoy/src/lib/paths.ts +130 -0
  54. package/.claude/envoy/src/lib/plan-io.ts +117 -0
  55. package/.claude/envoy/src/lib/prompts.ts +231 -0
  56. package/.claude/envoy/src/lib/protocols.ts +314 -0
  57. package/.claude/envoy/src/lib/repomix.ts +133 -0
  58. package/.claude/envoy/src/lib/retry.ts +138 -0
  59. package/.claude/envoy/src/lib/tree-sitter-utils.ts +301 -0
  60. package/.claude/envoy/src/lib/watcher.ts +167 -0
  61. package/.claude/envoy/src/types/tree-sitter.d.ts +76 -0
  62. package/.claude/envoy/tsconfig.json +21 -0
  63. package/.claude/hooks/scripts/enforce_research_fetch.py +1 -1
  64. package/.claude/hooks/scripts/scan_agents.py +62 -0
  65. package/.claude/hooks/scripts/scan_commands.py +50 -0
  66. package/.claude/hooks/scripts/scan_skills.py +46 -70
  67. package/.claude/hooks/scripts/validate_artifacts.py +128 -0
  68. package/.claude/hooks/startup.sh +26 -24
  69. package/.claude/protocols/bug-discovery.yaml +55 -0
  70. package/.claude/protocols/debugging.yaml +51 -0
  71. package/.claude/protocols/discovery.yaml +53 -0
  72. package/.claude/protocols/implementation.yaml +84 -0
  73. package/.claude/settings.json +38 -97
  74. package/.claude/skills/brainstorming/SKILL.md +54 -0
  75. package/.claude/skills/commands-development/SKILL.md +630 -0
  76. package/.claude/skills/commands-development/references/arguments.md +252 -0
  77. package/.claude/skills/commands-development/references/patterns.md +796 -0
  78. package/.claude/skills/commands-development/references/tool-restrictions.md +376 -0
  79. package/.claude/skills/discovery-mode/SKILL.md +108 -0
  80. package/.claude/skills/documentation-taxonomy/SKILL.md +287 -0
  81. package/.claude/skills/hooks-development/SKILL.md +332 -0
  82. package/.claude/skills/hooks-development/references/command-vs-prompt.md +269 -0
  83. package/.claude/skills/hooks-development/references/examples.md +658 -0
  84. package/.claude/skills/hooks-development/references/hook-types.md +463 -0
  85. package/.claude/skills/hooks-development/references/input-output-schemas.md +469 -0
  86. package/.claude/skills/hooks-development/references/matchers.md +470 -0
  87. package/.claude/skills/hooks-development/references/troubleshooting.md +587 -0
  88. package/.claude/skills/implementation-mode/SKILL.md +171 -0
  89. package/.claude/skills/knowledge-discovery/SKILL.md +178 -0
  90. package/.claude/skills/research-tools/SKILL.md +35 -33
  91. package/.claude/skills/skills-development/SKILL.md +192 -0
  92. package/.claude/skills/skills-development/references/api-security.md +226 -0
  93. package/.claude/skills/skills-development/references/be-clear-and-direct.md +531 -0
  94. package/.claude/skills/skills-development/references/common-patterns.md +595 -0
  95. package/.claude/skills/skills-development/references/core-principles.md +437 -0
  96. package/.claude/skills/skills-development/references/executable-code.md +175 -0
  97. package/.claude/skills/skills-development/references/iteration-and-testing.md +474 -0
  98. package/.claude/skills/skills-development/references/recommended-structure.md +168 -0
  99. package/.claude/skills/skills-development/references/skill-structure.md +372 -0
  100. package/.claude/skills/skills-development/references/use-xml-tags.md +466 -0
  101. package/.claude/skills/skills-development/references/using-scripts.md +113 -0
  102. package/.claude/skills/skills-development/references/using-templates.md +112 -0
  103. package/.claude/skills/skills-development/references/workflows-and-validation.md +510 -0
  104. package/.claude/skills/skills-development/templates/router-skill.md +73 -0
  105. package/.claude/skills/skills-development/templates/simple-skill.md +33 -0
  106. package/.claude/skills/skills-development/workflows/add-reference.md +96 -0
  107. package/.claude/skills/skills-development/workflows/add-script.md +93 -0
  108. package/.claude/skills/skills-development/workflows/add-template.md +74 -0
  109. package/.claude/skills/skills-development/workflows/add-workflow.md +120 -0
  110. package/.claude/skills/skills-development/workflows/audit-skill.md +138 -0
  111. package/.claude/skills/skills-development/workflows/create-domain-expertise-skill.md +605 -0
  112. package/.claude/skills/skills-development/workflows/create-new-skill.md +191 -0
  113. package/.claude/skills/skills-development/workflows/get-guidance.md +121 -0
  114. package/.claude/skills/skills-development/workflows/upgrade-to-router.md +161 -0
  115. package/.claude/skills/skills-development/workflows/verify-skill.md +204 -0
  116. package/.claude/skills/subagents-development/SKILL.md +325 -0
  117. package/.claude/skills/subagents-development/references/context-management.md +567 -0
  118. package/.claude/skills/subagents-development/references/debugging-agents.md +714 -0
  119. package/.claude/skills/subagents-development/references/error-handling-and-recovery.md +502 -0
  120. package/.claude/skills/subagents-development/references/evaluation-and-testing.md +374 -0
  121. package/.claude/skills/subagents-development/references/orchestration-patterns.md +591 -0
  122. package/.claude/skills/subagents-development/references/subagents.md +508 -0
  123. package/.claude/skills/subagents-development/references/writing-subagent-prompts.md +517 -0
  124. package/.claude/statusline.sh +24 -0
  125. package/bin/cli.js +150 -72
  126. package/package.json +1 -1
  127. package/.claude/agents/explorer.md +0 -62
  128. package/.claude/agents/parallel-worker.md +0 -121
  129. package/.claude/commands/curation-fix.md +0 -92
  130. package/.claude/commands/new-branch.md +0 -36
  131. package/.claude/commands/parallel-discovery.md +0 -69
  132. package/.claude/commands/parallel-orchestration.md +0 -99
  133. package/.claude/commands/plan-checkpoint.md +0 -37
  134. package/.claude/envoy/commands/__init__.py +0 -1
  135. package/.claude/envoy/commands/base.py +0 -95
  136. package/.claude/envoy/commands/parallel.py +0 -439
  137. package/.claude/envoy/commands/perplexity.py +0 -86
  138. package/.claude/envoy/commands/plans.py +0 -451
  139. package/.claude/envoy/commands/tavily.py +0 -156
  140. package/.claude/envoy/commands/vertex.py +0 -358
  141. package/.claude/envoy/commands/xai.py +0 -124
  142. package/.claude/envoy/envoy.py +0 -122
  143. package/.claude/envoy/pyrightconfig.json +0 -4
  144. package/.claude/envoy/requirements.txt +0 -2
  145. package/.claude/hooks/capture-queries.sh +0 -3
  146. package/.claude/hooks/scripts/enforce_planning.py +0 -118
  147. package/.claude/hooks/scripts/enforce_rg.py +0 -34
  148. package/.claude/hooks/scripts/validate_skill.py +0 -81
  149. package/.claude/skills/claude-envoy-curation/SKILL.md +0 -162
  150. package/.claude/skills/claude-envoy-usage/SKILL.md +0 -46
  151. package/.claude/skills/command-development/SKILL.md +0 -206
  152. package/.claude/skills/command-development/examples/simple-commands.md +0 -212
  153. package/.claude/skills/command-development/references/frontmatter-reference.md +0 -221
  154. package/.claude/skills/hook-development/SKILL.md +0 -127
  155. package/.claude/skills/hook-development/examples/command-hooks.md +0 -301
  156. package/.claude/skills/hook-development/examples/prompt-hooks.md +0 -114
  157. package/.claude/skills/hook-development/references/event-reference.md +0 -226
  158. package/.claude/skills/repomix-extraction/SKILL.md +0 -91
  159. package/.claude/skills/skill-development/SKILL.md +0 -168
  160. package/.claude/skills/skill-development/examples/complete-skill-examples.md +0 -281
  161. package/.claude/skills/skill-development/references/progressive-disclosure.md +0 -141
  162. package/.claude/skills/skill-development/references/writing-style.md +0 -180
  163. package/.claude/skills/skill-development/scripts/validate-skill.sh +0 -144
  164. package/.claude/skills/specialist-builder/SKILL.md +0 -327
  165. package/.claude/skills/specialist-builder/docs/agent-catalog.md +0 -28
  166. package/.claude/skills/specialist-builder/examples/complete-agent-examples.md +0 -206
  167. package/.claude/skills/specialist-builder/references/system-prompt-patterns.md +0 -281
  168. package/.claude/skills/specialist-builder/references/triggering-examples.md +0 -162
  169. package/.claude/skills/specialist-builder/scripts/validate-agent.sh +0 -137
  170. /package/.claude/{envoy/claude-envoy.py → skills/claude-envoy-patterns/SKILL.md} +0 -0
@@ -0,0 +1,262 @@
1
+ /**
2
+ * Observability system for claude-envoy.
3
+ * Dual system: metrics.jsonl (analytics) + envoy.log (detailed traces).
4
+ */
5
+
6
+ import { appendFileSync, mkdirSync, existsSync } from "fs";
7
+ import { join } from "path";
8
+ import { getProjectRoot, getBranch } from "./git.js";
9
+
10
+ /**
11
+ * Derive plan_name from branch.
12
+ * Worktree branches follow pattern: feature-foo/implementation-1-A
13
+ * Plan name is the parent branch before /implementation-*
14
+ */
15
+ export function getPlanName(branch?: string | null): string | undefined {
16
+ const b = branch ?? getBranch();
17
+ if (!b) return undefined;
18
+ // Strip /implementation-* suffix if present
19
+ const match = b.match(/^(.+?)\/implementation-/);
20
+ return match ? match[1] : b;
21
+ }
22
+
23
+ // --- Types ---
24
+
25
+ export type LogLevel = "info" | "warn" | "error";
26
+
27
+ export interface LogEntry {
28
+ timestamp: string;
29
+ level: LogLevel;
30
+ command: string;
31
+ plan_name?: string;
32
+ branch?: string;
33
+ caller?: string;
34
+ args?: Record<string, unknown>;
35
+ result?: "success" | "error";
36
+ duration_ms?: number;
37
+ context?: Record<string, unknown>;
38
+ }
39
+
40
+ export interface MetricEvent {
41
+ type: string;
42
+ timestamp: string;
43
+ plan_name?: string;
44
+ branch?: string;
45
+ [key: string]: unknown;
46
+ }
47
+
48
+ // --- File Paths ---
49
+
50
+ function getObservabilityDir(): string {
51
+ return join(getProjectRoot(), ".claude");
52
+ }
53
+
54
+ function getLogPath(): string {
55
+ return join(getObservabilityDir(), "envoy.log");
56
+ }
57
+
58
+ function getMetricsPath(): string {
59
+ return join(getObservabilityDir(), "metrics.jsonl");
60
+ }
61
+
62
+ // --- Ensure Directory Exists ---
63
+
64
+ function ensureObservabilityDir(): void {
65
+ const dir = getObservabilityDir();
66
+ if (!existsSync(dir)) {
67
+ mkdirSync(dir, { recursive: true });
68
+ }
69
+ }
70
+
71
+ // --- Logging ---
72
+
73
+ /**
74
+ * Write a log entry to envoy.log.
75
+ */
76
+ export function log(entry: Omit<LogEntry, "timestamp">): void {
77
+ ensureObservabilityDir();
78
+ const branch = getBranch() || undefined;
79
+ const fullEntry: LogEntry = {
80
+ timestamp: new Date().toISOString(),
81
+ branch,
82
+ plan_name: getPlanName(branch),
83
+ ...entry,
84
+ };
85
+ try {
86
+ appendFileSync(getLogPath(), JSON.stringify(fullEntry) + "\n");
87
+ } catch {
88
+ // Silent fail - observability should not break commands
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Log an info-level entry.
94
+ */
95
+ export function logInfo(
96
+ command: string,
97
+ context?: Record<string, unknown>,
98
+ args?: Record<string, unknown>
99
+ ): void {
100
+ log({ level: "info", command, context, args });
101
+ }
102
+
103
+ /**
104
+ * Log a warning-level entry.
105
+ */
106
+ export function logWarn(
107
+ command: string,
108
+ context?: Record<string, unknown>,
109
+ args?: Record<string, unknown>
110
+ ): void {
111
+ log({ level: "warn", command, context, args });
112
+ }
113
+
114
+ /**
115
+ * Log an error-level entry.
116
+ */
117
+ export function logError(
118
+ command: string,
119
+ context?: Record<string, unknown>,
120
+ args?: Record<string, unknown>
121
+ ): void {
122
+ log({ level: "error", command, context, args });
123
+ }
124
+
125
+ /**
126
+ * Log command start.
127
+ */
128
+ export function logCommandStart(
129
+ command: string,
130
+ args?: Record<string, unknown>
131
+ ): void {
132
+ log({
133
+ level: "info",
134
+ command,
135
+ args,
136
+ result: undefined,
137
+ context: { phase: "start" },
138
+ });
139
+ }
140
+
141
+ /**
142
+ * Log command completion.
143
+ */
144
+ export function logCommandComplete(
145
+ command: string,
146
+ result: "success" | "error",
147
+ duration_ms: number,
148
+ context?: Record<string, unknown>
149
+ ): void {
150
+ log({
151
+ level: result === "error" ? "error" : "info",
152
+ command,
153
+ result,
154
+ duration_ms,
155
+ context: { ...context, phase: "complete" },
156
+ });
157
+ }
158
+
159
+ // --- Metrics ---
160
+
161
+ /**
162
+ * Append a metric event to metrics.jsonl.
163
+ */
164
+ export function recordMetric(
165
+ event: { type: string } & Record<string, unknown>
166
+ ): void {
167
+ ensureObservabilityDir();
168
+ const branch = typeof event.branch === "string" ? event.branch : getBranch();
169
+ const planName = typeof event.plan_name === "string" ? event.plan_name : getPlanName(branch);
170
+ const fullEvent: MetricEvent = {
171
+ ...event,
172
+ timestamp: new Date().toISOString(),
173
+ branch: branch || undefined,
174
+ plan_name: planName,
175
+ };
176
+ try {
177
+ appendFileSync(getMetricsPath(), JSON.stringify(fullEvent) + "\n");
178
+ } catch {
179
+ // Silent fail - observability should not break commands
180
+ }
181
+ }
182
+
183
+ // --- Specific Metric Events ---
184
+
185
+ export function recordPlanCreated(data: {
186
+ mode: string;
187
+ prompt_count: number;
188
+ has_variants: boolean;
189
+ plan_name?: string;
190
+ }): void {
191
+ recordMetric({ type: "plan_created", ...data });
192
+ }
193
+
194
+ export function recordPlanCompleted(data: {
195
+ duration_ms: number;
196
+ prompt_count: number;
197
+ total_iterations: number;
198
+ gemini_calls: number;
199
+ plan_name?: string;
200
+ }): void {
201
+ recordMetric({ type: "plan_completed", ...data });
202
+ }
203
+
204
+ export function recordPromptStarted(data: {
205
+ prompt_num: number;
206
+ variant?: string | null;
207
+ specialist?: string;
208
+ is_debug: boolean;
209
+ plan_name?: string;
210
+ }): void {
211
+ recordMetric({ type: "prompt_started", ...data });
212
+ }
213
+
214
+ export function recordPromptCompleted(data: {
215
+ prompt_num: number;
216
+ variant?: string | null;
217
+ duration_ms: number;
218
+ iterations: number;
219
+ review_passes: number;
220
+ plan_name?: string;
221
+ }): void {
222
+ recordMetric({ type: "prompt_completed", ...data });
223
+ }
224
+
225
+ export function recordGateCompleted(data: {
226
+ gate_type: string;
227
+ duration_ms: number;
228
+ user_refinements_count: number;
229
+ plan_name?: string;
230
+ }): void {
231
+ recordMetric({ type: "gate_completed", ...data });
232
+ }
233
+
234
+ export function recordGeminiCall(data: {
235
+ endpoint: "audit" | "review" | "ask";
236
+ duration_ms: number;
237
+ success: boolean;
238
+ retries: number;
239
+ verdict?: string;
240
+ plan_name?: string;
241
+ }): void {
242
+ recordMetric({ type: "gemini_call", ...data });
243
+ }
244
+
245
+ export function recordDiscoveryCompleted(data: {
246
+ specialist: string;
247
+ approach_count: number;
248
+ variant_count: number;
249
+ question_count: number;
250
+ plan_name?: string;
251
+ }): void {
252
+ recordMetric({ type: "discovery_completed", ...data });
253
+ }
254
+
255
+ export function recordDocumentationExtracted(data: {
256
+ prompt_num: number;
257
+ variant?: string | null;
258
+ files_affected: number;
259
+ plan_name?: string;
260
+ }): void {
261
+ recordMetric({ type: "documentation_extracted", ...data });
262
+ }
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Plan directory path utilities.
3
+ */
4
+
5
+ import { mkdirSync, existsSync } from "fs";
6
+ import { join } from "path";
7
+ import { getPlanDir } from "./git.js";
8
+ import { logInfo } from "./observability.js";
9
+
10
+ /**
11
+ * Plan directory structure subdirectories.
12
+ */
13
+ const PLAN_SUBDIRS = [
14
+ "prompts",
15
+ "findings",
16
+ "findings/_archive",
17
+ "design",
18
+ "user_feedback",
19
+ ];
20
+
21
+ /**
22
+ * Ensure the plan directory exists with all required subdirectories.
23
+ * Creates on demand if not present.
24
+ *
25
+ * @returns The plan directory path
26
+ */
27
+ export function ensurePlanDir(): string {
28
+ const planDir = getPlanDir();
29
+
30
+ if (!existsSync(planDir)) {
31
+ logInfo("plans.create_dir", { path: planDir });
32
+ mkdirSync(planDir, { recursive: true });
33
+
34
+ // Create subdirectories
35
+ for (const subdir of PLAN_SUBDIRS) {
36
+ const subdirPath = join(planDir, subdir);
37
+ mkdirSync(subdirPath, { recursive: true });
38
+ }
39
+ }
40
+
41
+ return planDir;
42
+ }
43
+
44
+ /**
45
+ * Get paths for all standard plan files.
46
+ */
47
+ export function getPlanPaths(): {
48
+ planDir: string;
49
+ plan: string;
50
+ userInput: string;
51
+ curator: string;
52
+ summary: string;
53
+ prompts: string;
54
+ findings: string;
55
+ design: string;
56
+ userFeedback: string;
57
+ } {
58
+ const planDir = getPlanDir();
59
+ return {
60
+ planDir,
61
+ plan: join(planDir, "plan.md"),
62
+ userInput: join(planDir, "user_input.md"),
63
+ curator: join(planDir, "curator.md"),
64
+ summary: join(planDir, "summary.md"),
65
+ prompts: join(planDir, "prompts"),
66
+ findings: join(planDir, "findings"),
67
+ design: join(planDir, "design"),
68
+ userFeedback: join(planDir, "user_feedback"),
69
+ };
70
+ }
71
+
72
+ /**
73
+ * Get the prompt file path for a given prompt number and optional variant.
74
+ */
75
+ export function getPromptPath(number: number, variant?: string | null): string {
76
+ const paths = getPlanPaths();
77
+ const filename = variant ? `${number}_${variant}.md` : `${number}.md`;
78
+ return join(paths.prompts, filename);
79
+ }
80
+
81
+ /**
82
+ * Get the findings file path for a given specialist.
83
+ */
84
+ export function getFindingsPath(specialist: string): string {
85
+ const paths = getPlanPaths();
86
+ return join(paths.findings, `${specialist}.yaml`);
87
+ }
88
+
89
+ /**
90
+ * Get the user feedback file path (ephemeral, created by block commands).
91
+ */
92
+ export function getUserFeedbackPath(id: string, ext: string = ".yaml"): string {
93
+ const paths = getPlanPaths();
94
+ return join(paths.userFeedback, `${id}${ext}`);
95
+ }
96
+
97
+ /**
98
+ * Check if a plan directory exists for the current branch.
99
+ */
100
+ export function planExists(): boolean {
101
+ return existsSync(getPlanDir());
102
+ }
103
+
104
+ /**
105
+ * Get prompt identifier string (e.g., "1", "2_A", "3_B").
106
+ */
107
+ export function getPromptId(number: number, variant: string | null): string {
108
+ return variant ? `${number}_${variant}` : `${number}`;
109
+ }
110
+
111
+ /**
112
+ * Parse prompt identifier string into number and variant.
113
+ */
114
+ export function parsePromptId(id: string): { number: number; variant: string | null } {
115
+ const match = id.match(/^(\d+)(?:_([A-Z]))?$/);
116
+ if (!match) {
117
+ throw new Error(`Invalid prompt identifier: ${id}`);
118
+ }
119
+ return {
120
+ number: parseInt(match[1], 10),
121
+ variant: match[2] || null,
122
+ };
123
+ }
124
+
125
+ /**
126
+ * Get approach ID string: "{number}" or "{number}_{variant}"
127
+ */
128
+ export function getApproachId(number: number, variant: string | null): string {
129
+ return variant ? `${number}_${variant}` : `${number}`;
130
+ }
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Plan file I/O operations.
3
+ */
4
+
5
+ import { existsSync, readFileSync, writeFileSync, appendFileSync } from "fs";
6
+ import { getBranch } from "./git.js";
7
+ import { ensurePlanDir, getPlanPaths } from "./paths.js";
8
+ import { readMarkdownFile, writeMarkdownWithFrontMatter } from "./markdown.js";
9
+
10
+ export interface PlanFrontMatter {
11
+ stage: "draft" | "in_progress" | "completed";
12
+ branch_name: string;
13
+ audits: Array<{
14
+ review_context: string;
15
+ decision: "approved" | "needs_clarification" | "rejected";
16
+ total_questions: number;
17
+ were_changes_suggested: boolean;
18
+ }>;
19
+ reviews: Array<{
20
+ review_context: string;
21
+ decision: "approved" | "needs_changes" | "rejected";
22
+ total_questions: number;
23
+ were_changes_suggested: boolean;
24
+ }>;
25
+ }
26
+
27
+ /**
28
+ * Read plan.md and return parsed front matter + content.
29
+ */
30
+ export function readPlan(): { frontMatter: PlanFrontMatter; content: string } | null {
31
+ const paths = getPlanPaths();
32
+ const result = readMarkdownFile(paths.plan);
33
+ if (!result) return null;
34
+ return {
35
+ frontMatter: result.frontMatter as unknown as PlanFrontMatter,
36
+ content: result.content,
37
+ };
38
+ }
39
+
40
+ /**
41
+ * Write plan.md with front matter and content.
42
+ */
43
+ export function writePlan(frontMatter: Partial<PlanFrontMatter>, content: string): void {
44
+ const paths = getPlanPaths();
45
+ ensurePlanDir();
46
+
47
+ const defaults: PlanFrontMatter = {
48
+ stage: "draft",
49
+ branch_name: getBranch(),
50
+ audits: [],
51
+ reviews: [],
52
+ };
53
+
54
+ const merged = { ...defaults, ...frontMatter };
55
+ writeMarkdownWithFrontMatter(paths.plan, merged, content);
56
+ }
57
+
58
+ /**
59
+ * Update plan stage without modifying content.
60
+ */
61
+ export function updatePlanStage(stage: PlanFrontMatter["stage"]): void {
62
+ const plan = readPlan();
63
+ if (!plan) return;
64
+
65
+ const updatedFrontMatter = { ...plan.frontMatter, stage };
66
+ const paths = getPlanPaths();
67
+ writeMarkdownWithFrontMatter(paths.plan, updatedFrontMatter, plan.content);
68
+ }
69
+
70
+ /**
71
+ * Read user_input.md content.
72
+ */
73
+ export function readUserInput(): string | null {
74
+ const paths = getPlanPaths();
75
+ if (!existsSync(paths.userInput)) {
76
+ return null;
77
+ }
78
+ return readFileSync(paths.userInput, "utf-8");
79
+ }
80
+
81
+ /**
82
+ * Append content to user_input.md.
83
+ */
84
+ export function appendUserInput(content: string): void {
85
+ const paths = getPlanPaths();
86
+ ensurePlanDir();
87
+
88
+ // Initialize file if it doesn't exist
89
+ if (!existsSync(paths.userInput)) {
90
+ writeFileSync(paths.userInput, "# User Input Log\n\n", "utf-8");
91
+ }
92
+
93
+ // Append with timestamp
94
+ const timestamp = new Date().toISOString();
95
+ const entry = `\n---\n**${timestamp}**\n\n${content}\n`;
96
+ appendFileSync(paths.userInput, entry, "utf-8");
97
+ }
98
+
99
+ /**
100
+ * Read summary.md content.
101
+ */
102
+ export function readSummary(): string | null {
103
+ const paths = getPlanPaths();
104
+ if (!existsSync(paths.summary)) {
105
+ return null;
106
+ }
107
+ return readFileSync(paths.summary, "utf-8");
108
+ }
109
+
110
+ /**
111
+ * Write summary.md content.
112
+ */
113
+ export function writeSummary(content: string): void {
114
+ const paths = getPlanPaths();
115
+ ensurePlanDir();
116
+ writeFileSync(paths.summary, content, "utf-8");
117
+ }