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