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.
- package/.claude/agents/code-simplifier.md +52 -0
- package/.claude/agents/curator.md +189 -245
- package/.claude/agents/documentor.md +147 -0
- package/.claude/agents/planner.md +123 -166
- package/.claude/agents/researcher.md +58 -41
- package/.claude/agents/surveyor.md +81 -0
- package/.claude/agents/worker.md +74 -0
- package/.claude/commands/audit-docs.md +94 -0
- package/.claude/commands/continue.md +120 -0
- package/.claude/commands/create-docs.md +100 -0
- package/.claude/commands/create-skill.md +107 -0
- package/.claude/commands/create-specialist.md +111 -0
- package/.claude/commands/curator-audit.md +4 -0
- package/.claude/commands/debug.md +183 -0
- package/.claude/commands/plan.md +199 -102
- package/.claude/commands/validate.md +11 -0
- package/.claude/commands/whats-next.md +106 -134
- package/.claude/envoy/envoy +11 -14
- package/.claude/envoy/package-lock.json +1388 -0
- package/.claude/envoy/package.json +29 -0
- package/.claude/envoy/src/cli.ts +126 -0
- package/.claude/envoy/src/commands/base.ts +216 -0
- package/.claude/envoy/src/commands/gemini.ts +999 -0
- package/.claude/envoy/src/commands/git.ts +639 -0
- package/.claude/envoy/src/commands/index.ts +73 -0
- package/.claude/envoy/src/commands/knowledge.ts +187 -0
- package/.claude/envoy/src/commands/perplexity.ts +129 -0
- package/.claude/envoy/src/commands/plan/core.ts +134 -0
- package/.claude/envoy/src/commands/plan/findings.ts +446 -0
- package/.claude/envoy/src/commands/plan/gates.ts +672 -0
- package/.claude/envoy/src/commands/plan/index.ts +135 -0
- package/.claude/envoy/src/commands/plan/lifecycle.ts +648 -0
- package/.claude/envoy/src/commands/plan/plan-file.ts +138 -0
- package/.claude/envoy/src/commands/plan/prompts.ts +285 -0
- package/.claude/envoy/src/commands/plan/protocols.ts +166 -0
- package/.claude/envoy/src/commands/repomix.ts +99 -0
- package/.claude/envoy/src/commands/tavily.ts +220 -0
- package/.claude/envoy/src/commands/xai.ts +168 -0
- package/.claude/envoy/src/lib/design.ts +41 -0
- package/.claude/envoy/src/lib/feedback-schemas.ts +154 -0
- package/.claude/envoy/src/lib/findings.ts +215 -0
- package/.claude/envoy/src/lib/gates.ts +572 -0
- package/.claude/envoy/src/lib/git.ts +132 -0
- package/.claude/envoy/src/lib/index.ts +188 -0
- package/.claude/envoy/src/lib/knowledge.ts +594 -0
- package/.claude/envoy/src/lib/markdown.ts +75 -0
- package/.claude/envoy/src/lib/observability.ts +262 -0
- package/.claude/envoy/src/lib/paths.ts +130 -0
- package/.claude/envoy/src/lib/plan-io.ts +117 -0
- package/.claude/envoy/src/lib/prompts.ts +231 -0
- package/.claude/envoy/src/lib/protocols.ts +314 -0
- package/.claude/envoy/src/lib/repomix.ts +133 -0
- package/.claude/envoy/src/lib/retry.ts +138 -0
- package/.claude/envoy/src/lib/watcher.ts +167 -0
- package/.claude/envoy/tsconfig.json +21 -0
- package/.claude/hooks/scripts/scan_agents.py +62 -0
- package/.claude/hooks/scripts/scan_commands.py +50 -0
- package/.claude/hooks/scripts/scan_skills.py +46 -70
- package/.claude/hooks/scripts/validate_artifacts.py +128 -0
- package/.claude/hooks/startup.sh +26 -24
- package/.claude/protocols/bug-discovery.yaml +55 -0
- package/.claude/protocols/debugging.yaml +51 -0
- package/.claude/protocols/discovery.yaml +53 -0
- package/.claude/protocols/implementation.yaml +84 -0
- package/.claude/settings.json +37 -97
- package/.claude/skills/brainstorming/SKILL.md +54 -0
- package/.claude/skills/commands-development/SKILL.md +630 -0
- package/.claude/skills/commands-development/references/arguments.md +252 -0
- package/.claude/skills/commands-development/references/patterns.md +796 -0
- package/.claude/skills/commands-development/references/tool-restrictions.md +376 -0
- package/.claude/skills/discovery-mode/SKILL.md +108 -0
- package/.claude/skills/hooks-development/SKILL.md +332 -0
- package/.claude/skills/hooks-development/references/command-vs-prompt.md +269 -0
- package/.claude/skills/hooks-development/references/examples.md +658 -0
- package/.claude/skills/hooks-development/references/hook-types.md +463 -0
- package/.claude/skills/hooks-development/references/input-output-schemas.md +469 -0
- package/.claude/skills/hooks-development/references/matchers.md +470 -0
- package/.claude/skills/hooks-development/references/troubleshooting.md +587 -0
- package/.claude/skills/implementation-mode/SKILL.md +171 -0
- package/.claude/skills/research-tools/SKILL.md +35 -33
- package/.claude/skills/skills-development/SKILL.md +192 -0
- package/.claude/skills/skills-development/references/api-security.md +226 -0
- package/.claude/skills/skills-development/references/be-clear-and-direct.md +531 -0
- package/.claude/skills/skills-development/references/common-patterns.md +595 -0
- package/.claude/skills/skills-development/references/core-principles.md +437 -0
- package/.claude/skills/skills-development/references/executable-code.md +175 -0
- package/.claude/skills/skills-development/references/iteration-and-testing.md +474 -0
- package/.claude/skills/skills-development/references/recommended-structure.md +168 -0
- package/.claude/skills/skills-development/references/skill-structure.md +372 -0
- package/.claude/skills/skills-development/references/use-xml-tags.md +466 -0
- package/.claude/skills/skills-development/references/using-scripts.md +113 -0
- package/.claude/skills/skills-development/references/using-templates.md +112 -0
- package/.claude/skills/skills-development/references/workflows-and-validation.md +510 -0
- package/.claude/skills/skills-development/templates/router-skill.md +73 -0
- package/.claude/skills/skills-development/templates/simple-skill.md +33 -0
- package/.claude/skills/skills-development/workflows/add-reference.md +96 -0
- package/.claude/skills/skills-development/workflows/add-script.md +93 -0
- package/.claude/skills/skills-development/workflows/add-template.md +74 -0
- package/.claude/skills/skills-development/workflows/add-workflow.md +120 -0
- package/.claude/skills/skills-development/workflows/audit-skill.md +138 -0
- package/.claude/skills/skills-development/workflows/create-domain-expertise-skill.md +605 -0
- package/.claude/skills/skills-development/workflows/create-new-skill.md +191 -0
- package/.claude/skills/skills-development/workflows/get-guidance.md +121 -0
- package/.claude/skills/skills-development/workflows/upgrade-to-router.md +161 -0
- package/.claude/skills/skills-development/workflows/verify-skill.md +204 -0
- package/.claude/skills/subagents-development/SKILL.md +325 -0
- package/.claude/skills/subagents-development/references/context-management.md +567 -0
- package/.claude/skills/subagents-development/references/debugging-agents.md +714 -0
- package/.claude/skills/subagents-development/references/error-handling-and-recovery.md +502 -0
- package/.claude/skills/subagents-development/references/evaluation-and-testing.md +374 -0
- package/.claude/skills/subagents-development/references/orchestration-patterns.md +591 -0
- package/.claude/skills/subagents-development/references/subagents.md +508 -0
- package/.claude/skills/subagents-development/references/writing-subagent-prompts.md +517 -0
- package/.claude/statusline.sh +24 -0
- package/bin/cli.js +110 -72
- package/package.json +1 -1
- package/.claude/agents/explorer.md +0 -62
- package/.claude/agents/parallel-worker.md +0 -121
- package/.claude/commands/curation-fix.md +0 -92
- package/.claude/commands/new-branch.md +0 -36
- package/.claude/commands/parallel-discovery.md +0 -69
- package/.claude/commands/parallel-orchestration.md +0 -99
- package/.claude/commands/plan-checkpoint.md +0 -37
- package/.claude/envoy/commands/__init__.py +0 -1
- package/.claude/envoy/commands/base.py +0 -95
- package/.claude/envoy/commands/parallel.py +0 -439
- package/.claude/envoy/commands/perplexity.py +0 -86
- package/.claude/envoy/commands/plans.py +0 -451
- package/.claude/envoy/commands/tavily.py +0 -156
- package/.claude/envoy/commands/vertex.py +0 -358
- package/.claude/envoy/commands/xai.py +0 -124
- package/.claude/envoy/envoy.py +0 -122
- package/.claude/envoy/pyrightconfig.json +0 -4
- package/.claude/envoy/requirements.txt +0 -2
- package/.claude/hooks/capture-queries.sh +0 -3
- package/.claude/hooks/scripts/enforce_planning.py +0 -118
- package/.claude/hooks/scripts/enforce_rg.py +0 -34
- package/.claude/hooks/scripts/validate_skill.py +0 -81
- package/.claude/skills/claude-envoy-curation/SKILL.md +0 -162
- package/.claude/skills/claude-envoy-usage/SKILL.md +0 -46
- package/.claude/skills/command-development/SKILL.md +0 -206
- package/.claude/skills/command-development/examples/simple-commands.md +0 -212
- package/.claude/skills/command-development/references/frontmatter-reference.md +0 -221
- package/.claude/skills/hook-development/SKILL.md +0 -127
- package/.claude/skills/hook-development/examples/command-hooks.md +0 -301
- package/.claude/skills/hook-development/examples/prompt-hooks.md +0 -114
- package/.claude/skills/hook-development/references/event-reference.md +0 -226
- package/.claude/skills/repomix-extraction/SKILL.md +0 -91
- package/.claude/skills/skill-development/SKILL.md +0 -168
- package/.claude/skills/skill-development/examples/complete-skill-examples.md +0 -281
- package/.claude/skills/skill-development/references/progressive-disclosure.md +0 -141
- package/.claude/skills/skill-development/references/writing-style.md +0 -180
- package/.claude/skills/skill-development/scripts/validate-skill.sh +0 -144
- package/.claude/skills/specialist-builder/SKILL.md +0 -327
- package/.claude/skills/specialist-builder/docs/agent-catalog.md +0 -28
- package/.claude/skills/specialist-builder/examples/complete-agent-examples.md +0 -206
- package/.claude/skills/specialist-builder/references/system-prompt-patterns.md +0 -281
- package/.claude/skills/specialist-builder/references/triggering-examples.md +0 -162
- package/.claude/skills/specialist-builder/scripts/validate-agent.sh +0 -137
- /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
|
+
}
|