spets 0.1.2 → 0.1.4
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/README.md +56 -14
- package/dist/{chunk-XYU22TND.js → chunk-YK5ZZE4P.js} +2 -0
- package/dist/index.js +1066 -129
- package/dist/{state-H2GQS43T.js → state-JLYPJWUT.js} +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3,7 +3,9 @@ import {
|
|
|
3
3
|
generateTaskId,
|
|
4
4
|
getGitHubConfig,
|
|
5
5
|
getOutputPath,
|
|
6
|
+
getOutputsDir,
|
|
6
7
|
getSpetsDir,
|
|
8
|
+
getStepsDir,
|
|
7
9
|
getWorkflowState,
|
|
8
10
|
listTasks,
|
|
9
11
|
loadConfig,
|
|
@@ -14,7 +16,7 @@ import {
|
|
|
14
16
|
saveTaskMetadata,
|
|
15
17
|
spetsExists,
|
|
16
18
|
updateDocumentStatus
|
|
17
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-YK5ZZE4P.js";
|
|
18
20
|
|
|
19
21
|
// src/index.ts
|
|
20
22
|
import { Command } from "commander";
|
|
@@ -23,7 +25,24 @@ import { Command } from "commander";
|
|
|
23
25
|
import { mkdirSync, writeFileSync } from "fs";
|
|
24
26
|
import { join, dirname } from "path";
|
|
25
27
|
import { fileURLToPath } from "url";
|
|
28
|
+
import { execSync } from "child_process";
|
|
26
29
|
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
30
|
+
function getGitHubInfoFromRemote() {
|
|
31
|
+
try {
|
|
32
|
+
const remoteUrl = execSync("git remote get-url origin", { encoding: "utf-8" }).trim();
|
|
33
|
+
const sshMatch = remoteUrl.match(/git@github\.com:([^/]+)\/(.+?)(?:\.git)?$/);
|
|
34
|
+
if (sshMatch) {
|
|
35
|
+
return { owner: sshMatch[1], repo: sshMatch[2] };
|
|
36
|
+
}
|
|
37
|
+
const httpsMatch = remoteUrl.match(/https:\/\/github\.com\/([^/]+)\/(.+?)(?:\.git)?$/);
|
|
38
|
+
if (httpsMatch) {
|
|
39
|
+
return { owner: httpsMatch[1], repo: httpsMatch[2] };
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
} catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
27
46
|
async function initCommand(options) {
|
|
28
47
|
const cwd = process.cwd();
|
|
29
48
|
const spetsDir = getSpetsDir(cwd);
|
|
@@ -36,17 +55,21 @@ async function initCommand(options) {
|
|
|
36
55
|
mkdirSync(join(spetsDir, "outputs"), { recursive: true });
|
|
37
56
|
mkdirSync(join(spetsDir, "hooks"), { recursive: true });
|
|
38
57
|
const templatesDir = join(__dirname, "..", "templates");
|
|
39
|
-
|
|
58
|
+
const githubInfo = getGitHubInfoFromRemote();
|
|
59
|
+
writeFileSync(join(spetsDir, "config.yml"), getDefaultConfig(githubInfo));
|
|
40
60
|
createDefaultSteps(spetsDir);
|
|
61
|
+
createClaudeCommand(cwd);
|
|
41
62
|
console.log("Initialized spets in .spets/");
|
|
42
63
|
console.log("");
|
|
43
64
|
console.log("Created:");
|
|
44
|
-
console.log(" .spets/config.yml
|
|
45
|
-
console.log(" .spets/steps/01-plan/
|
|
46
|
-
console.log(" .spets/steps/02-implement/
|
|
65
|
+
console.log(" .spets/config.yml - Workflow configuration");
|
|
66
|
+
console.log(" .spets/steps/01-plan/ - Planning step");
|
|
67
|
+
console.log(" .spets/steps/02-implement/ - Implementation step");
|
|
68
|
+
console.log(" .claude/commands/spets.md - Claude Code command");
|
|
47
69
|
if (options.github) {
|
|
48
70
|
createGitHubWorkflow(cwd);
|
|
49
|
-
console.log(" .github/workflows/spets.yml
|
|
71
|
+
console.log(" .github/workflows/spets.yml - GitHub Actions workflow");
|
|
72
|
+
console.log(" .github/ISSUE_TEMPLATE/spets-task.yml - Issue template");
|
|
50
73
|
}
|
|
51
74
|
console.log("");
|
|
52
75
|
console.log("Next steps:");
|
|
@@ -54,19 +77,30 @@ async function initCommand(options) {
|
|
|
54
77
|
console.log(" 2. Customize step instructions in .spets/steps/");
|
|
55
78
|
if (options.github) {
|
|
56
79
|
console.log(" 3. Add ANTHROPIC_API_KEY to your repo secrets");
|
|
57
|
-
console.log(' 4.
|
|
80
|
+
console.log(' 4. Create a new Issue using the "Spets Task" template');
|
|
58
81
|
} else {
|
|
59
82
|
console.log(' 3. Run: spets start "your task description"');
|
|
60
83
|
}
|
|
61
84
|
}
|
|
62
|
-
function getDefaultConfig() {
|
|
85
|
+
function getDefaultConfig(githubInfo) {
|
|
86
|
+
const githubSection = githubInfo ? `
|
|
87
|
+
# GitHub integration (auto-detected from git remote)
|
|
88
|
+
github:
|
|
89
|
+
owner: ${githubInfo.owner}
|
|
90
|
+
repo: ${githubInfo.repo}
|
|
91
|
+
` : `
|
|
92
|
+
# GitHub integration (uncomment and fill in to enable)
|
|
93
|
+
# github:
|
|
94
|
+
# owner: your-org
|
|
95
|
+
# repo: your-repo
|
|
96
|
+
`;
|
|
63
97
|
return `# Spets Configuration
|
|
64
98
|
# Define your workflow steps here
|
|
65
99
|
|
|
66
100
|
steps:
|
|
67
101
|
- 01-plan
|
|
68
102
|
- 02-implement
|
|
69
|
-
|
|
103
|
+
${githubSection}
|
|
70
104
|
# Optional hooks (shell scripts)
|
|
71
105
|
# hooks:
|
|
72
106
|
# preStep: "./hooks/pre-step.sh"
|
|
@@ -237,31 +271,182 @@ None / List any deviations with justification.
|
|
|
237
271
|
- Follow-up task 2
|
|
238
272
|
`;
|
|
239
273
|
}
|
|
274
|
+
function createClaudeCommand(cwd) {
|
|
275
|
+
const commandDir = join(cwd, ".claude", "commands");
|
|
276
|
+
mkdirSync(commandDir, { recursive: true });
|
|
277
|
+
writeFileSync(join(commandDir, "spets.md"), getClaudeCommand());
|
|
278
|
+
}
|
|
279
|
+
function getClaudeCommand() {
|
|
280
|
+
return `# Spets - Spec Driven Development
|
|
281
|
+
|
|
282
|
+
Spec-Driven Development workflow execution skill for Claude Code.
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
## CRITICAL: Execution Model
|
|
287
|
+
|
|
288
|
+
This skill runs **deterministically** within the current Claude Code session.
|
|
289
|
+
- NO subprocess spawning for AI/LLM calls
|
|
290
|
+
- The orchestrator manages state via JSON protocol
|
|
291
|
+
- Each step follows a strict state machine flow
|
|
292
|
+
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
## Orchestrator Commands
|
|
296
|
+
|
|
297
|
+
All workflow state is managed by the orchestrator CLI:
|
|
298
|
+
|
|
299
|
+
\`\`\`bash
|
|
300
|
+
npx spets orchestrate init "<description>" # Start new workflow
|
|
301
|
+
npx spets orchestrate done <taskId> # Mark step complete
|
|
302
|
+
npx spets orchestrate clarified <taskId> '<json>' # Submit answers
|
|
303
|
+
npx spets orchestrate approve <taskId> # Approve and continue
|
|
304
|
+
npx spets orchestrate revise <taskId> "<feedback>" # Request revision
|
|
305
|
+
npx spets orchestrate reject <taskId> # Stop workflow
|
|
306
|
+
npx spets orchestrate stop <taskId> # Pause (can resume)
|
|
307
|
+
npx spets orchestrate status <taskId> # Get current status
|
|
308
|
+
\`\`\`
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
## Execution Flow
|
|
313
|
+
|
|
314
|
+
### 1. Workflow Start (FIRST ACTION - NO EXCEPTIONS)
|
|
315
|
+
|
|
316
|
+
When skill is loaded, **immediately** run:
|
|
317
|
+
|
|
318
|
+
\`\`\`bash
|
|
319
|
+
npx spets orchestrate init "{user_request}"
|
|
320
|
+
\`\`\`
|
|
321
|
+
|
|
322
|
+
### 2. Document Generation (type: step)
|
|
323
|
+
|
|
324
|
+
When you receive \`type: "step"\`:
|
|
325
|
+
|
|
326
|
+
1. **Read the instruction file** from context.instruction
|
|
327
|
+
2. **Read the template file** from context.template (if exists)
|
|
328
|
+
3. **Read previous output** from context.previousOutput (if exists)
|
|
329
|
+
4. **Generate the document** following the instruction and template
|
|
330
|
+
5. **Write the document** to context.output
|
|
331
|
+
6. **Mark step as done**: \`npx spets orchestrate done {taskId}\`
|
|
332
|
+
|
|
333
|
+
### 3. Question Resolution (type: checkpoint, checkpoint: clarify)
|
|
334
|
+
|
|
335
|
+
1. **Use AskUserQuestion** to ask the questions
|
|
336
|
+
2. **Collect answers** into JSON format
|
|
337
|
+
3. **Submit answers**: \`npx spets orchestrate clarified {taskId} '<answers_json>'\`
|
|
338
|
+
|
|
339
|
+
### 4. Approval Request (type: checkpoint, checkpoint: approve)
|
|
340
|
+
|
|
341
|
+
1. **Display document summary** from specPath
|
|
342
|
+
2. **Use AskUserQuestion** with options: Approve, Revise, Reject, Stop
|
|
343
|
+
3. **Execute the selected action**
|
|
344
|
+
|
|
345
|
+
### 5. Workflow Complete (type: complete)
|
|
346
|
+
|
|
347
|
+
Display completion message based on status (completed, stopped, rejected)
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
## Response Type Actions Summary
|
|
352
|
+
|
|
353
|
+
| Type | Checkpoint | Action |
|
|
354
|
+
|------|------------|--------|
|
|
355
|
+
| step | - | Generate document, write to output, call done |
|
|
356
|
+
| checkpoint | clarify | Ask questions, call clarified with answers |
|
|
357
|
+
| checkpoint | approve | Show doc, ask for decision, call appropriate command |
|
|
358
|
+
| complete | - | Show final message, end skill |
|
|
359
|
+
| error | - | Show error, end skill |
|
|
360
|
+
|
|
361
|
+
---
|
|
362
|
+
|
|
363
|
+
## Critical Implementation Rules
|
|
364
|
+
|
|
365
|
+
1. **ORCHESTRATOR FIRST** - Always call orchestrator before any action
|
|
366
|
+
2. **ONE STEP AT A TIME** - Complete one step, get approval, then next
|
|
367
|
+
3. **DETERMINISTIC FLOW** - Follow the exact state machine transitions
|
|
368
|
+
4. **NO PROCESS SPAWN** - Generate documents in current session
|
|
369
|
+
5. **WAIT FOR APPROVAL** - Never proceed without user approval
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
## Example Session
|
|
374
|
+
|
|
375
|
+
\`\`\`
|
|
376
|
+
User: /spets Add user authentication
|
|
377
|
+
|
|
378
|
+
[Skill calls: npx spets orchestrate init "Add user authentication"]
|
|
379
|
+
[Receives: type=step, step=01-plan]
|
|
380
|
+
|
|
381
|
+
[Skill reads instruction.md and template.md]
|
|
382
|
+
[Skill generates plan document]
|
|
383
|
+
[Skill writes to .spets/outputs/.../01-plan.md]
|
|
384
|
+
[Skill calls: npx spets orchestrate done mku3abc-xyz]
|
|
385
|
+
|
|
386
|
+
[Receives: type=checkpoint, checkpoint=approve]
|
|
387
|
+
[Skill shows document summary]
|
|
388
|
+
[Skill asks user: Approve/Revise/Reject/Stop?]
|
|
389
|
+
|
|
390
|
+
User: Approve
|
|
391
|
+
|
|
392
|
+
[Skill calls: npx spets orchestrate approve mku3abc-xyz]
|
|
393
|
+
[Receives: type=step, step=02-implement]
|
|
394
|
+
|
|
395
|
+
[... continues with implementation step ...]
|
|
396
|
+
\`\`\`
|
|
397
|
+
|
|
398
|
+
$ARGUMENTS
|
|
399
|
+
description: The task description for the workflow
|
|
400
|
+
`;
|
|
401
|
+
}
|
|
240
402
|
function createGitHubWorkflow(cwd) {
|
|
241
403
|
const workflowDir = join(cwd, ".github", "workflows");
|
|
404
|
+
const templateDir = join(cwd, ".github", "ISSUE_TEMPLATE");
|
|
242
405
|
mkdirSync(workflowDir, { recursive: true });
|
|
406
|
+
mkdirSync(templateDir, { recursive: true });
|
|
243
407
|
writeFileSync(join(workflowDir, "spets.yml"), getGitHubWorkflow());
|
|
408
|
+
writeFileSync(join(templateDir, "spets-task.yml"), getIssueTemplate());
|
|
409
|
+
}
|
|
410
|
+
function getIssueTemplate() {
|
|
411
|
+
return `name: Spets Task
|
|
412
|
+
description: Start a Spets workflow
|
|
413
|
+
labels: ["spets"]
|
|
414
|
+
body:
|
|
415
|
+
- type: input
|
|
416
|
+
id: task
|
|
417
|
+
attributes:
|
|
418
|
+
label: Task Description
|
|
419
|
+
description: What do you want to accomplish?
|
|
420
|
+
placeholder: "Add user authentication"
|
|
421
|
+
validations:
|
|
422
|
+
required: true
|
|
423
|
+
- type: input
|
|
424
|
+
id: branch
|
|
425
|
+
attributes:
|
|
426
|
+
label: Branch Name
|
|
427
|
+
description: Leave empty to auto-generate (spets/<issue-number>)
|
|
428
|
+
placeholder: "feature/my-feature"
|
|
429
|
+
validations:
|
|
430
|
+
required: false
|
|
431
|
+
`;
|
|
244
432
|
}
|
|
245
433
|
function getGitHubWorkflow() {
|
|
246
434
|
const gh = (expr) => `\${{ ${expr} }}`;
|
|
247
435
|
return `# Spets GitHub Action
|
|
248
|
-
#
|
|
436
|
+
# Handles workflow start from Issue creation and commands from comments
|
|
249
437
|
|
|
250
438
|
name: Spets Workflow
|
|
251
439
|
|
|
252
440
|
on:
|
|
441
|
+
issues:
|
|
442
|
+
types: [opened]
|
|
253
443
|
issue_comment:
|
|
254
444
|
types: [created]
|
|
255
445
|
|
|
256
446
|
jobs:
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
if:
|
|
260
|
-
contains(github.event.comment.body, '/approve') ||
|
|
261
|
-
contains(github.event.comment.body, '/revise') ||
|
|
262
|
-
contains(github.event.comment.body, '/reject') ||
|
|
263
|
-
contains(github.event.comment.body, '/answer')
|
|
264
|
-
|
|
447
|
+
# Start workflow when a spets Issue is created
|
|
448
|
+
start-workflow:
|
|
449
|
+
if: github.event.action == 'opened' && contains(github.event.issue.labels.*.name, 'spets')
|
|
265
450
|
runs-on: ubuntu-latest
|
|
266
451
|
|
|
267
452
|
steps:
|
|
@@ -270,6 +455,30 @@ jobs:
|
|
|
270
455
|
with:
|
|
271
456
|
fetch-depth: 0
|
|
272
457
|
|
|
458
|
+
- name: Parse Issue body
|
|
459
|
+
id: parse
|
|
460
|
+
env:
|
|
461
|
+
ISSUE_BODY: ${gh("github.event.issue.body")}
|
|
462
|
+
ISSUE_NUMBER: ${gh("github.event.issue.number")}
|
|
463
|
+
run: |
|
|
464
|
+
# Parse task description
|
|
465
|
+
TASK=$(echo "$ISSUE_BODY" | sed -n '/### Task Description/,/###/{/###/!p;}' | sed '/^$/d' | head -1)
|
|
466
|
+
echo "task=$TASK" >> $GITHUB_OUTPUT
|
|
467
|
+
|
|
468
|
+
# Parse branch name (optional)
|
|
469
|
+
BRANCH=$(echo "$ISSUE_BODY" | sed -n '/### Branch Name/,/###/{/###/!p;}' | sed '/^$/d' | head -1)
|
|
470
|
+
if [ -z "$BRANCH" ]; then
|
|
471
|
+
BRANCH="spets/$ISSUE_NUMBER"
|
|
472
|
+
fi
|
|
473
|
+
echo "branch=$BRANCH" >> $GITHUB_OUTPUT
|
|
474
|
+
|
|
475
|
+
- name: Create and checkout branch
|
|
476
|
+
run: |
|
|
477
|
+
git checkout -b ${gh("steps.parse.outputs.branch")}
|
|
478
|
+
git push -u origin ${gh("steps.parse.outputs.branch")}
|
|
479
|
+
env:
|
|
480
|
+
GH_TOKEN: ${gh("secrets.GITHUB_TOKEN")}
|
|
481
|
+
|
|
273
482
|
- name: Setup Node.js
|
|
274
483
|
uses: actions/setup-node@v4
|
|
275
484
|
with:
|
|
@@ -281,43 +490,69 @@ jobs:
|
|
|
281
490
|
- name: Install dependencies
|
|
282
491
|
run: npm ci
|
|
283
492
|
|
|
284
|
-
- name:
|
|
285
|
-
|
|
493
|
+
- name: Start Spets workflow
|
|
494
|
+
run: |
|
|
495
|
+
npx spets start "$TASK" --github --issue ${gh("github.event.issue.number")}
|
|
496
|
+
env:
|
|
497
|
+
TASK: ${gh("steps.parse.outputs.task")}
|
|
498
|
+
ANTHROPIC_API_KEY: ${gh("secrets.ANTHROPIC_API_KEY")}
|
|
499
|
+
GH_TOKEN: ${gh("secrets.GITHUB_TOKEN")}
|
|
500
|
+
|
|
501
|
+
- name: Push changes
|
|
502
|
+
run: |
|
|
503
|
+
git config user.name "github-actions[bot]"
|
|
504
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
505
|
+
git add -A
|
|
506
|
+
git diff --staged --quiet || git commit -m "Spets: Start workflow for #${gh("github.event.issue.number")}"
|
|
507
|
+
git push
|
|
508
|
+
env:
|
|
509
|
+
GH_TOKEN: ${gh("secrets.GITHUB_TOKEN")}
|
|
510
|
+
|
|
511
|
+
# Handle commands from Issue/PR comments
|
|
512
|
+
handle-command:
|
|
513
|
+
if: |
|
|
514
|
+
github.event.action == 'created' && (
|
|
515
|
+
contains(github.event.comment.body, '/approve') ||
|
|
516
|
+
contains(github.event.comment.body, '/revise') ||
|
|
517
|
+
contains(github.event.comment.body, '/reject')
|
|
518
|
+
)
|
|
519
|
+
runs-on: ubuntu-latest
|
|
520
|
+
|
|
521
|
+
steps:
|
|
522
|
+
- name: Find linked branch
|
|
523
|
+
id: branch
|
|
286
524
|
run: |
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
525
|
+
# Try to find branch linked to this issue
|
|
526
|
+
BRANCH=$(gh api repos/${gh("github.repository")}/issues/${gh("github.event.issue.number")} --jq '.body' | grep -oP 'spets/\\d+' || echo "")
|
|
527
|
+
if [ -z "$BRANCH" ]; then
|
|
528
|
+
BRANCH="spets/${gh("github.event.issue.number")}"
|
|
291
529
|
fi
|
|
292
|
-
echo "
|
|
530
|
+
echo "name=$BRANCH" >> $GITHUB_OUTPUT
|
|
531
|
+
env:
|
|
532
|
+
GH_TOKEN: ${gh("secrets.GITHUB_TOKEN")}
|
|
533
|
+
|
|
534
|
+
- name: Checkout
|
|
535
|
+
uses: actions/checkout@v4
|
|
536
|
+
with:
|
|
537
|
+
ref: ${gh("steps.branch.outputs.name")}
|
|
538
|
+
fetch-depth: 0
|
|
293
539
|
|
|
294
|
-
- name:
|
|
540
|
+
- name: Setup Node.js
|
|
541
|
+
uses: actions/setup-node@v4
|
|
542
|
+
with:
|
|
543
|
+
node-version: '20'
|
|
544
|
+
|
|
545
|
+
- name: Install Claude Code
|
|
546
|
+
run: npm install -g @anthropic-ai/claude-code
|
|
547
|
+
|
|
548
|
+
- name: Install dependencies
|
|
549
|
+
run: npm ci
|
|
550
|
+
|
|
551
|
+
- name: Run Spets command
|
|
295
552
|
run: |
|
|
296
|
-
|
|
297
|
-
You are running a Spets workflow via GitHub Actions.
|
|
298
|
-
|
|
299
|
-
Context:
|
|
300
|
-
- Repository: ${gh("github.repository")}
|
|
301
|
-
- Issue/PR: #${gh("github.event.issue.number")}
|
|
302
|
-
- Comment author: ${gh("github.event.comment.user.login")}
|
|
303
|
-
- Command received: $COMMENT_BODY
|
|
304
|
-
|
|
305
|
-
Instructions:
|
|
306
|
-
1. Parse the command from the comment (/approve, /revise, /reject, or /answer)
|
|
307
|
-
2. Find the active spets task in .spets/outputs/
|
|
308
|
-
3. Execute the appropriate action:
|
|
309
|
-
- /approve: Mark current step as approved, generate next step
|
|
310
|
-
- /revise <feedback>: Regenerate current step with feedback
|
|
311
|
-
- /reject: Mark workflow as rejected
|
|
312
|
-
- /answer: Process answers and continue
|
|
313
|
-
4. If generating content (plan or implementation), actually write the code/changes
|
|
314
|
-
5. Post a summary comment to the PR/Issue using gh cli
|
|
315
|
-
6. Commit any changes with a descriptive message
|
|
316
|
-
|
|
317
|
-
Use 'gh issue comment' or 'gh pr comment' to post updates.
|
|
318
|
-
"
|
|
553
|
+
npx spets github --issue ${gh("github.event.issue.number")} --comment "$COMMENT"
|
|
319
554
|
env:
|
|
320
|
-
|
|
555
|
+
COMMENT: ${gh("github.event.comment.body")}
|
|
321
556
|
ANTHROPIC_API_KEY: ${gh("secrets.ANTHROPIC_API_KEY")}
|
|
322
557
|
GH_TOKEN: ${gh("secrets.GITHUB_TOKEN")}
|
|
323
558
|
|
|
@@ -326,7 +561,7 @@ jobs:
|
|
|
326
561
|
git config user.name "github-actions[bot]"
|
|
327
562
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
328
563
|
git add -A
|
|
329
|
-
git diff --staged --quiet || git commit -m "Spets: Update from
|
|
564
|
+
git diff --staged --quiet || git commit -m "Spets: Update from #${gh("github.event.issue.number")}"
|
|
330
565
|
git push
|
|
331
566
|
env:
|
|
332
567
|
GH_TOKEN: ${gh("secrets.GITHUB_TOKEN")}
|
|
@@ -592,7 +827,7 @@ var Executor = class {
|
|
|
592
827
|
};
|
|
593
828
|
|
|
594
829
|
// src/platform/cli.ts
|
|
595
|
-
import { spawn as spawn2, execSync } from "child_process";
|
|
830
|
+
import { spawn as spawn2, execSync as execSync2 } from "child_process";
|
|
596
831
|
import { input, select, confirm } from "@inquirer/prompts";
|
|
597
832
|
|
|
598
833
|
// src/platform/interface.ts
|
|
@@ -744,7 +979,7 @@ var CliPlatform = class extends BasePlatform {
|
|
|
744
979
|
isClaudeInstalled() {
|
|
745
980
|
try {
|
|
746
981
|
const command = process.platform === "win32" ? "where" : "which";
|
|
747
|
-
|
|
982
|
+
execSync2(`${command} claude`, { stdio: "ignore" });
|
|
748
983
|
return true;
|
|
749
984
|
} catch {
|
|
750
985
|
return false;
|
|
@@ -1019,6 +1254,83 @@ var GitHubPlatform = class extends BasePlatform {
|
|
|
1019
1254
|
};
|
|
1020
1255
|
|
|
1021
1256
|
// src/commands/start.ts
|
|
1257
|
+
import { execSync as execSync3 } from "child_process";
|
|
1258
|
+
function getGitHubInfo(cwd) {
|
|
1259
|
+
const config = getGitHubConfig(cwd);
|
|
1260
|
+
if (config?.owner && config?.repo) {
|
|
1261
|
+
return { owner: config.owner, repo: config.repo };
|
|
1262
|
+
}
|
|
1263
|
+
try {
|
|
1264
|
+
const remoteUrl = execSync3("git remote get-url origin", { encoding: "utf-8", cwd }).trim();
|
|
1265
|
+
const sshMatch = remoteUrl.match(/git@github\.com:([^/]+)\/(.+?)(?:\.git)?$/);
|
|
1266
|
+
if (sshMatch) {
|
|
1267
|
+
return { owner: sshMatch[1], repo: sshMatch[2] };
|
|
1268
|
+
}
|
|
1269
|
+
const httpsMatch = remoteUrl.match(/https:\/\/github\.com\/([^/]+)\/(.+?)(?:\.git)?$/);
|
|
1270
|
+
if (httpsMatch) {
|
|
1271
|
+
return { owner: httpsMatch[1], repo: httpsMatch[2] };
|
|
1272
|
+
}
|
|
1273
|
+
} catch {
|
|
1274
|
+
}
|
|
1275
|
+
return null;
|
|
1276
|
+
}
|
|
1277
|
+
function createGitHubIssue(info, query, taskId) {
|
|
1278
|
+
const body = `## Task
|
|
1279
|
+
${query}
|
|
1280
|
+
|
|
1281
|
+
## Spets Workflow
|
|
1282
|
+
- Task ID: \`${taskId}\`
|
|
1283
|
+
- Status: In Progress
|
|
1284
|
+
|
|
1285
|
+
---
|
|
1286
|
+
_Managed by [Spets](https://github.com/eatnug/spets)_`;
|
|
1287
|
+
const result = execSync3(
|
|
1288
|
+
`gh issue create --repo ${info.owner}/${info.repo} --title "${query.slice(0, 50)}" --body "${body.replace(/"/g, '\\"')}" --label spets`,
|
|
1289
|
+
{ encoding: "utf-8" }
|
|
1290
|
+
);
|
|
1291
|
+
const match = result.match(/\/issues\/(\d+)/);
|
|
1292
|
+
if (!match) {
|
|
1293
|
+
throw new Error("Failed to parse issue number from gh output");
|
|
1294
|
+
}
|
|
1295
|
+
return parseInt(match[1], 10);
|
|
1296
|
+
}
|
|
1297
|
+
function createGitHubPR(info, query, taskId) {
|
|
1298
|
+
const branchName = `spets/${taskId}`;
|
|
1299
|
+
execSync3(`git checkout -b ${branchName}`, { encoding: "utf-8" });
|
|
1300
|
+
execSync3(`git push -u origin ${branchName}`, { encoding: "utf-8" });
|
|
1301
|
+
const body = `## Task
|
|
1302
|
+
${query}
|
|
1303
|
+
|
|
1304
|
+
## Spets Workflow
|
|
1305
|
+
- Task ID: \`${taskId}\`
|
|
1306
|
+
- Status: In Progress
|
|
1307
|
+
|
|
1308
|
+
---
|
|
1309
|
+
_Managed by [Spets](https://github.com/eatnug/spets)_`;
|
|
1310
|
+
const result = execSync3(
|
|
1311
|
+
`gh pr create --repo ${info.owner}/${info.repo} --title "${query.slice(0, 50)}" --body "${body.replace(/"/g, '\\"')}" --label spets`,
|
|
1312
|
+
{ encoding: "utf-8" }
|
|
1313
|
+
);
|
|
1314
|
+
const match = result.match(/\/pull\/(\d+)/);
|
|
1315
|
+
if (!match) {
|
|
1316
|
+
throw new Error("Failed to parse PR number from gh output");
|
|
1317
|
+
}
|
|
1318
|
+
return parseInt(match[1], 10);
|
|
1319
|
+
}
|
|
1320
|
+
function findLinkedIssueOrPR(info) {
|
|
1321
|
+
try {
|
|
1322
|
+
const result = execSync3(
|
|
1323
|
+
`gh pr view --repo ${info.owner}/${info.repo} --json number`,
|
|
1324
|
+
{ encoding: "utf-8" }
|
|
1325
|
+
);
|
|
1326
|
+
const pr = JSON.parse(result);
|
|
1327
|
+
if (pr.number) {
|
|
1328
|
+
return { type: "pr", number: pr.number };
|
|
1329
|
+
}
|
|
1330
|
+
} catch {
|
|
1331
|
+
}
|
|
1332
|
+
return null;
|
|
1333
|
+
}
|
|
1022
1334
|
async function startCommand(query, options) {
|
|
1023
1335
|
const cwd = process.cwd();
|
|
1024
1336
|
if (!spetsExists(cwd)) {
|
|
@@ -1027,35 +1339,67 @@ async function startCommand(query, options) {
|
|
|
1027
1339
|
}
|
|
1028
1340
|
const config = loadConfig(cwd);
|
|
1029
1341
|
const taskId = generateTaskId();
|
|
1030
|
-
console.log(`Starting new workflow: ${taskId}`);
|
|
1031
|
-
console.log(`Query: ${query}`);
|
|
1032
|
-
console.log(`Platform: ${options.platform || "cli"}`);
|
|
1033
|
-
console.log("");
|
|
1034
1342
|
saveTaskMetadata(taskId, query, cwd);
|
|
1035
1343
|
let platform;
|
|
1036
|
-
if (options.
|
|
1037
|
-
const
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
const pr = options.pr || (githubConfig?.defaultPr ? String(githubConfig.defaultPr) : void 0);
|
|
1041
|
-
const issue = options.issue || (githubConfig?.defaultIssue ? String(githubConfig.defaultIssue) : void 0);
|
|
1042
|
-
if (!owner || !repo) {
|
|
1043
|
-
console.error("GitHub platform requires --owner and --repo (or set in .spets/config.yml)");
|
|
1344
|
+
if (options.github || options.issue !== void 0 || options.pr !== void 0) {
|
|
1345
|
+
const githubInfo = getGitHubInfo(cwd);
|
|
1346
|
+
if (!githubInfo) {
|
|
1347
|
+
console.error("Could not determine GitHub owner/repo. Set in .spets/config.yml or add git remote.");
|
|
1044
1348
|
process.exit(1);
|
|
1045
1349
|
}
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1350
|
+
let issueNumber;
|
|
1351
|
+
let prNumber;
|
|
1352
|
+
if (options.issue !== void 0) {
|
|
1353
|
+
if (typeof options.issue === "string") {
|
|
1354
|
+
issueNumber = parseInt(options.issue, 10);
|
|
1355
|
+
console.log(`Using existing Issue #${issueNumber}`);
|
|
1356
|
+
} else {
|
|
1357
|
+
console.log("Creating new GitHub Issue...");
|
|
1358
|
+
issueNumber = createGitHubIssue(githubInfo, query, taskId);
|
|
1359
|
+
console.log(`Created Issue #${issueNumber}`);
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
if (options.pr !== void 0) {
|
|
1363
|
+
if (typeof options.pr === "string") {
|
|
1364
|
+
prNumber = parseInt(options.pr, 10);
|
|
1365
|
+
console.log(`Using existing PR #${prNumber}`);
|
|
1366
|
+
} else {
|
|
1367
|
+
console.log("Creating new GitHub PR...");
|
|
1368
|
+
prNumber = createGitHubPR(githubInfo, query, taskId);
|
|
1369
|
+
console.log(`Created PR #${prNumber}`);
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
if (issueNumber === void 0 && prNumber === void 0) {
|
|
1373
|
+
const linked = findLinkedIssueOrPR(githubInfo);
|
|
1374
|
+
if (linked) {
|
|
1375
|
+
console.log(`Found linked ${linked.type} #${linked.number}`);
|
|
1376
|
+
if (linked.type === "pr") {
|
|
1377
|
+
prNumber = linked.number;
|
|
1378
|
+
} else {
|
|
1379
|
+
issueNumber = linked.number;
|
|
1380
|
+
}
|
|
1381
|
+
} else {
|
|
1382
|
+
console.log("No linked Issue/PR found. Running in GitHub mode without Issue/PR.");
|
|
1383
|
+
}
|
|
1049
1384
|
}
|
|
1050
1385
|
platform = new GitHubPlatform({
|
|
1051
|
-
owner,
|
|
1052
|
-
repo,
|
|
1053
|
-
prNumber
|
|
1054
|
-
issueNumber
|
|
1386
|
+
owner: githubInfo.owner,
|
|
1387
|
+
repo: githubInfo.repo,
|
|
1388
|
+
prNumber,
|
|
1389
|
+
issueNumber
|
|
1055
1390
|
});
|
|
1391
|
+
console.log(`Starting workflow: ${taskId}`);
|
|
1392
|
+
console.log(`Query: ${query}`);
|
|
1393
|
+
console.log(`Platform: github (${githubInfo.owner}/${githubInfo.repo})`);
|
|
1394
|
+
if (issueNumber) console.log(`Issue: #${issueNumber}`);
|
|
1395
|
+
if (prNumber) console.log(`PR: #${prNumber}`);
|
|
1056
1396
|
} else {
|
|
1057
1397
|
platform = new CliPlatform();
|
|
1398
|
+
console.log(`Starting workflow: ${taskId}`);
|
|
1399
|
+
console.log(`Query: ${query}`);
|
|
1400
|
+
console.log(`Platform: cli`);
|
|
1058
1401
|
}
|
|
1402
|
+
console.log("");
|
|
1059
1403
|
const executor = new Executor({ platform, config, cwd });
|
|
1060
1404
|
try {
|
|
1061
1405
|
await executor.executeWorkflow(taskId, query);
|
|
@@ -1203,23 +1547,31 @@ function installClaudePlugin() {
|
|
|
1203
1547
|
const claudeDir = join3(homedir(), ".claude");
|
|
1204
1548
|
const commandsDir = join3(claudeDir, "commands");
|
|
1205
1549
|
mkdirSync2(commandsDir, { recursive: true });
|
|
1206
|
-
const skillPath = join3(commandsDir, "
|
|
1550
|
+
const skillPath = join3(commandsDir, "sdd-do.md");
|
|
1207
1551
|
writeFileSync2(skillPath, getClaudeSkillContent());
|
|
1208
1552
|
console.log("Installed Claude Code plugin.");
|
|
1209
1553
|
console.log(`Location: ${skillPath}`);
|
|
1210
1554
|
console.log("");
|
|
1211
1555
|
console.log("Usage in Claude Code:");
|
|
1212
|
-
console.log(' /
|
|
1213
|
-
console.log(" /spets status");
|
|
1214
|
-
console.log(" /spets resume");
|
|
1556
|
+
console.log(' /sdd-do "your task description"');
|
|
1215
1557
|
console.log("");
|
|
1216
|
-
console.log("
|
|
1558
|
+
console.log("This skill runs deterministically within your Claude Code session.");
|
|
1559
|
+
console.log("No additional Claude processes are spawned.");
|
|
1217
1560
|
}
|
|
1218
1561
|
async function uninstallPlugin(name) {
|
|
1219
1562
|
if (name === "claude") {
|
|
1220
|
-
const
|
|
1221
|
-
|
|
1222
|
-
|
|
1563
|
+
const oldSkillPath = join3(homedir(), ".claude", "commands", "spets.md");
|
|
1564
|
+
const newSkillPath = join3(homedir(), ".claude", "commands", "sdd-do.md");
|
|
1565
|
+
let uninstalled = false;
|
|
1566
|
+
if (existsSync3(oldSkillPath)) {
|
|
1567
|
+
rmSync(oldSkillPath);
|
|
1568
|
+
uninstalled = true;
|
|
1569
|
+
}
|
|
1570
|
+
if (existsSync3(newSkillPath)) {
|
|
1571
|
+
rmSync(newSkillPath);
|
|
1572
|
+
uninstalled = true;
|
|
1573
|
+
}
|
|
1574
|
+
if (uninstalled) {
|
|
1223
1575
|
console.log("Uninstalled Claude Code plugin.");
|
|
1224
1576
|
} else {
|
|
1225
1577
|
console.log("Claude Code plugin not installed.");
|
|
@@ -1232,10 +1584,11 @@ async function uninstallPlugin(name) {
|
|
|
1232
1584
|
async function listPlugins() {
|
|
1233
1585
|
console.log("Available plugins:");
|
|
1234
1586
|
console.log("");
|
|
1235
|
-
console.log(" claude - Claude Code
|
|
1587
|
+
console.log(" claude - Claude Code /sdd-do skill");
|
|
1236
1588
|
console.log("");
|
|
1237
|
-
const
|
|
1238
|
-
const
|
|
1589
|
+
const oldSkillPath = join3(homedir(), ".claude", "commands", "spets.md");
|
|
1590
|
+
const newSkillPath = join3(homedir(), ".claude", "commands", "sdd-do.md");
|
|
1591
|
+
const claudeInstalled = existsSync3(oldSkillPath) || existsSync3(newSkillPath);
|
|
1239
1592
|
console.log("Installed:");
|
|
1240
1593
|
if (claudeInstalled) {
|
|
1241
1594
|
console.log(" - claude");
|
|
@@ -1244,81 +1597,202 @@ async function listPlugins() {
|
|
|
1244
1597
|
}
|
|
1245
1598
|
}
|
|
1246
1599
|
function getClaudeSkillContent() {
|
|
1247
|
-
return `#
|
|
1600
|
+
return `# SDD-Do Skill
|
|
1601
|
+
|
|
1602
|
+
Spec-Driven Development workflow execution skill for Claude Code.
|
|
1603
|
+
|
|
1604
|
+
---
|
|
1605
|
+
|
|
1606
|
+
## When to Use This Skill
|
|
1607
|
+
|
|
1608
|
+
Automatically invoked when user uses:
|
|
1609
|
+
- \`/sdd-do\` - Run SDD workflow
|
|
1610
|
+
|
|
1611
|
+
---
|
|
1612
|
+
|
|
1613
|
+
## CRITICAL: Execution Model
|
|
1614
|
+
|
|
1615
|
+
This skill runs **deterministically** within the current Claude Code session.
|
|
1616
|
+
- NO subprocess spawning for AI/LLM calls
|
|
1617
|
+
- The orchestrator manages state via JSON protocol
|
|
1618
|
+
- Each step follows a strict state machine flow
|
|
1619
|
+
|
|
1620
|
+
---
|
|
1621
|
+
|
|
1622
|
+
## Orchestrator Commands
|
|
1623
|
+
|
|
1624
|
+
All workflow state is managed by the orchestrator CLI:
|
|
1625
|
+
|
|
1626
|
+
\`\`\`bash
|
|
1627
|
+
npx spets orchestrate init "<description>" # Start new workflow
|
|
1628
|
+
npx spets orchestrate done <taskId> # Mark step complete
|
|
1629
|
+
npx spets orchestrate clarified <taskId> '<json>' # Submit answers
|
|
1630
|
+
npx spets orchestrate approve <taskId> # Approve and continue
|
|
1631
|
+
npx spets orchestrate revise <taskId> "<feedback>" # Request revision
|
|
1632
|
+
npx spets orchestrate reject <taskId> # Stop workflow
|
|
1633
|
+
npx spets orchestrate stop <taskId> # Pause (can resume)
|
|
1634
|
+
npx spets orchestrate status <taskId> # Get current status
|
|
1635
|
+
\`\`\`
|
|
1636
|
+
|
|
1637
|
+
---
|
|
1638
|
+
|
|
1639
|
+
## Execution Flow
|
|
1248
1640
|
|
|
1249
|
-
|
|
1641
|
+
### 1. Workflow Start (FIRST ACTION - NO EXCEPTIONS)
|
|
1250
1642
|
|
|
1251
|
-
|
|
1643
|
+
When skill is loaded, **immediately** run:
|
|
1252
1644
|
|
|
1645
|
+
\`\`\`bash
|
|
1646
|
+
npx spets orchestrate init "{user_request}"
|
|
1253
1647
|
\`\`\`
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1648
|
+
|
|
1649
|
+
**JSON Output:**
|
|
1650
|
+
\`\`\`json
|
|
1651
|
+
{
|
|
1652
|
+
"type": "step",
|
|
1653
|
+
"step": "01-plan",
|
|
1654
|
+
"stepIndex": 1,
|
|
1655
|
+
"totalSteps": 2,
|
|
1656
|
+
"taskId": "mku3abc-xyz",
|
|
1657
|
+
"description": "User request here",
|
|
1658
|
+
"context": {
|
|
1659
|
+
"instruction": ".spets/steps/01-plan/instruction.md",
|
|
1660
|
+
"template": ".spets/steps/01-plan/template.md",
|
|
1661
|
+
"output": ".spets/outputs/mku3abc-xyz/01-plan.md",
|
|
1662
|
+
"revisionFeedback": null
|
|
1663
|
+
},
|
|
1664
|
+
"onComplete": "done mku3abc-xyz"
|
|
1665
|
+
}
|
|
1257
1666
|
\`\`\`
|
|
1258
1667
|
|
|
1259
|
-
|
|
1668
|
+
---
|
|
1260
1669
|
|
|
1261
|
-
|
|
1670
|
+
### 2. Document Generation (type: step)
|
|
1262
1671
|
|
|
1263
|
-
|
|
1264
|
-
2. Execute the appropriate spets CLI command using Bash
|
|
1265
|
-
3. For 'start', read the generated documents and help iterate
|
|
1672
|
+
When you receive \`type: "step"\`:
|
|
1266
1673
|
|
|
1267
|
-
|
|
1674
|
+
1. **Read the instruction file** using Read tool
|
|
1675
|
+
2. **Read the template file** (if exists) using Read tool
|
|
1676
|
+
3. **Read previous output** (if exists) using Read tool
|
|
1677
|
+
4. **Generate the document** following the instruction and template
|
|
1678
|
+
5. **Write the document** to context.output using Write tool
|
|
1679
|
+
6. **Mark step as done**:
|
|
1680
|
+
\`\`\`bash
|
|
1681
|
+
npx spets orchestrate done {taskId}
|
|
1682
|
+
\`\`\`
|
|
1268
1683
|
|
|
1269
|
-
|
|
1684
|
+
---
|
|
1270
1685
|
|
|
1271
|
-
|
|
1272
|
-
2. Read the generated plan document from .spets/outputs/<taskId>/
|
|
1273
|
-
3. Present the plan to the user
|
|
1274
|
-
4. If user approves, continue. If they want changes, provide feedback.
|
|
1686
|
+
### 3. Question Resolution (type: checkpoint, checkpoint: clarify)
|
|
1275
1687
|
|
|
1276
|
-
|
|
1688
|
+
When you receive \`type: "checkpoint"\` with \`checkpoint: "clarify"\`:
|
|
1277
1689
|
|
|
1278
|
-
1.
|
|
1279
|
-
2.
|
|
1690
|
+
1. **Use AskUserQuestion** tool to ask the questions
|
|
1691
|
+
2. **Collect answers** into JSON format
|
|
1692
|
+
3. **Submit answers**:
|
|
1693
|
+
\`\`\`bash
|
|
1694
|
+
npx spets orchestrate clarified {taskId} '[{"questionId":"q1","answer":"..."}]'
|
|
1695
|
+
\`\`\`
|
|
1280
1696
|
|
|
1281
|
-
|
|
1697
|
+
---
|
|
1282
1698
|
|
|
1283
|
-
|
|
1284
|
-
2. Continue the workflow from where it paused
|
|
1699
|
+
### 4. Approval Request (type: checkpoint, checkpoint: approve)
|
|
1285
1700
|
|
|
1286
|
-
|
|
1701
|
+
When you receive \`type: "checkpoint"\` with \`checkpoint: "approve"\`:
|
|
1702
|
+
|
|
1703
|
+
1. **Display document summary** from specPath using Read tool
|
|
1704
|
+
2. **Use AskUserQuestion** with options:
|
|
1705
|
+
- Approve & Continue
|
|
1706
|
+
- Revise (with feedback)
|
|
1707
|
+
- Reject
|
|
1708
|
+
- Stop (pause for later)
|
|
1709
|
+
|
|
1710
|
+
3. **Execute the selected action**:
|
|
1711
|
+
- **Approve**: \`npx spets orchestrate approve {taskId}\`
|
|
1712
|
+
- **Revise**: \`npx spets orchestrate revise {taskId} "{feedback}"\`
|
|
1713
|
+
- **Reject**: \`npx spets orchestrate reject {taskId}\`
|
|
1714
|
+
- **Stop**: \`npx spets orchestrate stop {taskId}\`
|
|
1715
|
+
|
|
1716
|
+
---
|
|
1717
|
+
|
|
1718
|
+
### 5. Workflow Complete (type: complete)
|
|
1719
|
+
|
|
1720
|
+
Display completion message based on status:
|
|
1721
|
+
- \`completed\`: Show success and list outputs
|
|
1722
|
+
- \`stopped\`: Show resume instructions
|
|
1723
|
+
- \`rejected\`: Show rejection message
|
|
1287
1724
|
|
|
1288
|
-
|
|
1725
|
+
---
|
|
1289
1726
|
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1727
|
+
## Response Type Actions Summary
|
|
1728
|
+
|
|
1729
|
+
| Type | Checkpoint | Action |
|
|
1730
|
+
|------|------------|--------|
|
|
1731
|
+
| step | - | Generate document, write to output, call done |
|
|
1732
|
+
| checkpoint | clarify | Ask questions, call clarified with answers |
|
|
1733
|
+
| checkpoint | approve | Show doc, ask for decision, call appropriate command |
|
|
1734
|
+
| complete | - | Show final message, end skill |
|
|
1735
|
+
| error | - | Show error, end skill |
|
|
1736
|
+
|
|
1737
|
+
---
|
|
1738
|
+
|
|
1739
|
+
## Critical Implementation Rules
|
|
1740
|
+
|
|
1741
|
+
1. **ORCHESTRATOR FIRST** - Always call orchestrator before any action
|
|
1742
|
+
2. **ONE STEP AT A TIME** - Complete one step, get approval, then next
|
|
1743
|
+
3. **DETERMINISTIC FLOW** - Follow the exact state machine transitions
|
|
1744
|
+
4. **NO PROCESS SPAWN FOR AI** - Generate documents in current session
|
|
1745
|
+
5. **WAIT FOR APPROVAL** - Never proceed without user approval
|
|
1746
|
+
|
|
1747
|
+
---
|
|
1748
|
+
|
|
1749
|
+
## Error Handling
|
|
1750
|
+
|
|
1751
|
+
- If \`.spets/\` not found, suggest running \`npx spets init\`
|
|
1752
|
+
- If orchestrator returns \`type: "error"\`, display the error and stop
|
|
1753
|
+
- If file read fails, show error and allow retry
|
|
1296
1754
|
|
|
1297
1755
|
$ARGUMENTS
|
|
1298
|
-
|
|
1299
|
-
args: Additional arguments for the command
|
|
1756
|
+
request: The user's task description for the workflow
|
|
1300
1757
|
`;
|
|
1301
1758
|
}
|
|
1302
1759
|
|
|
1303
1760
|
// src/commands/github.ts
|
|
1761
|
+
import { execSync as execSync4 } from "child_process";
|
|
1762
|
+
function getGitHubInfo2(cwd) {
|
|
1763
|
+
const config = getGitHubConfig(cwd);
|
|
1764
|
+
if (config?.owner && config?.repo) {
|
|
1765
|
+
return { owner: config.owner, repo: config.repo };
|
|
1766
|
+
}
|
|
1767
|
+
try {
|
|
1768
|
+
const remoteUrl = execSync4("git remote get-url origin", { encoding: "utf-8", cwd }).trim();
|
|
1769
|
+
const sshMatch = remoteUrl.match(/git@github\.com:([^/]+)\/(.+?)(?:\.git)?$/);
|
|
1770
|
+
if (sshMatch) {
|
|
1771
|
+
return { owner: sshMatch[1], repo: sshMatch[2] };
|
|
1772
|
+
}
|
|
1773
|
+
const httpsMatch = remoteUrl.match(/https:\/\/github\.com\/([^/]+)\/(.+?)(?:\.git)?$/);
|
|
1774
|
+
if (httpsMatch) {
|
|
1775
|
+
return { owner: httpsMatch[1], repo: httpsMatch[2] };
|
|
1776
|
+
}
|
|
1777
|
+
} catch {
|
|
1778
|
+
}
|
|
1779
|
+
return null;
|
|
1780
|
+
}
|
|
1304
1781
|
async function githubCommand(options) {
|
|
1305
1782
|
const cwd = process.cwd();
|
|
1306
1783
|
if (!spetsExists(cwd)) {
|
|
1307
1784
|
console.error("Spets not initialized.");
|
|
1308
1785
|
process.exit(1);
|
|
1309
1786
|
}
|
|
1310
|
-
const
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
const pr = options.pr || (configGitHub?.defaultPr ? String(configGitHub.defaultPr) : void 0);
|
|
1314
|
-
const issue = options.issue || (configGitHub?.defaultIssue ? String(configGitHub.defaultIssue) : void 0);
|
|
1315
|
-
const { comment, task } = options;
|
|
1316
|
-
if (!owner || !repo) {
|
|
1317
|
-
console.error("GitHub requires --owner and --repo (or set in .spets/config.yml)");
|
|
1787
|
+
const githubInfo = getGitHubInfo2(cwd);
|
|
1788
|
+
if (!githubInfo) {
|
|
1789
|
+
console.error("Could not determine GitHub owner/repo. Set in .spets/config.yml or add git remote.");
|
|
1318
1790
|
process.exit(1);
|
|
1319
1791
|
}
|
|
1792
|
+
const { owner, repo } = githubInfo;
|
|
1793
|
+
const { pr, issue, comment, task } = options;
|
|
1320
1794
|
if (!pr && !issue) {
|
|
1321
|
-
console.error("Either --pr or --issue is required
|
|
1795
|
+
console.error("Either --pr or --issue is required");
|
|
1322
1796
|
process.exit(1);
|
|
1323
1797
|
}
|
|
1324
1798
|
const parsed = parseGitHubCommand(comment);
|
|
@@ -1336,7 +1810,7 @@ async function githubCommand(options) {
|
|
|
1336
1810
|
}
|
|
1337
1811
|
if (!taskId) {
|
|
1338
1812
|
const config2 = loadConfig(cwd);
|
|
1339
|
-
const { listTasks: listTasks2 } = await import("./state-
|
|
1813
|
+
const { listTasks: listTasks2 } = await import("./state-JLYPJWUT.js");
|
|
1340
1814
|
const tasks = listTasks2(cwd);
|
|
1341
1815
|
for (const tid of tasks) {
|
|
1342
1816
|
const state2 = getWorkflowState(tid, config2, cwd);
|
|
@@ -1461,13 +1935,476 @@ async function postComment(config, body) {
|
|
|
1461
1935
|
});
|
|
1462
1936
|
}
|
|
1463
1937
|
|
|
1938
|
+
// src/orchestrator/index.ts
|
|
1939
|
+
import { readFileSync, writeFileSync as writeFileSync3, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
|
|
1940
|
+
import { join as join4, dirname as dirname2 } from "path";
|
|
1941
|
+
import matter from "gray-matter";
|
|
1942
|
+
var Orchestrator = class {
|
|
1943
|
+
cwd;
|
|
1944
|
+
constructor(cwd = process.cwd()) {
|
|
1945
|
+
this.cwd = cwd;
|
|
1946
|
+
}
|
|
1947
|
+
// ===========================================================================
|
|
1948
|
+
// Config & Path Helpers
|
|
1949
|
+
// ===========================================================================
|
|
1950
|
+
getSteps() {
|
|
1951
|
+
const config = loadConfig(this.cwd);
|
|
1952
|
+
return config.steps;
|
|
1953
|
+
}
|
|
1954
|
+
getOutputPath() {
|
|
1955
|
+
return getOutputsDir(this.cwd);
|
|
1956
|
+
}
|
|
1957
|
+
getStatePath(taskId) {
|
|
1958
|
+
return join4(this.getOutputPath(), taskId, ".state.json");
|
|
1959
|
+
}
|
|
1960
|
+
getSpecPath(taskId, step) {
|
|
1961
|
+
return join4(this.getOutputPath(), taskId, `${step}.md`);
|
|
1962
|
+
}
|
|
1963
|
+
getStepInstructionPath(step) {
|
|
1964
|
+
return join4(getStepsDir(this.cwd), step, "instruction.md");
|
|
1965
|
+
}
|
|
1966
|
+
getStepTemplatePath(step) {
|
|
1967
|
+
return join4(getStepsDir(this.cwd), step, "template.md");
|
|
1968
|
+
}
|
|
1969
|
+
// ===========================================================================
|
|
1970
|
+
// State Management
|
|
1971
|
+
// ===========================================================================
|
|
1972
|
+
loadState(taskId) {
|
|
1973
|
+
const statePath = this.getStatePath(taskId);
|
|
1974
|
+
if (!existsSync4(statePath)) {
|
|
1975
|
+
return null;
|
|
1976
|
+
}
|
|
1977
|
+
const data = JSON.parse(readFileSync(statePath, "utf-8"));
|
|
1978
|
+
return data;
|
|
1979
|
+
}
|
|
1980
|
+
saveState(state) {
|
|
1981
|
+
state.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1982
|
+
const statePath = this.getStatePath(state.taskId);
|
|
1983
|
+
const dir = dirname2(statePath);
|
|
1984
|
+
if (!existsSync4(dir)) {
|
|
1985
|
+
mkdirSync3(dir, { recursive: true });
|
|
1986
|
+
}
|
|
1987
|
+
writeFileSync3(statePath, JSON.stringify(state, null, 2));
|
|
1988
|
+
}
|
|
1989
|
+
// ===========================================================================
|
|
1990
|
+
// Spec Helpers
|
|
1991
|
+
// ===========================================================================
|
|
1992
|
+
checkUnresolvedQuestions(specPath) {
|
|
1993
|
+
if (!existsSync4(specPath)) {
|
|
1994
|
+
return [];
|
|
1995
|
+
}
|
|
1996
|
+
const content = readFileSync(specPath, "utf-8");
|
|
1997
|
+
const { data } = matter(content);
|
|
1998
|
+
const questions = [];
|
|
1999
|
+
if (data.open_questions && Array.isArray(data.open_questions)) {
|
|
2000
|
+
for (let i = 0; i < data.open_questions.length; i++) {
|
|
2001
|
+
const q = data.open_questions[i];
|
|
2002
|
+
if (!q.resolved) {
|
|
2003
|
+
questions.push({
|
|
2004
|
+
id: `q${i + 1}`,
|
|
2005
|
+
question: q.question || q.id || `Question ${i + 1}`,
|
|
2006
|
+
context: q.context,
|
|
2007
|
+
options: q.options,
|
|
2008
|
+
resolved: false
|
|
2009
|
+
});
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
}
|
|
2013
|
+
return questions;
|
|
2014
|
+
}
|
|
2015
|
+
generateTaskId(description) {
|
|
2016
|
+
const timestamp = Date.now().toString(36);
|
|
2017
|
+
const random = Math.random().toString(36).substring(2, 6);
|
|
2018
|
+
return `${timestamp}-${random}`;
|
|
2019
|
+
}
|
|
2020
|
+
// ===========================================================================
|
|
2021
|
+
// Protocol Response Builders
|
|
2022
|
+
// ===========================================================================
|
|
2023
|
+
responseStep(state) {
|
|
2024
|
+
const steps = this.getSteps();
|
|
2025
|
+
const outputPath = this.getOutputPath();
|
|
2026
|
+
let previousOutput;
|
|
2027
|
+
if (state.stepIndex > 1) {
|
|
2028
|
+
const prevStep = steps[state.stepIndex - 2];
|
|
2029
|
+
const prevPath = join4(outputPath, state.taskId, `${prevStep}.md`);
|
|
2030
|
+
if (existsSync4(prevPath)) {
|
|
2031
|
+
previousOutput = prevPath;
|
|
2032
|
+
}
|
|
2033
|
+
}
|
|
2034
|
+
const templatePath = this.getStepTemplatePath(state.currentStep);
|
|
2035
|
+
const hasTemplate = existsSync4(templatePath);
|
|
2036
|
+
return {
|
|
2037
|
+
type: "step",
|
|
2038
|
+
step: state.currentStep,
|
|
2039
|
+
stepIndex: state.stepIndex,
|
|
2040
|
+
totalSteps: state.totalSteps,
|
|
2041
|
+
taskId: state.taskId,
|
|
2042
|
+
description: state.description,
|
|
2043
|
+
context: {
|
|
2044
|
+
instruction: this.getStepInstructionPath(state.currentStep),
|
|
2045
|
+
template: hasTemplate ? templatePath : void 0,
|
|
2046
|
+
previousOutput,
|
|
2047
|
+
output: join4(outputPath, state.taskId, `${state.currentStep}.md`),
|
|
2048
|
+
revisionFeedback: state.revisionFeedback
|
|
2049
|
+
},
|
|
2050
|
+
onComplete: `done ${state.taskId}`
|
|
2051
|
+
};
|
|
2052
|
+
}
|
|
2053
|
+
responseCheckpointClarify(state, questions) {
|
|
2054
|
+
return {
|
|
2055
|
+
type: "checkpoint",
|
|
2056
|
+
checkpoint: "clarify",
|
|
2057
|
+
taskId: state.taskId,
|
|
2058
|
+
step: state.currentStep,
|
|
2059
|
+
questions: questions.map((q) => ({
|
|
2060
|
+
id: q.id,
|
|
2061
|
+
question: q.question,
|
|
2062
|
+
context: q.context,
|
|
2063
|
+
options: q.options
|
|
2064
|
+
})),
|
|
2065
|
+
onComplete: `clarified ${state.taskId} '<answers_json>'`
|
|
2066
|
+
};
|
|
2067
|
+
}
|
|
2068
|
+
responseCheckpointApprove(state) {
|
|
2069
|
+
const outputPath = this.getOutputPath();
|
|
2070
|
+
return {
|
|
2071
|
+
type: "checkpoint",
|
|
2072
|
+
checkpoint: "approve",
|
|
2073
|
+
taskId: state.taskId,
|
|
2074
|
+
step: state.currentStep,
|
|
2075
|
+
stepIndex: state.stepIndex,
|
|
2076
|
+
totalSteps: state.totalSteps,
|
|
2077
|
+
specPath: join4(outputPath, state.taskId, `${state.currentStep}.md`),
|
|
2078
|
+
options: ["approve", "revise", "reject", "stop"],
|
|
2079
|
+
onComplete: {
|
|
2080
|
+
approve: `approve ${state.taskId}`,
|
|
2081
|
+
revise: `revise ${state.taskId} '<feedback>'`,
|
|
2082
|
+
reject: `reject ${state.taskId}`,
|
|
2083
|
+
stop: `stop ${state.taskId}`
|
|
2084
|
+
}
|
|
2085
|
+
};
|
|
2086
|
+
}
|
|
2087
|
+
responseComplete(state, status) {
|
|
2088
|
+
const steps = this.getSteps();
|
|
2089
|
+
const outputPath = this.getOutputPath();
|
|
2090
|
+
const outputs = [];
|
|
2091
|
+
for (let i = 0; i < state.stepIndex; i++) {
|
|
2092
|
+
const specPath = join4(outputPath, state.taskId, `${steps[i]}.md`);
|
|
2093
|
+
if (existsSync4(specPath)) {
|
|
2094
|
+
outputs.push(specPath);
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
const messages = {
|
|
2098
|
+
completed: "Workflow completed successfully",
|
|
2099
|
+
stopped: `Workflow stopped. Resume with: spets resume --task ${state.taskId}`,
|
|
2100
|
+
rejected: "Workflow rejected"
|
|
2101
|
+
};
|
|
2102
|
+
return {
|
|
2103
|
+
type: "complete",
|
|
2104
|
+
status,
|
|
2105
|
+
taskId: state.taskId,
|
|
2106
|
+
outputs,
|
|
2107
|
+
message: messages[status]
|
|
2108
|
+
};
|
|
2109
|
+
}
|
|
2110
|
+
responseError(error, taskId, step) {
|
|
2111
|
+
const response = { type: "error", error };
|
|
2112
|
+
if (taskId) response.taskId = taskId;
|
|
2113
|
+
if (step) response.step = step;
|
|
2114
|
+
return response;
|
|
2115
|
+
}
|
|
2116
|
+
// ===========================================================================
|
|
2117
|
+
// Command Handlers
|
|
2118
|
+
// ===========================================================================
|
|
2119
|
+
/**
|
|
2120
|
+
* Initialize a new workflow
|
|
2121
|
+
*/
|
|
2122
|
+
cmdInit(description) {
|
|
2123
|
+
try {
|
|
2124
|
+
const steps = this.getSteps();
|
|
2125
|
+
const taskId = this.generateTaskId(description);
|
|
2126
|
+
const state = {
|
|
2127
|
+
taskId,
|
|
2128
|
+
description,
|
|
2129
|
+
currentStep: steps[0],
|
|
2130
|
+
stepIndex: 1,
|
|
2131
|
+
totalSteps: steps.length,
|
|
2132
|
+
status: "awaiting_spec",
|
|
2133
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2134
|
+
};
|
|
2135
|
+
this.saveState(state);
|
|
2136
|
+
return this.responseStep(state);
|
|
2137
|
+
} catch (e) {
|
|
2138
|
+
return this.responseError(e.message);
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2141
|
+
/**
|
|
2142
|
+
* Mark current step as done, check for questions or go to approve
|
|
2143
|
+
*/
|
|
2144
|
+
cmdDone(taskId) {
|
|
2145
|
+
const state = this.loadState(taskId);
|
|
2146
|
+
if (!state) {
|
|
2147
|
+
return this.responseError(`No workflow found: ${taskId}`, taskId);
|
|
2148
|
+
}
|
|
2149
|
+
const specPath = this.getSpecPath(taskId, state.currentStep);
|
|
2150
|
+
if (!existsSync4(specPath)) {
|
|
2151
|
+
return this.responseError(`Spec not found: ${specPath}`, taskId, state.currentStep);
|
|
2152
|
+
}
|
|
2153
|
+
const questions = this.checkUnresolvedQuestions(specPath);
|
|
2154
|
+
if (questions.length > 0) {
|
|
2155
|
+
state.status = "awaiting_clarify";
|
|
2156
|
+
this.saveState(state);
|
|
2157
|
+
return this.responseCheckpointClarify(state, questions);
|
|
2158
|
+
}
|
|
2159
|
+
state.status = "awaiting_approve";
|
|
2160
|
+
this.saveState(state);
|
|
2161
|
+
return this.responseCheckpointApprove(state);
|
|
2162
|
+
}
|
|
2163
|
+
/**
|
|
2164
|
+
* Submit clarification answers
|
|
2165
|
+
*/
|
|
2166
|
+
cmdClarified(taskId, answers) {
|
|
2167
|
+
const state = this.loadState(taskId);
|
|
2168
|
+
if (!state) {
|
|
2169
|
+
return this.responseError(`No workflow found: ${taskId}`, taskId);
|
|
2170
|
+
}
|
|
2171
|
+
const specPath = this.getSpecPath(taskId, state.currentStep);
|
|
2172
|
+
if (existsSync4(specPath)) {
|
|
2173
|
+
const content = readFileSync(specPath, "utf-8");
|
|
2174
|
+
const { content: body, data } = matter(content);
|
|
2175
|
+
if (data.open_questions && Array.isArray(data.open_questions)) {
|
|
2176
|
+
data.open_questions = data.open_questions.map((q, i) => ({
|
|
2177
|
+
...q,
|
|
2178
|
+
resolved: true,
|
|
2179
|
+
answer: answers.find((a) => a.questionId === `q${i + 1}`)?.answer
|
|
2180
|
+
}));
|
|
2181
|
+
}
|
|
2182
|
+
writeFileSync3(specPath, matter.stringify(body, data));
|
|
2183
|
+
}
|
|
2184
|
+
state.status = "awaiting_spec";
|
|
2185
|
+
state.revisionFeedback = `Answers provided: ${JSON.stringify(answers)}`;
|
|
2186
|
+
this.saveState(state);
|
|
2187
|
+
return this.responseStep(state);
|
|
2188
|
+
}
|
|
2189
|
+
/**
|
|
2190
|
+
* Approve current step and move to next
|
|
2191
|
+
*/
|
|
2192
|
+
cmdApprove(taskId) {
|
|
2193
|
+
const state = this.loadState(taskId);
|
|
2194
|
+
if (!state) {
|
|
2195
|
+
return this.responseError(`No workflow found: ${taskId}`, taskId);
|
|
2196
|
+
}
|
|
2197
|
+
const steps = this.getSteps();
|
|
2198
|
+
const specPath = this.getSpecPath(taskId, state.currentStep);
|
|
2199
|
+
if (existsSync4(specPath)) {
|
|
2200
|
+
const content = readFileSync(specPath, "utf-8");
|
|
2201
|
+
const { content: body, data } = matter(content);
|
|
2202
|
+
data.status = "approved";
|
|
2203
|
+
data.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
2204
|
+
writeFileSync3(specPath, matter.stringify(body, data));
|
|
2205
|
+
}
|
|
2206
|
+
if (state.stepIndex < state.totalSteps) {
|
|
2207
|
+
state.currentStep = steps[state.stepIndex];
|
|
2208
|
+
state.stepIndex += 1;
|
|
2209
|
+
state.status = "awaiting_spec";
|
|
2210
|
+
state.revisionFeedback = void 0;
|
|
2211
|
+
this.saveState(state);
|
|
2212
|
+
return this.responseStep(state);
|
|
2213
|
+
} else {
|
|
2214
|
+
state.status = "completed";
|
|
2215
|
+
this.saveState(state);
|
|
2216
|
+
return this.responseComplete(state, "completed");
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
/**
|
|
2220
|
+
* Request revision with feedback
|
|
2221
|
+
*/
|
|
2222
|
+
cmdRevise(taskId, feedback) {
|
|
2223
|
+
const state = this.loadState(taskId);
|
|
2224
|
+
if (!state) {
|
|
2225
|
+
return this.responseError(`No workflow found: ${taskId}`, taskId);
|
|
2226
|
+
}
|
|
2227
|
+
state.status = "awaiting_spec";
|
|
2228
|
+
state.revisionFeedback = feedback;
|
|
2229
|
+
this.saveState(state);
|
|
2230
|
+
return this.responseStep(state);
|
|
2231
|
+
}
|
|
2232
|
+
/**
|
|
2233
|
+
* Reject and stop workflow
|
|
2234
|
+
*/
|
|
2235
|
+
cmdReject(taskId) {
|
|
2236
|
+
const state = this.loadState(taskId);
|
|
2237
|
+
if (!state) {
|
|
2238
|
+
return this.responseError(`No workflow found: ${taskId}`, taskId);
|
|
2239
|
+
}
|
|
2240
|
+
const specPath = this.getSpecPath(taskId, state.currentStep);
|
|
2241
|
+
if (existsSync4(specPath)) {
|
|
2242
|
+
const content = readFileSync(specPath, "utf-8");
|
|
2243
|
+
const { content: body, data } = matter(content);
|
|
2244
|
+
data.status = "rejected";
|
|
2245
|
+
data.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
2246
|
+
writeFileSync3(specPath, matter.stringify(body, data));
|
|
2247
|
+
}
|
|
2248
|
+
state.status = "rejected";
|
|
2249
|
+
this.saveState(state);
|
|
2250
|
+
return this.responseComplete(state, "rejected");
|
|
2251
|
+
}
|
|
2252
|
+
/**
|
|
2253
|
+
* Stop workflow (can resume later)
|
|
2254
|
+
*/
|
|
2255
|
+
cmdStop(taskId) {
|
|
2256
|
+
const state = this.loadState(taskId);
|
|
2257
|
+
if (!state) {
|
|
2258
|
+
return this.responseError(`No workflow found: ${taskId}`, taskId);
|
|
2259
|
+
}
|
|
2260
|
+
state.status = "stopped";
|
|
2261
|
+
this.saveState(state);
|
|
2262
|
+
return this.responseComplete(state, "stopped");
|
|
2263
|
+
}
|
|
2264
|
+
/**
|
|
2265
|
+
* Get current workflow status
|
|
2266
|
+
*/
|
|
2267
|
+
cmdStatus(taskId) {
|
|
2268
|
+
const state = this.loadState(taskId);
|
|
2269
|
+
if (!state) {
|
|
2270
|
+
return this.responseError(`No workflow found: ${taskId}`, taskId);
|
|
2271
|
+
}
|
|
2272
|
+
switch (state.status) {
|
|
2273
|
+
case "awaiting_spec":
|
|
2274
|
+
return this.responseStep(state);
|
|
2275
|
+
case "awaiting_clarify": {
|
|
2276
|
+
const questions = this.checkUnresolvedQuestions(this.getSpecPath(taskId, state.currentStep));
|
|
2277
|
+
return this.responseCheckpointClarify(state, questions);
|
|
2278
|
+
}
|
|
2279
|
+
case "awaiting_approve":
|
|
2280
|
+
return this.responseCheckpointApprove(state);
|
|
2281
|
+
case "completed":
|
|
2282
|
+
case "stopped":
|
|
2283
|
+
case "rejected":
|
|
2284
|
+
return this.responseComplete(state, state.status);
|
|
2285
|
+
default:
|
|
2286
|
+
return this.responseError(`Unknown status: ${state.status}`, taskId);
|
|
2287
|
+
}
|
|
2288
|
+
}
|
|
2289
|
+
};
|
|
2290
|
+
|
|
2291
|
+
// src/commands/orchestrate.ts
|
|
2292
|
+
function outputJSON(data) {
|
|
2293
|
+
console.log(JSON.stringify(data, null, 2));
|
|
2294
|
+
}
|
|
2295
|
+
function outputError(error) {
|
|
2296
|
+
console.log(JSON.stringify({ type: "error", error }, null, 2));
|
|
2297
|
+
process.exit(1);
|
|
2298
|
+
}
|
|
2299
|
+
async function orchestrateCommand(action, args) {
|
|
2300
|
+
try {
|
|
2301
|
+
const orchestrator = new Orchestrator();
|
|
2302
|
+
switch (action) {
|
|
2303
|
+
case "init": {
|
|
2304
|
+
const description = args[0];
|
|
2305
|
+
if (!description) {
|
|
2306
|
+
outputError("Description is required for init");
|
|
2307
|
+
return;
|
|
2308
|
+
}
|
|
2309
|
+
const result = orchestrator.cmdInit(description);
|
|
2310
|
+
outputJSON(result);
|
|
2311
|
+
break;
|
|
2312
|
+
}
|
|
2313
|
+
case "done": {
|
|
2314
|
+
const taskId = args[0];
|
|
2315
|
+
if (!taskId) {
|
|
2316
|
+
outputError("Task ID is required for done");
|
|
2317
|
+
return;
|
|
2318
|
+
}
|
|
2319
|
+
const result = orchestrator.cmdDone(taskId);
|
|
2320
|
+
outputJSON(result);
|
|
2321
|
+
break;
|
|
2322
|
+
}
|
|
2323
|
+
case "clarified": {
|
|
2324
|
+
const taskId = args[0];
|
|
2325
|
+
const answersJson = args[1];
|
|
2326
|
+
if (!taskId || !answersJson) {
|
|
2327
|
+
outputError("Task ID and answers JSON are required for clarified");
|
|
2328
|
+
return;
|
|
2329
|
+
}
|
|
2330
|
+
let answers;
|
|
2331
|
+
try {
|
|
2332
|
+
answers = JSON.parse(answersJson);
|
|
2333
|
+
} catch {
|
|
2334
|
+
outputError("Invalid JSON for answers");
|
|
2335
|
+
return;
|
|
2336
|
+
}
|
|
2337
|
+
const result = orchestrator.cmdClarified(taskId, answers);
|
|
2338
|
+
outputJSON(result);
|
|
2339
|
+
break;
|
|
2340
|
+
}
|
|
2341
|
+
case "approve": {
|
|
2342
|
+
const taskId = args[0];
|
|
2343
|
+
if (!taskId) {
|
|
2344
|
+
outputError("Task ID is required for approve");
|
|
2345
|
+
return;
|
|
2346
|
+
}
|
|
2347
|
+
const result = orchestrator.cmdApprove(taskId);
|
|
2348
|
+
outputJSON(result);
|
|
2349
|
+
break;
|
|
2350
|
+
}
|
|
2351
|
+
case "revise": {
|
|
2352
|
+
const taskId = args[0];
|
|
2353
|
+
const feedback = args[1];
|
|
2354
|
+
if (!taskId || !feedback) {
|
|
2355
|
+
outputError("Task ID and feedback are required for revise");
|
|
2356
|
+
return;
|
|
2357
|
+
}
|
|
2358
|
+
const result = orchestrator.cmdRevise(taskId, feedback);
|
|
2359
|
+
outputJSON(result);
|
|
2360
|
+
break;
|
|
2361
|
+
}
|
|
2362
|
+
case "reject": {
|
|
2363
|
+
const taskId = args[0];
|
|
2364
|
+
if (!taskId) {
|
|
2365
|
+
outputError("Task ID is required for reject");
|
|
2366
|
+
return;
|
|
2367
|
+
}
|
|
2368
|
+
const result = orchestrator.cmdReject(taskId);
|
|
2369
|
+
outputJSON(result);
|
|
2370
|
+
break;
|
|
2371
|
+
}
|
|
2372
|
+
case "stop": {
|
|
2373
|
+
const taskId = args[0];
|
|
2374
|
+
if (!taskId) {
|
|
2375
|
+
outputError("Task ID is required for stop");
|
|
2376
|
+
return;
|
|
2377
|
+
}
|
|
2378
|
+
const result = orchestrator.cmdStop(taskId);
|
|
2379
|
+
outputJSON(result);
|
|
2380
|
+
break;
|
|
2381
|
+
}
|
|
2382
|
+
case "status": {
|
|
2383
|
+
const taskId = args[0];
|
|
2384
|
+
if (!taskId) {
|
|
2385
|
+
outputError("Task ID is required for status");
|
|
2386
|
+
return;
|
|
2387
|
+
}
|
|
2388
|
+
const result = orchestrator.cmdStatus(taskId);
|
|
2389
|
+
outputJSON(result);
|
|
2390
|
+
break;
|
|
2391
|
+
}
|
|
2392
|
+
default:
|
|
2393
|
+
outputError(`Unknown action: ${action}. Valid actions: init, done, clarified, approve, revise, reject, stop, status`);
|
|
2394
|
+
}
|
|
2395
|
+
} catch (e) {
|
|
2396
|
+
outputError(e.message);
|
|
2397
|
+
}
|
|
2398
|
+
}
|
|
2399
|
+
|
|
1464
2400
|
// src/index.ts
|
|
1465
2401
|
var program = new Command();
|
|
1466
|
-
program.name("spets").description("Spec Driven Development Execution Framework").version("0.1.
|
|
2402
|
+
program.name("spets").description("Spec Driven Development Execution Framework").version("0.1.3");
|
|
1467
2403
|
program.command("init").description("Initialize spets in current directory").option("-f, --force", "Overwrite existing config").option("--github", "Add GitHub Actions workflow for PR/Issue integration").action(initCommand);
|
|
1468
2404
|
program.command("status").description("Show current workflow status").option("-t, --task <taskId>", "Show status for specific task").action(statusCommand);
|
|
1469
|
-
program.command("start").description("Start a new workflow").argument("<query>", "User query describing the task").option("
|
|
2405
|
+
program.command("start").description("Start a new workflow").argument("<query>", "User query describing the task").option("--github", "Use GitHub platform (reads owner/repo from config or git remote)").option("--issue [number]", "Use or create GitHub Issue (optional: specify existing issue number)").option("--pr [number]", "Use or create GitHub PR (optional: specify existing PR number)").action(startCommand);
|
|
1470
2406
|
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);
|
|
1471
|
-
program.command("github").description("Handle GitHub Action callback (internal)").option("--
|
|
2407
|
+
program.command("github").description("Handle GitHub Action callback (internal)").option("--pr <number>", "PR number").option("--issue <number>", "Issue number").option("-t, --task <taskId>", "Task ID").requiredOption("--comment <comment>", "Comment body").action(githubCommand);
|
|
1472
2408
|
program.command("plugin").description("Manage plugins").argument("<action>", "Action: install, uninstall, list").argument("[name]", "Plugin name").action(pluginCommand);
|
|
2409
|
+
program.command("orchestrate").description("Workflow orchestration (JSON API for Claude Code)").argument("<action>", "Action: init, done, clarified, approve, revise, reject, stop, status").argument("[args...]", "Action arguments").action(orchestrateCommand);
|
|
1473
2410
|
program.parse();
|