spets 0.1.50 → 0.1.52
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +713 -919
- 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,203 @@ 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 spetsDir = join2(process.cwd(), ".codex", "skills", "spets");
|
|
93
130
|
mkdirSync(spetsDir, { recursive: true });
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
131
|
+
const skillPath = join2(spetsDir, "SKILL.md");
|
|
132
|
+
writeFileSync(skillPath, getCodexSkillContent());
|
|
133
|
+
console.log("Installed Codex CLI plugin.");
|
|
134
|
+
console.log(`Location: ${skillPath}`);
|
|
135
|
+
console.log("");
|
|
136
|
+
console.log("Usage in Codex CLI:");
|
|
137
|
+
console.log(' $spets "your task description"');
|
|
138
|
+
console.log("");
|
|
139
|
+
console.log("This skill runs deterministically within your Codex CLI session.");
|
|
140
|
+
console.log("No additional Codex processes are spawned.");
|
|
141
|
+
}
|
|
142
|
+
function installOpenCodePlugin() {
|
|
143
|
+
const spetsDir = join2(process.cwd(), ".opencode", "skills", "spets");
|
|
144
|
+
mkdirSync(spetsDir, { recursive: true });
|
|
145
|
+
const skillPath = join2(spetsDir, "SKILL.md");
|
|
146
|
+
writeFileSync(skillPath, getOpenCodeSkillContent());
|
|
147
|
+
console.log("Installed OpenCode plugin.");
|
|
148
|
+
console.log(`Location: ${skillPath}`);
|
|
149
|
+
console.log("");
|
|
150
|
+
console.log("Usage in OpenCode:");
|
|
151
|
+
console.log(" Use the spets skill in your OpenCode session");
|
|
152
|
+
console.log("");
|
|
153
|
+
console.log("This skill runs deterministically within your OpenCode session.");
|
|
154
|
+
console.log("No additional OpenCode processes are spawned.");
|
|
155
|
+
}
|
|
156
|
+
async function uninstallPlugin(name) {
|
|
157
|
+
if (name === "claude") {
|
|
158
|
+
const skillPath = join2(homedir(), ".claude", "commands", "spets.md");
|
|
159
|
+
const legacySkillPath = join2(homedir(), ".claude", "commands", "sdd-do.md");
|
|
160
|
+
let uninstalled = false;
|
|
161
|
+
if (existsSync2(skillPath)) {
|
|
162
|
+
rmSync(skillPath);
|
|
163
|
+
uninstalled = true;
|
|
164
|
+
}
|
|
165
|
+
if (existsSync2(legacySkillPath)) {
|
|
166
|
+
rmSync(legacySkillPath);
|
|
167
|
+
uninstalled = true;
|
|
168
|
+
}
|
|
169
|
+
if (uninstalled) {
|
|
170
|
+
console.log("Uninstalled Claude Code plugin.");
|
|
171
|
+
} else {
|
|
172
|
+
console.log("Claude Code plugin not installed.");
|
|
105
173
|
}
|
|
174
|
+
return;
|
|
106
175
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
176
|
+
if (name === "codex") {
|
|
177
|
+
const projectSkillPath = join2(process.cwd(), ".codex", "skills", "spets", "SKILL.md");
|
|
178
|
+
const globalSkillPath = join2(homedir(), ".codex", "skills", "spets", "SKILL.md");
|
|
179
|
+
let uninstalled = false;
|
|
180
|
+
for (const skillPath of [projectSkillPath, globalSkillPath]) {
|
|
181
|
+
if (existsSync2(skillPath)) {
|
|
182
|
+
rmSync(skillPath);
|
|
183
|
+
try {
|
|
184
|
+
const skillDir = join2(skillPath, "..");
|
|
185
|
+
const skillsDir = join2(skillDir, "..");
|
|
186
|
+
rmSync(skillDir, { recursive: true });
|
|
187
|
+
const remaining = readdirSync(skillsDir);
|
|
188
|
+
if (remaining.length === 0) {
|
|
189
|
+
rmSync(skillsDir);
|
|
190
|
+
}
|
|
191
|
+
} catch {
|
|
192
|
+
}
|
|
193
|
+
uninstalled = true;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (uninstalled) {
|
|
197
|
+
console.log("Uninstalled Codex CLI plugin.");
|
|
198
|
+
} else {
|
|
199
|
+
console.log("Codex CLI plugin not installed.");
|
|
200
|
+
}
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (name === "opencode") {
|
|
204
|
+
const projectSkillPath = join2(process.cwd(), ".opencode", "skills", "spets", "SKILL.md");
|
|
205
|
+
const globalSkillPath = join2(homedir(), ".config", "opencode", "skills", "spets", "SKILL.md");
|
|
206
|
+
let uninstalled = false;
|
|
207
|
+
for (const skillPath of [projectSkillPath, globalSkillPath]) {
|
|
208
|
+
if (existsSync2(skillPath)) {
|
|
209
|
+
rmSync(skillPath);
|
|
210
|
+
try {
|
|
211
|
+
const skillDir = join2(skillPath, "..");
|
|
212
|
+
const skillsDir = join2(skillDir, "..");
|
|
213
|
+
rmSync(skillDir, { recursive: true });
|
|
214
|
+
const remaining = readdirSync(skillsDir);
|
|
215
|
+
if (remaining.length === 0) {
|
|
216
|
+
rmSync(skillsDir);
|
|
217
|
+
}
|
|
218
|
+
} catch {
|
|
219
|
+
}
|
|
220
|
+
uninstalled = true;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (uninstalled) {
|
|
224
|
+
console.log("Uninstalled OpenCode plugin.");
|
|
225
|
+
} else {
|
|
226
|
+
console.log("OpenCode plugin not installed.");
|
|
227
|
+
}
|
|
228
|
+
return;
|
|
123
229
|
}
|
|
230
|
+
console.error(`Unknown plugin: ${name}`);
|
|
231
|
+
process.exit(1);
|
|
232
|
+
}
|
|
233
|
+
async function listPlugins() {
|
|
234
|
+
console.log("Available plugins:");
|
|
124
235
|
console.log("");
|
|
125
|
-
console.log("
|
|
126
|
-
console.log("
|
|
127
|
-
console.log("
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
236
|
+
console.log(" claude - Claude Code /spets skill (global)");
|
|
237
|
+
console.log(" codex - Codex CLI $spets skill (project-level)");
|
|
238
|
+
console.log(" opencode - OpenCode spets skill (project-level)");
|
|
239
|
+
console.log("");
|
|
240
|
+
const claudeSkillPath = join2(homedir(), ".claude", "commands", "spets.md");
|
|
241
|
+
const claudeLegacySkillPath = join2(homedir(), ".claude", "commands", "sdd-do.md");
|
|
242
|
+
const claudeInstalled = existsSync2(claudeSkillPath) || existsSync2(claudeLegacySkillPath);
|
|
243
|
+
const codexProjectPath = join2(process.cwd(), ".codex", "skills", "spets", "SKILL.md");
|
|
244
|
+
const codexGlobalPath = join2(homedir(), ".codex", "skills", "spets", "SKILL.md");
|
|
245
|
+
const codexInstalled = existsSync2(codexProjectPath) || existsSync2(codexGlobalPath);
|
|
246
|
+
const codexLocation = existsSync2(codexProjectPath) ? "project" : existsSync2(codexGlobalPath) ? "global" : null;
|
|
247
|
+
const opencodeProjectPath = join2(process.cwd(), ".opencode", "skills", "spets", "SKILL.md");
|
|
248
|
+
const opencodeGlobalPath = join2(homedir(), ".config", "opencode", "skills", "spets", "SKILL.md");
|
|
249
|
+
const opencodeInstalled = existsSync2(opencodeProjectPath) || existsSync2(opencodeGlobalPath);
|
|
250
|
+
const opencodeLocation = existsSync2(opencodeProjectPath) ? "project" : existsSync2(opencodeGlobalPath) ? "global" : null;
|
|
251
|
+
console.log("Installed:");
|
|
252
|
+
const installed = [];
|
|
253
|
+
if (claudeInstalled) installed.push("claude (global)");
|
|
254
|
+
if (codexInstalled) installed.push(`codex (${codexLocation})`);
|
|
255
|
+
if (opencodeInstalled) installed.push(`opencode (${opencodeLocation})`);
|
|
256
|
+
if (installed.length > 0) {
|
|
257
|
+
for (const plugin of installed) {
|
|
258
|
+
console.log(` - ${plugin}`);
|
|
259
|
+
}
|
|
131
260
|
} else {
|
|
132
|
-
console.log(
|
|
261
|
+
console.log(" (none)");
|
|
133
262
|
}
|
|
134
263
|
}
|
|
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() {
|
|
264
|
+
function getClaudeSkillContent() {
|
|
189
265
|
return `# Spets Command Executor
|
|
190
266
|
|
|
191
267
|
Execute orchestrator commands. Parse JSON response \u2192 execute matching action.
|
|
@@ -217,11 +293,10 @@ Parse JSON response \u2192 execute action from table below \u2192 loop until \`t
|
|
|
217
293
|
|
|
218
294
|
| \`phase\` | Action |
|
|
219
295
|
|---------|--------|
|
|
220
|
-
| \`
|
|
296
|
+
| \`explore\` | [ACTION_EXPLORE](#action_explore) |
|
|
221
297
|
| \`clarify\` | [ACTION_CLARIFY](#action_clarify) |
|
|
222
|
-
| \`
|
|
223
|
-
| \`
|
|
224
|
-
| \`consolidate\` | [ACTION_PARALLEL_CONSOLIDATE](#action_parallel_consolidate) |
|
|
298
|
+
| \`execute\` | [ACTION_EXECUTE](#action_execute) |
|
|
299
|
+
| \`verify\` | [ACTION_VERIFY](#action_verify) |
|
|
225
300
|
|
|
226
301
|
### \`type: "checkpoint"\`
|
|
227
302
|
|
|
@@ -246,96 +321,63 @@ Print: "Error: {error}" \u2192 STOP
|
|
|
246
321
|
|
|
247
322
|
## Action Definitions
|
|
248
323
|
|
|
249
|
-
|
|
324
|
+
**IMPORTANT:** Each phase response includes a \`prompt\` field containing the exact instructions to follow.
|
|
325
|
+
This ensures identical behavior across CLI, Claude Code, and GitHub modes.
|
|
326
|
+
|
|
327
|
+
### ACTION_EXPLORE
|
|
250
328
|
|
|
251
329
|
\`\`\`
|
|
252
|
-
1.
|
|
253
|
-
2.
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
330
|
+
1. Follow the instructions in response.prompt EXACTLY
|
|
331
|
+
2. The prompt tells you:
|
|
332
|
+
- What to search for
|
|
333
|
+
- How to analyze the codebase
|
|
334
|
+
- The exact JSON output format required
|
|
335
|
+
3. Bash: npx spets orchestrate explore-done {taskId} '{exploreJson}'
|
|
336
|
+
4. Parse response \u2192 next action
|
|
257
337
|
\`\`\`
|
|
258
338
|
|
|
259
339
|
### ACTION_CLARIFY
|
|
260
340
|
|
|
261
341
|
\`\`\`
|
|
262
|
-
1.
|
|
263
|
-
2.
|
|
342
|
+
1. Follow the instructions in response.prompt EXACTLY
|
|
343
|
+
2. The prompt tells you:
|
|
344
|
+
- What context to analyze
|
|
345
|
+
- How to generate questions
|
|
346
|
+
- The exact JSON output format required
|
|
264
347
|
3. Bash: npx spets orchestrate clarify-done {taskId} '{questionsJson}'
|
|
265
348
|
- Format: [{"id":"q1","question":"..."},...]
|
|
266
349
|
- Empty if no questions: []
|
|
267
350
|
4. Parse response \u2192 next action
|
|
268
351
|
\`\`\`
|
|
269
352
|
|
|
270
|
-
###
|
|
353
|
+
### ACTION_EXECUTE
|
|
271
354
|
|
|
272
355
|
\`\`\`
|
|
273
|
-
1.
|
|
274
|
-
2.
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
### ACTION_PARALLEL_SECTIONS
|
|
283
|
-
|
|
284
|
-
**PARALLEL EXECUTION (FOREGROUND)**
|
|
285
|
-
|
|
356
|
+
1. Follow the instructions in response.prompt EXACTLY
|
|
357
|
+
2. The prompt tells you:
|
|
358
|
+
- The instruction and template to follow
|
|
359
|
+
- The explore output and answers to use
|
|
360
|
+
- Where to save the document/code (context.output)
|
|
361
|
+
- Any revision/verify feedback to address
|
|
362
|
+
3. Write the document/code to context.output
|
|
363
|
+
4. Bash: npx spets orchestrate execute-done {taskId}
|
|
364
|
+
5. Parse response \u2192 next action
|
|
286
365
|
\`\`\`
|
|
287
|
-
1. FOR EACH section IN parallelSections:
|
|
288
|
-
Task(
|
|
289
|
-
subagent_type: "general-purpose",
|
|
290
|
-
prompt: "
|
|
291
|
-
Generate section and save to file. Return ONLY the path.
|
|
292
|
-
|
|
293
|
-
Path: {section.outputPath}
|
|
294
|
-
Title: {section.title}
|
|
295
|
-
Instruction: {section.instruction}
|
|
296
|
-
Context: {sharedContext.description}
|
|
297
|
-
|
|
298
|
-
1. Generate content for this section
|
|
299
|
-
2. Write to the path using Write tool
|
|
300
|
-
3. Return ONLY: {section.outputPath}
|
|
301
|
-
(no explanations, no content, just the path)
|
|
302
|
-
"
|
|
303
|
-
)
|
|
304
|
-
|
|
305
|
-
2. ALL Task calls MUST be in SINGLE message (parallel execution)
|
|
306
366
|
|
|
307
|
-
|
|
367
|
+
### ACTION_VERIFY
|
|
308
368
|
|
|
309
|
-
4. Parse response \u2192 next action
|
|
310
369
|
\`\`\`
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
Task(
|
|
319
|
-
subagent_type: "general-purpose",
|
|
320
|
-
prompt: "
|
|
321
|
-
Merge children into parent. Return ONLY the path.
|
|
322
|
-
|
|
323
|
-
Output: {group.outputPath}
|
|
324
|
-
Children: {group.childPaths}
|
|
325
|
-
|
|
326
|
-
1. Read each child file
|
|
327
|
-
2. Merge preserving hierarchy
|
|
328
|
-
3. Write to output using Write tool
|
|
329
|
-
4. Return ONLY: {group.outputPath}
|
|
330
|
-
(no explanations, no content, just the path)
|
|
331
|
-
"
|
|
332
|
-
)
|
|
333
|
-
|
|
334
|
-
2. ALL Task calls MUST be in SINGLE message (parallel execution)
|
|
335
|
-
|
|
336
|
-
3. Bash: npx spets orchestrate consolidate-level-done {taskId} {level}
|
|
337
|
-
|
|
370
|
+
1. Follow the instructions in response.prompt EXACTLY
|
|
371
|
+
2. The prompt tells you:
|
|
372
|
+
- The document to verify (already included in prompt)
|
|
373
|
+
- The requirements and template to check against
|
|
374
|
+
- The scoring criteria and pass conditions
|
|
375
|
+
- The exact JSON output format required
|
|
376
|
+
3. Bash: npx spets orchestrate verify-done {taskId} '{verifyJson}'
|
|
338
377
|
4. Parse response \u2192 next action
|
|
378
|
+
- If passed: goes to human review
|
|
379
|
+
- If failed (attempts < 3): goes back to draft phase (auto-fix)
|
|
380
|
+
- If failed (attempts >= 3): goes to human review with warning
|
|
339
381
|
\`\`\`
|
|
340
382
|
|
|
341
383
|
### ACTION_ASK_QUESTIONS
|
|
@@ -397,13 +439,13 @@ Print: "Error: {error}" \u2192 STOP
|
|
|
397
439
|
## Orchestrator Commands Reference
|
|
398
440
|
|
|
399
441
|
\`\`\`bash
|
|
442
|
+
# New 5-phase workflow
|
|
400
443
|
npx spets orchestrate init "<description>"
|
|
401
|
-
npx spets orchestrate
|
|
444
|
+
npx spets orchestrate explore-done <taskId> '<json>'
|
|
402
445
|
npx spets orchestrate clarify-done <taskId> '<json>'
|
|
403
446
|
npx spets orchestrate clarified <taskId> '<json>'
|
|
404
|
-
npx spets orchestrate
|
|
405
|
-
npx spets orchestrate
|
|
406
|
-
npx spets orchestrate consolidate-level-done <taskId> <level>
|
|
447
|
+
npx spets orchestrate execute-done <taskId>
|
|
448
|
+
npx spets orchestrate verify-done <taskId> '<json>'
|
|
407
449
|
npx spets orchestrate approve <taskId>
|
|
408
450
|
npx spets orchestrate revise <taskId> "<feedback>"
|
|
409
451
|
npx spets orchestrate reject <taskId>
|
|
@@ -416,111 +458,389 @@ $ARGUMENTS
|
|
|
416
458
|
description: Task description for the workflow
|
|
417
459
|
`;
|
|
418
460
|
}
|
|
419
|
-
function
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
const workflowTemplate = readFileSync2(join2(__dirname, "..", "assets", "github", "workflows", "spets.yml"), "utf-8");
|
|
425
|
-
const issueTemplate = readFileSync2(join2(__dirname, "..", "assets", "github", "ISSUE_TEMPLATE", "spets-task.yml"), "utf-8");
|
|
426
|
-
writeFileSync(join2(workflowDir, "spets.yml"), workflowTemplate);
|
|
427
|
-
writeFileSync(join2(templateDir, "spets-task.yml"), issueTemplate);
|
|
428
|
-
createGitHubLabel();
|
|
429
|
-
}
|
|
430
|
-
function createGitHubLabel() {
|
|
431
|
-
try {
|
|
432
|
-
execSync("gh --version", { stdio: "ignore" });
|
|
433
|
-
execSync('gh label create spets --description "Spets workflow task" --color 0E8A16 --force', {
|
|
434
|
-
stdio: "ignore"
|
|
435
|
-
});
|
|
436
|
-
console.log(' GitHub label "spets" created');
|
|
437
|
-
} catch {
|
|
438
|
-
console.log("");
|
|
439
|
-
console.log(" Note: Could not create GitHub label automatically.");
|
|
440
|
-
console.log(' Please create the "spets" label manually or run:');
|
|
441
|
-
console.log(' gh label create spets --description "Spets workflow task" --color 0E8A16');
|
|
442
|
-
}
|
|
443
|
-
}
|
|
461
|
+
function getCodexSkillContent() {
|
|
462
|
+
return `---
|
|
463
|
+
name: spets
|
|
464
|
+
description: SDD workflow executor - runs spec-driven development workflows within Codex CLI
|
|
465
|
+
---
|
|
444
466
|
|
|
445
|
-
|
|
446
|
-
import { readFileSync as readFileSync3, existsSync as existsSync3, readdirSync, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
447
|
-
import { join as join3 } from "path";
|
|
448
|
-
import matter from "gray-matter";
|
|
467
|
+
# Spets Command Executor
|
|
449
468
|
|
|
450
|
-
|
|
451
|
-
function generateRandomSuffix() {
|
|
452
|
-
return Math.random().toString(36).substring(2, 6);
|
|
453
|
-
}
|
|
454
|
-
function generateDatePrefix() {
|
|
455
|
-
const now = /* @__PURE__ */ new Date();
|
|
456
|
-
const year = now.getFullYear();
|
|
457
|
-
const month = String(now.getMonth() + 1).padStart(2, "0");
|
|
458
|
-
const day = String(now.getDate()).padStart(2, "0");
|
|
459
|
-
return `${year}-${month}-${day}`;
|
|
460
|
-
}
|
|
461
|
-
function sanitizeDescription(description) {
|
|
462
|
-
let sanitized = description.toLowerCase();
|
|
463
|
-
sanitized = sanitized.replace(/[^\x00-\x7F]/g, "");
|
|
464
|
-
sanitized = sanitized.replace(/[^a-z0-9\s]/g, "");
|
|
465
|
-
sanitized = sanitized.trim();
|
|
466
|
-
sanitized = sanitized.replace(/\s+/g, " ");
|
|
467
|
-
sanitized = sanitized.replace(/\s+/g, "-");
|
|
468
|
-
if (sanitized.length > 10) {
|
|
469
|
-
sanitized = sanitized.substring(0, 10);
|
|
470
|
-
}
|
|
471
|
-
sanitized = sanitized.replace(/-+$/, "");
|
|
472
|
-
if (sanitized === "") {
|
|
473
|
-
sanitized = "task";
|
|
474
|
-
}
|
|
475
|
-
return sanitized;
|
|
476
|
-
}
|
|
477
|
-
function generateSlug(description) {
|
|
478
|
-
const datePrefix = generateDatePrefix();
|
|
479
|
-
const meaningfulName = sanitizeDescription(description);
|
|
480
|
-
const randomSuffix = generateRandomSuffix();
|
|
481
|
-
return `${datePrefix}-${meaningfulName}-${randomSuffix}`;
|
|
482
|
-
}
|
|
469
|
+
Execute orchestrator commands. Parse JSON response \u2192 execute matching action.
|
|
483
470
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
471
|
+
---
|
|
472
|
+
|
|
473
|
+
## Constraints
|
|
474
|
+
|
|
475
|
+
- **IMMEDIATELY** execute commands, no planning
|
|
476
|
+
- **DO NOT** explore or search on your own - ONLY follow response.prompt
|
|
477
|
+
|
|
478
|
+
---
|
|
479
|
+
|
|
480
|
+
## Start
|
|
481
|
+
|
|
482
|
+
\`\`\`bash
|
|
483
|
+
npx spets orchestrate init "$ARGUMENTS"
|
|
484
|
+
\`\`\`
|
|
485
|
+
|
|
486
|
+
Parse JSON response \u2192 execute action from table below \u2192 loop until \`type: "complete"\` or \`type: "error"\`.
|
|
487
|
+
|
|
488
|
+
---
|
|
489
|
+
|
|
490
|
+
## Command Table
|
|
491
|
+
|
|
492
|
+
### \`type: "phase"\`
|
|
493
|
+
|
|
494
|
+
| \`phase\` | Action |
|
|
495
|
+
|---------|--------|
|
|
496
|
+
| \`explore\` | [ACTION_EXPLORE](#action_explore) |
|
|
497
|
+
| \`clarify\` | [ACTION_CLARIFY](#action_clarify) |
|
|
498
|
+
| \`execute\` | [ACTION_EXECUTE](#action_execute) |
|
|
499
|
+
| \`verify\` | [ACTION_VERIFY](#action_verify) |
|
|
500
|
+
|
|
501
|
+
### \`type: "checkpoint"\`
|
|
502
|
+
|
|
503
|
+
| \`checkpoint\` | Action |
|
|
504
|
+
|--------------|--------|
|
|
505
|
+
| \`clarify\` | [ACTION_ASK_QUESTIONS](#action_ask_questions) |
|
|
506
|
+
| \`approve\` | [ACTION_ASK_APPROVAL](#action_ask_approval) |
|
|
507
|
+
|
|
508
|
+
### \`type: "complete"\`
|
|
509
|
+
|
|
510
|
+
| \`status\` | Action |
|
|
511
|
+
|----------|--------|
|
|
512
|
+
| \`completed\` | Print: "Workflow complete. Outputs: {outputs}" |
|
|
513
|
+
| \`stopped\` | Print: "Workflow paused. Resume with: npx spets resume --task {taskId}" |
|
|
514
|
+
| \`rejected\` | Print: "Workflow rejected." |
|
|
515
|
+
|
|
516
|
+
### \`type: "error"\`
|
|
517
|
+
|
|
518
|
+
Print: "Error: {error}" \u2192 STOP
|
|
519
|
+
|
|
520
|
+
---
|
|
521
|
+
|
|
522
|
+
## Action Definitions
|
|
523
|
+
|
|
524
|
+
**IMPORTANT:** Each phase response includes a \`prompt\` field containing the exact instructions to follow.
|
|
525
|
+
You MUST follow the prompt EXACTLY. Do NOT explore or search on your own.
|
|
526
|
+
|
|
527
|
+
### ACTION_EXPLORE
|
|
528
|
+
|
|
529
|
+
\`\`\`
|
|
530
|
+
1. Follow the instructions in response.prompt EXACTLY
|
|
531
|
+
2. The prompt tells you:
|
|
532
|
+
- What to search for
|
|
533
|
+
- How to analyze the codebase
|
|
534
|
+
- The exact JSON output format required
|
|
535
|
+
3. Run: npx spets orchestrate explore-done {taskId} '{exploreJson}'
|
|
536
|
+
4. Parse response \u2192 next action
|
|
537
|
+
\`\`\`
|
|
538
|
+
|
|
539
|
+
### ACTION_CLARIFY
|
|
540
|
+
|
|
541
|
+
\`\`\`
|
|
542
|
+
1. Follow the instructions in response.prompt EXACTLY
|
|
543
|
+
2. The prompt tells you:
|
|
544
|
+
- What context to analyze
|
|
545
|
+
- How to generate questions
|
|
546
|
+
- The exact JSON output format required
|
|
547
|
+
3. Run: npx spets orchestrate clarify-done {taskId} '{questionsJson}'
|
|
548
|
+
- Format: [{"id":"q1","question":"..."},...]
|
|
549
|
+
- Empty if no questions: []
|
|
550
|
+
4. Parse response \u2192 next action
|
|
551
|
+
\`\`\`
|
|
552
|
+
|
|
553
|
+
### ACTION_EXECUTE
|
|
554
|
+
|
|
555
|
+
\`\`\`
|
|
556
|
+
1. Follow the instructions in response.prompt EXACTLY
|
|
557
|
+
2. The prompt tells you:
|
|
558
|
+
- The instruction and template to follow
|
|
559
|
+
- The explore output and answers to use
|
|
560
|
+
- Where to save the document/code (context.output)
|
|
561
|
+
- Any revision/verify feedback to address
|
|
562
|
+
3. Write the document/code to context.output
|
|
563
|
+
4. Run: npx spets orchestrate execute-done {taskId}
|
|
564
|
+
5. Parse response \u2192 next action
|
|
565
|
+
\`\`\`
|
|
566
|
+
|
|
567
|
+
### ACTION_VERIFY
|
|
568
|
+
|
|
569
|
+
\`\`\`
|
|
570
|
+
1. Follow the instructions in response.prompt EXACTLY
|
|
571
|
+
2. The prompt tells you:
|
|
572
|
+
- The document to verify (already included in prompt)
|
|
573
|
+
- The requirements and template to check against
|
|
574
|
+
- The scoring criteria and pass conditions
|
|
575
|
+
- The exact JSON output format required
|
|
576
|
+
3. Run: npx spets orchestrate verify-done {taskId} '{verifyJson}'
|
|
577
|
+
4. Parse response \u2192 next action
|
|
578
|
+
- If passed: goes to human review
|
|
579
|
+
- If failed (attempts < 3): goes back to draft phase (auto-fix)
|
|
580
|
+
- If failed (attempts >= 3): goes to human review with warning
|
|
581
|
+
\`\`\`
|
|
582
|
+
|
|
583
|
+
### ACTION_ASK_QUESTIONS
|
|
584
|
+
|
|
585
|
+
\`\`\`
|
|
586
|
+
1. Ask user each question from response.questions
|
|
587
|
+
2. Collect answers into: [{"questionId":"q1","answer":"..."},...]
|
|
588
|
+
3. Run: npx spets orchestrate clarified {taskId} '{answersJson}'
|
|
589
|
+
4. Parse response \u2192 next action
|
|
590
|
+
\`\`\`
|
|
591
|
+
|
|
592
|
+
### ACTION_ASK_APPROVAL
|
|
593
|
+
|
|
594
|
+
\`\`\`
|
|
595
|
+
1. Read the document at context.output
|
|
596
|
+
2. Summarize document (2-3 sentences)
|
|
597
|
+
3. Ask user: "Review {step} document. Approve / Revise / Reject / Stop?"
|
|
598
|
+
4. Based on answer:
|
|
599
|
+
- Approve: npx spets orchestrate approve {taskId}
|
|
600
|
+
- Revise: Ask for feedback, then: npx spets orchestrate revise {taskId} "{feedback}"
|
|
601
|
+
- Reject: npx spets orchestrate reject {taskId}
|
|
602
|
+
- Stop: npx spets orchestrate stop {taskId}
|
|
603
|
+
5. Parse response \u2192 next action
|
|
604
|
+
\`\`\`
|
|
605
|
+
|
|
606
|
+
---
|
|
607
|
+
|
|
608
|
+
$ARGUMENTS
|
|
609
|
+
description: Task description for the workflow
|
|
610
|
+
`;
|
|
495
611
|
}
|
|
496
|
-
function
|
|
497
|
-
return
|
|
612
|
+
function getOpenCodeSkillContent() {
|
|
613
|
+
return getCodexSkillContent().replace(
|
|
614
|
+
"runs spec-driven development workflows within Codex CLI",
|
|
615
|
+
"runs spec-driven development workflows within OpenCode"
|
|
616
|
+
);
|
|
498
617
|
}
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
618
|
+
|
|
619
|
+
// src/commands/init.ts
|
|
620
|
+
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
621
|
+
function getGitHubInfoFromRemote() {
|
|
622
|
+
try {
|
|
623
|
+
const remoteUrl = execSync("git remote get-url origin", { encoding: "utf-8" }).trim();
|
|
624
|
+
const sshMatch = remoteUrl.match(/git@github\.com:([^/]+)\/(.+?)(?:\.git)?$/);
|
|
625
|
+
if (sshMatch) {
|
|
626
|
+
return { owner: sshMatch[1], repo: sshMatch[2] };
|
|
627
|
+
}
|
|
628
|
+
const httpsMatch = remoteUrl.match(/https:\/\/github\.com\/([^/]+)\/(.+?)(?:\.git)?$/);
|
|
629
|
+
if (httpsMatch) {
|
|
630
|
+
return { owner: httpsMatch[1], repo: httpsMatch[2] };
|
|
631
|
+
}
|
|
632
|
+
return null;
|
|
633
|
+
} catch {
|
|
502
634
|
return null;
|
|
503
635
|
}
|
|
504
|
-
const raw = readFileSync3(outputPath, "utf-8");
|
|
505
|
-
const { content, data } = matter(raw);
|
|
506
|
-
return {
|
|
507
|
-
content,
|
|
508
|
-
frontmatter: data
|
|
509
|
-
};
|
|
510
636
|
}
|
|
511
|
-
function
|
|
512
|
-
const
|
|
513
|
-
|
|
514
|
-
|
|
637
|
+
async function initCommand(options) {
|
|
638
|
+
const cwd = process.cwd();
|
|
639
|
+
const spetsDir = getSpetsDir(cwd);
|
|
640
|
+
if (spetsExists(cwd) && !options.force) {
|
|
641
|
+
console.error("Spets already initialized. Use --force to overwrite.");
|
|
642
|
+
process.exit(1);
|
|
515
643
|
}
|
|
516
|
-
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
644
|
+
mkdirSync2(spetsDir, { recursive: true });
|
|
645
|
+
mkdirSync2(join3(spetsDir, "steps"), { recursive: true });
|
|
646
|
+
mkdirSync2(join3(spetsDir, "outputs"), { recursive: true });
|
|
647
|
+
mkdirSync2(join3(spetsDir, "hooks"), { recursive: true });
|
|
648
|
+
const templatesDir = join3(__dirname, "..", "templates");
|
|
649
|
+
const hookTemplate = join3(__dirname, "..", "templates", "hooks", "cleanup-branch.sh");
|
|
650
|
+
if (existsSync3(hookTemplate)) {
|
|
651
|
+
const hookDest = join3(spetsDir, "hooks", "cleanup-branch.sh");
|
|
652
|
+
cpSync(hookTemplate, hookDest);
|
|
653
|
+
try {
|
|
654
|
+
execSync(`chmod +x "${hookDest}"`);
|
|
655
|
+
} catch {
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
const githubInfo = getGitHubInfoFromRemote();
|
|
659
|
+
writeFileSync2(join3(spetsDir, "config.yml"), getDefaultConfig(githubInfo));
|
|
660
|
+
createDefaultSteps(spetsDir);
|
|
661
|
+
createClaudeCommand(cwd);
|
|
662
|
+
console.log("Initialized spets in .spets/");
|
|
663
|
+
console.log("");
|
|
664
|
+
console.log("Created:");
|
|
665
|
+
console.log(" .spets/config.yml - Workflow configuration");
|
|
666
|
+
console.log(" .spets/steps/01-plan/template.md - Planning step template");
|
|
667
|
+
console.log(" .spets/steps/02-implement/template.md - Implementation step template");
|
|
668
|
+
console.log(" .spets/hooks/cleanup-branch.sh - Example branch cleanup hook");
|
|
669
|
+
console.log(" .claude/commands/spets.md - Claude Code command");
|
|
670
|
+
if (options.github) {
|
|
671
|
+
createGitHubWorkflow(cwd);
|
|
672
|
+
console.log(" .github/workflows/spets.yml - GitHub Actions workflow");
|
|
673
|
+
console.log(" .github/ISSUE_TEMPLATE/spets-task.yml - Issue template");
|
|
674
|
+
}
|
|
675
|
+
console.log("");
|
|
676
|
+
console.log("Next steps:");
|
|
677
|
+
console.log(" 1. Edit .spets/config.yml to customize your workflow");
|
|
678
|
+
console.log(" 2. Customize step templates in .spets/steps/");
|
|
679
|
+
if (options.github) {
|
|
680
|
+
console.log(" 3. Add CLAUDE_CODE_OAUTH_TOKEN to your repo secrets");
|
|
681
|
+
console.log(' 4. Create a new Issue using the "Spets Task" template');
|
|
682
|
+
} else {
|
|
683
|
+
console.log(' 3. Run: spets start "your task description"');
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
function getDefaultConfig(githubInfo) {
|
|
687
|
+
const githubSection = githubInfo ? `
|
|
688
|
+
# GitHub integration (auto-detected from git remote)
|
|
689
|
+
github:
|
|
690
|
+
owner: ${githubInfo.owner}
|
|
691
|
+
repo: ${githubInfo.repo}
|
|
692
|
+
` : `
|
|
693
|
+
# GitHub integration (uncomment and fill in to enable)
|
|
694
|
+
# github:
|
|
695
|
+
# owner: your-org
|
|
696
|
+
# repo: your-repo
|
|
697
|
+
`;
|
|
698
|
+
return `# Spets Configuration
|
|
699
|
+
# Define your workflow steps here
|
|
700
|
+
|
|
701
|
+
steps:
|
|
702
|
+
- 01-plan
|
|
703
|
+
- 02-implement
|
|
704
|
+
|
|
705
|
+
# Output configuration
|
|
706
|
+
output:
|
|
707
|
+
path: .spets/outputs
|
|
708
|
+
${githubSection}
|
|
709
|
+
# Optional hooks (shell scripts)
|
|
710
|
+
# hooks:
|
|
711
|
+
# preStep: "./hooks/pre-step.sh"
|
|
712
|
+
# postStep: "./hooks/post-step.sh"
|
|
713
|
+
# onApprove: "./hooks/on-approve.sh"
|
|
714
|
+
# onReject: "./hooks/cleanup-branch.sh"
|
|
715
|
+
# onComplete: "./hooks/cleanup-branch.sh"
|
|
716
|
+
`;
|
|
717
|
+
}
|
|
718
|
+
function createDefaultSteps(spetsDir) {
|
|
719
|
+
const planDir = join3(spetsDir, "steps", "01-plan");
|
|
720
|
+
mkdirSync2(planDir, { recursive: true });
|
|
721
|
+
writeFileSync2(join3(planDir, "template.md"), getPlanTemplate());
|
|
722
|
+
const implementDir = join3(spetsDir, "steps", "02-implement");
|
|
723
|
+
mkdirSync2(implementDir, { recursive: true });
|
|
724
|
+
writeFileSync2(join3(implementDir, "template.md"), getImplementTemplate());
|
|
725
|
+
}
|
|
726
|
+
function getPlanTemplate() {
|
|
727
|
+
const fullTemplate = readFileSync2(join3(__dirname, "..", "templates", "steps", "01-plan", "template.md"), "utf-8");
|
|
728
|
+
return fullTemplate;
|
|
729
|
+
}
|
|
730
|
+
function getImplementTemplate() {
|
|
731
|
+
const fullTemplate = readFileSync2(join3(__dirname, "..", "templates", "steps", "02-implement", "template.md"), "utf-8");
|
|
732
|
+
return fullTemplate;
|
|
733
|
+
}
|
|
734
|
+
function createClaudeCommand(cwd) {
|
|
735
|
+
const commandDir = join3(cwd, ".claude", "commands");
|
|
736
|
+
mkdirSync2(commandDir, { recursive: true });
|
|
737
|
+
writeFileSync2(join3(commandDir, "spets.md"), getClaudeSkillContent());
|
|
738
|
+
}
|
|
739
|
+
function createGitHubWorkflow(cwd) {
|
|
740
|
+
const workflowDir = join3(cwd, ".github", "workflows");
|
|
741
|
+
const templateDir = join3(cwd, ".github", "ISSUE_TEMPLATE");
|
|
742
|
+
mkdirSync2(workflowDir, { recursive: true });
|
|
743
|
+
mkdirSync2(templateDir, { recursive: true });
|
|
744
|
+
const workflowTemplate = readFileSync2(join3(__dirname, "..", "assets", "github", "workflows", "spets.yml"), "utf-8");
|
|
745
|
+
const issueTemplate = readFileSync2(join3(__dirname, "..", "assets", "github", "ISSUE_TEMPLATE", "spets-task.yml"), "utf-8");
|
|
746
|
+
writeFileSync2(join3(workflowDir, "spets.yml"), workflowTemplate);
|
|
747
|
+
writeFileSync2(join3(templateDir, "spets-task.yml"), issueTemplate);
|
|
748
|
+
createGitHubLabel();
|
|
749
|
+
}
|
|
750
|
+
function createGitHubLabel() {
|
|
751
|
+
try {
|
|
752
|
+
execSync("gh --version", { stdio: "ignore" });
|
|
753
|
+
execSync('gh label create spets --description "Spets workflow task" --color 0E8A16 --force', {
|
|
754
|
+
stdio: "ignore"
|
|
755
|
+
});
|
|
756
|
+
console.log(' GitHub label "spets" created');
|
|
757
|
+
} catch {
|
|
758
|
+
console.log("");
|
|
759
|
+
console.log(" Note: Could not create GitHub label automatically.");
|
|
760
|
+
console.log(' Please create the "spets" label manually or run:');
|
|
761
|
+
console.log(' gh label create spets --description "Spets workflow task" --color 0E8A16');
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// src/core/state.ts
|
|
766
|
+
import { readFileSync as readFileSync3, existsSync as existsSync4, readdirSync as readdirSync2, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
|
|
767
|
+
import { join as join4 } from "path";
|
|
768
|
+
import matter from "gray-matter";
|
|
769
|
+
|
|
770
|
+
// src/core/slug.ts
|
|
771
|
+
function generateRandomSuffix() {
|
|
772
|
+
return Math.random().toString(36).substring(2, 6);
|
|
773
|
+
}
|
|
774
|
+
function generateDatePrefix() {
|
|
775
|
+
const now = /* @__PURE__ */ new Date();
|
|
776
|
+
const year = now.getFullYear();
|
|
777
|
+
const month = String(now.getMonth() + 1).padStart(2, "0");
|
|
778
|
+
const day = String(now.getDate()).padStart(2, "0");
|
|
779
|
+
return `${year}-${month}-${day}`;
|
|
780
|
+
}
|
|
781
|
+
function sanitizeDescription(description) {
|
|
782
|
+
let sanitized = description.toLowerCase();
|
|
783
|
+
sanitized = sanitized.replace(/[^\x00-\x7F]/g, "");
|
|
784
|
+
sanitized = sanitized.replace(/[^a-z0-9\s]/g, "");
|
|
785
|
+
sanitized = sanitized.trim();
|
|
786
|
+
sanitized = sanitized.replace(/\s+/g, " ");
|
|
787
|
+
sanitized = sanitized.replace(/\s+/g, "-");
|
|
788
|
+
if (sanitized.length > 10) {
|
|
789
|
+
sanitized = sanitized.substring(0, 10);
|
|
790
|
+
}
|
|
791
|
+
sanitized = sanitized.replace(/-+$/, "");
|
|
792
|
+
if (sanitized === "") {
|
|
793
|
+
sanitized = "task";
|
|
794
|
+
}
|
|
795
|
+
return sanitized;
|
|
796
|
+
}
|
|
797
|
+
function generateSlug(description) {
|
|
798
|
+
const datePrefix = generateDatePrefix();
|
|
799
|
+
const meaningfulName = sanitizeDescription(description);
|
|
800
|
+
const randomSuffix = generateRandomSuffix();
|
|
801
|
+
return `${datePrefix}-${meaningfulName}-${randomSuffix}`;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
// src/core/state.ts
|
|
805
|
+
function generateTaskId(description) {
|
|
806
|
+
if (description) {
|
|
807
|
+
return generateSlug(description);
|
|
808
|
+
}
|
|
809
|
+
const timestamp = Date.now().toString(36);
|
|
810
|
+
const random = Math.random().toString(36).substring(2, 6);
|
|
811
|
+
return `${timestamp}-${random}`;
|
|
812
|
+
}
|
|
813
|
+
function getTaskDir(taskId, cwd = process.cwd()) {
|
|
814
|
+
return join4(getOutputsDir(cwd), taskId);
|
|
815
|
+
}
|
|
816
|
+
function getOutputPath(taskId, stepName, cwd = process.cwd()) {
|
|
817
|
+
return join4(getTaskDir(taskId, cwd), `${stepName}.md`);
|
|
818
|
+
}
|
|
819
|
+
function loadDocument(taskId, stepName, cwd = process.cwd()) {
|
|
820
|
+
const outputPath = getOutputPath(taskId, stepName, cwd);
|
|
821
|
+
if (!existsSync4(outputPath)) {
|
|
822
|
+
return null;
|
|
823
|
+
}
|
|
824
|
+
const raw = readFileSync3(outputPath, "utf-8");
|
|
825
|
+
const { content, data } = matter(raw);
|
|
826
|
+
return {
|
|
827
|
+
content,
|
|
828
|
+
frontmatter: data
|
|
829
|
+
};
|
|
830
|
+
}
|
|
831
|
+
function listTasks(cwd = process.cwd()) {
|
|
832
|
+
const outputsDir = getOutputsDir(cwd);
|
|
833
|
+
if (!existsSync4(outputsDir)) {
|
|
834
|
+
return [];
|
|
835
|
+
}
|
|
836
|
+
return readdirSync2(outputsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort().reverse();
|
|
837
|
+
}
|
|
838
|
+
function getStateCachePath(taskId, cwd = process.cwd()) {
|
|
839
|
+
return join4(getTaskDir(taskId, cwd), ".state-cache.json");
|
|
840
|
+
}
|
|
521
841
|
function loadStateCache(taskId, cwd = process.cwd()) {
|
|
522
842
|
const cachePath = getStateCachePath(taskId, cwd);
|
|
523
|
-
if (!
|
|
843
|
+
if (!existsSync4(cachePath)) {
|
|
524
844
|
return null;
|
|
525
845
|
}
|
|
526
846
|
try {
|
|
@@ -546,7 +866,7 @@ function saveStateCache(taskId, state, cwd = process.cwd()) {
|
|
|
546
866
|
stepStatuses
|
|
547
867
|
};
|
|
548
868
|
try {
|
|
549
|
-
|
|
869
|
+
writeFileSync3(cachePath, JSON.stringify(cached, null, 2));
|
|
550
870
|
} catch {
|
|
551
871
|
}
|
|
552
872
|
}
|
|
@@ -556,7 +876,7 @@ function isStateCacheValid(cached, maxAgeMs = 5e3) {
|
|
|
556
876
|
}
|
|
557
877
|
function getWorkflowState(taskId, config, cwd = process.cwd()) {
|
|
558
878
|
const taskDir = getTaskDir(taskId, cwd);
|
|
559
|
-
if (!
|
|
879
|
+
if (!existsSync4(taskDir)) {
|
|
560
880
|
return null;
|
|
561
881
|
}
|
|
562
882
|
const cached = loadStateCache(taskId, cwd);
|
|
@@ -623,8 +943,8 @@ function getWorkflowState(taskId, config, cwd = process.cwd()) {
|
|
|
623
943
|
return state;
|
|
624
944
|
}
|
|
625
945
|
function loadTaskMetadata(taskId, cwd = process.cwd()) {
|
|
626
|
-
const metaPath =
|
|
627
|
-
if (!
|
|
946
|
+
const metaPath = join4(getTaskDir(taskId, cwd), ".meta.json");
|
|
947
|
+
if (!existsSync4(metaPath)) {
|
|
628
948
|
return null;
|
|
629
949
|
}
|
|
630
950
|
return JSON.parse(readFileSync3(metaPath, "utf-8"));
|
|
@@ -715,13 +1035,13 @@ function formatDocStatus(status) {
|
|
|
715
1035
|
import { execSync as execSync2 } from "child_process";
|
|
716
1036
|
|
|
717
1037
|
// src/orchestrator/index.ts
|
|
718
|
-
import { readFileSync as readFileSync5, writeFileSync as
|
|
719
|
-
import { join as
|
|
1038
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
|
|
1039
|
+
import { join as join6, dirname as dirname2 } from "path";
|
|
720
1040
|
import matter2 from "gray-matter";
|
|
721
1041
|
|
|
722
1042
|
// src/core/prompt-builder.ts
|
|
723
|
-
import { readFileSync as readFileSync4, existsSync as
|
|
724
|
-
import { join as
|
|
1043
|
+
import { readFileSync as readFileSync4, existsSync as existsSync5 } from "fs";
|
|
1044
|
+
import { join as join5 } from "path";
|
|
725
1045
|
function buildContextPrompt(params) {
|
|
726
1046
|
const cwd = params.cwd || process.cwd();
|
|
727
1047
|
const isFirstStep = params.stepIndex === 1;
|
|
@@ -744,7 +1064,7 @@ function buildContextPrompt(params) {
|
|
|
744
1064
|
} else if (params.previousOutput) {
|
|
745
1065
|
parts.push("## Previous Step Output");
|
|
746
1066
|
parts.push("");
|
|
747
|
-
if (
|
|
1067
|
+
if (existsSync5(params.previousOutput)) {
|
|
748
1068
|
parts.push(readFileSync4(params.previousOutput, "utf-8"));
|
|
749
1069
|
}
|
|
750
1070
|
parts.push("");
|
|
@@ -796,7 +1116,7 @@ function buildExplorePrompt(params) {
|
|
|
796
1116
|
} else if (params.previousOutput) {
|
|
797
1117
|
parts.push("## Previous Step Output");
|
|
798
1118
|
parts.push("");
|
|
799
|
-
if (
|
|
1119
|
+
if (existsSync5(params.previousOutput)) {
|
|
800
1120
|
parts.push(readFileSync4(params.previousOutput, "utf-8"));
|
|
801
1121
|
}
|
|
802
1122
|
parts.push("");
|
|
@@ -935,13 +1255,13 @@ function buildGeneratePrompt(params) {
|
|
|
935
1255
|
const outputsDir = getOutputsDir(cwd);
|
|
936
1256
|
const isFirstStep = params.stepIndex === 1;
|
|
937
1257
|
const prevStep = params.stepIndex > 1 ? config.steps[params.stepIndex - 2] : null;
|
|
938
|
-
const templatePath =
|
|
939
|
-
const outputPath =
|
|
940
|
-
const template =
|
|
1258
|
+
const templatePath = join5(stepsDir, params.step, "template.md");
|
|
1259
|
+
const outputPath = join5(outputsDir, params.taskId, `${params.step}.md`);
|
|
1260
|
+
const template = existsSync5(templatePath) ? readFileSync4(templatePath, "utf-8") : null;
|
|
941
1261
|
let previousSpec = null;
|
|
942
1262
|
if (prevStep) {
|
|
943
|
-
const prevPath =
|
|
944
|
-
if (
|
|
1263
|
+
const prevPath = join5(outputsDir, params.taskId, `${prevStep}.md`);
|
|
1264
|
+
if (existsSync5(prevPath)) {
|
|
945
1265
|
previousSpec = {
|
|
946
1266
|
step: prevStep,
|
|
947
1267
|
content: readFileSync4(prevPath, "utf-8")
|
|
@@ -1022,13 +1342,13 @@ function buildExecutePrompt(params) {
|
|
|
1022
1342
|
const outputsDir = getOutputsDir(cwd);
|
|
1023
1343
|
const isFirstStep = params.stepIndex === 1;
|
|
1024
1344
|
const prevStep = params.stepIndex > 1 ? config.steps[params.stepIndex - 2] : null;
|
|
1025
|
-
const templatePath =
|
|
1026
|
-
const outputPath =
|
|
1027
|
-
const template =
|
|
1345
|
+
const templatePath = join5(stepsDir, params.step, "template.md");
|
|
1346
|
+
const outputPath = join5(outputsDir, params.taskId, `${params.step}.md`);
|
|
1347
|
+
const template = existsSync5(templatePath) ? readFileSync4(templatePath, "utf-8") : null;
|
|
1028
1348
|
let previousSpec = null;
|
|
1029
1349
|
if (prevStep) {
|
|
1030
|
-
const prevPath =
|
|
1031
|
-
if (
|
|
1350
|
+
const prevPath = join5(outputsDir, params.taskId, `${prevStep}.md`);
|
|
1351
|
+
if (existsSync5(prevPath)) {
|
|
1032
1352
|
previousSpec = {
|
|
1033
1353
|
step: prevStep,
|
|
1034
1354
|
content: readFileSync4(prevPath, "utf-8")
|
|
@@ -1147,9 +1467,9 @@ function buildExecutePrompt(params) {
|
|
|
1147
1467
|
function buildVerifyPrompt(params) {
|
|
1148
1468
|
const cwd = params.cwd || process.cwd();
|
|
1149
1469
|
const stepsDir = getStepsDir(cwd);
|
|
1150
|
-
const templatePath =
|
|
1151
|
-
const template =
|
|
1152
|
-
const document =
|
|
1470
|
+
const templatePath = join5(stepsDir, params.step, "template.md");
|
|
1471
|
+
const template = existsSync5(templatePath) ? readFileSync4(templatePath, "utf-8") : "";
|
|
1472
|
+
const document = existsSync5(params.documentPath) ? readFileSync4(params.documentPath, "utf-8") : "";
|
|
1153
1473
|
const parts = [];
|
|
1154
1474
|
parts.push("# Verify Phase - Self-Validation");
|
|
1155
1475
|
parts.push("");
|
|
@@ -1253,20 +1573,20 @@ var Orchestrator = class {
|
|
|
1253
1573
|
return getOutputsDir(this.cwd);
|
|
1254
1574
|
}
|
|
1255
1575
|
getStatePath(taskId) {
|
|
1256
|
-
return
|
|
1576
|
+
return join6(this.getOutputPath(), taskId, ".state.json");
|
|
1257
1577
|
}
|
|
1258
1578
|
getSpecPath(taskId, step) {
|
|
1259
|
-
return
|
|
1579
|
+
return join6(this.getOutputPath(), taskId, `${step}.md`);
|
|
1260
1580
|
}
|
|
1261
1581
|
getStepTemplatePath(step) {
|
|
1262
|
-
return
|
|
1582
|
+
return join6(getStepsDir(this.cwd), step, "template.md");
|
|
1263
1583
|
}
|
|
1264
1584
|
// ===========================================================================
|
|
1265
1585
|
// State Management
|
|
1266
1586
|
// ===========================================================================
|
|
1267
1587
|
loadState(taskId) {
|
|
1268
1588
|
const statePath = this.getStatePath(taskId);
|
|
1269
|
-
if (!
|
|
1589
|
+
if (!existsSync6(statePath)) {
|
|
1270
1590
|
return null;
|
|
1271
1591
|
}
|
|
1272
1592
|
const data = JSON.parse(readFileSync5(statePath, "utf-8"));
|
|
@@ -1276,16 +1596,16 @@ var Orchestrator = class {
|
|
|
1276
1596
|
state.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1277
1597
|
const statePath = this.getStatePath(state.taskId);
|
|
1278
1598
|
const dir = dirname2(statePath);
|
|
1279
|
-
if (!
|
|
1280
|
-
|
|
1599
|
+
if (!existsSync6(dir)) {
|
|
1600
|
+
mkdirSync4(dir, { recursive: true });
|
|
1281
1601
|
}
|
|
1282
|
-
|
|
1602
|
+
writeFileSync4(statePath, JSON.stringify(state, null, 2));
|
|
1283
1603
|
}
|
|
1284
1604
|
// ===========================================================================
|
|
1285
1605
|
// Spec Helpers
|
|
1286
1606
|
// ===========================================================================
|
|
1287
1607
|
checkUnresolvedQuestions(specPath) {
|
|
1288
|
-
if (!
|
|
1608
|
+
if (!existsSync6(specPath)) {
|
|
1289
1609
|
return [];
|
|
1290
1610
|
}
|
|
1291
1611
|
const content = readFileSync5(specPath, "utf-8");
|
|
@@ -1320,8 +1640,8 @@ var Orchestrator = class {
|
|
|
1320
1640
|
let previousOutput;
|
|
1321
1641
|
if (state.stepIndex > 1) {
|
|
1322
1642
|
const prevStep = steps[state.stepIndex - 2];
|
|
1323
|
-
const prevPath =
|
|
1324
|
-
if (
|
|
1643
|
+
const prevPath = join6(outputPath, state.taskId, `${prevStep}.md`);
|
|
1644
|
+
if (existsSync6(prevPath)) {
|
|
1325
1645
|
previousOutput = prevPath;
|
|
1326
1646
|
}
|
|
1327
1647
|
}
|
|
@@ -1348,8 +1668,8 @@ var Orchestrator = class {
|
|
|
1348
1668
|
let previousOutput;
|
|
1349
1669
|
if (state.stepIndex > 1) {
|
|
1350
1670
|
const prevStep = steps[state.stepIndex - 2];
|
|
1351
|
-
const prevPath =
|
|
1352
|
-
if (
|
|
1671
|
+
const prevPath = join6(outputPath, state.taskId, `${prevStep}.md`);
|
|
1672
|
+
if (existsSync6(prevPath)) {
|
|
1353
1673
|
previousOutput = prevPath;
|
|
1354
1674
|
}
|
|
1355
1675
|
}
|
|
@@ -1411,13 +1731,13 @@ var Orchestrator = class {
|
|
|
1411
1731
|
let previousOutput;
|
|
1412
1732
|
if (state.stepIndex > 1) {
|
|
1413
1733
|
const prevStep = steps[state.stepIndex - 2];
|
|
1414
|
-
const prevPath =
|
|
1415
|
-
if (
|
|
1734
|
+
const prevPath = join6(outputPath, state.taskId, `${prevStep}.md`);
|
|
1735
|
+
if (existsSync6(prevPath)) {
|
|
1416
1736
|
previousOutput = prevPath;
|
|
1417
1737
|
}
|
|
1418
1738
|
}
|
|
1419
1739
|
const templatePath = this.getStepTemplatePath(state.currentStep);
|
|
1420
|
-
const hasTemplate =
|
|
1740
|
+
const hasTemplate = existsSync6(templatePath);
|
|
1421
1741
|
return {
|
|
1422
1742
|
type: "phase",
|
|
1423
1743
|
phase: "generate",
|
|
@@ -1431,7 +1751,7 @@ var Orchestrator = class {
|
|
|
1431
1751
|
context: {
|
|
1432
1752
|
template: hasTemplate ? templatePath : void 0,
|
|
1433
1753
|
previousOutput,
|
|
1434
|
-
output:
|
|
1754
|
+
output: join6(outputPath, state.taskId, `${state.currentStep}.md`),
|
|
1435
1755
|
revisionFeedback: state.revisionFeedback
|
|
1436
1756
|
},
|
|
1437
1757
|
onComplete: `generate-done ${state.taskId}`
|
|
@@ -1446,13 +1766,13 @@ var Orchestrator = class {
|
|
|
1446
1766
|
let previousOutput;
|
|
1447
1767
|
if (state.stepIndex > 1) {
|
|
1448
1768
|
const prevStep = steps[state.stepIndex - 2];
|
|
1449
|
-
const prevPath =
|
|
1450
|
-
if (
|
|
1769
|
+
const prevPath = join6(outputPath, state.taskId, `${prevStep}.md`);
|
|
1770
|
+
if (existsSync6(prevPath)) {
|
|
1451
1771
|
previousOutput = prevPath;
|
|
1452
1772
|
}
|
|
1453
1773
|
}
|
|
1454
1774
|
const templatePath = this.getStepTemplatePath(state.currentStep);
|
|
1455
|
-
const hasTemplate =
|
|
1775
|
+
const hasTemplate = existsSync6(templatePath);
|
|
1456
1776
|
let verifyFeedback;
|
|
1457
1777
|
if (state.verifyOutput && !state.verifyOutput.passed && state.verifyAttempts && state.verifyAttempts > 1) {
|
|
1458
1778
|
const issues = state.verifyOutput.issues.filter((i) => i.severity === "error").map((i) => `- [${i.category}] ${i.description}${i.suggestion ? ` \u2192 ${i.suggestion}` : ""}`).join("\n");
|
|
@@ -1492,7 +1812,7 @@ ${issues}`;
|
|
|
1492
1812
|
context: {
|
|
1493
1813
|
template: hasTemplate ? templatePath : void 0,
|
|
1494
1814
|
previousOutput,
|
|
1495
|
-
output:
|
|
1815
|
+
output: join6(outputPath, state.taskId, `${state.currentStep}.md`),
|
|
1496
1816
|
revisionFeedback: state.revisionFeedback,
|
|
1497
1817
|
verifyFeedback
|
|
1498
1818
|
},
|
|
@@ -1505,8 +1825,8 @@ ${issues}`;
|
|
|
1505
1825
|
responsePhaseVerify(state) {
|
|
1506
1826
|
const outputPath = this.getOutputPath();
|
|
1507
1827
|
const templatePath = this.getStepTemplatePath(state.currentStep);
|
|
1508
|
-
const hasTemplate =
|
|
1509
|
-
const documentPath =
|
|
1828
|
+
const hasTemplate = existsSync6(templatePath);
|
|
1829
|
+
const documentPath = join6(outputPath, state.taskId, `${state.currentStep}.md`);
|
|
1510
1830
|
const prompt = buildVerifyPrompt({
|
|
1511
1831
|
taskId: state.taskId,
|
|
1512
1832
|
step: state.currentStep,
|
|
@@ -1560,7 +1880,7 @@ ${issues}`;
|
|
|
1560
1880
|
step: state.currentStep,
|
|
1561
1881
|
stepIndex: state.stepIndex,
|
|
1562
1882
|
totalSteps: state.totalSteps,
|
|
1563
|
-
specPath:
|
|
1883
|
+
specPath: join6(outputPath, state.taskId, `${state.currentStep}.md`),
|
|
1564
1884
|
options: ["approve", "revise", "reject", "stop"],
|
|
1565
1885
|
onComplete: {
|
|
1566
1886
|
approve: `approve ${state.taskId}`,
|
|
@@ -1575,8 +1895,8 @@ ${issues}`;
|
|
|
1575
1895
|
const outputPath = this.getOutputPath();
|
|
1576
1896
|
const outputs = [];
|
|
1577
1897
|
for (let i = 0; i < state.stepIndex; i++) {
|
|
1578
|
-
const specPath =
|
|
1579
|
-
if (
|
|
1898
|
+
const specPath = join6(outputPath, state.taskId, `${steps[i]}.md`);
|
|
1899
|
+
if (existsSync6(specPath)) {
|
|
1580
1900
|
outputs.push(specPath);
|
|
1581
1901
|
}
|
|
1582
1902
|
}
|
|
@@ -1744,7 +2064,7 @@ ${issues}`;
|
|
|
1744
2064
|
return this.responseError(`No workflow found: ${taskId}`, taskId);
|
|
1745
2065
|
}
|
|
1746
2066
|
const specPath = this.getSpecPath(taskId, state.currentStep);
|
|
1747
|
-
if (!
|
|
2067
|
+
if (!existsSync6(specPath)) {
|
|
1748
2068
|
return this.responseError(`Document not found: ${specPath}`, taskId, state.currentStep);
|
|
1749
2069
|
}
|
|
1750
2070
|
state.status = "approve_pending";
|
|
@@ -1761,7 +2081,7 @@ ${issues}`;
|
|
|
1761
2081
|
return this.responseError(`No workflow found: ${taskId}`, taskId);
|
|
1762
2082
|
}
|
|
1763
2083
|
const specPath = this.getSpecPath(taskId, state.currentStep);
|
|
1764
|
-
if (!
|
|
2084
|
+
if (!existsSync6(specPath)) {
|
|
1765
2085
|
return this.responseError(`Document not found: ${specPath}`, taskId, state.currentStep);
|
|
1766
2086
|
}
|
|
1767
2087
|
state.status = "phase_verify";
|
|
@@ -1816,7 +2136,7 @@ ${issues}`;
|
|
|
1816
2136
|
return this.responseError(`No workflow found: ${taskId}`, taskId);
|
|
1817
2137
|
}
|
|
1818
2138
|
const specPath = this.getSpecPath(taskId, state.currentStep);
|
|
1819
|
-
if (!
|
|
2139
|
+
if (!existsSync6(specPath)) {
|
|
1820
2140
|
return this.responseError(`Spec not found: ${specPath}`, taskId, state.currentStep);
|
|
1821
2141
|
}
|
|
1822
2142
|
const questions = this.checkUnresolvedQuestions(specPath);
|
|
@@ -1842,12 +2162,12 @@ ${issues}`;
|
|
|
1842
2162
|
}
|
|
1843
2163
|
const steps = this.getSteps();
|
|
1844
2164
|
const specPath = this.getSpecPath(taskId, state.currentStep);
|
|
1845
|
-
if (
|
|
2165
|
+
if (existsSync6(specPath)) {
|
|
1846
2166
|
const content = readFileSync5(specPath, "utf-8");
|
|
1847
2167
|
const { content: body, data } = matter2(content);
|
|
1848
2168
|
data.status = "approved";
|
|
1849
2169
|
data.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
1850
|
-
|
|
2170
|
+
writeFileSync4(specPath, matter2.stringify(body, data));
|
|
1851
2171
|
}
|
|
1852
2172
|
if (state.stepIndex < state.totalSteps) {
|
|
1853
2173
|
state.currentStep = steps[state.stepIndex];
|
|
@@ -1895,12 +2215,12 @@ ${issues}`;
|
|
|
1895
2215
|
return this.responseError(`No workflow found: ${taskId}`, taskId);
|
|
1896
2216
|
}
|
|
1897
2217
|
const specPath = this.getSpecPath(taskId, state.currentStep);
|
|
1898
|
-
if (
|
|
2218
|
+
if (existsSync6(specPath)) {
|
|
1899
2219
|
const content = readFileSync5(specPath, "utf-8");
|
|
1900
2220
|
const { content: body, data } = matter2(content);
|
|
1901
2221
|
data.status = "rejected";
|
|
1902
2222
|
data.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
1903
|
-
|
|
2223
|
+
writeFileSync4(specPath, matter2.stringify(body, data));
|
|
1904
2224
|
}
|
|
1905
2225
|
state.status = "rejected";
|
|
1906
2226
|
this.saveState(state);
|
|
@@ -2000,8 +2320,8 @@ ${issues}`;
|
|
|
2000
2320
|
};
|
|
2001
2321
|
|
|
2002
2322
|
// src/core/step-executor.ts
|
|
2003
|
-
import { existsSync as
|
|
2004
|
-
import { join as
|
|
2323
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
2324
|
+
import { join as join7 } from "path";
|
|
2005
2325
|
import matter3 from "gray-matter";
|
|
2006
2326
|
var StepExecutor = class {
|
|
2007
2327
|
adapter;
|
|
@@ -2149,7 +2469,7 @@ var StepExecutor = class {
|
|
|
2149
2469
|
};
|
|
2150
2470
|
const { prompt, outputPath } = buildGeneratePrompt(params);
|
|
2151
2471
|
await this.adapter.ai.execute({ prompt, outputPath });
|
|
2152
|
-
if (!
|
|
2472
|
+
if (!existsSync7(outputPath)) {
|
|
2153
2473
|
throw new Error(`AI did not create document at ${outputPath}`);
|
|
2154
2474
|
}
|
|
2155
2475
|
this.adapter.io.notify(`Document created: ${outputPath}`, "success");
|
|
@@ -2188,7 +2508,7 @@ var StepExecutor = class {
|
|
|
2188
2508
|
};
|
|
2189
2509
|
const { prompt, outputPath } = buildExecutePrompt(params);
|
|
2190
2510
|
await this.adapter.ai.execute({ prompt, outputPath });
|
|
2191
|
-
if (!
|
|
2511
|
+
if (!existsSync7(outputPath)) {
|
|
2192
2512
|
throw new Error(`AI did not create document at ${outputPath}`);
|
|
2193
2513
|
}
|
|
2194
2514
|
this.adapter.io.notify(`Document created: ${outputPath}`, "success");
|
|
@@ -2210,8 +2530,8 @@ var StepExecutor = class {
|
|
|
2210
2530
|
const attempts = context.verifyAttempts || 1;
|
|
2211
2531
|
this.adapter.io.notify(`Phase 4/5: Verifying document for ${step} (attempt ${attempts}/3)`, "info");
|
|
2212
2532
|
const outputsDir = getOutputsDir(this.cwd);
|
|
2213
|
-
const documentPath =
|
|
2214
|
-
if (!
|
|
2533
|
+
const documentPath = join7(outputsDir, context.taskId, `${step}.md`);
|
|
2534
|
+
if (!existsSync7(documentPath)) {
|
|
2215
2535
|
throw new Error(`Document not found: ${documentPath}`);
|
|
2216
2536
|
}
|
|
2217
2537
|
const params = {
|
|
@@ -2252,8 +2572,8 @@ var StepExecutor = class {
|
|
|
2252
2572
|
async executeReviewPhase(step, context) {
|
|
2253
2573
|
this.adapter.io.notify(`Phase 4/4: Review for ${step}`, "info");
|
|
2254
2574
|
const outputsDir = getOutputsDir(this.cwd);
|
|
2255
|
-
const outputPath =
|
|
2256
|
-
if (!
|
|
2575
|
+
const outputPath = join7(outputsDir, context.taskId, `${step}.md`);
|
|
2576
|
+
if (!existsSync7(outputPath)) {
|
|
2257
2577
|
throw new Error(`Document not found: ${outputPath}`);
|
|
2258
2578
|
}
|
|
2259
2579
|
const approval = await this.adapter.io.approve(
|
|
@@ -2439,13 +2759,13 @@ var StepExecutor = class {
|
|
|
2439
2759
|
const { content: body, data } = matter3(content);
|
|
2440
2760
|
data.status = status;
|
|
2441
2761
|
data.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
2442
|
-
|
|
2762
|
+
writeFileSync5(docPath, matter3.stringify(body, data));
|
|
2443
2763
|
}
|
|
2444
2764
|
};
|
|
2445
2765
|
|
|
2446
2766
|
// src/adapters/cli.ts
|
|
2447
2767
|
import { spawn, spawnSync } from "child_process";
|
|
2448
|
-
import { readFileSync as readFileSync7, writeFileSync as
|
|
2768
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync6, existsSync as existsSync8, mkdirSync as mkdirSync5 } from "fs";
|
|
2449
2769
|
import { dirname as dirname3 } from "path";
|
|
2450
2770
|
import { input, select, confirm } from "@inquirer/prompts";
|
|
2451
2771
|
var CLIAIAdapter = class {
|
|
@@ -2458,12 +2778,12 @@ var CLIAIAdapter = class {
|
|
|
2458
2778
|
\u{1F4DD} Generating...`);
|
|
2459
2779
|
if (params.outputPath) {
|
|
2460
2780
|
const outputDir = dirname3(params.outputPath);
|
|
2461
|
-
if (!
|
|
2462
|
-
|
|
2781
|
+
if (!existsSync8(outputDir)) {
|
|
2782
|
+
mkdirSync5(outputDir, { recursive: true });
|
|
2463
2783
|
}
|
|
2464
2784
|
}
|
|
2465
2785
|
const stdout = await this.callClaude(params.prompt);
|
|
2466
|
-
if (params.outputPath &&
|
|
2786
|
+
if (params.outputPath && existsSync8(params.outputPath)) {
|
|
2467
2787
|
return readFileSync7(params.outputPath, "utf-8");
|
|
2468
2788
|
}
|
|
2469
2789
|
return stdout;
|
|
@@ -2541,13 +2861,13 @@ var CLIAIAdapter = class {
|
|
|
2541
2861
|
try {
|
|
2542
2862
|
if (p.outputPath) {
|
|
2543
2863
|
const outputDir = dirname3(p.outputPath);
|
|
2544
|
-
if (!
|
|
2545
|
-
|
|
2864
|
+
if (!existsSync8(outputDir)) {
|
|
2865
|
+
mkdirSync5(outputDir, { recursive: true });
|
|
2546
2866
|
}
|
|
2547
2867
|
}
|
|
2548
2868
|
await this.callClaude(p.prompt, false);
|
|
2549
2869
|
let result = "";
|
|
2550
|
-
if (p.outputPath &&
|
|
2870
|
+
if (p.outputPath && existsSync8(p.outputPath)) {
|
|
2551
2871
|
result = readFileSync7(p.outputPath, "utf-8");
|
|
2552
2872
|
}
|
|
2553
2873
|
completed++;
|
|
@@ -2598,7 +2918,7 @@ Context: ${question.context}`);
|
|
|
2598
2918
|
return answers;
|
|
2599
2919
|
}
|
|
2600
2920
|
async approve(specPath, stepName, stepIndex, totalSteps) {
|
|
2601
|
-
if (
|
|
2921
|
+
if (existsSync8(specPath)) {
|
|
2602
2922
|
const doc = readFileSync7(specPath, "utf-8");
|
|
2603
2923
|
console.log("\n" + "=".repeat(60));
|
|
2604
2924
|
console.log(`\u{1F4C4} ${stepName} Document (Step ${stepIndex}/${totalSteps})`);
|
|
@@ -2648,13 +2968,13 @@ var CLISystemAdapter = class {
|
|
|
2648
2968
|
}
|
|
2649
2969
|
writeFile(path, content) {
|
|
2650
2970
|
const dir = dirname3(path);
|
|
2651
|
-
if (!
|
|
2652
|
-
|
|
2971
|
+
if (!existsSync8(dir)) {
|
|
2972
|
+
mkdirSync5(dir, { recursive: true });
|
|
2653
2973
|
}
|
|
2654
|
-
|
|
2974
|
+
writeFileSync6(path, content);
|
|
2655
2975
|
}
|
|
2656
2976
|
fileExists(path) {
|
|
2657
|
-
return
|
|
2977
|
+
return existsSync8(path);
|
|
2658
2978
|
}
|
|
2659
2979
|
exec(command) {
|
|
2660
2980
|
try {
|
|
@@ -2685,7 +3005,7 @@ function createCLIAdapter(claudeCommand = "claude") {
|
|
|
2685
3005
|
|
|
2686
3006
|
// src/adapters/github.ts
|
|
2687
3007
|
import { spawn as spawn2, spawnSync as spawnSync2 } from "child_process";
|
|
2688
|
-
import { readFileSync as readFileSync8, writeFileSync as
|
|
3008
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync7, existsSync as existsSync9, mkdirSync as mkdirSync6 } from "fs";
|
|
2689
3009
|
import { dirname as dirname4 } from "path";
|
|
2690
3010
|
var GitHubAIAdapter = class {
|
|
2691
3011
|
claudeCommand;
|
|
@@ -2697,12 +3017,12 @@ var GitHubAIAdapter = class {
|
|
|
2697
3017
|
\u{1F4DD} Generating...`);
|
|
2698
3018
|
if (params.outputPath) {
|
|
2699
3019
|
const outputDir = dirname4(params.outputPath);
|
|
2700
|
-
if (!
|
|
2701
|
-
|
|
3020
|
+
if (!existsSync9(outputDir)) {
|
|
3021
|
+
mkdirSync6(outputDir, { recursive: true });
|
|
2702
3022
|
}
|
|
2703
3023
|
}
|
|
2704
3024
|
const stdout = await this.callClaude(params.prompt);
|
|
2705
|
-
if (params.outputPath &&
|
|
3025
|
+
if (params.outputPath && existsSync9(params.outputPath)) {
|
|
2706
3026
|
return readFileSync8(params.outputPath, "utf-8");
|
|
2707
3027
|
}
|
|
2708
3028
|
return stdout;
|
|
@@ -2755,13 +3075,13 @@ var GitHubAIAdapter = class {
|
|
|
2755
3075
|
try {
|
|
2756
3076
|
if (p.outputPath) {
|
|
2757
3077
|
const outputDir = dirname4(p.outputPath);
|
|
2758
|
-
if (!
|
|
2759
|
-
|
|
3078
|
+
if (!existsSync9(outputDir)) {
|
|
3079
|
+
mkdirSync6(outputDir, { recursive: true });
|
|
2760
3080
|
}
|
|
2761
3081
|
}
|
|
2762
3082
|
await this.callClaude(p.prompt);
|
|
2763
3083
|
let result = "";
|
|
2764
|
-
if (p.outputPath &&
|
|
3084
|
+
if (p.outputPath && existsSync9(p.outputPath)) {
|
|
2765
3085
|
result = readFileSync8(p.outputPath, "utf-8");
|
|
2766
3086
|
}
|
|
2767
3087
|
completed++;
|
|
@@ -2807,7 +3127,7 @@ var GitHubIOAdapter = class {
|
|
|
2807
3127
|
return [];
|
|
2808
3128
|
}
|
|
2809
3129
|
async approve(specPath, stepName, stepIndex, totalSteps) {
|
|
2810
|
-
const doc =
|
|
3130
|
+
const doc = existsSync9(specPath) ? readFileSync8(specPath, "utf-8") : "";
|
|
2811
3131
|
const comment = this.formatApprovalComment(doc, stepName, stepIndex, totalSteps, specPath);
|
|
2812
3132
|
await this.postComment(comment);
|
|
2813
3133
|
console.log("\n\u23F8\uFE0F Approval request posted to GitHub.");
|
|
@@ -2930,13 +3250,13 @@ var GitHubSystemAdapter = class {
|
|
|
2930
3250
|
}
|
|
2931
3251
|
writeFile(path, content) {
|
|
2932
3252
|
const dir = dirname4(path);
|
|
2933
|
-
if (!
|
|
2934
|
-
|
|
3253
|
+
if (!existsSync9(dir)) {
|
|
3254
|
+
mkdirSync6(dir, { recursive: true });
|
|
2935
3255
|
}
|
|
2936
|
-
|
|
3256
|
+
writeFileSync7(path, content);
|
|
2937
3257
|
}
|
|
2938
3258
|
fileExists(path) {
|
|
2939
|
-
return
|
|
3259
|
+
return existsSync9(path);
|
|
2940
3260
|
}
|
|
2941
3261
|
exec(command) {
|
|
2942
3262
|
try {
|
|
@@ -2967,7 +3287,7 @@ function createGitHubAdapter(config, claudeCommand = "claude") {
|
|
|
2967
3287
|
|
|
2968
3288
|
// src/adapters/codex.ts
|
|
2969
3289
|
import { spawn as spawn3 } from "child_process";
|
|
2970
|
-
import { readFileSync as readFileSync9, existsSync as
|
|
3290
|
+
import { readFileSync as readFileSync9, existsSync as existsSync10, mkdirSync as mkdirSync7 } from "fs";
|
|
2971
3291
|
import { dirname as dirname5 } from "path";
|
|
2972
3292
|
var CodexAIAdapter = class {
|
|
2973
3293
|
codexCommand;
|
|
@@ -2979,12 +3299,12 @@ var CodexAIAdapter = class {
|
|
|
2979
3299
|
\u{1F4DD} Generating...`);
|
|
2980
3300
|
if (params.outputPath) {
|
|
2981
3301
|
const outputDir = dirname5(params.outputPath);
|
|
2982
|
-
if (!
|
|
2983
|
-
|
|
3302
|
+
if (!existsSync10(outputDir)) {
|
|
3303
|
+
mkdirSync7(outputDir, { recursive: true });
|
|
2984
3304
|
}
|
|
2985
3305
|
}
|
|
2986
3306
|
const stdout = await this.callCodex(params.prompt);
|
|
2987
|
-
if (params.outputPath &&
|
|
3307
|
+
if (params.outputPath && existsSync10(params.outputPath)) {
|
|
2988
3308
|
return readFileSync9(params.outputPath, "utf-8");
|
|
2989
3309
|
}
|
|
2990
3310
|
return stdout;
|
|
@@ -3062,13 +3382,13 @@ var CodexAIAdapter = class {
|
|
|
3062
3382
|
try {
|
|
3063
3383
|
if (p.outputPath) {
|
|
3064
3384
|
const outputDir = dirname5(p.outputPath);
|
|
3065
|
-
if (!
|
|
3066
|
-
|
|
3385
|
+
if (!existsSync10(outputDir)) {
|
|
3386
|
+
mkdirSync7(outputDir, { recursive: true });
|
|
3067
3387
|
}
|
|
3068
3388
|
}
|
|
3069
3389
|
await this.callCodex(p.prompt, false);
|
|
3070
3390
|
let result = "";
|
|
3071
|
-
if (p.outputPath &&
|
|
3391
|
+
if (p.outputPath && existsSync10(p.outputPath)) {
|
|
3072
3392
|
result = readFileSync9(p.outputPath, "utf-8");
|
|
3073
3393
|
}
|
|
3074
3394
|
completed++;
|
|
@@ -3097,7 +3417,7 @@ function createCodexAdapter(codexCommand = "codex") {
|
|
|
3097
3417
|
|
|
3098
3418
|
// src/adapters/opencode.ts
|
|
3099
3419
|
import { spawn as spawn4 } from "child_process";
|
|
3100
|
-
import { readFileSync as readFileSync10, existsSync as
|
|
3420
|
+
import { readFileSync as readFileSync10, existsSync as existsSync11, mkdirSync as mkdirSync8 } from "fs";
|
|
3101
3421
|
import { dirname as dirname6 } from "path";
|
|
3102
3422
|
var OpenCodeAIAdapter = class {
|
|
3103
3423
|
opencodeCommand;
|
|
@@ -3109,12 +3429,12 @@ var OpenCodeAIAdapter = class {
|
|
|
3109
3429
|
\u{1F4DD} Generating...`);
|
|
3110
3430
|
if (params.outputPath) {
|
|
3111
3431
|
const outputDir = dirname6(params.outputPath);
|
|
3112
|
-
if (!
|
|
3113
|
-
|
|
3432
|
+
if (!existsSync11(outputDir)) {
|
|
3433
|
+
mkdirSync8(outputDir, { recursive: true });
|
|
3114
3434
|
}
|
|
3115
3435
|
}
|
|
3116
3436
|
const stdout = await this.callOpenCode(params.prompt);
|
|
3117
|
-
if (params.outputPath &&
|
|
3437
|
+
if (params.outputPath && existsSync11(params.outputPath)) {
|
|
3118
3438
|
return readFileSync10(params.outputPath, "utf-8");
|
|
3119
3439
|
}
|
|
3120
3440
|
return stdout;
|
|
@@ -3191,13 +3511,13 @@ var OpenCodeAIAdapter = class {
|
|
|
3191
3511
|
try {
|
|
3192
3512
|
if (p.outputPath) {
|
|
3193
3513
|
const outputDir = dirname6(p.outputPath);
|
|
3194
|
-
if (!
|
|
3195
|
-
|
|
3514
|
+
if (!existsSync11(outputDir)) {
|
|
3515
|
+
mkdirSync8(outputDir, { recursive: true });
|
|
3196
3516
|
}
|
|
3197
3517
|
}
|
|
3198
3518
|
await this.callOpenCode(p.prompt, false);
|
|
3199
3519
|
let result = "";
|
|
3200
|
-
if (p.outputPath &&
|
|
3520
|
+
if (p.outputPath && existsSync11(p.outputPath)) {
|
|
3201
3521
|
result = readFileSync10(p.outputPath, "utf-8");
|
|
3202
3522
|
}
|
|
3203
3523
|
completed++;
|
|
@@ -3481,14 +3801,14 @@ Resume with: spets resume --task ${taskId}`);
|
|
|
3481
3801
|
|
|
3482
3802
|
// src/commands/resume.ts
|
|
3483
3803
|
import { select as select2 } from "@inquirer/prompts";
|
|
3484
|
-
import { existsSync as
|
|
3485
|
-
import { join as
|
|
3804
|
+
import { existsSync as existsSync12, readdirSync as readdirSync3, readFileSync as readFileSync11 } from "fs";
|
|
3805
|
+
import { join as join8 } from "path";
|
|
3486
3806
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
3487
3807
|
async function createPR(cwd, taskId, outputs) {
|
|
3488
3808
|
const outputContents = [];
|
|
3489
3809
|
for (const outputPath of outputs) {
|
|
3490
|
-
const fullPath =
|
|
3491
|
-
if (
|
|
3810
|
+
const fullPath = join8(cwd, outputPath);
|
|
3811
|
+
if (existsSync12(fullPath)) {
|
|
3492
3812
|
const content = readFileSync11(fullPath, "utf-8");
|
|
3493
3813
|
outputContents.push(`## ${outputPath}
|
|
3494
3814
|
|
|
@@ -3511,15 +3831,15 @@ ${outputContents.join("\n\n---\n\n")}`;
|
|
|
3511
3831
|
}
|
|
3512
3832
|
function listResumableTasks(cwd) {
|
|
3513
3833
|
const outputsDir = getOutputsDir(cwd);
|
|
3514
|
-
if (!
|
|
3834
|
+
if (!existsSync12(outputsDir)) {
|
|
3515
3835
|
return [];
|
|
3516
3836
|
}
|
|
3517
3837
|
const tasks = [];
|
|
3518
|
-
const taskDirs =
|
|
3838
|
+
const taskDirs = readdirSync3(outputsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
3519
3839
|
const orchestrator = new Orchestrator(cwd);
|
|
3520
3840
|
for (const taskId of taskDirs) {
|
|
3521
|
-
const stateFile =
|
|
3522
|
-
if (!
|
|
3841
|
+
const stateFile = join8(outputsDir, taskId, ".state.json");
|
|
3842
|
+
if (!existsSync12(stateFile)) continue;
|
|
3523
3843
|
try {
|
|
3524
3844
|
const response = orchestrator.cmdStatus(taskId);
|
|
3525
3845
|
if (response.type === "error") continue;
|
|
@@ -3769,532 +4089,6 @@ Resume with: spets resume --task ${taskId}`);
|
|
|
3769
4089
|
}
|
|
3770
4090
|
}
|
|
3771
4091
|
|
|
3772
|
-
// src/commands/plugin.ts
|
|
3773
|
-
import { existsSync as existsSync12, mkdirSync as mkdirSync8, writeFileSync as writeFileSync7, rmSync, readdirSync as readdirSync3 } from "fs";
|
|
3774
|
-
import { join as join8 } from "path";
|
|
3775
|
-
import { homedir } from "os";
|
|
3776
|
-
async function pluginCommand(action, name) {
|
|
3777
|
-
switch (action) {
|
|
3778
|
-
case "install":
|
|
3779
|
-
if (!name) {
|
|
3780
|
-
console.error("Plugin name required.");
|
|
3781
|
-
console.error("Usage: spets plugin install <name>");
|
|
3782
|
-
process.exit(1);
|
|
3783
|
-
}
|
|
3784
|
-
await installPlugin(name);
|
|
3785
|
-
break;
|
|
3786
|
-
case "uninstall":
|
|
3787
|
-
if (!name) {
|
|
3788
|
-
console.error("Plugin name required.");
|
|
3789
|
-
console.error("Usage: spets plugin uninstall <name>");
|
|
3790
|
-
process.exit(1);
|
|
3791
|
-
}
|
|
3792
|
-
await uninstallPlugin(name);
|
|
3793
|
-
break;
|
|
3794
|
-
case "list":
|
|
3795
|
-
await listPlugins();
|
|
3796
|
-
break;
|
|
3797
|
-
default:
|
|
3798
|
-
console.error(`Unknown action: ${action}`);
|
|
3799
|
-
console.error("Available actions: install, uninstall, list");
|
|
3800
|
-
process.exit(1);
|
|
3801
|
-
}
|
|
3802
|
-
}
|
|
3803
|
-
async function installPlugin(name) {
|
|
3804
|
-
const plugins = {
|
|
3805
|
-
claude: installClaudePlugin,
|
|
3806
|
-
codex: installCodexPlugin,
|
|
3807
|
-
opencode: installOpenCodePlugin
|
|
3808
|
-
};
|
|
3809
|
-
const installer = plugins[name];
|
|
3810
|
-
if (!installer) {
|
|
3811
|
-
console.error(`Unknown plugin: ${name}`);
|
|
3812
|
-
console.error("Available plugins: claude, codex, opencode");
|
|
3813
|
-
process.exit(1);
|
|
3814
|
-
}
|
|
3815
|
-
installer();
|
|
3816
|
-
}
|
|
3817
|
-
function installClaudePlugin() {
|
|
3818
|
-
const claudeDir = join8(homedir(), ".claude");
|
|
3819
|
-
const commandsDir = join8(claudeDir, "commands");
|
|
3820
|
-
mkdirSync8(commandsDir, { recursive: true });
|
|
3821
|
-
const skillPath = join8(commandsDir, "spets.md");
|
|
3822
|
-
writeFileSync7(skillPath, getClaudeSkillContent());
|
|
3823
|
-
console.log("Installed Claude Code plugin.");
|
|
3824
|
-
console.log(`Location: ${skillPath}`);
|
|
3825
|
-
console.log("");
|
|
3826
|
-
console.log("Usage in Claude Code:");
|
|
3827
|
-
console.log(' /spets "your task description"');
|
|
3828
|
-
console.log("");
|
|
3829
|
-
console.log("This skill runs deterministically within your Claude Code session.");
|
|
3830
|
-
console.log("No additional Claude processes are spawned.");
|
|
3831
|
-
}
|
|
3832
|
-
function installCodexPlugin() {
|
|
3833
|
-
const codexDir = join8(homedir(), ".codex");
|
|
3834
|
-
const skillsDir = join8(codexDir, "skills");
|
|
3835
|
-
const spetsDir = join8(skillsDir, "spets");
|
|
3836
|
-
mkdirSync8(spetsDir, { recursive: true });
|
|
3837
|
-
const skillPath = join8(spetsDir, "SKILL.md");
|
|
3838
|
-
writeFileSync7(skillPath, getCodexSkillContent());
|
|
3839
|
-
console.log("Installed Codex CLI plugin.");
|
|
3840
|
-
console.log(`Location: ${skillPath}`);
|
|
3841
|
-
console.log("");
|
|
3842
|
-
console.log("Usage in Codex CLI:");
|
|
3843
|
-
console.log(' $spets "your task description"');
|
|
3844
|
-
console.log("");
|
|
3845
|
-
console.log("This skill runs deterministically within your Codex CLI session.");
|
|
3846
|
-
console.log("No additional Codex processes are spawned.");
|
|
3847
|
-
}
|
|
3848
|
-
function installOpenCodePlugin() {
|
|
3849
|
-
const configDir = join8(homedir(), ".config", "opencode");
|
|
3850
|
-
const skillsDir = join8(configDir, "skills");
|
|
3851
|
-
const spetsDir = join8(skillsDir, "spets");
|
|
3852
|
-
mkdirSync8(spetsDir, { recursive: true });
|
|
3853
|
-
const skillPath = join8(spetsDir, "SKILL.md");
|
|
3854
|
-
writeFileSync7(skillPath, getOpenCodeSkillContent());
|
|
3855
|
-
console.log("Installed OpenCode plugin.");
|
|
3856
|
-
console.log(`Location: ${skillPath}`);
|
|
3857
|
-
console.log("");
|
|
3858
|
-
console.log("Usage in OpenCode:");
|
|
3859
|
-
console.log(" Use the spets skill in your OpenCode session");
|
|
3860
|
-
console.log("");
|
|
3861
|
-
console.log("This skill runs deterministically within your OpenCode session.");
|
|
3862
|
-
console.log("No additional OpenCode processes are spawned.");
|
|
3863
|
-
}
|
|
3864
|
-
async function uninstallPlugin(name) {
|
|
3865
|
-
if (name === "claude") {
|
|
3866
|
-
const skillPath = join8(homedir(), ".claude", "commands", "spets.md");
|
|
3867
|
-
const legacySkillPath = join8(homedir(), ".claude", "commands", "sdd-do.md");
|
|
3868
|
-
let uninstalled = false;
|
|
3869
|
-
if (existsSync12(skillPath)) {
|
|
3870
|
-
rmSync(skillPath);
|
|
3871
|
-
uninstalled = true;
|
|
3872
|
-
}
|
|
3873
|
-
if (existsSync12(legacySkillPath)) {
|
|
3874
|
-
rmSync(legacySkillPath);
|
|
3875
|
-
uninstalled = true;
|
|
3876
|
-
}
|
|
3877
|
-
if (uninstalled) {
|
|
3878
|
-
console.log("Uninstalled Claude Code plugin.");
|
|
3879
|
-
} else {
|
|
3880
|
-
console.log("Claude Code plugin not installed.");
|
|
3881
|
-
}
|
|
3882
|
-
return;
|
|
3883
|
-
}
|
|
3884
|
-
if (name === "codex") {
|
|
3885
|
-
const skillPath = join8(homedir(), ".codex", "skills", "spets", "SKILL.md");
|
|
3886
|
-
if (existsSync12(skillPath)) {
|
|
3887
|
-
rmSync(skillPath);
|
|
3888
|
-
try {
|
|
3889
|
-
const skillDir = join8(homedir(), ".codex", "skills", "spets");
|
|
3890
|
-
const skillsDir = join8(homedir(), ".codex", "skills");
|
|
3891
|
-
rmSync(skillDir, { recursive: true });
|
|
3892
|
-
const remaining = readdirSync3(skillsDir);
|
|
3893
|
-
if (remaining.length === 0) {
|
|
3894
|
-
rmSync(skillsDir);
|
|
3895
|
-
}
|
|
3896
|
-
} catch {
|
|
3897
|
-
}
|
|
3898
|
-
console.log("Uninstalled Codex CLI plugin.");
|
|
3899
|
-
} else {
|
|
3900
|
-
console.log("Codex CLI plugin not installed.");
|
|
3901
|
-
}
|
|
3902
|
-
return;
|
|
3903
|
-
}
|
|
3904
|
-
if (name === "opencode") {
|
|
3905
|
-
const skillPath = join8(homedir(), ".config", "opencode", "skills", "spets", "SKILL.md");
|
|
3906
|
-
if (existsSync12(skillPath)) {
|
|
3907
|
-
rmSync(skillPath);
|
|
3908
|
-
try {
|
|
3909
|
-
const skillDir = join8(homedir(), ".config", "opencode", "skills", "spets");
|
|
3910
|
-
const skillsDir = join8(homedir(), ".config", "opencode", "skills");
|
|
3911
|
-
rmSync(skillDir, { recursive: true });
|
|
3912
|
-
const remaining = readdirSync3(skillsDir);
|
|
3913
|
-
if (remaining.length === 0) {
|
|
3914
|
-
rmSync(skillsDir);
|
|
3915
|
-
}
|
|
3916
|
-
} catch {
|
|
3917
|
-
}
|
|
3918
|
-
console.log("Uninstalled OpenCode plugin.");
|
|
3919
|
-
} else {
|
|
3920
|
-
console.log("OpenCode plugin not installed.");
|
|
3921
|
-
}
|
|
3922
|
-
return;
|
|
3923
|
-
}
|
|
3924
|
-
console.error(`Unknown plugin: ${name}`);
|
|
3925
|
-
process.exit(1);
|
|
3926
|
-
}
|
|
3927
|
-
async function listPlugins() {
|
|
3928
|
-
console.log("Available plugins:");
|
|
3929
|
-
console.log("");
|
|
3930
|
-
console.log(" claude - Claude Code /spets skill");
|
|
3931
|
-
console.log(" codex - Codex CLI $spets skill");
|
|
3932
|
-
console.log(" opencode - OpenCode spets skill");
|
|
3933
|
-
console.log("");
|
|
3934
|
-
const claudeSkillPath = join8(homedir(), ".claude", "commands", "spets.md");
|
|
3935
|
-
const claudeLegacySkillPath = join8(homedir(), ".claude", "commands", "sdd-do.md");
|
|
3936
|
-
const claudeInstalled = existsSync12(claudeSkillPath) || existsSync12(claudeLegacySkillPath);
|
|
3937
|
-
const codexSkillPath = join8(homedir(), ".codex", "skills", "spets", "SKILL.md");
|
|
3938
|
-
const codexInstalled = existsSync12(codexSkillPath);
|
|
3939
|
-
const opencodeSkillPath = join8(homedir(), ".config", "opencode", "skills", "spets", "SKILL.md");
|
|
3940
|
-
const opencodeInstalled = existsSync12(opencodeSkillPath);
|
|
3941
|
-
console.log("Installed:");
|
|
3942
|
-
const installed = [];
|
|
3943
|
-
if (claudeInstalled) installed.push("claude");
|
|
3944
|
-
if (codexInstalled) installed.push("codex");
|
|
3945
|
-
if (opencodeInstalled) installed.push("opencode");
|
|
3946
|
-
if (installed.length > 0) {
|
|
3947
|
-
for (const plugin of installed) {
|
|
3948
|
-
console.log(` - ${plugin}`);
|
|
3949
|
-
}
|
|
3950
|
-
} else {
|
|
3951
|
-
console.log(" (none)");
|
|
3952
|
-
}
|
|
3953
|
-
}
|
|
3954
|
-
function getClaudeSkillContent() {
|
|
3955
|
-
return `# Spets Command Executor
|
|
3956
|
-
|
|
3957
|
-
Execute orchestrator commands. Parse JSON response \u2192 execute matching action.
|
|
3958
|
-
|
|
3959
|
-
---
|
|
3960
|
-
|
|
3961
|
-
## Constraints
|
|
3962
|
-
|
|
3963
|
-
- **NO** EnterPlanMode
|
|
3964
|
-
- **NO** TaskCreate/TaskUpdate
|
|
3965
|
-
- **NO** subprocess spawning for AI calls
|
|
3966
|
-
- **IMMEDIATELY** execute commands, no planning
|
|
3967
|
-
|
|
3968
|
-
---
|
|
3969
|
-
|
|
3970
|
-
## Start
|
|
3971
|
-
|
|
3972
|
-
\`\`\`bash
|
|
3973
|
-
npx spets orchestrate init "$ARGUMENTS"
|
|
3974
|
-
\`\`\`
|
|
3975
|
-
|
|
3976
|
-
Parse JSON response \u2192 execute action from table below \u2192 loop until \`type: "complete"\` or \`type: "error"\`.
|
|
3977
|
-
|
|
3978
|
-
---
|
|
3979
|
-
|
|
3980
|
-
## Command Table
|
|
3981
|
-
|
|
3982
|
-
### \`type: "phase"\`
|
|
3983
|
-
|
|
3984
|
-
| \`phase\` | Action |
|
|
3985
|
-
|---------|--------|
|
|
3986
|
-
| \`explore\` | [ACTION_EXPLORE](#action_explore) |
|
|
3987
|
-
| \`clarify\` | [ACTION_CLARIFY](#action_clarify) |
|
|
3988
|
-
| \`execute\` | [ACTION_EXECUTE](#action_execute) |
|
|
3989
|
-
| \`verify\` | [ACTION_VERIFY](#action_verify) |
|
|
3990
|
-
| \`context\` | [ACTION_CONTEXT](#action_context) (legacy) |
|
|
3991
|
-
| \`generate\` | [ACTION_GENERATE](#action_generate) (legacy) |
|
|
3992
|
-
|
|
3993
|
-
### \`type: "checkpoint"\`
|
|
3994
|
-
|
|
3995
|
-
| \`checkpoint\` | Action |
|
|
3996
|
-
|--------------|--------|
|
|
3997
|
-
| \`clarify\` | [ACTION_ASK_QUESTIONS](#action_ask_questions) |
|
|
3998
|
-
| \`approve\` | [ACTION_ASK_APPROVAL](#action_ask_approval) |
|
|
3999
|
-
|
|
4000
|
-
### \`type: "complete"\`
|
|
4001
|
-
|
|
4002
|
-
| \`status\` | Action |
|
|
4003
|
-
|----------|--------|
|
|
4004
|
-
| \`completed\` | Print: "Workflow complete. Outputs: {outputs}" |
|
|
4005
|
-
| \`stopped\` | Print: "Workflow paused. Resume with: npx spets resume --task {taskId}" |
|
|
4006
|
-
| \`rejected\` | Print: "Workflow rejected." |
|
|
4007
|
-
|
|
4008
|
-
### \`type: "error"\`
|
|
4009
|
-
|
|
4010
|
-
Print: "Error: {error}" \u2192 STOP
|
|
4011
|
-
|
|
4012
|
-
---
|
|
4013
|
-
|
|
4014
|
-
## Action Definitions
|
|
4015
|
-
|
|
4016
|
-
**IMPORTANT:** Each phase response includes a \`prompt\` field containing the exact instructions to follow.
|
|
4017
|
-
This ensures identical behavior across CLI, Claude Code, and GitHub modes.
|
|
4018
|
-
|
|
4019
|
-
### ACTION_EXPLORE
|
|
4020
|
-
|
|
4021
|
-
\`\`\`
|
|
4022
|
-
1. Follow the instructions in response.prompt EXACTLY
|
|
4023
|
-
2. The prompt tells you:
|
|
4024
|
-
- What to search for
|
|
4025
|
-
- How to analyze the codebase
|
|
4026
|
-
- The exact JSON output format required
|
|
4027
|
-
3. Bash: npx spets orchestrate explore-done {taskId} '{exploreJson}'
|
|
4028
|
-
4. Parse response \u2192 next action
|
|
4029
|
-
\`\`\`
|
|
4030
|
-
|
|
4031
|
-
### ACTION_CLARIFY
|
|
4032
|
-
|
|
4033
|
-
\`\`\`
|
|
4034
|
-
1. Follow the instructions in response.prompt EXACTLY
|
|
4035
|
-
2. The prompt tells you:
|
|
4036
|
-
- What context to analyze
|
|
4037
|
-
- How to generate questions
|
|
4038
|
-
- The exact JSON output format required
|
|
4039
|
-
3. Bash: npx spets orchestrate clarify-done {taskId} '{questionsJson}'
|
|
4040
|
-
- Format: [{"id":"q1","question":"..."},...]
|
|
4041
|
-
- Empty if no questions: []
|
|
4042
|
-
4. Parse response \u2192 next action
|
|
4043
|
-
\`\`\`
|
|
4044
|
-
|
|
4045
|
-
### ACTION_EXECUTE
|
|
4046
|
-
|
|
4047
|
-
\`\`\`
|
|
4048
|
-
1. Follow the instructions in response.prompt EXACTLY
|
|
4049
|
-
2. The prompt tells you:
|
|
4050
|
-
- The instruction and template to follow
|
|
4051
|
-
- The explore output and answers to use
|
|
4052
|
-
- Where to save the document/code (context.output)
|
|
4053
|
-
- Any revision/verify feedback to address
|
|
4054
|
-
3. Write the document/code to context.output
|
|
4055
|
-
4. Bash: npx spets orchestrate execute-done {taskId}
|
|
4056
|
-
5. Parse response \u2192 next action
|
|
4057
|
-
\`\`\`
|
|
4058
|
-
|
|
4059
|
-
### ACTION_VERIFY
|
|
4060
|
-
|
|
4061
|
-
\`\`\`
|
|
4062
|
-
1. Follow the instructions in response.prompt EXACTLY
|
|
4063
|
-
2. The prompt tells you:
|
|
4064
|
-
- The document to verify (already included in prompt)
|
|
4065
|
-
- The requirements and template to check against
|
|
4066
|
-
- The scoring criteria and pass conditions
|
|
4067
|
-
- The exact JSON output format required
|
|
4068
|
-
3. Bash: npx spets orchestrate verify-done {taskId} '{verifyJson}'
|
|
4069
|
-
4. Parse response \u2192 next action
|
|
4070
|
-
- If passed: goes to human review
|
|
4071
|
-
- If failed (attempts < 3): goes back to draft phase (auto-fix)
|
|
4072
|
-
- If failed (attempts >= 3): goes to human review with warning
|
|
4073
|
-
\`\`\`
|
|
4074
|
-
|
|
4075
|
-
### ACTION_GENERATE (legacy)
|
|
4076
|
-
|
|
4077
|
-
\`\`\`
|
|
4078
|
-
1. Read(context.instruction)
|
|
4079
|
-
2. IF context.template EXISTS: Read(context.template)
|
|
4080
|
-
3. IF context.previousOutput EXISTS: Read(context.previousOutput)
|
|
4081
|
-
4. Generate document using: gatheredContext, answers, revisionFeedback
|
|
4082
|
-
5. Write(context.output, documentContent)
|
|
4083
|
-
6. Bash: npx spets orchestrate generate-done {taskId}
|
|
4084
|
-
7. Parse response \u2192 next action
|
|
4085
|
-
\`\`\`
|
|
4086
|
-
|
|
4087
|
-
### ACTION_CONTEXT (legacy)
|
|
4088
|
-
|
|
4089
|
-
\`\`\`
|
|
4090
|
-
1. Read(context.instruction)
|
|
4091
|
-
2. IF context.previousOutput EXISTS: Read(context.previousOutput)
|
|
4092
|
-
3. Explore codebase for: {description}
|
|
4093
|
-
4. Bash: npx spets orchestrate context-done {taskId}
|
|
4094
|
-
5. Parse response \u2192 next action
|
|
4095
|
-
\`\`\`
|
|
4096
|
-
|
|
4097
|
-
### ACTION_ASK_QUESTIONS
|
|
4098
|
-
|
|
4099
|
-
\`\`\`
|
|
4100
|
-
1. AskUserQuestion(
|
|
4101
|
-
questions: [
|
|
4102
|
-
FOR EACH q IN questions:
|
|
4103
|
-
{
|
|
4104
|
-
question: q.question,
|
|
4105
|
-
header: "Q{index}",
|
|
4106
|
-
options: q.options OR [
|
|
4107
|
-
{label: "Provide answer", description: "Type your answer"}
|
|
4108
|
-
]
|
|
4109
|
-
}
|
|
4110
|
-
]
|
|
4111
|
-
)
|
|
4112
|
-
|
|
4113
|
-
2. Collect answers into: [{"questionId":"q1","answer":"..."},...]
|
|
4114
|
-
|
|
4115
|
-
3. Bash: npx spets orchestrate clarified {taskId} '{answersJson}'
|
|
4116
|
-
|
|
4117
|
-
4. Parse response \u2192 next action
|
|
4118
|
-
\`\`\`
|
|
4119
|
-
|
|
4120
|
-
### ACTION_ASK_APPROVAL
|
|
4121
|
-
|
|
4122
|
-
\`\`\`
|
|
4123
|
-
1. Read(specPath)
|
|
4124
|
-
|
|
4125
|
-
2. Summarize document (2-3 sentences)
|
|
4126
|
-
|
|
4127
|
-
3. AskUserQuestion(
|
|
4128
|
-
questions: [{
|
|
4129
|
-
question: "Review {step} document. What would you like to do?",
|
|
4130
|
-
header: "Review",
|
|
4131
|
-
options: [
|
|
4132
|
-
{label: "Approve", description: "Continue to next step"},
|
|
4133
|
-
{label: "Revise", description: "Request changes"},
|
|
4134
|
-
{label: "Reject", description: "Stop workflow"},
|
|
4135
|
-
{label: "Stop", description: "Pause for later"}
|
|
4136
|
-
]
|
|
4137
|
-
}]
|
|
4138
|
-
)
|
|
4139
|
-
|
|
4140
|
-
4. SWITCH answer:
|
|
4141
|
-
- "Approve": Bash: npx spets orchestrate approve {taskId}
|
|
4142
|
-
- "Revise":
|
|
4143
|
-
- Ask for feedback (AskUserQuestion or text input)
|
|
4144
|
-
- Bash: npx spets orchestrate revise {taskId} "{feedback}"
|
|
4145
|
-
- "Reject": Bash: npx spets orchestrate reject {taskId}
|
|
4146
|
-
- "Stop": Bash: npx spets orchestrate stop {taskId}
|
|
4147
|
-
|
|
4148
|
-
5. Parse response \u2192 next action
|
|
4149
|
-
\`\`\`
|
|
4150
|
-
|
|
4151
|
-
---
|
|
4152
|
-
|
|
4153
|
-
## Orchestrator Commands Reference
|
|
4154
|
-
|
|
4155
|
-
\`\`\`bash
|
|
4156
|
-
# New 5-phase workflow
|
|
4157
|
-
npx spets orchestrate init "<description>"
|
|
4158
|
-
npx spets orchestrate explore-done <taskId> '<json>'
|
|
4159
|
-
npx spets orchestrate clarify-done <taskId> '<json>'
|
|
4160
|
-
npx spets orchestrate clarified <taskId> '<json>'
|
|
4161
|
-
npx spets orchestrate execute-done <taskId>
|
|
4162
|
-
npx spets orchestrate verify-done <taskId> '<json>'
|
|
4163
|
-
npx spets orchestrate approve <taskId>
|
|
4164
|
-
npx spets orchestrate revise <taskId> "<feedback>"
|
|
4165
|
-
npx spets orchestrate reject <taskId>
|
|
4166
|
-
npx spets orchestrate stop <taskId>
|
|
4167
|
-
|
|
4168
|
-
# Legacy (backward compatible)
|
|
4169
|
-
npx spets orchestrate context-done <taskId>
|
|
4170
|
-
npx spets orchestrate generate-done <taskId>
|
|
4171
|
-
\`\`\`
|
|
4172
|
-
|
|
4173
|
-
---
|
|
4174
|
-
|
|
4175
|
-
$ARGUMENTS
|
|
4176
|
-
description: Task description for the workflow
|
|
4177
|
-
`;
|
|
4178
|
-
}
|
|
4179
|
-
function getCodexSkillContent() {
|
|
4180
|
-
return `---
|
|
4181
|
-
name: spets
|
|
4182
|
-
description: SDD workflow executor - runs spec-driven development workflows within Codex CLI
|
|
4183
|
-
---
|
|
4184
|
-
|
|
4185
|
-
# Spets Command Executor
|
|
4186
|
-
|
|
4187
|
-
Execute orchestrator commands. Parse JSON response and execute matching action.
|
|
4188
|
-
|
|
4189
|
-
## Start
|
|
4190
|
-
|
|
4191
|
-
Run: npx spets orchestrate init "$ARGUMENTS"
|
|
4192
|
-
|
|
4193
|
-
Parse JSON response, then execute action from table below, loop until type: "complete" or "error".
|
|
4194
|
-
|
|
4195
|
-
## Command Table
|
|
4196
|
-
|
|
4197
|
-
### type: "phase"
|
|
4198
|
-
|
|
4199
|
-
| phase | Action |
|
|
4200
|
-
|-------|--------|
|
|
4201
|
-
| explore | Follow response.prompt to explore codebase |
|
|
4202
|
-
| clarify | Follow response.prompt to generate questions |
|
|
4203
|
-
| execute | Follow response.prompt to write document |
|
|
4204
|
-
| verify | Follow response.prompt to validate document |
|
|
4205
|
-
|
|
4206
|
-
### type: "checkpoint"
|
|
4207
|
-
|
|
4208
|
-
| checkpoint | Action |
|
|
4209
|
-
|------------|--------|
|
|
4210
|
-
| clarify | Ask user questions, collect answers |
|
|
4211
|
-
| approve | Ask user to review document |
|
|
4212
|
-
|
|
4213
|
-
### type: "complete"
|
|
4214
|
-
|
|
4215
|
-
Print completion status and outputs.
|
|
4216
|
-
|
|
4217
|
-
### type: "error"
|
|
4218
|
-
|
|
4219
|
-
Print error and stop.
|
|
4220
|
-
|
|
4221
|
-
## Orchestrator Commands
|
|
4222
|
-
|
|
4223
|
-
npx spets orchestrate init "<description>"
|
|
4224
|
-
npx spets orchestrate explore-done <taskId> '<json>'
|
|
4225
|
-
npx spets orchestrate clarify-done <taskId> '<json>'
|
|
4226
|
-
npx spets orchestrate clarified <taskId> '<json>'
|
|
4227
|
-
npx spets orchestrate execute-done <taskId>
|
|
4228
|
-
npx spets orchestrate verify-done <taskId> '<json>'
|
|
4229
|
-
npx spets orchestrate approve <taskId>
|
|
4230
|
-
npx spets orchestrate revise <taskId> "<feedback>"
|
|
4231
|
-
npx spets orchestrate reject <taskId>
|
|
4232
|
-
npx spets orchestrate stop <taskId>
|
|
4233
|
-
|
|
4234
|
-
$ARGUMENTS
|
|
4235
|
-
description: Task description for the workflow
|
|
4236
|
-
`;
|
|
4237
|
-
}
|
|
4238
|
-
function getOpenCodeSkillContent() {
|
|
4239
|
-
return `---
|
|
4240
|
-
name: spets
|
|
4241
|
-
description: SDD workflow executor - runs spec-driven development workflows within OpenCode
|
|
4242
|
-
---
|
|
4243
|
-
|
|
4244
|
-
# Spets Command Executor
|
|
4245
|
-
|
|
4246
|
-
Execute orchestrator commands. Parse JSON response and execute matching action.
|
|
4247
|
-
|
|
4248
|
-
## Start
|
|
4249
|
-
|
|
4250
|
-
Run: npx spets orchestrate init "$ARGUMENTS"
|
|
4251
|
-
|
|
4252
|
-
Parse JSON response, then execute action from table below, loop until type: "complete" or "error".
|
|
4253
|
-
|
|
4254
|
-
## Command Table
|
|
4255
|
-
|
|
4256
|
-
### type: "phase"
|
|
4257
|
-
|
|
4258
|
-
| phase | Action |
|
|
4259
|
-
|-------|--------|
|
|
4260
|
-
| explore | Follow response.prompt to explore codebase |
|
|
4261
|
-
| clarify | Follow response.prompt to generate questions |
|
|
4262
|
-
| execute | Follow response.prompt to write document |
|
|
4263
|
-
| verify | Follow response.prompt to validate document |
|
|
4264
|
-
|
|
4265
|
-
### type: "checkpoint"
|
|
4266
|
-
|
|
4267
|
-
| checkpoint | Action |
|
|
4268
|
-
|------------|--------|
|
|
4269
|
-
| clarify | Ask user questions, collect answers |
|
|
4270
|
-
| approve | Ask user to review document |
|
|
4271
|
-
|
|
4272
|
-
### type: "complete"
|
|
4273
|
-
|
|
4274
|
-
Print completion status and outputs.
|
|
4275
|
-
|
|
4276
|
-
### type: "error"
|
|
4277
|
-
|
|
4278
|
-
Print error and stop.
|
|
4279
|
-
|
|
4280
|
-
## Orchestrator Commands
|
|
4281
|
-
|
|
4282
|
-
npx spets orchestrate init "<description>"
|
|
4283
|
-
npx spets orchestrate explore-done <taskId> '<json>'
|
|
4284
|
-
npx spets orchestrate clarify-done <taskId> '<json>'
|
|
4285
|
-
npx spets orchestrate clarified <taskId> '<json>'
|
|
4286
|
-
npx spets orchestrate execute-done <taskId>
|
|
4287
|
-
npx spets orchestrate verify-done <taskId> '<json>'
|
|
4288
|
-
npx spets orchestrate approve <taskId>
|
|
4289
|
-
npx spets orchestrate revise <taskId> "<feedback>"
|
|
4290
|
-
npx spets orchestrate reject <taskId>
|
|
4291
|
-
npx spets orchestrate stop <taskId>
|
|
4292
|
-
|
|
4293
|
-
$ARGUMENTS
|
|
4294
|
-
description: Task description for the workflow
|
|
4295
|
-
`;
|
|
4296
|
-
}
|
|
4297
|
-
|
|
4298
4092
|
// src/commands/github.ts
|
|
4299
4093
|
import { execSync as execSync4, spawn as spawn6, spawnSync as spawnSync4 } from "child_process";
|
|
4300
4094
|
import { readdirSync as readdirSync4, readFileSync as readFileSync12, existsSync as existsSync14 } from "fs";
|