spets 0.1.50 → 0.1.52

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 +713 -919
  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,203 @@ 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 spetsDir = join2(process.cwd(), ".codex", "skills", "spets");
93
130
  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 {
131
+ const skillPath = join2(spetsDir, "SKILL.md");
132
+ writeFileSync(skillPath, getCodexSkillContent());
133
+ console.log("Installed Codex CLI plugin.");
134
+ console.log(`Location: ${skillPath}`);
135
+ console.log("");
136
+ console.log("Usage in Codex CLI:");
137
+ console.log(' $spets "your task description"');
138
+ console.log("");
139
+ console.log("This skill runs deterministically within your Codex CLI session.");
140
+ console.log("No additional Codex processes are spawned.");
141
+ }
142
+ function installOpenCodePlugin() {
143
+ const spetsDir = join2(process.cwd(), ".opencode", "skills", "spets");
144
+ mkdirSync(spetsDir, { recursive: true });
145
+ const skillPath = join2(spetsDir, "SKILL.md");
146
+ writeFileSync(skillPath, getOpenCodeSkillContent());
147
+ console.log("Installed OpenCode plugin.");
148
+ console.log(`Location: ${skillPath}`);
149
+ console.log("");
150
+ console.log("Usage in OpenCode:");
151
+ console.log(" Use the spets skill in your OpenCode session");
152
+ console.log("");
153
+ console.log("This skill runs deterministically within your OpenCode session.");
154
+ console.log("No additional OpenCode processes are spawned.");
155
+ }
156
+ async function uninstallPlugin(name) {
157
+ if (name === "claude") {
158
+ const skillPath = join2(homedir(), ".claude", "commands", "spets.md");
159
+ const legacySkillPath = join2(homedir(), ".claude", "commands", "sdd-do.md");
160
+ let uninstalled = false;
161
+ if (existsSync2(skillPath)) {
162
+ rmSync(skillPath);
163
+ uninstalled = true;
164
+ }
165
+ if (existsSync2(legacySkillPath)) {
166
+ rmSync(legacySkillPath);
167
+ uninstalled = true;
168
+ }
169
+ if (uninstalled) {
170
+ console.log("Uninstalled Claude Code plugin.");
171
+ } else {
172
+ console.log("Claude Code plugin not installed.");
105
173
  }
174
+ return;
106
175
  }
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");
176
+ if (name === "codex") {
177
+ const projectSkillPath = join2(process.cwd(), ".codex", "skills", "spets", "SKILL.md");
178
+ const globalSkillPath = join2(homedir(), ".codex", "skills", "spets", "SKILL.md");
179
+ let uninstalled = false;
180
+ for (const skillPath of [projectSkillPath, globalSkillPath]) {
181
+ if (existsSync2(skillPath)) {
182
+ rmSync(skillPath);
183
+ try {
184
+ const skillDir = join2(skillPath, "..");
185
+ const skillsDir = join2(skillDir, "..");
186
+ rmSync(skillDir, { recursive: true });
187
+ const remaining = readdirSync(skillsDir);
188
+ if (remaining.length === 0) {
189
+ rmSync(skillsDir);
190
+ }
191
+ } catch {
192
+ }
193
+ uninstalled = true;
194
+ }
195
+ }
196
+ if (uninstalled) {
197
+ console.log("Uninstalled Codex CLI plugin.");
198
+ } else {
199
+ console.log("Codex CLI plugin not installed.");
200
+ }
201
+ return;
202
+ }
203
+ if (name === "opencode") {
204
+ const projectSkillPath = join2(process.cwd(), ".opencode", "skills", "spets", "SKILL.md");
205
+ const globalSkillPath = join2(homedir(), ".config", "opencode", "skills", "spets", "SKILL.md");
206
+ let uninstalled = false;
207
+ for (const skillPath of [projectSkillPath, globalSkillPath]) {
208
+ if (existsSync2(skillPath)) {
209
+ rmSync(skillPath);
210
+ try {
211
+ const skillDir = join2(skillPath, "..");
212
+ const skillsDir = join2(skillDir, "..");
213
+ rmSync(skillDir, { recursive: true });
214
+ const remaining = readdirSync(skillsDir);
215
+ if (remaining.length === 0) {
216
+ rmSync(skillsDir);
217
+ }
218
+ } catch {
219
+ }
220
+ uninstalled = true;
221
+ }
222
+ }
223
+ if (uninstalled) {
224
+ console.log("Uninstalled OpenCode plugin.");
225
+ } else {
226
+ console.log("OpenCode plugin not installed.");
227
+ }
228
+ return;
123
229
  }
230
+ console.error(`Unknown plugin: ${name}`);
231
+ process.exit(1);
232
+ }
233
+ async function listPlugins() {
234
+ console.log("Available plugins:");
124
235
  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');
236
+ console.log(" claude - Claude Code /spets skill (global)");
237
+ console.log(" codex - Codex CLI $spets skill (project-level)");
238
+ console.log(" opencode - OpenCode spets skill (project-level)");
239
+ console.log("");
240
+ const claudeSkillPath = join2(homedir(), ".claude", "commands", "spets.md");
241
+ const claudeLegacySkillPath = join2(homedir(), ".claude", "commands", "sdd-do.md");
242
+ const claudeInstalled = existsSync2(claudeSkillPath) || existsSync2(claudeLegacySkillPath);
243
+ const codexProjectPath = join2(process.cwd(), ".codex", "skills", "spets", "SKILL.md");
244
+ const codexGlobalPath = join2(homedir(), ".codex", "skills", "spets", "SKILL.md");
245
+ const codexInstalled = existsSync2(codexProjectPath) || existsSync2(codexGlobalPath);
246
+ const codexLocation = existsSync2(codexProjectPath) ? "project" : existsSync2(codexGlobalPath) ? "global" : null;
247
+ const opencodeProjectPath = join2(process.cwd(), ".opencode", "skills", "spets", "SKILL.md");
248
+ const opencodeGlobalPath = join2(homedir(), ".config", "opencode", "skills", "spets", "SKILL.md");
249
+ const opencodeInstalled = existsSync2(opencodeProjectPath) || existsSync2(opencodeGlobalPath);
250
+ const opencodeLocation = existsSync2(opencodeProjectPath) ? "project" : existsSync2(opencodeGlobalPath) ? "global" : null;
251
+ console.log("Installed:");
252
+ const installed = [];
253
+ if (claudeInstalled) installed.push("claude (global)");
254
+ if (codexInstalled) installed.push(`codex (${codexLocation})`);
255
+ if (opencodeInstalled) installed.push(`opencode (${opencodeLocation})`);
256
+ if (installed.length > 0) {
257
+ for (const plugin of installed) {
258
+ console.log(` - ${plugin}`);
259
+ }
131
260
  } else {
132
- console.log(' 3. Run: spets start "your task description"');
261
+ console.log(" (none)");
133
262
  }
134
263
  }
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() {
264
+ function getClaudeSkillContent() {
189
265
  return `# Spets Command Executor
190
266
 
191
267
  Execute orchestrator commands. Parse JSON response \u2192 execute matching action.
@@ -217,11 +293,10 @@ Parse JSON response \u2192 execute action from table below \u2192 loop until \`t
217
293
 
218
294
  | \`phase\` | Action |
219
295
  |---------|--------|
220
- | \`context\` | [ACTION_CONTEXT](#action_context) |
296
+ | \`explore\` | [ACTION_EXPLORE](#action_explore) |
221
297
  | \`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) |
298
+ | \`execute\` | [ACTION_EXECUTE](#action_execute) |
299
+ | \`verify\` | [ACTION_VERIFY](#action_verify) |
225
300
 
226
301
  ### \`type: "checkpoint"\`
227
302
 
@@ -246,96 +321,63 @@ Print: "Error: {error}" \u2192 STOP
246
321
 
247
322
  ## Action Definitions
248
323
 
249
- ### ACTION_CONTEXT
324
+ **IMPORTANT:** Each phase response includes a \`prompt\` field containing the exact instructions to follow.
325
+ This ensures identical behavior across CLI, Claude Code, and GitHub modes.
326
+
327
+ ### ACTION_EXPLORE
250
328
 
251
329
  \`\`\`
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
330
+ 1. Follow the instructions in response.prompt EXACTLY
331
+ 2. The prompt tells you:
332
+ - What to search for
333
+ - How to analyze the codebase
334
+ - The exact JSON output format required
335
+ 3. Bash: npx spets orchestrate explore-done {taskId} '{exploreJson}'
336
+ 4. Parse response \u2192 next action
257
337
  \`\`\`
258
338
 
259
339
  ### ACTION_CLARIFY
260
340
 
261
341
  \`\`\`
262
- 1. Analyze: gatheredContext, previousQA (if exists)
263
- 2. Generate questions array (0-4 questions)
342
+ 1. Follow the instructions in response.prompt EXACTLY
343
+ 2. The prompt tells you:
344
+ - What context to analyze
345
+ - How to generate questions
346
+ - The exact JSON output format required
264
347
  3. Bash: npx spets orchestrate clarify-done {taskId} '{questionsJson}'
265
348
  - Format: [{"id":"q1","question":"..."},...]
266
349
  - Empty if no questions: []
267
350
  4. Parse response \u2192 next action
268
351
  \`\`\`
269
352
 
270
- ### ACTION_GENERATE
353
+ ### ACTION_EXECUTE
271
354
 
272
355
  \`\`\`
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
280
- \`\`\`
281
-
282
- ### ACTION_PARALLEL_SECTIONS
283
-
284
- **PARALLEL EXECUTION (FOREGROUND)**
285
-
356
+ 1. Follow the instructions in response.prompt EXACTLY
357
+ 2. The prompt tells you:
358
+ - The instruction and template to follow
359
+ - The explore output and answers to use
360
+ - Where to save the document/code (context.output)
361
+ - Any revision/verify feedback to address
362
+ 3. Write the document/code to context.output
363
+ 4. Bash: npx spets orchestrate execute-done {taskId}
364
+ 5. Parse response \u2192 next action
286
365
  \`\`\`
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
366
 
307
- 3. Bash: npx spets orchestrate sections-done {taskId}
367
+ ### ACTION_VERIFY
308
368
 
309
- 4. Parse response \u2192 next action
310
369
  \`\`\`
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
-
370
+ 1. Follow the instructions in response.prompt EXACTLY
371
+ 2. The prompt tells you:
372
+ - The document to verify (already included in prompt)
373
+ - The requirements and template to check against
374
+ - The scoring criteria and pass conditions
375
+ - The exact JSON output format required
376
+ 3. Bash: npx spets orchestrate verify-done {taskId} '{verifyJson}'
338
377
  4. Parse response \u2192 next action
378
+ - If passed: goes to human review
379
+ - If failed (attempts < 3): goes back to draft phase (auto-fix)
380
+ - If failed (attempts >= 3): goes to human review with warning
339
381
  \`\`\`
340
382
 
341
383
  ### ACTION_ASK_QUESTIONS
@@ -397,13 +439,13 @@ Print: "Error: {error}" \u2192 STOP
397
439
  ## Orchestrator Commands Reference
398
440
 
399
441
  \`\`\`bash
442
+ # New 5-phase workflow
400
443
  npx spets orchestrate init "<description>"
401
- npx spets orchestrate context-done <taskId>
444
+ npx spets orchestrate explore-done <taskId> '<json>'
402
445
  npx spets orchestrate clarify-done <taskId> '<json>'
403
446
  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>
447
+ npx spets orchestrate execute-done <taskId>
448
+ npx spets orchestrate verify-done <taskId> '<json>'
407
449
  npx spets orchestrate approve <taskId>
408
450
  npx spets orchestrate revise <taskId> "<feedback>"
409
451
  npx spets orchestrate reject <taskId>
@@ -416,111 +458,389 @@ $ARGUMENTS
416
458
  description: Task description for the workflow
417
459
  `;
418
460
  }
419
- 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);
428
- createGitHubLabel();
429
- }
430
- function createGitHubLabel() {
431
- try {
432
- execSync("gh --version", { stdio: "ignore" });
433
- execSync('gh label create spets --description "Spets workflow task" --color 0E8A16 --force', {
434
- stdio: "ignore"
435
- });
436
- console.log(' GitHub label "spets" created');
437
- } catch {
438
- console.log("");
439
- console.log(" Note: Could not create GitHub label automatically.");
440
- console.log(' Please create the "spets" label manually or run:');
441
- console.log(' gh label create spets --description "Spets workflow task" --color 0E8A16');
442
- }
443
- }
461
+ function getCodexSkillContent() {
462
+ return `---
463
+ name: spets
464
+ description: SDD workflow executor - runs spec-driven development workflows within Codex CLI
465
+ ---
444
466
 
445
- // 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";
448
- import matter from "gray-matter";
467
+ # Spets Command Executor
449
468
 
450
- // src/core/slug.ts
451
- function generateRandomSuffix() {
452
- return Math.random().toString(36).substring(2, 6);
453
- }
454
- function generateDatePrefix() {
455
- const now = /* @__PURE__ */ new Date();
456
- const year = now.getFullYear();
457
- const month = String(now.getMonth() + 1).padStart(2, "0");
458
- const day = String(now.getDate()).padStart(2, "0");
459
- return `${year}-${month}-${day}`;
460
- }
461
- function sanitizeDescription(description) {
462
- let sanitized = description.toLowerCase();
463
- sanitized = sanitized.replace(/[^\x00-\x7F]/g, "");
464
- sanitized = sanitized.replace(/[^a-z0-9\s]/g, "");
465
- sanitized = sanitized.trim();
466
- sanitized = sanitized.replace(/\s+/g, " ");
467
- sanitized = sanitized.replace(/\s+/g, "-");
468
- if (sanitized.length > 10) {
469
- sanitized = sanitized.substring(0, 10);
470
- }
471
- sanitized = sanitized.replace(/-+$/, "");
472
- if (sanitized === "") {
473
- sanitized = "task";
474
- }
475
- return sanitized;
476
- }
477
- function generateSlug(description) {
478
- const datePrefix = generateDatePrefix();
479
- const meaningfulName = sanitizeDescription(description);
480
- const randomSuffix = generateRandomSuffix();
481
- return `${datePrefix}-${meaningfulName}-${randomSuffix}`;
482
- }
469
+ Execute orchestrator commands. Parse JSON response \u2192 execute matching action.
483
470
 
484
- // src/core/state.ts
485
- function generateTaskId(description) {
486
- if (description) {
487
- return generateSlug(description);
488
- }
489
- const timestamp = Date.now().toString(36);
490
- const random = Math.random().toString(36).substring(2, 6);
491
- return `${timestamp}-${random}`;
492
- }
493
- function getTaskDir(taskId, cwd = process.cwd()) {
494
- return join3(getOutputsDir(cwd), taskId);
471
+ ---
472
+
473
+ ## Constraints
474
+
475
+ - **IMMEDIATELY** execute commands, no planning
476
+ - **DO NOT** explore or search on your own - ONLY follow response.prompt
477
+
478
+ ---
479
+
480
+ ## Start
481
+
482
+ \`\`\`bash
483
+ npx spets orchestrate init "$ARGUMENTS"
484
+ \`\`\`
485
+
486
+ Parse JSON response \u2192 execute action from table below \u2192 loop until \`type: "complete"\` or \`type: "error"\`.
487
+
488
+ ---
489
+
490
+ ## Command Table
491
+
492
+ ### \`type: "phase"\`
493
+
494
+ | \`phase\` | Action |
495
+ |---------|--------|
496
+ | \`explore\` | [ACTION_EXPLORE](#action_explore) |
497
+ | \`clarify\` | [ACTION_CLARIFY](#action_clarify) |
498
+ | \`execute\` | [ACTION_EXECUTE](#action_execute) |
499
+ | \`verify\` | [ACTION_VERIFY](#action_verify) |
500
+
501
+ ### \`type: "checkpoint"\`
502
+
503
+ | \`checkpoint\` | Action |
504
+ |--------------|--------|
505
+ | \`clarify\` | [ACTION_ASK_QUESTIONS](#action_ask_questions) |
506
+ | \`approve\` | [ACTION_ASK_APPROVAL](#action_ask_approval) |
507
+
508
+ ### \`type: "complete"\`
509
+
510
+ | \`status\` | Action |
511
+ |----------|--------|
512
+ | \`completed\` | Print: "Workflow complete. Outputs: {outputs}" |
513
+ | \`stopped\` | Print: "Workflow paused. Resume with: npx spets resume --task {taskId}" |
514
+ | \`rejected\` | Print: "Workflow rejected." |
515
+
516
+ ### \`type: "error"\`
517
+
518
+ Print: "Error: {error}" \u2192 STOP
519
+
520
+ ---
521
+
522
+ ## Action Definitions
523
+
524
+ **IMPORTANT:** Each phase response includes a \`prompt\` field containing the exact instructions to follow.
525
+ You MUST follow the prompt EXACTLY. Do NOT explore or search on your own.
526
+
527
+ ### ACTION_EXPLORE
528
+
529
+ \`\`\`
530
+ 1. Follow the instructions in response.prompt EXACTLY
531
+ 2. The prompt tells you:
532
+ - What to search for
533
+ - How to analyze the codebase
534
+ - The exact JSON output format required
535
+ 3. Run: npx spets orchestrate explore-done {taskId} '{exploreJson}'
536
+ 4. Parse response \u2192 next action
537
+ \`\`\`
538
+
539
+ ### ACTION_CLARIFY
540
+
541
+ \`\`\`
542
+ 1. Follow the instructions in response.prompt EXACTLY
543
+ 2. The prompt tells you:
544
+ - What context to analyze
545
+ - How to generate questions
546
+ - The exact JSON output format required
547
+ 3. Run: npx spets orchestrate clarify-done {taskId} '{questionsJson}'
548
+ - Format: [{"id":"q1","question":"..."},...]
549
+ - Empty if no questions: []
550
+ 4. Parse response \u2192 next action
551
+ \`\`\`
552
+
553
+ ### ACTION_EXECUTE
554
+
555
+ \`\`\`
556
+ 1. Follow the instructions in response.prompt EXACTLY
557
+ 2. The prompt tells you:
558
+ - The instruction and template to follow
559
+ - The explore output and answers to use
560
+ - Where to save the document/code (context.output)
561
+ - Any revision/verify feedback to address
562
+ 3. Write the document/code to context.output
563
+ 4. Run: npx spets orchestrate execute-done {taskId}
564
+ 5. Parse response \u2192 next action
565
+ \`\`\`
566
+
567
+ ### ACTION_VERIFY
568
+
569
+ \`\`\`
570
+ 1. Follow the instructions in response.prompt EXACTLY
571
+ 2. The prompt tells you:
572
+ - The document to verify (already included in prompt)
573
+ - The requirements and template to check against
574
+ - The scoring criteria and pass conditions
575
+ - The exact JSON output format required
576
+ 3. Run: npx spets orchestrate verify-done {taskId} '{verifyJson}'
577
+ 4. Parse response \u2192 next action
578
+ - If passed: goes to human review
579
+ - If failed (attempts < 3): goes back to draft phase (auto-fix)
580
+ - If failed (attempts >= 3): goes to human review with warning
581
+ \`\`\`
582
+
583
+ ### ACTION_ASK_QUESTIONS
584
+
585
+ \`\`\`
586
+ 1. Ask user each question from response.questions
587
+ 2. Collect answers into: [{"questionId":"q1","answer":"..."},...]
588
+ 3. Run: npx spets orchestrate clarified {taskId} '{answersJson}'
589
+ 4. Parse response \u2192 next action
590
+ \`\`\`
591
+
592
+ ### ACTION_ASK_APPROVAL
593
+
594
+ \`\`\`
595
+ 1. Read the document at context.output
596
+ 2. Summarize document (2-3 sentences)
597
+ 3. Ask user: "Review {step} document. Approve / Revise / Reject / Stop?"
598
+ 4. Based on answer:
599
+ - Approve: npx spets orchestrate approve {taskId}
600
+ - Revise: Ask for feedback, then: npx spets orchestrate revise {taskId} "{feedback}"
601
+ - Reject: npx spets orchestrate reject {taskId}
602
+ - Stop: npx spets orchestrate stop {taskId}
603
+ 5. Parse response \u2192 next action
604
+ \`\`\`
605
+
606
+ ---
607
+
608
+ $ARGUMENTS
609
+ description: Task description for the workflow
610
+ `;
495
611
  }
