spets 0.1.3 → 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 +1032 -156
- 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,7 +55,8 @@ 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);
|
|
41
61
|
createClaudeCommand(cwd);
|
|
42
62
|
console.log("Initialized spets in .spets/");
|
|
@@ -48,7 +68,8 @@ async function initCommand(options) {
|
|
|
48
68
|
console.log(" .claude/commands/spets.md - Claude Code command");
|
|
49
69
|
if (options.github) {
|
|
50
70
|
createGitHubWorkflow(cwd);
|
|
51
|
-
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");
|
|
52
73
|
}
|
|
53
74
|
console.log("");
|
|
54
75
|
console.log("Next steps:");
|
|
@@ -56,19 +77,30 @@ async function initCommand(options) {
|
|
|
56
77
|
console.log(" 2. Customize step instructions in .spets/steps/");
|
|
57
78
|
if (options.github) {
|
|
58
79
|
console.log(" 3. Add ANTHROPIC_API_KEY to your repo secrets");
|
|
59
|
-
console.log(' 4.
|
|
80
|
+
console.log(' 4. Create a new Issue using the "Spets Task" template');
|
|
60
81
|
} else {
|
|
61
82
|
console.log(' 3. Run: spets start "your task description"');
|
|
62
83
|
}
|
|
63
84
|
}
|
|
64
|
-
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
|
+
`;
|
|
65
97
|
return `# Spets Configuration
|
|
66
98
|
# Define your workflow steps here
|
|
67
99
|
|
|
68
100
|
steps:
|
|
69
101
|
- 01-plan
|
|
70
102
|
- 02-implement
|
|
71
|
-
|
|
103
|
+
${githubSection}
|
|
72
104
|
# Optional hooks (shell scripts)
|
|
73
105
|
# hooks:
|
|
74
106
|
# preStep: "./hooks/pre-step.sh"
|
|
@@ -247,82 +279,174 @@ function createClaudeCommand(cwd) {
|
|
|
247
279
|
function getClaudeCommand() {
|
|
248
280
|
return `# Spets - Spec Driven Development
|
|
249
281
|
|
|
250
|
-
|
|
282
|
+
Spec-Driven Development workflow execution skill for Claude Code.
|
|
283
|
+
|
|
284
|
+
---
|
|
251
285
|
|
|
252
|
-
##
|
|
286
|
+
## CRITICAL: Execution Model
|
|
253
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
|
|
254
308
|
\`\`\`
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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}"
|
|
258
320
|
\`\`\`
|
|
259
321
|
|
|
260
|
-
|
|
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)
|
|
261
340
|
|
|
262
|
-
|
|
341
|
+
1. **Display document summary** from specPath
|
|
342
|
+
2. **Use AskUserQuestion** with options: Approve, Revise, Reject, Stop
|
|
343
|
+
3. **Execute the selected action**
|
|
263
344
|
|
|
264
|
-
|
|
265
|
-
2. Execute the appropriate spets CLI command using Bash
|
|
266
|
-
3. For 'start', read the generated documents and help iterate
|
|
345
|
+
### 5. Workflow Complete (type: complete)
|
|
267
346
|
|
|
268
|
-
|
|
347
|
+
Display completion message based on status (completed, stopped, rejected)
|
|
269
348
|
|
|
270
|
-
|
|
271
|
-
2. Read the generated plan document from .spets/outputs/<taskId>/
|
|
272
|
-
3. Present the plan to the user
|
|
273
|
-
4. If user approves, continue. If they want changes, provide feedback.
|
|
349
|
+
---
|
|
274
350
|
|
|
275
|
-
|
|
351
|
+
## Response Type Actions Summary
|
|
276
352
|
|
|
277
|
-
|
|
278
|
-
|
|
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 |
|
|
279
360
|
|
|
280
|
-
|
|
361
|
+
---
|
|
281
362
|
|
|
282
|
-
|
|
283
|
-
|
|
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
|
+
---
|
|
284
372
|
|
|
285
373
|
## Example Session
|
|
286
374
|
|
|
287
|
-
|
|
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
|
|
288
391
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
5. On revise: \`npx spets resume --revise "user feedback"\`
|
|
392
|
+
[Skill calls: npx spets orchestrate approve mku3abc-xyz]
|
|
393
|
+
[Receives: type=step, step=02-implement]
|
|
394
|
+
|
|
395
|
+
[... continues with implementation step ...]
|
|
396
|
+
\`\`\`
|
|
295
397
|
|
|
296
398
|
$ARGUMENTS
|
|
297
|
-
|
|
298
|
-
args: Additional arguments for the command
|
|
399
|
+
description: The task description for the workflow
|
|
299
400
|
`;
|
|
300
401
|
}
|
|
301
402
|
function createGitHubWorkflow(cwd) {
|
|
302
403
|
const workflowDir = join(cwd, ".github", "workflows");
|
|
404
|
+
const templateDir = join(cwd, ".github", "ISSUE_TEMPLATE");
|
|
303
405
|
mkdirSync(workflowDir, { recursive: true });
|
|
406
|
+
mkdirSync(templateDir, { recursive: true });
|
|
304
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
|
+
`;
|
|
305
432
|
}
|
|
306
433
|
function getGitHubWorkflow() {
|
|
307
434
|
const gh = (expr) => `\${{ ${expr} }}`;
|
|
308
435
|
return `# Spets GitHub Action
|
|
309
|
-
#
|
|
436
|
+
# Handles workflow start from Issue creation and commands from comments
|
|
310
437
|
|
|
311
438
|
name: Spets Workflow
|
|
312
439
|
|
|
313
440
|
on:
|
|
441
|
+
issues:
|
|
442
|
+
types: [opened]
|
|
314
443
|
issue_comment:
|
|
315
444
|
types: [created]
|
|
316
445
|
|
|
317
446
|
jobs:
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
if:
|
|
321
|
-
contains(github.event.comment.body, '/approve') ||
|
|
322
|
-
contains(github.event.comment.body, '/revise') ||
|
|
323
|
-
contains(github.event.comment.body, '/reject') ||
|
|
324
|
-
contains(github.event.comment.body, '/answer')
|
|
325
|
-
|
|
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')
|
|
326
450
|
runs-on: ubuntu-latest
|
|
327
451
|
|
|
328
452
|
steps:
|
|
@@ -331,6 +455,30 @@ jobs:
|
|
|
331
455
|
with:
|
|
332
456
|
fetch-depth: 0
|
|
333
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
|
+
|
|
334
482
|
- name: Setup Node.js
|
|
335
483
|
uses: actions/setup-node@v4
|
|
336
484
|
with:
|
|
@@ -342,43 +490,69 @@ jobs:
|
|
|
342
490
|
- name: Install dependencies
|
|
343
491
|
run: npm ci
|
|
344
492
|
|
|
345
|
-
- name:
|
|
346
|
-
id: context
|
|
493
|
+
- name: Start Spets workflow
|
|
347
494
|
run: |
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
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
|
|
524
|
+
run: |
|
|
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")}"
|
|
352
529
|
fi
|
|
353
|
-
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
|
|
539
|
+
|
|
540
|
+
- name: Setup Node.js
|
|
541
|
+
uses: actions/setup-node@v4
|
|
542
|
+
with:
|
|
543
|
+
node-version: '20'
|
|
354
544
|
|
|
355
|
-
- name:
|
|
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
|
|
356
552
|
run: |
|
|
357
|
-
|
|
358
|
-
You are running a Spets workflow via GitHub Actions.
|
|
359
|
-
|
|
360
|
-
Context:
|
|
361
|
-
- Repository: ${gh("github.repository")}
|
|
362
|
-
- Issue/PR: #${gh("github.event.issue.number")}
|
|
363
|
-
- Comment author: ${gh("github.event.comment.user.login")}
|
|
364
|
-
- Command received: $COMMENT_BODY
|
|
365
|
-
|
|
366
|
-
Instructions:
|
|
367
|
-
1. Parse the command from the comment (/approve, /revise, /reject, or /answer)
|
|
368
|
-
2. Find the active spets task in .spets/outputs/
|
|
369
|
-
3. Execute the appropriate action:
|
|
370
|
-
- /approve: Mark current step as approved, generate next step
|
|
371
|
-
- /revise <feedback>: Regenerate current step with feedback
|
|
372
|
-
- /reject: Mark workflow as rejected
|
|
373
|
-
- /answer: Process answers and continue
|
|
374
|
-
4. If generating content (plan or implementation), actually write the code/changes
|
|
375
|
-
5. Post a summary comment to the PR/Issue using gh cli
|
|
376
|
-
6. Commit any changes with a descriptive message
|
|
377
|
-
|
|
378
|
-
Use 'gh issue comment' or 'gh pr comment' to post updates.
|
|
379
|
-
"
|
|
553
|
+
npx spets github --issue ${gh("github.event.issue.number")} --comment "$COMMENT"
|
|
380
554
|
env:
|
|
381
|
-
|
|
555
|
+
COMMENT: ${gh("github.event.comment.body")}
|
|
382
556
|
ANTHROPIC_API_KEY: ${gh("secrets.ANTHROPIC_API_KEY")}
|
|
383
557
|
GH_TOKEN: ${gh("secrets.GITHUB_TOKEN")}
|
|
384
558
|
|
|
@@ -387,7 +561,7 @@ jobs:
|
|
|
387
561
|
git config user.name "github-actions[bot]"
|
|
388
562
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
389
563
|
git add -A
|
|
390
|
-
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")}"
|
|
391
565
|
git push
|
|
392
566
|
env:
|
|
393
567
|
GH_TOKEN: ${gh("secrets.GITHUB_TOKEN")}
|
|
@@ -653,7 +827,7 @@ var Executor = class {
|
|
|
653
827
|
};
|
|
654
828
|
|
|
655
829
|
// src/platform/cli.ts
|
|
656
|
-
import { spawn as spawn2, execSync } from "child_process";
|
|
830
|
+
import { spawn as spawn2, execSync as execSync2 } from "child_process";
|
|
657
831
|
import { input, select, confirm } from "@inquirer/prompts";
|
|
658
832
|
|
|
659
833
|
// src/platform/interface.ts
|
|
@@ -805,7 +979,7 @@ var CliPlatform = class extends BasePlatform {
|
|
|
805
979
|
isClaudeInstalled() {
|
|
806
980
|
try {
|
|
807
981
|
const command = process.platform === "win32" ? "where" : "which";
|
|
808
|
-
|
|
982
|
+
execSync2(`${command} claude`, { stdio: "ignore" });
|
|
809
983
|
return true;
|
|
810
984
|
} catch {
|
|
811
985
|
return false;
|
|
@@ -1080,6 +1254,83 @@ var GitHubPlatform = class extends BasePlatform {
|
|
|
1080
1254
|
};
|
|
1081
1255
|
|
|
1082
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
|
+
}
|
|
1083
1334
|
async function startCommand(query, options) {
|
|
1084
1335
|
const cwd = process.cwd();
|
|
1085
1336
|
if (!spetsExists(cwd)) {
|
|
@@ -1088,35 +1339,67 @@ async function startCommand(query, options) {
|
|
|
1088
1339
|
}
|
|
1089
1340
|
const config = loadConfig(cwd);
|
|
1090
1341
|
const taskId = generateTaskId();
|
|
1091
|
-
console.log(`Starting new workflow: ${taskId}`);
|
|
1092
|
-
console.log(`Query: ${query}`);
|
|
1093
|
-
console.log(`Platform: ${options.platform || "cli"}`);
|
|
1094
|
-
console.log("");
|
|
1095
1342
|
saveTaskMetadata(taskId, query, cwd);
|
|
1096
1343
|
let platform;
|
|
1097
|
-
if (options.
|
|
1098
|
-
const
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
const pr = options.pr || (githubConfig?.defaultPr ? String(githubConfig.defaultPr) : void 0);
|
|
1102
|
-
const issue = options.issue || (githubConfig?.defaultIssue ? String(githubConfig.defaultIssue) : void 0);
|
|
1103
|
-
if (!owner || !repo) {
|
|
1104
|
-
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.");
|
|
1105
1348
|
process.exit(1);
|
|
1106
1349
|
}
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
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
|
+
}
|
|
1110
1384
|
}
|
|
1111
1385
|
platform = new GitHubPlatform({
|
|
1112
|
-
owner,
|
|
1113
|
-
repo,
|
|
1114
|
-
prNumber
|
|
1115
|
-
issueNumber
|
|
1386
|
+
owner: githubInfo.owner,
|
|
1387
|
+
repo: githubInfo.repo,
|
|
1388
|
+
prNumber,
|
|
1389
|
+
issueNumber
|
|
1116
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}`);
|
|
1117
1396
|
} else {
|
|
1118
1397
|
platform = new CliPlatform();
|
|
1398
|
+
console.log(`Starting workflow: ${taskId}`);
|
|
1399
|
+
console.log(`Query: ${query}`);
|
|
1400
|
+
console.log(`Platform: cli`);
|
|
1119
1401
|
}
|
|
1402
|
+
console.log("");
|
|
1120
1403
|
const executor = new Executor({ platform, config, cwd });
|
|
1121
1404
|
try {
|
|
1122
1405
|
await executor.executeWorkflow(taskId, query);
|
|
@@ -1264,23 +1547,31 @@ function installClaudePlugin() {
|
|
|
1264
1547
|
const claudeDir = join3(homedir(), ".claude");
|
|
1265
1548
|
const commandsDir = join3(claudeDir, "commands");
|
|
1266
1549
|
mkdirSync2(commandsDir, { recursive: true });
|
|
1267
|
-
const skillPath = join3(commandsDir, "
|
|
1550
|
+
const skillPath = join3(commandsDir, "sdd-do.md");
|
|
1268
1551
|
writeFileSync2(skillPath, getClaudeSkillContent());
|
|
1269
1552
|
console.log("Installed Claude Code plugin.");
|
|
1270
1553
|
console.log(`Location: ${skillPath}`);
|
|
1271
1554
|
console.log("");
|
|
1272
1555
|
console.log("Usage in Claude Code:");
|
|
1273
|
-
console.log(' /
|
|
1274
|
-
console.log(" /spets status");
|
|
1275
|
-
console.log(" /spets resume");
|
|
1556
|
+
console.log(' /sdd-do "your task description"');
|
|
1276
1557
|
console.log("");
|
|
1277
|
-
console.log("
|
|
1558
|
+
console.log("This skill runs deterministically within your Claude Code session.");
|
|
1559
|
+
console.log("No additional Claude processes are spawned.");
|
|
1278
1560
|
}
|
|
1279
1561
|
async function uninstallPlugin(name) {
|
|
1280
1562
|
if (name === "claude") {
|
|
1281
|
-
const
|
|
1282
|
-
|
|
1283
|
-
|
|
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) {
|
|
1284
1575
|
console.log("Uninstalled Claude Code plugin.");
|
|
1285
1576
|
} else {
|
|
1286
1577
|
console.log("Claude Code plugin not installed.");
|
|
@@ -1293,10 +1584,11 @@ async function uninstallPlugin(name) {
|
|
|
1293
1584
|
async function listPlugins() {
|
|
1294
1585
|
console.log("Available plugins:");
|
|
1295
1586
|
console.log("");
|
|
1296
|
-
console.log(" claude - Claude Code
|
|
1587
|
+
console.log(" claude - Claude Code /sdd-do skill");
|
|
1297
1588
|
console.log("");
|
|
1298
|
-
const
|
|
1299
|
-
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);
|
|
1300
1592
|
console.log("Installed:");
|
|
1301
1593
|
if (claudeInstalled) {
|
|
1302
1594
|
console.log(" - claude");
|
|
@@ -1305,81 +1597,202 @@ async function listPlugins() {
|
|
|
1305
1597
|
}
|
|
1306
1598
|
}
|
|
1307
1599
|
function getClaudeSkillContent() {
|
|
1308
|
-
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
|
|
1309
1607
|
|
|
1310
|
-
|
|
1608
|
+
Automatically invoked when user uses:
|
|
1609
|
+
- \`/sdd-do\` - Run SDD workflow
|
|
1311
1610
|
|
|
1312
|
-
|
|
1611
|
+
---
|
|
1313
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
|
|
1314
1635
|
\`\`\`
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1636
|
+
|
|
1637
|
+
---
|
|
1638
|
+
|
|
1639
|
+
## Execution Flow
|
|
1640
|
+
|
|
1641
|
+
### 1. Workflow Start (FIRST ACTION - NO EXCEPTIONS)
|
|
1642
|
+
|
|
1643
|
+
When skill is loaded, **immediately** run:
|
|
1644
|
+
|
|
1645
|
+
\`\`\`bash
|
|
1646
|
+
npx spets orchestrate init "{user_request}"
|
|
1647
|
+
\`\`\`
|
|
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
|
+
}
|
|
1318
1666
|
\`\`\`
|
|
1319
1667
|
|
|
1320
|
-
|
|
1668
|
+
---
|
|
1321
1669
|
|
|
1322
|
-
|
|
1670
|
+
### 2. Document Generation (type: step)
|
|
1323
1671
|
|
|
1324
|
-
|
|
1325
|
-
2. Execute the appropriate spets CLI command using Bash
|
|
1326
|
-
3. For 'start', read the generated documents and help iterate
|
|
1672
|
+
When you receive \`type: "step"\`:
|
|
1327
1673
|
|
|
1328
|
-
|
|
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
|
+
\`\`\`
|
|
1329
1683
|
|
|
1330
|
-
|
|
1684
|
+
---
|
|
1331
1685
|
|
|
1332
|
-
|
|
1333
|
-
2. Read the generated plan document from .spets/outputs/<taskId>/
|
|
1334
|
-
3. Present the plan to the user
|
|
1335
|
-
4. If user approves, continue. If they want changes, provide feedback.
|
|
1686
|
+
### 3. Question Resolution (type: checkpoint, checkpoint: clarify)
|
|
1336
1687
|
|
|
1337
|
-
|
|
1688
|
+
When you receive \`type: "checkpoint"\` with \`checkpoint: "clarify"\`:
|
|
1338
1689
|
|
|
1339
|
-
1.
|
|
1340
|
-
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
|
+
\`\`\`
|
|
1341
1696
|
|
|
1342
|
-
|
|
1697
|
+
---
|
|
1343
1698
|
|
|
1344
|
-
|
|
1345
|
-
2. Continue the workflow from where it paused
|
|
1699
|
+
### 4. Approval Request (type: checkpoint, checkpoint: approve)
|
|
1346
1700
|
|
|
1347
|
-
|
|
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
|
|
1724
|
+
|
|
1725
|
+
---
|
|
1726
|
+
|
|
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 |
|
|
1348
1736
|
|
|
1349
|
-
|
|
1737
|
+
---
|
|
1350
1738
|
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
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
|
|
1357
1754
|
|
|
1358
1755
|
$ARGUMENTS
|
|
1359
|
-
|
|
1360
|
-
args: Additional arguments for the command
|
|
1756
|
+
request: The user's task description for the workflow
|
|
1361
1757
|
`;
|
|
1362
1758
|
}
|
|
1363
1759
|
|
|
1364
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
|
+
}
|
|
1365
1781
|
async function githubCommand(options) {
|
|
1366
1782
|
const cwd = process.cwd();
|
|
1367
1783
|
if (!spetsExists(cwd)) {
|
|
1368
1784
|
console.error("Spets not initialized.");
|
|
1369
1785
|
process.exit(1);
|
|
1370
1786
|
}
|
|
1371
|
-
const
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
const pr = options.pr || (configGitHub?.defaultPr ? String(configGitHub.defaultPr) : void 0);
|
|
1375
|
-
const issue = options.issue || (configGitHub?.defaultIssue ? String(configGitHub.defaultIssue) : void 0);
|
|
1376
|
-
const { comment, task } = options;
|
|
1377
|
-
if (!owner || !repo) {
|
|
1378
|
-
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.");
|
|
1379
1790
|
process.exit(1);
|
|
1380
1791
|
}
|
|
1792
|
+
const { owner, repo } = githubInfo;
|
|
1793
|
+
const { pr, issue, comment, task } = options;
|
|
1381
1794
|
if (!pr && !issue) {
|
|
1382
|
-
console.error("Either --pr or --issue is required
|
|
1795
|
+
console.error("Either --pr or --issue is required");
|
|
1383
1796
|
process.exit(1);
|
|
1384
1797
|
}
|
|
1385
1798
|
const parsed = parseGitHubCommand(comment);
|
|
@@ -1397,7 +1810,7 @@ async function githubCommand(options) {
|
|
|
1397
1810
|
}
|
|
1398
1811
|
if (!taskId) {
|
|
1399
1812
|
const config2 = loadConfig(cwd);
|
|
1400
|
-
const { listTasks: listTasks2 } = await import("./state-
|
|
1813
|
+
const { listTasks: listTasks2 } = await import("./state-JLYPJWUT.js");
|
|
1401
1814
|
const tasks = listTasks2(cwd);
|
|
1402
1815
|
for (const tid of tasks) {
|
|
1403
1816
|
const state2 = getWorkflowState(tid, config2, cwd);
|
|
@@ -1522,13 +1935,476 @@ async function postComment(config, body) {
|
|
|
1522
1935
|
});
|
|
1523
1936
|
}
|
|
1524
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
|
+
|
|
1525
2400
|
// src/index.ts
|
|
1526
2401
|
var program = new Command();
|
|
1527
|
-
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");
|
|
1528
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);
|
|
1529
2404
|
program.command("status").description("Show current workflow status").option("-t, --task <taskId>", "Show status for specific task").action(statusCommand);
|
|
1530
|
-
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);
|
|
1531
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);
|
|
1532
|
-
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);
|
|
1533
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);
|
|
1534
2410
|
program.parse();
|