spets 0.1.0
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/LICENSE +21 -0
- package/README.md +103 -0
- package/dist/chunk-KZQ5KNMC.js +210 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1424 -0
- package/dist/state-MCFJFWJC.js +30 -0
- package/package.json +52 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1424 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
generateTaskId,
|
|
4
|
+
getOutputPath,
|
|
5
|
+
getSeptDir,
|
|
6
|
+
getWorkflowState,
|
|
7
|
+
listTasks,
|
|
8
|
+
loadConfig,
|
|
9
|
+
loadDocument,
|
|
10
|
+
loadStepDefinition,
|
|
11
|
+
loadTaskMetadata,
|
|
12
|
+
saveDocument,
|
|
13
|
+
saveTaskMetadata,
|
|
14
|
+
septExists,
|
|
15
|
+
updateDocumentStatus
|
|
16
|
+
} from "./chunk-KZQ5KNMC.js";
|
|
17
|
+
|
|
18
|
+
// src/index.ts
|
|
19
|
+
import { Command } from "commander";
|
|
20
|
+
|
|
21
|
+
// src/commands/init.ts
|
|
22
|
+
import { mkdirSync, writeFileSync } from "fs";
|
|
23
|
+
import { join, dirname } from "path";
|
|
24
|
+
import { fileURLToPath } from "url";
|
|
25
|
+
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
26
|
+
async function initCommand(options) {
|
|
27
|
+
const cwd = process.cwd();
|
|
28
|
+
const septDir = getSeptDir(cwd);
|
|
29
|
+
if (septExists(cwd) && !options.force) {
|
|
30
|
+
console.error("Spets already initialized. Use --force to overwrite.");
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
mkdirSync(septDir, { recursive: true });
|
|
34
|
+
mkdirSync(join(septDir, "steps"), { recursive: true });
|
|
35
|
+
mkdirSync(join(septDir, "outputs"), { recursive: true });
|
|
36
|
+
mkdirSync(join(septDir, "hooks"), { recursive: true });
|
|
37
|
+
const templatesDir = join(__dirname, "..", "templates");
|
|
38
|
+
writeFileSync(join(septDir, "config.yml"), getDefaultConfig());
|
|
39
|
+
createDefaultSteps(septDir);
|
|
40
|
+
console.log("Initialized spets in .sept/");
|
|
41
|
+
console.log("");
|
|
42
|
+
console.log("Created:");
|
|
43
|
+
console.log(" .sept/config.yml - Workflow configuration");
|
|
44
|
+
console.log(" .sept/steps/01-plan/ - Planning step");
|
|
45
|
+
console.log(" .sept/steps/02-implement/ - Implementation step");
|
|
46
|
+
if (options.github) {
|
|
47
|
+
createGitHubWorkflow(cwd);
|
|
48
|
+
console.log(" .github/workflows/sept.yml - GitHub Actions workflow");
|
|
49
|
+
}
|
|
50
|
+
console.log("");
|
|
51
|
+
console.log("Next steps:");
|
|
52
|
+
console.log(" 1. Edit .sept/config.yml to customize your workflow");
|
|
53
|
+
console.log(" 2. Customize step instructions in .sept/steps/");
|
|
54
|
+
if (options.github) {
|
|
55
|
+
console.log(" 3. Add ANTHROPIC_API_KEY to your repo secrets");
|
|
56
|
+
console.log(' 4. Run: spets start "task" --platform github --owner <owner> --repo <repo> --issue <n>');
|
|
57
|
+
} else {
|
|
58
|
+
console.log(' 3. Run: spets start "your task description"');
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function getDefaultConfig() {
|
|
62
|
+
return `# Spets Configuration
|
|
63
|
+
# Define your workflow steps here
|
|
64
|
+
|
|
65
|
+
steps:
|
|
66
|
+
- 01-plan
|
|
67
|
+
- 02-implement
|
|
68
|
+
|
|
69
|
+
# Optional hooks (shell scripts)
|
|
70
|
+
# hooks:
|
|
71
|
+
# preStep: "./hooks/pre-step.sh"
|
|
72
|
+
# postStep: "./hooks/post-step.sh"
|
|
73
|
+
# onApprove: "./hooks/on-approve.sh"
|
|
74
|
+
# onReject: "./hooks/on-reject.sh"
|
|
75
|
+
# onComplete: "./hooks/on-complete.sh"
|
|
76
|
+
`;
|
|
77
|
+
}
|
|
78
|
+
function createDefaultSteps(septDir) {
|
|
79
|
+
const planDir = join(septDir, "steps", "01-plan");
|
|
80
|
+
mkdirSync(planDir, { recursive: true });
|
|
81
|
+
writeFileSync(join(planDir, "instruction.md"), getPlanInstruction());
|
|
82
|
+
writeFileSync(join(planDir, "template.md"), getPlanTemplate());
|
|
83
|
+
const implementDir = join(septDir, "steps", "02-implement");
|
|
84
|
+
mkdirSync(implementDir, { recursive: true });
|
|
85
|
+
writeFileSync(join(implementDir, "instruction.md"), getImplementInstruction());
|
|
86
|
+
writeFileSync(join(implementDir, "template.md"), getImplementTemplate());
|
|
87
|
+
}
|
|
88
|
+
function getPlanInstruction() {
|
|
89
|
+
return `# Plan Step
|
|
90
|
+
|
|
91
|
+
You are creating a technical plan for the given task.
|
|
92
|
+
|
|
93
|
+
## Your Goal
|
|
94
|
+
|
|
95
|
+
Analyze the user's request and create a detailed implementation plan.
|
|
96
|
+
|
|
97
|
+
## Process
|
|
98
|
+
|
|
99
|
+
1. **Understand the Request**
|
|
100
|
+
- Parse the user's query to identify the core requirements
|
|
101
|
+
- Identify any ambiguities or missing information
|
|
102
|
+
|
|
103
|
+
2. **Ask Clarifying Questions** (if needed)
|
|
104
|
+
- If requirements are unclear, list questions in the \`open_questions\` section
|
|
105
|
+
- Questions should be specific and actionable
|
|
106
|
+
|
|
107
|
+
3. **Create the Plan**
|
|
108
|
+
- Break down the task into concrete steps
|
|
109
|
+
- Identify files to create/modify
|
|
110
|
+
- Consider edge cases and potential issues
|
|
111
|
+
|
|
112
|
+
## Output Format
|
|
113
|
+
|
|
114
|
+
Follow the template provided. Include:
|
|
115
|
+
- Summary of what will be built
|
|
116
|
+
- Step-by-step implementation plan
|
|
117
|
+
- Files to be created/modified
|
|
118
|
+
- Any open questions (if requirements are unclear)
|
|
119
|
+
`;
|
|
120
|
+
}
|
|
121
|
+
function getPlanTemplate() {
|
|
122
|
+
return `# Plan: {{title}}
|
|
123
|
+
|
|
124
|
+
## Summary
|
|
125
|
+
|
|
126
|
+
Brief description of what will be implemented.
|
|
127
|
+
|
|
128
|
+
## Requirements
|
|
129
|
+
|
|
130
|
+
- Requirement 1
|
|
131
|
+
- Requirement 2
|
|
132
|
+
|
|
133
|
+
## Implementation Steps
|
|
134
|
+
|
|
135
|
+
### Step 1: [Description]
|
|
136
|
+
|
|
137
|
+
Details...
|
|
138
|
+
|
|
139
|
+
### Step 2: [Description]
|
|
140
|
+
|
|
141
|
+
Details...
|
|
142
|
+
|
|
143
|
+
## Files to Modify
|
|
144
|
+
|
|
145
|
+
| File | Action | Description |
|
|
146
|
+
|------|--------|-------------|
|
|
147
|
+
| path/to/file | Create/Modify | What changes |
|
|
148
|
+
|
|
149
|
+
## Open Questions
|
|
150
|
+
|
|
151
|
+
<!-- Remove this section if no questions -->
|
|
152
|
+
|
|
153
|
+
- Question 1?
|
|
154
|
+
- Question 2?
|
|
155
|
+
|
|
156
|
+
## Risks & Considerations
|
|
157
|
+
|
|
158
|
+
- Risk 1
|
|
159
|
+
- Risk 2
|
|
160
|
+
`;
|
|
161
|
+
}
|
|
162
|
+
function getImplementInstruction() {
|
|
163
|
+
return `# Implement Step
|
|
164
|
+
|
|
165
|
+
You are implementing the plan from the previous step.
|
|
166
|
+
|
|
167
|
+
## Your Goal
|
|
168
|
+
|
|
169
|
+
Write the actual code based on the approved plan.
|
|
170
|
+
|
|
171
|
+
## Process
|
|
172
|
+
|
|
173
|
+
1. **Review the Plan**
|
|
174
|
+
- Read the approved plan document carefully
|
|
175
|
+
- Understand all requirements and steps
|
|
176
|
+
|
|
177
|
+
2. **Implement**
|
|
178
|
+
- Follow the plan step by step
|
|
179
|
+
- Write clean, well-documented code
|
|
180
|
+
- Handle edge cases identified in the plan
|
|
181
|
+
|
|
182
|
+
3. **Document Changes**
|
|
183
|
+
- List all files created/modified
|
|
184
|
+
- Explain key decisions made during implementation
|
|
185
|
+
|
|
186
|
+
## Output Format
|
|
187
|
+
|
|
188
|
+
Follow the template provided. Include:
|
|
189
|
+
- Summary of implementation
|
|
190
|
+
- List of all changes made
|
|
191
|
+
- Any deviations from the plan (with justification)
|
|
192
|
+
- Testing notes
|
|
193
|
+
`;
|
|
194
|
+
}
|
|
195
|
+
function getImplementTemplate() {
|
|
196
|
+
return `# Implementation: {{title}}
|
|
197
|
+
|
|
198
|
+
## Summary
|
|
199
|
+
|
|
200
|
+
Brief description of what was implemented.
|
|
201
|
+
|
|
202
|
+
## Changes Made
|
|
203
|
+
|
|
204
|
+
### New Files
|
|
205
|
+
|
|
206
|
+
| File | Description |
|
|
207
|
+
|------|-------------|
|
|
208
|
+
| path/to/file | What it does |
|
|
209
|
+
|
|
210
|
+
### Modified Files
|
|
211
|
+
|
|
212
|
+
| File | Changes |
|
|
213
|
+
|------|---------|
|
|
214
|
+
| path/to/file | What changed |
|
|
215
|
+
|
|
216
|
+
## Key Decisions
|
|
217
|
+
|
|
218
|
+
- Decision 1: Explanation
|
|
219
|
+
- Decision 2: Explanation
|
|
220
|
+
|
|
221
|
+
## Deviations from Plan
|
|
222
|
+
|
|
223
|
+
<!-- Remove if no deviations -->
|
|
224
|
+
|
|
225
|
+
None / List any deviations with justification.
|
|
226
|
+
|
|
227
|
+
## Testing
|
|
228
|
+
|
|
229
|
+
- [ ] Manual testing completed
|
|
230
|
+
- [ ] Unit tests added
|
|
231
|
+
- [ ] Integration tests pass
|
|
232
|
+
|
|
233
|
+
## Next Steps
|
|
234
|
+
|
|
235
|
+
- Follow-up task 1
|
|
236
|
+
- Follow-up task 2
|
|
237
|
+
`;
|
|
238
|
+
}
|
|
239
|
+
function createGitHubWorkflow(cwd) {
|
|
240
|
+
const workflowDir = join(cwd, ".github", "workflows");
|
|
241
|
+
mkdirSync(workflowDir, { recursive: true });
|
|
242
|
+
writeFileSync(join(workflowDir, "sept.yml"), getGitHubWorkflow());
|
|
243
|
+
}
|
|
244
|
+
function getGitHubWorkflow() {
|
|
245
|
+
const gh = (expr) => `\${{ ${expr} }}`;
|
|
246
|
+
return `# Spets GitHub Action
|
|
247
|
+
# Runs Claude Code to handle /approve, /revise, /reject commands
|
|
248
|
+
|
|
249
|
+
name: Spets Workflow
|
|
250
|
+
|
|
251
|
+
on:
|
|
252
|
+
issue_comment:
|
|
253
|
+
types: [created]
|
|
254
|
+
|
|
255
|
+
jobs:
|
|
256
|
+
handle-sept-command:
|
|
257
|
+
# Only run if comment contains a sept command
|
|
258
|
+
if: |
|
|
259
|
+
contains(github.event.comment.body, '/approve') ||
|
|
260
|
+
contains(github.event.comment.body, '/revise') ||
|
|
261
|
+
contains(github.event.comment.body, '/reject') ||
|
|
262
|
+
contains(github.event.comment.body, '/answer')
|
|
263
|
+
|
|
264
|
+
runs-on: ubuntu-latest
|
|
265
|
+
|
|
266
|
+
steps:
|
|
267
|
+
- name: Checkout
|
|
268
|
+
uses: actions/checkout@v4
|
|
269
|
+
with:
|
|
270
|
+
fetch-depth: 0
|
|
271
|
+
|
|
272
|
+
- name: Setup Node.js
|
|
273
|
+
uses: actions/setup-node@v4
|
|
274
|
+
with:
|
|
275
|
+
node-version: '20'
|
|
276
|
+
|
|
277
|
+
- name: Install Claude Code
|
|
278
|
+
run: npm install -g @anthropic-ai/claude-code
|
|
279
|
+
|
|
280
|
+
- name: Install dependencies
|
|
281
|
+
run: npm ci
|
|
282
|
+
|
|
283
|
+
- name: Determine context
|
|
284
|
+
id: context
|
|
285
|
+
run: |
|
|
286
|
+
if [ -n "${gh("github.event.issue.pull_request")}" ]; then
|
|
287
|
+
echo "type=pr" >> $GITHUB_OUTPUT
|
|
288
|
+
else
|
|
289
|
+
echo "type=issue" >> $GITHUB_OUTPUT
|
|
290
|
+
fi
|
|
291
|
+
echo "number=${gh("github.event.issue.number")}" >> $GITHUB_OUTPUT
|
|
292
|
+
|
|
293
|
+
- name: Run Claude Code
|
|
294
|
+
run: |
|
|
295
|
+
claude -p "
|
|
296
|
+
You are running a Spets workflow via GitHub Actions.
|
|
297
|
+
|
|
298
|
+
Context:
|
|
299
|
+
- Repository: ${gh("github.repository")}
|
|
300
|
+
- Issue/PR: #${gh("github.event.issue.number")}
|
|
301
|
+
- Comment author: ${gh("github.event.comment.user.login")}
|
|
302
|
+
- Command received: $COMMENT_BODY
|
|
303
|
+
|
|
304
|
+
Instructions:
|
|
305
|
+
1. Parse the command from the comment (/approve, /revise, /reject, or /answer)
|
|
306
|
+
2. Find the active sept task in .sept/outputs/
|
|
307
|
+
3. Execute the appropriate action:
|
|
308
|
+
- /approve: Mark current step as approved, generate next step
|
|
309
|
+
- /revise <feedback>: Regenerate current step with feedback
|
|
310
|
+
- /reject: Mark workflow as rejected
|
|
311
|
+
- /answer: Process answers and continue
|
|
312
|
+
4. If generating content (plan or implementation), actually write the code/changes
|
|
313
|
+
5. Post a summary comment to the PR/Issue using gh cli
|
|
314
|
+
6. Commit any changes with a descriptive message
|
|
315
|
+
|
|
316
|
+
Use 'gh issue comment' or 'gh pr comment' to post updates.
|
|
317
|
+
"
|
|
318
|
+
env:
|
|
319
|
+
COMMENT_BODY: ${gh("github.event.comment.body")}
|
|
320
|
+
ANTHROPIC_API_KEY: ${gh("secrets.ANTHROPIC_API_KEY")}
|
|
321
|
+
GH_TOKEN: ${gh("secrets.GITHUB_TOKEN")}
|
|
322
|
+
|
|
323
|
+
- name: Push changes
|
|
324
|
+
run: |
|
|
325
|
+
git config user.name "github-actions[bot]"
|
|
326
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
327
|
+
git add -A
|
|
328
|
+
git diff --staged --quiet || git commit -m "Sept: Update from workflow"
|
|
329
|
+
git push
|
|
330
|
+
env:
|
|
331
|
+
GH_TOKEN: ${gh("secrets.GITHUB_TOKEN")}
|
|
332
|
+
`;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// src/commands/status.ts
|
|
336
|
+
async function statusCommand(options) {
|
|
337
|
+
const cwd = process.cwd();
|
|
338
|
+
if (!septExists(cwd)) {
|
|
339
|
+
console.error('Spets not initialized. Run "spets init" first.');
|
|
340
|
+
process.exit(1);
|
|
341
|
+
}
|
|
342
|
+
const config = loadConfig(cwd);
|
|
343
|
+
if (options.task) {
|
|
344
|
+
showTaskStatus(options.task, config, cwd);
|
|
345
|
+
} else {
|
|
346
|
+
showAllTasks(config, cwd);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
function showTaskStatus(taskId, config, cwd) {
|
|
350
|
+
const state = getWorkflowState(taskId, config, cwd);
|
|
351
|
+
const meta = loadTaskMetadata(taskId, cwd);
|
|
352
|
+
if (!state) {
|
|
353
|
+
console.error(`Task '${taskId}' not found.`);
|
|
354
|
+
process.exit(1);
|
|
355
|
+
}
|
|
356
|
+
console.log(`Task: ${taskId}`);
|
|
357
|
+
console.log(`Query: ${meta?.userQuery || state.userQuery || "(unknown)"}`);
|
|
358
|
+
console.log(`Status: ${formatStatus(state.status)}`);
|
|
359
|
+
console.log(`Current Step: ${state.currentStepName} (${state.currentStepIndex + 1}/${config.steps.length})`);
|
|
360
|
+
console.log("");
|
|
361
|
+
console.log("Steps:");
|
|
362
|
+
for (let i = 0; i < config.steps.length; i++) {
|
|
363
|
+
const stepName = config.steps[i];
|
|
364
|
+
const output = state.outputs.get(stepName);
|
|
365
|
+
const isCurrent = i === state.currentStepIndex;
|
|
366
|
+
const marker = isCurrent ? "\u2192" : " ";
|
|
367
|
+
const status = output ? formatDocStatus(output.status) : "\u25CB";
|
|
368
|
+
console.log(` ${marker} ${i + 1}. ${stepName} ${status}`);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
function showAllTasks(config, cwd) {
|
|
372
|
+
const tasks = listTasks(cwd);
|
|
373
|
+
if (tasks.length === 0) {
|
|
374
|
+
console.log("No tasks found.");
|
|
375
|
+
console.log("");
|
|
376
|
+
console.log('Start a new task with: sept start "your task description"');
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
console.log("Tasks:");
|
|
380
|
+
console.log("");
|
|
381
|
+
for (const taskId of tasks.slice(0, 10)) {
|
|
382
|
+
const state = getWorkflowState(taskId, config, cwd);
|
|
383
|
+
const meta = loadTaskMetadata(taskId, cwd);
|
|
384
|
+
if (!state) continue;
|
|
385
|
+
const query = meta?.userQuery || state.userQuery || "(no query)";
|
|
386
|
+
const truncatedQuery = query.length > 50 ? query.substring(0, 47) + "..." : query;
|
|
387
|
+
const progress = `${state.currentStepIndex + 1}/${config.steps.length}`;
|
|
388
|
+
console.log(` ${taskId} ${formatStatus(state.status)} [${progress}]`);
|
|
389
|
+
console.log(` ${truncatedQuery}`);
|
|
390
|
+
console.log("");
|
|
391
|
+
}
|
|
392
|
+
if (tasks.length > 10) {
|
|
393
|
+
console.log(` ... and ${tasks.length - 10} more tasks`);
|
|
394
|
+
}
|
|
395
|
+
console.log("");
|
|
396
|
+
console.log('Use "sept status -t <taskId>" for details');
|
|
397
|
+
}
|
|
398
|
+
function formatStatus(status) {
|
|
399
|
+
const icons = {
|
|
400
|
+
in_progress: "\u{1F504} in_progress",
|
|
401
|
+
completed: "\u2705 completed",
|
|
402
|
+
paused: "\u23F8\uFE0F paused",
|
|
403
|
+
rejected: "\u274C rejected"
|
|
404
|
+
};
|
|
405
|
+
return icons[status] || status;
|
|
406
|
+
}
|
|
407
|
+
function formatDocStatus(status) {
|
|
408
|
+
const icons = {
|
|
409
|
+
draft: "\u25D0",
|
|
410
|
+
approved: "\u25CF",
|
|
411
|
+
rejected: "\u2717"
|
|
412
|
+
};
|
|
413
|
+
return icons[status] || "\u25CB";
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// src/hooks/runner.ts
|
|
417
|
+
import { spawn } from "child_process";
|
|
418
|
+
import { existsSync as existsSync2 } from "fs";
|
|
419
|
+
import { join as join2, isAbsolute } from "path";
|
|
420
|
+
async function runHook(hookPath, context, cwd = process.cwd()) {
|
|
421
|
+
const resolvedPath = isAbsolute(hookPath) ? hookPath : join2(getSeptDir(cwd), hookPath);
|
|
422
|
+
if (!existsSync2(resolvedPath)) {
|
|
423
|
+
console.warn(`Hook not found: ${resolvedPath}`);
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
const env = {
|
|
427
|
+
...process.env,
|
|
428
|
+
SEPT_TASK_ID: context.taskId,
|
|
429
|
+
SEPT_STEP_NAME: context.stepName,
|
|
430
|
+
SEPT_STEP_INDEX: String(context.stepIndex),
|
|
431
|
+
SEPT_OUTPUT_PATH: context.outputPath,
|
|
432
|
+
SEPT_CWD: cwd
|
|
433
|
+
};
|
|
434
|
+
return new Promise((resolve, reject) => {
|
|
435
|
+
const proc = spawn(resolvedPath, [], {
|
|
436
|
+
cwd,
|
|
437
|
+
env,
|
|
438
|
+
stdio: "inherit",
|
|
439
|
+
shell: true
|
|
440
|
+
});
|
|
441
|
+
proc.on("close", (code) => {
|
|
442
|
+
if (code !== 0) {
|
|
443
|
+
console.warn(`Hook exited with code ${code}: ${resolvedPath}`);
|
|
444
|
+
}
|
|
445
|
+
resolve();
|
|
446
|
+
});
|
|
447
|
+
proc.on("error", (err) => {
|
|
448
|
+
console.warn(`Failed to run hook: ${err.message}`);
|
|
449
|
+
resolve();
|
|
450
|
+
});
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// src/core/executor.ts
|
|
455
|
+
var Executor = class {
|
|
456
|
+
platform;
|
|
457
|
+
config;
|
|
458
|
+
cwd;
|
|
459
|
+
constructor(options) {
|
|
460
|
+
this.platform = options.platform;
|
|
461
|
+
this.config = options.config;
|
|
462
|
+
this.cwd = options.cwd || process.cwd();
|
|
463
|
+
}
|
|
464
|
+
async executeWorkflow(taskId, userQuery, startIndex = 0) {
|
|
465
|
+
let previousOutput;
|
|
466
|
+
if (startIndex > 0) {
|
|
467
|
+
const prevStepName = this.config.steps[startIndex - 1];
|
|
468
|
+
const prevDoc = loadDocument(taskId, prevStepName, this.cwd);
|
|
469
|
+
if (prevDoc) {
|
|
470
|
+
previousOutput = prevDoc.content;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
for (let i = startIndex; i < this.config.steps.length; i++) {
|
|
474
|
+
const stepName = this.config.steps[i];
|
|
475
|
+
const stepDef = loadStepDefinition(stepName, this.cwd);
|
|
476
|
+
const context = {
|
|
477
|
+
taskId,
|
|
478
|
+
userQuery,
|
|
479
|
+
stepName,
|
|
480
|
+
stepIndex: i,
|
|
481
|
+
instruction: stepDef.instruction,
|
|
482
|
+
template: stepDef.template,
|
|
483
|
+
previousOutput,
|
|
484
|
+
outputPath: getOutputPath(taskId, stepName, this.cwd)
|
|
485
|
+
};
|
|
486
|
+
if (this.config.hooks?.preStep) {
|
|
487
|
+
await runHook(this.config.hooks.preStep, {
|
|
488
|
+
taskId,
|
|
489
|
+
stepName,
|
|
490
|
+
stepIndex: i,
|
|
491
|
+
outputPath: context.outputPath,
|
|
492
|
+
document: ""
|
|
493
|
+
}, this.cwd);
|
|
494
|
+
}
|
|
495
|
+
const result = await this.executeStep(context);
|
|
496
|
+
if (this.config.hooks?.postStep) {
|
|
497
|
+
await runHook(this.config.hooks.postStep, {
|
|
498
|
+
taskId,
|
|
499
|
+
stepName,
|
|
500
|
+
stepIndex: i,
|
|
501
|
+
outputPath: context.outputPath,
|
|
502
|
+
document: result.document
|
|
503
|
+
}, this.cwd);
|
|
504
|
+
}
|
|
505
|
+
if (result.rejected) {
|
|
506
|
+
console.log("\n\u274C Workflow rejected.");
|
|
507
|
+
if (this.config.hooks?.onReject) {
|
|
508
|
+
await runHook(this.config.hooks.onReject, {
|
|
509
|
+
taskId,
|
|
510
|
+
stepName,
|
|
511
|
+
stepIndex: i,
|
|
512
|
+
outputPath: context.outputPath,
|
|
513
|
+
document: result.document
|
|
514
|
+
}, this.cwd);
|
|
515
|
+
}
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
if (result.paused) {
|
|
519
|
+
console.log("\n\u23F8\uFE0F Workflow paused. Resume with: sept resume");
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
if (result.approved) {
|
|
523
|
+
if (this.config.hooks?.onApprove) {
|
|
524
|
+
await runHook(this.config.hooks.onApprove, {
|
|
525
|
+
taskId,
|
|
526
|
+
stepName,
|
|
527
|
+
stepIndex: i,
|
|
528
|
+
outputPath: context.outputPath,
|
|
529
|
+
document: result.document
|
|
530
|
+
}, this.cwd);
|
|
531
|
+
}
|
|
532
|
+
previousOutput = result.document;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
console.log("\n\u2705 Workflow completed!");
|
|
536
|
+
if (this.config.hooks?.onComplete) {
|
|
537
|
+
await runHook(this.config.hooks.onComplete, {
|
|
538
|
+
taskId,
|
|
539
|
+
stepName: this.config.steps[this.config.steps.length - 1],
|
|
540
|
+
stepIndex: this.config.steps.length - 1,
|
|
541
|
+
outputPath: "",
|
|
542
|
+
document: previousOutput || ""
|
|
543
|
+
}, this.cwd);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
async executeStep(context) {
|
|
547
|
+
let result = await this.platform.generateDocument(context);
|
|
548
|
+
saveDocument(
|
|
549
|
+
context.taskId,
|
|
550
|
+
context.stepName,
|
|
551
|
+
result.document,
|
|
552
|
+
"draft",
|
|
553
|
+
result.questions,
|
|
554
|
+
this.cwd
|
|
555
|
+
);
|
|
556
|
+
while (result.questions.length > 0) {
|
|
557
|
+
const answers = await this.platform.askUser(result.questions);
|
|
558
|
+
result = await this.platform.generateDocument({
|
|
559
|
+
...context,
|
|
560
|
+
answers
|
|
561
|
+
});
|
|
562
|
+
saveDocument(
|
|
563
|
+
context.taskId,
|
|
564
|
+
context.stepName,
|
|
565
|
+
result.document,
|
|
566
|
+
"draft",
|
|
567
|
+
result.questions,
|
|
568
|
+
this.cwd
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
const approval = await this.platform.requestApproval(result.document, context.stepName);
|
|
572
|
+
if (approval.action === "revise") {
|
|
573
|
+
return this.executeStep({
|
|
574
|
+
...context,
|
|
575
|
+
feedback: approval.feedback
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
const outputPath = getOutputPath(context.taskId, context.stepName, this.cwd);
|
|
579
|
+
if (approval.action === "approve") {
|
|
580
|
+
updateDocumentStatus(outputPath, "approved");
|
|
581
|
+
} else if (approval.action === "reject") {
|
|
582
|
+
updateDocumentStatus(outputPath, "rejected");
|
|
583
|
+
}
|
|
584
|
+
return {
|
|
585
|
+
document: result.document,
|
|
586
|
+
approved: approval.action === "approve",
|
|
587
|
+
rejected: approval.action === "reject",
|
|
588
|
+
paused: approval.action === "pause"
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
};
|
|
592
|
+
|
|
593
|
+
// src/platform/cli.ts
|
|
594
|
+
import { spawn as spawn2 } from "child_process";
|
|
595
|
+
import { input, select, confirm } from "@inquirer/prompts";
|
|
596
|
+
|
|
597
|
+
// src/platform/interface.ts
|
|
598
|
+
var BasePlatform = class {
|
|
599
|
+
};
|
|
600
|
+
|
|
601
|
+
// src/platform/cli.ts
|
|
602
|
+
var CliPlatform = class extends BasePlatform {
|
|
603
|
+
claudeCommand;
|
|
604
|
+
constructor(claudeCommand = "claude") {
|
|
605
|
+
super();
|
|
606
|
+
this.claudeCommand = claudeCommand;
|
|
607
|
+
}
|
|
608
|
+
async generateDocument(context) {
|
|
609
|
+
const prompt = this.buildPrompt(context);
|
|
610
|
+
console.log(`
|
|
611
|
+
\u{1F4DD} Generating ${context.stepName}...`);
|
|
612
|
+
const response = await this.callClaude(prompt);
|
|
613
|
+
const { document, questions } = this.parseResponse(response);
|
|
614
|
+
return { document, questions };
|
|
615
|
+
}
|
|
616
|
+
async askUser(questions) {
|
|
617
|
+
const answers = [];
|
|
618
|
+
console.log("\n\u2753 Please answer the following questions:\n");
|
|
619
|
+
for (const q of questions) {
|
|
620
|
+
if (q.context) {
|
|
621
|
+
console.log(`Context: ${q.context}`);
|
|
622
|
+
}
|
|
623
|
+
const answer = await input({
|
|
624
|
+
message: q.question
|
|
625
|
+
});
|
|
626
|
+
answers.push({
|
|
627
|
+
questionId: q.id,
|
|
628
|
+
answer
|
|
629
|
+
});
|
|
630
|
+
console.log("");
|
|
631
|
+
}
|
|
632
|
+
return answers;
|
|
633
|
+
}
|
|
634
|
+
async requestApproval(doc, stepName) {
|
|
635
|
+
console.log("\n" + "=".repeat(60));
|
|
636
|
+
console.log(`\u{1F4C4} ${stepName} Document`);
|
|
637
|
+
console.log("=".repeat(60));
|
|
638
|
+
console.log(doc);
|
|
639
|
+
console.log("=".repeat(60) + "\n");
|
|
640
|
+
const action = await select({
|
|
641
|
+
message: "What would you like to do?",
|
|
642
|
+
choices: [
|
|
643
|
+
{ value: "approve", name: "\u2705 Approve - Continue to next step" },
|
|
644
|
+
{ value: "revise", name: "\u270F\uFE0F Revise - Request changes" },
|
|
645
|
+
{ value: "reject", name: "\u274C Reject - Stop workflow" },
|
|
646
|
+
{ value: "pause", name: "\u23F8\uFE0F Pause - Save and exit" }
|
|
647
|
+
]
|
|
648
|
+
});
|
|
649
|
+
if (action === "revise") {
|
|
650
|
+
const feedback = await input({
|
|
651
|
+
message: "What changes would you like?"
|
|
652
|
+
});
|
|
653
|
+
return { action: "revise", feedback };
|
|
654
|
+
}
|
|
655
|
+
if (action === "reject") {
|
|
656
|
+
const confirmed = await confirm({
|
|
657
|
+
message: "Are you sure you want to reject? This will stop the workflow.",
|
|
658
|
+
default: false
|
|
659
|
+
});
|
|
660
|
+
if (!confirmed) {
|
|
661
|
+
return this.requestApproval(doc, stepName);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
return { action };
|
|
665
|
+
}
|
|
666
|
+
buildPrompt(context) {
|
|
667
|
+
const parts = [];
|
|
668
|
+
parts.push("# Task");
|
|
669
|
+
parts.push(`You are executing step "${context.stepName}" of a spec-driven development workflow.`);
|
|
670
|
+
parts.push("");
|
|
671
|
+
parts.push("## User Query");
|
|
672
|
+
parts.push(context.userQuery);
|
|
673
|
+
parts.push("");
|
|
674
|
+
parts.push("## Step Instructions");
|
|
675
|
+
parts.push(context.instruction);
|
|
676
|
+
parts.push("");
|
|
677
|
+
if (context.template) {
|
|
678
|
+
parts.push("## Output Template");
|
|
679
|
+
parts.push("Follow this template structure:");
|
|
680
|
+
parts.push(context.template);
|
|
681
|
+
parts.push("");
|
|
682
|
+
}
|
|
683
|
+
if (context.previousOutput) {
|
|
684
|
+
parts.push("## Previous Step Output");
|
|
685
|
+
parts.push(context.previousOutput);
|
|
686
|
+
parts.push("");
|
|
687
|
+
}
|
|
688
|
+
if (context.answers && context.answers.length > 0) {
|
|
689
|
+
parts.push("## Answers to Questions");
|
|
690
|
+
for (const a of context.answers) {
|
|
691
|
+
parts.push(`- ${a.questionId}: ${a.answer}`);
|
|
692
|
+
}
|
|
693
|
+
parts.push("");
|
|
694
|
+
}
|
|
695
|
+
if (context.feedback) {
|
|
696
|
+
parts.push("## Revision Feedback");
|
|
697
|
+
parts.push(context.feedback);
|
|
698
|
+
parts.push("");
|
|
699
|
+
}
|
|
700
|
+
parts.push("## Output Format");
|
|
701
|
+
parts.push("Generate the document content.");
|
|
702
|
+
parts.push("If you have clarifying questions, include them at the end in this format:");
|
|
703
|
+
parts.push("```questions");
|
|
704
|
+
parts.push("Q1: [Your question here]");
|
|
705
|
+
parts.push("Q2: [Another question if needed]");
|
|
706
|
+
parts.push("```");
|
|
707
|
+
parts.push("");
|
|
708
|
+
parts.push("Only ask questions if absolutely necessary for the task.");
|
|
709
|
+
return parts.join("\n");
|
|
710
|
+
}
|
|
711
|
+
async callClaude(prompt) {
|
|
712
|
+
return new Promise((resolve, reject) => {
|
|
713
|
+
const proc = spawn2(this.claudeCommand, ["-p", prompt], {
|
|
714
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
715
|
+
});
|
|
716
|
+
let stdout = "";
|
|
717
|
+
let stderr = "";
|
|
718
|
+
proc.stdout.on("data", (data) => {
|
|
719
|
+
stdout += data.toString();
|
|
720
|
+
process.stdout.write(data);
|
|
721
|
+
});
|
|
722
|
+
proc.stderr.on("data", (data) => {
|
|
723
|
+
stderr += data.toString();
|
|
724
|
+
});
|
|
725
|
+
proc.on("close", (code) => {
|
|
726
|
+
if (code !== 0) {
|
|
727
|
+
reject(new Error(`Claude CLI exited with code ${code}: ${stderr}`));
|
|
728
|
+
} else {
|
|
729
|
+
resolve(stdout);
|
|
730
|
+
}
|
|
731
|
+
});
|
|
732
|
+
proc.on("error", (err) => {
|
|
733
|
+
reject(new Error(`Failed to run Claude CLI: ${err.message}`));
|
|
734
|
+
});
|
|
735
|
+
});
|
|
736
|
+
}
|
|
737
|
+
parseResponse(response) {
|
|
738
|
+
const questions = [];
|
|
739
|
+
const questionMatch = response.match(/```questions\n([\s\S]*?)```/);
|
|
740
|
+
if (questionMatch) {
|
|
741
|
+
const questionsText = questionMatch[1];
|
|
742
|
+
const questionLines = questionsText.split("\n").filter((l) => l.trim());
|
|
743
|
+
for (const line of questionLines) {
|
|
744
|
+
const match = line.match(/^Q(\d+):\s*(.+)$/);
|
|
745
|
+
if (match) {
|
|
746
|
+
questions.push({
|
|
747
|
+
id: `q${match[1]}`,
|
|
748
|
+
question: match[2].trim()
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
const document = response.replace(/```questions\n[\s\S]*?```\n?/, "").trim();
|
|
754
|
+
return { document, questions };
|
|
755
|
+
}
|
|
756
|
+
};
|
|
757
|
+
|
|
758
|
+
// src/platform/github.ts
|
|
759
|
+
import { spawn as spawn3 } from "child_process";
|
|
760
|
+
var PauseForInputError = class extends Error {
|
|
761
|
+
constructor(inputType, data) {
|
|
762
|
+
super(`Paused waiting for ${inputType}`);
|
|
763
|
+
this.inputType = inputType;
|
|
764
|
+
this.data = data;
|
|
765
|
+
this.name = "PauseForInputError";
|
|
766
|
+
}
|
|
767
|
+
};
|
|
768
|
+
function parseGitHubCommand(comment) {
|
|
769
|
+
const trimmed = comment.trim();
|
|
770
|
+
if (trimmed === "/approve") {
|
|
771
|
+
return { command: "approve" };
|
|
772
|
+
}
|
|
773
|
+
if (trimmed === "/reject") {
|
|
774
|
+
return { command: "reject" };
|
|
775
|
+
}
|
|
776
|
+
const reviseMatch = trimmed.match(/^\/revise\s+(.+)$/s);
|
|
777
|
+
if (reviseMatch) {
|
|
778
|
+
return { command: "revise", feedback: reviseMatch[1].trim() };
|
|
779
|
+
}
|
|
780
|
+
const answerMatch = trimmed.match(/^\/answer\s*\n([\s\S]+)$/m);
|
|
781
|
+
if (answerMatch) {
|
|
782
|
+
const answersText = answerMatch[1];
|
|
783
|
+
const answers = {};
|
|
784
|
+
const answerLines = answersText.split("\n");
|
|
785
|
+
for (const line of answerLines) {
|
|
786
|
+
const match = line.match(/^(Q\d+):\s*(.+)$/i);
|
|
787
|
+
if (match) {
|
|
788
|
+
answers[match[1].toLowerCase()] = match[2].trim();
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
return { command: "answer", answers };
|
|
792
|
+
}
|
|
793
|
+
return { command: null };
|
|
794
|
+
}
|
|
795
|
+
var GitHubPlatform = class extends BasePlatform {
|
|
796
|
+
config;
|
|
797
|
+
currentTaskId;
|
|
798
|
+
constructor(config) {
|
|
799
|
+
super();
|
|
800
|
+
this.config = config;
|
|
801
|
+
}
|
|
802
|
+
setTaskId(taskId) {
|
|
803
|
+
this.currentTaskId = taskId;
|
|
804
|
+
}
|
|
805
|
+
async generateDocument(context) {
|
|
806
|
+
this.currentTaskId = context.taskId;
|
|
807
|
+
const prompt = this.buildPrompt(context);
|
|
808
|
+
const response = await this.callClaude(prompt);
|
|
809
|
+
const { document, questions } = this.parseResponse(response);
|
|
810
|
+
return { document, questions };
|
|
811
|
+
}
|
|
812
|
+
async askUser(questions) {
|
|
813
|
+
const comment = this.formatQuestionsComment(questions, this.currentTaskId);
|
|
814
|
+
await this.postComment(comment);
|
|
815
|
+
console.log("\n\u23F8\uFE0F Waiting for answers on GitHub...");
|
|
816
|
+
console.log(" The workflow will resume when questions are answered.");
|
|
817
|
+
throw new PauseForInputError("questions", questions);
|
|
818
|
+
}
|
|
819
|
+
async requestApproval(doc, stepName) {
|
|
820
|
+
const comment = this.formatApprovalComment(doc, stepName, this.currentTaskId);
|
|
821
|
+
await this.postComment(comment);
|
|
822
|
+
console.log("\n\u23F8\uFE0F Waiting for approval on GitHub...");
|
|
823
|
+
console.log(" Comment /approve, /revise <feedback>, or /reject on the PR/Issue.");
|
|
824
|
+
return { action: "pause" };
|
|
825
|
+
}
|
|
826
|
+
async postComment(body) {
|
|
827
|
+
if (this.config.prNumber) {
|
|
828
|
+
await this.postPRComment(body);
|
|
829
|
+
} else if (this.config.issueNumber) {
|
|
830
|
+
await this.postIssueComment(body);
|
|
831
|
+
} else {
|
|
832
|
+
throw new Error("Either prNumber or issueNumber must be specified");
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
formatQuestionsComment(questions, taskId) {
|
|
836
|
+
const lines = [
|
|
837
|
+
"## \u{1F4CB} Sept: Questions Need Answers",
|
|
838
|
+
"",
|
|
839
|
+
`> Task ID: \`${taskId}\``,
|
|
840
|
+
"",
|
|
841
|
+
"Please answer the following questions:",
|
|
842
|
+
""
|
|
843
|
+
];
|
|
844
|
+
for (let i = 0; i < questions.length; i++) {
|
|
845
|
+
const q = questions[i];
|
|
846
|
+
lines.push(`### Q${i + 1}: ${q.question}`);
|
|
847
|
+
if (q.context) {
|
|
848
|
+
lines.push(`> ${q.context}`);
|
|
849
|
+
}
|
|
850
|
+
lines.push("");
|
|
851
|
+
}
|
|
852
|
+
lines.push("---");
|
|
853
|
+
lines.push("**Reply with:**");
|
|
854
|
+
lines.push("```");
|
|
855
|
+
lines.push("/answer");
|
|
856
|
+
for (let i = 0; i < questions.length; i++) {
|
|
857
|
+
lines.push(`Q${i + 1}: your answer here`);
|
|
858
|
+
}
|
|
859
|
+
lines.push("```");
|
|
860
|
+
return lines.join("\n");
|
|
861
|
+
}
|
|
862
|
+
formatApprovalComment(doc, stepName, taskId) {
|
|
863
|
+
const lines = [
|
|
864
|
+
`## \u{1F4C4} Sept: ${stepName} - Review Required`,
|
|
865
|
+
"",
|
|
866
|
+
`> Task ID: \`${taskId}\``,
|
|
867
|
+
"",
|
|
868
|
+
"<details>",
|
|
869
|
+
"<summary>\u{1F4DD} View Document</summary>",
|
|
870
|
+
"",
|
|
871
|
+
"```markdown",
|
|
872
|
+
doc,
|
|
873
|
+
"```",
|
|
874
|
+
"",
|
|
875
|
+
"</details>",
|
|
876
|
+
"",
|
|
877
|
+
"---",
|
|
878
|
+
"",
|
|
879
|
+
"**Commands:**",
|
|
880
|
+
"| Command | Description |",
|
|
881
|
+
"|---------|-------------|",
|
|
882
|
+
"| `/approve` | Approve and continue to next step |",
|
|
883
|
+
"| `/revise <feedback>` | Request changes with feedback |",
|
|
884
|
+
"| `/reject` | Reject and stop workflow |"
|
|
885
|
+
];
|
|
886
|
+
return lines.join("\n");
|
|
887
|
+
}
|
|
888
|
+
async postPRComment(body) {
|
|
889
|
+
const { owner, repo, prNumber } = this.config;
|
|
890
|
+
await this.runGh(["pr", "comment", String(prNumber), "--body", body, "-R", `${owner}/${repo}`]);
|
|
891
|
+
}
|
|
892
|
+
async postIssueComment(body) {
|
|
893
|
+
const { owner, repo, issueNumber } = this.config;
|
|
894
|
+
await this.runGh(["issue", "comment", String(issueNumber), "--body", body, "-R", `${owner}/${repo}`]);
|
|
895
|
+
}
|
|
896
|
+
async runGh(args) {
|
|
897
|
+
return new Promise((resolve, reject) => {
|
|
898
|
+
const proc = spawn3("gh", args, { stdio: ["pipe", "pipe", "pipe"] });
|
|
899
|
+
let stdout = "";
|
|
900
|
+
let stderr = "";
|
|
901
|
+
proc.stdout.on("data", (data) => {
|
|
902
|
+
stdout += data.toString();
|
|
903
|
+
});
|
|
904
|
+
proc.stderr.on("data", (data) => {
|
|
905
|
+
stderr += data.toString();
|
|
906
|
+
});
|
|
907
|
+
proc.on("close", (code) => {
|
|
908
|
+
if (code !== 0) {
|
|
909
|
+
reject(new Error(`gh command failed: ${stderr}`));
|
|
910
|
+
} else {
|
|
911
|
+
resolve(stdout);
|
|
912
|
+
}
|
|
913
|
+
});
|
|
914
|
+
proc.on("error", (err) => {
|
|
915
|
+
reject(new Error(`Failed to run gh: ${err.message}`));
|
|
916
|
+
});
|
|
917
|
+
});
|
|
918
|
+
}
|
|
919
|
+
buildPrompt(context) {
|
|
920
|
+
const parts = [];
|
|
921
|
+
parts.push("# Task");
|
|
922
|
+
parts.push(`You are executing step "${context.stepName}" of a spec-driven development workflow.`);
|
|
923
|
+
parts.push("");
|
|
924
|
+
parts.push("## User Query");
|
|
925
|
+
parts.push(context.userQuery);
|
|
926
|
+
parts.push("");
|
|
927
|
+
parts.push("## Step Instructions");
|
|
928
|
+
parts.push(context.instruction);
|
|
929
|
+
if (context.template) {
|
|
930
|
+
parts.push("");
|
|
931
|
+
parts.push("## Output Template");
|
|
932
|
+
parts.push(context.template);
|
|
933
|
+
}
|
|
934
|
+
if (context.previousOutput) {
|
|
935
|
+
parts.push("");
|
|
936
|
+
parts.push("## Previous Step Output");
|
|
937
|
+
parts.push(context.previousOutput);
|
|
938
|
+
}
|
|
939
|
+
if (context.feedback) {
|
|
940
|
+
parts.push("");
|
|
941
|
+
parts.push("## Revision Feedback");
|
|
942
|
+
parts.push(context.feedback);
|
|
943
|
+
}
|
|
944
|
+
return parts.join("\n");
|
|
945
|
+
}
|
|
946
|
+
async callClaude(prompt) {
|
|
947
|
+
return new Promise((resolve, reject) => {
|
|
948
|
+
const proc = spawn3("claude", ["-p", prompt], { stdio: ["pipe", "pipe", "pipe"] });
|
|
949
|
+
let stdout = "";
|
|
950
|
+
let stderr = "";
|
|
951
|
+
proc.stdout.on("data", (data) => {
|
|
952
|
+
stdout += data.toString();
|
|
953
|
+
});
|
|
954
|
+
proc.stderr.on("data", (data) => {
|
|
955
|
+
stderr += data.toString();
|
|
956
|
+
});
|
|
957
|
+
proc.on("close", (code) => {
|
|
958
|
+
if (code !== 0) {
|
|
959
|
+
reject(new Error(`Claude CLI failed: ${stderr}`));
|
|
960
|
+
} else {
|
|
961
|
+
resolve(stdout);
|
|
962
|
+
}
|
|
963
|
+
});
|
|
964
|
+
proc.on("error", (err) => {
|
|
965
|
+
reject(new Error(`Failed to run Claude: ${err.message}`));
|
|
966
|
+
});
|
|
967
|
+
});
|
|
968
|
+
}
|
|
969
|
+
parseResponse(response) {
|
|
970
|
+
const questions = [];
|
|
971
|
+
const questionMatch = response.match(/```questions\n([\s\S]*?)```/);
|
|
972
|
+
if (questionMatch) {
|
|
973
|
+
const questionsText = questionMatch[1];
|
|
974
|
+
const questionLines = questionsText.split("\n").filter((l) => l.trim());
|
|
975
|
+
for (const line of questionLines) {
|
|
976
|
+
const match = line.match(/^Q(\d+):\s*(.+)$/);
|
|
977
|
+
if (match) {
|
|
978
|
+
questions.push({
|
|
979
|
+
id: `q${match[1]}`,
|
|
980
|
+
question: match[2].trim()
|
|
981
|
+
});
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
const document = response.replace(/```questions\n[\s\S]*?```\n?/, "").trim();
|
|
986
|
+
return { document, questions };
|
|
987
|
+
}
|
|
988
|
+
};
|
|
989
|
+
|
|
990
|
+
// src/commands/start.ts
|
|
991
|
+
async function startCommand(query, options) {
|
|
992
|
+
const cwd = process.cwd();
|
|
993
|
+
if (!septExists(cwd)) {
|
|
994
|
+
console.error('Spets not initialized. Run "spets init" first.');
|
|
995
|
+
process.exit(1);
|
|
996
|
+
}
|
|
997
|
+
const config = loadConfig(cwd);
|
|
998
|
+
const taskId = generateTaskId();
|
|
999
|
+
console.log(`Starting new workflow: ${taskId}`);
|
|
1000
|
+
console.log(`Query: ${query}`);
|
|
1001
|
+
console.log(`Platform: ${options.platform || "cli"}`);
|
|
1002
|
+
console.log("");
|
|
1003
|
+
saveTaskMetadata(taskId, query, cwd);
|
|
1004
|
+
let platform;
|
|
1005
|
+
if (options.platform === "github") {
|
|
1006
|
+
if (!options.owner || !options.repo) {
|
|
1007
|
+
console.error("GitHub platform requires --owner and --repo");
|
|
1008
|
+
process.exit(1);
|
|
1009
|
+
}
|
|
1010
|
+
if (!options.pr && !options.issue) {
|
|
1011
|
+
console.error("GitHub platform requires --pr or --issue");
|
|
1012
|
+
process.exit(1);
|
|
1013
|
+
}
|
|
1014
|
+
platform = new GitHubPlatform({
|
|
1015
|
+
owner: options.owner,
|
|
1016
|
+
repo: options.repo,
|
|
1017
|
+
prNumber: options.pr ? parseInt(options.pr, 10) : void 0,
|
|
1018
|
+
issueNumber: options.issue ? parseInt(options.issue, 10) : void 0
|
|
1019
|
+
});
|
|
1020
|
+
} else {
|
|
1021
|
+
platform = new CliPlatform();
|
|
1022
|
+
}
|
|
1023
|
+
const executor = new Executor({ platform, config, cwd });
|
|
1024
|
+
try {
|
|
1025
|
+
await executor.executeWorkflow(taskId, query);
|
|
1026
|
+
} catch (error) {
|
|
1027
|
+
if (error instanceof PauseForInputError) {
|
|
1028
|
+
console.log("\nWorkflow paused. Waiting for GitHub input.");
|
|
1029
|
+
console.log(`Task ID: ${taskId}`);
|
|
1030
|
+
return;
|
|
1031
|
+
}
|
|
1032
|
+
console.error("Workflow failed:", error instanceof Error ? error.message : error);
|
|
1033
|
+
process.exit(1);
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
// src/commands/resume.ts
|
|
1038
|
+
import { select as select2 } from "@inquirer/prompts";
|
|
1039
|
+
async function resumeCommand(options) {
|
|
1040
|
+
const cwd = process.cwd();
|
|
1041
|
+
if (!septExists(cwd)) {
|
|
1042
|
+
console.error('Spets not initialized. Run "spets init" first.');
|
|
1043
|
+
process.exit(1);
|
|
1044
|
+
}
|
|
1045
|
+
const config = loadConfig(cwd);
|
|
1046
|
+
let taskId = options.task;
|
|
1047
|
+
if (!taskId) {
|
|
1048
|
+
const tasks = listTasks(cwd);
|
|
1049
|
+
const resumableTasks = [];
|
|
1050
|
+
for (const tid of tasks) {
|
|
1051
|
+
const state2 = getWorkflowState(tid, config, cwd);
|
|
1052
|
+
const meta2 = loadTaskMetadata(tid, cwd);
|
|
1053
|
+
if (state2 && (state2.status === "in_progress" || state2.status === "paused")) {
|
|
1054
|
+
resumableTasks.push({
|
|
1055
|
+
taskId: tid,
|
|
1056
|
+
query: meta2?.userQuery || state2.userQuery || "(unknown)",
|
|
1057
|
+
status: state2.status
|
|
1058
|
+
});
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
if (resumableTasks.length === 0) {
|
|
1062
|
+
console.log("No tasks to resume.");
|
|
1063
|
+
console.log('Start a new task with: sept start "your task description"');
|
|
1064
|
+
return;
|
|
1065
|
+
}
|
|
1066
|
+
if (resumableTasks.length === 1) {
|
|
1067
|
+
taskId = resumableTasks[0].taskId;
|
|
1068
|
+
} else {
|
|
1069
|
+
taskId = await select2({
|
|
1070
|
+
message: "Select task to resume:",
|
|
1071
|
+
choices: resumableTasks.map((t) => ({
|
|
1072
|
+
value: t.taskId,
|
|
1073
|
+
name: `${t.taskId} [${t.status}] - ${t.query.substring(0, 50)}${t.query.length > 50 ? "..." : ""}`
|
|
1074
|
+
}))
|
|
1075
|
+
});
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
const state = getWorkflowState(taskId, config, cwd);
|
|
1079
|
+
const meta = loadTaskMetadata(taskId, cwd);
|
|
1080
|
+
if (!state) {
|
|
1081
|
+
console.error(`Task '${taskId}' not found.`);
|
|
1082
|
+
process.exit(1);
|
|
1083
|
+
}
|
|
1084
|
+
if (state.status === "completed") {
|
|
1085
|
+
console.log("Task already completed.");
|
|
1086
|
+
return;
|
|
1087
|
+
}
|
|
1088
|
+
if (state.status === "rejected") {
|
|
1089
|
+
console.log("Task was rejected. Cannot resume.");
|
|
1090
|
+
return;
|
|
1091
|
+
}
|
|
1092
|
+
const userQuery = meta?.userQuery || state.userQuery || "";
|
|
1093
|
+
console.log(`Resuming task: ${taskId}`);
|
|
1094
|
+
console.log(`Query: ${userQuery}`);
|
|
1095
|
+
console.log(`Current step: ${state.currentStepName}`);
|
|
1096
|
+
console.log("");
|
|
1097
|
+
if (options.approve) {
|
|
1098
|
+
const outputPath = getOutputPath(taskId, state.currentStepName, cwd);
|
|
1099
|
+
updateDocumentStatus(outputPath, "approved");
|
|
1100
|
+
console.log(`Approved: ${state.currentStepName}`);
|
|
1101
|
+
const nextIndex = state.currentStepIndex + 1;
|
|
1102
|
+
if (nextIndex >= config.steps.length) {
|
|
1103
|
+
console.log("\n\u2705 Workflow completed!");
|
|
1104
|
+
return;
|
|
1105
|
+
}
|
|
1106
|
+
const platform2 = new CliPlatform();
|
|
1107
|
+
const executor2 = new Executor({ platform: platform2, config, cwd });
|
|
1108
|
+
await executor2.executeWorkflow(taskId, userQuery, nextIndex);
|
|
1109
|
+
return;
|
|
1110
|
+
}
|
|
1111
|
+
if (options.revise) {
|
|
1112
|
+
const platform2 = new CliPlatform();
|
|
1113
|
+
const executor2 = new Executor({ platform: platform2, config, cwd });
|
|
1114
|
+
console.log(`Revising with feedback: ${options.revise}`);
|
|
1115
|
+
await executor2.executeWorkflow(taskId, userQuery, state.currentStepIndex);
|
|
1116
|
+
return;
|
|
1117
|
+
}
|
|
1118
|
+
const platform = new CliPlatform();
|
|
1119
|
+
const executor = new Executor({ platform, config, cwd });
|
|
1120
|
+
await executor.executeWorkflow(taskId, userQuery, state.currentStepIndex);
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
// src/commands/plugin.ts
|
|
1124
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, rmSync } from "fs";
|
|
1125
|
+
import { join as join3 } from "path";
|
|
1126
|
+
import { homedir } from "os";
|
|
1127
|
+
async function pluginCommand(action, name) {
|
|
1128
|
+
switch (action) {
|
|
1129
|
+
case "install":
|
|
1130
|
+
if (!name) {
|
|
1131
|
+
console.error("Plugin name required.");
|
|
1132
|
+
console.error("Usage: spets plugin install <name>");
|
|
1133
|
+
process.exit(1);
|
|
1134
|
+
}
|
|
1135
|
+
await installPlugin(name);
|
|
1136
|
+
break;
|
|
1137
|
+
case "uninstall":
|
|
1138
|
+
if (!name) {
|
|
1139
|
+
console.error("Plugin name required.");
|
|
1140
|
+
console.error("Usage: spets plugin uninstall <name>");
|
|
1141
|
+
process.exit(1);
|
|
1142
|
+
}
|
|
1143
|
+
await uninstallPlugin(name);
|
|
1144
|
+
break;
|
|
1145
|
+
case "list":
|
|
1146
|
+
await listPlugins();
|
|
1147
|
+
break;
|
|
1148
|
+
default:
|
|
1149
|
+
console.error(`Unknown action: ${action}`);
|
|
1150
|
+
console.error("Available actions: install, uninstall, list");
|
|
1151
|
+
process.exit(1);
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
async function installPlugin(name) {
|
|
1155
|
+
const plugins = {
|
|
1156
|
+
claude: installClaudePlugin
|
|
1157
|
+
};
|
|
1158
|
+
const installer = plugins[name];
|
|
1159
|
+
if (!installer) {
|
|
1160
|
+
console.error(`Unknown plugin: ${name}`);
|
|
1161
|
+
console.error("Available plugins: claude");
|
|
1162
|
+
process.exit(1);
|
|
1163
|
+
}
|
|
1164
|
+
installer();
|
|
1165
|
+
}
|
|
1166
|
+
function installClaudePlugin() {
|
|
1167
|
+
const claudeDir = join3(homedir(), ".claude");
|
|
1168
|
+
const commandsDir = join3(claudeDir, "commands");
|
|
1169
|
+
mkdirSync2(commandsDir, { recursive: true });
|
|
1170
|
+
const skillPath = join3(commandsDir, "spets.md");
|
|
1171
|
+
writeFileSync2(skillPath, getClaudeSkillContent());
|
|
1172
|
+
console.log("Installed Claude Code plugin.");
|
|
1173
|
+
console.log(`Location: ${skillPath}`);
|
|
1174
|
+
console.log("");
|
|
1175
|
+
console.log("Usage in Claude Code:");
|
|
1176
|
+
console.log(' /spets start "your task description"');
|
|
1177
|
+
console.log(" /spets status");
|
|
1178
|
+
console.log(" /spets resume");
|
|
1179
|
+
}
|
|
1180
|
+
async function uninstallPlugin(name) {
|
|
1181
|
+
if (name === "claude") {
|
|
1182
|
+
const skillPath = join3(homedir(), ".claude", "commands", "spets.md");
|
|
1183
|
+
if (existsSync3(skillPath)) {
|
|
1184
|
+
rmSync(skillPath);
|
|
1185
|
+
console.log("Uninstalled Claude Code plugin.");
|
|
1186
|
+
} else {
|
|
1187
|
+
console.log("Claude Code plugin not installed.");
|
|
1188
|
+
}
|
|
1189
|
+
return;
|
|
1190
|
+
}
|
|
1191
|
+
console.error(`Unknown plugin: ${name}`);
|
|
1192
|
+
process.exit(1);
|
|
1193
|
+
}
|
|
1194
|
+
async function listPlugins() {
|
|
1195
|
+
console.log("Available plugins:");
|
|
1196
|
+
console.log("");
|
|
1197
|
+
console.log(" claude - Claude Code slash command integration");
|
|
1198
|
+
console.log("");
|
|
1199
|
+
const claudeSkillPath = join3(homedir(), ".claude", "commands", "spets.md");
|
|
1200
|
+
const claudeInstalled = existsSync3(claudeSkillPath);
|
|
1201
|
+
console.log("Installed:");
|
|
1202
|
+
if (claudeInstalled) {
|
|
1203
|
+
console.log(" - claude");
|
|
1204
|
+
} else {
|
|
1205
|
+
console.log(" (none)");
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
function getClaudeSkillContent() {
|
|
1209
|
+
return `# Spets - Spec Driven Development
|
|
1210
|
+
|
|
1211
|
+
Run spets workflows from Claude Code.
|
|
1212
|
+
|
|
1213
|
+
## Usage
|
|
1214
|
+
|
|
1215
|
+
\`\`\`
|
|
1216
|
+
/spets start "task description" - Start a new workflow
|
|
1217
|
+
/spets status - Show workflow status
|
|
1218
|
+
/spets resume - Resume paused workflow
|
|
1219
|
+
\`\`\`
|
|
1220
|
+
|
|
1221
|
+
## Instructions
|
|
1222
|
+
|
|
1223
|
+
When the user invokes this command:
|
|
1224
|
+
|
|
1225
|
+
1. Parse the subcommand (start, status, resume)
|
|
1226
|
+
2. Execute the appropriate spets CLI command using Bash
|
|
1227
|
+
3. For 'start', read the generated documents and help iterate
|
|
1228
|
+
|
|
1229
|
+
### Start Flow
|
|
1230
|
+
|
|
1231
|
+
1. Run: \`spets start "<query>"\`
|
|
1232
|
+
2. Read the generated plan document from .sept/outputs/<taskId>/
|
|
1233
|
+
3. Present the plan to the user
|
|
1234
|
+
4. If user approves, continue. If they want changes, provide feedback.
|
|
1235
|
+
|
|
1236
|
+
### Status Flow
|
|
1237
|
+
|
|
1238
|
+
1. Run: \`spets status\`
|
|
1239
|
+
2. Present the current workflow state
|
|
1240
|
+
|
|
1241
|
+
### Resume Flow
|
|
1242
|
+
|
|
1243
|
+
1. Run: \`spets resume\`
|
|
1244
|
+
2. Continue the workflow from where it paused
|
|
1245
|
+
|
|
1246
|
+
## Example Session
|
|
1247
|
+
|
|
1248
|
+
User: /spets start "Create a REST API for user management"
|
|
1249
|
+
|
|
1250
|
+
Claude:
|
|
1251
|
+
1. Runs \`spets start "Create a REST API for user management"\`
|
|
1252
|
+
2. Reads the generated plan
|
|
1253
|
+
3. Asks user: "Here's the plan. [shows plan] Would you like to approve or revise?"
|
|
1254
|
+
4. On approve: \`spets resume --approve\`
|
|
1255
|
+
5. On revise: \`spets resume --revise "user feedback"\`
|
|
1256
|
+
|
|
1257
|
+
$ARGUMENTS
|
|
1258
|
+
command: The spets command to run (start, status, resume)
|
|
1259
|
+
args: Additional arguments for the command
|
|
1260
|
+
`;
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
// src/commands/github.ts
|
|
1264
|
+
async function githubCommand(options) {
|
|
1265
|
+
const cwd = process.cwd();
|
|
1266
|
+
if (!septExists(cwd)) {
|
|
1267
|
+
console.error("Spets not initialized.");
|
|
1268
|
+
process.exit(1);
|
|
1269
|
+
}
|
|
1270
|
+
const { owner, repo, pr, issue, comment, task } = options;
|
|
1271
|
+
if (!pr && !issue) {
|
|
1272
|
+
console.error("Either --pr or --issue is required");
|
|
1273
|
+
process.exit(1);
|
|
1274
|
+
}
|
|
1275
|
+
const parsed = parseGitHubCommand(comment);
|
|
1276
|
+
if (!parsed.command) {
|
|
1277
|
+
console.log("No sept command found in comment. Ignoring.");
|
|
1278
|
+
return;
|
|
1279
|
+
}
|
|
1280
|
+
console.log(`Received command: ${parsed.command}`);
|
|
1281
|
+
let taskId = task;
|
|
1282
|
+
if (!taskId) {
|
|
1283
|
+
const taskMatch = comment.match(/Task ID: `([^`]+)`/);
|
|
1284
|
+
if (taskMatch) {
|
|
1285
|
+
taskId = taskMatch[1];
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
if (!taskId) {
|
|
1289
|
+
const config2 = loadConfig(cwd);
|
|
1290
|
+
const { listTasks: listTasks2 } = await import("./state-MCFJFWJC.js");
|
|
1291
|
+
const tasks = listTasks2(cwd);
|
|
1292
|
+
for (const tid of tasks) {
|
|
1293
|
+
const state2 = getWorkflowState(tid, config2, cwd);
|
|
1294
|
+
if (state2 && (state2.status === "in_progress" || state2.status === "paused")) {
|
|
1295
|
+
taskId = tid;
|
|
1296
|
+
break;
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
if (!taskId) {
|
|
1301
|
+
console.error("Could not determine task ID. Use --task option.");
|
|
1302
|
+
process.exit(1);
|
|
1303
|
+
}
|
|
1304
|
+
const config = loadConfig(cwd);
|
|
1305
|
+
const state = getWorkflowState(taskId, config, cwd);
|
|
1306
|
+
const meta = loadTaskMetadata(taskId, cwd);
|
|
1307
|
+
if (!state) {
|
|
1308
|
+
console.error(`Task '${taskId}' not found.`);
|
|
1309
|
+
process.exit(1);
|
|
1310
|
+
}
|
|
1311
|
+
const userQuery = meta?.userQuery || state.userQuery || "";
|
|
1312
|
+
const outputPath = getOutputPath(taskId, state.currentStepName, cwd);
|
|
1313
|
+
const githubConfig = {
|
|
1314
|
+
owner,
|
|
1315
|
+
repo,
|
|
1316
|
+
prNumber: pr ? parseInt(pr, 10) : void 0,
|
|
1317
|
+
issueNumber: issue ? parseInt(issue, 10) : void 0
|
|
1318
|
+
};
|
|
1319
|
+
switch (parsed.command) {
|
|
1320
|
+
case "approve": {
|
|
1321
|
+
console.log(`Approving step: ${state.currentStepName}`);
|
|
1322
|
+
updateDocumentStatus(outputPath, "approved");
|
|
1323
|
+
const nextIndex = state.currentStepIndex + 1;
|
|
1324
|
+
if (nextIndex >= config.steps.length) {
|
|
1325
|
+
console.log("\u2705 Workflow completed!");
|
|
1326
|
+
await postCompletionComment(githubConfig, taskId);
|
|
1327
|
+
return;
|
|
1328
|
+
}
|
|
1329
|
+
const platform = new GitHubPlatform(githubConfig);
|
|
1330
|
+
const executor = new Executor({ platform, config, cwd });
|
|
1331
|
+
try {
|
|
1332
|
+
await executor.executeWorkflow(taskId, userQuery, nextIndex);
|
|
1333
|
+
} catch (error) {
|
|
1334
|
+
if (error.name !== "PauseForInputError") {
|
|
1335
|
+
throw error;
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
break;
|
|
1339
|
+
}
|
|
1340
|
+
case "revise": {
|
|
1341
|
+
console.log(`Revising step with feedback: ${parsed.feedback}`);
|
|
1342
|
+
const platform = new GitHubPlatform(githubConfig);
|
|
1343
|
+
const executor = new Executor({ platform, config, cwd });
|
|
1344
|
+
try {
|
|
1345
|
+
await executor.executeWorkflow(taskId, userQuery, state.currentStepIndex);
|
|
1346
|
+
} catch (error) {
|
|
1347
|
+
if (error.name !== "PauseForInputError") {
|
|
1348
|
+
throw error;
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
break;
|
|
1352
|
+
}
|
|
1353
|
+
case "reject": {
|
|
1354
|
+
console.log("Rejecting workflow");
|
|
1355
|
+
updateDocumentStatus(outputPath, "rejected");
|
|
1356
|
+
await postRejectionComment(githubConfig, taskId, state.currentStepName);
|
|
1357
|
+
break;
|
|
1358
|
+
}
|
|
1359
|
+
case "answer": {
|
|
1360
|
+
console.log("Processing answers");
|
|
1361
|
+
const doc = loadDocument(taskId, state.currentStepName, cwd);
|
|
1362
|
+
if (!doc?.frontmatter.open_questions?.length) {
|
|
1363
|
+
console.log("No open questions for this task.");
|
|
1364
|
+
return;
|
|
1365
|
+
}
|
|
1366
|
+
const answers = Object.entries(parsed.answers || {}).map(([id, answer], index) => ({
|
|
1367
|
+
questionId: id,
|
|
1368
|
+
answer
|
|
1369
|
+
}));
|
|
1370
|
+
const platform = new GitHubPlatform(githubConfig);
|
|
1371
|
+
const executor = new Executor({ platform, config, cwd });
|
|
1372
|
+
try {
|
|
1373
|
+
await executor.executeWorkflow(taskId, userQuery, state.currentStepIndex);
|
|
1374
|
+
} catch (error) {
|
|
1375
|
+
if (error.name !== "PauseForInputError") {
|
|
1376
|
+
throw error;
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
break;
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
async function postCompletionComment(config, taskId) {
|
|
1384
|
+
const comment = [
|
|
1385
|
+
"## \u2705 Spets Workflow Completed",
|
|
1386
|
+
"",
|
|
1387
|
+
`Task \`${taskId}\` has been completed successfully!`,
|
|
1388
|
+
"",
|
|
1389
|
+
"All steps have been approved."
|
|
1390
|
+
].join("\n");
|
|
1391
|
+
await postComment(config, comment);
|
|
1392
|
+
}
|
|
1393
|
+
async function postRejectionComment(config, taskId, stepName) {
|
|
1394
|
+
const comment = [
|
|
1395
|
+
"## \u274C Spets Workflow Rejected",
|
|
1396
|
+
"",
|
|
1397
|
+
`Task \`${taskId}\` was rejected at step: **${stepName}**`
|
|
1398
|
+
].join("\n");
|
|
1399
|
+
await postComment(config, comment);
|
|
1400
|
+
}
|
|
1401
|
+
async function postComment(config, body) {
|
|
1402
|
+
const { spawn: spawn4 } = await import("child_process");
|
|
1403
|
+
const { owner, repo, prNumber, issueNumber } = config;
|
|
1404
|
+
const args = prNumber ? ["pr", "comment", String(prNumber), "--body", body, "-R", `${owner}/${repo}`] : ["issue", "comment", String(issueNumber), "--body", body, "-R", `${owner}/${repo}`];
|
|
1405
|
+
return new Promise((resolve, reject) => {
|
|
1406
|
+
const proc = spawn4("gh", args, { stdio: "inherit" });
|
|
1407
|
+
proc.on("close", (code) => {
|
|
1408
|
+
if (code !== 0) reject(new Error(`gh failed with code ${code}`));
|
|
1409
|
+
else resolve();
|
|
1410
|
+
});
|
|
1411
|
+
proc.on("error", reject);
|
|
1412
|
+
});
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
// src/index.ts
|
|
1416
|
+
var program = new Command();
|
|
1417
|
+
program.name("spets").description("Spec Driven Development Execution Framework").version("0.1.0");
|
|
1418
|
+
program.command("init").description("Initialize sept in current directory").option("-f, --force", "Overwrite existing config").option("--github", "Add GitHub Actions workflow for PR/Issue integration").action(initCommand);
|
|
1419
|
+
program.command("status").description("Show current workflow status").option("-t, --task <taskId>", "Show status for specific task").action(statusCommand);
|
|
1420
|
+
program.command("start").description("Start a new workflow").argument("<query>", "User query describing the task").option("-p, --platform <platform>", "Platform to use (cli, github)", "cli").option("--owner <owner>", "GitHub owner (for github platform)").option("--repo <repo>", "GitHub repo (for github platform)").option("--pr <number>", "GitHub PR number (for github platform)").option("--issue <number>", "GitHub issue number (for github platform)").action(startCommand);
|
|
1421
|
+
program.command("resume").description("Resume paused workflow").option("-t, --task <taskId>", "Resume specific task").option("--approve", "Approve current document and proceed").option("--revise <feedback>", "Request revision with feedback").action(resumeCommand);
|
|
1422
|
+
program.command("github").description("Handle GitHub Action callback (internal)").requiredOption("--owner <owner>", "GitHub owner").requiredOption("--repo <repo>", "GitHub repo").option("--pr <number>", "PR number").option("--issue <number>", "Issue number").option("-t, --task <taskId>", "Task ID").requiredOption("--comment <comment>", "Comment body").action(githubCommand);
|
|
1423
|
+
program.command("plugin").description("Manage plugins").argument("<action>", "Action: install, uninstall, list").argument("[name]", "Plugin name").action(pluginCommand);
|
|
1424
|
+
program.parse();
|