claude-all-hands 1.0.1 → 1.0.2

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 (160) hide show
  1. package/.claude/agents/code-simplifier.md +52 -0
  2. package/.claude/agents/curator.md +189 -245
  3. package/.claude/agents/documentor.md +147 -0
  4. package/.claude/agents/planner.md +123 -166
  5. package/.claude/agents/researcher.md +58 -41
  6. package/.claude/agents/surveyor.md +81 -0
  7. package/.claude/agents/worker.md +74 -0
  8. package/.claude/commands/audit-docs.md +94 -0
  9. package/.claude/commands/continue.md +120 -0
  10. package/.claude/commands/create-docs.md +100 -0
  11. package/.claude/commands/create-skill.md +107 -0
  12. package/.claude/commands/create-specialist.md +111 -0
  13. package/.claude/commands/curator-audit.md +4 -0
  14. package/.claude/commands/debug.md +183 -0
  15. package/.claude/commands/plan.md +199 -102
  16. package/.claude/commands/validate.md +11 -0
  17. package/.claude/commands/whats-next.md +106 -134
  18. package/.claude/envoy/envoy +11 -14
  19. package/.claude/envoy/package-lock.json +1388 -0
  20. package/.claude/envoy/package.json +29 -0
  21. package/.claude/envoy/src/cli.ts +126 -0
  22. package/.claude/envoy/src/commands/base.ts +216 -0
  23. package/.claude/envoy/src/commands/gemini.ts +999 -0
  24. package/.claude/envoy/src/commands/git.ts +639 -0
  25. package/.claude/envoy/src/commands/index.ts +73 -0
  26. package/.claude/envoy/src/commands/knowledge.ts +187 -0
  27. package/.claude/envoy/src/commands/perplexity.ts +129 -0
  28. package/.claude/envoy/src/commands/plan/core.ts +134 -0
  29. package/.claude/envoy/src/commands/plan/findings.ts +446 -0
  30. package/.claude/envoy/src/commands/plan/gates.ts +672 -0
  31. package/.claude/envoy/src/commands/plan/index.ts +135 -0
  32. package/.claude/envoy/src/commands/plan/lifecycle.ts +648 -0
  33. package/.claude/envoy/src/commands/plan/plan-file.ts +138 -0
  34. package/.claude/envoy/src/commands/plan/prompts.ts +285 -0
  35. package/.claude/envoy/src/commands/plan/protocols.ts +166 -0
  36. package/.claude/envoy/src/commands/repomix.ts +99 -0
  37. package/.claude/envoy/src/commands/tavily.ts +220 -0
  38. package/.claude/envoy/src/commands/xai.ts +168 -0
  39. package/.claude/envoy/src/lib/design.ts +41 -0
  40. package/.claude/envoy/src/lib/feedback-schemas.ts +154 -0
  41. package/.claude/envoy/src/lib/findings.ts +215 -0
  42. package/.claude/envoy/src/lib/gates.ts +572 -0
  43. package/.claude/envoy/src/lib/git.ts +132 -0
  44. package/.claude/envoy/src/lib/index.ts +188 -0
  45. package/.claude/envoy/src/lib/knowledge.ts +594 -0
  46. package/.claude/envoy/src/lib/markdown.ts +75 -0
  47. package/.claude/envoy/src/lib/observability.ts +262 -0
  48. package/.claude/envoy/src/lib/paths.ts +130 -0
  49. package/.claude/envoy/src/lib/plan-io.ts +117 -0
  50. package/.claude/envoy/src/lib/prompts.ts +231 -0
  51. package/.claude/envoy/src/lib/protocols.ts +314 -0
  52. package/.claude/envoy/src/lib/repomix.ts +133 -0
  53. package/.claude/envoy/src/lib/retry.ts +138 -0
  54. package/.claude/envoy/src/lib/watcher.ts +167 -0
  55. package/.claude/envoy/tsconfig.json +21 -0
  56. package/.claude/hooks/scripts/scan_agents.py +62 -0
  57. package/.claude/hooks/scripts/scan_commands.py +50 -0
  58. package/.claude/hooks/scripts/scan_skills.py +46 -70
  59. package/.claude/hooks/scripts/validate_artifacts.py +128 -0
  60. package/.claude/hooks/startup.sh +26 -24
  61. package/.claude/protocols/bug-discovery.yaml +55 -0
  62. package/.claude/protocols/debugging.yaml +51 -0
  63. package/.claude/protocols/discovery.yaml +53 -0
  64. package/.claude/protocols/implementation.yaml +84 -0
  65. package/.claude/settings.json +37 -97
  66. package/.claude/skills/brainstorming/SKILL.md +54 -0
  67. package/.claude/skills/commands-development/SKILL.md +630 -0
  68. package/.claude/skills/commands-development/references/arguments.md +252 -0
  69. package/.claude/skills/commands-development/references/patterns.md +796 -0
  70. package/.claude/skills/commands-development/references/tool-restrictions.md +376 -0
  71. package/.claude/skills/discovery-mode/SKILL.md +108 -0
  72. package/.claude/skills/hooks-development/SKILL.md +332 -0
  73. package/.claude/skills/hooks-development/references/command-vs-prompt.md +269 -0
  74. package/.claude/skills/hooks-development/references/examples.md +658 -0
  75. package/.claude/skills/hooks-development/references/hook-types.md +463 -0
  76. package/.claude/skills/hooks-development/references/input-output-schemas.md +469 -0
  77. package/.claude/skills/hooks-development/references/matchers.md +470 -0
  78. package/.claude/skills/hooks-development/references/troubleshooting.md +587 -0
  79. package/.claude/skills/implementation-mode/SKILL.md +171 -0
  80. package/.claude/skills/research-tools/SKILL.md +35 -33
  81. package/.claude/skills/skills-development/SKILL.md +192 -0
  82. package/.claude/skills/skills-development/references/api-security.md +226 -0
  83. package/.claude/skills/skills-development/references/be-clear-and-direct.md +531 -0
  84. package/.claude/skills/skills-development/references/common-patterns.md +595 -0
  85. package/.claude/skills/skills-development/references/core-principles.md +437 -0
  86. package/.claude/skills/skills-development/references/executable-code.md +175 -0
  87. package/.claude/skills/skills-development/references/iteration-and-testing.md +474 -0
  88. package/.claude/skills/skills-development/references/recommended-structure.md +168 -0
  89. package/.claude/skills/skills-development/references/skill-structure.md +372 -0
  90. package/.claude/skills/skills-development/references/use-xml-tags.md +466 -0
  91. package/.claude/skills/skills-development/references/using-scripts.md +113 -0
  92. package/.claude/skills/skills-development/references/using-templates.md +112 -0
  93. package/.claude/skills/skills-development/references/workflows-and-validation.md +510 -0
  94. package/.claude/skills/skills-development/templates/router-skill.md +73 -0
  95. package/.claude/skills/skills-development/templates/simple-skill.md +33 -0
  96. package/.claude/skills/skills-development/workflows/add-reference.md +96 -0
  97. package/.claude/skills/skills-development/workflows/add-script.md +93 -0
  98. package/.claude/skills/skills-development/workflows/add-template.md +74 -0
  99. package/.claude/skills/skills-development/workflows/add-workflow.md +120 -0
  100. package/.claude/skills/skills-development/workflows/audit-skill.md +138 -0
  101. package/.claude/skills/skills-development/workflows/create-domain-expertise-skill.md +605 -0
  102. package/.claude/skills/skills-development/workflows/create-new-skill.md +191 -0
  103. package/.claude/skills/skills-development/workflows/get-guidance.md +121 -0
  104. package/.claude/skills/skills-development/workflows/upgrade-to-router.md +161 -0
  105. package/.claude/skills/skills-development/workflows/verify-skill.md +204 -0
  106. package/.claude/skills/subagents-development/SKILL.md +325 -0
  107. package/.claude/skills/subagents-development/references/context-management.md +567 -0
  108. package/.claude/skills/subagents-development/references/debugging-agents.md +714 -0
  109. package/.claude/skills/subagents-development/references/error-handling-and-recovery.md +502 -0
  110. package/.claude/skills/subagents-development/references/evaluation-and-testing.md +374 -0
  111. package/.claude/skills/subagents-development/references/orchestration-patterns.md +591 -0
  112. package/.claude/skills/subagents-development/references/subagents.md +508 -0
  113. package/.claude/skills/subagents-development/references/writing-subagent-prompts.md +517 -0
  114. package/.claude/statusline.sh +24 -0
  115. package/bin/cli.js +110 -72
  116. package/package.json +1 -1
  117. package/.claude/agents/explorer.md +0 -62
  118. package/.claude/agents/parallel-worker.md +0 -121
  119. package/.claude/commands/curation-fix.md +0 -92
  120. package/.claude/commands/new-branch.md +0 -36
  121. package/.claude/commands/parallel-discovery.md +0 -69
  122. package/.claude/commands/parallel-orchestration.md +0 -99
  123. package/.claude/commands/plan-checkpoint.md +0 -37
  124. package/.claude/envoy/commands/__init__.py +0 -1
  125. package/.claude/envoy/commands/base.py +0 -95
  126. package/.claude/envoy/commands/parallel.py +0 -439
  127. package/.claude/envoy/commands/perplexity.py +0 -86
  128. package/.claude/envoy/commands/plans.py +0 -451
  129. package/.claude/envoy/commands/tavily.py +0 -156
  130. package/.claude/envoy/commands/vertex.py +0 -358
  131. package/.claude/envoy/commands/xai.py +0 -124
  132. package/.claude/envoy/envoy.py +0 -122
  133. package/.claude/envoy/pyrightconfig.json +0 -4
  134. package/.claude/envoy/requirements.txt +0 -2
  135. package/.claude/hooks/capture-queries.sh +0 -3
  136. package/.claude/hooks/scripts/enforce_planning.py +0 -118
  137. package/.claude/hooks/scripts/enforce_rg.py +0 -34
  138. package/.claude/hooks/scripts/validate_skill.py +0 -81
  139. package/.claude/skills/claude-envoy-curation/SKILL.md +0 -162
  140. package/.claude/skills/claude-envoy-usage/SKILL.md +0 -46
  141. package/.claude/skills/command-development/SKILL.md +0 -206
  142. package/.claude/skills/command-development/examples/simple-commands.md +0 -212
  143. package/.claude/skills/command-development/references/frontmatter-reference.md +0 -221
  144. package/.claude/skills/hook-development/SKILL.md +0 -127
  145. package/.claude/skills/hook-development/examples/command-hooks.md +0 -301
  146. package/.claude/skills/hook-development/examples/prompt-hooks.md +0 -114
  147. package/.claude/skills/hook-development/references/event-reference.md +0 -226
  148. package/.claude/skills/repomix-extraction/SKILL.md +0 -91
  149. package/.claude/skills/skill-development/SKILL.md +0 -168
  150. package/.claude/skills/skill-development/examples/complete-skill-examples.md +0 -281
  151. package/.claude/skills/skill-development/references/progressive-disclosure.md +0 -141
  152. package/.claude/skills/skill-development/references/writing-style.md +0 -180
  153. package/.claude/skills/skill-development/scripts/validate-skill.sh +0 -144
  154. package/.claude/skills/specialist-builder/SKILL.md +0 -327
  155. package/.claude/skills/specialist-builder/docs/agent-catalog.md +0 -28
  156. package/.claude/skills/specialist-builder/examples/complete-agent-examples.md +0 -206
  157. package/.claude/skills/specialist-builder/references/system-prompt-patterns.md +0 -281
  158. package/.claude/skills/specialist-builder/references/triggering-examples.md +0 -162
  159. package/.claude/skills/specialist-builder/scripts/validate-agent.sh +0 -137
  160. /package/.claude/{envoy/claude-envoy.py → skills/claude-envoy-patterns/SKILL.md} +0 -0
