spets 0.1.49 → 0.1.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +725 -752
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
|
-
import { readFileSync as
|
|
6
|
-
import { dirname as
|
|
5
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
6
|
+
import { dirname as dirname7, join as join11 } from "path";
|
|
7
7
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
8
8
|
|
|
9
9
|
// src/commands/init.ts
|
|
10
|
-
import { existsSync as
|
|
11
|
-
import { join as
|
|
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/
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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("
|
|
126
|
-
console.log("
|
|
127
|
-
console.log("
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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(
|
|
247
|
+
console.log(" (none)");
|
|
133
248
|
}
|
|
134
249
|
}
|
|
135
|
-
function
|
|
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
|
-
| \`
|
|
282
|
+
| \`explore\` | [ACTION_EXPLORE](#action_explore) |
|
|
221
283
|
| \`clarify\` | [ACTION_CLARIFY](#action_clarify) |
|
|
222
|
-
| \`
|
|
223
|
-
| \`
|
|
224
|
-
| \`consolidate\` | [ACTION_PARALLEL_CONSOLIDATE](#action_parallel_consolidate) |
|
|
284
|
+
| \`execute\` | [ACTION_EXECUTE](#action_execute) |
|
|
285
|
+
| \`verify\` | [ACTION_VERIFY](#action_verify) |
|
|
225
286
|
|
|
226
287
|
### \`type: "checkpoint"\`
|
|
227
288
|
|
|
@@ -246,99 +307,66 @@ Print: "Error: {error}" \u2192 STOP
|
|
|
246
307
|
|
|
247
308
|
## Action Definitions
|
|
248
309
|
|
|
249
|
-
|
|
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.
|
|
253
|
-
2.
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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.
|
|
263
|
-
2.
|
|
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
|
-
###
|
|
339
|
+
### ACTION_EXECUTE
|
|
271
340
|
|
|
272
341
|
\`\`\`
|
|
273
|
-
1.
|
|
274
|
-
2.
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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
|
-
###
|
|
283
|
-
|
|
284
|
-
**PARALLEL EXECUTION (FOREGROUND)**
|
|
353
|
+
### ACTION_VERIFY
|
|
285
354
|
|
|
286
355
|
\`\`\`
|
|
287
|
-
1.
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
Title: {section.title}
|
|
295
|
-
Instruction: {section.instruction}
|
|
296
|
-
Context: {sharedContext.description}
|
|
297
|
-
|
|
298
|
-
1. Generate content for this section
|
|
299
|
-
2. Write to the path using Write tool
|
|
300
|
-
3. Return ONLY: {section.outputPath}
|
|
301
|
-
(no explanations, no content, just the path)
|
|
302
|
-
"
|
|
303
|
-
)
|
|
304
|
-
|
|
305
|
-
2. ALL Task calls MUST be in SINGLE message (parallel execution)
|
|
306
|
-
|
|
307
|
-
3. Bash: npx spets orchestrate sections-done {taskId}
|
|
308
|
-
|
|
356
|
+
1. Follow the instructions in response.prompt EXACTLY
|
|
357
|
+
2. The prompt tells you:
|
|
358
|
+
- The document to verify (already included in prompt)
|
|
359
|
+
- The requirements and template to check against
|
|
360
|
+
- The scoring criteria and pass conditions
|
|
361
|
+
- The exact JSON output format required
|
|
362
|
+
3. Bash: npx spets orchestrate verify-done {taskId} '{verifyJson}'
|
|
309
363
|
4. Parse response \u2192 next action
|
|
364
|
+
- If passed: goes to human review
|
|
365
|
+
- If failed (attempts < 3): goes back to draft phase (auto-fix)
|
|
366
|
+
- If failed (attempts >= 3): goes to human review with warning
|
|
310
367
|
\`\`\`
|
|
311
368
|
|
|
312
|
-
###
|
|
313
|
-
|
|
314
|
-
**PARALLEL EXECUTION (FOREGROUND)**
|
|
315
|
-
|
|
316
|
-
\`\`\`
|
|
317
|
-
1. FOR EACH group IN parallelGroups:
|
|
318
|
-
Task(
|
|
319
|
-
subagent_type: "general-purpose",
|
|
320
|
-
prompt: "
|
|
321
|
-
Merge children into parent. Return ONLY the path.
|
|
322
|
-
|
|
323
|
-
Output: {group.outputPath}
|
|
324
|
-
Children: {group.childPaths}
|
|
325
|
-
|
|
326
|
-
1. Read each child file
|
|
327
|
-
2. Merge preserving hierarchy
|
|
328
|
-
3. Write to output using Write tool
|
|
329
|
-
4. Return ONLY: {group.outputPath}
|
|
330
|
-
(no explanations, no content, just the path)
|
|
331
|
-
"
|
|
332
|
-
)
|
|
333
|
-
|
|
334
|
-
2. ALL Task calls MUST be in SINGLE message (parallel execution)
|
|
335
|
-
|
|
336
|
-
3. Bash: npx spets orchestrate consolidate-level-done {taskId} {level}
|
|
337
|
-
|
|
338
|
-
4. Parse response \u2192 next action
|
|
339
|
-
\`\`\`
|
|
340
|
-
|
|
341
|
-
### ACTION_ASK_QUESTIONS
|
|
369
|
+
### ACTION_ASK_QUESTIONS
|
|
342
370
|
|
|
343
371
|
\`\`\`
|
|
344
372
|
1. AskUserQuestion(
|
|
@@ -397,13 +425,13 @@ Print: "Error: {error}" \u2192 STOP
|
|
|
397
425
|
## Orchestrator Commands Reference
|
|
398
426
|
|
|
399
427
|
\`\`\`bash
|
|
428
|
+
# New 5-phase workflow
|
|
400
429
|
npx spets orchestrate init "<description>"
|
|
401
|
-
npx spets orchestrate
|
|
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
|
|
405
|
-
npx spets orchestrate
|
|
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 =
|
|
421
|
-
const templateDir =
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
const workflowTemplate = readFileSync2(
|
|
425
|
-
const issueTemplate = readFileSync2(
|
|
426
|
-
|
|
427
|
-
|
|
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
|
|
447
|
-
import { join as
|
|
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
|
|
761
|
+
return join4(getOutputsDir(cwd), taskId);
|
|
495
762
|
}
|
|
496
763
|
function getOutputPath(taskId, stepName, cwd = process.cwd()) {
|
|
497
|
-
return
|
|
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 (!
|
|
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 (!
|
|
780
|
+
if (!existsSync4(outputsDir)) {
|
|
514
781
|
return [];
|
|
515
782
|
}
|
|
516
|
-
return
|
|
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
|
|
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 (!
|
|
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
|
-
|
|
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 (!
|
|
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 =
|
|
627
|
-
if (!
|
|
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
|
|
719
|
-
import { join as
|
|
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
|
|
724
|
-
import { join as
|
|
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 (
|
|
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 (
|
|
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 =
|
|
939
|
-
const outputPath =
|
|
940
|
-
const template =
|
|
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 =
|
|
944
|
-
if (
|
|
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 =
|
|
1026
|
-
const outputPath =
|
|
1027
|
-
const template =
|
|
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 =
|
|
1031
|
-
if (
|
|
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 =
|
|
1151
|
-
const template =
|
|
1152
|
-
const document =
|
|
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
|
|
1523
|
+
return join6(this.getOutputPath(), taskId, ".state.json");
|
|
1257
1524
|
}
|
|
1258
1525
|
getSpecPath(taskId, step) {
|
|
1259
|
-
return
|
|
1526
|
+
return join6(this.getOutputPath(), taskId, `${step}.md`);
|
|
1260
1527
|
}
|
|
1261
1528
|
getStepTemplatePath(step) {
|
|
1262
|
-
return
|
|
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 (!
|
|
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 (!
|
|
1280
|
-
|
|
1546
|
+
if (!existsSync6(dir)) {
|
|
1547
|
+
mkdirSync4(dir, { recursive: true });
|
|
1281
1548
|
}
|
|
1282
|
-
|
|
1549
|
+
writeFileSync4(statePath, JSON.stringify(state, null, 2));
|
|
1283
1550
|
}
|
|
1284
1551
|
// ===========================================================================
|
|
1285
1552
|
// Spec Helpers
|
|
1286
1553
|
// ===========================================================================
|
|
1287
1554
|
checkUnresolvedQuestions(specPath) {
|
|
1288
|
-
if (!
|
|
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 =
|
|
1324
|
-
if (
|
|
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 =
|
|
1352
|
-
if (
|
|
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 =
|
|
1415
|
-
if (
|
|
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 =
|
|
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:
|
|
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 =
|
|
1450
|
-
if (
|
|
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 =
|
|
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:
|
|
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 =
|
|
1509
|
-
const documentPath =
|
|
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:
|
|
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 =
|
|
1579
|
-
if (
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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 (
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
|
2004
|
-
import { join as
|
|
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 (!
|
|
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 (!
|
|
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 =
|
|
2214
|
-
if (!
|
|
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 =
|
|
2256
|
-
if (!
|
|
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
|
-
|
|
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
|
|
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 (!
|
|
2462
|
-
|
|
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 &&
|
|
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 (!
|
|
2545
|
-
|
|
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 &&
|
|
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 (
|
|
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 (!
|
|
2652
|
-
|
|
2918
|
+
if (!existsSync8(dir)) {
|
|
2919
|
+
mkdirSync5(dir, { recursive: true });
|
|
2653
2920
|
}
|
|
2654
|
-
|
|
2921
|
+
writeFileSync6(path, content);
|
|
2655
2922
|
}
|
|
2656
2923
|
fileExists(path) {
|
|
2657
|
-
return
|
|
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
|
|
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 (!
|
|
2701
|
-
|
|
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 &&
|
|
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 (!
|
|
2759
|
-
|
|
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 &&
|
|
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 =
|
|
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 (!
|
|
2934
|
-
|
|
3200
|
+
if (!existsSync9(dir)) {
|
|
3201
|
+
mkdirSync6(dir, { recursive: true });
|
|
2935
3202
|
}
|
|
2936
|
-
|
|
3203
|
+
writeFileSync7(path, content);
|
|
2937
3204
|
}
|
|
2938
3205
|
fileExists(path) {
|
|
2939
|
-
return
|
|
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
|
|
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 (!
|
|
2983
|
-
|
|
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 &&
|
|
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 (!
|
|
3066
|
-
|
|
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 &&
|
|
3338
|
+
if (p.outputPath && existsSync10(p.outputPath)) {
|
|
3072
3339
|
result = readFileSync9(p.outputPath, "utf-8");
|
|
3073
3340
|
}
|
|
3074
3341
|
completed++;
|
|
@@ -3095,6 +3362,135 @@ function createCodexAdapter(codexCommand = "codex") {
|
|
|
3095
3362
|
};
|
|
3096
3363
|
}
|
|
3097
3364
|
|
|
3365
|
+
// src/adapters/opencode.ts
|
|
3366
|
+
import { spawn as spawn4 } from "child_process";
|
|
3367
|
+
import { readFileSync as readFileSync10, existsSync as existsSync11, mkdirSync as mkdirSync8 } from "fs";
|
|
3368
|
+
import { dirname as dirname6 } from "path";
|
|
3369
|
+
var OpenCodeAIAdapter = class {
|
|
3370
|
+
opencodeCommand;
|
|
3371
|
+
constructor(opencodeCommand = "opencode") {
|
|
3372
|
+
this.opencodeCommand = opencodeCommand;
|
|
3373
|
+
}
|
|
3374
|
+
async execute(params) {
|
|
3375
|
+
console.log(`
|
|
3376
|
+
\u{1F4DD} Generating...`);
|
|
3377
|
+
if (params.outputPath) {
|
|
3378
|
+
const outputDir = dirname6(params.outputPath);
|
|
3379
|
+
if (!existsSync11(outputDir)) {
|
|
3380
|
+
mkdirSync8(outputDir, { recursive: true });
|
|
3381
|
+
}
|
|
3382
|
+
}
|
|
3383
|
+
const stdout = await this.callOpenCode(params.prompt);
|
|
3384
|
+
if (params.outputPath && existsSync11(params.outputPath)) {
|
|
3385
|
+
return readFileSync10(params.outputPath, "utf-8");
|
|
3386
|
+
}
|
|
3387
|
+
return stdout;
|
|
3388
|
+
}
|
|
3389
|
+
async callOpenCode(prompt, showSpinner = true) {
|
|
3390
|
+
return new Promise((resolve, reject) => {
|
|
3391
|
+
const proc = spawn4(this.opencodeCommand, [
|
|
3392
|
+
"run"
|
|
3393
|
+
], {
|
|
3394
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3395
|
+
});
|
|
3396
|
+
proc.stdin.write(prompt);
|
|
3397
|
+
proc.stdin.end();
|
|
3398
|
+
let stdout = "";
|
|
3399
|
+
let stderr = "";
|
|
3400
|
+
let spinner;
|
|
3401
|
+
if (showSpinner) {
|
|
3402
|
+
const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
3403
|
+
let frameIndex = 0;
|
|
3404
|
+
spinner = setInterval(() => {
|
|
3405
|
+
process.stdout.write(`\r${frames[frameIndex++ % frames.length]} OpenCode is working...`);
|
|
3406
|
+
}, 100);
|
|
3407
|
+
}
|
|
3408
|
+
const stopSpinner = (success) => {
|
|
3409
|
+
if (spinner) {
|
|
3410
|
+
clearInterval(spinner);
|
|
3411
|
+
process.stdout.write(`\r${success ? "\u2713" : "\u2717"} OpenCode ${success ? "done" : "failed"}
|
|
3412
|
+
`);
|
|
3413
|
+
}
|
|
3414
|
+
};
|
|
3415
|
+
proc.stdout.on("data", (data) => {
|
|
3416
|
+
stdout += data.toString();
|
|
3417
|
+
});
|
|
3418
|
+
proc.stderr.on("data", (data) => {
|
|
3419
|
+
stderr += data.toString();
|
|
3420
|
+
});
|
|
3421
|
+
proc.on("close", (code) => {
|
|
3422
|
+
stopSpinner(code === 0);
|
|
3423
|
+
if (code !== 0) {
|
|
3424
|
+
reject(new Error(`OpenCode CLI exited with code ${code}: ${stderr}`));
|
|
3425
|
+
} else {
|
|
3426
|
+
resolve(stdout);
|
|
3427
|
+
}
|
|
3428
|
+
});
|
|
3429
|
+
proc.on("error", (err) => {
|
|
3430
|
+
stopSpinner(false);
|
|
3431
|
+
reject(new Error(`Failed to run OpenCode CLI: ${err.message}`));
|
|
3432
|
+
});
|
|
3433
|
+
});
|
|
3434
|
+
}
|
|
3435
|
+
/**
|
|
3436
|
+
* Execute multiple prompts in parallel with shared progress display
|
|
3437
|
+
*/
|
|
3438
|
+
async executeParallel(params) {
|
|
3439
|
+
const total = params.length;
|
|
3440
|
+
let completed = 0;
|
|
3441
|
+
const results = /* @__PURE__ */ new Map();
|
|
3442
|
+
const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
3443
|
+
let frameIndex = 0;
|
|
3444
|
+
const spinner = setInterval(() => {
|
|
3445
|
+
const progress = `${completed}/${total}`;
|
|
3446
|
+
process.stdout.write(`\r${frames[frameIndex++ % frames.length]} Generating sections in parallel... [${progress}]`);
|
|
3447
|
+
}, 100);
|
|
3448
|
+
const stopSpinner = (success) => {
|
|
3449
|
+
clearInterval(spinner);
|
|
3450
|
+
const status = success ? "\u2713" : "\u2717";
|
|
3451
|
+
const message = success ? `All ${total} sections generated` : `Generation completed with errors`;
|
|
3452
|
+
process.stdout.write(`\r${status} ${message}
|
|
3453
|
+
`);
|
|
3454
|
+
};
|
|
3455
|
+
await Promise.all(
|
|
3456
|
+
params.map(async (p) => {
|
|
3457
|
+
const id = p.id || p.outputPath;
|
|
3458
|
+
try {
|
|
3459
|
+
if (p.outputPath) {
|
|
3460
|
+
const outputDir = dirname6(p.outputPath);
|
|
3461
|
+
if (!existsSync11(outputDir)) {
|
|
3462
|
+
mkdirSync8(outputDir, { recursive: true });
|
|
3463
|
+
}
|
|
3464
|
+
}
|
|
3465
|
+
await this.callOpenCode(p.prompt, false);
|
|
3466
|
+
let result = "";
|
|
3467
|
+
if (p.outputPath && existsSync11(p.outputPath)) {
|
|
3468
|
+
result = readFileSync10(p.outputPath, "utf-8");
|
|
3469
|
+
}
|
|
3470
|
+
completed++;
|
|
3471
|
+
results.set(id, { id, success: true, result });
|
|
3472
|
+
} catch (error) {
|
|
3473
|
+
completed++;
|
|
3474
|
+
results.set(id, {
|
|
3475
|
+
id,
|
|
3476
|
+
success: false,
|
|
3477
|
+
error: error instanceof Error ? error.message : String(error)
|
|
3478
|
+
});
|
|
3479
|
+
}
|
|
3480
|
+
})
|
|
3481
|
+
);
|
|
3482
|
+
stopSpinner(Array.from(results.values()).every((r) => r.success));
|
|
3483
|
+
return params.map((p) => results.get(p.id || p.outputPath));
|
|
3484
|
+
}
|
|
3485
|
+
};
|
|
3486
|
+
function createOpenCodeAdapter(opencodeCommand = "opencode") {
|
|
3487
|
+
return {
|
|
3488
|
+
ai: new OpenCodeAIAdapter(opencodeCommand),
|
|
3489
|
+
io: new CLIIOAdapter(),
|
|
3490
|
+
system: new CLISystemAdapter()
|
|
3491
|
+
};
|
|
3492
|
+
}
|
|
3493
|
+
|
|
3098
3494
|
// src/commands/start.ts
|
|
3099
3495
|
function getGitHubInfo(cwd) {
|
|
3100
3496
|
const config = getGitHubConfig(cwd);
|
|
@@ -3166,6 +3562,9 @@ async function startCommand(query, options) {
|
|
|
3166
3562
|
if (agentType === "codex") {
|
|
3167
3563
|
adapter = createCodexAdapter();
|
|
3168
3564
|
console.log("Platform: cli (codex)");
|
|
3565
|
+
} else if (agentType === "opencode") {
|
|
3566
|
+
adapter = createOpenCodeAdapter();
|
|
3567
|
+
console.log("Platform: cli (opencode)");
|
|
3169
3568
|
} else {
|
|
3170
3569
|
adapter = createCLIAdapter();
|
|
3171
3570
|
console.log("Platform: cli (claude)");
|
|
@@ -3349,15 +3748,15 @@ Resume with: spets resume --task ${taskId}`);
|
|
|
3349
3748
|
|
|
3350
3749
|
// src/commands/resume.ts
|
|
3351
3750
|
import { select as select2 } from "@inquirer/prompts";
|
|
3352
|
-
import { existsSync as
|
|
3353
|
-
import { join as
|
|
3751
|
+
import { existsSync as existsSync12, readdirSync as readdirSync3, readFileSync as readFileSync11 } from "fs";
|
|
3752
|
+
import { join as join8 } from "path";
|
|
3354
3753
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
3355
3754
|
async function createPR(cwd, taskId, outputs) {
|
|
3356
3755
|
const outputContents = [];
|
|
3357
3756
|
for (const outputPath of outputs) {
|
|
3358
|
-
const fullPath =
|
|
3359
|
-
if (
|
|
3360
|
-
const content =
|
|
3757
|
+
const fullPath = join8(cwd, outputPath);
|
|
3758
|
+
if (existsSync12(fullPath)) {
|
|
3759
|
+
const content = readFileSync11(fullPath, "utf-8");
|
|
3361
3760
|
outputContents.push(`## ${outputPath}
|
|
3362
3761
|
|
|
3363
3762
|
${content}`);
|
|
@@ -3379,15 +3778,15 @@ ${outputContents.join("\n\n---\n\n")}`;
|
|
|
3379
3778
|
}
|
|
3380
3779
|
function listResumableTasks(cwd) {
|
|
3381
3780
|
const outputsDir = getOutputsDir(cwd);
|
|
3382
|
-
if (!
|
|
3781
|
+
if (!existsSync12(outputsDir)) {
|
|
3383
3782
|
return [];
|
|
3384
3783
|
}
|
|
3385
3784
|
const tasks = [];
|
|
3386
|
-
const taskDirs =
|
|
3785
|
+
const taskDirs = readdirSync3(outputsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
3387
3786
|
const orchestrator = new Orchestrator(cwd);
|
|
3388
3787
|
for (const taskId of taskDirs) {
|
|
3389
|
-
const stateFile =
|
|
3390
|
-
if (!
|
|
3788
|
+
const stateFile = join8(outputsDir, taskId, ".state.json");
|
|
3789
|
+
if (!existsSync12(stateFile)) continue;
|
|
3391
3790
|
try {
|
|
3392
3791
|
const response = orchestrator.cmdStatus(taskId);
|
|
3393
3792
|
if (response.type === "error") continue;
|
|
@@ -3637,444 +4036,18 @@ Resume with: spets resume --task ${taskId}`);
|
|
|
3637
4036
|
}
|
|
3638
4037
|
}
|
|
3639
4038
|
|
|
3640
|
-
// src/commands/plugin.ts
|
|
3641
|
-
import { existsSync as existsSync11, mkdirSync as mkdirSync7, writeFileSync as writeFileSync7, rmSync, readdirSync as readdirSync3 } from "fs";
|
|
3642
|
-
import { join as join8 } from "path";
|
|
3643
|
-
import { homedir } from "os";
|
|
3644
|
-
async function pluginCommand(action, name) {
|
|
3645
|
-
switch (action) {
|
|
3646
|
-
case "install":
|
|
3647
|
-
if (!name) {
|
|
3648
|
-
console.error("Plugin name required.");
|
|
3649
|
-
console.error("Usage: spets plugin install <name>");
|
|
3650
|
-
process.exit(1);
|
|
3651
|
-
}
|
|
3652
|
-
await installPlugin(name);
|
|
3653
|
-
break;
|
|
3654
|
-
case "uninstall":
|
|
3655
|
-
if (!name) {
|
|
3656
|
-
console.error("Plugin name required.");
|
|
3657
|
-
console.error("Usage: spets plugin uninstall <name>");
|
|
3658
|
-
process.exit(1);
|
|
3659
|
-
}
|
|
3660
|
-
await uninstallPlugin(name);
|
|
3661
|
-
break;
|
|
3662
|
-
case "list":
|
|
3663
|
-
await listPlugins();
|
|
3664
|
-
break;
|
|
3665
|
-
default:
|
|
3666
|
-
console.error(`Unknown action: ${action}`);
|
|
3667
|
-
console.error("Available actions: install, uninstall, list");
|
|
3668
|
-
process.exit(1);
|
|
3669
|
-
}
|
|
3670
|
-
}
|
|
3671
|
-
async function installPlugin(name) {
|
|
3672
|
-
const plugins = {
|
|
3673
|
-
claude: installClaudePlugin,
|
|
3674
|
-
codex: installCodexPlugin
|
|
3675
|
-
};
|
|
3676
|
-
const installer = plugins[name];
|
|
3677
|
-
if (!installer) {
|
|
3678
|
-
console.error(`Unknown plugin: ${name}`);
|
|
3679
|
-
console.error("Available plugins: claude, codex");
|
|
3680
|
-
process.exit(1);
|
|
3681
|
-
}
|
|
3682
|
-
installer();
|
|
3683
|
-
}
|
|
3684
|
-
function installClaudePlugin() {
|
|
3685
|
-
const claudeDir = join8(homedir(), ".claude");
|
|
3686
|
-
const commandsDir = join8(claudeDir, "commands");
|
|
3687
|
-
mkdirSync7(commandsDir, { recursive: true });
|
|
3688
|
-
const skillPath = join8(commandsDir, "spets.md");
|
|
3689
|
-
writeFileSync7(skillPath, getClaudeSkillContent());
|
|
3690
|
-
console.log("Installed Claude Code plugin.");
|
|
3691
|
-
console.log(`Location: ${skillPath}`);
|
|
3692
|
-
console.log("");
|
|
3693
|
-
console.log("Usage in Claude Code:");
|
|
3694
|
-
console.log(' /spets "your task description"');
|
|
3695
|
-
console.log("");
|
|
3696
|
-
console.log("This skill runs deterministically within your Claude Code session.");
|
|
3697
|
-
console.log("No additional Claude processes are spawned.");
|
|
3698
|
-
}
|
|
3699
|
-
function installCodexPlugin() {
|
|
3700
|
-
const codexDir = join8(homedir(), ".codex");
|
|
3701
|
-
const skillsDir = join8(codexDir, "skills");
|
|
3702
|
-
const spetsDir = join8(skillsDir, "spets");
|
|
3703
|
-
mkdirSync7(spetsDir, { recursive: true });
|
|
3704
|
-
const skillPath = join8(spetsDir, "SKILL.md");
|
|
3705
|
-
writeFileSync7(skillPath, getCodexSkillContent());
|
|
3706
|
-
console.log("Installed Codex CLI plugin.");
|
|
3707
|
-
console.log(`Location: ${skillPath}`);
|
|
3708
|
-
console.log("");
|
|
3709
|
-
console.log("Usage in Codex CLI:");
|
|
3710
|
-
console.log(' $spets "your task description"');
|
|
3711
|
-
console.log("");
|
|
3712
|
-
console.log("This skill runs deterministically within your Codex CLI session.");
|
|
3713
|
-
console.log("No additional Codex processes are spawned.");
|
|
3714
|
-
}
|
|
3715
|
-
async function uninstallPlugin(name) {
|
|
3716
|
-
if (name === "claude") {
|
|
3717
|
-
const skillPath = join8(homedir(), ".claude", "commands", "spets.md");
|
|
3718
|
-
const legacySkillPath = join8(homedir(), ".claude", "commands", "sdd-do.md");
|
|
3719
|
-
let uninstalled = false;
|
|
3720
|
-
if (existsSync11(skillPath)) {
|
|
3721
|
-
rmSync(skillPath);
|
|
3722
|
-
uninstalled = true;
|
|
3723
|
-
}
|
|
3724
|
-
if (existsSync11(legacySkillPath)) {
|
|
3725
|
-
rmSync(legacySkillPath);
|
|
3726
|
-
uninstalled = true;
|
|
3727
|
-
}
|
|
3728
|
-
if (uninstalled) {
|
|
3729
|
-
console.log("Uninstalled Claude Code plugin.");
|
|
3730
|
-
} else {
|
|
3731
|
-
console.log("Claude Code plugin not installed.");
|
|
3732
|
-
}
|
|
3733
|
-
return;
|
|
3734
|
-
}
|
|
3735
|
-
if (name === "codex") {
|
|
3736
|
-
const skillPath = join8(homedir(), ".codex", "skills", "spets", "SKILL.md");
|
|
3737
|
-
if (existsSync11(skillPath)) {
|
|
3738
|
-
rmSync(skillPath);
|
|
3739
|
-
try {
|
|
3740
|
-
const skillDir = join8(homedir(), ".codex", "skills", "spets");
|
|
3741
|
-
const skillsDir = join8(homedir(), ".codex", "skills");
|
|
3742
|
-
rmSync(skillDir, { recursive: true });
|
|
3743
|
-
const remaining = readdirSync3(skillsDir);
|
|
3744
|
-
if (remaining.length === 0) {
|
|
3745
|
-
rmSync(skillsDir);
|
|
3746
|
-
}
|
|
3747
|
-
} catch {
|
|
3748
|
-
}
|
|
3749
|
-
console.log("Uninstalled Codex CLI plugin.");
|
|
3750
|
-
} else {
|
|
3751
|
-
console.log("Codex CLI plugin not installed.");
|
|
3752
|
-
}
|
|
3753
|
-
return;
|
|
3754
|
-
}
|
|
3755
|
-
console.error(`Unknown plugin: ${name}`);
|
|
3756
|
-
process.exit(1);
|
|
3757
|
-
}
|
|
3758
|
-
async function listPlugins() {
|
|
3759
|
-
console.log("Available plugins:");
|
|
3760
|
-
console.log("");
|
|
3761
|
-
console.log(" claude - Claude Code /spets skill");
|
|
3762
|
-
console.log(" codex - Codex CLI $spets skill");
|
|
3763
|
-
console.log("");
|
|
3764
|
-
const claudeSkillPath = join8(homedir(), ".claude", "commands", "spets.md");
|
|
3765
|
-
const claudeLegacySkillPath = join8(homedir(), ".claude", "commands", "sdd-do.md");
|
|
3766
|
-
const claudeInstalled = existsSync11(claudeSkillPath) || existsSync11(claudeLegacySkillPath);
|
|
3767
|
-
const codexSkillPath = join8(homedir(), ".codex", "skills", "spets", "SKILL.md");
|
|
3768
|
-
const codexInstalled = existsSync11(codexSkillPath);
|
|
3769
|
-
console.log("Installed:");
|
|
3770
|
-
const installed = [];
|
|
3771
|
-
if (claudeInstalled) installed.push("claude");
|
|
3772
|
-
if (codexInstalled) installed.push("codex");
|
|
3773
|
-
if (installed.length > 0) {
|
|
3774
|
-
for (const plugin of installed) {
|
|
3775
|
-
console.log(` - ${plugin}`);
|
|
3776
|
-
}
|
|
3777
|
-
} else {
|
|
3778
|
-
console.log(" (none)");
|
|
3779
|
-
}
|
|
3780
|
-
}
|
|
3781
|
-
function getClaudeSkillContent() {
|
|
3782
|
-
return `# Spets Command Executor
|
|
3783
|
-
|
|
3784
|
-
Execute orchestrator commands. Parse JSON response \u2192 execute matching action.
|
|
3785
|
-
|
|
3786
|
-
---
|
|
3787
|
-
|
|
3788
|
-
## Constraints
|
|
3789
|
-
|
|
3790
|
-
- **NO** EnterPlanMode
|
|
3791
|
-
- **NO** TaskCreate/TaskUpdate
|
|
3792
|
-
- **NO** subprocess spawning for AI calls
|
|
3793
|
-
- **IMMEDIATELY** execute commands, no planning
|
|
3794
|
-
|
|
3795
|
-
---
|
|
3796
|
-
|
|
3797
|
-
## Start
|
|
3798
|
-
|
|
3799
|
-
\`\`\`bash
|
|
3800
|
-
npx spets orchestrate init "$ARGUMENTS"
|
|
3801
|
-
\`\`\`
|
|
3802
|
-
|
|
3803
|
-
Parse JSON response \u2192 execute action from table below \u2192 loop until \`type: "complete"\` or \`type: "error"\`.
|
|
3804
|
-
|
|
3805
|
-
---
|
|
3806
|
-
|
|
3807
|
-
## Command Table
|
|
3808
|
-
|
|
3809
|
-
### \`type: "phase"\`
|
|
3810
|
-
|
|
3811
|
-
| \`phase\` | Action |
|
|
3812
|
-
|---------|--------|
|
|
3813
|
-
| \`explore\` | [ACTION_EXPLORE](#action_explore) |
|
|
3814
|
-
| \`clarify\` | [ACTION_CLARIFY](#action_clarify) |
|
|
3815
|
-
| \`execute\` | [ACTION_EXECUTE](#action_execute) |
|
|
3816
|
-
| \`verify\` | [ACTION_VERIFY](#action_verify) |
|
|
3817
|
-
| \`context\` | [ACTION_CONTEXT](#action_context) (legacy) |
|
|
3818
|
-
| \`generate\` | [ACTION_GENERATE](#action_generate) (legacy) |
|
|
3819
|
-
|
|
3820
|
-
### \`type: "checkpoint"\`
|
|
3821
|
-
|
|
3822
|
-
| \`checkpoint\` | Action |
|
|
3823
|
-
|--------------|--------|
|
|
3824
|
-
| \`clarify\` | [ACTION_ASK_QUESTIONS](#action_ask_questions) |
|
|
3825
|
-
| \`approve\` | [ACTION_ASK_APPROVAL](#action_ask_approval) |
|
|
3826
|
-
|
|
3827
|
-
### \`type: "complete"\`
|
|
3828
|
-
|
|
3829
|
-
| \`status\` | Action |
|
|
3830
|
-
|----------|--------|
|
|
3831
|
-
| \`completed\` | Print: "Workflow complete. Outputs: {outputs}" |
|
|
3832
|
-
| \`stopped\` | Print: "Workflow paused. Resume with: npx spets resume --task {taskId}" |
|
|
3833
|
-
| \`rejected\` | Print: "Workflow rejected." |
|
|
3834
|
-
|
|
3835
|
-
### \`type: "error"\`
|
|
3836
|
-
|
|
3837
|
-
Print: "Error: {error}" \u2192 STOP
|
|
3838
|
-
|
|
3839
|
-
---
|
|
3840
|
-
|
|
3841
|
-
## Action Definitions
|
|
3842
|
-
|
|
3843
|
-
**IMPORTANT:** Each phase response includes a \`prompt\` field containing the exact instructions to follow.
|
|
3844
|
-
This ensures identical behavior across CLI, Claude Code, and GitHub modes.
|
|
3845
|
-
|
|
3846
|
-
### ACTION_EXPLORE
|
|
3847
|
-
|
|
3848
|
-
\`\`\`
|
|
3849
|
-
1. Follow the instructions in response.prompt EXACTLY
|
|
3850
|
-
2. The prompt tells you:
|
|
3851
|
-
- What to search for
|
|
3852
|
-
- How to analyze the codebase
|
|
3853
|
-
- The exact JSON output format required
|
|
3854
|
-
3. Bash: npx spets orchestrate explore-done {taskId} '{exploreJson}'
|
|
3855
|
-
4. Parse response \u2192 next action
|
|
3856
|
-
\`\`\`
|
|
3857
|
-
|
|
3858
|
-
### ACTION_CLARIFY
|
|
3859
|
-
|
|
3860
|
-
\`\`\`
|
|
3861
|
-
1. Follow the instructions in response.prompt EXACTLY
|
|
3862
|
-
2. The prompt tells you:
|
|
3863
|
-
- What context to analyze
|
|
3864
|
-
- How to generate questions
|
|
3865
|
-
- The exact JSON output format required
|
|
3866
|
-
3. Bash: npx spets orchestrate clarify-done {taskId} '{questionsJson}'
|
|
3867
|
-
- Format: [{"id":"q1","question":"..."},...]
|
|
3868
|
-
- Empty if no questions: []
|
|
3869
|
-
4. Parse response \u2192 next action
|
|
3870
|
-
\`\`\`
|
|
3871
|
-
|
|
3872
|
-
### ACTION_EXECUTE
|
|
3873
|
-
|
|
3874
|
-
\`\`\`
|
|
3875
|
-
1. Follow the instructions in response.prompt EXACTLY
|
|
3876
|
-
2. The prompt tells you:
|
|
3877
|
-
- The instruction and template to follow
|
|
3878
|
-
- The explore output and answers to use
|
|
3879
|
-
- Where to save the document/code (context.output)
|
|
3880
|
-
- Any revision/verify feedback to address
|
|
3881
|
-
3. Write the document/code to context.output
|
|
3882
|
-
4. Bash: npx spets orchestrate execute-done {taskId}
|
|
3883
|
-
5. Parse response \u2192 next action
|
|
3884
|
-
\`\`\`
|
|
3885
|
-
|
|
3886
|
-
### ACTION_VERIFY
|
|
3887
|
-
|
|
3888
|
-
\`\`\`
|
|
3889
|
-
1. Follow the instructions in response.prompt EXACTLY
|
|
3890
|
-
2. The prompt tells you:
|
|
3891
|
-
- The document to verify (already included in prompt)
|
|
3892
|
-
- The requirements and template to check against
|
|
3893
|
-
- The scoring criteria and pass conditions
|
|
3894
|
-
- The exact JSON output format required
|
|
3895
|
-
3. Bash: npx spets orchestrate verify-done {taskId} '{verifyJson}'
|
|
3896
|
-
4. Parse response \u2192 next action
|
|
3897
|
-
- If passed: goes to human review
|
|
3898
|
-
- If failed (attempts < 3): goes back to draft phase (auto-fix)
|
|
3899
|
-
- If failed (attempts >= 3): goes to human review with warning
|
|
3900
|
-
\`\`\`
|
|
3901
|
-
|
|
3902
|
-
### ACTION_GENERATE (legacy)
|
|
3903
|
-
|
|
3904
|
-
\`\`\`
|
|
3905
|
-
1. Read(context.instruction)
|
|
3906
|
-
2. IF context.template EXISTS: Read(context.template)
|
|
3907
|
-
3. IF context.previousOutput EXISTS: Read(context.previousOutput)
|
|
3908
|
-
4. Generate document using: gatheredContext, answers, revisionFeedback
|
|
3909
|
-
5. Write(context.output, documentContent)
|
|
3910
|
-
6. Bash: npx spets orchestrate generate-done {taskId}
|
|
3911
|
-
7. Parse response \u2192 next action
|
|
3912
|
-
\`\`\`
|
|
3913
|
-
|
|
3914
|
-
### ACTION_CONTEXT (legacy)
|
|
3915
|
-
|
|
3916
|
-
\`\`\`
|
|
3917
|
-
1. Read(context.instruction)
|
|
3918
|
-
2. IF context.previousOutput EXISTS: Read(context.previousOutput)
|
|
3919
|
-
3. Explore codebase for: {description}
|
|
3920
|
-
4. Bash: npx spets orchestrate context-done {taskId}
|
|
3921
|
-
5. Parse response \u2192 next action
|
|
3922
|
-
\`\`\`
|
|
3923
|
-
|
|
3924
|
-
### ACTION_ASK_QUESTIONS
|
|
3925
|
-
|
|
3926
|
-
\`\`\`
|
|
3927
|
-
1. AskUserQuestion(
|
|
3928
|
-
questions: [
|
|
3929
|
-
FOR EACH q IN questions:
|
|
3930
|
-
{
|
|
3931
|
-
question: q.question,
|
|
3932
|
-
header: "Q{index}",
|
|
3933
|
-
options: q.options OR [
|
|
3934
|
-
{label: "Provide answer", description: "Type your answer"}
|
|
3935
|
-
]
|
|
3936
|
-
}
|
|
3937
|
-
]
|
|
3938
|
-
)
|
|
3939
|
-
|
|
3940
|
-
2. Collect answers into: [{"questionId":"q1","answer":"..."},...]
|
|
3941
|
-
|
|
3942
|
-
3. Bash: npx spets orchestrate clarified {taskId} '{answersJson}'
|
|
3943
|
-
|
|
3944
|
-
4. Parse response \u2192 next action
|
|
3945
|
-
\`\`\`
|
|
3946
|
-
|
|
3947
|
-
### ACTION_ASK_APPROVAL
|
|
3948
|
-
|
|
3949
|
-
\`\`\`
|
|
3950
|
-
1. Read(specPath)
|
|
3951
|
-
|
|
3952
|
-
2. Summarize document (2-3 sentences)
|
|
3953
|
-
|
|
3954
|
-
3. AskUserQuestion(
|
|
3955
|
-
questions: [{
|
|
3956
|
-
question: "Review {step} document. What would you like to do?",
|
|
3957
|
-
header: "Review",
|
|
3958
|
-
options: [
|
|
3959
|
-
{label: "Approve", description: "Continue to next step"},
|
|
3960
|
-
{label: "Revise", description: "Request changes"},
|
|
3961
|
-
{label: "Reject", description: "Stop workflow"},
|
|
3962
|
-
{label: "Stop", description: "Pause for later"}
|
|
3963
|
-
]
|
|
3964
|
-
}]
|
|
3965
|
-
)
|
|
3966
|
-
|
|
3967
|
-
4. SWITCH answer:
|
|
3968
|
-
- "Approve": Bash: npx spets orchestrate approve {taskId}
|
|
3969
|
-
- "Revise":
|
|
3970
|
-
- Ask for feedback (AskUserQuestion or text input)
|
|
3971
|
-
- Bash: npx spets orchestrate revise {taskId} "{feedback}"
|
|
3972
|
-
- "Reject": Bash: npx spets orchestrate reject {taskId}
|
|
3973
|
-
- "Stop": Bash: npx spets orchestrate stop {taskId}
|
|
3974
|
-
|
|
3975
|
-
5. Parse response \u2192 next action
|
|
3976
|
-
\`\`\`
|
|
3977
|
-
|
|
3978
|
-
---
|
|
3979
|
-
|
|
3980
|
-
## Orchestrator Commands Reference
|
|
3981
|
-
|
|
3982
|
-
\`\`\`bash
|
|
3983
|
-
# New 5-phase workflow
|
|
3984
|
-
npx spets orchestrate init "<description>"
|
|
3985
|
-
npx spets orchestrate explore-done <taskId> '<json>'
|
|
3986
|
-
npx spets orchestrate clarify-done <taskId> '<json>'
|
|
3987
|
-
npx spets orchestrate clarified <taskId> '<json>'
|
|
3988
|
-
npx spets orchestrate execute-done <taskId>
|
|
3989
|
-
npx spets orchestrate verify-done <taskId> '<json>'
|
|
3990
|
-
npx spets orchestrate approve <taskId>
|
|
3991
|
-
npx spets orchestrate revise <taskId> "<feedback>"
|
|
3992
|
-
npx spets orchestrate reject <taskId>
|
|
3993
|
-
npx spets orchestrate stop <taskId>
|
|
3994
|
-
|
|
3995
|
-
# Legacy (backward compatible)
|
|
3996
|
-
npx spets orchestrate context-done <taskId>
|
|
3997
|
-
npx spets orchestrate generate-done <taskId>
|
|
3998
|
-
\`\`\`
|
|
3999
|
-
|
|
4000
|
-
---
|
|
4001
|
-
|
|
4002
|
-
$ARGUMENTS
|
|
4003
|
-
description: Task description for the workflow
|
|
4004
|
-
`;
|
|
4005
|
-
}
|
|
4006
|
-
function getCodexSkillContent() {
|
|
4007
|
-
return `---
|
|
4008
|
-
name: spets
|
|
4009
|
-
description: SDD workflow executor - runs spec-driven development workflows within Codex CLI
|
|
4010
|
-
---
|
|
4011
|
-
|
|
4012
|
-
# Spets Command Executor
|
|
4013
|
-
|
|
4014
|
-
Execute orchestrator commands. Parse JSON response and execute matching action.
|
|
4015
|
-
|
|
4016
|
-
## Start
|
|
4017
|
-
|
|
4018
|
-
Run: npx spets orchestrate init "$ARGUMENTS"
|
|
4019
|
-
|
|
4020
|
-
Parse JSON response, then execute action from table below, loop until type: "complete" or "error".
|
|
4021
|
-
|
|
4022
|
-
## Command Table
|
|
4023
|
-
|
|
4024
|
-
### type: "phase"
|
|
4025
|
-
|
|
4026
|
-
| phase | Action |
|
|
4027
|
-
|-------|--------|
|
|
4028
|
-
| explore | Follow response.prompt to explore codebase |
|
|
4029
|
-
| clarify | Follow response.prompt to generate questions |
|
|
4030
|
-
| execute | Follow response.prompt to write document |
|
|
4031
|
-
| verify | Follow response.prompt to validate document |
|
|
4032
|
-
|
|
4033
|
-
### type: "checkpoint"
|
|
4034
|
-
|
|
4035
|
-
| checkpoint | Action |
|
|
4036
|
-
|------------|--------|
|
|
4037
|
-
| clarify | Ask user questions, collect answers |
|
|
4038
|
-
| approve | Ask user to review document |
|
|
4039
|
-
|
|
4040
|
-
### type: "complete"
|
|
4041
|
-
|
|
4042
|
-
Print completion status and outputs.
|
|
4043
|
-
|
|
4044
|
-
### type: "error"
|
|
4045
|
-
|
|
4046
|
-
Print error and stop.
|
|
4047
|
-
|
|
4048
|
-
## Orchestrator Commands
|
|
4049
|
-
|
|
4050
|
-
npx spets orchestrate init "<description>"
|
|
4051
|
-
npx spets orchestrate explore-done <taskId> '<json>'
|
|
4052
|
-
npx spets orchestrate clarify-done <taskId> '<json>'
|
|
4053
|
-
npx spets orchestrate clarified <taskId> '<json>'
|
|
4054
|
-
npx spets orchestrate execute-done <taskId>
|
|
4055
|
-
npx spets orchestrate verify-done <taskId> '<json>'
|
|
4056
|
-
npx spets orchestrate approve <taskId>
|
|
4057
|
-
npx spets orchestrate revise <taskId> "<feedback>"
|
|
4058
|
-
npx spets orchestrate reject <taskId>
|
|
4059
|
-
npx spets orchestrate stop <taskId>
|
|
4060
|
-
|
|
4061
|
-
$ARGUMENTS
|
|
4062
|
-
description: Task description for the workflow
|
|
4063
|
-
`;
|
|
4064
|
-
}
|
|
4065
|
-
|
|
4066
4039
|
// src/commands/github.ts
|
|
4067
|
-
import { execSync as execSync4, spawn as
|
|
4068
|
-
import { readdirSync as readdirSync4, readFileSync as
|
|
4040
|
+
import { execSync as execSync4, spawn as spawn6, spawnSync as spawnSync4 } from "child_process";
|
|
4041
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync12, existsSync as existsSync14 } from "fs";
|
|
4069
4042
|
import { join as join10 } from "path";
|
|
4070
4043
|
|
|
4071
4044
|
// src/hooks/runner.ts
|
|
4072
|
-
import { spawn as
|
|
4073
|
-
import { existsSync as
|
|
4045
|
+
import { spawn as spawn5 } from "child_process";
|
|
4046
|
+
import { existsSync as existsSync13 } from "fs";
|
|
4074
4047
|
import { join as join9, isAbsolute } from "path";
|
|
4075
4048
|
async function runHook(hookPath, context, cwd = process.cwd()) {
|
|
4076
4049
|
const resolvedPath = isAbsolute(hookPath) ? hookPath : join9(getSpetsDir(cwd), hookPath);
|
|
4077
|
-
if (!
|
|
4050
|
+
if (!existsSync13(resolvedPath)) {
|
|
4078
4051
|
console.warn(`Hook not found: ${resolvedPath}`);
|
|
4079
4052
|
return;
|
|
4080
4053
|
}
|
|
@@ -4088,7 +4061,7 @@ async function runHook(hookPath, context, cwd = process.cwd()) {
|
|
|
4088
4061
|
SPETS_CWD: cwd
|
|
4089
4062
|
};
|
|
4090
4063
|
return new Promise((resolve, reject) => {
|
|
4091
|
-
const proc =
|
|
4064
|
+
const proc = spawn5(resolvedPath, [], {
|
|
4092
4065
|
cwd,
|
|
4093
4066
|
env,
|
|
4094
4067
|
stdio: "inherit",
|
|
@@ -4185,7 +4158,7 @@ function findTaskId(cwd, owner, repo, issueOrPr) {
|
|
|
4185
4158
|
}
|
|
4186
4159
|
}
|
|
4187
4160
|
const outputsDir = getOutputsDir(cwd);
|
|
4188
|
-
if (
|
|
4161
|
+
if (existsSync14(outputsDir)) {
|
|
4189
4162
|
const taskDirs = readdirSync4(outputsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort().reverse();
|
|
4190
4163
|
if (taskDirs.length > 0) {
|
|
4191
4164
|
return taskDirs[0];
|
|
@@ -4418,7 +4391,7 @@ async function postComment(config, body) {
|
|
|
4418
4391
|
const { owner, repo, prNumber, issueNumber } = config;
|
|
4419
4392
|
const args = prNumber ? ["pr", "comment", String(prNumber), "--body", body, "-R", `${owner}/${repo}`] : ["issue", "comment", String(issueNumber), "--body", body, "-R", `${owner}/${repo}`];
|
|
4420
4393
|
return new Promise((resolve, reject) => {
|
|
4421
|
-
const proc =
|
|
4394
|
+
const proc = spawn6("gh", args, { stdio: "inherit" });
|
|
4422
4395
|
proc.on("close", (code) => {
|
|
4423
4396
|
if (code !== 0) reject(new Error(`gh failed with code ${code}`));
|
|
4424
4397
|
else resolve();
|
|
@@ -4430,10 +4403,10 @@ async function createPullRequestWithAI(config, taskId, userQuery, cwd) {
|
|
|
4430
4403
|
const { owner, repo, issueNumber } = config;
|
|
4431
4404
|
const outputsDir = join10(getOutputsDir(cwd), taskId);
|
|
4432
4405
|
const outputs = [];
|
|
4433
|
-
if (
|
|
4406
|
+
if (existsSync14(outputsDir)) {
|
|
4434
4407
|
const files = readdirSync4(outputsDir).filter((f) => f.endsWith(".md"));
|
|
4435
4408
|
for (const file of files) {
|
|
4436
|
-
const content =
|
|
4409
|
+
const content = readFileSync12(join10(outputsDir, file), "utf-8");
|
|
4437
4410
|
outputs.push({ step: file.replace(".md", ""), content });
|
|
4438
4411
|
}
|
|
4439
4412
|
}
|
|
@@ -4519,7 +4492,7 @@ Closes #${issueNumber}`;
|
|
|
4519
4492
|
}
|
|
4520
4493
|
async function callClaude(prompt) {
|
|
4521
4494
|
return new Promise((resolve, reject) => {
|
|
4522
|
-
const proc =
|
|
4495
|
+
const proc = spawn6("claude", ["--print"], { stdio: ["pipe", "pipe", "pipe"] });
|
|
4523
4496
|
let stdout = "";
|
|
4524
4497
|
let stderr = "";
|
|
4525
4498
|
proc.stdout.on("data", (data) => {
|
|
@@ -4799,8 +4772,8 @@ async function orchestrateCommand(action, args) {
|
|
|
4799
4772
|
}
|
|
4800
4773
|
|
|
4801
4774
|
// src/index.ts
|
|
4802
|
-
var __dirname2 =
|
|
4803
|
-
var pkg = JSON.parse(
|
|
4775
|
+
var __dirname2 = dirname7(fileURLToPath2(import.meta.url));
|
|
4776
|
+
var pkg = JSON.parse(readFileSync13(join11(__dirname2, "..", "package.json"), "utf-8"));
|
|
4804
4777
|
var program = new Command();
|
|
4805
4778
|
program.name("spets").description("Spec Driven Development Execution Framework").version(pkg.version);
|
|
4806
4779
|
program.command("init").description("Initialize spets in current directory").option("-f, --force", "Overwrite existing config").option("--github", "Add GitHub Actions workflow for PR/Issue integration").action(initCommand);
|