claude-all-hands 1.0.1 ā 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/agents/code-simplifier.md +52 -0
- package/.claude/agents/curator.md +186 -246
- package/.claude/agents/documentation-taxonomist.md +255 -0
- package/.claude/agents/documentation-writer.md +366 -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/continue.md +122 -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/docs-adjust.md +214 -0
- package/.claude/commands/docs-audit.md +172 -0
- package/.claude/commands/docs-init.md +210 -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/README.md +5 -5
- package/.claude/envoy/envoy +11 -14
- package/.claude/envoy/package-lock.json +1594 -0
- package/.claude/envoy/package.json +38 -0
- package/.claude/envoy/src/cli.ts +126 -0
- package/.claude/envoy/src/commands/base.ts +216 -0
- package/.claude/envoy/src/commands/docs.ts +881 -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 +178 -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/ast-queries.ts +261 -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 +646 -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/tree-sitter-utils.ts +301 -0
- package/.claude/envoy/src/lib/watcher.ts +167 -0
- package/.claude/envoy/src/types/tree-sitter.d.ts +76 -0
- package/.claude/envoy/tsconfig.json +21 -0
- package/.claude/hooks/scripts/enforce_research_fetch.py +1 -1
- 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 +38 -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/documentation-taxonomy/SKILL.md +287 -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/knowledge-discovery/SKILL.md +178 -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 +150 -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,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prompt file operations.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { existsSync, mkdirSync, readdirSync, unlinkSync } from "fs";
|
|
6
|
+
import { join } from "path";
|
|
7
|
+
import { ensurePlanDir, getPlanPaths, getPromptPath } from "./paths.js";
|
|
8
|
+
import { readMarkdownFile, writeMarkdownWithFrontMatter } from "./markdown.js";
|
|
9
|
+
|
|
10
|
+
export interface PromptFrontMatter {
|
|
11
|
+
number: number;
|
|
12
|
+
variant: string | null;
|
|
13
|
+
description: string;
|
|
14
|
+
kind: "debug" | "feature";
|
|
15
|
+
relevant_files: string[];
|
|
16
|
+
success_criteria: string;
|
|
17
|
+
depends_on: number[];
|
|
18
|
+
requires_manual_testing: boolean;
|
|
19
|
+
in_progress: boolean;
|
|
20
|
+
current_iteration: number;
|
|
21
|
+
reviews: Array<{
|
|
22
|
+
review_context: string;
|
|
23
|
+
decision: "approved" | "needs_changes" | "rejected";
|
|
24
|
+
total_questions: number;
|
|
25
|
+
were_changes_suggested: boolean;
|
|
26
|
+
}>;
|
|
27
|
+
delegated_to: "frontend" | "backend" | "fullstack" | null;
|
|
28
|
+
worktree_branch_name: string | null;
|
|
29
|
+
status: "unimplemented" | "implemented" | "reviewed" | "tested" | "merged";
|
|
30
|
+
variant_solution: "discard" | "accept" | "feature-flag" | null;
|
|
31
|
+
design_files: Array<{ path: string; description: string }>;
|
|
32
|
+
walkthrough: Array<{
|
|
33
|
+
iteration: number;
|
|
34
|
+
type: "initial" | "review-refinement" | "testing-refinement";
|
|
35
|
+
refinement_reason: string | null;
|
|
36
|
+
approach: string;
|
|
37
|
+
changes: Array<{ file: string; description: string }>;
|
|
38
|
+
decisions: Array<{ decision: string; rationale: string }>;
|
|
39
|
+
}>;
|
|
40
|
+
documentation_extracted: boolean;
|
|
41
|
+
planned_at: string;
|
|
42
|
+
merge_commit_hash: string | null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Create default prompt front matter.
|
|
47
|
+
*/
|
|
48
|
+
export function createDefaultPromptFrontMatter(
|
|
49
|
+
number: number,
|
|
50
|
+
variant: string | null
|
|
51
|
+
): PromptFrontMatter {
|
|
52
|
+
return {
|
|
53
|
+
number,
|
|
54
|
+
variant,
|
|
55
|
+
description: "",
|
|
56
|
+
kind: "feature",
|
|
57
|
+
relevant_files: [],
|
|
58
|
+
success_criteria: "",
|
|
59
|
+
depends_on: [],
|
|
60
|
+
requires_manual_testing: false,
|
|
61
|
+
in_progress: false,
|
|
62
|
+
current_iteration: 1,
|
|
63
|
+
reviews: [],
|
|
64
|
+
delegated_to: null,
|
|
65
|
+
worktree_branch_name: null,
|
|
66
|
+
status: "unimplemented",
|
|
67
|
+
variant_solution: null,
|
|
68
|
+
design_files: [],
|
|
69
|
+
walkthrough: [],
|
|
70
|
+
documentation_extracted: false,
|
|
71
|
+
planned_at: new Date().toISOString(),
|
|
72
|
+
merge_commit_hash: null,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Read a prompt file.
|
|
78
|
+
*/
|
|
79
|
+
export function readPrompt(
|
|
80
|
+
number: number,
|
|
81
|
+
variant?: string | null
|
|
82
|
+
): { frontMatter: PromptFrontMatter; content: string } | null {
|
|
83
|
+
const promptPath = getPromptPath(number, variant);
|
|
84
|
+
const result = readMarkdownFile(promptPath);
|
|
85
|
+
if (!result) return null;
|
|
86
|
+
return {
|
|
87
|
+
frontMatter: result.frontMatter as unknown as PromptFrontMatter,
|
|
88
|
+
content: result.content,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Write a prompt file.
|
|
94
|
+
*/
|
|
95
|
+
export function writePrompt(
|
|
96
|
+
number: number,
|
|
97
|
+
variant: string | null,
|
|
98
|
+
frontMatter: Partial<PromptFrontMatter>,
|
|
99
|
+
content: string
|
|
100
|
+
): void {
|
|
101
|
+
const paths = getPlanPaths();
|
|
102
|
+
ensurePlanDir();
|
|
103
|
+
|
|
104
|
+
// Ensure prompts directory exists
|
|
105
|
+
if (!existsSync(paths.prompts)) {
|
|
106
|
+
mkdirSync(paths.prompts, { recursive: true });
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const defaults = createDefaultPromptFrontMatter(number, variant);
|
|
110
|
+
const merged = { ...defaults, ...frontMatter };
|
|
111
|
+
const promptPath = getPromptPath(number, variant);
|
|
112
|
+
writeMarkdownWithFrontMatter(promptPath, merged, content);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Delete a prompt file.
|
|
117
|
+
*/
|
|
118
|
+
export function deletePrompt(number: number, variant?: string | null): boolean {
|
|
119
|
+
const promptPath = getPromptPath(number, variant);
|
|
120
|
+
if (existsSync(promptPath)) {
|
|
121
|
+
unlinkSync(promptPath);
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* List all prompt files in the prompts directory.
|
|
129
|
+
* Returns array of { number, variant, path }
|
|
130
|
+
*/
|
|
131
|
+
export function listPrompts(): Array<{
|
|
132
|
+
number: number;
|
|
133
|
+
variant: string | null;
|
|
134
|
+
path: string;
|
|
135
|
+
}> {
|
|
136
|
+
const paths = getPlanPaths();
|
|
137
|
+
if (!existsSync(paths.prompts)) {
|
|
138
|
+
return [];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const files = readdirSync(paths.prompts).filter((f) => f.endsWith(".md"));
|
|
142
|
+
const prompts: Array<{ number: number; variant: string | null; path: string }> = [];
|
|
143
|
+
|
|
144
|
+
for (const file of files) {
|
|
145
|
+
// Match patterns: 1.md, 1_A.md, 12.md, 12_B.md
|
|
146
|
+
const match = file.match(/^(\d+)(?:_([A-Z]))?\.md$/);
|
|
147
|
+
if (match) {
|
|
148
|
+
prompts.push({
|
|
149
|
+
number: parseInt(match[1], 10),
|
|
150
|
+
variant: match[2] || null,
|
|
151
|
+
path: join(paths.prompts, file),
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return prompts.sort((a, b) => {
|
|
157
|
+
if (a.number !== b.number) return a.number - b.number;
|
|
158
|
+
if (!a.variant && b.variant) return -1;
|
|
159
|
+
if (a.variant && !b.variant) return 1;
|
|
160
|
+
return (a.variant || "").localeCompare(b.variant || "");
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Read all prompts and return their full data.
|
|
166
|
+
*/
|
|
167
|
+
export function readAllPrompts(): Array<{
|
|
168
|
+
number: number;
|
|
169
|
+
variant: string | null;
|
|
170
|
+
frontMatter: PromptFrontMatter;
|
|
171
|
+
content: string;
|
|
172
|
+
}> {
|
|
173
|
+
const promptList = listPrompts();
|
|
174
|
+
const results: Array<{
|
|
175
|
+
number: number;
|
|
176
|
+
variant: string | null;
|
|
177
|
+
frontMatter: PromptFrontMatter;
|
|
178
|
+
content: string;
|
|
179
|
+
}> = [];
|
|
180
|
+
|
|
181
|
+
for (const prompt of promptList) {
|
|
182
|
+
const data = readPrompt(prompt.number, prompt.variant);
|
|
183
|
+
if (data) {
|
|
184
|
+
results.push({
|
|
185
|
+
number: prompt.number,
|
|
186
|
+
variant: prompt.variant,
|
|
187
|
+
...data,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return results;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Update prompt status.
|
|
197
|
+
*/
|
|
198
|
+
export function updatePromptStatus(
|
|
199
|
+
number: number,
|
|
200
|
+
variant: string | null,
|
|
201
|
+
status: PromptFrontMatter["status"]
|
|
202
|
+
): boolean {
|
|
203
|
+
const prompt = readPrompt(number, variant);
|
|
204
|
+
if (!prompt) return false;
|
|
205
|
+
|
|
206
|
+
const updatedFrontMatter: PromptFrontMatter = {
|
|
207
|
+
...prompt.frontMatter,
|
|
208
|
+
status,
|
|
209
|
+
};
|
|
210
|
+
writePrompt(number, variant, updatedFrontMatter, prompt.content);
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Update prompt variant_solution field.
|
|
216
|
+
*/
|
|
217
|
+
export function updatePromptVariantSolution(
|
|
218
|
+
number: number,
|
|
219
|
+
variant: string | null,
|
|
220
|
+
variantSolution: PromptFrontMatter["variant_solution"]
|
|
221
|
+
): boolean {
|
|
222
|
+
const prompt = readPrompt(number, variant);
|
|
223
|
+
if (!prompt) return false;
|
|
224
|
+
|
|
225
|
+
const updatedFrontMatter: PromptFrontMatter = {
|
|
226
|
+
...prompt.frontMatter,
|
|
227
|
+
variant_solution: variantSolution,
|
|
228
|
+
};
|
|
229
|
+
writePrompt(number, variant, updatedFrontMatter, prompt.content);
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Protocol YAML parser with inheritance support.
|
|
3
|
+
*
|
|
4
|
+
* Protocols define sequential workflows for agents to follow.
|
|
5
|
+
* Extension protocols can:
|
|
6
|
+
* - Replace a step: declare same step number (e.g., `7:`)
|
|
7
|
+
* - Append to a step: use `+` suffix (e.g., `6+:`)
|
|
8
|
+
* - Insert new steps: use dot notation (e.g., `6.1:`, `6.2:`)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { readFileSync, existsSync, readdirSync } from "fs";
|
|
12
|
+
import { join } from "path";
|
|
13
|
+
import YAML from "yaml";
|
|
14
|
+
import { getProjectRoot } from "./git.js";
|
|
15
|
+
|
|
16
|
+
// ============================================================================
|
|
17
|
+
// Types
|
|
18
|
+
// ============================================================================
|
|
19
|
+
|
|
20
|
+
export interface ProtocolInput {
|
|
21
|
+
name: string;
|
|
22
|
+
type: "integer" | "string" | "array";
|
|
23
|
+
optional: boolean;
|
|
24
|
+
description: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ProtocolOutput {
|
|
28
|
+
value: string;
|
|
29
|
+
description: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface Protocol {
|
|
33
|
+
name: string;
|
|
34
|
+
description: string;
|
|
35
|
+
extends: string | null;
|
|
36
|
+
inputs: ProtocolInput[] | null;
|
|
37
|
+
outputs: ProtocolOutput[];
|
|
38
|
+
steps: Record<string, string>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface ResolvedStep {
|
|
42
|
+
number: number;
|
|
43
|
+
subNumber: number;
|
|
44
|
+
content: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface ResolvedProtocol {
|
|
48
|
+
name: string;
|
|
49
|
+
description: string;
|
|
50
|
+
inputs: ProtocolInput[];
|
|
51
|
+
outputs: ProtocolOutput[];
|
|
52
|
+
steps: ResolvedStep[];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ============================================================================
|
|
56
|
+
// Protocol Resolution
|
|
57
|
+
// ============================================================================
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Get protocols directory path.
|
|
61
|
+
*/
|
|
62
|
+
export function getProtocolsDir(): string {
|
|
63
|
+
return join(getProjectRoot(), ".claude", "protocols");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Get protocol file path by name.
|
|
68
|
+
*/
|
|
69
|
+
export function getProtocolPath(name: string): string {
|
|
70
|
+
return join(getProtocolsDir(), `${name}.yaml`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Read a protocol file.
|
|
75
|
+
*/
|
|
76
|
+
export function readProtocol(name: string): Protocol | null {
|
|
77
|
+
const path = getProtocolPath(name);
|
|
78
|
+
if (!existsSync(path)) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
const content = readFileSync(path, "utf-8");
|
|
84
|
+
return YAML.parse(content) as Protocol;
|
|
85
|
+
} catch {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Parse a step key into its components.
|
|
92
|
+
*
|
|
93
|
+
* Examples:
|
|
94
|
+
* - "1" -> { base: 1, sub: 0, append: false }
|
|
95
|
+
* - "6+" -> { base: 6, sub: 0, append: true }
|
|
96
|
+
* - "6.1" -> { base: 6, sub: 1, append: false }
|
|
97
|
+
* - "13.1" -> { base: 13, sub: 1, append: false }
|
|
98
|
+
*/
|
|
99
|
+
function parseStepKey(key: string): { base: number; sub: number; append: boolean } {
|
|
100
|
+
// Check for append modifier
|
|
101
|
+
const append = key.endsWith("+");
|
|
102
|
+
const cleanKey = append ? key.slice(0, -1) : key;
|
|
103
|
+
|
|
104
|
+
// Check for dot notation
|
|
105
|
+
if (cleanKey.includes(".")) {
|
|
106
|
+
const [baseStr, subStr] = cleanKey.split(".");
|
|
107
|
+
return {
|
|
108
|
+
base: parseInt(baseStr, 10),
|
|
109
|
+
sub: parseInt(subStr, 10),
|
|
110
|
+
append: false, // Dot notation doesn't support append
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
base: parseInt(cleanKey, 10),
|
|
116
|
+
sub: 0,
|
|
117
|
+
append,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Merge child protocol steps into base protocol steps.
|
|
123
|
+
*
|
|
124
|
+
* Inheritance rules:
|
|
125
|
+
* - Replace: Same step number replaces base step
|
|
126
|
+
* - Append: `+` suffix appends content to base step
|
|
127
|
+
* - Insert: Dot notation inserts new steps (e.g., 6.1 between 6 and 7)
|
|
128
|
+
*/
|
|
129
|
+
function mergeSteps(
|
|
130
|
+
baseSteps: Record<string, string>,
|
|
131
|
+
childSteps: Record<string, string>
|
|
132
|
+
): Record<string, string> {
|
|
133
|
+
const merged = { ...baseSteps };
|
|
134
|
+
|
|
135
|
+
for (const [key, content] of Object.entries(childSteps)) {
|
|
136
|
+
const parsed = parseStepKey(key);
|
|
137
|
+
|
|
138
|
+
if (parsed.sub > 0) {
|
|
139
|
+
// Insert: New step with dot notation (e.g., "6.1")
|
|
140
|
+
// Store with dot notation key
|
|
141
|
+
merged[key] = content;
|
|
142
|
+
} else if (parsed.append) {
|
|
143
|
+
// Append: Add content to existing step
|
|
144
|
+
const baseKey = String(parsed.base);
|
|
145
|
+
if (merged[baseKey]) {
|
|
146
|
+
merged[baseKey] = merged[baseKey].trimEnd() + "\n" + content;
|
|
147
|
+
} else {
|
|
148
|
+
// If base step doesn't exist, just set it
|
|
149
|
+
merged[baseKey] = content;
|
|
150
|
+
}
|
|
151
|
+
} else {
|
|
152
|
+
// Replace: Same step number replaces base step
|
|
153
|
+
merged[key] = content;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return merged;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Convert steps record to sorted array of resolved steps.
|
|
162
|
+
*/
|
|
163
|
+
function sortSteps(steps: Record<string, string>): ResolvedStep[] {
|
|
164
|
+
const resolved: ResolvedStep[] = [];
|
|
165
|
+
|
|
166
|
+
for (const [key, content] of Object.entries(steps)) {
|
|
167
|
+
const parsed = parseStepKey(key);
|
|
168
|
+
resolved.push({
|
|
169
|
+
number: parsed.base,
|
|
170
|
+
subNumber: parsed.sub,
|
|
171
|
+
content: content.trim(),
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Sort by base number, then by sub number
|
|
176
|
+
resolved.sort((a, b) => {
|
|
177
|
+
if (a.number !== b.number) {
|
|
178
|
+
return a.number - b.number;
|
|
179
|
+
}
|
|
180
|
+
return a.subNumber - b.subNumber;
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
return resolved;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Resolve a protocol with inheritance.
|
|
188
|
+
* Returns null if protocol not found.
|
|
189
|
+
*/
|
|
190
|
+
export function resolveProtocol(name: string): ResolvedProtocol | null {
|
|
191
|
+
const protocol = readProtocol(name);
|
|
192
|
+
if (!protocol) {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// If no extends, return as-is
|
|
197
|
+
if (!protocol.extends) {
|
|
198
|
+
return {
|
|
199
|
+
name: protocol.name,
|
|
200
|
+
description: protocol.description,
|
|
201
|
+
inputs: protocol.inputs || [],
|
|
202
|
+
outputs: protocol.outputs || [],
|
|
203
|
+
steps: sortSteps(protocol.steps),
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Resolve base protocol recursively
|
|
208
|
+
const baseProtocol = resolveProtocol(protocol.extends);
|
|
209
|
+
if (!baseProtocol) {
|
|
210
|
+
// Base protocol not found, return current protocol without inheritance
|
|
211
|
+
return {
|
|
212
|
+
name: protocol.name,
|
|
213
|
+
description: protocol.description,
|
|
214
|
+
inputs: protocol.inputs || [],
|
|
215
|
+
outputs: protocol.outputs || [],
|
|
216
|
+
steps: sortSteps(protocol.steps),
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Merge steps
|
|
221
|
+
// First, convert base steps back to record format
|
|
222
|
+
const baseStepsRecord: Record<string, string> = {};
|
|
223
|
+
for (const step of baseProtocol.steps) {
|
|
224
|
+
const key = step.subNumber > 0
|
|
225
|
+
? `${step.number}.${step.subNumber}`
|
|
226
|
+
: String(step.number);
|
|
227
|
+
baseStepsRecord[key] = step.content;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const mergedSteps = mergeSteps(baseStepsRecord, protocol.steps);
|
|
231
|
+
|
|
232
|
+
// Use child's inputs if specified, otherwise inherit from base
|
|
233
|
+
const inputs = protocol.inputs !== null ? protocol.inputs : baseProtocol.inputs;
|
|
234
|
+
|
|
235
|
+
// Use child's outputs if specified, otherwise inherit from base
|
|
236
|
+
const outputs = protocol.outputs.length > 0 ? protocol.outputs : baseProtocol.outputs;
|
|
237
|
+
|
|
238
|
+
return {
|
|
239
|
+
name: protocol.name,
|
|
240
|
+
description: protocol.description,
|
|
241
|
+
inputs: inputs || [],
|
|
242
|
+
outputs: outputs || [],
|
|
243
|
+
steps: sortSteps(mergedSteps),
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Format a resolved protocol for output.
|
|
249
|
+
* Returns a string with:
|
|
250
|
+
* - Protocol name and description
|
|
251
|
+
* - Inputs/outputs schema
|
|
252
|
+
* - Numbered steps
|
|
253
|
+
*/
|
|
254
|
+
export function formatProtocol(protocol: ResolvedProtocol): string {
|
|
255
|
+
const lines: string[] = [];
|
|
256
|
+
|
|
257
|
+
// Header
|
|
258
|
+
lines.push(`# ${protocol.name}`);
|
|
259
|
+
lines.push(`${protocol.description}`);
|
|
260
|
+
lines.push("");
|
|
261
|
+
|
|
262
|
+
// Inputs
|
|
263
|
+
if (protocol.inputs.length > 0) {
|
|
264
|
+
lines.push("## Inputs");
|
|
265
|
+
for (const input of protocol.inputs) {
|
|
266
|
+
const optional = input.optional ? "(optional)" : "(required)";
|
|
267
|
+
lines.push(`- ${input.name}: ${input.type} ${optional} - ${input.description}`);
|
|
268
|
+
}
|
|
269
|
+
lines.push("");
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Outputs
|
|
273
|
+
if (protocol.outputs.length > 0) {
|
|
274
|
+
lines.push("## Outputs");
|
|
275
|
+
for (const output of protocol.outputs) {
|
|
276
|
+
lines.push(`- ${output.value}`);
|
|
277
|
+
lines.push(` ${output.description}`);
|
|
278
|
+
}
|
|
279
|
+
lines.push("");
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Steps
|
|
283
|
+
lines.push("## Steps");
|
|
284
|
+
lines.push("");
|
|
285
|
+
|
|
286
|
+
let stepCounter = 1;
|
|
287
|
+
for (const step of protocol.steps) {
|
|
288
|
+
// Use sequential numbering for output
|
|
289
|
+
lines.push(`${stepCounter}: ${step.content}`);
|
|
290
|
+
lines.push("");
|
|
291
|
+
stepCounter++;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return lines.join("\n");
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* List all available protocol names.
|
|
299
|
+
*/
|
|
300
|
+
export function listProtocols(): string[] {
|
|
301
|
+
const dir = getProtocolsDir();
|
|
302
|
+
if (!existsSync(dir)) {
|
|
303
|
+
return [];
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
try {
|
|
307
|
+
const files = readdirSync(dir);
|
|
308
|
+
return files
|
|
309
|
+
.filter((f) => f.endsWith(".yaml"))
|
|
310
|
+
.map((f) => f.replace(".yaml", ""));
|
|
311
|
+
} catch {
|
|
312
|
+
return [];
|
|
313
|
+
}
|
|
314
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repomix utilities for token counting.
|
|
3
|
+
* Extracted from commands/repomix.ts for reuse by blocking gates.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { spawnSync } from "child_process";
|
|
7
|
+
|
|
8
|
+
export interface TreeEntry {
|
|
9
|
+
path: string;
|
|
10
|
+
tokens: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface RepomixResult {
|
|
14
|
+
success: boolean;
|
|
15
|
+
output: string;
|
|
16
|
+
tokenCount: number;
|
|
17
|
+
tree: TreeEntry[];
|
|
18
|
+
error?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Parse the token count tree from repomix output.
|
|
23
|
+
* Builds full paths by tracking parent directories.
|
|
24
|
+
*/
|
|
25
|
+
export function parseTokenTree(output: string, maxDepth: number): TreeEntry[] {
|
|
26
|
+
const entries: TreeEntry[] = [];
|
|
27
|
+
|
|
28
|
+
const treeMatch = output.match(/Token Count Tree:[\s\S]*?(?=\n\n|\nš|\nš|$)/);
|
|
29
|
+
if (!treeMatch) return entries;
|
|
30
|
+
|
|
31
|
+
const lines = treeMatch[0].split("\n").slice(2);
|
|
32
|
+
const pathStack: string[] = [];
|
|
33
|
+
|
|
34
|
+
for (const line of lines) {
|
|
35
|
+
const match = line.match(/^([āāāā\s]+)(.+?)\s+\(([0-9,]+)\s+tokens?\)/);
|
|
36
|
+
if (!match) continue;
|
|
37
|
+
|
|
38
|
+
const prefix = match[1];
|
|
39
|
+
const name = match[2].trim();
|
|
40
|
+
const tokens = parseInt(match[3].replace(/,/g, ""), 10);
|
|
41
|
+
const depth = Math.floor(prefix.length / 4);
|
|
42
|
+
|
|
43
|
+
pathStack.length = depth;
|
|
44
|
+
const fullPath = [...pathStack, name].join("");
|
|
45
|
+
|
|
46
|
+
if (name.endsWith("/")) {
|
|
47
|
+
pathStack.push(name);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (maxDepth <= 0 || depth <= maxDepth) {
|
|
51
|
+
entries.push({ path: fullPath, tokens });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return entries.sort((a, b) => b.tokens - a.tokens);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Run repomix on paths and return token info.
|
|
60
|
+
*/
|
|
61
|
+
export function runRepomix(paths: string[], tokenCountOnly: boolean): RepomixResult {
|
|
62
|
+
const cmd = ["npx", "repomix@latest", ...paths];
|
|
63
|
+
|
|
64
|
+
if (tokenCountOnly) {
|
|
65
|
+
cmd.push("--token-count-tree");
|
|
66
|
+
} else {
|
|
67
|
+
cmd.push("--stdout");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const result = spawnSync(cmd[0], cmd.slice(1), {
|
|
71
|
+
encoding: "utf-8",
|
|
72
|
+
timeout: 300000,
|
|
73
|
+
maxBuffer: 50 * 1024 * 1024,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
if (result.status !== 0) {
|
|
77
|
+
return {
|
|
78
|
+
success: false,
|
|
79
|
+
output: result.stderr || "repomix failed",
|
|
80
|
+
tokenCount: 0,
|
|
81
|
+
tree: [],
|
|
82
|
+
error: result.stderr || "repomix failed",
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const output = result.stdout || "";
|
|
87
|
+
|
|
88
|
+
let tokenCount = 0;
|
|
89
|
+
const tokenMatch = output.match(/Total\s+Tokens?:\s+([\d,]+)/i);
|
|
90
|
+
if (tokenMatch) {
|
|
91
|
+
tokenCount = parseInt(tokenMatch[1].replace(/,/g, ""), 10);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const maxDepth = parseInt(process.env.REPOMIX_MAX_DEPTH || "0", 10);
|
|
95
|
+
const tree = parseTokenTree(output, maxDepth);
|
|
96
|
+
|
|
97
|
+
return { success: true, output, tokenCount, tree };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Get token count for a single file.
|
|
102
|
+
* Returns the file's token count (not wrapper overhead).
|
|
103
|
+
*/
|
|
104
|
+
export function getFileTokenCount(filePath: string): {
|
|
105
|
+
success: boolean;
|
|
106
|
+
tokenCount: number;
|
|
107
|
+
error?: string;
|
|
108
|
+
} {
|
|
109
|
+
const result = runRepomix([filePath], true);
|
|
110
|
+
|
|
111
|
+
if (!result.success) {
|
|
112
|
+
return { success: false, tokenCount: 0, error: result.error };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Find the specific file in the tree (it will be the only file entry)
|
|
116
|
+
const fileEntry = result.tree.find((e) => !e.path.endsWith("/"));
|
|
117
|
+
const tokenCount = fileEntry?.tokens ?? 0;
|
|
118
|
+
|
|
119
|
+
return { success: true, tokenCount };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Get max log tokens from env or default.
|
|
124
|
+
* Default: 10000 tokens
|
|
125
|
+
*/
|
|
126
|
+
export function getMaxLogTokens(): number {
|
|
127
|
+
const envVal = process.env.MAX_LOGS_TOKENS;
|
|
128
|
+
if (envVal) {
|
|
129
|
+
const parsed = parseInt(envVal, 10);
|
|
130
|
+
if (!isNaN(parsed) && parsed > 0) return parsed;
|
|
131
|
+
}
|
|
132
|
+
return 10000;
|
|
133
|
+
}
|