496
- function getOutputPath(taskId, stepName, cwd = process.cwd()) {
497
- return join3(getTaskDir(taskId, cwd), `${stepName}.md`);
612
+ function getOpenCodeSkillContent() {
613
+ return getCodexSkillContent().replace(
614
+ "runs spec-driven development workflows within Codex CLI",
615
+ "runs spec-driven development workflows within OpenCode"
616
+ );
498
617
  }
499
- function loadDocument(taskId, stepName, cwd = process.cwd()) {
500
- const outputPath = getOutputPath(taskId, stepName, cwd);
501
- if (!existsSync3(outputPath)) {
618
+
619
+ // src/commands/init.ts
620
+ var __dirname = dirname(fileURLToPath(import.meta.url));
621
+ function getGitHubInfoFromRemote() {
622
+ try {
623
+ const remoteUrl = execSync("git remote get-url origin", { encoding: "utf-8" }).trim();
624
+ const sshMatch = remoteUrl.match(/git@github\.com:([^/]+)\/(.+?)(?:\.git)?$/);
625
+ if (sshMatch) {
626
+ return { owner: sshMatch[1], repo: sshMatch[2] };
627
+ }
628
+ const httpsMatch = remoteUrl.match(/https:\/\/github\.com\/([^/]+)\/(.+?)(?:\.git)?$/);
629
+ if (httpsMatch) {
630
+ return { owner: httpsMatch[1], repo: httpsMatch[2] };
631
+ }
632
+ return null;
633
+ } catch {
502
634
  return null;
503
635
  }
504
- const raw = readFileSync3(outputPath, "utf-8");
505
- const { content, data } = matter(raw);
506
- return {
507
- content,
508
- frontmatter: data
509
- };
510
636
  }
511
- function listTasks(cwd = process.cwd()) {
512
- const outputsDir = getOutputsDir(cwd);
513
- if (!existsSync3(outputsDir)) {
514
- return [];
637
+ async function initCommand(options) {
638
+ const cwd = process.cwd();
639
+ const spetsDir = getSpetsDir(cwd);
640
+ if (spetsExists(cwd) && !options.force) {
641
+ console.error("Spets already initialized. Use --force to overwrite.");
642
+ process.exit(1);
515
643
  }
516
- return readdirSync(outputsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort().reverse();
517
- }
518
- function getStateCachePath(taskId, cwd = process.cwd()) {
519
- return join3(getTaskDir(taskId, cwd), ".state-cache.json");
520
- }
644
+ mkdirSync2(spetsDir, { recursive: true });
645
+ mkdirSync2(join3(spetsDir, "steps"), { recursive: true });
646
+ mkdirSync2(join3(spetsDir, "outputs"), { recursive: true });
647
+ mkdirSync2(join3(spetsDir, "hooks"), { recursive: true });
648
+ const templatesDir = join3(__dirname, "..", "templates");
649
+ const hookTemplate = join3(__dirname, "..", "templates", "hooks", "cleanup-branch.sh");
650
+ if (existsSync3(hookTemplate)) {
651
+ const hookDest = join3(spetsDir, "hooks", "cleanup-branch.sh");
652
+ cpSync(hookTemplate, hookDest);
653
+ try {
654
+ execSync(`chmod +x "${hookDest}"`);
655
+ } catch {
656
+ }
657
+ }
658
+ const githubInfo = getGitHubInfoFromRemote();
659
+ writeFileSync2(join3(spetsDir, "config.yml"), getDefaultConfig(githubInfo));
660
+ createDefaultSteps(spetsDir);
661
+ createClaudeCommand(cwd);
662
+ console.log("Initialized spets in .spets/");
663
+ console.log("");
664
+ console.log("Created:");
665
+ console.log(" .spets/config.yml - Workflow configuration");
666
+ console.log(" .spets/steps/01-plan/template.md - Planning step template");
667
+ console.log(" .spets/steps/02-implement/template.md - Implementation step template");
668
+ console.log(" .spets/hooks/cleanup-branch.sh - Example branch cleanup hook");
669
+ console.log(" .claude/commands/spets.md - Claude Code command");
670
+ if (options.github) {
671
+ createGitHubWorkflow(cwd);
672
+ console.log(" .github/workflows/spets.yml - GitHub Actions workflow");
673
+ console.log(" .github/ISSUE_TEMPLATE/spets-task.yml - Issue template");
674
+ }
675
+ console.log("");
676
+ console.log("Next steps:");
677
+ console.log(" 1. Edit .spets/config.yml to customize your workflow");
678
+ console.log(" 2. Customize step templates in .spets/steps/");
679
+ if (options.github) {
680
+ console.log(" 3. Add CLAUDE_CODE_OAUTH_TOKEN to your repo secrets");
681
+ console.log(' 4. Create a new Issue using the "Spets Task" template');
682
+ } else {
683
+ console.log(' 3. Run: spets start "your task description"');
684
+ }
685
+ }
686
+ function getDefaultConfig(githubInfo) {
687
+ const githubSection = githubInfo ? `
688
+ # GitHub integration (auto-detected from git remote)
689
+ github:
690
+ owner: ${githubInfo.owner}
691
+ repo: ${githubInfo.repo}
692
+ ` : `
693
+ # GitHub integration (uncomment and fill in to enable)
694
+ # github:
695
+ # owner: your-org
696
+ # repo: your-repo
697
+ `;
698
+ return `# Spets Configuration
699
+ # Define your workflow steps here
700
+
701
+ steps:
702
+ - 01-plan
703
+ - 02-implement
704
+
705
+ # Output configuration
706
+ output:
707
+ path: .spets/outputs
708
+ ${githubSection}
709
+ # Optional hooks (shell scripts)
710
+ # hooks:
711
+ # preStep: "./hooks/pre-step.sh"
712
+ # postStep: "./hooks/post-step.sh"
713
+ # onApprove: "./hooks/on-approve.sh"
714
+ # onReject: "./hooks/cleanup-branch.sh"
715
+ # onComplete: "./hooks/cleanup-branch.sh"
716
+ `;
717
+ }
718
+ function createDefaultSteps(spetsDir) {
719
+ const planDir = join3(spetsDir, "steps", "01-plan");
720
+ mkdirSync2(planDir, { recursive: true });
721
+ writeFileSync2(join3(planDir, "template.md"), getPlanTemplate());
722
+ const implementDir = join3(spetsDir, "steps", "02-implement");
723
+ mkdirSync2(implementDir, { recursive: true });
724
+ writeFileSync2(join3(implementDir, "template.md"), getImplementTemplate());
725
+ }
726
+ function getPlanTemplate() {
727
+ const fullTemplate = readFileSync2(join3(__dirname, "..", "templates", "steps", "01-plan", "template.md"), "utf-8");
728
+ return fullTemplate;
729
+ }
730
+ function getImplementTemplate() {
731
+ const fullTemplate = readFileSync2(join3(__dirname, "..", "templates", "steps", "02-implement", "template.md"), "utf-8");
732
+ return fullTemplate;
733
+ }
734
+ function createClaudeCommand(cwd) {
735
+ const commandDir = join3(cwd, ".claude", "commands");
736
+ mkdirSync2(commandDir, { recursive: true });
737
+ writeFileSync2(join3(commandDir, "spets.md"), getClaudeSkillContent());
738
+ }
739
+ function createGitHubWorkflow(cwd) {
740
+ const workflowDir = join3(cwd, ".github", "workflows");
741
+ const templateDir = join3(cwd, ".github", "ISSUE_TEMPLATE");
742
+ mkdirSync2(workflowDir, { recursive: true });
743
+ mkdirSync2(templateDir, { recursive: true });
744
+ const workflowTemplate = readFileSync2(join3(__dirname, "..", "assets", "github", "workflows", "spets.yml"), "utf-8");
745
+ const issueTemplate = readFileSync2(join3(__dirname, "..", "assets", "github", "ISSUE_TEMPLATE", "spets-task.yml"), "utf-8");
746
+ writeFileSync2(join3(workflowDir, "spets.yml"), workflowTemplate);
747
+ writeFileSync2(join3(templateDir, "spets-task.yml"), issueTemplate);
748
+ createGitHubLabel();
749
+ }
750
+ function createGitHubLabel() {
751
+ try {
752
+ execSync("gh --version", { stdio: "ignore" });
753
+ execSync('gh label create spets --description "Spets workflow task" --color 0E8A16 --force', {
754
+ stdio: "ignore"
755
+ });
756
+ console.log(' GitHub label "spets" created');
757
+ } catch {
758
+ console.log("");
759
+ console.log(" Note: Could not create GitHub label automatically.");
760
+ console.log(' Please create the "spets" label manually or run:');
761
+ console.log(' gh label create spets --description "Spets workflow task" --color 0E8A16');
762
+ }
763
+ }
764
+
765
+ // src/core/state.ts
766
+ import { readFileSync as readFileSync3, existsSync as existsSync4, readdirSync as readdirSync2, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
767
+ import { join as join4 } from "path";
768
+ import matter from "gray-matter";
769
+
770
+ // src/core/slug.ts
771
+ function generateRandomSuffix() {
772
+ return Math.random().toString(36).substring(2, 6);
773
+ }
774
+ function generateDatePrefix() {
775
+ const now = /* @__PURE__ */ new Date();
776
+ const year = now.getFullYear();
777
+ const month = String(now.getMonth() + 1).padStart(2, "0");
778
+ const day = String(now.getDate()).padStart(2, "0");
779
+ return `${year}-${month}-${day}`;
780
+ }
781
+ function sanitizeDescription(description) {
782
+ let sanitized = description.toLowerCase();
783
+ sanitized = sanitized.replace(/[^\x00-\x7F]/g, "");
784
+ sanitized = sanitized.replace(/[^a-z0-9\s]/g, "");
785
+ sanitized = sanitized.trim();
786
+ sanitized = sanitized.replace(/\s+/g, " ");
787
+ sanitized = sanitized.replace(/\s+/g, "-");
788
+ if (sanitized.length > 10) {
789
+ sanitized = sanitized.substring(0, 10);
790
+ }
791
+ sanitized = sanitized.replace(/-+$/, "");
792
+ if (sanitized === "") {
793
+ sanitized = "task";
794
+ }
795
+ return sanitized;
796
+ }
797
+ function generateSlug(description) {
798
+ const datePrefix = generateDatePrefix();
799
+ const meaningfulName = sanitizeDescription(description);
800
+ const randomSuffix = generateRandomSuffix();
801
+ return `${datePrefix}-${meaningfulName}-${randomSuffix}`;
802
+ }
803
+
804
+ // src/core/state.ts
805
+ function generateTaskId(description) {
806
+ if (description) {
807
+ return generateSlug(description);
808
+ }
809
+ const timestamp = Date.now().toString(36);
810
+ const random = Math.random().toString(36).substring(2, 6);
811
+ return `${timestamp}-${random}`;
812
+ }
813
+ function getTaskDir(taskId, cwd = process.cwd()) {
814
+ return join4(getOutputsDir(cwd), taskId);
815
+ }
816
+ function getOutputPath(taskId, stepName, cwd = process.cwd()) {
817
+ return join4(getTaskDir(taskId, cwd), `${stepName}.md`);
818
+ }
819
+ function loadDocument(taskId, stepName, cwd = process.cwd()) {
820
+ const outputPath = getOutputPath(taskId, stepName, cwd);
821
+ if (!existsSync4(outputPath)) {
822
+ return null;
823
+ }
824
+ const raw = readFileSync3(outputPath, "utf-8");
825
+ const { content, data } = matter(raw);
826
+ return {
827
+ content,
828
+ frontmatter: data
829
+ };
830
+ }
831
+ function listTasks(cwd = process.cwd()) {
832
+ const outputsDir = getOutputsDir(cwd);
833
+ if (!existsSync4(outputsDir)) {
834
+ return [];
835
+ }
836
+ return readdirSync2(outputsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort().reverse();
837
+ }
838
+ function getStateCachePath(taskId, cwd = process.cwd()) {
839
+ return join4(getTaskDir(taskId, cwd), ".state-cache.json");
840
+ }
521
841
  function loadStateCache(taskId, cwd = process.cwd()) {
522
842
  const cachePath = getStateCachePath(taskId, cwd);
523
- if (!existsSync3(cachePath)) {
843
+ if (!existsSync4(cachePath)) {
524
844
  return null;
525
845
  }
526
846
  try {
@@ -546,7 +866,7 @@ function saveStateCache(taskId, state, cwd = process.cwd()) {
546
866
  stepStatuses
547
867
  };
548
868
  try {
549
- writeFileSync2(cachePath, JSON.stringify(cached, null, 2));
869
+ writeFileSync3(cachePath, JSON.stringify(cached, null, 2));
550
870
  } catch {
551
871
  }
552
872
  }
@@ -556,7 +876,7 @@ function isStateCacheValid(cached, maxAgeMs = 5e3) {
556
876
  }
557
877
  function getWorkflowState(taskId, config, cwd = process.cwd()) {
558
878
  const taskDir = getTaskDir(taskId, cwd);
559
- if (!existsSync3(taskDir)) {
879
+ if (!existsSync4(taskDir)) {
560
880
  return null;
561
881
  }
562
882
  const cached = loadStateCache(taskId, cwd);
@@ -623,8 +943,8 @@ function getWorkflowState(taskId, config, cwd = process.cwd()) {
623
943
  return state;
624
944
  }
625
945
  function loadTaskMetadata(taskId, cwd = process.cwd()) {
626
- const metaPath = join3(getTaskDir(taskId, cwd), ".meta.json");
627
- if (!existsSync3(metaPath)) {
946
+ const metaPath = join4(getTaskDir(taskId, cwd), ".meta.json");
947
+ if (!existsSync4(metaPath)) {
628
948
  return null;
629
949
  }
630
950
  return JSON.parse(readFileSync3(metaPath, "utf-8"));
@@ -715,13 +1035,13 @@ function formatDocStatus(status) {
715
1035
  import { execSync as execSync2 } from "child_process";
716
1036
 
717
1037
  // 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";
1038
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
1039
+ import { join as join6, dirname as dirname2 } from "path";
720
1040
  import matter2 from "gray-matter";
721
1041
 
722
1042
  // src/core/prompt-builder.ts
723
- import { readFileSync as readFileSync4, existsSync as existsSync4 } from "fs";
724
- import { join as join4 } from "path";
1043
+ import { readFileSync as readFileSync4, existsSync as existsSync5 } from "fs";
1044
+ import { join as join5 } from "path";
725
1045
  function buildContextPrompt(params) {
726
1046
  const cwd = params.cwd || process.cwd();
727
1047
  const isFirstStep = params.stepIndex === 1;
@@ -744,7 +1064,7 @@ function buildContextPrompt(params) {
744
1064
  } else if (params.previousOutput) {
745
1065
  parts.push("## Previous Step Output");
746
1066
  parts.push("");
747
- if (existsSync4(params.previousOutput)) {
1067
+ if (existsSync5(params.previousOutput)) {
748
1068
  parts.push(readFileSync4(params.previousOutput, "utf-8"));
749
1069
  }
750
1070
  parts.push("");
@@ -796,7 +1116,7 @@ function buildExplorePrompt(params) {
796
1116
  } else if (params.previousOutput) {
797
1117
  parts.push("## Previous Step Output");
798
1118
  parts.push("");
799
- if (existsSync4(params.previousOutput)) {
1119
+ if (existsSync5(params.previousOutput)) {
800
1120
  parts.push(readFileSync4(params.previousOutput, "utf-8"));
801
1121
  }
802
1122
  parts.push("");
@@ -935,13 +1255,13 @@ function buildGeneratePrompt(params) {
935
1255
  const outputsDir = getOutputsDir(cwd);
936
1256
  const isFirstStep = params.stepIndex === 1;
937
1257
  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;
1258
+ const templatePath = join5(stepsDir, params.step, "template.md");
1259
+ const outputPath = join5(outputsDir, params.taskId, `${params.step}.md`);
1260
+ const template = existsSync5(templatePath) ? readFileSync4(templatePath, "utf-8") : null;
941
1261
  let previousSpec = null;
942
1262
  if (prevStep) {
943
- const prevPath = join4(outputsDir, params.taskId, `${prevStep}.md`);
944
- if (existsSync4(prevPath)) {
1263
+ const prevPath = join5(outputsDir, params.taskId, `${prevStep}.md`);
1264
+ if (existsSync5(prevPath)) {
945
1265
  previousSpec = {
946
1266
  step: prevStep,
947
1267
  content: readFileSync4(prevPath, "utf-8")
@@ -1022,13 +1342,13 @@ function buildExecutePrompt(params) {
1022
1342
  const outputsDir = getOutputsDir(cwd);
1023
1343
  const isFirstStep = params.stepIndex === 1;
1024
1344
  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;
1345
+ const templatePath = join5(stepsDir, params.step, "template.md");
1346
+ const outputPath = join5(outputsDir, params.taskId, `${params.step}.md`);
1347
+ const template = existsSync5(templatePath) ? readFileSync4(templatePath, "utf-8") : null;
1028
1348
  let previousSpec = null;
1029
1349
  if (prevStep) {
1030
- const prevPath = join4(outputsDir, params.taskId, `${prevStep}.md`);
1031
- if (existsSync4(prevPath)) {
1350
+ const prevPath = join5(outputsDir, params.taskId, `${prevStep}.md`);
1351
+ if (existsSync5(prevPath)) {
1032
1352
  previousSpec = {
1033
1353
  step: prevStep,
1034
1354
  content: readFileSync4(prevPath, "utf-8")
@@ -1147,9 +1467,9 @@ function buildExecutePrompt(params) {
1147
1467
  function buildVerifyPrompt(params) {
1148
1468
  const cwd = params.cwd || process.cwd();
1149
1469
  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") : "";
1470
+ const templatePath = join5(stepsDir, params.step, "template.md");
1471
+ const template = existsSync5(templatePath) ? readFileSync4(templatePath, "utf-8") : "";
1472
+ const document = existsSync5(params.documentPath) ? readFileSync4(params.documentPath, "utf-8") : "";
1153
1473
  const parts = [];
1154
1474
  parts.push("# Verify Phase - Self-Validation");
1155
1475
  parts.push("");
@@ -1253,20 +1573,20 @@ var Orchestrator = class {
1253
1573
  return getOutputsDir(this.cwd);
1254
1574
  }
1255
1575
  getStatePath(taskId) {
1256
- return join5(this.getOutputPath(), taskId, ".state.json");
1576
+ return join6(this.getOutputPath(), taskId, ".state.json");
1257
1577
  }
1258
1578
  getSpecPath(taskId, step) {
1259
- return join5(this.getOutputPath(), taskId, `${step}.md`);
1579
+ return join6(this.getOutputPath(), taskId, `${step}.md`);
1260
1580
  }
1261
1581
  getStepTemplatePath(step) {
1262
- return join5(getStepsDir(this.cwd), step, "template.md");
1582
+ return join6(getStepsDir(this.cwd), step, "template.md");
1263
1583
  }
1264
1584
  // ===========================================================================
1265
1585
  // State Management
1266
1586
  // ===========================================================================
1267
1587
  loadState(taskId) {
1268
1588
  const statePath = this.getStatePath(taskId);
1269
- if (!existsSync5(statePath)) {
1589
+ if (!existsSync6(statePath)) {
1270
1590
  return null;
1271
1591
  }
1272
1592
  const data = JSON.parse(readFileSync5(statePath, "utf-8"));
@@ -1276,16 +1596,16 @@ var Orchestrator = class {
1276
1596
  state.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1277
1597
  const statePath = this.getStatePath(state.taskId);
1278
1598
  const dir = dirname2(statePath);
1279
- if (!existsSync5(dir)) {
1280
- mkdirSync3(dir, { recursive: true });
1599
+ if (!existsSync6(dir)) {
1600
+ mkdirSync4(dir, { recursive: true });
1281
1601
  }
1282
- writeFileSync3(statePath, JSON.stringify(state, null, 2));
1602
+ writeFileSync4(statePath, JSON.stringify(state, null, 2));
1283
1603
  }
1284
1604
  // ===========================================================================
1285
1605
  // Spec Helpers
1286
1606
  // ===========================================================================
1287
1607
  checkUnresolvedQuestions(specPath) {
1288
- if (!existsSync5(specPath)) {
1608
+ if (!existsSync6(specPath)) {
1289
1609
  return [];
1290
1610
  }
1291
1611
  const content = readFileSync5(specPath, "utf-8");
@@ -1320,8 +1640,8 @@ var Orchestrator = class {
1320
1640
  let previousOutput;
1321
1641
  if (state.stepIndex > 1) {
1322
1642
  const prevStep = steps[state.stepIndex - 2];
1323
- const prevPath = join5(outputPath, state.taskId, `${prevStep}.md`);
1324
- if (existsSync5(prevPath)) {
1643
+ const prevPath = join6(outputPath, state.taskId, `${prevStep}.md`);
1644
+ if (existsSync6(prevPath)) {
1325
1645
  previousOutput = prevPath;
1326
1646
  }
1327
1647
  }
@@ -1348,8 +1668,8 @@ var Orchestrator = class {
1348
1668
  let previousOutput;
1349
1669
  if (state.stepIndex > 1) {
1350
1670
  const prevStep = steps[state.stepIndex - 2];
1351
- const prevPath = join5(outputPath, state.taskId, `${prevStep}.md`);
1352
- if (existsSync5(prevPath)) {
1671
+ const prevPath = join6(outputPath, state.taskId, `${prevStep}.md`);
1672
+ if (existsSync6(prevPath)) {
1353
1673
  previousOutput = prevPath;
1354
1674
  }
1355
1675
  }
@@ -1411,13 +1731,13 @@ var Orchestrator = class {
1411
1731
  let previousOutput;
1412
1732
  if (state.stepIndex > 1) {
1413
1733
  const prevStep = steps[state.stepIndex - 2];
1414
- const prevPath = join5(outputPath, state.taskId, `${prevStep}.md`);
1415
- if (existsSync5(prevPath)) {
1734
+ const prevPath = join6(outputPath, state.taskId, `${prevStep}.md`);
1735
+ if (existsSync6(prevPath)) {
1416
1736
  previousOutput = prevPath;
1417
1737
  }
1418
1738
  }
1419
1739
  const templatePath = this.getStepTemplatePath(state.currentStep);
1420
- const hasTemplate = existsSync5(templatePath);
1740
+ const hasTemplate = existsSync6(templatePath);
1421
1741
  return {
1422
1742
  type: "phase",
1423
1743
  phase: "generate",
@@ -1431,7 +1751,7 @@ var Orchestrator = class {
1431
1751
  context: {
1432
1752
  template: hasTemplate ? templatePath : void 0,
1433
1753
  previousOutput,
1434
- output: join5(outputPath, state.taskId, `${state.currentStep}.md`),
1754
+ output: join6(outputPath, state.taskId, `${state.currentStep}.md`),
1435
1755
  revisionFeedback: state.revisionFeedback
1436
1756
  },
1437
1757
  onComplete: `generate-done ${state.taskId}`
@@ -1446,13 +1766,13 @@ var Orchestrator = class {
1446
1766
  let previousOutput;
1447
1767
  if (state.stepIndex > 1) {
1448
1768
  const prevStep = steps[state.stepIndex - 2];
1449
- const prevPath = join5(outputPath, state.taskId, `${prevStep}.md`);
1450
- if (existsSync5(prevPath)) {
1769
+ const prevPath = join6(outputPath, state.taskId, `${prevStep}.md`);
1770
+ if (existsSync6(prevPath)) {
1451
1771
  previousOutput = prevPath;
1452
1772
  }
1453
1773
  }
1454
1774
  const templatePath = this.getStepTemplatePath(state.currentStep);
1455
- const hasTemplate = existsSync5(templatePath);
1775
+ const hasTemplate = existsSync6(templatePath);
1456
1776
  let verifyFeedback;
1457
1777
  if (state.verifyOutput && !state.verifyOutput.passed && state.verifyAttempts && state.verifyAttempts > 1) {
1458
1778
  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 +1812,7 @@ ${issues}`;
1492
1812
  context: {
1493
1813
  template: hasTemplate ? templatePath : void 0,
1494
1814
  previousOutput,
1495
- output: join5(outputPath, state.taskId, `${state.currentStep}.md`),
1815
+ output: join6(outputPath, state.taskId, `${state.currentStep}.md`),
1496
1816
  revisionFeedback: state.revisionFeedback,
1497
1817
  verifyFeedback
1498
1818
  },
@@ -1505,8 +1825,8 @@ ${issues}`;
1505
1825
  responsePhaseVerify(state) {
1506
1826
  const outputPath = this.getOutputPath();
1507
1827
  const templatePath = this.getStepTemplatePath(state.currentStep);
1508
- const hasTemplate = existsSync5(templatePath);
1509
- const documentPath = join5(outputPath, state.taskId, `${state.currentStep}.md`);
1828
+ const hasTemplate = existsSync6(templatePath);
1829
+ const documentPath = join6(outputPath, state.taskId, `${state.currentStep}.md`);
1510
1830
  const prompt = buildVerifyPrompt({
1511
1831
  taskId: state.taskId,
1512
1832
  step: state.currentStep,
@@ -1560,7 +1880,7 @@ ${issues}`;
1560
1880
  step: state.currentStep,
1561
1881
  stepIndex: state.stepIndex,
1562
1882
  totalSteps: state.totalSteps,
1563
- specPath: join5(outputPath, state.taskId, `${state.currentStep}.md`),
1883
+ specPath: join6(outputPath, state.taskId, `${state.currentStep}.md`),
1564
1884
  options: ["approve", "revise", "reject", "stop"],
1565
1885
  onComplete: {
1566
1886
  approve: `approve ${state.taskId}`,
@@ -1575,8 +1895,8 @@ ${issues}`;
1575
1895
  const outputPath = this.getOutputPath();
1576
1896
  const outputs = [];
1577
1897
  for (let i = 0; i < state.stepIndex; i++) {
1578
- const specPath = join5(outputPath, state.taskId, `${steps[i]}.md`);
1579
- if (existsSync5(specPath)) {
1898
+ const specPath = join6(outputPath, state.taskId, `${steps[i]}.md`);
1899
+ if (existsSync6(specPath)) {
1580
1900
  outputs.push(specPath);
1581
1901
  }
1582
1902
  }
@@ -1744,7 +2064,7 @@ ${issues}`;
1744
2064
  return this.responseError(`No workflow found: ${taskId}`, taskId);
1745
2065
  }
1746
2066
  const specPath = this.getSpecPath(taskId, state.currentStep);
1747
- if (!existsSync5(specPath)) {
2067
+ if (!existsSync6(specPath)) {
1748
2068
  return this.responseError(`Document not found: ${specPath}`, taskId, state.currentStep);
1749
2069
  }
1750
2070
  state.status = "approve_pending";
@@ -1761,7 +2081,7 @@ ${issues}`;
1761
2081
  return this.responseError(`No workflow found: ${taskId}`, taskId);
1762
2082
  }
1763
2083
  const specPath = this.getSpecPath(taskId, state.currentStep);
1764
- if (!existsSync5(specPath)) {
2084
+ if (!existsSync6(specPath)) {
1765
2085
  return this.responseError(`Document not found: ${specPath}`, taskId, state.currentStep);
1766
2086
  }
1767
2087
  state.status = "phase_verify";
@@ -1816,7 +2136,7 @@ ${issues}`;
1816
2136
  return this.responseError(`No workflow found: ${taskId}`, taskId);
1817
2137
  }
1818
2138
  const specPath = this.getSpecPath(taskId, state.currentStep);
1819
- if (!existsSync5(specPath)) {
2139
+ if (!existsSync6(specPath)) {
1820
2140
  return this.responseError(`Spec not found: ${specPath}`, taskId, state.currentStep);
1821
2141
  }
1822
2142
  const questions = this.checkUnresolvedQuestions(specPath);
@@ -1842,12 +2162,12 @@ ${issues}`;
1842
2162
  }
1843
2163
  const steps = this.getSteps();
1844
2164
  const specPath = this.getSpecPath(taskId, state.currentStep);
1845
- if (existsSync5(specPath)) {
2165
+ if (existsSync6(specPath)) {
1846
2166
  const content = readFileSync5(specPath, "utf-8");
1847
2167
  const { content: body, data } = matter2(content);
1848
2168
  data.status = "approved";
1849
2169
  data.updated_at = (/* @__PURE__ */ new Date()).toISOString();
1850
- writeFileSync3(specPath, matter2.stringify(body, data));
2170
+ writeFileSync4(specPath, matter2.stringify(body, data));
1851
2171
  }
1852
2172
  if (state.stepIndex < state.totalSteps) {
1853
2173
  state.currentStep = steps[state.stepIndex];
@@ -1895,12 +2215,12 @@ ${issues}`;
1895
2215
  return this.responseError(`No workflow found: ${taskId}`, taskId);
1896
2216
  }
1897
2217
  const specPath = this.getSpecPath(taskId, state.currentStep);
1898
- if (existsSync5(specPath)) {
2218
+ if (existsSync6(specPath)) {
1899
2219
  const content = readFileSync5(specPath, "utf-8");
1900
2220
  const { content: body, data } = matter2(content);
1901
2221
  data.status = "rejected";
1902
2222
  data.updated_at = (/* @__PURE__ */ new Date()).toISOString();
1903
- writeFileSync3(specPath, matter2.stringify(body, data));
2223
+ writeFileSync4(specPath, matter2.stringify(body, data));
1904
2224
  }
1905
2225
  state.status = "rejected";
1906
2226
  this.saveState(state);
@@ -2000,8 +2320,8 @@ ${issues}`;
2000
2320
  };
2001
2321
 
2002
2322
  // 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";
2323
+ import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
2324
+ import { join as join7 } from "path";
2005
2325
  import matter3 from "gray-matter";
2006
2326
  var StepExecutor = class {
2007
2327
  adapter;
@@ -2149,7 +2469,7 @@ var StepExecutor = class {
2149
2469
  };
2150
2470
  const { prompt, outputPath } = buildGeneratePrompt(params);
2151
2471
  await this.adapter.ai.execute({ prompt, outputPath });
2152
- if (!existsSync6(outputPath)) {
2472
+ if (!existsSync7(outputPath)) {
2153
2473
  throw new Error(`AI did not create document at ${outputPath}`);
2154
2474
  }
2155
2475
  this.adapter.io.notify(`Document created: ${outputPath}`, "success");
@@ -2188,7 +2508,7 @@ var StepExecutor = class {
2188
2508
  };
2189
2509
  const { prompt, outputPath } = buildExecutePrompt(params);
2190
2510
  await this.adapter.ai.execute({ prompt, outputPath });
2191
- if (!existsSync6(outputPath)) {
2511
+ if (!existsSync7(outputPath)) {
2192
2512
  throw new Error(`AI did not create document at ${outputPath}`);
2193
2513
  }
2194
2514
  this.adapter.io.notify(`Document created: ${outputPath}`, "success");
@@ -2210,8 +2530,8 @@ var StepExecutor = class {
2210
2530
  const attempts = context.verifyAttempts || 1;
2211
2531
  this.adapter.io.notify(`Phase 4/5: Verifying document for ${step} (attempt ${attempts}/3)`, "info");
2212
2532
  const outputsDir = getOutputsDir(this.cwd);
2213
- const documentPath = join6(outputsDir, context.taskId, `${step}.md`);
2214
- if (!existsSync6(documentPath)) {
2533
+ const documentPath = join7(outputsDir, context.taskId, `${step}.md`);
2534
+ if (!existsSync7(documentPath)) {
2215
2535
  throw new Error(`Document not found: ${documentPath}`);
2216
2536
  }
2217
2537
  const params = {
@@ -2252,8 +2572,8 @@ var StepExecutor = class {
2252
2572
  async executeReviewPhase(step, context) {
2253
2573
  this.adapter.io.notify(`Phase 4/4: Review for ${step}`, "info");
2254
2574
  const outputsDir = getOutputsDir(this.cwd);
2255
- const outputPath = join6(outputsDir, context.taskId, `${step}.md`);
2256
- if (!existsSync6(outputPath)) {
2575
+ const outputPath = join7(outputsDir, context.taskId, `${step}.md`);
2576
+ if (!existsSync7(outputPath)) {
2257
2577
  throw new Error(`Document not found: ${outputPath}`);
2258
2578
  }
2259
2579
  const approval = await this.adapter.io.approve(
@@ -2439,13 +2759,13 @@ var StepExecutor = class {
2439
2759
  const { content: body, data } = matter3(content);
2440
2760
  data.status = status;
2441
2761
  data.updated_at = (/* @__PURE__ */ new Date()).toISOString();
2442
- writeFileSync4(docPath, matter3.stringify(body, data));
2762
+ writeFileSync5(docPath, matter3.stringify(body, data));
2443
2763
  }
2444
2764
  };
2445
2765
 
2446
2766
  // src/adapters/cli.ts
2447
2767
  import { spawn, spawnSync } from "child_process";
2448
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
2768
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync6, existsSync as existsSync8, mkdirSync as mkdirSync5 } from "fs";
2449
2769
  import { dirname as dirname3 } from "path";
2450
2770
  import { input, select, confirm } from "@inquirer/prompts";
2451
2771
  var CLIAIAdapter = class {
@@ -2458,12 +2778,12 @@ var CLIAIAdapter = class {
2458
2778
  \u{1F4DD} Generating...`);
2459
2779
  if (params.outputPath) {
2460
2780
  const outputDir = dirname3(params.outputPath);
2461
- if (!existsSync7(outputDir)) {
2462
- mkdirSync4(outputDir, { recursive: true });
2781
+ if (!existsSync8(outputDir)) {
2782
+ mkdirSync5(outputDir, { recursive: true });
2463
2783
  }
2464
2784
  }
2465
2785
  const stdout = await this.callClaude(params.prompt);
2466
- if (params.outputPath && existsSync7(params.outputPath)) {
2786
+ if (params.outputPath && existsSync8(params.outputPath)) {
2467
2787
  return readFileSync7(params.outputPath, "utf-8");
2468
2788
  }
2469
2789
  return stdout;
@@ -2541,13 +2861,13 @@ var CLIAIAdapter = class {
2541
2861
  try {
2542
2862
  if (p.outputPath) {
2543
2863
  const outputDir = dirname3(p.outputPath);
2544
- if (!existsSync7(outputDir)) {
2545
- mkdirSync4(outputDir, { recursive: true });
2864
+ if (!existsSync8(outputDir)) {
2865
+ mkdirSync5(outputDir, { recursive: true });
2546
2866
  }
2547
2867
  }
2548
2868
  await this.callClaude(p.prompt, false);
2549
2869
  let result = "";
2550
- if (p.outputPath && existsSync7(p.outputPath)) {
2870
+ if (p.outputPath && existsSync8(p.outputPath)) {
2551
2871
  result = readFileSync7(p.outputPath, "utf-8");
2552
2872
  }
2553
2873
  completed++;
@@ -2598,7 +2918,7 @@ Context: ${question.context}`);
2598
2918
  return answers;
2599
2919
  }
2600
2920
  async approve(specPath, stepName, stepIndex, totalSteps) {
2601
- if (existsSync7(specPath)) {
2921
+ if (existsSync8(specPath)) {
2602
2922
  const doc = readFileSync7(specPath, "utf-8");
2603
2923
  console.log("\n" + "=".repeat(60));
2604
2924
  console.log(`\u{1F4C4} ${stepName} Document (Step ${stepIndex}/${totalSteps})`);
@@ -2648,13 +2968,13 @@ var CLISystemAdapter = class {
2648
2968
  }
2649
2969
  writeFile(path, content) {
2650
2970
  const dir = dirname3(path);
2651
- if (!existsSync7(dir)) {
2652
- mkdirSync4(dir, { recursive: true });
2971
+ if (!existsSync8(dir)) {
2972
+ mkdirSync5(dir, { recursive: true });
2653
2973
  }
2654
- writeFileSync5(path, content);
2974
+ writeFileSync6(path, content);
2655
2975
  }
2656
2976
  fileExists(path) {
2657
- return existsSync7(path);
2977
+ return existsSync8(path);
2658
2978
  }
2659
2979
  exec(command) {
2660
2980
  try {
@@ -2685,7 +3005,7 @@ function createCLIAdapter(claudeCommand = "claude") {
2685
3005
 
2686
3006
  // src/adapters/github.ts
2687
3007
  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";
3008
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync7, existsSync as existsSync9, mkdirSync as mkdirSync6 } from "fs";
2689
3009
  import { dirname as dirname4 } from "path";
2690
3010
  var GitHubAIAdapter = class {
2691
3011
  claudeCommand;
@@ -2697,12 +3017,12 @@ var GitHubAIAdapter = class {
2697
3017
  \u{1F4DD} Generating...`);
2698
3018
  if (params.outputPath) {
2699
3019
  const outputDir = dirname4(params.outputPath);
2700
- if (!existsSync8(outputDir)) {
2701
- mkdirSync5(outputDir, { recursive: true });
3020
+ if (!existsSync9(outputDir)) {
3021
+ mkdirSync6(outputDir, { recursive: true });
2702
3022
  }
2703
3023
  }
2704
3024
  const stdout = await this.callClaude(params.prompt);
2705
- if (params.outputPath && existsSync8(params.outputPath)) {
3025
+ if (params.outputPath && existsSync9(params.outputPath)) {
2706
3026
  return readFileSync8(params.outputPath, "utf-8");
2707
3027
  }
2708
3028
  return stdout;
@@ -2755,13 +3075,13 @@ var GitHubAIAdapter = class {
2755
3075
  try {
2756
3076
  if (p.outputPath) {
2757
3077
  const outputDir = dirname4(p.outputPath);
2758
- if (!existsSync8(outputDir)) {
2759
- mkdirSync5(outputDir, { recursive: true });
3078
+ if (!existsSync9(outputDir)) {
3079
+ mkdirSync6(outputDir, { recursive: true });
2760
3080
  }
2761
3081
  }
2762
3082
  await this.callClaude(p.prompt);
2763
3083
  let result = "";
2764
- if (p.outputPath && existsSync8(p.outputPath)) {
3084
+ if (p.outputPath && existsSync9(p.outputPath)) {
2765
3085
  result = readFileSync8(p.outputPath, "utf-8");
2766
3086
  }
2767
3087
  completed++;
@@ -2807,7 +3127,7 @@ var GitHubIOAdapter = class {
2807
3127
  return [];
2808
3128
  }
2809
3129
  async approve(specPath, stepName, stepIndex, totalSteps) {
2810
- const doc = existsSync8(specPath) ? readFileSync8(specPath, "utf-8") : "";
3130
+ const doc = existsSync9(specPath) ? readFileSync8(specPath, "utf-8") : "";
2811
3131
  const comment = this.formatApprovalComment(doc, stepName, stepIndex, totalSteps, specPath);
2812
3132
  await this.postComment(comment);
2813
3133
  console.log("\n\u23F8\uFE0F Approval request posted to GitHub.");
@@ -2930,13 +3250,13 @@ var GitHubSystemAdapter = class {
2930
3250
  }
2931
3251
  writeFile(path, content) {
2932
3252
  const dir = dirname4(path);
2933
- if (!existsSync8(dir)) {
2934
- mkdirSync5(dir, { recursive: true });
3253
+ if (!existsSync9(dir)) {
3254
+ mkdirSync6(dir, { recursive: true });
2935
3255
  }
2936
- writeFileSync6(path, content);
3256
+ writeFileSync7(path, content);
2937
3257
  }
2938
3258
  fileExists(path) {
2939
- return existsSync8(path);
3259
+ return existsSync9(path);
2940
3260
  }
2941
3261
  exec(command) {
2942
3262
  try {
@@ -2967,7 +3287,7 @@ function createGitHubAdapter(config, claudeCommand = "claude") {
2967
3287
 
2968
3288
  // src/adapters/codex.ts
2969
3289
  import { spawn as spawn3 } from "child_process";
2970
- import { readFileSync as readFileSync9, existsSync as existsSync9, mkdirSync as mkdirSync6 } from "fs";
3290
+ import { readFileSync as readFileSync9, existsSync as existsSync10, mkdirSync as mkdirSync7 } from "fs";
2971
3291
  import { dirname as dirname5 } from "path";
2972
3292
  var CodexAIAdapter = class {
2973
3293
  codexCommand;
@@ -2979,12 +3299,12 @@ var CodexAIAdapter = class {
2979
3299
  \u{1F4DD} Generating...`);
2980
3300
  if (params.outputPath) {
2981
3301
  const outputDir = dirname5(params.outputPath);
2982
- if (!existsSync9(outputDir)) {
2983
- mkdirSync6(outputDir, { recursive: true });
3302
+ if (!existsSync10(outputDir)) {
3303
+ mkdirSync7(outputDir, { recursive: true });
2984
3304
  }
2985
3305
  }
2986
3306
  const stdout = await this.callCodex(params.prompt);
2987
- if (params.outputPath && existsSync9(params.outputPath)) {
3307
+ if (params.outputPath && existsSync10(params.outputPath)) {
2988
3308
  return readFileSync9(params.outputPath, "utf-8");
2989
3309
  }
2990
3310
  return stdout;
@@ -3062,13 +3382,13 @@ var CodexAIAdapter = class {
3062
3382
  try {
3063
3383
  if (p.outputPath) {
3064
3384
  const outputDir = dirname5(p.outputPath);
3065
- if (!existsSync9(outputDir)) {
3066
- mkdirSync6(outputDir, { recursive: true });
3385
+ if (!existsSync10(outputDir)) {
3386
+ mkdirSync7(outputDir, { recursive: true });
3067
3387
  }
3068
3388
  }
3069
3389
  await this.callCodex(p.prompt, false);
3070
3390
  let result = "";
3071
- if (p.outputPath && existsSync9(p.outputPath)) {
3391
+ if (p.outputPath && existsSync10(p.outputPath)) {
3072
3392
  result = readFileSync9(p.outputPath, "utf-8");
3073
3393
  }
3074
3394
  completed++;
@@ -3097,7 +3417,7 @@ function createCodexAdapter(codexCommand = "codex") {
3097
3417
 
3098
3418
  // src/adapters/opencode.ts
3099
3419
  import { spawn as spawn4 } from "child_process";
3100
- import { readFileSync as readFileSync10, existsSync as existsSync10, mkdirSync as mkdirSync7 } from "fs";
3420
+ import { readFileSync as readFileSync10, existsSync as existsSync11, mkdirSync as mkdirSync8 } from "fs";
3101
3421
  import { dirname as dirname6 } from "path";
3102
3422
  var OpenCodeAIAdapter = class {
3103
3423
  opencodeCommand;
@@ -3109,12 +3429,12 @@ var OpenCodeAIAdapter = class {
3109
3429
  \u{1F4DD} Generating...`);
3110
3430
  if (params.outputPath) {
3111
3431
  const outputDir = dirname6(params.outputPath);
3112
- if (!existsSync10(outputDir)) {
3113
- mkdirSync7(outputDir, { recursive: true });
3432
+ if (!existsSync11(outputDir)) {
3433
+ mkdirSync8(outputDir, { recursive: true });
3114
3434
  }
3115
3435
  }
3116
3436
  const stdout = await this.callOpenCode(params.prompt);
3117
- if (params.outputPath && existsSync10(params.outputPath)) {
3437
+ if (params.outputPath && existsSync11(params.outputPath)) {
3118
3438
  return readFileSync10(params.outputPath, "utf-8");
3119
3439
  }
3120
3440
  return stdout;
@@ -3191,13 +3511,13 @@ var OpenCodeAIAdapter = class {
3191
3511
  try {
3192
3512
  if (p.outputPath) {
3193
3513
  const outputDir = dirname6(p.outputPath);
3194
- if (!existsSync10(outputDir)) {
3195
- mkdirSync7(outputDir, { recursive: true });
3514
+ if (!existsSync11(outputDir)) {
3515
+ mkdirSync8(outputDir, { recursive: true });
3196
3516
  }
3197
3517
  }
3198
3518
  await this.callOpenCode(p.prompt, false);
3199
3519
  let result = "";
3200
- if (p.outputPath && existsSync10(p.outputPath)) {
3520
+ if (p.outputPath && existsSync11(p.outputPath)) {
3201
3521
  result = readFileSync10(p.outputPath, "utf-8");
3202
3522
  }
3203
3523
  completed++;
@@ -3481,14 +3801,14 @@ Resume with: spets resume --task ${taskId}`);
3481
3801
 
3482
3802
  // src/commands/resume.ts
3483
3803
  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";
3804
+ import { existsSync as existsSync12, readdirSync as readdirSync3, readFileSync as readFileSync11 } from "fs";
3805
+ import { join as join8 } from "path";
3486
3806
  import { spawnSync as spawnSync3 } from "child_process";
3487
3807
  async function createPR(cwd, taskId, outputs) {
3488
3808
  const outputContents = [];
3489
3809
  for (const outputPath of outputs) {
3490
- const fullPath = join7(cwd, outputPath);
3491
- if (existsSync11(fullPath)) {
3810
+ const fullPath = join8(cwd, outputPath);
3811
+ if (existsSync12(fullPath)) {
3492
3812
  const content = readFileSync11(fullPath, "utf-8");
3493
3813
  outputContents.push(`## ${outputPath}
3494
3814
 
@@ -3511,15 +3831,15 @@ ${outputContents.join("\n\n---\n\n")}`;
3511
3831
  }
3512
3832
  function listResumableTasks(cwd) {
3513
3833
  const outputsDir = getOutputsDir(cwd);
3514
- if (!existsSync11(outputsDir)) {
3834
+ if (!existsSync12(outputsDir)) {
3515
3835
  return [];
3516
3836
  }
3517
3837
  const tasks = [];
3518
- const taskDirs = readdirSync2(outputsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
3838
+ const taskDirs = readdirSync3(outputsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
3519
3839
  const orchestrator = new Orchestrator(cwd);
3520
3840
  for (const taskId of taskDirs) {
3521
- const stateFile = join7(outputsDir, taskId, ".state.json");
3522
- if (!existsSync11(stateFile)) continue;
3841
+ const stateFile = join8(outputsDir, taskId, ".state.json");
3842
+ if (!existsSync12(stateFile)) continue;
3523
3843
  try {
3524
3844
  const response = orchestrator.cmdStatus(taskId);
3525
3845
  if (response.type === "error") continue;
@@ -3769,532 +4089,6 @@ Resume with: spets resume --task ${taskId}`);
3769
4089
  }
3770
4090
  }
3771
4091
 
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
4092
  // src/commands/github.ts
4299
4093
  import { execSync as execSync4, spawn as spawn6, spawnSync as spawnSync4 } from "child_process";
4300
4094
  import { readdirSync as readdirSync4, readFileSync as readFileSync12, existsSync as existsSync14 } from "fs";