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.
Files changed (170) hide show
  1. package/.claude/agents/code-simplifier.md +52 -0
  2. package/.claude/agents/curator.md +186 -246
  3. package/.claude/agents/documentation-taxonomist.md +255 -0
  4. package/.claude/agents/documentation-writer.md +366 -0
  5. package/.claude/agents/planner.md +123 -166
  6. package/.claude/agents/researcher.md +58 -41
  7. package/.claude/agents/surveyor.md +81 -0
  8. package/.claude/agents/worker.md +74 -0
  9. package/.claude/commands/continue.md +122 -0
  10. package/.claude/commands/create-skill.md +107 -0
  11. package/.claude/commands/create-specialist.md +111 -0
  12. package/.claude/commands/curator-audit.md +4 -0
  13. package/.claude/commands/debug.md +183 -0
  14. package/.claude/commands/docs-adjust.md +214 -0
  15. package/.claude/commands/docs-audit.md +172 -0
  16. package/.claude/commands/docs-init.md +210 -0
  17. package/.claude/commands/plan.md +199 -102
  18. package/.claude/commands/validate.md +11 -0
  19. package/.claude/commands/whats-next.md +106 -134
  20. package/.claude/envoy/README.md +5 -5
  21. package/.claude/envoy/envoy +11 -14
  22. package/.claude/envoy/package-lock.json +1594 -0
  23. package/.claude/envoy/package.json +38 -0
  24. package/.claude/envoy/src/cli.ts +126 -0
  25. package/.claude/envoy/src/commands/base.ts +216 -0
  26. package/.claude/envoy/src/commands/docs.ts +881 -0
  27. package/.claude/envoy/src/commands/gemini.ts +999 -0
  28. package/.claude/envoy/src/commands/git.ts +639 -0
  29. package/.claude/envoy/src/commands/index.ts +73 -0
  30. package/.claude/envoy/src/commands/knowledge.ts +178 -0
  31. package/.claude/envoy/src/commands/perplexity.ts +129 -0
  32. package/.claude/envoy/src/commands/plan/core.ts +134 -0
  33. package/.claude/envoy/src/commands/plan/findings.ts +446 -0
  34. package/.claude/envoy/src/commands/plan/gates.ts +672 -0
  35. package/.claude/envoy/src/commands/plan/index.ts +135 -0
  36. package/.claude/envoy/src/commands/plan/lifecycle.ts +648 -0
  37. package/.claude/envoy/src/commands/plan/plan-file.ts +138 -0
  38. package/.claude/envoy/src/commands/plan/prompts.ts +285 -0
  39. package/.claude/envoy/src/commands/plan/protocols.ts +166 -0
  40. package/.claude/envoy/src/commands/repomix.ts +99 -0
  41. package/.claude/envoy/src/commands/tavily.ts +220 -0
  42. package/.claude/envoy/src/commands/xai.ts +168 -0
  43. package/.claude/envoy/src/lib/ast-queries.ts +261 -0
  44. package/.claude/envoy/src/lib/design.ts +41 -0
  45. package/.claude/envoy/src/lib/feedback-schemas.ts +154 -0
  46. package/.claude/envoy/src/lib/findings.ts +215 -0
  47. package/.claude/envoy/src/lib/gates.ts +572 -0
  48. package/.claude/envoy/src/lib/git.ts +132 -0
  49. package/.claude/envoy/src/lib/index.ts +188 -0
  50. package/.claude/envoy/src/lib/knowledge.ts +646 -0
  51. package/.claude/envoy/src/lib/markdown.ts +75 -0
  52. package/.claude/envoy/src/lib/observability.ts +262 -0
  53. package/.claude/envoy/src/lib/paths.ts +130 -0
  54. package/.claude/envoy/src/lib/plan-io.ts +117 -0
  55. package/.claude/envoy/src/lib/prompts.ts +231 -0
  56. package/.claude/envoy/src/lib/protocols.ts +314 -0
  57. package/.claude/envoy/src/lib/repomix.ts +133 -0
  58. package/.claude/envoy/src/lib/retry.ts +138 -0
  59. package/.claude/envoy/src/lib/tree-sitter-utils.ts +301 -0
  60. package/.claude/envoy/src/lib/watcher.ts +167 -0
  61. package/.claude/envoy/src/types/tree-sitter.d.ts +76 -0
  62. package/.claude/envoy/tsconfig.json +21 -0
  63. package/.claude/hooks/scripts/enforce_research_fetch.py +1 -1
  64. package/.claude/hooks/scripts/scan_agents.py +62 -0
  65. package/.claude/hooks/scripts/scan_commands.py +50 -0
  66. package/.claude/hooks/scripts/scan_skills.py +46 -70
  67. package/.claude/hooks/scripts/validate_artifacts.py +128 -0
  68. package/.claude/hooks/startup.sh +26 -24
  69. package/.claude/protocols/bug-discovery.yaml +55 -0
  70. package/.claude/protocols/debugging.yaml +51 -0
  71. package/.claude/protocols/discovery.yaml +53 -0
  72. package/.claude/protocols/implementation.yaml +84 -0
  73. package/.claude/settings.json +38 -97
  74. package/.claude/skills/brainstorming/SKILL.md +54 -0
  75. package/.claude/skills/commands-development/SKILL.md +630 -0
  76. package/.claude/skills/commands-development/references/arguments.md +252 -0
  77. package/.claude/skills/commands-development/references/patterns.md +796 -0
  78. package/.claude/skills/commands-development/references/tool-restrictions.md +376 -0
  79. package/.claude/skills/discovery-mode/SKILL.md +108 -0
  80. package/.claude/skills/documentation-taxonomy/SKILL.md +287 -0
  81. package/.claude/skills/hooks-development/SKILL.md +332 -0
  82. package/.claude/skills/hooks-development/references/command-vs-prompt.md +269 -0
  83. package/.claude/skills/hooks-development/references/examples.md +658 -0
  84. package/.claude/skills/hooks-development/references/hook-types.md +463 -0
  85. package/.claude/skills/hooks-development/references/input-output-schemas.md +469 -0
  86. package/.claude/skills/hooks-development/references/matchers.md +470 -0
  87. package/.claude/skills/hooks-development/references/troubleshooting.md +587 -0
  88. package/.claude/skills/implementation-mode/SKILL.md +171 -0
  89. package/.claude/skills/knowledge-discovery/SKILL.md +178 -0
  90. package/.claude/skills/research-tools/SKILL.md +35 -33
  91. package/.claude/skills/skills-development/SKILL.md +192 -0
  92. package/.claude/skills/skills-development/references/api-security.md +226 -0
  93. package/.claude/skills/skills-development/references/be-clear-and-direct.md +531 -0
  94. package/.claude/skills/skills-development/references/common-patterns.md +595 -0
  95. package/.claude/skills/skills-development/references/core-principles.md +437 -0
  96. package/.claude/skills/skills-development/references/executable-code.md +175 -0
  97. package/.claude/skills/skills-development/references/iteration-and-testing.md +474 -0
  98. package/.claude/skills/skills-development/references/recommended-structure.md +168 -0
  99. package/.claude/skills/skills-development/references/skill-structure.md +372 -0
  100. package/.claude/skills/skills-development/references/use-xml-tags.md +466 -0
  101. package/.claude/skills/skills-development/references/using-scripts.md +113 -0
  102. package/.claude/skills/skills-development/references/using-templates.md +112 -0
  103. package/.claude/skills/skills-development/references/workflows-and-validation.md +510 -0
  104. package/.claude/skills/skills-development/templates/router-skill.md +73 -0
  105. package/.claude/skills/skills-development/templates/simple-skill.md +33 -0
  106. package/.claude/skills/skills-development/workflows/add-reference.md +96 -0
  107. package/.claude/skills/skills-development/workflows/add-script.md +93 -0
  108. package/.claude/skills/skills-development/workflows/add-template.md +74 -0
  109. package/.claude/skills/skills-development/workflows/add-workflow.md +120 -0
  110. package/.claude/skills/skills-development/workflows/audit-skill.md +138 -0
  111. package/.claude/skills/skills-development/workflows/create-domain-expertise-skill.md +605 -0
  112. package/.claude/skills/skills-development/workflows/create-new-skill.md +191 -0
  113. package/.claude/skills/skills-development/workflows/get-guidance.md +121 -0
  114. package/.claude/skills/skills-development/workflows/upgrade-to-router.md +161 -0
  115. package/.claude/skills/skills-development/workflows/verify-skill.md +204 -0
  116. package/.claude/skills/subagents-development/SKILL.md +325 -0
  117. package/.claude/skills/subagents-development/references/context-management.md +567 -0
  118. package/.claude/skills/subagents-development/references/debugging-agents.md +714 -0
  119. package/.claude/skills/subagents-development/references/error-handling-and-recovery.md +502 -0
  120. package/.claude/skills/subagents-development/references/evaluation-and-testing.md +374 -0
  121. package/.claude/skills/subagents-development/references/orchestration-patterns.md +591 -0
  122. package/.claude/skills/subagents-development/references/subagents.md +508 -0
  123. package/.claude/skills/subagents-development/references/writing-subagent-prompts.md +517 -0
  124. package/.claude/statusline.sh +24 -0
  125. package/bin/cli.js +150 -72
  126. package/package.json +1 -1
  127. package/.claude/agents/explorer.md +0 -62
  128. package/.claude/agents/parallel-worker.md +0 -121
  129. package/.claude/commands/curation-fix.md +0 -92
  130. package/.claude/commands/new-branch.md +0 -36
  131. package/.claude/commands/parallel-discovery.md +0 -69
  132. package/.claude/commands/parallel-orchestration.md +0 -99
  133. package/.claude/commands/plan-checkpoint.md +0 -37
  134. package/.claude/envoy/commands/__init__.py +0 -1
  135. package/.claude/envoy/commands/base.py +0 -95
  136. package/.claude/envoy/commands/parallel.py +0 -439
  137. package/.claude/envoy/commands/perplexity.py +0 -86
  138. package/.claude/envoy/commands/plans.py +0 -451
  139. package/.claude/envoy/commands/tavily.py +0 -156
  140. package/.claude/envoy/commands/vertex.py +0 -358
  141. package/.claude/envoy/commands/xai.py +0 -124
  142. package/.claude/envoy/envoy.py +0 -122
  143. package/.claude/envoy/pyrightconfig.json +0 -4
  144. package/.claude/envoy/requirements.txt +0 -2
  145. package/.claude/hooks/capture-queries.sh +0 -3
  146. package/.claude/hooks/scripts/enforce_planning.py +0 -118
  147. package/.claude/hooks/scripts/enforce_rg.py +0 -34
  148. package/.claude/hooks/scripts/validate_skill.py +0 -81
  149. package/.claude/skills/claude-envoy-curation/SKILL.md +0 -162
  150. package/.claude/skills/claude-envoy-usage/SKILL.md +0 -46
  151. package/.claude/skills/command-development/SKILL.md +0 -206
  152. package/.claude/skills/command-development/examples/simple-commands.md +0 -212
  153. package/.claude/skills/command-development/references/frontmatter-reference.md +0 -221
  154. package/.claude/skills/hook-development/SKILL.md +0 -127
  155. package/.claude/skills/hook-development/examples/command-hooks.md +0 -301
  156. package/.claude/skills/hook-development/examples/prompt-hooks.md +0 -114
  157. package/.claude/skills/hook-development/references/event-reference.md +0 -226
  158. package/.claude/skills/repomix-extraction/SKILL.md +0 -91
  159. package/.claude/skills/skill-development/SKILL.md +0 -168
  160. package/.claude/skills/skill-development/examples/complete-skill-examples.md +0 -281
  161. package/.claude/skills/skill-development/references/progressive-disclosure.md +0 -141
  162. package/.claude/skills/skill-development/references/writing-style.md +0 -180
  163. package/.claude/skills/skill-development/scripts/validate-skill.sh +0 -144
  164. package/.claude/skills/specialist-builder/SKILL.md +0 -327
  165. package/.claude/skills/specialist-builder/docs/agent-catalog.md +0 -28
  166. package/.claude/skills/specialist-builder/examples/complete-agent-examples.md +0 -206
  167. package/.claude/skills/specialist-builder/references/system-prompt-patterns.md +0 -281
  168. package/.claude/skills/specialist-builder/references/triggering-examples.md +0 -162
  169. package/.claude/skills/specialist-builder/scripts/validate-agent.sh +0 -137
  170. /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
+ }