@@ -0,0 +1,572 @@
1
+ /**
2
+ * User feedback gate operations (blocking gates).
3
+ */
4
+
5
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from "fs";
6
+ import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
7
+ import { ensurePlanDir, getPlanPaths, getUserFeedbackPath, getPromptId } from "./paths.js";
8
+ import { readMarkdownFile, writeMarkdownWithFrontMatter, stripLogPlaceholder } from "./markdown.js";
9
+ import { logInfo } from "./observability.js";
10
+ import {
11
+ FindingsGateSchema,
12
+ PlanGateSchema,
13
+ TestingGateSchema,
14
+ VariantsGateSchema,
15
+ LoggingGateSchema,
16
+ AuditQuestionsSchema,
17
+ ReviewQuestionsSchema,
18
+ safeParseFeedback,
19
+ type FindingsGateFeedback,
20
+ type PlanGateFeedback,
21
+ type TestingGateFeedback,
22
+ type VariantsGateFeedback,
23
+ type LoggingGateFeedback,
24
+ type AuditQuestionsFeedback,
25
+ type ReviewQuestionsFeedback,
26
+ } from "./feedback-schemas.js";
27
+ import { readPlan, type PlanFrontMatter } from "./plan-io.js";
28
+ import { readPrompt, writePrompt, type PromptFrontMatter } from "./prompts.js";
29
+
30
+ // Re-export feedback types for convenience
31
+ export type {
32
+ FindingsGateFeedback,
33
+ PlanGateFeedback,
34
+ TestingGateFeedback,
35
+ VariantsGateFeedback,
36
+ LoggingGateFeedback,
37
+ AuditQuestionsFeedback,
38
+ ReviewQuestionsFeedback,
39
+ };
40
+
41
+ /**
42
+ * Write a findings gate feedback file.
43
+ */
44
+ export function writeFindingsGateFeedback(
45
+ approachFeedback: Record<string, { user_required_changes: string; rejected?: boolean; question_answers?: Array<{ question: string; answer: string }> }>
46
+ ): string {
47
+ const paths = getPlanPaths();
48
+ ensurePlanDir();
49
+
50
+ // Ensure user_feedback directory exists
51
+ if (!existsSync(paths.userFeedback)) {
52
+ mkdirSync(paths.userFeedback, { recursive: true });
53
+ }
54
+
55
+ const filePath = getUserFeedbackPath("findings_gate");
56
+ const content: FindingsGateFeedback = {
57
+ done: false,
58
+ thoughts: "",
59
+ approach_feedback: approachFeedback,
60
+ };
61
+
62
+ const yaml = stringifyYaml(content, { lineWidth: 0 });
63
+ writeFileSync(filePath, yaml, "utf-8");
64
+ logInfo("feedback.write", { type: "findings_gate", path: filePath });
65
+ return filePath;
66
+ }
67
+
68
+ /**
69
+ * Read and validate findings gate feedback file.
70
+ */
71
+ export function readFindingsGateFeedback(): { success: true; data: FindingsGateFeedback } | { success: false; error: string } {
72
+ const filePath = getUserFeedbackPath("findings_gate");
73
+ if (!existsSync(filePath)) {
74
+ return { success: false, error: "Feedback file not found" };
75
+ }
76
+ try {
77
+ const content = readFileSync(filePath, "utf-8");
78
+ const parsed = parseYaml(content);
79
+ return safeParseFeedback(FindingsGateSchema, parsed);
80
+ } catch (e) {
81
+ return { success: false, error: `Failed to parse YAML: ${e}` };
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Write a plan gate feedback file.
87
+ */
88
+ export function writePlanGateFeedback(
89
+ promptIds: string[]
90
+ ): string {
91
+ const paths = getPlanPaths();
92
+ ensurePlanDir();
93
+
94
+ if (!existsSync(paths.userFeedback)) {
95
+ mkdirSync(paths.userFeedback, { recursive: true });
96
+ }
97
+
98
+ const filePath = getUserFeedbackPath("plan_gate");
99
+ const promptFeedback: Record<string, { user_required_changes: string }> = {};
100
+ for (const id of promptIds) {
101
+ promptFeedback[id] = { user_required_changes: "" };
102
+ }
103
+
104
+ const content: PlanGateFeedback = {
105
+ done: false,
106
+ thoughts: "",
107
+ user_required_plan_changes: "",
108
+ prompt_feedback: promptFeedback,
109
+ };
110
+
111
+ const yaml = stringifyYaml(content, { lineWidth: 0 });
112
+ writeFileSync(filePath, yaml, "utf-8");
113
+ logInfo("feedback.write", { type: "plan_gate", path: filePath });
114
+ return filePath;
115
+ }
116
+
117
+ /**
118
+ * Read and validate plan gate feedback file.
119
+ */
120
+ export function readPlanGateFeedback(): { success: true; data: PlanGateFeedback } | { success: false; error: string } {
121
+ const filePath = getUserFeedbackPath("plan_gate");
122
+ if (!existsSync(filePath)) {
123
+ return { success: false, error: "Feedback file not found" };
124
+ }
125
+ try {
126
+ const content = readFileSync(filePath, "utf-8");
127
+ const parsed = parseYaml(content);
128
+ return safeParseFeedback(PlanGateSchema, parsed);
129
+ } catch (e) {
130
+ return { success: false, error: `Failed to parse YAML: ${e}` };
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Write a testing gate feedback file.
136
+ */
137
+ export function writeTestingGateFeedback(
138
+ promptNum: number,
139
+ variant: string | null
140
+ ): { yamlPath: string; logsPath: string } {
141
+ const paths = getPlanPaths();
142
+ ensurePlanDir();
143
+
144
+ if (!existsSync(paths.userFeedback)) {
145
+ mkdirSync(paths.userFeedback, { recursive: true });
146
+ }
147
+
148
+ const id = variant ? `${promptNum}_${variant}` : `${promptNum}`;
149
+ const yamlPath = getUserFeedbackPath(`${id}_testing`);
150
+ const logsPath = getUserFeedbackPath(`${id}_testing_logs`, ".md");
151
+
152
+ // YAML file (no logs field - logs go in sibling file)
153
+ const content = {
154
+ done: false,
155
+ thoughts: "",
156
+ test_passed: true, // Default true for minimal friction
157
+ user_required_changes: "",
158
+ };
159
+
160
+ const yaml = stringifyYaml(content, { lineWidth: 0 });
161
+ writeFileSync(yamlPath, yaml, "utf-8");
162
+
163
+ // Sibling log file
164
+ writeFileSync(logsPath, "<!-- Paste test logs here -->\n", "utf-8");
165
+
166
+ logInfo("feedback.write", { type: "testing_gate", yamlPath, logsPath, promptNum, variant });
167
+ return { yamlPath, logsPath };
168
+ }
169
+
170
+ /**
171
+ * Read and validate testing gate feedback file + sibling logs.
172
+ */
173
+ export function readTestingGateFeedback(
174
+ promptNum: number,
175
+ variant: string | null
176
+ ): { success: true; data: TestingGateFeedback } | { success: false; error: string } {
177
+ const id = variant ? `${promptNum}_${variant}` : `${promptNum}`;
178
+ const yamlPath = getUserFeedbackPath(`${id}_testing`);
179
+ const logsPath = getUserFeedbackPath(`${id}_testing_logs`, ".md");
180
+
181
+ if (!existsSync(yamlPath)) {
182
+ return { success: false, error: "Feedback file not found" };
183
+ }
184
+
185
+ try {
186
+ const content = readFileSync(yamlPath, "utf-8");
187
+ const parsed = parseYaml(content);
188
+ const result = safeParseFeedback(TestingGateSchema, parsed);
189
+
190
+ if (!result.success) {
191
+ return result;
192
+ }
193
+
194
+ // Read sibling log file, stripping placeholder if no real content
195
+ let logs = "";
196
+ if (existsSync(logsPath)) {
197
+ logs = stripLogPlaceholder(readFileSync(logsPath, "utf-8"));
198
+ }
199
+
200
+ return { success: true, data: { ...result.data, logs } };
201
+ } catch (e) {
202
+ return { success: false, error: `Failed to parse YAML: ${e}` };
203
+ }
204
+ }
205
+
206
+ /**
207
+ * Get testing gate log file path.
208
+ */
209
+ export function getTestingGateLogsPath(promptNum: number, variant: string | null): string {
210
+ const id = variant ? `${promptNum}_${variant}` : `${promptNum}`;
211
+ return getUserFeedbackPath(`${id}_testing_logs`, ".md");
212
+ }
213
+
214
+ /**
215
+ * Reset testing gate done flag to false (for retry after token limit exceeded).
216
+ */
217
+ export function resetTestingGateDone(promptNum: number, variant: string | null): void {
218
+ const id = variant ? `${promptNum}_${variant}` : `${promptNum}`;
219
+ const yamlPath = getUserFeedbackPath(`${id}_testing`);
220
+
221
+ if (!existsSync(yamlPath)) return;
222
+
223
+ const content = readFileSync(yamlPath, "utf-8");
224
+ const parsed = parseYaml(content) as Record<string, unknown>;
225
+ parsed.done = false;
226
+
227
+ const yaml = stringifyYaml(parsed, { lineWidth: 0 });
228
+ writeFileSync(yamlPath, yaml, "utf-8");
229
+ }
230
+
231
+ /**
232
+ * Write a variants gate feedback file.
233
+ * Only creates if doesn't exist (first variant to call creates it).
234
+ */
235
+ export function writeVariantsGateFeedback(
236
+ promptNum: number,
237
+ variantLetters: string[]
238
+ ): string {
239
+ const paths = getPlanPaths();
240
+ ensurePlanDir();
241
+
242
+ if (!existsSync(paths.userFeedback)) {
243
+ mkdirSync(paths.userFeedback, { recursive: true });
244
+ }
245
+
246
+ const filePath = getUserFeedbackPath(`${promptNum}_variants`);
247
+
248
+ // Only create if doesn't exist
249
+ if (existsSync(filePath)) {
250
+ logInfo("feedback.exists", { type: "variants_gate", path: filePath });
251
+ return filePath;
252
+ }
253
+
254
+ const variants: Record<string, { decision: null; reason: string }> = {};
255
+ for (const letter of variantLetters) {
256
+ variants[letter] = { decision: null, reason: "" };
257
+ }
258
+
259
+ const content: VariantsGateFeedback = {
260
+ done: false,
261
+ thoughts: "",
262
+ variants,
263
+ };
264
+
265
+ const yaml = stringifyYaml(content, { lineWidth: 0 });
266
+ writeFileSync(filePath, yaml, "utf-8");
267
+ logInfo("feedback.write", { type: "variants_gate", path: filePath, promptNum, variants: variantLetters });
268
+ return filePath;
269
+ }
270
+
271
+ /**
272
+ * Read and validate variants gate feedback file.
273
+ */
274
+ export function readVariantsGateFeedback(
275
+ promptNum: number
276
+ ): { success: true; data: VariantsGateFeedback } | { success: false; error: string } {
277
+ const filePath = getUserFeedbackPath(`${promptNum}_variants`);
278
+ if (!existsSync(filePath)) {
279
+ return { success: false, error: "Feedback file not found" };
280
+ }
281
+ try {
282
+ const content = readFileSync(filePath, "utf-8");
283
+ const parsed = parseYaml(content);
284
+ return safeParseFeedback(VariantsGateSchema, parsed);
285
+ } catch (e) {
286
+ return { success: false, error: `Failed to parse YAML: ${e}` };
287
+ }
288
+ }
289
+
290
+ /**
291
+ * Write a logging gate feedback file.
292
+ */
293
+ export function writeLoggingGateFeedback(
294
+ promptNum: number,
295
+ variant: string | null
296
+ ): { yamlPath: string; logsPath: string } {
297
+ const paths = getPlanPaths();
298
+ ensurePlanDir();
299
+
300
+ if (!existsSync(paths.userFeedback)) {
301
+ mkdirSync(paths.userFeedback, { recursive: true });
302
+ }
303
+
304
+ const id = variant ? `${promptNum}_${variant}` : `${promptNum}`;
305
+ const yamlPath = getUserFeedbackPath(`${id}_logging`);
306
+ const logsPath = getUserFeedbackPath(`${id}_logging_logs`, ".md");
307
+
308
+ // YAML file (no logs field - logs go in sibling file)
309
+ const content = {
310
+ done: false,
311
+ thoughts: "",
312
+ };
313
+
314
+ const yaml = stringifyYaml(content, { lineWidth: 0 });
315
+ writeFileSync(yamlPath, yaml, "utf-8");
316
+
317
+ // Sibling log file
318
+ writeFileSync(logsPath, "<!-- Paste debug logs here -->\n", "utf-8");
319
+
320
+ logInfo("feedback.write", { type: "logging_gate", yamlPath, logsPath, promptNum, variant });
321
+ return { yamlPath, logsPath };
322
+ }
323
+
324
+ /**
325
+ * Read and validate logging gate feedback file + sibling logs.
326
+ */
327
+ export function readLoggingGateFeedback(
328
+ promptNum: number,
329
+ variant: string | null
330
+ ): { success: true; data: LoggingGateFeedback } | { success: false; error: string } {
331
+ const id = variant ? `${promptNum}_${variant}` : `${promptNum}`;
332
+ const yamlPath = getUserFeedbackPath(`${id}_logging`);
333
+ const logsPath = getUserFeedbackPath(`${id}_logging_logs`, ".md");
334
+
335
+ if (!existsSync(yamlPath)) {
336
+ return { success: false, error: "Feedback file not found" };
337
+ }
338
+
339
+ try {
340
+ const content = readFileSync(yamlPath, "utf-8");
341
+ const parsed = parseYaml(content);
342
+ const result = safeParseFeedback(LoggingGateSchema, parsed);
343
+
344
+ if (!result.success) {
345
+ return result;
346
+ }
347
+
348
+ // Read sibling log file, stripping placeholder if no real content
349
+ let logs = "";
350
+ if (existsSync(logsPath)) {
351
+ logs = stripLogPlaceholder(readFileSync(logsPath, "utf-8"));
352
+ }
353
+
354
+ return { success: true, data: { ...result.data, logs } };
355
+ } catch (e) {
356
+ return { success: false, error: `Failed to parse YAML: ${e}` };
357
+ }
358
+ }
359
+
360
+ /**
361
+ * Get logging gate log file path.
362
+ */
363
+ export function getLoggingGateLogsPath(promptNum: number, variant: string | null): string {
364
+ const id = variant ? `${promptNum}_${variant}` : `${promptNum}`;
365
+ return getUserFeedbackPath(`${id}_logging_logs`, ".md");
366
+ }
367
+
368
+ /**
369
+ * Reset logging gate done flag to false (for retry after token limit exceeded).
370
+ */
371
+ export function resetLoggingGateDone(promptNum: number, variant: string | null): void {
372
+ const id = variant ? `${promptNum}_${variant}` : `${promptNum}`;
373
+ const yamlPath = getUserFeedbackPath(`${id}_logging`);
374
+
375
+ if (!existsSync(yamlPath)) return;
376
+
377
+ const content = readFileSync(yamlPath, "utf-8");
378
+ const parsed = parseYaml(content) as Record<string, unknown>;
379
+ parsed.done = false;
380
+
381
+ const yaml = stringifyYaml(parsed, { lineWidth: 0 });
382
+ writeFileSync(yamlPath, yaml, "utf-8");
383
+ }
384
+
385
+ /**
386
+ * Delete a feedback file and its sibling log file if exists.
387
+ */
388
+ export function deleteFeedbackFile(id: string): boolean {
389
+ const yamlPath = getUserFeedbackPath(id);
390
+ const logsPath = getUserFeedbackPath(`${id}_logs`, ".md");
391
+
392
+ let deleted = false;
393
+
394
+ if (existsSync(yamlPath)) {
395
+ unlinkSync(yamlPath);
396
+ logInfo("feedback.delete", { path: yamlPath });
397
+ deleted = true;
398
+ }
399
+
400
+ // Also delete sibling log file if exists
401
+ if (existsSync(logsPath)) {
402
+ unlinkSync(logsPath);
403
+ logInfo("feedback.delete", { path: logsPath });
404
+ }
405
+
406
+ return deleted;
407
+ }
408
+
409
+ /**
410
+ * Check if a feedback file exists.
411
+ */
412
+ export function feedbackFileExists(id: string): boolean {
413
+ return existsSync(getUserFeedbackPath(id));
414
+ }
415
+
416
+ // ============================================================================
417
+ // Gemini Audit/Review Feedback Operations
418
+ // ============================================================================
419
+
420
+ /**
421
+ * Write an audit questions feedback file.
422
+ * Created when Gemini audit needs clarifying questions answered.
423
+ */
424
+ export function writeAuditQuestionsFeedback(
425
+ questions: string[]
426
+ ): string {
427
+ const paths = getPlanPaths();
428
+ ensurePlanDir();
429
+
430
+ if (!existsSync(paths.userFeedback)) {
431
+ mkdirSync(paths.userFeedback, { recursive: true });
432
+ }
433
+
434
+ const filePath = getUserFeedbackPath("audit_questions");
435
+ const content: AuditQuestionsFeedback = {
436
+ done: false,
437
+ thoughts: "",
438
+ questions: questions.map((q) => ({ question: q, answer: "" })),
439
+ };
440
+
441
+ const yaml = stringifyYaml(content, { lineWidth: 0 });
442
+ writeFileSync(filePath, yaml, "utf-8");
443
+ logInfo("feedback.write", { type: "audit_questions", path: filePath, questionCount: questions.length });
444
+ return filePath;
445
+ }
446
+
447
+ /**
448
+ * Read and validate audit questions feedback file.
449
+ */
450
+ export function readAuditQuestionsFeedback(): { success: true; data: AuditQuestionsFeedback } | { success: false; error: string } {
451
+ const filePath = getUserFeedbackPath("audit_questions");
452
+ if (!existsSync(filePath)) {
453
+ return { success: false, error: "Feedback file not found" };
454
+ }
455
+ try {
456
+ const content = readFileSync(filePath, "utf-8");
457
+ const parsed = parseYaml(content);
458
+ return safeParseFeedback(AuditQuestionsSchema, parsed);
459
+ } catch (e) {
460
+ return { success: false, error: `Failed to parse YAML: ${e}` };
461
+ }
462
+ }
463
+
464
+ /**
465
+ * Write a review questions feedback file.
466
+ * Created when Gemini review needs clarifying questions answered.
467
+ *
468
+ * @param promptId - Prompt ID (e.g., "1", "2_A") or "full" for full plan review
469
+ * @param questions - Array of clarifying questions
470
+ */
471
+ export function writeReviewQuestionsFeedback(
472
+ promptId: string,
473
+ questions: string[]
474
+ ): string {
475
+ const paths = getPlanPaths();
476
+ ensurePlanDir();
477
+
478
+ if (!existsSync(paths.userFeedback)) {
479
+ mkdirSync(paths.userFeedback, { recursive: true });
480
+ }
481
+
482
+ const fileId = promptId === "full" ? "full_review_questions" : `${promptId}_review_questions`;
483
+ const filePath = getUserFeedbackPath(fileId);
484
+ const content: ReviewQuestionsFeedback = {
485
+ done: false,
486
+ thoughts: "",
487
+ questions: questions.map((q) => ({ question: q, answer: "" })),
488
+ suggested_changes: "",
489
+ };
490
+
491
+ const yaml = stringifyYaml(content, { lineWidth: 0 });
492
+ writeFileSync(filePath, yaml, "utf-8");
493
+ logInfo("feedback.write", { type: "review_questions", path: filePath, promptId, questionCount: questions.length });
494
+ return filePath;
495
+ }
496
+
497
+ /**
498
+ * Read and validate review questions feedback file.
499
+ *
500
+ * @param promptId - Prompt ID (e.g., "1", "2_A") or "full" for full plan review
501
+ */
502
+ export function readReviewQuestionsFeedback(
503
+ promptId: string
504
+ ): { success: true; data: ReviewQuestionsFeedback } | { success: false; error: string } {
505
+ const fileId = promptId === "full" ? "full_review_questions" : `${promptId}_review_questions`;
506
+ const filePath = getUserFeedbackPath(fileId);
507
+ if (!existsSync(filePath)) {
508
+ return { success: false, error: "Feedback file not found" };
509
+ }
510
+ try {
511
+ const content = readFileSync(filePath, "utf-8");
512
+ const parsed = parseYaml(content);
513
+ return safeParseFeedback(ReviewQuestionsSchema, parsed);
514
+ } catch (e) {
515
+ return { success: false, error: `Failed to parse YAML: ${e}` };
516
+ }
517
+ }
518
+
519
+ /**
520
+ * Append an audit entry to plan.md front matter.
521
+ */
522
+ export function appendPlanAudit(audit: PlanFrontMatter["audits"][0]): boolean {
523
+ const plan = readPlan();
524
+ if (!plan) return false;
525
+
526
+ const audits = plan.frontMatter.audits ?? [];
527
+ audits.push(audit);
528
+
529
+ const updatedFrontMatter = { ...plan.frontMatter, audits };
530
+ const paths = getPlanPaths();
531
+ writeMarkdownWithFrontMatter(paths.plan, updatedFrontMatter, plan.content);
532
+ logInfo("plan.append_audit", { decision: audit.decision, totalQuestions: audit.total_questions });
533
+ return true;
534
+ }
535
+
536
+ /**
537
+ * Append a review entry to plan.md front matter (for full reviews).
538
+ */
539
+ export function appendPlanReview(review: PlanFrontMatter["reviews"][0]): boolean {
540
+ const plan = readPlan();
541
+ if (!plan) return false;
542
+
543
+ const reviews = plan.frontMatter.reviews ?? [];
544
+ reviews.push(review);
545
+
546
+ const updatedFrontMatter = { ...plan.frontMatter, reviews };
547
+ const paths = getPlanPaths();
548
+ writeMarkdownWithFrontMatter(paths.plan, updatedFrontMatter, plan.content);
549
+ logInfo("plan.append_review", { decision: review.decision, totalQuestions: review.total_questions });
550
+ return true;
551
+ }
552
+
553
+ /**
554
+ * Append a review entry to a prompt file's front matter.
555
+ */
556
+ export function appendPromptReview(
557
+ number: number,
558
+ variant: string | null,
559
+ review: PromptFrontMatter["reviews"][0]
560
+ ): boolean {
561
+ const prompt = readPrompt(number, variant);
562
+ if (!prompt) return false;
563
+
564
+ const reviews = prompt.frontMatter.reviews ?? [];
565
+ reviews.push(review);
566
+
567
+ const updatedFrontMatter: PromptFrontMatter = { ...prompt.frontMatter, reviews };
568
+ writePrompt(number, variant, updatedFrontMatter, prompt.content);
569
+ const promptId = getPromptId(number, variant);
570
+ logInfo("prompt.append_review", { promptId, decision: review.decision, totalQuestions: review.total_questions });
571
+ return true;
572
+ }
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Git utilities for claude-envoy.
3
+ */
4
+
5
+ import { execSync, spawnSync } from "child_process";
6
+
7
+ // Protected branches - no planning required
8
+ const PROTECTED_BRANCHES = new Set([
9
+ "main",
10
+ "master",
11
+ "develop",
12
+ "development",
13
+ "dev",
14
+ "staging",
15
+ "stage",
16
+ "production",
17
+ "prod",
18
+ ]);
19
+
20
+ // Prefixes that indicate direct mode (no planning)
21
+ const DIRECT_MODE_PREFIXES = ["quick/", "curator/"];
22
+
23
+ /**
24
+ * Get current git branch name.
25
+ */
26
+ export function getBranch(): string {
27
+ try {
28
+ const result = spawnSync("git", ["branch", "--show-current"], {
29
+ encoding: "utf-8",
30
+ });
31
+ return result.status === 0 ? result.stdout.trim() : "";
32
+ } catch {
33
+ return "";
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Convert branch name to safe directory name (feat/auth -> feat-auth).
39
+ */
40
+ export function sanitizeBranch(branch: string): string {
41
+ return branch.replace(/[^a-zA-Z0-9_-]/g, "-");
42
+ }
43
+
44
+ /**
45
+ * Check if branch should skip planning.
46
+ */
47
+ export function isDirectModeBranch(branch: string): boolean {
48
+ if (PROTECTED_BRANCHES.has(branch)) {
49
+ return true;
50
+ }
51
+ return DIRECT_MODE_PREFIXES.some((prefix) => branch.startsWith(prefix));
52
+ }
53
+
54
+ /**
55
+ * Auto-detect base branch using merge-base.
56
+ * Respects BASE_BRANCH env variable if set.
57
+ */
58
+ export function getBaseBranch(): string {
59
+ // Check env variable first
60
+ const envBase = process.env.BASE_BRANCH;
61
+ if (envBase) {
62
+ return envBase;
63
+ }
64
+
65
+ const candidates = ["main", "master", "develop", "staging", "production"];
66
+
67
+ for (const base of candidates) {
68
+ try {
69
+ const result = spawnSync("git", ["merge-base", base, "HEAD"], {
70
+ encoding: "utf-8",
71
+ });
72
+ if (result.status === 0) {
73
+ return base;
74
+ }
75
+ } catch {
76
+ continue;
77
+ }
78
+ }
79
+
80
+ return "main";
81
+ }
82
+
83
+ /**
84
+ * Get git diff against a reference.
85
+ */
86
+ export function getDiff(ref: string): string {
87
+ try {
88
+ const result = spawnSync("git", ["diff", ref], {
89
+ encoding: "utf-8",
90
+ maxBuffer: 10 * 1024 * 1024, // 10MB
91
+ });
92
+
93
+ if (result.status !== 0) {
94
+ // Fallback to empty tree if ref doesn't exist (fresh repo)
95
+ const emptyTree = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
96
+ const fallback = spawnSync("git", ["diff", emptyTree], {
97
+ encoding: "utf-8",
98
+ maxBuffer: 10 * 1024 * 1024,
99
+ });
100
+ return fallback.stdout || "(No changes)";
101
+ }
102
+
103
+ return result.stdout || "(No changes)";
104
+ } catch {
105
+ return "(Unable to get diff)";
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Get the project root directory (where .git is located).
111
+ */
112
+ export function getProjectRoot(): string {
113
+ try {
114
+ const result = execSync("git rev-parse --show-toplevel", {
115
+ encoding: "utf-8",
116
+ });
117
+ return result.trim();
118
+ } catch {
119
+ return process.cwd();
120
+ }
121
+ }
122
+
123
+ /**
124
+ * Get the plan directory path for current branch.
125
+ */
126
+ export function getPlanDir(cwd?: string): string {
127
+ const root = cwd ?? getProjectRoot();
128
+ const branch = getBranch();
129
+ const planId = sanitizeBranch(branch);
130
+ return `${root}/.claude/plans/${planId}`;
131
+ }
132
+