spets 0.1.50 → 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 +583 -842
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -7,8 +7,8 @@ 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,96 +307,63 @@ 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
-
309
- 4. Parse response \u2192 next action
310
- \`\`\`
311
-
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
-
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}'
338
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
339
367
  \`\`\`
340
368
 
341
369
  ### ACTION_ASK_QUESTIONS
@@ -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++;
@@ -3097,7 +3364,7 @@ function createCodexAdapter(codexCommand = "codex") {
3097
3364
 
3098
3365
  // src/adapters/opencode.ts
3099
3366
  import { spawn as spawn4 } from "child_process";
3100
- import { readFileSync as readFileSync10, existsSync as existsSync10, mkdirSync as mkdirSync7 } from "fs";
3367
+ import { readFileSync as readFileSync10, existsSync as existsSync11, mkdirSync as mkdirSync8 } from "fs";
3101
3368
  import { dirname as dirname6 } from "path";
3102
3369
  var OpenCodeAIAdapter = class {
3103
3370
  opencodeCommand;
@@ -3109,12 +3376,12 @@ var OpenCodeAIAdapter = class {
3109
3376
  \u{1F4DD} Generating...`);
3110
3377
  if (params.outputPath) {
3111
3378
  const outputDir = dirname6(params.outputPath);
3112
- if (!existsSync10(outputDir)) {
3113
- mkdirSync7(outputDir, { recursive: true });
3379
+ if (!existsSync11(outputDir)) {
3380
+ mkdirSync8(outputDir, { recursive: true });
3114
3381
  }
3115
3382
  }
3116
3383
  const stdout = await this.callOpenCode(params.prompt);
3117
- if (params.outputPath && existsSync10(params.outputPath)) {
3384
+ if (params.outputPath && existsSync11(params.outputPath)) {
3118
3385
  return readFileSync10(params.outputPath, "utf-8");
3119
3386
  }
3120
3387
  return stdout;
@@ -3191,13 +3458,13 @@ var OpenCodeAIAdapter = class {
3191
3458
  try {
3192
3459
  if (p.outputPath) {
3193
3460
  const outputDir = dirname6(p.outputPath);
3194
- if (!existsSync10(outputDir)) {
3195
- mkdirSync7(outputDir, { recursive: true });
3461
+ if (!existsSync11(outputDir)) {
3462
+ mkdirSync8(outputDir, { recursive: true });
3196
3463
  }
3197
3464
  }
3198
3465
  await this.callOpenCode(p.prompt, false);
3199
3466
  let result = "";
3200
- if (p.outputPath && existsSync10(p.outputPath)) {
3467
+ if (p.outputPath && existsSync11(p.outputPath)) {
3201
3468
  result = readFileSync10(p.outputPath, "utf-8");
3202
3469
  }
3203
3470
  completed++;
@@ -3481,14 +3748,14 @@ Resume with: spets resume --task ${taskId}`);
3481
3748
 
3482
3749
  // src/commands/resume.ts
3483
3750
  import { select as select2 } from "@inquirer/prompts";
3484
- import { existsSync as existsSync11, readdirSync as readdirSync2, readFileSync as readFileSync11 } from "fs";
3485
- 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";
3486
3753
  import { spawnSync as spawnSync3 } from "child_process";
3487
3754
  async function createPR(cwd, taskId, outputs) {
3488
3755
  const outputContents = [];
3489
3756
  for (const outputPath of outputs) {
3490
- const fullPath = join7(cwd, outputPath);
3491
- if (existsSync11(fullPath)) {
3757
+ const fullPath = join8(cwd, outputPath);
3758
+ if (existsSync12(fullPath)) {
3492
3759
  const content = readFileSync11(fullPath, "utf-8");
3493
3760
  outputContents.push(`## ${outputPath}
3494
3761
 
@@ -3511,15 +3778,15 @@ ${outputContents.join("\n\n---\n\n")}`;
3511
3778
  }
3512
3779
  function listResumableTasks(cwd) {
3513
3780
  const outputsDir = getOutputsDir(cwd);
3514
- if (!existsSync11(outputsDir)) {
3781
+ if (!existsSync12(outputsDir)) {
3515
3782
  return [];
3516
3783
  }
3517
3784
  const tasks = [];
3518
- 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);
3519
3786
  const orchestrator = new Orchestrator(cwd);
3520
3787
  for (const taskId of taskDirs) {
3521
- const stateFile = join7(outputsDir, taskId, ".state.json");
3522
- if (!existsSync11(stateFile)) continue;
3788
+ const stateFile = join8(outputsDir, taskId, ".state.json");
3789
+ if (!existsSync12(stateFile)) continue;
3523
3790
  try {
3524
3791
  const response = orchestrator.cmdStatus(taskId);
3525
3792
  if (response.type === "error") continue;
@@ -3769,532 +4036,6 @@ Resume with: spets resume --task ${taskId}`);
3769
4036
  }
3770
4037
  }
3771
4038
 
3772
- // src/commands/plugin.ts
3773
- import { existsSync as existsSync12, mkdirSync as mkdirSync8, writeFileSync as writeFileSync7, rmSync, readdirSync as readdirSync3 } from "fs";
3774
- import { join as join8 } from "path";
3775
- import { homedir } from "os";
3776
- async function pluginCommand(action, name) {
3777
- switch (action) {
3778
- case "install":
3779
- if (!name) {
3780
- console.error("Plugin name required.");
3781
- console.error("Usage: spets plugin install <name>");
3782
- process.exit(1);
3783
- }
3784
- await installPlugin(name);
3785
- break;
3786
- case "uninstall":
3787
- if (!name) {
3788
- console.error("Plugin name required.");
3789
- console.error("Usage: spets plugin uninstall <name>");
3790
- process.exit(1);
3791
- }
3792
- await uninstallPlugin(name);
3793
- break;
3794
- case "list":
3795
- await listPlugins();
3796
- break;
3797
- default:
3798
- console.error(`Unknown action: ${action}`);
3799
- console.error("Available actions: install, uninstall, list");
3800
- process.exit(1);
3801
- }
3802
- }
3803
- async function installPlugin(name) {
3804
- const plugins = {
3805
- claude: installClaudePlugin,
3806
- codex: installCodexPlugin,
3807
- opencode: installOpenCodePlugin
3808
- };
3809
- const installer = plugins[name];
3810
- if (!installer) {
3811
- console.error(`Unknown plugin: ${name}`);
3812
- console.error("Available plugins: claude, codex, opencode");
3813
- process.exit(1);
3814
- }
3815
- installer();
3816
- }
3817
- function installClaudePlugin() {
3818
- const claudeDir = join8(homedir(), ".claude");
3819
- const commandsDir = join8(claudeDir, "commands");
3820
- mkdirSync8(commandsDir, { recursive: true });
3821
- const skillPath = join8(commandsDir, "spets.md");
3822
- writeFileSync7(skillPath, getClaudeSkillContent());
3823
- console.log("Installed Claude Code plugin.");
3824
- console.log(`Location: ${skillPath}`);
3825
- console.log("");
3826
- console.log("Usage in Claude Code:");
3827
- console.log(' /spets "your task description"');
3828
- console.log("");
3829
- console.log("This skill runs deterministically within your Claude Code session.");
3830
- console.log("No additional Claude processes are spawned.");
3831
- }
3832
- function installCodexPlugin() {
3833
- const codexDir = join8(homedir(), ".codex");
3834
- const skillsDir = join8(codexDir, "skills");
3835
- const spetsDir = join8(skillsDir, "spets");
3836
- mkdirSync8(spetsDir, { recursive: true });
3837
- const skillPath = join8(spetsDir, "SKILL.md");
3838
- writeFileSync7(skillPath, getCodexSkillContent());
3839
- console.log("Installed Codex CLI plugin.");
3840
- console.log(`Location: ${skillPath}`);
3841
- console.log("");
3842
- console.log("Usage in Codex CLI:");
3843
- console.log(' $spets "your task description"');
3844
- console.log("");
3845
- console.log("This skill runs deterministically within your Codex CLI session.");
3846
- console.log("No additional Codex processes are spawned.");
3847
- }
3848
- function installOpenCodePlugin() {
3849
- const configDir = join8(homedir(), ".config", "opencode");
3850
- const skillsDir = join8(configDir, "skills");
3851
- const spetsDir = join8(skillsDir, "spets");
3852
- mkdirSync8(spetsDir, { recursive: true });
3853
- const skillPath = join8(spetsDir, "SKILL.md");
3854
- writeFileSync7(skillPath, getOpenCodeSkillContent());
3855
- console.log("Installed OpenCode plugin.");
3856
- console.log(`Location: ${skillPath}`);
3857
- console.log("");
3858
- console.log("Usage in OpenCode:");
3859
- console.log(" Use the spets skill in your OpenCode session");
3860
- console.log("");
3861
- console.log("This skill runs deterministically within your OpenCode session.");
3862
- console.log("No additional OpenCode processes are spawned.");
3863
- }
3864
- async function uninstallPlugin(name) {
3865
- if (name === "claude") {
3866
- const skillPath = join8(homedir(), ".claude", "commands", "spets.md");
3867
- const legacySkillPath = join8(homedir(), ".claude", "commands", "sdd-do.md");
3868
- let uninstalled = false;
3869
- if (existsSync12(skillPath)) {
3870
- rmSync(skillPath);
3871
- uninstalled = true;
3872
- }
3873
- if (existsSync12(legacySkillPath)) {
3874
- rmSync(legacySkillPath);
3875
- uninstalled = true;
3876
- }
3877
- if (uninstalled) {
3878
- console.log("Uninstalled Claude Code plugin.");
3879
- } else {
3880
- console.log("Claude Code plugin not installed.");
3881
- }
3882
- return;
3883
- }
3884
- if (name === "codex") {
3885
- const skillPath = join8(homedir(), ".codex", "skills", "spets", "SKILL.md");
3886
- if (existsSync12(skillPath)) {
3887
- rmSync(skillPath);
3888
- try {
3889
- const skillDir = join8(homedir(), ".codex", "skills", "spets");
3890
- const skillsDir = join8(homedir(), ".codex", "skills");
3891
- rmSync(skillDir, { recursive: true });
3892
- const remaining = readdirSync3(skillsDir);
3893
- if (remaining.length === 0) {
3894
- rmSync(skillsDir);
3895
- }
3896
- } catch {
3897
- }
3898
- console.log("Uninstalled Codex CLI plugin.");
3899
- } else {
3900
- console.log("Codex CLI plugin not installed.");
3901
- }
3902
- return;
3903
- }
3904
- if (name === "opencode") {
3905
- const skillPath = join8(homedir(), ".config", "opencode", "skills", "spets", "SKILL.md");
3906
- if (existsSync12(skillPath)) {
3907
- rmSync(skillPath);
3908
- try {
3909
- const skillDir = join8(homedir(), ".config", "opencode", "skills", "spets");
3910
- const skillsDir = join8(homedir(), ".config", "opencode", "skills");
3911
- rmSync(skillDir, { recursive: true });
3912
- const remaining = readdirSync3(skillsDir);
3913
- if (remaining.length === 0) {
3914
- rmSync(skillsDir);
3915
- }
3916
- } catch {
3917
- }
3918
- console.log("Uninstalled OpenCode plugin.");
3919
- } else {
3920
- console.log("OpenCode plugin not installed.");
3921
- }
3922
- return;
3923
- }
3924
- console.error(`Unknown plugin: ${name}`);
3925
- process.exit(1);
3926
- }
3927
- async function listPlugins() {
3928
- console.log("Available plugins:");
3929
- console.log("");
3930
- console.log(" claude - Claude Code /spets skill");
3931
- console.log(" codex - Codex CLI $spets skill");
3932
- console.log(" opencode - OpenCode spets skill");
3933
- console.log("");
3934
- const claudeSkillPath = join8(homedir(), ".claude", "commands", "spets.md");
3935
- const claudeLegacySkillPath = join8(homedir(), ".claude", "commands", "sdd-do.md");
3936
- const claudeInstalled = existsSync12(claudeSkillPath) || existsSync12(claudeLegacySkillPath);
3937
- const codexSkillPath = join8(homedir(), ".codex", "skills", "spets", "SKILL.md");
3938
- const codexInstalled = existsSync12(codexSkillPath);
3939
- const opencodeSkillPath = join8(homedir(), ".config", "opencode", "skills", "spets", "SKILL.md");
3940
- const opencodeInstalled = existsSync12(opencodeSkillPath);
3941
- console.log("Installed:");
3942
- const installed = [];
3943
- if (claudeInstalled) installed.push("claude");
3944
- if (codexInstalled) installed.push("codex");
3945
- if (opencodeInstalled) installed.push("opencode");
3946
- if (installed.length > 0) {
3947
- for (const plugin of installed) {
3948
- console.log(` - ${plugin}`);
3949
- }
3950
- } else {
3951
- console.log(" (none)");
3952
- }
3953
- }
3954
- function getClaudeSkillContent() {
3955
- return `# Spets Command Executor
3956
-
3957
- Execute orchestrator commands. Parse JSON response \u2192 execute matching action.
3958
-
3959
- ---
3960
-
3961
- ## Constraints
3962
-
3963
- - **NO** EnterPlanMode
3964
- - **NO** TaskCreate/TaskUpdate
3965
- - **NO** subprocess spawning for AI calls
3966
- - **IMMEDIATELY** execute commands, no planning
3967
-
3968
- ---
3969
-
3970
- ## Start
3971
-
3972
- \`\`\`bash
3973
- npx spets orchestrate init "$ARGUMENTS"
3974
- \`\`\`
3975
-
3976
- Parse JSON response \u2192 execute action from table below \u2192 loop until \`type: "complete"\` or \`type: "error"\`.
3977
-
3978
- ---
3979
-
3980
- ## Command Table
3981
-
3982
- ### \`type: "phase"\`
3983
-
3984
- | \`phase\` | Action |
3985
- |---------|--------|
3986
- | \`explore\` | [ACTION_EXPLORE](#action_explore) |
3987
- | \`clarify\` | [ACTION_CLARIFY](#action_clarify) |
3988
- | \`execute\` | [ACTION_EXECUTE](#action_execute) |
3989
- | \`verify\` | [ACTION_VERIFY](#action_verify) |
3990
- | \`context\` | [ACTION_CONTEXT](#action_context) (legacy) |
3991
- | \`generate\` | [ACTION_GENERATE](#action_generate) (legacy) |
3992
-
3993
- ### \`type: "checkpoint"\`
3994
-
3995
- | \`checkpoint\` | Action |
3996
- |--------------|--------|
3997
- | \`clarify\` | [ACTION_ASK_QUESTIONS](#action_ask_questions) |
3998
- | \`approve\` | [ACTION_ASK_APPROVAL](#action_ask_approval) |
3999
-
4000
- ### \`type: "complete"\`
4001
-
4002
- | \`status\` | Action |
4003
- |----------|--------|
4004
- | \`completed\` | Print: "Workflow complete. Outputs: {outputs}" |
4005
- | \`stopped\` | Print: "Workflow paused. Resume with: npx spets resume --task {taskId}" |
4006
- | \`rejected\` | Print: "Workflow rejected." |
4007
-
4008
- ### \`type: "error"\`
4009
-
4010
- Print: "Error: {error}" \u2192 STOP
4011
-
4012
- ---
4013
-
4014
- ## Action Definitions
4015
-
4016
- **IMPORTANT:** Each phase response includes a \`prompt\` field containing the exact instructions to follow.
4017
- This ensures identical behavior across CLI, Claude Code, and GitHub modes.
4018
-
4019
- ### ACTION_EXPLORE
4020
-
4021
- \`\`\`
4022
- 1. Follow the instructions in response.prompt EXACTLY
4023
- 2. The prompt tells you:
4024
- - What to search for
4025
- - How to analyze the codebase
4026
- - The exact JSON output format required
4027
- 3. Bash: npx spets orchestrate explore-done {taskId} '{exploreJson}'
4028
- 4. Parse response \u2192 next action
4029
- \`\`\`
4030
-
4031
- ### ACTION_CLARIFY
4032
-
4033
- \`\`\`
4034
- 1. Follow the instructions in response.prompt EXACTLY
4035
- 2. The prompt tells you:
4036
- - What context to analyze
4037
- - How to generate questions
4038
- - The exact JSON output format required
4039
- 3. Bash: npx spets orchestrate clarify-done {taskId} '{questionsJson}'
4040
- - Format: [{"id":"q1","question":"..."},...]
4041
- - Empty if no questions: []
4042
- 4. Parse response \u2192 next action
4043
- \`\`\`
4044
-
4045
- ### ACTION_EXECUTE
4046
-
4047
- \`\`\`
4048
- 1. Follow the instructions in response.prompt EXACTLY
4049
- 2. The prompt tells you:
4050
- - The instruction and template to follow
4051
- - The explore output and answers to use
4052
- - Where to save the document/code (context.output)
4053
- - Any revision/verify feedback to address
4054
- 3. Write the document/code to context.output
4055
- 4. Bash: npx spets orchestrate execute-done {taskId}
4056
- 5. Parse response \u2192 next action
4057
- \`\`\`
4058
-
4059
- ### ACTION_VERIFY
4060
-
4061
- \`\`\`
4062
- 1. Follow the instructions in response.prompt EXACTLY
4063
- 2. The prompt tells you:
4064
- - The document to verify (already included in prompt)
4065
- - The requirements and template to check against
4066
- - The scoring criteria and pass conditions
4067
- - The exact JSON output format required
4068
- 3. Bash: npx spets orchestrate verify-done {taskId} '{verifyJson}'
4069
- 4. Parse response \u2192 next action
4070
- - If passed: goes to human review
4071
- - If failed (attempts < 3): goes back to draft phase (auto-fix)
4072
- - If failed (attempts >= 3): goes to human review with warning
4073
- \`\`\`
4074
-
4075
- ### ACTION_GENERATE (legacy)
4076
-
4077
- \`\`\`
4078
- 1. Read(context.instruction)
4079
- 2. IF context.template EXISTS: Read(context.template)
4080
- 3. IF context.previousOutput EXISTS: Read(context.previousOutput)
4081
- 4. Generate document using: gatheredContext, answers, revisionFeedback
4082
- 5. Write(context.output, documentContent)
4083
- 6. Bash: npx spets orchestrate generate-done {taskId}
4084
- 7. Parse response \u2192 next action
4085
- \`\`\`
4086
-
4087
- ### ACTION_CONTEXT (legacy)
4088
-
4089
- \`\`\`
4090
- 1. Read(context.instruction)
4091
- 2. IF context.previousOutput EXISTS: Read(context.previousOutput)
4092
- 3. Explore codebase for: {description}
4093
- 4. Bash: npx spets orchestrate context-done {taskId}
4094
- 5. Parse response \u2192 next action
4095
- \`\`\`
4096
-
4097
- ### ACTION_ASK_QUESTIONS
4098
-
4099
- \`\`\`
4100
- 1. AskUserQuestion(
4101
- questions: [
4102
- FOR EACH q IN questions:
4103
- {
4104
- question: q.question,
4105
- header: "Q{index}",
4106
- options: q.options OR [
4107
- {label: "Provide answer", description: "Type your answer"}
4108
- ]
4109
- }
4110
- ]
4111
- )
4112
-
4113
- 2. Collect answers into: [{"questionId":"q1","answer":"..."},...]
4114
-
4115
- 3. Bash: npx spets orchestrate clarified {taskId} '{answersJson}'
4116
-
4117
- 4. Parse response \u2192 next action
4118
- \`\`\`
4119
-
4120
- ### ACTION_ASK_APPROVAL
4121
-
4122
- \`\`\`
4123
- 1. Read(specPath)
4124
-
4125
- 2. Summarize document (2-3 sentences)
4126
-
4127
- 3. AskUserQuestion(
4128
- questions: [{
4129
- question: "Review {step} document. What would you like to do?",
4130
- header: "Review",
4131
- options: [
4132
- {label: "Approve", description: "Continue to next step"},
4133
- {label: "Revise", description: "Request changes"},
4134
- {label: "Reject", description: "Stop workflow"},
4135
- {label: "Stop", description: "Pause for later"}
4136
- ]
4137
- }]
4138
- )
4139
-
4140
- 4. SWITCH answer:
4141
- - "Approve": Bash: npx spets orchestrate approve {taskId}
4142
- - "Revise":
4143
- - Ask for feedback (AskUserQuestion or text input)
4144
- - Bash: npx spets orchestrate revise {taskId} "{feedback}"
4145
- - "Reject": Bash: npx spets orchestrate reject {taskId}
4146
- - "Stop": Bash: npx spets orchestrate stop {taskId}
4147
-
4148
- 5. Parse response \u2192 next action
4149
- \`\`\`
4150
-
4151
- ---
4152
-
4153
- ## Orchestrator Commands Reference
4154
-
4155
- \`\`\`bash
4156
- # New 5-phase workflow
4157
- npx spets orchestrate init "<description>"
4158
- npx spets orchestrate explore-done <taskId> '<json>'
4159
- npx spets orchestrate clarify-done <taskId> '<json>'
4160
- npx spets orchestrate clarified <taskId> '<json>'
4161
- npx spets orchestrate execute-done <taskId>
4162
- npx spets orchestrate verify-done <taskId> '<json>'
4163
- npx spets orchestrate approve <taskId>
4164
- npx spets orchestrate revise <taskId> "<feedback>"
4165
- npx spets orchestrate reject <taskId>
4166
- npx spets orchestrate stop <taskId>
4167
-
4168
- # Legacy (backward compatible)
4169
- npx spets orchestrate context-done <taskId>
4170
- npx spets orchestrate generate-done <taskId>
4171
- \`\`\`
4172
-
4173
- ---
4174
-
4175
- $ARGUMENTS
4176
- description: Task description for the workflow
4177
- `;
4178
- }
4179
- function getCodexSkillContent() {
4180
- return `---
4181
- name: spets
4182
- description: SDD workflow executor - runs spec-driven development workflows within Codex CLI
4183
- ---
4184
-
4185
- # Spets Command Executor
4186
-
4187
- Execute orchestrator commands. Parse JSON response and execute matching action.
4188
-
4189
- ## Start
4190
-
4191
- Run: npx spets orchestrate init "$ARGUMENTS"
4192
-
4193
- Parse JSON response, then execute action from table below, loop until type: "complete" or "error".
4194
-
4195
- ## Command Table
4196
-
4197
- ### type: "phase"
4198
-
4199
- | phase | Action |
4200
- |-------|--------|
4201
- | explore | Follow response.prompt to explore codebase |
4202
- | clarify | Follow response.prompt to generate questions |
4203
- | execute | Follow response.prompt to write document |
4204
- | verify | Follow response.prompt to validate document |
4205
-
4206
- ### type: "checkpoint"
4207
-
4208
- | checkpoint | Action |
4209
- |------------|--------|
4210
- | clarify | Ask user questions, collect answers |
4211
- | approve | Ask user to review document |
4212
-
4213
- ### type: "complete"
4214
-
4215
- Print completion status and outputs.
4216
-
4217
- ### type: "error"
4218
-
4219
- Print error and stop.
4220
-
4221
- ## Orchestrator Commands
4222
-
4223
- npx spets orchestrate init "<description>"
4224
- npx spets orchestrate explore-done <taskId> '<json>'
4225
- npx spets orchestrate clarify-done <taskId> '<json>'
4226
- npx spets orchestrate clarified <taskId> '<json>'
4227
- npx spets orchestrate execute-done <taskId>
4228
- npx spets orchestrate verify-done <taskId> '<json>'
4229
- npx spets orchestrate approve <taskId>
4230
- npx spets orchestrate revise <taskId> "<feedback>"
4231
- npx spets orchestrate reject <taskId>
4232
- npx spets orchestrate stop <taskId>
4233
-
4234
- $ARGUMENTS
4235
- description: Task description for the workflow
4236
- `;
4237
- }
4238
- function getOpenCodeSkillContent() {
4239
- return `---
4240
- name: spets
4241
- description: SDD workflow executor - runs spec-driven development workflows within OpenCode
4242
- ---
4243
-
4244
- # Spets Command Executor
4245
-
4246
- Execute orchestrator commands. Parse JSON response and execute matching action.
4247
-
4248
- ## Start
4249
-
4250
- Run: npx spets orchestrate init "$ARGUMENTS"
4251
-
4252
- Parse JSON response, then execute action from table below, loop until type: "complete" or "error".
4253
-
4254
- ## Command Table
4255
-
4256
- ### type: "phase"
4257
-
4258
- | phase | Action |
4259
- |-------|--------|
4260
- | explore | Follow response.prompt to explore codebase |
4261
- | clarify | Follow response.prompt to generate questions |
4262
- | execute | Follow response.prompt to write document |
4263
- | verify | Follow response.prompt to validate document |
4264
-
4265
- ### type: "checkpoint"
4266
-
4267
- | checkpoint | Action |
4268
- |------------|--------|
4269
- | clarify | Ask user questions, collect answers |
4270
- | approve | Ask user to review document |
4271
-
4272
- ### type: "complete"
4273
-
4274
- Print completion status and outputs.
4275
-
4276
- ### type: "error"
4277
-
4278
- Print error and stop.
4279
-
4280
- ## Orchestrator Commands
4281
-
4282
- npx spets orchestrate init "<description>"
4283
- npx spets orchestrate explore-done <taskId> '<json>'
4284
- npx spets orchestrate clarify-done <taskId> '<json>'
4285
- npx spets orchestrate clarified <taskId> '<json>'
4286
- npx spets orchestrate execute-done <taskId>
4287
- npx spets orchestrate verify-done <taskId> '<json>'
4288
- npx spets orchestrate approve <taskId>
4289
- npx spets orchestrate revise <taskId> "<feedback>"
4290
- npx spets orchestrate reject <taskId>
4291
- npx spets orchestrate stop <taskId>
4292
-
4293
- $ARGUMENTS
4294
- description: Task description for the workflow
4295
- `;
4296
- }
4297
-
4298
4039
  // src/commands/github.ts
4299
4040
  import { execSync as execSync4, spawn as spawn6, spawnSync as spawnSync4 } from "child_process";
4300
4041
  import { readdirSync as readdirSync4, readFileSync as readFileSync12, existsSync as existsSync14 } from "fs";