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,672 @@
1
+ /**
2
+ * Blocking gate commands: block-findings-gate, block-plan-gate, block-prompt-testing-gate, etc.
3
+ */
4
+
5
+ import { Command } from "commander";
6
+ import type { FindingApproach, PromptFrontMatter } from "../../lib/index.js";
7
+ import {
8
+ appendUserInput as appendUserInputLib,
9
+ archiveFindings,
10
+ deleteApproach,
11
+ deleteFeedbackFile,
12
+ deletePrompt,
13
+ getApproachId,
14
+ getBranch,
15
+ getFileTokenCount,
16
+ getLoggingGateLogsPath,
17
+ getMaxLogTokens,
18
+ getPromptId,
19
+ getTestingGateLogsPath,
20
+ listPrompts,
21
+ planExists,
22
+ readAllFindings,
23
+ readAllPrompts,
24
+ readFindings,
25
+ readFindingsGateFeedback,
26
+ readLoggingGateFeedback,
27
+ readPlanGateFeedback,
28
+ readPrompt,
29
+ readTestingGateFeedback,
30
+ readVariantsGateFeedback,
31
+ resetLoggingGateDone,
32
+ resetTestingGateDone,
33
+ updateApproachFeedback,
34
+ updatePromptStatus,
35
+ updatePromptVariantSolution,
36
+ watchForDone,
37
+ writeFindingsGateFeedback,
38
+ writeFindings,
39
+ writeLoggingGateFeedback,
40
+ writePlanGateFeedback,
41
+ writeTestingGateFeedback,
42
+ writeVariantsGateFeedback,
43
+ } from "../../lib/index.js";
44
+ import { BaseCommand, CommandResult } from "../base.js";
45
+
46
+ /**
47
+ * Default timeout for blocking gates: 12 hours in milliseconds.
48
+ * Can be overridden via BLOCKING_GATE_TIMEOUT_MS environment variable.
49
+ */
50
+ const DEFAULT_BLOCKING_GATE_TIMEOUT_MS = 12 * 60 * 60 * 1000;
51
+
52
+ export function getBlockingGateTimeout(): number {
53
+ const envTimeout = process.env.BLOCKING_GATE_TIMEOUT_MS;
54
+ if (envTimeout) {
55
+ const parsed = parseInt(envTimeout, 10);
56
+ if (!isNaN(parsed) && parsed > 0) {
57
+ return parsed;
58
+ }
59
+ }
60
+ return DEFAULT_BLOCKING_GATE_TIMEOUT_MS;
61
+ }
62
+
63
+ /**
64
+ * Block for findings gate - user reviews specialist approaches before planning.
65
+ */
66
+ export class BlockFindingsGateCommand extends BaseCommand {
67
+ readonly name = "block-findings-gate";
68
+ readonly description = "Block until user reviews findings and approaches";
69
+
70
+ defineArguments(_cmd: Command): void {
71
+ // No arguments
72
+ }
73
+
74
+ async execute(_args: Record<string, unknown>): Promise<CommandResult> {
75
+ const branch = getBranch();
76
+ if (!branch) {
77
+ return this.error("no_branch", "Not in a git repository or no branch checked out");
78
+ }
79
+
80
+ if (!planExists()) {
81
+ return this.error("no_plan", "No plan directory exists for this branch");
82
+ }
83
+
84
+ // Gather all findings and approaches
85
+ const allFindings = readAllFindings();
86
+ if (allFindings.length === 0) {
87
+ return this.success({
88
+ skipped: true,
89
+ reason: "No findings to review",
90
+ thoughts: "",
91
+ affected_approaches: [],
92
+ });
93
+ }
94
+
95
+ // Build approach feedback structure with clarifying questions
96
+ const approachFeedback: Record<string, { user_required_changes: string; rejected?: boolean; question_answers?: Array<{ question: string; answer: string }> }> = {};
97
+
98
+ for (const findings of allFindings) {
99
+ for (const approach of findings.approaches) {
100
+ const approachId = getApproachId(approach.number, approach.variant);
101
+ const key = `${findings.specialist_name}_${approachId}`;
102
+ const entry: { user_required_changes: string; rejected?: boolean; question_answers?: Array<{ question: string; answer: string }> } = {
103
+ user_required_changes: "",
104
+ };
105
+
106
+ // Only add rejected field for variant approaches
107
+ if (approach.variant !== null) {
108
+ entry.rejected = false;
109
+ }
110
+
111
+ // Only add question_answers if there are questions
112
+ if (approach.required_clarifying_questions.length > 0) {
113
+ entry.question_answers = approach.required_clarifying_questions.map((q) => ({
114
+ question: q.question,
115
+ answer: "",
116
+ }));
117
+ }
118
+
119
+ approachFeedback[key] = entry;
120
+ }
121
+ }
122
+
123
+ // Create feedback file
124
+ const filePath = writeFindingsGateFeedback(approachFeedback);
125
+
126
+ // Block until done: true (with timeout)
127
+ const watchResult = await watchForDone(filePath, getBlockingGateTimeout());
128
+ const feedback = readFindingsGateFeedback();
129
+
130
+ if (!feedback.success) {
131
+ return this.error("invalid_feedback", feedback.error);
132
+ }
133
+
134
+ const data = feedback.data;
135
+
136
+ // Validate: at least one variant per approach number must NOT be rejected
137
+ const variantGroups: Record<string, { key: string; variant: string; rejected: boolean }[]> = {};
138
+ for (const [key, value] of Object.entries(data.approach_feedback || {})) {
139
+ const match = key.match(/^(.+)_(\d+)(?:_([A-Z]))?$/);
140
+ if (!match) continue;
141
+ const specialist = match[1];
142
+ const approachNum = match[2];
143
+ const variant = match[3];
144
+ if (variant) {
145
+ const groupKey = `${specialist}_${approachNum}`;
146
+ if (!variantGroups[groupKey]) variantGroups[groupKey] = [];
147
+ variantGroups[groupKey].push({ key, variant, rejected: !!value.rejected });
148
+ }
149
+ }
150
+ for (const [groupKey, variants] of Object.entries(variantGroups)) {
151
+ const allRejected = variants.every((v) => v.rejected);
152
+ if (allRejected) {
153
+ const variantList = variants.map((v) => v.variant).join(", ");
154
+ return this.error(
155
+ "all_variants_rejected",
156
+ `All variants (${variantList}) for ${groupKey} are rejected. At least one variant must be kept.`,
157
+ `Set rejected: false for at least one variant of ${groupKey}`
158
+ );
159
+ }
160
+ }
161
+
162
+ // Append thoughts to user_input.md if non-empty
163
+ if (data.thoughts && data.thoughts.trim()) {
164
+ appendUserInputLib(`[Findings Gate]\n${data.thoughts}`);
165
+ }
166
+
167
+ // Process approach feedback
168
+ const affectedApproaches: Array<{ specialist_name: string; approach_id: string }> = [];
169
+ const rejectedApproaches: Array<{ specialist_name: string; approach_id: string }> = [];
170
+
171
+ for (const [key, value] of Object.entries(data.approach_feedback || {})) {
172
+ const match = key.match(/^(.+)_(\d+)(?:_([A-Z]))?$/);
173
+ if (!match) continue;
174
+
175
+ const specialist = match[1];
176
+ const approachNum = parseInt(match[2], 10);
177
+ const variant = match[3] || null;
178
+ const approachId = getApproachId(approachNum, variant);
179
+
180
+ // Handle rejection
181
+ if (value.rejected) {
182
+ deleteApproach(specialist, approachNum, variant);
183
+ rejectedApproaches.push({ specialist_name: specialist, approach_id: approachId });
184
+ continue;
185
+ }
186
+
187
+ // Check if there's any meaningful feedback
188
+ const hasChanges = value.user_required_changes && value.user_required_changes.trim();
189
+ const hasAnswers = value.question_answers?.some((qa) => qa.answer && qa.answer.trim());
190
+
191
+ if (hasChanges || hasAnswers) {
192
+ const answeredQs = value.question_answers?.filter((qa) => qa.answer && qa.answer.trim()) ?? [];
193
+ updateApproachFeedback(specialist, approachNum, variant, {
194
+ userRequestedChanges: hasChanges ? value.user_required_changes : undefined,
195
+ questionAnswers: hasAnswers ? answeredQs : undefined,
196
+ });
197
+
198
+ // Append to user_input.md for audit trail
199
+ if (hasChanges) {
200
+ appendUserInputLib(`[User Required Changes for ${specialist} approach ${approachId}]\n${value.user_required_changes}`);
201
+ }
202
+ if (hasAnswers && answeredQs.length > 0) {
203
+ const formattedQs = answeredQs
204
+ .map((qa) => `Q: ${qa.question}\nA: ${qa.answer}`)
205
+ .join("\n\n");
206
+ appendUserInputLib(`[User Addressed Questions for ${specialist} approach ${approachId}]\n${formattedQs}`);
207
+ }
208
+
209
+ affectedApproaches.push({ specialist_name: specialist, approach_id: approachId });
210
+ }
211
+ }
212
+
213
+ // Post-process: If only one variant remains, strip the variant letter
214
+ const specialistsToCheck = new Set<string>();
215
+ for (const { specialist_name } of [...affectedApproaches, ...rejectedApproaches]) {
216
+ specialistsToCheck.add(specialist_name);
217
+ }
218
+ for (const specialist of specialistsToCheck) {
219
+ const findings = readFindings(specialist);
220
+ if (!findings) continue;
221
+
222
+ const byNumber: Record<number, FindingApproach[]> = {};
223
+ for (const approach of findings.approaches) {
224
+ if (!byNumber[approach.number]) byNumber[approach.number] = [];
225
+ byNumber[approach.number].push(approach);
226
+ }
227
+
228
+ let modified = false;
229
+ for (const [_numStr, approaches] of Object.entries(byNumber)) {
230
+ if (approaches.length === 1 && approaches[0].variant !== null) {
231
+ const oldId = getApproachId(approaches[0].number, approaches[0].variant);
232
+ approaches[0].variant = null;
233
+ modified = true;
234
+ const affectedIdx = affectedApproaches.findIndex(
235
+ (a) => a.specialist_name === specialist && a.approach_id === oldId
236
+ );
237
+ if (affectedIdx >= 0) {
238
+ affectedApproaches[affectedIdx].approach_id = getApproachId(approaches[0].number, null);
239
+ }
240
+ }
241
+ }
242
+
243
+ if (modified) {
244
+ writeFindings(specialist, findings);
245
+ }
246
+ }
247
+
248
+ // Delete the feedback file
249
+ deleteFeedbackFile("findings_gate");
250
+
251
+ return this.success({
252
+ thoughts: data.thoughts || "",
253
+ affected_approaches: affectedApproaches,
254
+ rejected_approaches: rejectedApproaches,
255
+ duration_ms: watchResult.duration_ms,
256
+ });
257
+ }
258
+ }
259
+
260
+ /**
261
+ * Block for plan gate - user reviews plan and prompts before implementation.
262
+ */
263
+ export class BlockPlanGateCommand extends BaseCommand {
264
+ readonly name = "block-plan-gate";
265
+ readonly description = "Block until user reviews plan and prompts";
266
+
267
+ defineArguments(_cmd: Command): void {
268
+ // No arguments
269
+ }
270
+
271
+ async execute(_args: Record<string, unknown>): Promise<CommandResult> {
272
+ const branch = getBranch();
273
+ if (!branch) {
274
+ return this.error("no_branch", "Not in a git repository or no branch checked out");
275
+ }
276
+
277
+ if (!planExists()) {
278
+ return this.error("no_plan", "No plan directory exists for this branch");
279
+ }
280
+
281
+ // Get all prompt IDs
282
+ const prompts = readAllPrompts();
283
+ if (prompts.length === 0) {
284
+ return this.error(
285
+ "no_prompts",
286
+ "No prompts exist to review. Create prompts with write-prompt first.",
287
+ "Run: envoy plan write-prompt <number> ..."
288
+ );
289
+ }
290
+ const promptIds = prompts.map((p) => getPromptId(p.number, p.variant));
291
+
292
+ // Create feedback file
293
+ const filePath = writePlanGateFeedback(promptIds);
294
+
295
+ // Block until done: true (with timeout)
296
+ const watchResult = await watchForDone(filePath, getBlockingGateTimeout());
297
+ const feedback = readPlanGateFeedback();
298
+
299
+ if (!feedback.success) {
300
+ return this.error("invalid_feedback", feedback.error);
301
+ }
302
+
303
+ const data = feedback.data;
304
+
305
+ // Append thoughts to user_input.md if non-empty
306
+ if (data.thoughts && data.thoughts.trim()) {
307
+ appendUserInputLib(`[Plan Gate]\n${data.thoughts}`);
308
+ }
309
+
310
+ // Append user_required_plan_changes to user_input.md if non-empty
311
+ if (data.user_required_plan_changes && data.user_required_plan_changes.trim()) {
312
+ appendUserInputLib(`[User Required Plan Changes]\n${data.user_required_plan_changes}`);
313
+ }
314
+
315
+ // Collect prompt changes
316
+ const promptChanges: Array<{ prompt_id: string; user_required_changes: string }> = [];
317
+ for (const [id, value] of Object.entries(data.prompt_feedback || {})) {
318
+ if (value.user_required_changes && value.user_required_changes.trim()) {
319
+ appendUserInputLib(`[User Required Changes for Prompt ${id}]\n${value.user_required_changes}`);
320
+ promptChanges.push({ prompt_id: id, user_required_changes: value.user_required_changes });
321
+ }
322
+ }
323
+
324
+ const hasChanges = !!(
325
+ (data.user_required_plan_changes && data.user_required_plan_changes.trim()) ||
326
+ promptChanges.length > 0
327
+ );
328
+
329
+ // Only archive findings when user approves with NO changes
330
+ let archivedFindings: string[] = [];
331
+ if (!hasChanges) {
332
+ const archiveResult = archiveFindings();
333
+ if (archiveResult.error) {
334
+ return this.error("archive_error", archiveResult.error);
335
+ }
336
+ archivedFindings = archiveResult.archived;
337
+ }
338
+
339
+ // Delete the feedback file
340
+ deleteFeedbackFile("plan_gate");
341
+
342
+ return this.success({
343
+ thoughts: data.thoughts || "",
344
+ has_user_required_changes: hasChanges,
345
+ user_required_plan_changes: data.user_required_plan_changes || "",
346
+ prompt_changes: promptChanges,
347
+ archived_findings: archivedFindings,
348
+ duration_ms: watchResult.duration_ms,
349
+ });
350
+ }
351
+ }
352
+
353
+ /**
354
+ * Block for prompt testing gate - user tests implementation manually.
355
+ */
356
+ export class BlockPromptTestingGateCommand extends BaseCommand {
357
+ readonly name = "block-prompt-testing-gate";
358
+ readonly description = "Block until user completes manual testing";
359
+
360
+ defineArguments(cmd: Command): void {
361
+ cmd.argument("<prompt_num>", "Prompt number (integer)");
362
+ cmd.argument("[variant]", "Optional variant letter (A, B, etc.)");
363
+ }
364
+
365
+ async execute(args: Record<string, unknown>): Promise<CommandResult> {
366
+ const branch = getBranch();
367
+ if (!branch) {
368
+ return this.error("no_branch", "Not in a git repository or no branch checked out");
369
+ }
370
+
371
+ const promptNum = parseInt(args.prompt_num as string, 10);
372
+ if (isNaN(promptNum) || promptNum < 1) {
373
+ return this.error("invalid_number", "Prompt number must be a positive integer");
374
+ }
375
+
376
+ const variant = args.variant as string | undefined;
377
+ if (variant && !/^[A-Z]$/.test(variant)) {
378
+ return this.error("invalid_variant", "Variant must be a single uppercase letter (A-Z)");
379
+ }
380
+
381
+ // Verify prompt exists
382
+ const prompt = readPrompt(promptNum, variant || null);
383
+ if (!prompt) {
384
+ const id = getPromptId(promptNum, variant || null);
385
+ return this.error("not_found", `Prompt ${id} not found`);
386
+ }
387
+
388
+ // Verify prompt is in implemented or reviewed status
389
+ const status = prompt.frontMatter.status;
390
+ if (status !== "implemented" && status !== "reviewed") {
391
+ const id = getPromptId(promptNum, variant || null);
392
+ return this.error(
393
+ "invalid_status",
394
+ `Prompt ${id} is in '${status}' status. Must be 'implemented' or 'reviewed' for testing.`,
395
+ "Complete implementation first with: envoy plan complete-prompt <number>"
396
+ );
397
+ }
398
+
399
+ // Create feedback files (YAML + sibling log file)
400
+ const { yamlPath, logsPath } = writeTestingGateFeedback(promptNum, variant || null);
401
+
402
+ // Block until done: true (with timeout)
403
+ const watchResult = await watchForDone(yamlPath, getBlockingGateTimeout());
404
+ const feedback = readTestingGateFeedback(promptNum, variant || null);
405
+
406
+ if (!feedback.success) {
407
+ return this.error("invalid_feedback", feedback.error);
408
+ }
409
+
410
+ const data = feedback.data;
411
+
412
+ // Check token count on log file
413
+ const tokenResult = getFileTokenCount(logsPath);
414
+ if (tokenResult.success) {
415
+ const maxTokens = getMaxLogTokens();
416
+ if (tokenResult.tokenCount > maxTokens) {
417
+ resetTestingGateDone(promptNum, variant || null);
418
+ return this.error(
419
+ "logs_too_large",
420
+ `Log file has ${tokenResult.tokenCount} tokens, max allowed is ${maxTokens}. Please reduce log size and set done: true again.`,
421
+ `Current: ${tokenResult.tokenCount} tokens, Max: ${maxTokens} tokens`
422
+ );
423
+ }
424
+ }
425
+
426
+ // Append thoughts to user_input.md if non-empty
427
+ if (data.thoughts && data.thoughts.trim()) {
428
+ const id = getPromptId(promptNum, variant || null);
429
+ appendUserInputLib(`[Testing Gate ${id}]\n${data.thoughts}`);
430
+ }
431
+
432
+ // Delete the feedback files
433
+ const feedbackId = variant ? `${promptNum}_${variant}_testing` : `${promptNum}_testing`;
434
+ deleteFeedbackFile(feedbackId);
435
+
436
+ if (data.test_passed === false) {
437
+ if (data.user_required_changes && data.user_required_changes.trim()) {
438
+ const id = getPromptId(promptNum, variant || null);
439
+ appendUserInputLib(`[User Required Changes for ${id}]\n${data.user_required_changes}`);
440
+ }
441
+
442
+ return this.success({
443
+ thoughts: data.thoughts || "",
444
+ passed: false,
445
+ user_required_changes: data.user_required_changes || "",
446
+ logs: data.logs || "",
447
+ duration_ms: watchResult.duration_ms,
448
+ });
449
+ }
450
+
451
+ // Test passed - update prompt status to tested
452
+ updatePromptStatus(promptNum, variant || null, "tested");
453
+
454
+ return this.success({
455
+ thoughts: data.thoughts || "",
456
+ passed: true,
457
+ logs: data.logs || "",
458
+ duration_ms: watchResult.duration_ms,
459
+ });
460
+ }
461
+ }
462
+
463
+ /**
464
+ * Block for variant selection gate - user chooses between variants after all tested.
465
+ */
466
+ export class BlockPromptVariantsGateCommand extends BaseCommand {
467
+ readonly name = "block-prompt-variants-gate";
468
+ readonly description = "Block until user selects variant";
469
+
470
+ defineArguments(cmd: Command): void {
471
+ cmd.argument("<prompt_num>", "Prompt number (integer)");
472
+ cmd.argument("<variant>", "Variant letter (A, B, etc.)");
473
+ }
474
+
475
+ async execute(args: Record<string, unknown>): Promise<CommandResult> {
476
+ const branch = getBranch();
477
+ if (!branch) {
478
+ return this.error("no_branch", "Not in a git repository or no branch checked out");
479
+ }
480
+
481
+ const promptNum = parseInt(args.prompt_num as string, 10);
482
+ if (isNaN(promptNum) || promptNum < 1) {
483
+ return this.error("invalid_number", "Prompt number must be a positive integer");
484
+ }
485
+
486
+ const variant = args.variant as string;
487
+ if (!variant || !/^[A-Z]$/.test(variant)) {
488
+ return this.error("invalid_variant", "Variant must be a single uppercase letter (A-Z)");
489
+ }
490
+
491
+ // Verify this is a variant prompt
492
+ const prompt = readPrompt(promptNum, variant);
493
+ if (!prompt) {
494
+ return this.error("not_found", `Prompt ${promptNum}_${variant} not found`);
495
+ }
496
+
497
+ if (!prompt.frontMatter.variant) {
498
+ return this.success({
499
+ skipped: true,
500
+ reason: "Not a variant prompt",
501
+ variant_solution: null,
502
+ reason_text: "",
503
+ });
504
+ }
505
+
506
+ // Find all variants for this prompt number
507
+ const allPrompts = listPrompts();
508
+ const variantLetters = allPrompts
509
+ .filter((p) => p.number === promptNum && p.variant)
510
+ .map((p) => p.variant as string)
511
+ .sort();
512
+
513
+ if (variantLetters.length < 2) {
514
+ return this.success({
515
+ skipped: true,
516
+ reason: "Only one variant exists",
517
+ variant_solution: null,
518
+ reason_text: "",
519
+ });
520
+ }
521
+
522
+ // Create feedback file (only creates if doesn't exist)
523
+ const filePath = writeVariantsGateFeedback(promptNum, variantLetters);
524
+
525
+ // Block until done: true (with timeout)
526
+ const watchResult = await watchForDone(filePath, getBlockingGateTimeout());
527
+ const feedback = readVariantsGateFeedback(promptNum);
528
+
529
+ if (!feedback.success) {
530
+ return this.error("invalid_feedback", feedback.error);
531
+ }
532
+
533
+ const data = feedback.data;
534
+
535
+ // Append thoughts to user_input.md if non-empty
536
+ if (data.thoughts && data.thoughts.trim()) {
537
+ appendUserInputLib(`[Variants Gate ${promptNum}]\n${data.thoughts}`);
538
+ }
539
+
540
+ // Process variant decisions
541
+ for (const [letter, decision] of Object.entries(data.variants)) {
542
+ if (decision.decision) {
543
+ let variantSolution: PromptFrontMatter["variant_solution"] = null;
544
+ if (decision.decision === "accepted") {
545
+ variantSolution = "accept";
546
+ } else if (decision.decision === "rejected") {
547
+ variantSolution = "discard";
548
+ } else if (decision.decision === "feature-flag") {
549
+ variantSolution = "feature-flag";
550
+ }
551
+
552
+ updatePromptVariantSolution(promptNum, letter, variantSolution);
553
+
554
+ // Delete rejected prompts
555
+ if (decision.decision === "rejected") {
556
+ deletePrompt(promptNum, letter);
557
+ }
558
+ }
559
+ }
560
+
561
+ // Delete the feedback file
562
+ deleteFeedbackFile(`${promptNum}_variants`);
563
+
564
+ // Return this variant's decision
565
+ const thisDecision = data.variants[variant];
566
+ let variantSolution: string | null = null;
567
+ if (thisDecision?.decision === "accepted") {
568
+ variantSolution = "accepted";
569
+ } else if (thisDecision?.decision === "rejected") {
570
+ variantSolution = "rejected";
571
+ } else if (thisDecision?.decision === "feature-flag") {
572
+ variantSolution = "feature-flag";
573
+ }
574
+
575
+ return this.success({
576
+ thoughts: data.thoughts || "",
577
+ variant_solution: variantSolution,
578
+ reason: thisDecision?.reason || "",
579
+ duration_ms: watchResult.duration_ms,
580
+ });
581
+ }
582
+ }
583
+
584
+ /**
585
+ * Block for debugging logging gate - user captures debug output.
586
+ */
587
+ export class BlockDebuggingLoggingGateCommand extends BaseCommand {
588
+ readonly name = "block-debugging-logging-gate";
589
+ readonly description = "Block until user captures debug logs";
590
+
591
+ defineArguments(cmd: Command): void {
592
+ cmd.argument("<prompt_num>", "Prompt number (integer)");
593
+ cmd.argument("[variant]", "Optional variant letter (A, B, etc.)");
594
+ }
595
+
596
+ async execute(args: Record<string, unknown>): Promise<CommandResult> {
597
+ const branch = getBranch();
598
+ if (!branch) {
599
+ return this.error("no_branch", "Not in a git repository or no branch checked out");
600
+ }
601
+
602
+ const promptNum = parseInt(args.prompt_num as string, 10);
603
+ if (isNaN(promptNum) || promptNum < 1) {
604
+ return this.error("invalid_number", "Prompt number must be a positive integer");
605
+ }
606
+
607
+ const variant = args.variant as string | undefined;
608
+ if (variant && !/^[A-Z]$/.test(variant)) {
609
+ return this.error("invalid_variant", "Variant must be a single uppercase letter (A-Z)");
610
+ }
611
+
612
+ // Verify prompt exists
613
+ const prompt = readPrompt(promptNum, variant || null);
614
+ if (!prompt) {
615
+ const id = getPromptId(promptNum, variant || null);
616
+ return this.error("not_found", `Prompt ${id} not found`);
617
+ }
618
+
619
+ // Verify prompt is debug kind
620
+ if (prompt.frontMatter.kind !== "debug") {
621
+ const id = getPromptId(promptNum, variant || null);
622
+ return this.error(
623
+ "not_debug",
624
+ `Prompt ${id} is '${prompt.frontMatter.kind}' kind. Logging gate only applies to 'debug' prompts.`,
625
+ "Use block-prompt-testing-gate for feature prompts"
626
+ );
627
+ }
628
+
629
+ // Create feedback files (YAML + sibling log file)
630
+ const { yamlPath, logsPath } = writeLoggingGateFeedback(promptNum, variant || null);
631
+
632
+ // Block until done: true (with timeout)
633
+ const watchResult = await watchForDone(yamlPath, getBlockingGateTimeout());
634
+ const feedback = readLoggingGateFeedback(promptNum, variant || null);
635
+
636
+ if (!feedback.success) {
637
+ return this.error("invalid_feedback", feedback.error);
638
+ }
639
+
640
+ const data = feedback.data;
641
+
642
+ // Check token count on log file
643
+ const tokenResult = getFileTokenCount(logsPath);
644
+ if (tokenResult.success) {
645
+ const maxTokens = getMaxLogTokens();
646
+ if (tokenResult.tokenCount > maxTokens) {
647
+ resetLoggingGateDone(promptNum, variant || null);
648
+ return this.error(
649
+ "logs_too_large",
650
+ `Log file has ${tokenResult.tokenCount} tokens, max allowed is ${maxTokens}. Please reduce log size and set done: true again.`,
651
+ `Current: ${tokenResult.tokenCount} tokens, Max: ${maxTokens} tokens`
652
+ );
653
+ }
654
+ }
655
+
656
+ // Append thoughts to user_input.md if non-empty
657
+ if (data.thoughts && data.thoughts.trim()) {
658
+ const id = getPromptId(promptNum, variant || null);
659
+ appendUserInputLib(`[Logging Gate ${id}]\n${data.thoughts}`);
660
+ }
661
+
662
+ // Delete the feedback files
663
+ const feedbackId = variant ? `${promptNum}_${variant}_logging` : `${promptNum}_logging`;
664
+ deleteFeedbackFile(feedbackId);
665
+
666
+ return this.success({
667
+ thoughts: data.thoughts || "",
668
+ logs: data.logs || "",
669
+ duration_ms: watchResult.duration_ms,
670
+ });
671
+ }
672
+ }