spets 0.1.49 → 0.1.51

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 (2) hide show
  1. package/dist/index.js +725 -752
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2,13 +2,13 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { Command } from "commander";
5
- import { readFileSync as readFileSync12 } from "fs";
6
- import { dirname as dirname6, join as join11 } from "path";
5
+ import { readFileSync as readFileSync13 } from "fs";
6
+ import { dirname as dirname7, join as join11 } from "path";
7
7
  import { fileURLToPath as fileURLToPath2 } from "url";
8
8
 
9
9
  // src/commands/init.ts
10
- import { existsSync as existsSync2, mkdirSync, writeFileSync, cpSync, readFileSync as readFileSync2 } from "fs";
11
- import { join as join2, dirname } from "path";
10
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, cpSync, readFileSync as readFileSync2 } from "fs";
11
+ import { join as join3, dirname } from "path";
12
12
  import { fileURLToPath } from "url";
13
13
  import { execSync } from "child_process";
14
14
 
@@ -65,127 +65,189 @@ function getGitHubConfig(cwd = process.cwd()) {
65
65
  return config.github;
66
66
  }
67
67
 
68
- // src/commands/init.ts
69
- var __dirname = dirname(fileURLToPath(import.meta.url));
70
- function getGitHubInfoFromRemote() {
71
- try {
72
- const remoteUrl = execSync("git remote get-url origin", { encoding: "utf-8" }).trim();
73
- const sshMatch = remoteUrl.match(/git@github\.com:([^/]+)\/(.+?)(?:\.git)?$/);
74
- if (sshMatch) {
75
- return { owner: sshMatch[1], repo: sshMatch[2] };
76
- }
77
- const httpsMatch = remoteUrl.match(/https:\/\/github\.com\/([^/]+)\/(.+?)(?:\.git)?$/);
78
- if (httpsMatch) {
79
- return { owner: httpsMatch[1], repo: httpsMatch[2] };
80
- }
81
- return null;
82
- } catch {
83
- return null;
68
+ // src/commands/plugin.ts
69
+ import { existsSync as existsSync2, mkdirSync, writeFileSync, rmSync, readdirSync } from "fs";
70
+ import { join as join2 } from "path";
71
+ import { homedir } from "os";
72
+ async function pluginCommand(action, name) {
73
+ switch (action) {
74
+ case "install":
75
+ if (!name) {
76
+ console.error("Plugin name required.");
77
+ console.error("Usage: spets plugin install <name>");
78
+ process.exit(1);
79
+ }
80
+ await installPlugin(name);
81
+ break;
82
+ case "uninstall":
83
+ if (!name) {
84
+ console.error("Plugin name required.");
85
+ console.error("Usage: spets plugin uninstall <name>");
86
+ process.exit(1);
87
+ }
88
+ await uninstallPlugin(name);
89
+ break;
90
+ case "list":
91
+ await listPlugins();
92
+ break;
93
+ default:
94
+ console.error(`Unknown action: ${action}`);
95
+ console.error("Available actions: install, uninstall, list");
96
+ process.exit(1);
84
97
  }
85
98
  }
86
- async function initCommand(options) {
87
- const cwd = process.cwd();
88
- const spetsDir = getSpetsDir(cwd);
89
- if (spetsExists(cwd) && !options.force) {
90
- console.error("Spets already initialized. Use --force to overwrite.");
99
+ async function installPlugin(name) {
100
+ const plugins = {
101
+ claude: installClaudePlugin,
102
+ codex: installCodexPlugin,
103
+ opencode: installOpenCodePlugin
104
+ };
105
+ const installer = plugins[name];
106
+ if (!installer) {
107
+ console.error(`Unknown plugin: ${name}`);
108
+ console.error("Available plugins: claude, codex, opencode");
91
109
  process.exit(1);
92
110
  }
111
+ installer();
112
+ }
113
+ function installClaudePlugin() {
114
+ const claudeDir = join2(homedir(), ".claude");
115
+ const commandsDir = join2(claudeDir, "commands");
116
+ mkdirSync(commandsDir, { recursive: true });
117
+ const skillPath = join2(commandsDir, "spets.md");
118
+ writeFileSync(skillPath, getClaudeSkillContent());
119
+ console.log("Installed Claude Code plugin.");
120
+ console.log(`Location: ${skillPath}`);
121
+ console.log("");
122
+ console.log("Usage in Claude Code:");
123
+ console.log(' /spets "your task description"');
124
+ console.log("");
125
+ console.log("This skill runs deterministically within your Claude Code session.");
126
+ console.log("No additional Claude processes are spawned.");
127
+ }
128
+ function installCodexPlugin() {
129
+ const codexDir = join2(homedir(), ".codex");
130
+ const skillsDir = join2(codexDir, "skills");
131
+ const spetsDir = join2(skillsDir, "spets");
93
132
  mkdirSync(spetsDir, { recursive: true });
94
- mkdirSync(join2(spetsDir, "steps"), { recursive: true });
95
- mkdirSync(join2(spetsDir, "outputs"), { recursive: true });
96
- mkdirSync(join2(spetsDir, "hooks"), { recursive: true });
97
- const templatesDir = join2(__dirname, "..", "templates");
98
- const hookTemplate = join2(__dirname, "..", "templates", "hooks", "cleanup-branch.sh");
99
- if (existsSync2(hookTemplate)) {
100
- const hookDest = join2(spetsDir, "hooks", "cleanup-branch.sh");
101
- cpSync(hookTemplate, hookDest);
102
- try {
103
- execSync(`chmod +x "${hookDest}"`);
104
- } catch {
133
+ const skillPath = join2(spetsDir, "SKILL.md");
134
+ writeFileSync(skillPath, getCodexSkillContent());
135
+ console.log("Installed Codex CLI plugin.");
136
+ console.log(`Location: ${skillPath}`);
137
+ console.log("");
138
+ console.log("Usage in Codex CLI:");
139
+ console.log(' $spets "your task description"');
140
+ console.log("");
141
+ console.log("This skill runs deterministically within your Codex CLI session.");
142
+ console.log("No additional Codex processes are spawned.");
143
+ }
144
+ function installOpenCodePlugin() {
145
+ const configDir = join2(homedir(), ".config", "opencode");
146
+ const skillsDir = join2(configDir, "skills");
147
+ const spetsDir = join2(skillsDir, "spets");
148
+ mkdirSync(spetsDir, { recursive: true });
149
+ const skillPath = join2(spetsDir, "SKILL.md");
150
+ writeFileSync(skillPath, getOpenCodeSkillContent());
151
+ console.log("Installed OpenCode plugin.");
152
+ console.log(`Location: ${skillPath}`);
153
+ console.log("");
154
+ console.log("Usage in OpenCode:");
155
+ console.log(" Use the spets skill in your OpenCode session");
156
+ console.log("");
157
+ console.log("This skill runs deterministically within your OpenCode session.");
158
+ console.log("No additional OpenCode processes are spawned.");
159
+ }
160
+ async function uninstallPlugin(name) {
161
+ if (name === "claude") {
162
+ const skillPath = join2(homedir(), ".claude", "commands", "spets.md");
163
+ const legacySkillPath = join2(homedir(), ".claude", "commands", "sdd-do.md");
164
+ let uninstalled = false;
165
+ if (existsSync2(skillPath)) {
166
+ rmSync(skillPath);
167
+ uninstalled = true;
168
+ }
169
+ if (existsSync2(legacySkillPath)) {
170
+ rmSync(legacySkillPath);
171
+ uninstalled = true;
172
+ }
173
+ if (uninstalled) {
174
+ console.log("Uninstalled Claude Code plugin.");
175
+ } else {
176
+ console.log("Claude Code plugin not installed.");
105
177
  }
178
+ return;
106
179
  }
107
- const githubInfo = getGitHubInfoFromRemote();
108
- writeFileSync(join2(spetsDir, "config.yml"), getDefaultConfig(githubInfo));
109
- createDefaultSteps(spetsDir);
110
- createClaudeCommand(cwd);
111
- console.log("Initialized spets in .spets/");
112
- console.log("");
113
- console.log("Created:");
114
- console.log(" .spets/config.yml - Workflow configuration");
115
- console.log(" .spets/steps/01-plan/template.md - Planning step template");
116
- console.log(" .spets/steps/02-implement/template.md - Implementation step template");
117
- console.log(" .spets/hooks/cleanup-branch.sh - Example branch cleanup hook");
118
- console.log(" .claude/commands/spets.md - Claude Code command");
119
- if (options.github) {
120
- createGitHubWorkflow(cwd);
121
- console.log(" .github/workflows/spets.yml - GitHub Actions workflow");
122
- console.log(" .github/ISSUE_TEMPLATE/spets-task.yml - Issue template");
180
+ if (name === "codex") {
181
+ const skillPath = join2(homedir(), ".codex", "skills", "spets", "SKILL.md");
182
+ if (existsSync2(skillPath)) {
183
+ rmSync(skillPath);
184
+ try {
185
+ const skillDir = join2(homedir(), ".codex", "skills", "spets");
186
+ const skillsDir = join2(homedir(), ".codex", "skills");
187
+ rmSync(skillDir, { recursive: true });
188
+ const remaining = readdirSync(skillsDir);
189
+ if (remaining.length === 0) {
190
+ rmSync(skillsDir);
191
+ }
192
+ } catch {
193
+ }
194
+ console.log("Uninstalled Codex CLI plugin.");
195
+ } else {
196
+ console.log("Codex CLI plugin not installed.");
197
+ }
198
+ return;
199
+ }
200
+ if (name === "opencode") {
201
+ const skillPath = join2(homedir(), ".config", "opencode", "skills", "spets", "SKILL.md");
202
+ if (existsSync2(skillPath)) {
203
+ rmSync(skillPath);
204
+ try {
205
+ const skillDir = join2(homedir(), ".config", "opencode", "skills", "spets");
206
+ const skillsDir = join2(homedir(), ".config", "opencode", "skills");
207
+ rmSync(skillDir, { recursive: true });
208
+ const remaining = readdirSync(skillsDir);
209
+ if (remaining.length === 0) {
210
+ rmSync(skillsDir);
211
+ }
212
+ } catch {
213
+ }
214
+ console.log("Uninstalled OpenCode plugin.");
215
+ } else {
216
+ console.log("OpenCode plugin not installed.");
217
+ }
218
+ return;
123
219
  }
220
+ console.error(`Unknown plugin: ${name}`);
221
+ process.exit(1);
222
+ }
223
+ async function listPlugins() {
224
+ console.log("Available plugins:");
124
225
  console.log("");
125
- console.log("Next steps:");
126
- console.log(" 1. Edit .spets/config.yml to customize your workflow");
127
- console.log(" 2. Customize step templates in .spets/steps/");
128
- if (options.github) {
129
- console.log(" 3. Add CLAUDE_CODE_OAUTH_TOKEN to your repo secrets");
130
- console.log(' 4. Create a new Issue using the "Spets Task" template');
226
+ console.log(" claude - Claude Code /spets skill");
227
+ console.log(" codex - Codex CLI $spets skill");
228
+ console.log(" opencode - OpenCode spets skill");
229
+ console.log("");
230
+ const claudeSkillPath = join2(homedir(), ".claude", "commands", "spets.md");
231
+ const claudeLegacySkillPath = join2(homedir(), ".claude", "commands", "sdd-do.md");
232
+ const claudeInstalled = existsSync2(claudeSkillPath) || existsSync2(claudeLegacySkillPath);
233
+ const codexSkillPath = join2(homedir(), ".codex", "skills", "spets", "SKILL.md");
234
+ const codexInstalled = existsSync2(codexSkillPath);
235
+ const opencodeSkillPath = join2(homedir(), ".config", "opencode", "skills", "spets", "SKILL.md");
236
+ const opencodeInstalled = existsSync2(opencodeSkillPath);
237
+ console.log("Installed:");
238
+ const installed = [];
239
+ if (claudeInstalled) installed.push("claude");
240
+ if (codexInstalled) installed.push("codex");
241
+ if (opencodeInstalled) installed.push("opencode");
242
+ if (installed.length > 0) {
243
+ for (const plugin of installed) {
244
+ console.log(` - ${plugin}`);
245
+ }
131
246
  } else {
132
- console.log(' 3. Run: spets start "your task description"');
247
+ console.log(" (none)");
133
248
  }
134
249
  }
135
- function getDefaultConfig(githubInfo) {
136
- const githubSection = githubInfo ? `
137
- # GitHub integration (auto-detected from git remote)
138
- github:
139
- owner: ${githubInfo.owner}
140
- repo: ${githubInfo.repo}
141
- ` : `
142
- # GitHub integration (uncomment and fill in to enable)
143
- # github:
144
- # owner: your-org
145
- # repo: your-repo
146
- `;
147
- return `# Spets Configuration
148
- # Define your workflow steps here
149
-
150
- steps:
151
- - 01-plan
152
- - 02-implement
153
-
154
- # Output configuration
155
- output:
156
- path: .spets/outputs
157
- ${githubSection}
158
- # Optional hooks (shell scripts)
159
- # hooks:
160
- # preStep: "./hooks/pre-step.sh"
161
- # postStep: "./hooks/post-step.sh"
162
- # onApprove: "./hooks/on-approve.sh"
163
- # onReject: "./hooks/cleanup-branch.sh"
164
- # onComplete: "./hooks/cleanup-branch.sh"
165
- `;
166
- }
167
- function createDefaultSteps(spetsDir) {
168
- const planDir = join2(spetsDir, "steps", "01-plan");
169
- mkdirSync(planDir, { recursive: true });
170
- writeFileSync(join2(planDir, "template.md"), getPlanTemplate());
171
- const implementDir = join2(spetsDir, "steps", "02-implement");
172
- mkdirSync(implementDir, { recursive: true });
173
- writeFileSync(join2(implementDir, "template.md"), getImplementTemplate());
174
- }
175
- function getPlanTemplate() {
176
- const fullTemplate = readFileSync2(join2(__dirname, "..", "templates", "steps", "01-plan", "template.md"), "utf-8");
177
- return fullTemplate;
178
- }
179
- function getImplementTemplate() {
180
- const fullTemplate = readFileSync2(join2(__dirname, "..", "templates", "steps", "02-implement", "template.md"), "utf-8");
181
- return fullTemplate;
182
- }
183
- function createClaudeCommand(cwd) {
184
- const commandDir = join2(cwd, ".claude", "commands");
185
- mkdirSync(commandDir, { recursive: true });
186
- writeFileSync(join2(commandDir, "spets.md"), getClaudeCommand());
187
- }
188
- function getClaudeCommand() {
250
+ function getClaudeSkillContent() {
189
251
  return `# Spets Command Executor
190
252
 
191
253
  Execute orchestrator commands. Parse JSON response \u2192 execute matching action.
@@ -217,11 +279,10 @@ Parse JSON response \u2192 execute action from table below \u2192 loop until \`t
217
279
 
218
280
  | \`phase\` | Action |
219
281
  |---------|--------|
220
- | \`context\` | [ACTION_CONTEXT](#action_context) |
282
+ | \`explore\` | [ACTION_EXPLORE](#action_explore) |
221
283
  | \`clarify\` | [ACTION_CLARIFY](#action_clarify) |
222
- | \`generate\` | [ACTION_GENERATE](#action_generate) |
223
- | \`generate-sections\` | [ACTION_PARALLEL_SECTIONS](#action_parallel_sections) |
224
- | \`consolidate\` | [ACTION_PARALLEL_CONSOLIDATE](#action_parallel_consolidate) |
284
+ | \`execute\` | [ACTION_EXECUTE](#action_execute) |
285
+ | \`verify\` | [ACTION_VERIFY](#action_verify) |
225
286
 
226
287
  ### \`type: "checkpoint"\`
227
288
 
@@ -246,99 +307,66 @@ Print: "Error: {error}" \u2192 STOP
246
307
 
247
308
  ## Action Definitions
248
309
 
249
- ### ACTION_CONTEXT
310
+ **IMPORTANT:** Each phase response includes a \`prompt\` field containing the exact instructions to follow.
311
+ This ensures identical behavior across CLI, Claude Code, and GitHub modes.
312
+
313
+ ### ACTION_EXPLORE
250
314
 
251
315
  \`\`\`
252
- 1. Read(context.instruction)
253
- 2. IF context.previousOutput EXISTS: Read(context.previousOutput)
254
- 3. Explore codebase for: {description}
255
- 4. Bash: npx spets orchestrate context-done {taskId}
256
- 5. Parse response \u2192 next action
316
+ 1. Follow the instructions in response.prompt EXACTLY
317
+ 2. The prompt tells you:
318
+ - What to search for
319
+ - How to analyze the codebase
320
+ - The exact JSON output format required
321
+ 3. Bash: npx spets orchestrate explore-done {taskId} '{exploreJson}'
322
+ 4. Parse response \u2192 next action
257
323
  \`\`\`
258
324
 
259
325
  ### ACTION_CLARIFY
260
326
 
261
327
  \`\`\`
262
- 1. Analyze: gatheredContext, previousQA (if exists)
263
- 2. Generate questions array (0-4 questions)
328
+ 1. Follow the instructions in response.prompt EXACTLY
329
+ 2. The prompt tells you:
330
+ - What context to analyze
331
+ - How to generate questions
332
+ - The exact JSON output format required
264
333
  3. Bash: npx spets orchestrate clarify-done {taskId} '{questionsJson}'
265
334
  - Format: [{"id":"q1","question":"..."},...]
266
335
  - Empty if no questions: []
267
336
  4. Parse response \u2192 next action
268
337
  \`\`\`
269
338
 
270
- ### ACTION_GENERATE
339
+ ### ACTION_EXECUTE
271
340
 
272
341
  \`\`\`
273
- 1. Read(context.instruction)
274
- 2. IF context.template EXISTS: Read(context.template)
275
- 3. IF context.previousOutput EXISTS: Read(context.previousOutput)
276
- 4. Generate document using: gatheredContext, answers, revisionFeedback
277
- 5. Write(context.output, documentContent)
278
- 6. Bash: npx spets orchestrate generate-done {taskId}
279
- 7. Parse response \u2192 next action
342
+ 1. Follow the instructions in response.prompt EXACTLY
343
+ 2. The prompt tells you:
344
+ - The instruction and template to follow
345
+ - The explore output and answers to use
346
+ - Where to save the document/code (context.output)
347
+ - Any revision/verify feedback to address
348
+ 3. Write the document/code to context.output
349
+ 4. Bash: npx spets orchestrate execute-done {taskId}
350
+ 5. Parse response \u2192 next action
280
351
  \`\`\`
281
352
 
282
- ### ACTION_PARALLEL_SECTIONS
283
-
284
- **PARALLEL EXECUTION (FOREGROUND)**
353
+ ### ACTION_VERIFY
285
354
 
286
355
  \`\`\`
287
- 1. FOR EACH section IN parallelSections:
288
- Task(
289
- subagent_type: "general-purpose",
290
- prompt: "
291
- Generate section and save to file. Return ONLY the path.
292
-
293
- Path: {section.outputPath}
294
- Title: {section.title}
295
- Instruction: {section.instruction}
296
- Context: {sharedContext.description}
297
-
298
- 1. Generate content for this section
299
- 2. Write to the path using Write tool
300
- 3. Return ONLY: {section.outputPath}
301
- (no explanations, no content, just the path)
302
- "
303
- )
304
-
305
- 2. ALL Task calls MUST be in SINGLE message (parallel execution)
306
-
307
- 3. Bash: npx spets orchestrate sections-done {taskId}
308
-
356
+ 1. Follow the instructions in response.prompt EXACTLY
357
+ 2. The prompt tells you:
358
+ - The document to verify (already included in prompt)
359
+ - The requirements and template to check against
360
+ - The scoring criteria and pass conditions
361
+ - The exact JSON output format required
362
+ 3. Bash: npx spets orchestrate verify-done {taskId} '{verifyJson}'
309
363
  4. Parse response \u2192 next action
364
+ - If passed: goes to human review
365
+ - If failed (attempts < 3): goes back to draft phase (auto-fix)
366
+ - If failed (attempts >= 3): goes to human review with warning
310
367
  \`\`\`
311
368
 
312
- ### ACTION_PARALLEL_CONSOLIDATE
313
-
314
- **PARALLEL EXECUTION (FOREGROUND)**
315
-
316
- \`\`\`
317
- 1. FOR EACH group IN parallelGroups:
318
- Task(
319
- subagent_type: "general-purpose",
320
- prompt: "
321
- Merge children into parent. Return ONLY the path.
322
-
323
- Output: {group.outputPath}
324
- Children: {group.childPaths}
325
-
326
- 1. Read each child file
327
- 2. Merge preserving hierarchy
328
- 3. Write to output using Write tool
329
- 4. Return ONLY: {group.outputPath}
330
- (no explanations, no content, just the path)
331
- "
332
- )
333
-
334
- 2. ALL Task calls MUST be in SINGLE message (parallel execution)
335
-
336
- 3. Bash: npx spets orchestrate consolidate-level-done {taskId} {level}
337
-
338
- 4. Parse response \u2192 next action
339
- \`\`\`
340
-
341
- ### ACTION_ASK_QUESTIONS
369
+ ### ACTION_ASK_QUESTIONS
342
370
 
343
371
  \`\`\`
344
372
  1. AskUserQuestion(
@@ -397,13 +425,13 @@ Print: "Error: {error}" \u2192 STOP
397
425
  ## Orchestrator Commands Reference
398
426
 
399
427
  \`\`\`bash
428
+ # New 5-phase workflow
400
429
  npx spets orchestrate init "<description>"
401
- npx spets orchestrate context-done <taskId>
430
+ npx spets orchestrate explore-done <taskId> '<json>'
402
431
  npx spets orchestrate clarify-done <taskId> '<json>'
403
432
  npx spets orchestrate clarified <taskId> '<json>'
404
- npx spets orchestrate generate-done <taskId>
405
- npx spets orchestrate sections-done <taskId>
406
- npx spets orchestrate consolidate-level-done <taskId> <level>
433
+ npx spets orchestrate execute-done <taskId>
434
+ npx spets orchestrate verify-done <taskId> '<json>'
407
435
  npx spets orchestrate approve <taskId>
408
436
  npx spets orchestrate revise <taskId> "<feedback>"
409
437
  npx spets orchestrate reject <taskId>
@@ -416,15 +444,254 @@ $ARGUMENTS
416
444
  description: Task description for the workflow
417
445
  `;
418
446
  }
447
+ function getCodexSkillContent() {
448
+ return `---
449
+ name: spets
450
+ description: SDD workflow executor - runs spec-driven development workflows within Codex CLI
451
+ ---
452
+
453
+ # Spets Command Executor
454
+
455
+ Execute orchestrator commands. Parse JSON response and execute matching action.
456
+
457
+ ## Start
458
+
459
+ Run: npx spets orchestrate init "$ARGUMENTS"
460
+
461
+ Parse JSON response, then execute action from table below, loop until type: "complete" or "error".
462
+
463
+ ## Command Table
464
+
465
+ ### type: "phase"
466
+
467
+ | phase | Action |
468
+ |-------|--------|
469
+ | explore | Follow response.prompt to explore codebase |
470
+ | clarify | Follow response.prompt to generate questions |
471
+ | execute | Follow response.prompt to write document |
472
+ | verify | Follow response.prompt to validate document |
473
+
474
+ ### type: "checkpoint"
475
+
476
+ | checkpoint | Action |
477
+ |------------|--------|
478
+ | clarify | Ask user questions, collect answers |
479
+ | approve | Ask user to review document |
480
+
481
+ ### type: "complete"
482
+
483
+ Print completion status and outputs.
484
+
485
+ ### type: "error"
486
+
487
+ Print error and stop.
488
+
489
+ ## Orchestrator Commands
490
+
491
+ npx spets orchestrate init "<description>"
492
+ npx spets orchestrate explore-done <taskId> '<json>'
493
+ npx spets orchestrate clarify-done <taskId> '<json>'
494
+ npx spets orchestrate clarified <taskId> '<json>'
495
+ npx spets orchestrate execute-done <taskId>
496
+ npx spets orchestrate verify-done <taskId> '<json>'
497
+ npx spets orchestrate approve <taskId>
498
+ npx spets orchestrate revise <taskId> "<feedback>"
499
+ npx spets orchestrate reject <taskId>
500
+ npx spets orchestrate stop <taskId>
501
+
502
+ $ARGUMENTS
503
+ description: Task description for the workflow
504
+ `;
505
+ }
506
+ function getOpenCodeSkillContent() {
507
+ return `---
508
+ name: spets
509
+ description: SDD workflow executor - runs spec-driven development workflows within OpenCode
510
+ ---
511
+
512
+ # Spets Command Executor
513
+
514
+ Execute orchestrator commands. Parse JSON response and execute matching action.
515
+
516
+ ## Start
517
+
518
+ Run: npx spets orchestrate init "$ARGUMENTS"
519
+
520
+ Parse JSON response, then execute action from table below, loop until type: "complete" or "error".
521
+
522
+ ## Command Table
523
+
524
+ ### type: "phase"
525
+
526
+ | phase | Action |
527
+ |-------|--------|
528
+ | explore | Follow response.prompt to explore codebase |
529
+ | clarify | Follow response.prompt to generate questions |
530
+ | execute | Follow response.prompt to write document |
531
+ | verify | Follow response.prompt to validate document |
532
+
533
+ ### type: "checkpoint"
534
+
535
+ | checkpoint | Action |
536
+ |------------|--------|
537
+ | clarify | Ask user questions, collect answers |
538
+ | approve | Ask user to review document |
539
+
540
+ ### type: "complete"
541
+
542
+ Print completion status and outputs.
543
+
544
+ ### type: "error"
545
+
546
+ Print error and stop.
547
+
548
+ ## Orchestrator Commands
549
+
550
+ npx spets orchestrate init "<description>"
551
+ npx spets orchestrate explore-done <taskId> '<json>'
552
+ npx spets orchestrate clarify-done <taskId> '<json>'
553
+ npx spets orchestrate clarified <taskId> '<json>'
554
+ npx spets orchestrate execute-done <taskId>
555
+ npx spets orchestrate verify-done <taskId> '<json>'
556
+ npx spets orchestrate approve <taskId>
557
+ npx spets orchestrate revise <taskId> "<feedback>"
558
+ npx spets orchestrate reject <taskId>
559
+ npx spets orchestrate stop <taskId>
560
+
561
+ $ARGUMENTS
562
+ description: Task description for the workflow
563
+ `;
564
+ }
565
+
566
+ // src/commands/init.ts
567
+ var __dirname = dirname(fileURLToPath(import.meta.url));
568
+ function getGitHubInfoFromRemote() {
569
+ try {
570
+ const remoteUrl = execSync("git remote get-url origin", { encoding: "utf-8" }).trim();
571
+ const sshMatch = remoteUrl.match(/git@github\.com:([^/]+)\/(.+?)(?:\.git)?$/);
572
+ if (sshMatch) {
573
+ return { owner: sshMatch[1], repo: sshMatch[2] };
574
+ }
575
+ const httpsMatch = remoteUrl.match(/https:\/\/github\.com\/([^/]+)\/(.+?)(?:\.git)?$/);
576
+ if (httpsMatch) {
577
+ return { owner: httpsMatch[1], repo: httpsMatch[2] };
578
+ }
579
+ return null;
580
+ } catch {
581
+ return null;
582
+ }
583
+ }
584
+ async function initCommand(options) {
585
+ const cwd = process.cwd();
586
+ const spetsDir = getSpetsDir(cwd);
587
+ if (spetsExists(cwd) && !options.force) {
588
+ console.error("Spets already initialized. Use --force to overwrite.");
589
+ process.exit(1);
590
+ }
591
+ mkdirSync2(spetsDir, { recursive: true });
592
+ mkdirSync2(join3(spetsDir, "steps"), { recursive: true });
593
+ mkdirSync2(join3(spetsDir, "outputs"), { recursive: true });
594
+ mkdirSync2(join3(spetsDir, "hooks"), { recursive: true });
595
+ const templatesDir = join3(__dirname, "..", "templates");
596
+ const hookTemplate = join3(__dirname, "..", "templates", "hooks", "cleanup-branch.sh");
597
+ if (existsSync3(hookTemplate)) {
598
+ const hookDest = join3(spetsDir, "hooks", "cleanup-branch.sh");
599
+ cpSync(hookTemplate, hookDest);
600
+ try {
601
+ execSync(`chmod +x "${hookDest}"`);
602
+ } catch {
603
+ }
604
+ }
605
+ const githubInfo = getGitHubInfoFromRemote();
606
+ writeFileSync2(join3(spetsDir, "config.yml"), getDefaultConfig(githubInfo));
607
+ createDefaultSteps(spetsDir);
608
+ createClaudeCommand(cwd);
609
+ console.log("Initialized spets in .spets/");
610
+ console.log("");
611
+ console.log("Created:");
612
+ console.log(" .spets/config.yml - Workflow configuration");
613
+ console.log(" .spets/steps/01-plan/template.md - Planning step template");
614
+ console.log(" .spets/steps/02-implement/template.md - Implementation step template");
615
+ console.log(" .spets/hooks/cleanup-branch.sh - Example branch cleanup hook");
616
+ console.log(" .claude/commands/spets.md - Claude Code command");
617
+ if (options.github) {
618
+ createGitHubWorkflow(cwd);
619
+ console.log(" .github/workflows/spets.yml - GitHub Actions workflow");
620
+ console.log(" .github/ISSUE_TEMPLATE/spets-task.yml - Issue template");
621
+ }
622
+ console.log("");
623
+ console.log("Next steps:");
624
+ console.log(" 1. Edit .spets/config.yml to customize your workflow");
625
+ console.log(" 2. Customize step templates in .spets/steps/");
626
+ if (options.github) {
627
+ console.log(" 3. Add CLAUDE_CODE_OAUTH_TOKEN to your repo secrets");
628
+ console.log(' 4. Create a new Issue using the "Spets Task" template');
629
+ } else {
630
+ console.log(' 3. Run: spets start "your task description"');
631
+ }
632
+ }
633
+ function getDefaultConfig(githubInfo) {
634
+ const githubSection = githubInfo ? `
635
+ # GitHub integration (auto-detected from git remote)
636
+ github:
637
+ owner: ${githubInfo.owner}
638
+ repo: ${githubInfo.repo}
639
+ ` : `
640
+ # GitHub integration (uncomment and fill in to enable)
641
+ # github:
642
+ # owner: your-org
643
+ # repo: your-repo
644
+ `;
645
+ return `# Spets Configuration
646
+ # Define your workflow steps here
647
+
648
+ steps:
649
+ - 01-plan
650
+ - 02-implement
651
+
652
+ # Output configuration
653
+ output:
654
+ path: .spets/outputs
655
+ ${githubSection}
656
+ # Optional hooks (shell scripts)
657
+ # hooks:
658
+ # preStep: "./hooks/pre-step.sh"
659
+ # postStep: "./hooks/post-step.sh"
660
+ # onApprove: "./hooks/on-approve.sh"
661
+ # onReject: "./hooks/cleanup-branch.sh"
662
+ # onComplete: "./hooks/cleanup-branch.sh"
663
+ `;
664
+ }
665
+ function createDefaultSteps(spetsDir) {
666
+ const planDir = join3(spetsDir, "steps", "01-plan");
667
+ mkdirSync2(planDir, { recursive: true });
668
+ writeFileSync2(join3(planDir, "template.md"), getPlanTemplate());
669
+ const implementDir = join3(spetsDir, "steps", "02-implement");
670
+ mkdirSync2(implementDir, { recursive: true });
671
+ writeFileSync2(join3(implementDir, "template.md"), getImplementTemplate());
672
+ }
673
+ function getPlanTemplate() {
674
+ const fullTemplate = readFileSync2(join3(__dirname, "..", "templates", "steps", "01-plan", "template.md"), "utf-8");
675
+ return fullTemplate;
676
+ }
677
+ function getImplementTemplate() {
678
+ const fullTemplate = readFileSync2(join3(__dirname, "..", "templates", "steps", "02-implement", "template.md"), "utf-8");
679
+ return fullTemplate;
680
+ }
681
+ function createClaudeCommand(cwd) {
682
+ const commandDir = join3(cwd, ".claude", "commands");
683
+ mkdirSync2(commandDir, { recursive: true });
684
+ writeFileSync2(join3(commandDir, "spets.md"), getClaudeSkillContent());
685
+ }
419
686
  function createGitHubWorkflow(cwd) {
420
- const workflowDir = join2(cwd, ".github", "workflows");
421
- const templateDir = join2(cwd, ".github", "ISSUE_TEMPLATE");
422
- mkdirSync(workflowDir, { recursive: true });
423
- mkdirSync(templateDir, { recursive: true });
424
- const workflowTemplate = readFileSync2(join2(__dirname, "..", "assets", "github", "workflows", "spets.yml"), "utf-8");
425
- const issueTemplate = readFileSync2(join2(__dirname, "..", "assets", "github", "ISSUE_TEMPLATE", "spets-task.yml"), "utf-8");
426
- writeFileSync(join2(workflowDir, "spets.yml"), workflowTemplate);
427
- writeFileSync(join2(templateDir, "spets-task.yml"), issueTemplate);
687
+ const workflowDir = join3(cwd, ".github", "workflows");
688
+ const templateDir = join3(cwd, ".github", "ISSUE_TEMPLATE");
689
+ mkdirSync2(workflowDir, { recursive: true });
690
+ mkdirSync2(templateDir, { recursive: true });
691
+ const workflowTemplate = readFileSync2(join3(__dirname, "..", "assets", "github", "workflows", "spets.yml"), "utf-8");
692
+ const issueTemplate = readFileSync2(join3(__dirname, "..", "assets", "github", "ISSUE_TEMPLATE", "spets-task.yml"), "utf-8");
693
+ writeFileSync2(join3(workflowDir, "spets.yml"), workflowTemplate);
694
+ writeFileSync2(join3(templateDir, "spets-task.yml"), issueTemplate);
428
695
  createGitHubLabel();
429
696
  }
430
697
  function createGitHubLabel() {
@@ -443,8 +710,8 @@ function createGitHubLabel() {
443
710
  }
444
711
 
445
712
  // src/core/state.ts
446
- import { readFileSync as readFileSync3, existsSync as existsSync3, readdirSync, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
447
- import { join as join3 } from "path";
713
+ import { readFileSync as readFileSync3, existsSync as existsSync4, readdirSync as readdirSync2, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
714
+ import { join as join4 } from "path";
448
715
  import matter from "gray-matter";
449
716
 
450
717
  // src/core/slug.ts
@@ -491,14 +758,14 @@ function generateTaskId(description) {
491
758
  return `${timestamp}-${random}`;
492
759
  }
493
760
  function getTaskDir(taskId, cwd = process.cwd()) {
494
- return join3(getOutputsDir(cwd), taskId);
761
+ return join4(getOutputsDir(cwd), taskId);
495
762
  }
496
763
  function getOutputPath(taskId, stepName, cwd = process.cwd()) {
497
- return join3(getTaskDir(taskId, cwd), `${stepName}.md`);
764
+ return join4(getTaskDir(taskId, cwd), `${stepName}.md`);
498
765
  }
499
766
  function loadDocument(taskId, stepName, cwd = process.cwd()) {
500
767
  const outputPath = getOutputPath(taskId, stepName, cwd);
501
- if (!existsSync3(outputPath)) {
768
+ if (!existsSync4(outputPath)) {
502
769
  return null;
503
770
  }
504
771
  const raw = readFileSync3(outputPath, "utf-8");
@@ -510,17 +777,17 @@ function loadDocument(taskId, stepName, cwd = process.cwd()) {
510
777
  }
511
778
  function listTasks(cwd = process.cwd()) {
512
779
  const outputsDir = getOutputsDir(cwd);
513
- if (!existsSync3(outputsDir)) {
780
+ if (!existsSync4(outputsDir)) {
514
781
  return [];
515
782
  }
516
- return readdirSync(outputsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort().reverse();
783
+ return readdirSync2(outputsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort().reverse();
517
784
  }
518
785
  function getStateCachePath(taskId, cwd = process.cwd()) {
519
- return join3(getTaskDir(taskId, cwd), ".state-cache.json");
786
+ return join4(getTaskDir(taskId, cwd), ".state-cache.json");
520
787
  }
521
788
  function loadStateCache(taskId, cwd = process.cwd()) {
522
789
  const cachePath = getStateCachePath(taskId, cwd);
523
- if (!existsSync3(cachePath)) {
790
+ if (!existsSync4(cachePath)) {
524
791
  return null;
525
792
  }
526
793
  try {
@@ -546,7 +813,7 @@ function saveStateCache(taskId, state, cwd = process.cwd()) {
546
813
  stepStatuses
547
814
  };
548
815
  try {
549
- writeFileSync2(cachePath, JSON.stringify(cached, null, 2));
816
+ writeFileSync3(cachePath, JSON.stringify(cached, null, 2));
550
817
  } catch {
551
818
  }
552
819
  }
@@ -556,7 +823,7 @@ function isStateCacheValid(cached, maxAgeMs = 5e3) {
556
823
  }
557
824
  function getWorkflowState(taskId, config, cwd = process.cwd()) {
558
825
  const taskDir = getTaskDir(taskId, cwd);
559
- if (!existsSync3(taskDir)) {
826
+ if (!existsSync4(taskDir)) {
560
827
  return null;
561
828
  }
562
829
  const cached = loadStateCache(taskId, cwd);
@@ -623,8 +890,8 @@ function getWorkflowState(taskId, config, cwd = process.cwd()) {
623
890
  return state;
624
891
  }
625
892
  function loadTaskMetadata(taskId, cwd = process.cwd()) {
626
- const metaPath = join3(getTaskDir(taskId, cwd), ".meta.json");
627
- if (!existsSync3(metaPath)) {
893
+ const metaPath = join4(getTaskDir(taskId, cwd), ".meta.json");
894
+ if (!existsSync4(metaPath)) {
628
895
  return null;
629
896
  }
630
897
  return JSON.parse(readFileSync3(metaPath, "utf-8"));
@@ -715,13 +982,13 @@ function formatDocStatus(status) {
715
982
  import { execSync as execSync2 } from "child_process";
716
983
 
717
984
  // src/orchestrator/index.ts
718
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
719
- import { join as join5, dirname as dirname2 } from "path";
985
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
986
+ import { join as join6, dirname as dirname2 } from "path";
720
987
  import matter2 from "gray-matter";
721
988
 
722
989
  // src/core/prompt-builder.ts
723
- import { readFileSync as readFileSync4, existsSync as existsSync4 } from "fs";
724
- import { join as join4 } from "path";
990
+ import { readFileSync as readFileSync4, existsSync as existsSync5 } from "fs";
991
+ import { join as join5 } from "path";
725
992
  function buildContextPrompt(params) {
726
993
  const cwd = params.cwd || process.cwd();
727
994
  const isFirstStep = params.stepIndex === 1;
@@ -744,7 +1011,7 @@ function buildContextPrompt(params) {
744
1011
  } else if (params.previousOutput) {
745
1012
  parts.push("## Previous Step Output");
746
1013
  parts.push("");
747
- if (existsSync4(params.previousOutput)) {
1014
+ if (existsSync5(params.previousOutput)) {
748
1015
  parts.push(readFileSync4(params.previousOutput, "utf-8"));
749
1016
  }
750
1017
  parts.push("");
@@ -796,7 +1063,7 @@ function buildExplorePrompt(params) {
796
1063
  } else if (params.previousOutput) {
797
1064
  parts.push("## Previous Step Output");
798
1065
  parts.push("");
799
- if (existsSync4(params.previousOutput)) {
1066
+ if (existsSync5(params.previousOutput)) {
800
1067
  parts.push(readFileSync4(params.previousOutput, "utf-8"));
801
1068
  }
802
1069
  parts.push("");
@@ -935,13 +1202,13 @@ function buildGeneratePrompt(params) {
935
1202
  const outputsDir = getOutputsDir(cwd);
936
1203
  const isFirstStep = params.stepIndex === 1;
937
1204
  const prevStep = params.stepIndex > 1 ? config.steps[params.stepIndex - 2] : null;
938
- const templatePath = join4(stepsDir, params.step, "template.md");
939
- const outputPath = join4(outputsDir, params.taskId, `${params.step}.md`);
940
- const template = existsSync4(templatePath) ? readFileSync4(templatePath, "utf-8") : null;
1205
+ const templatePath = join5(stepsDir, params.step, "template.md");
1206
+ const outputPath = join5(outputsDir, params.taskId, `${params.step}.md`);
1207
+ const template = existsSync5(templatePath) ? readFileSync4(templatePath, "utf-8") : null;
941
1208
  let previousSpec = null;
942
1209
  if (prevStep) {
943
- const prevPath = join4(outputsDir, params.taskId, `${prevStep}.md`);
944
- if (existsSync4(prevPath)) {
1210
+ const prevPath = join5(outputsDir, params.taskId, `${prevStep}.md`);
1211
+ if (existsSync5(prevPath)) {
945
1212
  previousSpec = {
946
1213
  step: prevStep,
947
1214
  content: readFileSync4(prevPath, "utf-8")
@@ -1022,13 +1289,13 @@ function buildExecutePrompt(params) {
1022
1289
  const outputsDir = getOutputsDir(cwd);
1023
1290
  const isFirstStep = params.stepIndex === 1;
1024
1291
  const prevStep = params.stepIndex > 1 ? config.steps[params.stepIndex - 2] : null;
1025
- const templatePath = join4(stepsDir, params.step, "template.md");
1026
- const outputPath = join4(outputsDir, params.taskId, `${params.step}.md`);
1027
- const template = existsSync4(templatePath) ? readFileSync4(templatePath, "utf-8") : null;
1292
+ const templatePath = join5(stepsDir, params.step, "template.md");
1293
+ const outputPath = join5(outputsDir, params.taskId, `${params.step}.md`);
1294
+ const template = existsSync5(templatePath) ? readFileSync4(templatePath, "utf-8") : null;
1028
1295
  let previousSpec = null;
1029
1296
  if (prevStep) {
1030
- const prevPath = join4(outputsDir, params.taskId, `${prevStep}.md`);
1031
- if (existsSync4(prevPath)) {
1297
+ const prevPath = join5(outputsDir, params.taskId, `${prevStep}.md`);
1298
+ if (existsSync5(prevPath)) {
1032
1299
  previousSpec = {
1033
1300
  step: prevStep,
1034
1301
  content: readFileSync4(prevPath, "utf-8")
@@ -1147,9 +1414,9 @@ function buildExecutePrompt(params) {
1147
1414
  function buildVerifyPrompt(params) {
1148
1415
  const cwd = params.cwd || process.cwd();
1149
1416
  const stepsDir = getStepsDir(cwd);
1150
- const templatePath = join4(stepsDir, params.step, "template.md");
1151
- const template = existsSync4(templatePath) ? readFileSync4(templatePath, "utf-8") : "";
1152
- const document = existsSync4(params.documentPath) ? readFileSync4(params.documentPath, "utf-8") : "";
1417
+ const templatePath = join5(stepsDir, params.step, "template.md");
1418
+ const template = existsSync5(templatePath) ? readFileSync4(templatePath, "utf-8") : "";
1419
+ const document = existsSync5(params.documentPath) ? readFileSync4(params.documentPath, "utf-8") : "";
1153
1420
  const parts = [];
1154
1421
  parts.push("# Verify Phase - Self-Validation");
1155
1422
  parts.push("");
@@ -1253,20 +1520,20 @@ var Orchestrator = class {
1253
1520
  return getOutputsDir(this.cwd);
1254
1521
  }
1255
1522
  getStatePath(taskId) {
1256
- return join5(this.getOutputPath(), taskId, ".state.json");
1523
+ return join6(this.getOutputPath(), taskId, ".state.json");
1257
1524
  }
1258
1525
  getSpecPath(taskId, step) {
1259
- return join5(this.getOutputPath(), taskId, `${step}.md`);
1526
+ return join6(this.getOutputPath(), taskId, `${step}.md`);
1260
1527
  }
1261
1528
  getStepTemplatePath(step) {
1262
- return join5(getStepsDir(this.cwd), step, "template.md");
1529
+ return join6(getStepsDir(this.cwd), step, "template.md");
1263
1530
  }
1264
1531
  // ===========================================================================
1265
1532
  // State Management
1266
1533
  // ===========================================================================
1267
1534
  loadState(taskId) {
1268
1535
  const statePath = this.getStatePath(taskId);
1269
- if (!existsSync5(statePath)) {
1536
+ if (!existsSync6(statePath)) {
1270
1537
  return null;
1271
1538
  }
1272
1539
  const data = JSON.parse(readFileSync5(statePath, "utf-8"));
@@ -1276,16 +1543,16 @@ var Orchestrator = class {
1276
1543
  state.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1277
1544
  const statePath = this.getStatePath(state.taskId);
1278
1545
  const dir = dirname2(statePath);
1279
- if (!existsSync5(dir)) {
1280
- mkdirSync3(dir, { recursive: true });
1546
+ if (!existsSync6(dir)) {
1547
+ mkdirSync4(dir, { recursive: true });
1281
1548
  }
1282
- writeFileSync3(statePath, JSON.stringify(state, null, 2));
1549
+ writeFileSync4(statePath, JSON.stringify(state, null, 2));
1283
1550
  }
1284
1551
  // ===========================================================================
1285
1552
  // Spec Helpers
1286
1553
  // ===========================================================================
1287
1554
  checkUnresolvedQuestions(specPath) {
1288
- if (!existsSync5(specPath)) {
1555
+ if (!existsSync6(specPath)) {
1289
1556
  return [];
1290
1557
  }
1291
1558
  const content = readFileSync5(specPath, "utf-8");
@@ -1320,8 +1587,8 @@ var Orchestrator = class {
1320
1587
  let previousOutput;
1321
1588
  if (state.stepIndex > 1) {
1322
1589
  const prevStep = steps[state.stepIndex - 2];
1323
- const prevPath = join5(outputPath, state.taskId, `${prevStep}.md`);
1324
- if (existsSync5(prevPath)) {
1590
+ const prevPath = join6(outputPath, state.taskId, `${prevStep}.md`);
1591
+ if (existsSync6(prevPath)) {
1325
1592
  previousOutput = prevPath;
1326
1593
  }
1327
1594
  }
@@ -1348,8 +1615,8 @@ var Orchestrator = class {
1348
1615
  let previousOutput;
1349
1616
  if (state.stepIndex > 1) {
1350
1617
  const prevStep = steps[state.stepIndex - 2];
1351
- const prevPath = join5(outputPath, state.taskId, `${prevStep}.md`);
1352
- if (existsSync5(prevPath)) {
1618
+ const prevPath = join6(outputPath, state.taskId, `${prevStep}.md`);
1619
+ if (existsSync6(prevPath)) {
1353
1620
  previousOutput = prevPath;
1354
1621
  }
1355
1622
  }
@@ -1411,13 +1678,13 @@ var Orchestrator = class {
1411
1678
  let previousOutput;
1412
1679
  if (state.stepIndex > 1) {
1413
1680
  const prevStep = steps[state.stepIndex - 2];
1414
- const prevPath = join5(outputPath, state.taskId, `${prevStep}.md`);
1415
- if (existsSync5(prevPath)) {
1681
+ const prevPath = join6(outputPath, state.taskId, `${prevStep}.md`);
1682
+ if (existsSync6(prevPath)) {
1416
1683
  previousOutput = prevPath;
1417
1684
  }
1418
1685
  }
1419
1686
  const templatePath = this.getStepTemplatePath(state.currentStep);
1420
- const hasTemplate = existsSync5(templatePath);
1687
+ const hasTemplate = existsSync6(templatePath);
1421
1688
  return {
1422
1689
  type: "phase",
1423
1690
  phase: "generate",
@@ -1431,7 +1698,7 @@ var Orchestrator = class {
1431
1698
  context: {
1432
1699
  template: hasTemplate ? templatePath : void 0,
1433
1700
  previousOutput,
1434
- output: join5(outputPath, state.taskId, `${state.currentStep}.md`),
1701
+ output: join6(outputPath, state.taskId, `${state.currentStep}.md`),
1435
1702
  revisionFeedback: state.revisionFeedback
1436
1703
  },
1437
1704
  onComplete: `generate-done ${state.taskId}`
@@ -1446,13 +1713,13 @@ var Orchestrator = class {
1446
1713
  let previousOutput;
1447
1714
  if (state.stepIndex > 1) {
1448
1715
  const prevStep = steps[state.stepIndex - 2];
1449
- const prevPath = join5(outputPath, state.taskId, `${prevStep}.md`);
1450
- if (existsSync5(prevPath)) {
1716
+ const prevPath = join6(outputPath, state.taskId, `${prevStep}.md`);
1717
+ if (existsSync6(prevPath)) {
1451
1718
  previousOutput = prevPath;
1452
1719
  }
1453
1720
  }
1454
1721
  const templatePath = this.getStepTemplatePath(state.currentStep);
1455
- const hasTemplate = existsSync5(templatePath);
1722
+ const hasTemplate = existsSync6(templatePath);
1456
1723
  let verifyFeedback;
1457
1724
  if (state.verifyOutput && !state.verifyOutput.passed && state.verifyAttempts && state.verifyAttempts > 1) {
1458
1725
  const issues = state.verifyOutput.issues.filter((i) => i.severity === "error").map((i) => `- [${i.category}] ${i.description}${i.suggestion ? ` \u2192 ${i.suggestion}` : ""}`).join("\n");
@@ -1492,7 +1759,7 @@ ${issues}`;
1492
1759
  context: {
1493
1760
  template: hasTemplate ? templatePath : void 0,
1494
1761
  previousOutput,
1495
- output: join5(outputPath, state.taskId, `${state.currentStep}.md`),
1762
+ output: join6(outputPath, state.taskId, `${state.currentStep}.md`),
1496
1763
  revisionFeedback: state.revisionFeedback,
1497
1764
  verifyFeedback
1498
1765
  },
@@ -1505,8 +1772,8 @@ ${issues}`;
1505
1772
  responsePhaseVerify(state) {
1506
1773
  const outputPath = this.getOutputPath();
1507
1774
  const templatePath = this.getStepTemplatePath(state.currentStep);
1508
- const hasTemplate = existsSync5(templatePath);
1509
- const documentPath = join5(outputPath, state.taskId, `${state.currentStep}.md`);
1775
+ const hasTemplate = existsSync6(templatePath);
1776
+ const documentPath = join6(outputPath, state.taskId, `${state.currentStep}.md`);
1510
1777
  const prompt = buildVerifyPrompt({
1511
1778
  taskId: state.taskId,
1512
1779
  step: state.currentStep,
@@ -1560,7 +1827,7 @@ ${issues}`;
1560
1827
  step: state.currentStep,
1561
1828
  stepIndex: state.stepIndex,
1562
1829
  totalSteps: state.totalSteps,
1563
- specPath: join5(outputPath, state.taskId, `${state.currentStep}.md`),
1830
+ specPath: join6(outputPath, state.taskId, `${state.currentStep}.md`),
1564
1831
  options: ["approve", "revise", "reject", "stop"],
1565
1832
  onComplete: {
1566
1833
  approve: `approve ${state.taskId}`,
@@ -1575,8 +1842,8 @@ ${issues}`;
1575
1842
  const outputPath = this.getOutputPath();
1576
1843
  const outputs = [];
1577
1844
  for (let i = 0; i < state.stepIndex; i++) {
1578
- const specPath = join5(outputPath, state.taskId, `${steps[i]}.md`);
1579
- if (existsSync5(specPath)) {
1845
+ const specPath = join6(outputPath, state.taskId, `${steps[i]}.md`);
1846
+ if (existsSync6(specPath)) {
1580
1847
  outputs.push(specPath);
1581
1848
  }
1582
1849
  }
@@ -1744,7 +2011,7 @@ ${issues}`;
1744
2011
  return this.responseError(`No workflow found: ${taskId}`, taskId);
1745
2012
  }
1746
2013
  const specPath = this.getSpecPath(taskId, state.currentStep);
1747
- if (!existsSync5(specPath)) {
2014
+ if (!existsSync6(specPath)) {
1748
2015
  return this.responseError(`Document not found: ${specPath}`, taskId, state.currentStep);
1749
2016
  }
1750
2017
  state.status = "approve_pending";
@@ -1761,7 +2028,7 @@ ${issues}`;
1761
2028
  return this.responseError(`No workflow found: ${taskId}`, taskId);
1762
2029
  }
1763
2030
  const specPath = this.getSpecPath(taskId, state.currentStep);
1764
- if (!existsSync5(specPath)) {
2031
+ if (!existsSync6(specPath)) {
1765
2032
  return this.responseError(`Document not found: ${specPath}`, taskId, state.currentStep);
1766
2033
  }
1767
2034
  state.status = "phase_verify";
@@ -1816,7 +2083,7 @@ ${issues}`;
1816
2083
  return this.responseError(`No workflow found: ${taskId}`, taskId);
1817
2084
  }
1818
2085
  const specPath = this.getSpecPath(taskId, state.currentStep);
1819
- if (!existsSync5(specPath)) {
2086
+ if (!existsSync6(specPath)) {
1820
2087
  return this.responseError(`Spec not found: ${specPath}`, taskId, state.currentStep);
1821
2088
  }
1822
2089
  const questions = this.checkUnresolvedQuestions(specPath);
@@ -1842,12 +2109,12 @@ ${issues}`;
1842
2109
  }
1843
2110
  const steps = this.getSteps();
1844
2111
  const specPath = this.getSpecPath(taskId, state.currentStep);
1845
- if (existsSync5(specPath)) {
2112
+ if (existsSync6(specPath)) {
1846
2113
  const content = readFileSync5(specPath, "utf-8");
1847
2114
  const { content: body, data } = matter2(content);
1848
2115
  data.status = "approved";
1849
2116
  data.updated_at = (/* @__PURE__ */ new Date()).toISOString();
1850
- writeFileSync3(specPath, matter2.stringify(body, data));
2117
+ writeFileSync4(specPath, matter2.stringify(body, data));
1851
2118
  }
1852
2119
  if (state.stepIndex < state.totalSteps) {
1853
2120
  state.currentStep = steps[state.stepIndex];
@@ -1895,12 +2162,12 @@ ${issues}`;
1895
2162
  return this.responseError(`No workflow found: ${taskId}`, taskId);
1896
2163
  }
1897
2164
  const specPath = this.getSpecPath(taskId, state.currentStep);
1898
- if (existsSync5(specPath)) {
2165
+ if (existsSync6(specPath)) {
1899
2166
  const content = readFileSync5(specPath, "utf-8");
1900
2167
  const { content: body, data } = matter2(content);
1901
2168
  data.status = "rejected";
1902
2169
  data.updated_at = (/* @__PURE__ */ new Date()).toISOString();
1903
- writeFileSync3(specPath, matter2.stringify(body, data));
2170
+ writeFileSync4(specPath, matter2.stringify(body, data));
1904
2171
  }
1905
2172
  state.status = "rejected";
1906
2173
  this.saveState(state);
@@ -2000,8 +2267,8 @@ ${issues}`;
2000
2267
  };
2001
2268
 
2002
2269
  // src/core/step-executor.ts
2003
- import { existsSync as existsSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
2004
- import { join as join6 } from "path";
2270
+ import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
2271
+ import { join as join7 } from "path";
2005
2272
  import matter3 from "gray-matter";
2006
2273
  var StepExecutor = class {
2007
2274
  adapter;
@@ -2149,7 +2416,7 @@ var StepExecutor = class {
2149
2416
  };
2150
2417
  const { prompt, outputPath } = buildGeneratePrompt(params);
2151
2418
  await this.adapter.ai.execute({ prompt, outputPath });
2152
- if (!existsSync6(outputPath)) {
2419
+ if (!existsSync7(outputPath)) {
2153
2420
  throw new Error(`AI did not create document at ${outputPath}`);
2154
2421
  }
2155
2422
  this.adapter.io.notify(`Document created: ${outputPath}`, "success");
@@ -2188,7 +2455,7 @@ var StepExecutor = class {
2188
2455
  };
2189
2456
  const { prompt, outputPath } = buildExecutePrompt(params);
2190
2457
  await this.adapter.ai.execute({ prompt, outputPath });
2191
- if (!existsSync6(outputPath)) {
2458
+ if (!existsSync7(outputPath)) {
2192
2459
  throw new Error(`AI did not create document at ${outputPath}`);
2193
2460
  }
2194
2461
  this.adapter.io.notify(`Document created: ${outputPath}`, "success");
@@ -2210,8 +2477,8 @@ var StepExecutor = class {
2210
2477
  const attempts = context.verifyAttempts || 1;
2211
2478
  this.adapter.io.notify(`Phase 4/5: Verifying document for ${step} (attempt ${attempts}/3)`, "info");
2212
2479
  const outputsDir = getOutputsDir(this.cwd);
2213
- const documentPath = join6(outputsDir, context.taskId, `${step}.md`);
2214
- if (!existsSync6(documentPath)) {
2480
+ const documentPath = join7(outputsDir, context.taskId, `${step}.md`);
2481
+ if (!existsSync7(documentPath)) {
2215
2482
  throw new Error(`Document not found: ${documentPath}`);
2216
2483
  }
2217
2484
  const params = {
@@ -2252,8 +2519,8 @@ var StepExecutor = class {
2252
2519
  async executeReviewPhase(step, context) {
2253
2520
  this.adapter.io.notify(`Phase 4/4: Review for ${step}`, "info");
2254
2521
  const outputsDir = getOutputsDir(this.cwd);
2255
- const outputPath = join6(outputsDir, context.taskId, `${step}.md`);
2256
- if (!existsSync6(outputPath)) {
2522
+ const outputPath = join7(outputsDir, context.taskId, `${step}.md`);
2523
+ if (!existsSync7(outputPath)) {
2257
2524
  throw new Error(`Document not found: ${outputPath}`);
2258
2525
  }
2259
2526
  const approval = await this.adapter.io.approve(
@@ -2439,13 +2706,13 @@ var StepExecutor = class {
2439
2706
  const { content: body, data } = matter3(content);
2440
2707
  data.status = status;
2441
2708
  data.updated_at = (/* @__PURE__ */ new Date()).toISOString();
2442
- writeFileSync4(docPath, matter3.stringify(body, data));
2709
+ writeFileSync5(docPath, matter3.stringify(body, data));
2443
2710
  }
2444
2711
  };
2445
2712
 
2446
2713
  // src/adapters/cli.ts
2447
2714
  import { spawn, spawnSync } from "child_process";
2448
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
2715
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync6, existsSync as existsSync8, mkdirSync as mkdirSync5 } from "fs";
2449
2716
  import { dirname as dirname3 } from "path";
2450
2717
  import { input, select, confirm } from "@inquirer/prompts";
2451
2718
  var CLIAIAdapter = class {
@@ -2458,12 +2725,12 @@ var CLIAIAdapter = class {
2458
2725
  \u{1F4DD} Generating...`);
2459
2726
  if (params.outputPath) {
2460
2727
  const outputDir = dirname3(params.outputPath);
2461
- if (!existsSync7(outputDir)) {
2462
- mkdirSync4(outputDir, { recursive: true });
2728
+ if (!existsSync8(outputDir)) {
2729
+ mkdirSync5(outputDir, { recursive: true });
2463
2730
  }
2464
2731
  }
2465
2732
  const stdout = await this.callClaude(params.prompt);
2466
- if (params.outputPath && existsSync7(params.outputPath)) {
2733
+ if (params.outputPath && existsSync8(params.outputPath)) {
2467
2734
  return readFileSync7(params.outputPath, "utf-8");
2468
2735
  }
2469
2736
  return stdout;
@@ -2541,13 +2808,13 @@ var CLIAIAdapter = class {
2541
2808
  try {
2542
2809
  if (p.outputPath) {
2543
2810
  const outputDir = dirname3(p.outputPath);
2544
- if (!existsSync7(outputDir)) {
2545
- mkdirSync4(outputDir, { recursive: true });
2811
+ if (!existsSync8(outputDir)) {
2812
+ mkdirSync5(outputDir, { recursive: true });
2546
2813
  }
2547
2814
  }
2548
2815
  await this.callClaude(p.prompt, false);
2549
2816
  let result = "";
2550
- if (p.outputPath && existsSync7(p.outputPath)) {
2817
+ if (p.outputPath && existsSync8(p.outputPath)) {
2551
2818
  result = readFileSync7(p.outputPath, "utf-8");
2552
2819
  }
2553
2820
  completed++;
@@ -2598,7 +2865,7 @@ Context: ${question.context}`);
2598
2865
  return answers;
2599
2866
  }
2600
2867
  async approve(specPath, stepName, stepIndex, totalSteps) {
2601
- if (existsSync7(specPath)) {
2868
+ if (existsSync8(specPath)) {
2602
2869
  const doc = readFileSync7(specPath, "utf-8");
2603
2870
  console.log("\n" + "=".repeat(60));
2604
2871
  console.log(`\u{1F4C4} ${stepName} Document (Step ${stepIndex}/${totalSteps})`);
@@ -2648,13 +2915,13 @@ var CLISystemAdapter = class {
2648
2915
  }
2649
2916
  writeFile(path, content) {
2650
2917
  const dir = dirname3(path);
2651
- if (!existsSync7(dir)) {
2652
- mkdirSync4(dir, { recursive: true });
2918
+ if (!existsSync8(dir)) {
2919
+ mkdirSync5(dir, { recursive: true });
2653
2920
  }
2654
- writeFileSync5(path, content);
2921
+ writeFileSync6(path, content);
2655
2922
  }
2656
2923
  fileExists(path) {
2657
- return existsSync7(path);
2924
+ return existsSync8(path);
2658
2925
  }
2659
2926
  exec(command) {
2660
2927
  try {
@@ -2685,7 +2952,7 @@ function createCLIAdapter(claudeCommand = "claude") {
2685
2952
 
2686
2953
  // src/adapters/github.ts
2687
2954
  import { spawn as spawn2, spawnSync as spawnSync2 } from "child_process";
2688
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync8, mkdirSync as mkdirSync5 } from "fs";
2955
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync7, existsSync as existsSync9, mkdirSync as mkdirSync6 } from "fs";
2689
2956
  import { dirname as dirname4 } from "path";
2690
2957
  var GitHubAIAdapter = class {
2691
2958
  claudeCommand;
@@ -2697,12 +2964,12 @@ var GitHubAIAdapter = class {
2697
2964
  \u{1F4DD} Generating...`);
2698
2965
  if (params.outputPath) {
2699
2966
  const outputDir = dirname4(params.outputPath);
2700
- if (!existsSync8(outputDir)) {
2701
- mkdirSync5(outputDir, { recursive: true });
2967
+ if (!existsSync9(outputDir)) {
2968
+ mkdirSync6(outputDir, { recursive: true });
2702
2969
  }
2703
2970
  }
2704
2971
  const stdout = await this.callClaude(params.prompt);
2705
- if (params.outputPath && existsSync8(params.outputPath)) {
2972
+ if (params.outputPath && existsSync9(params.outputPath)) {
2706
2973
  return readFileSync8(params.outputPath, "utf-8");
2707
2974
  }
2708
2975
  return stdout;
@@ -2755,13 +3022,13 @@ var GitHubAIAdapter = class {
2755
3022
  try {
2756
3023
  if (p.outputPath) {
2757
3024
  const outputDir = dirname4(p.outputPath);
2758
- if (!existsSync8(outputDir)) {
2759
- mkdirSync5(outputDir, { recursive: true });
3025
+ if (!existsSync9(outputDir)) {
3026
+ mkdirSync6(outputDir, { recursive: true });
2760
3027
  }
2761
3028
  }
2762
3029
  await this.callClaude(p.prompt);
2763
3030
  let result = "";
2764
- if (p.outputPath && existsSync8(p.outputPath)) {
3031
+ if (p.outputPath && existsSync9(p.outputPath)) {
2765
3032
  result = readFileSync8(p.outputPath, "utf-8");
2766
3033
  }
2767
3034
  completed++;
@@ -2807,7 +3074,7 @@ var GitHubIOAdapter = class {
2807
3074
  return [];
2808
3075
  }
2809
3076
  async approve(specPath, stepName, stepIndex, totalSteps) {
2810
- const doc = existsSync8(specPath) ? readFileSync8(specPath, "utf-8") : "";
3077
+ const doc = existsSync9(specPath) ? readFileSync8(specPath, "utf-8") : "";
2811
3078
  const comment = this.formatApprovalComment(doc, stepName, stepIndex, totalSteps, specPath);
2812
3079
  await this.postComment(comment);
2813
3080
  console.log("\n\u23F8\uFE0F Approval request posted to GitHub.");
@@ -2930,13 +3197,13 @@ var GitHubSystemAdapter = class {
2930
3197
  }
2931
3198
  writeFile(path, content) {
2932
3199
  const dir = dirname4(path);
2933
- if (!existsSync8(dir)) {
2934
- mkdirSync5(dir, { recursive: true });
3200
+ if (!existsSync9(dir)) {
3201
+ mkdirSync6(dir, { recursive: true });
2935
3202
  }
2936
- writeFileSync6(path, content);
3203
+ writeFileSync7(path, content);
2937
3204
  }
2938
3205
  fileExists(path) {
2939
- return existsSync8(path);
3206
+ return existsSync9(path);
2940
3207
  }
2941
3208
  exec(command) {
2942
3209
  try {
@@ -2967,7 +3234,7 @@ function createGitHubAdapter(config, claudeCommand = "claude") {
2967
3234
 
2968
3235
  // src/adapters/codex.ts
2969
3236
  import { spawn as spawn3 } from "child_process";
2970
- import { readFileSync as readFileSync9, existsSync as existsSync9, mkdirSync as mkdirSync6 } from "fs";
3237
+ import { readFileSync as readFileSync9, existsSync as existsSync10, mkdirSync as mkdirSync7 } from "fs";
2971
3238
  import { dirname as dirname5 } from "path";
2972
3239
  var CodexAIAdapter = class {
2973
3240
  codexCommand;
@@ -2979,12 +3246,12 @@ var CodexAIAdapter = class {
2979
3246
  \u{1F4DD} Generating...`);
2980
3247
  if (params.outputPath) {
2981
3248
  const outputDir = dirname5(params.outputPath);
2982
- if (!existsSync9(outputDir)) {
2983
- mkdirSync6(outputDir, { recursive: true });
3249
+ if (!existsSync10(outputDir)) {
3250
+ mkdirSync7(outputDir, { recursive: true });
2984
3251
  }
2985
3252
  }
2986
3253
  const stdout = await this.callCodex(params.prompt);
2987
- if (params.outputPath && existsSync9(params.outputPath)) {
3254
+ if (params.outputPath && existsSync10(params.outputPath)) {
2988
3255
  return readFileSync9(params.outputPath, "utf-8");
2989
3256
  }
2990
3257
  return stdout;
@@ -3062,13 +3329,13 @@ var CodexAIAdapter = class {
3062
3329
  try {
3063
3330
  if (p.outputPath) {
3064
3331
  const outputDir = dirname5(p.outputPath);
3065
- if (!existsSync9(outputDir)) {
3066
- mkdirSync6(outputDir, { recursive: true });
3332
+ if (!existsSync10(outputDir)) {
3333
+ mkdirSync7(outputDir, { recursive: true });
3067
3334
  }
3068
3335
  }
3069
3336
  await this.callCodex(p.prompt, false);
3070
3337
  let result = "";
3071
- if (p.outputPath && existsSync9(p.outputPath)) {
3338
+ if (p.outputPath && existsSync10(p.outputPath)) {
3072
3339
  result = readFileSync9(p.outputPath, "utf-8");
3073
3340
  }
3074
3341
  completed++;
@@ -3095,6 +3362,135 @@ function createCodexAdapter(codexCommand = "codex") {
3095
3362
  };
3096
3363
  }
3097
3364
 
3365
+ // src/adapters/opencode.ts
3366
+ import { spawn as spawn4 } from "child_process";
3367
+ import { readFileSync as readFileSync10, existsSync as existsSync11, mkdirSync as mkdirSync8 } from "fs";
3368
+ import { dirname as dirname6 } from "path";
3369
+ var OpenCodeAIAdapter = class {
3370
+ opencodeCommand;
3371
+ constructor(opencodeCommand = "opencode") {
3372
+ this.opencodeCommand = opencodeCommand;
3373
+ }
3374
+ async execute(params) {
3375
+ console.log(`
3376
+ \u{1F4DD} Generating...`);
3377
+ if (params.outputPath) {
3378
+ const outputDir = dirname6(params.outputPath);
3379
+ if (!existsSync11(outputDir)) {
3380
+ mkdirSync8(outputDir, { recursive: true });
3381
+ }
3382
+ }
3383
+ const stdout = await this.callOpenCode(params.prompt);
3384
+ if (params.outputPath && existsSync11(params.outputPath)) {
3385
+ return readFileSync10(params.outputPath, "utf-8");
3386
+ }
3387
+ return stdout;
3388
+ }
3389
+ async callOpenCode(prompt, showSpinner = true) {
3390
+ return new Promise((resolve, reject) => {
3391
+ const proc = spawn4(this.opencodeCommand, [
3392
+ "run"
3393
+ ], {
3394
+ stdio: ["pipe", "pipe", "pipe"]
3395
+ });
3396
+ proc.stdin.write(prompt);
3397
+ proc.stdin.end();
3398
+ let stdout = "";
3399
+ let stderr = "";
3400
+ let spinner;
3401
+ if (showSpinner) {
3402
+ const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
3403
+ let frameIndex = 0;
3404
+ spinner = setInterval(() => {
3405
+ process.stdout.write(`\r${frames[frameIndex++ % frames.length]} OpenCode is working...`);
3406
+ }, 100);
3407
+ }
3408
+ const stopSpinner = (success) => {
3409
+ if (spinner) {
3410
+ clearInterval(spinner);
3411
+ process.stdout.write(`\r${success ? "\u2713" : "\u2717"} OpenCode ${success ? "done" : "failed"}
3412
+ `);
3413
+ }
3414
+ };
3415
+ proc.stdout.on("data", (data) => {
3416
+ stdout += data.toString();
3417
+ });
3418
+ proc.stderr.on("data", (data) => {
3419
+ stderr += data.toString();
3420
+ });
3421
+ proc.on("close", (code) => {
3422
+ stopSpinner(code === 0);
3423
+ if (code !== 0) {
3424
+ reject(new Error(`OpenCode CLI exited with code ${code}: ${stderr}`));
3425
+ } else {
3426
+ resolve(stdout);
3427
+ }
3428
+ });
3429
+ proc.on("error", (err) => {
3430
+ stopSpinner(false);
3431
+ reject(new Error(`Failed to run OpenCode CLI: ${err.message}`));
3432
+ });
3433
+ });
3434
+ }
3435
+ /**
3436
+ * Execute multiple prompts in parallel with shared progress display
3437
+ */
3438
+ async executeParallel(params) {
3439
+ const total = params.length;
3440
+ let completed = 0;
3441
+ const results = /* @__PURE__ */ new Map();
3442
+ const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
3443
+ let frameIndex = 0;
3444
+ const spinner = setInterval(() => {
3445
+ const progress = `${completed}/${total}`;
3446
+ process.stdout.write(`\r${frames[frameIndex++ % frames.length]} Generating sections in parallel... [${progress}]`);
3447
+ }, 100);
3448
+ const stopSpinner = (success) => {
3449
+ clearInterval(spinner);
3450
+ const status = success ? "\u2713" : "\u2717";
3451
+ const message = success ? `All ${total} sections generated` : `Generation completed with errors`;
3452
+ process.stdout.write(`\r${status} ${message}
3453
+ `);
3454
+ };
3455
+ await Promise.all(
3456
+ params.map(async (p) => {
3457
+ const id = p.id || p.outputPath;
3458
+ try {
3459
+ if (p.outputPath) {
3460
+ const outputDir = dirname6(p.outputPath);
3461
+ if (!existsSync11(outputDir)) {
3462
+ mkdirSync8(outputDir, { recursive: true });
3463
+ }
3464
+ }
3465
+ await this.callOpenCode(p.prompt, false);
3466
+ let result = "";
3467
+ if (p.outputPath && existsSync11(p.outputPath)) {
3468
+ result = readFileSync10(p.outputPath, "utf-8");
3469
+ }
3470
+ completed++;
3471
+ results.set(id, { id, success: true, result });
3472
+ } catch (error) {
3473
+ completed++;
3474
+ results.set(id, {
3475
+ id,
3476
+ success: false,
3477
+ error: error instanceof Error ? error.message : String(error)
3478
+ });
3479
+ }
3480
+ })
3481
+ );
3482
+ stopSpinner(Array.from(results.values()).every((r) => r.success));
3483
+ return params.map((p) => results.get(p.id || p.outputPath));
3484
+ }
3485
+ };
3486
+ function createOpenCodeAdapter(opencodeCommand = "opencode") {
3487
+ return {
3488
+ ai: new OpenCodeAIAdapter(opencodeCommand),
3489
+ io: new CLIIOAdapter(),
3490
+ system: new CLISystemAdapter()
3491
+ };
3492
+ }
3493
+
3098
3494
  // src/commands/start.ts
3099
3495
  function getGitHubInfo(cwd) {
3100
3496
  const config = getGitHubConfig(cwd);
@@ -3166,6 +3562,9 @@ async function startCommand(query, options) {
3166
3562
  if (agentType === "codex") {
3167
3563
  adapter = createCodexAdapter();
3168
3564
  console.log("Platform: cli (codex)");
3565
+ } else if (agentType === "opencode") {
3566
+ adapter = createOpenCodeAdapter();
3567
+ console.log("Platform: cli (opencode)");
3169
3568
  } else {
3170
3569
  adapter = createCLIAdapter();
3171
3570
  console.log("Platform: cli (claude)");
@@ -3349,15 +3748,15 @@ Resume with: spets resume --task ${taskId}`);
3349
3748
 
3350
3749
  // src/commands/resume.ts
3351
3750
  import { select as select2 } from "@inquirer/prompts";
3352
- import { existsSync as existsSync10, readdirSync as readdirSync2, readFileSync as readFileSync10 } from "fs";
3353
- import { join as join7 } from "path";
3751
+ import { existsSync as existsSync12, readdirSync as readdirSync3, readFileSync as readFileSync11 } from "fs";
3752
+ import { join as join8 } from "path";
3354
3753
  import { spawnSync as spawnSync3 } from "child_process";
3355
3754
  async function createPR(cwd, taskId, outputs) {
3356
3755
  const outputContents = [];
3357
3756
  for (const outputPath of outputs) {
3358
- const fullPath = join7(cwd, outputPath);
3359
- if (existsSync10(fullPath)) {
3360
- const content = readFileSync10(fullPath, "utf-8");
3757
+ const fullPath = join8(cwd, outputPath);
3758
+ if (existsSync12(fullPath)) {
3759
+ const content = readFileSync11(fullPath, "utf-8");
3361
3760
  outputContents.push(`## ${outputPath}
3362
3761
 
3363
3762
  ${content}`);
@@ -3379,15 +3778,15 @@ ${outputContents.join("\n\n---\n\n")}`;
3379
3778
  }
3380
3779
  function listResumableTasks(cwd) {
3381
3780
  const outputsDir = getOutputsDir(cwd);
3382
- if (!existsSync10(outputsDir)) {
3781
+ if (!existsSync12(outputsDir)) {
3383
3782
  return [];
3384
3783
  }
3385
3784
  const tasks = [];
3386
- const taskDirs = readdirSync2(outputsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
3785
+ const taskDirs = readdirSync3(outputsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
3387
3786
  const orchestrator = new Orchestrator(cwd);
3388
3787
  for (const taskId of taskDirs) {
3389
- const stateFile = join7(outputsDir, taskId, ".state.json");
3390
- if (!existsSync10(stateFile)) continue;
3788
+ const stateFile = join8(outputsDir, taskId, ".state.json");
3789
+ if (!existsSync12(stateFile)) continue;
3391
3790
  try {
3392
3791
  const response = orchestrator.cmdStatus(taskId);
3393
3792
  if (response.type === "error") continue;
@@ -3637,444 +4036,18 @@ Resume with: spets resume --task ${taskId}`);
3637
4036
  }
3638
4037
  }
3639
4038
 
3640
- // src/commands/plugin.ts
3641
- import { existsSync as existsSync11, mkdirSync as mkdirSync7, writeFileSync as writeFileSync7, rmSync, readdirSync as readdirSync3 } from "fs";
3642
- import { join as join8 } from "path";
3643
- import { homedir } from "os";
3644
- async function pluginCommand(action, name) {
3645
- switch (action) {
3646
- case "install":
3647
- if (!name) {
3648
- console.error("Plugin name required.");
3649
- console.error("Usage: spets plugin install <name>");
3650
- process.exit(1);
3651
- }
3652
- await installPlugin(name);
3653
- break;
3654
- case "uninstall":
3655
- if (!name) {
3656
- console.error("Plugin name required.");
3657
- console.error("Usage: spets plugin uninstall <name>");
3658
- process.exit(1);
3659
- }
3660
- await uninstallPlugin(name);
3661
- break;
3662
- case "list":
3663
- await listPlugins();
3664
- break;
3665
- default:
3666
- console.error(`Unknown action: ${action}`);
3667
- console.error("Available actions: install, uninstall, list");
3668
- process.exit(1);
3669
- }
3670
- }
3671
- async function installPlugin(name) {
3672
- const plugins = {
3673
- claude: installClaudePlugin,
3674
- codex: installCodexPlugin
3675
- };
3676
- const installer = plugins[name];
3677
- if (!installer) {
3678
- console.error(`Unknown plugin: ${name}`);
3679
- console.error("Available plugins: claude, codex");
3680
- process.exit(1);
3681
- }
3682
- installer();
3683
- }
3684
- function installClaudePlugin() {
3685
- const claudeDir = join8(homedir(), ".claude");
3686
- const commandsDir = join8(claudeDir, "commands");
3687
- mkdirSync7(commandsDir, { recursive: true });
3688
- const skillPath = join8(commandsDir, "spets.md");
3689
- writeFileSync7(skillPath, getClaudeSkillContent());
3690
- console.log("Installed Claude Code plugin.");
3691
- console.log(`Location: ${skillPath}`);
3692
- console.log("");
3693
- console.log("Usage in Claude Code:");
3694
- console.log(' /spets "your task description"');
3695
- console.log("");
3696
- console.log("This skill runs deterministically within your Claude Code session.");
3697
- console.log("No additional Claude processes are spawned.");
3698
- }
3699
- function installCodexPlugin() {
3700
- const codexDir = join8(homedir(), ".codex");
3701
- const skillsDir = join8(codexDir, "skills");
3702
- const spetsDir = join8(skillsDir, "spets");
3703
- mkdirSync7(spetsDir, { recursive: true });
3704
- const skillPath = join8(spetsDir, "SKILL.md");
3705
- writeFileSync7(skillPath, getCodexSkillContent());
3706
- console.log("Installed Codex CLI plugin.");
3707
- console.log(`Location: ${skillPath}`);
3708
- console.log("");
3709
- console.log("Usage in Codex CLI:");
3710
- console.log(' $spets "your task description"');
3711
- console.log("");
3712
- console.log("This skill runs deterministically within your Codex CLI session.");
3713
- console.log("No additional Codex processes are spawned.");
3714
- }
3715
- async function uninstallPlugin(name) {
3716
- if (name === "claude") {
3717
- const skillPath = join8(homedir(), ".claude", "commands", "spets.md");
3718
- const legacySkillPath = join8(homedir(), ".claude", "commands", "sdd-do.md");
3719
- let uninstalled = false;
3720
- if (existsSync11(skillPath)) {
3721
- rmSync(skillPath);
3722
- uninstalled = true;
3723
- }
3724
- if (existsSync11(legacySkillPath)) {
3725
- rmSync(legacySkillPath);
3726
- uninstalled = true;
3727
- }
3728
- if (uninstalled) {
3729
- console.log("Uninstalled Claude Code plugin.");
3730
- } else {
3731
- console.log("Claude Code plugin not installed.");
3732
- }
3733
- return;
3734
- }
3735
- if (name === "codex") {
3736
- const skillPath = join8(homedir(), ".codex", "skills", "spets", "SKILL.md");
3737
- if (existsSync11(skillPath)) {
3738
- rmSync(skillPath);
3739
- try {
3740
- const skillDir = join8(homedir(), ".codex", "skills", "spets");
3741
- const skillsDir = join8(homedir(), ".codex", "skills");
3742
- rmSync(skillDir, { recursive: true });
3743
- const remaining = readdirSync3(skillsDir);
3744
- if (remaining.length === 0) {
3745
- rmSync(skillsDir);
3746
- }
3747
- } catch {
3748
- }
3749
- console.log("Uninstalled Codex CLI plugin.");
3750
- } else {
3751
- console.log("Codex CLI plugin not installed.");
3752
- }
3753
- return;
3754
- }
3755
- console.error(`Unknown plugin: ${name}`);
3756
- process.exit(1);
3757
- }
3758
- async function listPlugins() {
3759
- console.log("Available plugins:");
3760
- console.log("");
3761
- console.log(" claude - Claude Code /spets skill");
3762
- console.log(" codex - Codex CLI $spets skill");
3763
- console.log("");
3764
- const claudeSkillPath = join8(homedir(), ".claude", "commands", "spets.md");
3765
- const claudeLegacySkillPath = join8(homedir(), ".claude", "commands", "sdd-do.md");
3766
- const claudeInstalled = existsSync11(claudeSkillPath) || existsSync11(claudeLegacySkillPath);
3767
- const codexSkillPath = join8(homedir(), ".codex", "skills", "spets", "SKILL.md");
3768
- const codexInstalled = existsSync11(codexSkillPath);
3769
- console.log("Installed:");
3770
- const installed = [];
3771
- if (claudeInstalled) installed.push("claude");
3772
- if (codexInstalled) installed.push("codex");
3773
- if (installed.length > 0) {
3774
- for (const plugin of installed) {
3775
- console.log(` - ${plugin}`);
3776
- }
3777
- } else {
3778
- console.log(" (none)");
3779
- }
3780
- }
3781
- function getClaudeSkillContent() {
3782
- return `# Spets Command Executor
3783
-
3784
- Execute orchestrator commands. Parse JSON response \u2192 execute matching action.
3785
-
3786
- ---
3787
-
3788
- ## Constraints
3789
-
3790
- - **NO** EnterPlanMode
3791
- - **NO** TaskCreate/TaskUpdate
3792
- - **NO** subprocess spawning for AI calls
3793
- - **IMMEDIATELY** execute commands, no planning
3794
-
3795
- ---
3796
-
3797
- ## Start
3798
-
3799
- \`\`\`bash
3800
- npx spets orchestrate init "$ARGUMENTS"
3801
- \`\`\`
3802
-
3803
- Parse JSON response \u2192 execute action from table below \u2192 loop until \`type: "complete"\` or \`type: "error"\`.
3804
-
3805
- ---
3806
-
3807
- ## Command Table
3808
-
3809
- ### \`type: "phase"\`
3810
-
3811
- | \`phase\` | Action |
3812
- |---------|--------|
3813
- | \`explore\` | [ACTION_EXPLORE](#action_explore) |
3814
- | \`clarify\` | [ACTION_CLARIFY](#action_clarify) |
3815
- | \`execute\` | [ACTION_EXECUTE](#action_execute) |
3816
- | \`verify\` | [ACTION_VERIFY](#action_verify) |
3817
- | \`context\` | [ACTION_CONTEXT](#action_context) (legacy) |
3818
- | \`generate\` | [ACTION_GENERATE](#action_generate) (legacy) |
3819
-
3820
- ### \`type: "checkpoint"\`
3821
-
3822
- | \`checkpoint\` | Action |
3823
- |--------------|--------|
3824
- | \`clarify\` | [ACTION_ASK_QUESTIONS](#action_ask_questions) |
3825
- | \`approve\` | [ACTION_ASK_APPROVAL](#action_ask_approval) |
3826
-
3827
- ### \`type: "complete"\`
3828
-
3829
- | \`status\` | Action |
3830
- |----------|--------|
3831
- | \`completed\` | Print: "Workflow complete. Outputs: {outputs}" |
3832
- | \`stopped\` | Print: "Workflow paused. Resume with: npx spets resume --task {taskId}" |
3833
- | \`rejected\` | Print: "Workflow rejected." |
3834
-
3835
- ### \`type: "error"\`
3836
-
3837
- Print: "Error: {error}" \u2192 STOP
3838
-
3839
- ---
3840
-
3841
- ## Action Definitions
3842
-
3843
- **IMPORTANT:** Each phase response includes a \`prompt\` field containing the exact instructions to follow.
3844
- This ensures identical behavior across CLI, Claude Code, and GitHub modes.
3845
-
3846
- ### ACTION_EXPLORE
3847
-
3848
- \`\`\`
3849
- 1. Follow the instructions in response.prompt EXACTLY
3850
- 2. The prompt tells you:
3851
- - What to search for
3852
- - How to analyze the codebase
3853
- - The exact JSON output format required
3854
- 3. Bash: npx spets orchestrate explore-done {taskId} '{exploreJson}'
3855
- 4. Parse response \u2192 next action
3856
- \`\`\`
3857
-
3858
- ### ACTION_CLARIFY
3859
-
3860
- \`\`\`
3861
- 1. Follow the instructions in response.prompt EXACTLY
3862
- 2. The prompt tells you:
3863
- - What context to analyze
3864
- - How to generate questions
3865
- - The exact JSON output format required
3866
- 3. Bash: npx spets orchestrate clarify-done {taskId} '{questionsJson}'
3867
- - Format: [{"id":"q1","question":"..."},...]
3868
- - Empty if no questions: []
3869
- 4. Parse response \u2192 next action
3870
- \`\`\`
3871
-
3872
- ### ACTION_EXECUTE
3873
-
3874
- \`\`\`
3875
- 1. Follow the instructions in response.prompt EXACTLY
3876
- 2. The prompt tells you:
3877
- - The instruction and template to follow
3878
- - The explore output and answers to use
3879
- - Where to save the document/code (context.output)
3880
- - Any revision/verify feedback to address
3881
- 3. Write the document/code to context.output
3882
- 4. Bash: npx spets orchestrate execute-done {taskId}
3883
- 5. Parse response \u2192 next action
3884
- \`\`\`
3885
-
3886
- ### ACTION_VERIFY
3887
-
3888
- \`\`\`
3889
- 1. Follow the instructions in response.prompt EXACTLY
3890
- 2. The prompt tells you:
3891
- - The document to verify (already included in prompt)
3892
- - The requirements and template to check against
3893
- - The scoring criteria and pass conditions
3894
- - The exact JSON output format required
3895
- 3. Bash: npx spets orchestrate verify-done {taskId} '{verifyJson}'
3896
- 4. Parse response \u2192 next action
3897
- - If passed: goes to human review
3898
- - If failed (attempts < 3): goes back to draft phase (auto-fix)
3899
- - If failed (attempts >= 3): goes to human review with warning
3900
- \`\`\`
3901
-
3902
- ### ACTION_GENERATE (legacy)
3903
-
3904
- \`\`\`
3905
- 1. Read(context.instruction)
3906
- 2. IF context.template EXISTS: Read(context.template)
3907
- 3. IF context.previousOutput EXISTS: Read(context.previousOutput)
3908
- 4. Generate document using: gatheredContext, answers, revisionFeedback
3909
- 5. Write(context.output, documentContent)
3910
- 6. Bash: npx spets orchestrate generate-done {taskId}
3911
- 7. Parse response \u2192 next action
3912
- \`\`\`
3913
-
3914
- ### ACTION_CONTEXT (legacy)
3915
-
3916
- \`\`\`
3917
- 1. Read(context.instruction)
3918
- 2. IF context.previousOutput EXISTS: Read(context.previousOutput)
3919
- 3. Explore codebase for: {description}
3920
- 4. Bash: npx spets orchestrate context-done {taskId}
3921
- 5. Parse response \u2192 next action
3922
- \`\`\`
3923
-
3924
- ### ACTION_ASK_QUESTIONS
3925
-
3926
- \`\`\`
3927
- 1. AskUserQuestion(
3928
- questions: [
3929
- FOR EACH q IN questions:
3930
- {
3931
- question: q.question,
3932
- header: "Q{index}",
3933
- options: q.options OR [
3934
- {label: "Provide answer", description: "Type your answer"}
3935
- ]
3936
- }
3937
- ]
3938
- )
3939
-
3940
- 2. Collect answers into: [{"questionId":"q1","answer":"..."},...]
3941
-
3942
- 3. Bash: npx spets orchestrate clarified {taskId} '{answersJson}'
3943
-
3944
- 4. Parse response \u2192 next action
3945
- \`\`\`
3946
-
3947
- ### ACTION_ASK_APPROVAL
3948
-
3949
- \`\`\`
3950
- 1. Read(specPath)
3951
-
3952
- 2. Summarize document (2-3 sentences)
3953
-
3954
- 3. AskUserQuestion(
3955
- questions: [{
3956
- question: "Review {step} document. What would you like to do?",
3957
- header: "Review",
3958
- options: [
3959
- {label: "Approve", description: "Continue to next step"},
3960
- {label: "Revise", description: "Request changes"},
3961
- {label: "Reject", description: "Stop workflow"},
3962
- {label: "Stop", description: "Pause for later"}
3963
- ]
3964
- }]
3965
- )
3966
-
3967
- 4. SWITCH answer:
3968
- - "Approve": Bash: npx spets orchestrate approve {taskId}
3969
- - "Revise":
3970
- - Ask for feedback (AskUserQuestion or text input)
3971
- - Bash: npx spets orchestrate revise {taskId} "{feedback}"
3972
- - "Reject": Bash: npx spets orchestrate reject {taskId}
3973
- - "Stop": Bash: npx spets orchestrate stop {taskId}
3974
-
3975
- 5. Parse response \u2192 next action
3976
- \`\`\`
3977
-
3978
- ---
3979
-
3980
- ## Orchestrator Commands Reference
3981
-
3982
- \`\`\`bash
3983
- # New 5-phase workflow
3984
- npx spets orchestrate init "<description>"
3985
- npx spets orchestrate explore-done <taskId> '<json>'
3986
- npx spets orchestrate clarify-done <taskId> '<json>'
3987
- npx spets orchestrate clarified <taskId> '<json>'
3988
- npx spets orchestrate execute-done <taskId>
3989
- npx spets orchestrate verify-done <taskId> '<json>'
3990
- npx spets orchestrate approve <taskId>
3991
- npx spets orchestrate revise <taskId> "<feedback>"
3992
- npx spets orchestrate reject <taskId>
3993
- npx spets orchestrate stop <taskId>
3994
-
3995
- # Legacy (backward compatible)
3996
- npx spets orchestrate context-done <taskId>
3997
- npx spets orchestrate generate-done <taskId>
3998
- \`\`\`
3999
-
4000
- ---
4001
-
4002
- $ARGUMENTS
4003
- description: Task description for the workflow
4004
- `;
4005
- }
4006
- function getCodexSkillContent() {
4007
- return `---
4008
- name: spets
4009
- description: SDD workflow executor - runs spec-driven development workflows within Codex CLI
4010
- ---
4011
-
4012
- # Spets Command Executor
4013
-
4014
- Execute orchestrator commands. Parse JSON response and execute matching action.
4015
-
4016
- ## Start
4017
-
4018
- Run: npx spets orchestrate init "$ARGUMENTS"
4019
-
4020
- Parse JSON response, then execute action from table below, loop until type: "complete" or "error".
4021
-
4022
- ## Command Table
4023
-
4024
- ### type: "phase"
4025
-
4026
- | phase | Action |
4027
- |-------|--------|
4028
- | explore | Follow response.prompt to explore codebase |
4029
- | clarify | Follow response.prompt to generate questions |
4030
- | execute | Follow response.prompt to write document |
4031
- | verify | Follow response.prompt to validate document |
4032
-
4033
- ### type: "checkpoint"
4034
-
4035
- | checkpoint | Action |
4036
- |------------|--------|
4037
- | clarify | Ask user questions, collect answers |
4038
- | approve | Ask user to review document |
4039
-
4040
- ### type: "complete"
4041
-
4042
- Print completion status and outputs.
4043
-
4044
- ### type: "error"
4045
-
4046
- Print error and stop.
4047
-
4048
- ## Orchestrator Commands
4049
-
4050
- npx spets orchestrate init "<description>"
4051
- npx spets orchestrate explore-done <taskId> '<json>'
4052
- npx spets orchestrate clarify-done <taskId> '<json>'
4053
- npx spets orchestrate clarified <taskId> '<json>'
4054
- npx spets orchestrate execute-done <taskId>
4055
- npx spets orchestrate verify-done <taskId> '<json>'
4056
- npx spets orchestrate approve <taskId>
4057
- npx spets orchestrate revise <taskId> "<feedback>"
4058
- npx spets orchestrate reject <taskId>
4059
- npx spets orchestrate stop <taskId>
4060
-
4061
- $ARGUMENTS
4062
- description: Task description for the workflow
4063
- `;
4064
- }
4065
-
4066
4039
  // src/commands/github.ts
4067
- import { execSync as execSync4, spawn as spawn5, spawnSync as spawnSync4 } from "child_process";
4068
- import { readdirSync as readdirSync4, readFileSync as readFileSync11, existsSync as existsSync13 } from "fs";
4040
+ import { execSync as execSync4, spawn as spawn6, spawnSync as spawnSync4 } from "child_process";
4041
+ import { readdirSync as readdirSync4, readFileSync as readFileSync12, existsSync as existsSync14 } from "fs";
4069
4042
  import { join as join10 } from "path";
4070
4043
 
4071
4044
  // src/hooks/runner.ts
4072
- import { spawn as spawn4 } from "child_process";
4073
- import { existsSync as existsSync12 } from "fs";
4045
+ import { spawn as spawn5 } from "child_process";
4046
+ import { existsSync as existsSync13 } from "fs";
4074
4047
  import { join as join9, isAbsolute } from "path";
4075
4048
  async function runHook(hookPath, context, cwd = process.cwd()) {
4076
4049
  const resolvedPath = isAbsolute(hookPath) ? hookPath : join9(getSpetsDir(cwd), hookPath);
4077
- if (!existsSync12(resolvedPath)) {
4050
+ if (!existsSync13(resolvedPath)) {
4078
4051
  console.warn(`Hook not found: ${resolvedPath}`);
4079
4052
  return;
4080
4053
  }
@@ -4088,7 +4061,7 @@ async function runHook(hookPath, context, cwd = process.cwd()) {
4088
4061
  SPETS_CWD: cwd
4089
4062
  };
4090
4063
  return new Promise((resolve, reject) => {
4091
- const proc = spawn4(resolvedPath, [], {
4064
+ const proc = spawn5(resolvedPath, [], {
4092
4065
  cwd,
4093
4066
  env,
4094
4067
  stdio: "inherit",
@@ -4185,7 +4158,7 @@ function findTaskId(cwd, owner, repo, issueOrPr) {
4185
4158
  }
4186
4159
  }
4187
4160
  const outputsDir = getOutputsDir(cwd);
4188
- if (existsSync13(outputsDir)) {
4161
+ if (existsSync14(outputsDir)) {
4189
4162
  const taskDirs = readdirSync4(outputsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort().reverse();
4190
4163
  if (taskDirs.length > 0) {
4191
4164
  return taskDirs[0];
@@ -4418,7 +4391,7 @@ async function postComment(config, body) {
4418
4391
  const { owner, repo, prNumber, issueNumber } = config;
4419
4392
  const args = prNumber ? ["pr", "comment", String(prNumber), "--body", body, "-R", `${owner}/${repo}`] : ["issue", "comment", String(issueNumber), "--body", body, "-R", `${owner}/${repo}`];
4420
4393
  return new Promise((resolve, reject) => {
4421
- const proc = spawn5("gh", args, { stdio: "inherit" });
4394
+ const proc = spawn6("gh", args, { stdio: "inherit" });
4422
4395
  proc.on("close", (code) => {
4423
4396
  if (code !== 0) reject(new Error(`gh failed with code ${code}`));
4424
4397
  else resolve();
@@ -4430,10 +4403,10 @@ async function createPullRequestWithAI(config, taskId, userQuery, cwd) {
4430
4403
  const { owner, repo, issueNumber } = config;
4431
4404
  const outputsDir = join10(getOutputsDir(cwd), taskId);
4432
4405
  const outputs = [];
4433
- if (existsSync13(outputsDir)) {
4406
+ if (existsSync14(outputsDir)) {
4434
4407
  const files = readdirSync4(outputsDir).filter((f) => f.endsWith(".md"));
4435
4408
  for (const file of files) {
4436
- const content = readFileSync11(join10(outputsDir, file), "utf-8");
4409
+ const content = readFileSync12(join10(outputsDir, file), "utf-8");
4437
4410
  outputs.push({ step: file.replace(".md", ""), content });
4438
4411
  }
4439
4412
  }
@@ -4519,7 +4492,7 @@ Closes #${issueNumber}`;
4519
4492
  }
4520
4493
  async function callClaude(prompt) {
4521
4494
  return new Promise((resolve, reject) => {
4522
- const proc = spawn5("claude", ["--print"], { stdio: ["pipe", "pipe", "pipe"] });
4495
+ const proc = spawn6("claude", ["--print"], { stdio: ["pipe", "pipe", "pipe"] });
4523
4496
  let stdout = "";
4524
4497
  let stderr = "";
4525
4498
  proc.stdout.on("data", (data) => {
@@ -4799,8 +4772,8 @@ async function orchestrateCommand(action, args) {
4799
4772
  }
4800
4773
 
4801
4774
  // src/index.ts
4802
- var __dirname2 = dirname6(fileURLToPath2(import.meta.url));
4803
- var pkg = JSON.parse(readFileSync12(join11(__dirname2, "..", "package.json"), "utf-8"));
4775
+ var __dirname2 = dirname7(fileURLToPath2(import.meta.url));
4776
+ var pkg = JSON.parse(readFileSync13(join11(__dirname2, "..", "package.json"), "utf-8"));
4804
4777
  var program = new Command();
4805
4778
  program.name("spets").description("Spec Driven Development Execution Framework").version(pkg.version);
4806
4779
  program.command("init").description("Initialize spets in current directory").option("-f, --force", "Overwrite existing config").option("--github", "Add GitHub Actions workflow for PR/Issue integration").action(initCommand);