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,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
|
+
|