spets 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js 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-XYU22TND.js";
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
- writeFileSync(join(spetsDir, "config.yml"), getDefaultConfig());
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,27 +68,39 @@ 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 - GitHub Actions workflow");
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:");
55
76
  console.log(" 1. Edit .spets/config.yml to customize your workflow");
56
77
  console.log(" 2. Customize step instructions in .spets/steps/");
57
78
  if (options.github) {
58
- console.log(" 3. Add ANTHROPIC_API_KEY to your repo secrets");
59
- console.log(' 4. Run: spets start "task" --platform github --owner <owner> --repo <repo> --issue <n>');
79
+ console.log(" 3. Add CLAUDE_CODE_OAUTH_TOKEN to your repo secrets");
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,179 @@ function createClaudeCommand(cwd) {
247
279
  function getClaudeCommand() {
248
280
  return `# Spets - Spec Driven Development
249
281
 
250
- Run spets workflows from Claude Code.
282
+ Spec-Driven Development workflow execution skill for Claude Code.
283
+
284
+ ---
251
285
 
252
- ## Usage
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
- /spets start "task description" - Start a new workflow
256
- /spets status - Show workflow status
257
- /spets resume - Resume paused workflow
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
- ## Instructions
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)
261
346
 
262
- When the user invokes this command:
347
+ Display completion message based on status (completed, stopped, rejected)
263
348
 
264
- 1. Parse the subcommand (start, status, resume)
265
- 2. Execute the appropriate spets CLI command using Bash
266
- 3. For 'start', read the generated documents and help iterate
349
+ ---
267
350
 
268
- ### Start Flow
351
+ ## Response Type Actions Summary
269
352
 
270
- 1. Run: \`npx spets start "<query>"\`
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.
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 |
274
360
 
275
- ### Status Flow
361
+ ---
276
362
 
277
- 1. Run: \`npx spets status\`
278
- 2. Present the current workflow state
363
+ ## Critical Implementation Rules
279
364
 
280
- ### Resume Flow
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
281
370
 
282
- 1. Run: \`npx spets resume\`
283
- 2. Continue the workflow from where it paused
371
+ ---
284
372
 
285
373
  ## Example Session
286
374
 
287
- User: /spets start "Create a REST API for user management"
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?]
288
389
 
289
- Claude:
290
- 1. Runs \`npx spets start "Create a REST API for user management"\`
291
- 2. Reads the generated plan
292
- 3. Asks user: "Here's the plan. [shows plan] Would you like to approve or revise?"
293
- 4. On approve: \`npx spets resume --approve\`
294
- 5. On revise: \`npx spets resume --revise "user feedback"\`
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
+ \`\`\`
295
397
 
296
398
  $ARGUMENTS
297
- command: The spets command to run (start, status, resume)
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
- # Runs Claude Code to handle /approve, /revise, /reject commands
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
- jobs:
318
- handle-spets-command:
319
- # Only run if comment contains a spets command
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')
446
+ permissions:
447
+ contents: write
448
+ issues: write
449
+ pull-requests: write
325
450
 
451
+ jobs:
452
+ # Start workflow when a spets Issue is created
453
+ start-workflow:
454
+ if: github.event.action == 'opened' && contains(github.event.issue.labels.*.name, 'spets')
326
455
  runs-on: ubuntu-latest
327
456
 
328
457
  steps:
@@ -331,6 +460,30 @@ jobs:
331
460
  with:
332
461
  fetch-depth: 0
333
462
 
463
+ - name: Parse Issue body
464
+ id: parse
465
+ env:
466
+ ISSUE_BODY: ${gh("github.event.issue.body")}
467
+ ISSUE_NUMBER: ${gh("github.event.issue.number")}
468
+ run: |
469
+ # Parse task description
470
+ TASK=$(echo "$ISSUE_BODY" | sed -n '/### Task Description/,/###/{/###/!p;}' | sed '/^$/d' | head -1)
471
+ echo "task=$TASK" >> $GITHUB_OUTPUT
472
+
473
+ # Parse branch name (optional)
474
+ BRANCH=$(echo "$ISSUE_BODY" | sed -n '/### Branch Name/,/###/{/###/!p;}' | sed '/^$/d' | head -1)
475
+ if [ -z "$BRANCH" ]; then
476
+ BRANCH="spets/$ISSUE_NUMBER"
477
+ fi
478
+ echo "branch=$BRANCH" >> $GITHUB_OUTPUT
479
+
480
+ - name: Create and checkout branch
481
+ run: |
482
+ git checkout -b ${gh("steps.parse.outputs.branch")}
483
+ git push -u origin ${gh("steps.parse.outputs.branch")}
484
+ env:
485
+ GH_TOKEN: ${gh("secrets.GITHUB_TOKEN")}
486
+
334
487
  - name: Setup Node.js
335
488
  uses: actions/setup-node@v4
336
489
  with:
@@ -342,44 +495,70 @@ jobs:
342
495
  - name: Install dependencies
343
496
  run: npm ci
344
497
 
345
- - name: Determine context
346
- id: context
498
+ - name: Start Spets workflow
347
499
  run: |
348
- if [ -n "${gh("github.event.issue.pull_request")}" ]; then
349
- echo "type=pr" >> $GITHUB_OUTPUT
350
- else
351
- echo "type=issue" >> $GITHUB_OUTPUT
500
+ npx spets start "$TASK" --github --issue ${gh("github.event.issue.number")}
501
+ env:
502
+ TASK: ${gh("steps.parse.outputs.task")}
503
+ CLAUDE_CODE_OAUTH_TOKEN: ${gh("secrets.CLAUDE_CODE_OAUTH_TOKEN")}
504
+ GH_TOKEN: ${gh("secrets.GITHUB_TOKEN")}
505
+
506
+ - name: Push changes
507
+ run: |
508
+ git config user.name "github-actions[bot]"
509
+ git config user.email "github-actions[bot]@users.noreply.github.com"
510
+ git add -A
511
+ git diff --staged --quiet || git commit -m "Spets: Start workflow for #${gh("github.event.issue.number")}"
512
+ git push
513
+ env:
514
+ GH_TOKEN: ${gh("secrets.GITHUB_TOKEN")}
515
+
516
+ # Handle commands from Issue/PR comments
517
+ handle-command:
518
+ if: |
519
+ github.event.action == 'created' && (
520
+ contains(github.event.comment.body, '/approve') ||
521
+ contains(github.event.comment.body, '/revise') ||
522
+ contains(github.event.comment.body, '/reject')
523
+ )
524
+ runs-on: ubuntu-latest
525
+
526
+ steps:
527
+ - name: Find linked branch
528
+ id: branch
529
+ run: |
530
+ # Try to find branch linked to this issue
531
+ BRANCH=$(gh api repos/${gh("github.repository")}/issues/${gh("github.event.issue.number")} --jq '.body' | grep -oP 'spets/\\d+' || echo "")
532
+ if [ -z "$BRANCH" ]; then
533
+ BRANCH="spets/${gh("github.event.issue.number")}"
352
534
  fi
353
- echo "number=${gh("github.event.issue.number")}" >> $GITHUB_OUTPUT
535
+ echo "name=$BRANCH" >> $GITHUB_OUTPUT
536
+ env:
537
+ GH_TOKEN: ${gh("secrets.GITHUB_TOKEN")}
538
+
539
+ - name: Checkout
540
+ uses: actions/checkout@v4
541
+ with:
542
+ ref: ${gh("steps.branch.outputs.name")}
543
+ fetch-depth: 0
544
+
545
+ - name: Setup Node.js
546
+ uses: actions/setup-node@v4
547
+ with:
548
+ node-version: '20'
549
+
550
+ - name: Install Claude Code
551
+ run: npm install -g @anthropic-ai/claude-code
354
552
 
355
- - name: Run Claude Code
553
+ - name: Install dependencies
554
+ run: npm ci
555
+
556
+ - name: Run Spets command
356
557
  run: |
357
- claude -p "
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
- "
558
+ npx spets github --issue ${gh("github.event.issue.number")} --comment "$COMMENT"
380
559
  env:
381
- COMMENT_BODY: ${gh("github.event.comment.body")}
382
- ANTHROPIC_API_KEY: ${gh("secrets.ANTHROPIC_API_KEY")}
560
+ COMMENT: ${gh("github.event.comment.body")}
561
+ CLAUDE_CODE_OAUTH_TOKEN: ${gh("secrets.CLAUDE_CODE_OAUTH_TOKEN")}
383
562
  GH_TOKEN: ${gh("secrets.GITHUB_TOKEN")}
384
563
 
385
564
  - name: Push changes
@@ -387,7 +566,7 @@ jobs:
387
566
  git config user.name "github-actions[bot]"
388
567
  git config user.email "github-actions[bot]@users.noreply.github.com"
389
568
  git add -A
390
- git diff --staged --quiet || git commit -m "Spets: Update from workflow"
569
+ git diff --staged --quiet || git commit -m "Spets: Update from #${gh("github.event.issue.number")}"
391
570
  git push
392
571
  env:
393
572
  GH_TOKEN: ${gh("secrets.GITHUB_TOKEN")}
@@ -523,7 +702,7 @@ var Executor = class {
523
702
  this.config = options.config;
524
703
  this.cwd = options.cwd || process.cwd();
525
704
  }
526
- async executeWorkflow(taskId, userQuery, startIndex = 0) {
705
+ async executeWorkflow(taskId, userQuery, startIndex = 0, feedback) {
527
706
  let previousOutput;
528
707
  if (startIndex > 0) {
529
708
  const prevStepName = this.config.steps[startIndex - 1];
@@ -543,7 +722,9 @@ var Executor = class {
543
722
  instruction: stepDef.instruction,
544
723
  template: stepDef.template,
545
724
  previousOutput,
546
- outputPath: getOutputPath(taskId, stepName, this.cwd)
725
+ outputPath: getOutputPath(taskId, stepName, this.cwd),
726
+ feedback: i === startIndex ? feedback : void 0
727
+ // Only apply feedback to the target step
547
728
  };
548
729
  if (this.config.hooks?.preStep) {
549
730
  await runHook(this.config.hooks.preStep, {
@@ -653,7 +834,7 @@ var Executor = class {
653
834
  };
654
835
 
655
836
  // src/platform/cli.ts
656
- import { spawn as spawn2, execSync } from "child_process";
837
+ import { spawn as spawn2, execSync as execSync2 } from "child_process";
657
838
  import { input, select, confirm } from "@inquirer/prompts";
658
839
 
659
840
  // src/platform/interface.ts
@@ -805,7 +986,7 @@ var CliPlatform = class extends BasePlatform {
805
986
  isClaudeInstalled() {
806
987
  try {
807
988
  const command = process.platform === "win32" ? "where" : "which";
808
- execSync(`${command} claude`, { stdio: "ignore" });
989
+ execSync2(`${command} claude`, { stdio: "ignore" });
809
990
  return true;
810
991
  } catch {
811
992
  return false;
@@ -856,6 +1037,12 @@ function parseGitHubCommand(comment) {
856
1037
  if (trimmed === "/approve") {
857
1038
  return { command: "approve" };
858
1039
  }
1040
+ if (trimmed === "/approve --pr") {
1041
+ return { command: "approve", createPR: true };
1042
+ }
1043
+ if (trimmed === "/approve --issue") {
1044
+ return { command: "approve", createIssue: true };
1045
+ }
859
1046
  if (trimmed === "/reject") {
860
1047
  return { command: "reject" };
861
1048
  }
@@ -881,6 +1068,7 @@ function parseGitHubCommand(comment) {
881
1068
  var GitHubPlatform = class extends BasePlatform {
882
1069
  config;
883
1070
  currentTaskId;
1071
+ currentOutputPath;
884
1072
  constructor(config) {
885
1073
  super();
886
1074
  this.config = config;
@@ -890,6 +1078,7 @@ var GitHubPlatform = class extends BasePlatform {
890
1078
  }
891
1079
  async generateDocument(context) {
892
1080
  this.currentTaskId = context.taskId;
1081
+ this.currentOutputPath = context.outputPath;
893
1082
  const prompt = this.buildPrompt(context);
894
1083
  const response = await this.callClaude(prompt);
895
1084
  const { document, questions } = this.parseResponse(response);
@@ -903,7 +1092,7 @@ var GitHubPlatform = class extends BasePlatform {
903
1092
  throw new PauseForInputError("questions", questions);
904
1093
  }
905
1094
  async requestApproval(doc, stepName) {
906
- const comment = this.formatApprovalComment(doc, stepName, this.currentTaskId);
1095
+ const comment = this.formatApprovalComment(doc, stepName, this.currentTaskId, this.currentOutputPath);
907
1096
  await this.postComment(comment);
908
1097
  console.log("\n\u23F8\uFE0F Waiting for approval on GitHub...");
909
1098
  console.log(" Comment /approve, /revise <feedback>, or /reject on the PR/Issue.");
@@ -936,20 +1125,31 @@ var GitHubPlatform = class extends BasePlatform {
936
1125
  lines.push("");
937
1126
  }
938
1127
  lines.push("---");
939
- lines.push("**Reply with:**");
1128
+ lines.push("");
1129
+ lines.push("**How to answer:**");
1130
+ lines.push("");
940
1131
  lines.push("```");
941
1132
  lines.push("/answer");
942
1133
  for (let i = 0; i < questions.length; i++) {
943
- lines.push(`Q${i + 1}: your answer here`);
1134
+ lines.push(`Q${i + 1}: <your answer for question ${i + 1}>`);
944
1135
  }
945
1136
  lines.push("```");
1137
+ lines.push("");
1138
+ lines.push("Example:");
1139
+ lines.push("```");
1140
+ lines.push("/answer");
1141
+ lines.push("Q1: Use TypeScript");
1142
+ lines.push("Q2: Yes, add unit tests");
1143
+ lines.push("```");
946
1144
  return lines.join("\n");
947
1145
  }
948
- formatApprovalComment(doc, stepName, taskId) {
1146
+ formatApprovalComment(doc, stepName, taskId, outputPath) {
1147
+ const relativePath = outputPath ? outputPath.replace(process.cwd() + "/", "") : `.spets/outputs/${taskId}/${stepName}.md`;
949
1148
  const lines = [
950
1149
  `## \u{1F4C4} Spets: ${stepName} - Review Required`,
951
1150
  "",
952
1151
  `> Task ID: \`${taskId}\``,
1152
+ `> Output: \`${relativePath}\``,
953
1153
  "",
954
1154
  "<details>",
955
1155
  "<summary>\u{1F4DD} View Document</summary>",
@@ -966,8 +1166,12 @@ var GitHubPlatform = class extends BasePlatform {
966
1166
  "| Command | Description |",
967
1167
  "|---------|-------------|",
968
1168
  "| `/approve` | Approve and continue to next step |",
1169
+ "| `/approve --pr` | Approve and create a Pull Request |",
1170
+ "| `/approve --issue` | Approve and create/update an Issue |",
969
1171
  "| `/revise <feedback>` | Request changes with feedback |",
970
- "| `/reject` | Reject and stop workflow |"
1172
+ "| `/reject` | Reject and stop workflow |",
1173
+ "",
1174
+ "Example: `/revise Please add more details about error handling`"
971
1175
  ];
972
1176
  return lines.join("\n");
973
1177
  }
@@ -1032,12 +1236,11 @@ var GitHubPlatform = class extends BasePlatform {
1032
1236
  async callClaude(prompt) {
1033
1237
  return new Promise((resolve, reject) => {
1034
1238
  const proc = spawn3("claude", [
1035
- "--print",
1239
+ "-p",
1240
+ prompt,
1036
1241
  "--permission-mode",
1037
1242
  "bypassPermissions"
1038
- ], { stdio: ["pipe", "pipe", "pipe"] });
1039
- proc.stdin.write(prompt);
1040
- proc.stdin.end();
1243
+ ], { stdio: ["ignore", "pipe", "pipe"] });
1041
1244
  let stdout = "";
1042
1245
  let stderr = "";
1043
1246
  proc.stdout.on("data", (data) => {
@@ -1048,7 +1251,7 @@ var GitHubPlatform = class extends BasePlatform {
1048
1251
  });
1049
1252
  proc.on("close", (code) => {
1050
1253
  if (code !== 0) {
1051
- reject(new Error(`Claude CLI failed: ${stderr}`));
1254
+ reject(new Error(`Claude CLI failed (code ${code}): stderr=${stderr}, stdout=${stdout}`));
1052
1255
  } else {
1053
1256
  resolve(stdout);
1054
1257
  }
@@ -1080,6 +1283,83 @@ var GitHubPlatform = class extends BasePlatform {
1080
1283
  };
1081
1284
 
1082
1285
  // src/commands/start.ts
1286
+ import { execSync as execSync3 } from "child_process";
1287
+ function getGitHubInfo(cwd) {
1288
+ const config = getGitHubConfig(cwd);
1289
+ if (config?.owner && config?.repo) {
1290
+ return { owner: config.owner, repo: config.repo };
1291
+ }
1292
+ try {
1293
+ const remoteUrl = execSync3("git remote get-url origin", { encoding: "utf-8", cwd }).trim();
1294
+ const sshMatch = remoteUrl.match(/git@github\.com:([^/]+)\/(.+?)(?:\.git)?$/);
1295
+ if (sshMatch) {
1296
+ return { owner: sshMatch[1], repo: sshMatch[2] };
1297
+ }
1298
+ const httpsMatch = remoteUrl.match(/https:\/\/github\.com\/([^/]+)\/(.+?)(?:\.git)?$/);
1299
+ if (httpsMatch) {
1300
+ return { owner: httpsMatch[1], repo: httpsMatch[2] };
1301
+ }
1302
+ } catch {
1303
+ }
1304
+ return null;
1305
+ }
1306
+ function createGitHubIssue(info, query, taskId) {
1307
+ const body = `## Task
1308
+ ${query}
1309
+
1310
+ ## Spets Workflow
1311
+ - Task ID: \`${taskId}\`
1312
+ - Status: In Progress
1313
+
1314
+ ---
1315
+ _Managed by [Spets](https://github.com/eatnug/spets)_`;
1316
+ const result = execSync3(
1317
+ `gh issue create --repo ${info.owner}/${info.repo} --title "${query.slice(0, 50)}" --body "${body.replace(/"/g, '\\"')}" --label spets`,
1318
+ { encoding: "utf-8" }
1319
+ );
1320
+ const match = result.match(/\/issues\/(\d+)/);
1321
+ if (!match) {
1322
+ throw new Error("Failed to parse issue number from gh output");
1323
+ }
1324
+ return parseInt(match[1], 10);
1325
+ }
1326
+ function createGitHubPR(info, query, taskId) {
1327
+ const branchName = `spets/${taskId}`;
1328
+ execSync3(`git checkout -b ${branchName}`, { encoding: "utf-8" });
1329
+ execSync3(`git push -u origin ${branchName}`, { encoding: "utf-8" });
1330
+ const body = `## Task
1331
+ ${query}
1332
+
1333
+ ## Spets Workflow
1334
+ - Task ID: \`${taskId}\`
1335
+ - Status: In Progress
1336
+
1337
+ ---
1338
+ _Managed by [Spets](https://github.com/eatnug/spets)_`;
1339
+ const result = execSync3(
1340
+ `gh pr create --repo ${info.owner}/${info.repo} --title "${query.slice(0, 50)}" --body "${body.replace(/"/g, '\\"')}" --label spets`,
1341
+ { encoding: "utf-8" }
1342
+ );
1343
+ const match = result.match(/\/pull\/(\d+)/);
1344
+ if (!match) {
1345
+ throw new Error("Failed to parse PR number from gh output");
1346
+ }
1347
+ return parseInt(match[1], 10);
1348
+ }
1349
+ function findLinkedIssueOrPR(info) {
1350
+ try {
1351
+ const result = execSync3(
1352
+ `gh pr view --repo ${info.owner}/${info.repo} --json number`,
1353
+ { encoding: "utf-8" }
1354
+ );
1355
+ const pr = JSON.parse(result);
1356
+ if (pr.number) {
1357
+ return { type: "pr", number: pr.number };
1358
+ }
1359
+ } catch {
1360
+ }
1361
+ return null;
1362
+ }
1083
1363
  async function startCommand(query, options) {
1084
1364
  const cwd = process.cwd();
1085
1365
  if (!spetsExists(cwd)) {
@@ -1088,35 +1368,67 @@ async function startCommand(query, options) {
1088
1368
  }
1089
1369
  const config = loadConfig(cwd);
1090
1370
  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
1371
  saveTaskMetadata(taskId, query, cwd);
1096
1372
  let platform;
1097
- if (options.platform === "github") {
1098
- const githubConfig = getGitHubConfig(cwd);
1099
- const owner = options.owner || githubConfig?.owner;
1100
- const repo = options.repo || githubConfig?.repo;
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)");
1373
+ if (options.github || options.issue !== void 0 || options.pr !== void 0) {
1374
+ const githubInfo = getGitHubInfo(cwd);
1375
+ if (!githubInfo) {
1376
+ console.error("Could not determine GitHub owner/repo. Set in .spets/config.yml or add git remote.");
1105
1377
  process.exit(1);
1106
1378
  }
1107
- if (!pr && !issue) {
1108
- console.error("GitHub platform requires --pr or --issue (or set defaultPr/defaultIssue in .spets/config.yml)");
1109
- process.exit(1);
1379
+ let issueNumber;
1380
+ let prNumber;
1381
+ if (options.issue !== void 0) {
1382
+ if (typeof options.issue === "string") {
1383
+ issueNumber = parseInt(options.issue, 10);
1384
+ console.log(`Using existing Issue #${issueNumber}`);
1385
+ } else {
1386
+ console.log("Creating new GitHub Issue...");
1387
+ issueNumber = createGitHubIssue(githubInfo, query, taskId);
1388
+ console.log(`Created Issue #${issueNumber}`);
1389
+ }
1390
+ }
1391
+ if (options.pr !== void 0) {
1392
+ if (typeof options.pr === "string") {
1393
+ prNumber = parseInt(options.pr, 10);
1394
+ console.log(`Using existing PR #${prNumber}`);
1395
+ } else {
1396
+ console.log("Creating new GitHub PR...");
1397
+ prNumber = createGitHubPR(githubInfo, query, taskId);
1398
+ console.log(`Created PR #${prNumber}`);
1399
+ }
1400
+ }
1401
+ if (issueNumber === void 0 && prNumber === void 0) {
1402
+ const linked = findLinkedIssueOrPR(githubInfo);
1403
+ if (linked) {
1404
+ console.log(`Found linked ${linked.type} #${linked.number}`);
1405
+ if (linked.type === "pr") {
1406
+ prNumber = linked.number;
1407
+ } else {
1408
+ issueNumber = linked.number;
1409
+ }
1410
+ } else {
1411
+ console.log("No linked Issue/PR found. Running in GitHub mode without Issue/PR.");
1412
+ }
1110
1413
  }
1111
1414
  platform = new GitHubPlatform({
1112
- owner,
1113
- repo,
1114
- prNumber: pr ? parseInt(pr, 10) : void 0,
1115
- issueNumber: issue ? parseInt(issue, 10) : void 0
1415
+ owner: githubInfo.owner,
1416
+ repo: githubInfo.repo,
1417
+ prNumber,
1418
+ issueNumber
1116
1419
  });
1420
+ console.log(`Starting workflow: ${taskId}`);
1421
+ console.log(`Query: ${query}`);
1422
+ console.log(`Platform: github (${githubInfo.owner}/${githubInfo.repo})`);
1423
+ if (issueNumber) console.log(`Issue: #${issueNumber}`);
1424
+ if (prNumber) console.log(`PR: #${prNumber}`);
1117
1425
  } else {
1118
1426
  platform = new CliPlatform();
1427
+ console.log(`Starting workflow: ${taskId}`);
1428
+ console.log(`Query: ${query}`);
1429
+ console.log(`Platform: cli`);
1119
1430
  }
1431
+ console.log("");
1120
1432
  const executor = new Executor({ platform, config, cwd });
1121
1433
  try {
1122
1434
  await executor.executeWorkflow(taskId, query);
@@ -1264,23 +1576,31 @@ function installClaudePlugin() {
1264
1576
  const claudeDir = join3(homedir(), ".claude");
1265
1577
  const commandsDir = join3(claudeDir, "commands");
1266
1578
  mkdirSync2(commandsDir, { recursive: true });
1267
- const skillPath = join3(commandsDir, "spets.md");
1579
+ const skillPath = join3(commandsDir, "sdd-do.md");
1268
1580
  writeFileSync2(skillPath, getClaudeSkillContent());
1269
1581
  console.log("Installed Claude Code plugin.");
1270
1582
  console.log(`Location: ${skillPath}`);
1271
1583
  console.log("");
1272
1584
  console.log("Usage in Claude Code:");
1273
- console.log(' /spets start "your task description"');
1274
- console.log(" /spets status");
1275
- console.log(" /spets resume");
1585
+ console.log(' /sdd-do "your task description"');
1276
1586
  console.log("");
1277
- console.log("Note: The plugin uses npx to run spets, so global installation is not required.");
1587
+ console.log("This skill runs deterministically within your Claude Code session.");
1588
+ console.log("No additional Claude processes are spawned.");
1278
1589
  }
1279
1590
  async function uninstallPlugin(name) {
1280
1591
  if (name === "claude") {
1281
- const skillPath = join3(homedir(), ".claude", "commands", "spets.md");
1282
- if (existsSync3(skillPath)) {
1283
- rmSync(skillPath);
1592
+ const oldSkillPath = join3(homedir(), ".claude", "commands", "spets.md");
1593
+ const newSkillPath = join3(homedir(), ".claude", "commands", "sdd-do.md");
1594
+ let uninstalled = false;
1595
+ if (existsSync3(oldSkillPath)) {
1596
+ rmSync(oldSkillPath);
1597
+ uninstalled = true;
1598
+ }
1599
+ if (existsSync3(newSkillPath)) {
1600
+ rmSync(newSkillPath);
1601
+ uninstalled = true;
1602
+ }
1603
+ if (uninstalled) {
1284
1604
  console.log("Uninstalled Claude Code plugin.");
1285
1605
  } else {
1286
1606
  console.log("Claude Code plugin not installed.");
@@ -1293,10 +1613,11 @@ async function uninstallPlugin(name) {
1293
1613
  async function listPlugins() {
1294
1614
  console.log("Available plugins:");
1295
1615
  console.log("");
1296
- console.log(" claude - Claude Code slash command integration");
1616
+ console.log(" claude - Claude Code /sdd-do skill");
1297
1617
  console.log("");
1298
- const claudeSkillPath = join3(homedir(), ".claude", "commands", "spets.md");
1299
- const claudeInstalled = existsSync3(claudeSkillPath);
1618
+ const oldSkillPath = join3(homedir(), ".claude", "commands", "spets.md");
1619
+ const newSkillPath = join3(homedir(), ".claude", "commands", "sdd-do.md");
1620
+ const claudeInstalled = existsSync3(oldSkillPath) || existsSync3(newSkillPath);
1300
1621
  console.log("Installed:");
1301
1622
  if (claudeInstalled) {
1302
1623
  console.log(" - claude");
@@ -1305,81 +1626,202 @@ async function listPlugins() {
1305
1626
  }
1306
1627
  }
1307
1628
  function getClaudeSkillContent() {
1308
- return `# Spets - Spec Driven Development
1629
+ return `# SDD-Do Skill
1630
+
1631
+ Spec-Driven Development workflow execution skill for Claude Code.
1632
+
1633
+ ---
1634
+
1635
+ ## When to Use This Skill
1309
1636
 
1310
- Run spets workflows from Claude Code.
1637
+ Automatically invoked when user uses:
1638
+ - \`/sdd-do\` - Run SDD workflow
1311
1639
 
1312
- ## Usage
1640
+ ---
1313
1641
 
1642
+ ## CRITICAL: Execution Model
1643
+
1644
+ This skill runs **deterministically** within the current Claude Code session.
1645
+ - NO subprocess spawning for AI/LLM calls
1646
+ - The orchestrator manages state via JSON protocol
1647
+ - Each step follows a strict state machine flow
1648
+
1649
+ ---
1650
+
1651
+ ## Orchestrator Commands
1652
+
1653
+ All workflow state is managed by the orchestrator CLI:
1654
+
1655
+ \`\`\`bash
1656
+ npx spets orchestrate init "<description>" # Start new workflow
1657
+ npx spets orchestrate done <taskId> # Mark step complete
1658
+ npx spets orchestrate clarified <taskId> '<json>' # Submit answers
1659
+ npx spets orchestrate approve <taskId> # Approve and continue
1660
+ npx spets orchestrate revise <taskId> "<feedback>" # Request revision
1661
+ npx spets orchestrate reject <taskId> # Stop workflow
1662
+ npx spets orchestrate stop <taskId> # Pause (can resume)
1663
+ npx spets orchestrate status <taskId> # Get current status
1314
1664
  \`\`\`
1315
- /spets start "task description" - Start a new workflow
1316
- /spets status - Show workflow status
1317
- /spets resume - Resume paused workflow
1665
+
1666
+ ---
1667
+
1668
+ ## Execution Flow
1669
+
1670
+ ### 1. Workflow Start (FIRST ACTION - NO EXCEPTIONS)
1671
+
1672
+ When skill is loaded, **immediately** run:
1673
+
1674
+ \`\`\`bash
1675
+ npx spets orchestrate init "{user_request}"
1676
+ \`\`\`
1677
+
1678
+ **JSON Output:**
1679
+ \`\`\`json
1680
+ {
1681
+ "type": "step",
1682
+ "step": "01-plan",
1683
+ "stepIndex": 1,
1684
+ "totalSteps": 2,
1685
+ "taskId": "mku3abc-xyz",
1686
+ "description": "User request here",
1687
+ "context": {
1688
+ "instruction": ".spets/steps/01-plan/instruction.md",
1689
+ "template": ".spets/steps/01-plan/template.md",
1690
+ "output": ".spets/outputs/mku3abc-xyz/01-plan.md",
1691
+ "revisionFeedback": null
1692
+ },
1693
+ "onComplete": "done mku3abc-xyz"
1694
+ }
1318
1695
  \`\`\`
1319
1696
 
1320
- ## Instructions
1697
+ ---
1321
1698
 
1322
- When the user invokes this command:
1699
+ ### 2. Document Generation (type: step)
1323
1700
 
1324
- 1. Parse the subcommand (start, status, resume)
1325
- 2. Execute the appropriate spets CLI command using Bash
1326
- 3. For 'start', read the generated documents and help iterate
1701
+ When you receive \`type: "step"\`:
1327
1702
 
1328
- **Important**: Use \`npx spets\` to run commands. This ensures the command works even if spets is not globally installed.
1703
+ 1. **Read the instruction file** using Read tool
1704
+ 2. **Read the template file** (if exists) using Read tool
1705
+ 3. **Read previous output** (if exists) using Read tool
1706
+ 4. **Generate the document** following the instruction and template
1707
+ 5. **Write the document** to context.output using Write tool
1708
+ 6. **Mark step as done**:
1709
+ \`\`\`bash
1710
+ npx spets orchestrate done {taskId}
1711
+ \`\`\`
1329
1712
 
1330
- ### Start Flow
1713
+ ---
1331
1714
 
1332
- 1. Run: \`npx spets start "<query>"\`
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.
1715
+ ### 3. Question Resolution (type: checkpoint, checkpoint: clarify)
1336
1716
 
1337
- ### Status Flow
1717
+ When you receive \`type: "checkpoint"\` with \`checkpoint: "clarify"\`:
1338
1718
 
1339
- 1. Run: \`npx spets status\`
1340
- 2. Present the current workflow state
1719
+ 1. **Use AskUserQuestion** tool to ask the questions
1720
+ 2. **Collect answers** into JSON format
1721
+ 3. **Submit answers**:
1722
+ \`\`\`bash
1723
+ npx spets orchestrate clarified {taskId} '[{"questionId":"q1","answer":"..."}]'
1724
+ \`\`\`
1341
1725
 
1342
- ### Resume Flow
1726
+ ---
1343
1727
 
1344
- 1. Run: \`npx spets resume\`
1345
- 2. Continue the workflow from where it paused
1728
+ ### 4. Approval Request (type: checkpoint, checkpoint: approve)
1346
1729
 
1347
- ## Example Session
1730
+ When you receive \`type: "checkpoint"\` with \`checkpoint: "approve"\`:
1731
+
1732
+ 1. **Display document summary** from specPath using Read tool
1733
+ 2. **Use AskUserQuestion** with options:
1734
+ - Approve & Continue
1735
+ - Revise (with feedback)
1736
+ - Reject
1737
+ - Stop (pause for later)
1738
+
1739
+ 3. **Execute the selected action**:
1740
+ - **Approve**: \`npx spets orchestrate approve {taskId}\`
1741
+ - **Revise**: \`npx spets orchestrate revise {taskId} "{feedback}"\`
1742
+ - **Reject**: \`npx spets orchestrate reject {taskId}\`
1743
+ - **Stop**: \`npx spets orchestrate stop {taskId}\`
1744
+
1745
+ ---
1746
+
1747
+ ### 5. Workflow Complete (type: complete)
1748
+
1749
+ Display completion message based on status:
1750
+ - \`completed\`: Show success and list outputs
1751
+ - \`stopped\`: Show resume instructions
1752
+ - \`rejected\`: Show rejection message
1753
+
1754
+ ---
1755
+
1756
+ ## Response Type Actions Summary
1757
+
1758
+ | Type | Checkpoint | Action |
1759
+ |------|------------|--------|
1760
+ | step | - | Generate document, write to output, call done |
1761
+ | checkpoint | clarify | Ask questions, call clarified with answers |
1762
+ | checkpoint | approve | Show doc, ask for decision, call appropriate command |
1763
+ | complete | - | Show final message, end skill |
1764
+ | error | - | Show error, end skill |
1765
+
1766
+ ---
1348
1767
 
1349
- User: /spets start "Create a REST API for user management"
1768
+ ## Critical Implementation Rules
1350
1769
 
1351
- Claude:
1352
- 1. Runs \`npx spets start "Create a REST API for user management"\`
1353
- 2. Reads the generated plan
1354
- 3. Asks user: "Here's the plan. [shows plan] Would you like to approve or revise?"
1355
- 4. On approve: \`npx spets resume --approve\`
1356
- 5. On revise: \`npx spets resume --revise "user feedback"\`
1770
+ 1. **ORCHESTRATOR FIRST** - Always call orchestrator before any action
1771
+ 2. **ONE STEP AT A TIME** - Complete one step, get approval, then next
1772
+ 3. **DETERMINISTIC FLOW** - Follow the exact state machine transitions
1773
+ 4. **NO PROCESS SPAWN FOR AI** - Generate documents in current session
1774
+ 5. **WAIT FOR APPROVAL** - Never proceed without user approval
1775
+
1776
+ ---
1777
+
1778
+ ## Error Handling
1779
+
1780
+ - If \`.spets/\` not found, suggest running \`npx spets init\`
1781
+ - If orchestrator returns \`type: "error"\`, display the error and stop
1782
+ - If file read fails, show error and allow retry
1357
1783
 
1358
1784
  $ARGUMENTS
1359
- command: The spets command to run (start, status, resume)
1360
- args: Additional arguments for the command
1785
+ request: The user's task description for the workflow
1361
1786
  `;
1362
1787
  }
1363
1788
 
1364
1789
  // src/commands/github.ts
1790
+ import { execSync as execSync4 } from "child_process";
1791
+ function getGitHubInfo2(cwd) {
1792
+ const config = getGitHubConfig(cwd);
1793
+ if (config?.owner && config?.repo) {
1794
+ return { owner: config.owner, repo: config.repo };
1795
+ }
1796
+ try {
1797
+ const remoteUrl = execSync4("git remote get-url origin", { encoding: "utf-8", cwd }).trim();
1798
+ const sshMatch = remoteUrl.match(/git@github\.com:([^/]+)\/(.+?)(?:\.git)?$/);
1799
+ if (sshMatch) {
1800
+ return { owner: sshMatch[1], repo: sshMatch[2] };
1801
+ }
1802
+ const httpsMatch = remoteUrl.match(/https:\/\/github\.com\/([^/]+)\/(.+?)(?:\.git)?$/);
1803
+ if (httpsMatch) {
1804
+ return { owner: httpsMatch[1], repo: httpsMatch[2] };
1805
+ }
1806
+ } catch {
1807
+ }
1808
+ return null;
1809
+ }
1365
1810
  async function githubCommand(options) {
1366
1811
  const cwd = process.cwd();
1367
1812
  if (!spetsExists(cwd)) {
1368
1813
  console.error("Spets not initialized.");
1369
1814
  process.exit(1);
1370
1815
  }
1371
- const configGitHub = getGitHubConfig(cwd);
1372
- const owner = options.owner || configGitHub?.owner;
1373
- const repo = options.repo || configGitHub?.repo;
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)");
1816
+ const githubInfo = getGitHubInfo2(cwd);
1817
+ if (!githubInfo) {
1818
+ console.error("Could not determine GitHub owner/repo. Set in .spets/config.yml or add git remote.");
1379
1819
  process.exit(1);
1380
1820
  }
1821
+ const { owner, repo } = githubInfo;
1822
+ const { pr, issue, comment, task } = options;
1381
1823
  if (!pr && !issue) {
1382
- console.error("Either --pr or --issue is required (or set defaultPr/defaultIssue in .spets/config.yml)");
1824
+ console.error("Either --pr or --issue is required");
1383
1825
  process.exit(1);
1384
1826
  }
1385
1827
  const parsed = parseGitHubCommand(comment);
@@ -1397,7 +1839,7 @@ async function githubCommand(options) {
1397
1839
  }
1398
1840
  if (!taskId) {
1399
1841
  const config2 = loadConfig(cwd);
1400
- const { listTasks: listTasks2 } = await import("./state-H2GQS43T.js");
1842
+ const { listTasks: listTasks2 } = await import("./state-JLYPJWUT.js");
1401
1843
  const tasks = listTasks2(cwd);
1402
1844
  for (const tid of tasks) {
1403
1845
  const state2 = getWorkflowState(tid, config2, cwd);
@@ -1430,6 +1872,15 @@ async function githubCommand(options) {
1430
1872
  case "approve": {
1431
1873
  console.log(`Approving step: ${state.currentStepName}`);
1432
1874
  updateDocumentStatus(outputPath, "approved");
1875
+ if (parsed.createPR) {
1876
+ console.log("Creating Pull Request...");
1877
+ const prNumber = await createPullRequest(githubConfig, taskId, userQuery, state.currentStepName);
1878
+ console.log(`Created PR #${prNumber}`);
1879
+ }
1880
+ if (parsed.createIssue) {
1881
+ console.log("Creating/Updating Issue...");
1882
+ await createOrUpdateIssue(githubConfig, taskId, userQuery, state.currentStepName);
1883
+ }
1433
1884
  const nextIndex = state.currentStepIndex + 1;
1434
1885
  if (nextIndex >= config.steps.length) {
1435
1886
  console.log("\u2705 Workflow completed!");
@@ -1452,7 +1903,7 @@ async function githubCommand(options) {
1452
1903
  const platform = new GitHubPlatform(githubConfig);
1453
1904
  const executor = new Executor({ platform, config, cwd });
1454
1905
  try {
1455
- await executor.executeWorkflow(taskId, userQuery, state.currentStepIndex);
1906
+ await executor.executeWorkflow(taskId, userQuery, state.currentStepIndex, parsed.feedback);
1456
1907
  } catch (error) {
1457
1908
  if (error.name !== "PauseForInputError") {
1458
1909
  throw error;
@@ -1521,14 +1972,527 @@ async function postComment(config, body) {
1521
1972
  proc.on("error", reject);
1522
1973
  });
1523
1974
  }
1975
+ async function createPullRequest(config, taskId, userQuery, stepName) {
1976
+ const { execSync: execSync5 } = await import("child_process");
1977
+ const { owner, repo } = config;
1978
+ const title = userQuery.slice(0, 50) + (userQuery.length > 50 ? "..." : "");
1979
+ const body = `## Spets Workflow
1980
+
1981
+ - Task ID: \`${taskId}\`
1982
+ - Current Step: **${stepName}**
1983
+
1984
+ ### Description
1985
+ ${userQuery}
1986
+
1987
+ ---
1988
+ _Created by [Spets](https://github.com/eatnug/spets)_`;
1989
+ const result = execSync5(
1990
+ `gh pr create --repo ${owner}/${repo} --title "${title.replace(/"/g, '\\"')}" --body "${body.replace(/"/g, '\\"')}"`,
1991
+ { encoding: "utf-8" }
1992
+ );
1993
+ const match = result.match(/\/pull\/(\d+)/);
1994
+ if (!match) {
1995
+ throw new Error("Failed to parse PR number from gh output");
1996
+ }
1997
+ return parseInt(match[1], 10);
1998
+ }
1999
+ async function createOrUpdateIssue(config, taskId, userQuery, stepName) {
2000
+ const { execSync: execSync5 } = await import("child_process");
2001
+ const { owner, repo, issueNumber } = config;
2002
+ const body = `## Spets Workflow Update
2003
+
2004
+ - Task ID: \`${taskId}\`
2005
+ - Current Step: **${stepName}** \u2705 Approved
2006
+
2007
+ ### Description
2008
+ ${userQuery}
2009
+
2010
+ ---
2011
+ _Updated by [Spets](https://github.com/eatnug/spets)_`;
2012
+ if (issueNumber) {
2013
+ execSync5(
2014
+ `gh issue comment ${issueNumber} --repo ${owner}/${repo} --body "${body.replace(/"/g, '\\"')}"`,
2015
+ { encoding: "utf-8" }
2016
+ );
2017
+ } else {
2018
+ const title = userQuery.slice(0, 50) + (userQuery.length > 50 ? "..." : "");
2019
+ execSync5(
2020
+ `gh issue create --repo ${owner}/${repo} --title "${title.replace(/"/g, '\\"')}" --body "${body.replace(/"/g, '\\"')}" --label spets`,
2021
+ { encoding: "utf-8" }
2022
+ );
2023
+ }
2024
+ }
2025
+
2026
+ // src/orchestrator/index.ts
2027
+ import { readFileSync, writeFileSync as writeFileSync3, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
2028
+ import { join as join4, dirname as dirname2 } from "path";
2029
+ import matter from "gray-matter";
2030
+ var Orchestrator = class {
2031
+ cwd;
2032
+ constructor(cwd = process.cwd()) {
2033
+ this.cwd = cwd;
2034
+ }
2035
+ // ===========================================================================
2036
+ // Config & Path Helpers
2037
+ // ===========================================================================
2038
+ getSteps() {
2039
+ const config = loadConfig(this.cwd);
2040
+ return config.steps;
2041
+ }
2042
+ getOutputPath() {
2043
+ return getOutputsDir(this.cwd);
2044
+ }
2045
+ getStatePath(taskId) {
2046
+ return join4(this.getOutputPath(), taskId, ".state.json");
2047
+ }
2048
+ getSpecPath(taskId, step) {
2049
+ return join4(this.getOutputPath(), taskId, `${step}.md`);
2050
+ }
2051
+ getStepInstructionPath(step) {
2052
+ return join4(getStepsDir(this.cwd), step, "instruction.md");
2053
+ }
2054
+ getStepTemplatePath(step) {
2055
+ return join4(getStepsDir(this.cwd), step, "template.md");
2056
+ }
2057
+ // ===========================================================================
2058
+ // State Management
2059
+ // ===========================================================================
2060
+ loadState(taskId) {
2061
+ const statePath = this.getStatePath(taskId);
2062
+ if (!existsSync4(statePath)) {
2063
+ return null;
2064
+ }
2065
+ const data = JSON.parse(readFileSync(statePath, "utf-8"));
2066
+ return data;
2067
+ }
2068
+ saveState(state) {
2069
+ state.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
2070
+ const statePath = this.getStatePath(state.taskId);
2071
+ const dir = dirname2(statePath);
2072
+ if (!existsSync4(dir)) {
2073
+ mkdirSync3(dir, { recursive: true });
2074
+ }
2075
+ writeFileSync3(statePath, JSON.stringify(state, null, 2));
2076
+ }
2077
+ // ===========================================================================
2078
+ // Spec Helpers
2079
+ // ===========================================================================
2080
+ checkUnresolvedQuestions(specPath) {
2081
+ if (!existsSync4(specPath)) {
2082
+ return [];
2083
+ }
2084
+ const content = readFileSync(specPath, "utf-8");
2085
+ const { data } = matter(content);
2086
+ const questions = [];
2087
+ if (data.open_questions && Array.isArray(data.open_questions)) {
2088
+ for (let i = 0; i < data.open_questions.length; i++) {
2089
+ const q = data.open_questions[i];
2090
+ if (!q.resolved) {
2091
+ questions.push({
2092
+ id: `q${i + 1}`,
2093
+ question: q.question || q.id || `Question ${i + 1}`,
2094
+ context: q.context,
2095
+ options: q.options,
2096
+ resolved: false
2097
+ });
2098
+ }
2099
+ }
2100
+ }
2101
+ return questions;
2102
+ }
2103
+ generateTaskId(description) {
2104
+ const timestamp = Date.now().toString(36);
2105
+ const random = Math.random().toString(36).substring(2, 6);
2106
+ return `${timestamp}-${random}`;
2107
+ }
2108
+ // ===========================================================================
2109
+ // Protocol Response Builders
2110
+ // ===========================================================================
2111
+ responseStep(state) {
2112
+ const steps = this.getSteps();
2113
+ const outputPath = this.getOutputPath();
2114
+ let previousOutput;
2115
+ if (state.stepIndex > 1) {
2116
+ const prevStep = steps[state.stepIndex - 2];
2117
+ const prevPath = join4(outputPath, state.taskId, `${prevStep}.md`);
2118
+ if (existsSync4(prevPath)) {
2119
+ previousOutput = prevPath;
2120
+ }
2121
+ }
2122
+ const templatePath = this.getStepTemplatePath(state.currentStep);
2123
+ const hasTemplate = existsSync4(templatePath);
2124
+ return {
2125
+ type: "step",
2126
+ step: state.currentStep,
2127
+ stepIndex: state.stepIndex,
2128
+ totalSteps: state.totalSteps,
2129
+ taskId: state.taskId,
2130
+ description: state.description,
2131
+ context: {
2132
+ instruction: this.getStepInstructionPath(state.currentStep),
2133
+ template: hasTemplate ? templatePath : void 0,
2134
+ previousOutput,
2135
+ output: join4(outputPath, state.taskId, `${state.currentStep}.md`),
2136
+ revisionFeedback: state.revisionFeedback
2137
+ },
2138
+ onComplete: `done ${state.taskId}`
2139
+ };
2140
+ }
2141
+ responseCheckpointClarify(state, questions) {
2142
+ return {
2143
+ type: "checkpoint",
2144
+ checkpoint: "clarify",
2145
+ taskId: state.taskId,
2146
+ step: state.currentStep,
2147
+ questions: questions.map((q) => ({
2148
+ id: q.id,
2149
+ question: q.question,
2150
+ context: q.context,
2151
+ options: q.options
2152
+ })),
2153
+ onComplete: `clarified ${state.taskId} '<answers_json>'`
2154
+ };
2155
+ }
2156
+ responseCheckpointApprove(state) {
2157
+ const outputPath = this.getOutputPath();
2158
+ return {
2159
+ type: "checkpoint",
2160
+ checkpoint: "approve",
2161
+ taskId: state.taskId,
2162
+ step: state.currentStep,
2163
+ stepIndex: state.stepIndex,
2164
+ totalSteps: state.totalSteps,
2165
+ specPath: join4(outputPath, state.taskId, `${state.currentStep}.md`),
2166
+ options: ["approve", "revise", "reject", "stop"],
2167
+ onComplete: {
2168
+ approve: `approve ${state.taskId}`,
2169
+ revise: `revise ${state.taskId} '<feedback>'`,
2170
+ reject: `reject ${state.taskId}`,
2171
+ stop: `stop ${state.taskId}`
2172
+ }
2173
+ };
2174
+ }
2175
+ responseComplete(state, status) {
2176
+ const steps = this.getSteps();
2177
+ const outputPath = this.getOutputPath();
2178
+ const outputs = [];
2179
+ for (let i = 0; i < state.stepIndex; i++) {
2180
+ const specPath = join4(outputPath, state.taskId, `${steps[i]}.md`);
2181
+ if (existsSync4(specPath)) {
2182
+ outputs.push(specPath);
2183
+ }
2184
+ }
2185
+ const messages = {
2186
+ completed: "Workflow completed successfully",
2187
+ stopped: `Workflow stopped. Resume with: spets resume --task ${state.taskId}`,
2188
+ rejected: "Workflow rejected"
2189
+ };
2190
+ return {
2191
+ type: "complete",
2192
+ status,
2193
+ taskId: state.taskId,
2194
+ outputs,
2195
+ message: messages[status]
2196
+ };
2197
+ }
2198
+ responseError(error, taskId, step) {
2199
+ const response = { type: "error", error };
2200
+ if (taskId) response.taskId = taskId;
2201
+ if (step) response.step = step;
2202
+ return response;
2203
+ }
2204
+ // ===========================================================================
2205
+ // Command Handlers
2206
+ // ===========================================================================
2207
+ /**
2208
+ * Initialize a new workflow
2209
+ */
2210
+ cmdInit(description) {
2211
+ try {
2212
+ const steps = this.getSteps();
2213
+ const taskId = this.generateTaskId(description);
2214
+ const state = {
2215
+ taskId,
2216
+ description,
2217
+ currentStep: steps[0],
2218
+ stepIndex: 1,
2219
+ totalSteps: steps.length,
2220
+ status: "awaiting_spec",
2221
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
2222
+ };
2223
+ this.saveState(state);
2224
+ return this.responseStep(state);
2225
+ } catch (e) {
2226
+ return this.responseError(e.message);
2227
+ }
2228
+ }
2229
+ /**
2230
+ * Mark current step as done, check for questions or go to approve
2231
+ */
2232
+ cmdDone(taskId) {
2233
+ const state = this.loadState(taskId);
2234
+ if (!state) {
2235
+ return this.responseError(`No workflow found: ${taskId}`, taskId);
2236
+ }
2237
+ const specPath = this.getSpecPath(taskId, state.currentStep);
2238
+ if (!existsSync4(specPath)) {
2239
+ return this.responseError(`Spec not found: ${specPath}`, taskId, state.currentStep);
2240
+ }
2241
+ const questions = this.checkUnresolvedQuestions(specPath);
2242
+ if (questions.length > 0) {
2243
+ state.status = "awaiting_clarify";
2244
+ this.saveState(state);
2245
+ return this.responseCheckpointClarify(state, questions);
2246
+ }
2247
+ state.status = "awaiting_approve";
2248
+ this.saveState(state);
2249
+ return this.responseCheckpointApprove(state);
2250
+ }
2251
+ /**
2252
+ * Submit clarification answers
2253
+ */
2254
+ cmdClarified(taskId, answers) {
2255
+ const state = this.loadState(taskId);
2256
+ if (!state) {
2257
+ return this.responseError(`No workflow found: ${taskId}`, taskId);
2258
+ }
2259
+ const specPath = this.getSpecPath(taskId, state.currentStep);
2260
+ if (existsSync4(specPath)) {
2261
+ const content = readFileSync(specPath, "utf-8");
2262
+ const { content: body, data } = matter(content);
2263
+ if (data.open_questions && Array.isArray(data.open_questions)) {
2264
+ data.open_questions = data.open_questions.map((q, i) => ({
2265
+ ...q,
2266
+ resolved: true,
2267
+ answer: answers.find((a) => a.questionId === `q${i + 1}`)?.answer
2268
+ }));
2269
+ }
2270
+ writeFileSync3(specPath, matter.stringify(body, data));
2271
+ }
2272
+ state.status = "awaiting_spec";
2273
+ state.revisionFeedback = `Answers provided: ${JSON.stringify(answers)}`;
2274
+ this.saveState(state);
2275
+ return this.responseStep(state);
2276
+ }
2277
+ /**
2278
+ * Approve current step and move to next
2279
+ */
2280
+ cmdApprove(taskId) {
2281
+ const state = this.loadState(taskId);
2282
+ if (!state) {
2283
+ return this.responseError(`No workflow found: ${taskId}`, taskId);
2284
+ }
2285
+ const steps = this.getSteps();
2286
+ const specPath = this.getSpecPath(taskId, state.currentStep);
2287
+ if (existsSync4(specPath)) {
2288
+ const content = readFileSync(specPath, "utf-8");
2289
+ const { content: body, data } = matter(content);
2290
+ data.status = "approved";
2291
+ data.updated_at = (/* @__PURE__ */ new Date()).toISOString();
2292
+ writeFileSync3(specPath, matter.stringify(body, data));
2293
+ }
2294
+ if (state.stepIndex < state.totalSteps) {
2295
+ state.currentStep = steps[state.stepIndex];
2296
+ state.stepIndex += 1;
2297
+ state.status = "awaiting_spec";
2298
+ state.revisionFeedback = void 0;
2299
+ this.saveState(state);
2300
+ return this.responseStep(state);
2301
+ } else {
2302
+ state.status = "completed";
2303
+ this.saveState(state);
2304
+ return this.responseComplete(state, "completed");
2305
+ }
2306
+ }
2307
+ /**
2308
+ * Request revision with feedback
2309
+ */
2310
+ cmdRevise(taskId, feedback) {
2311
+ const state = this.loadState(taskId);
2312
+ if (!state) {
2313
+ return this.responseError(`No workflow found: ${taskId}`, taskId);
2314
+ }
2315
+ state.status = "awaiting_spec";
2316
+ state.revisionFeedback = feedback;
2317
+ this.saveState(state);
2318
+ return this.responseStep(state);
2319
+ }
2320
+ /**
2321
+ * Reject and stop workflow
2322
+ */
2323
+ cmdReject(taskId) {
2324
+ const state = this.loadState(taskId);
2325
+ if (!state) {
2326
+ return this.responseError(`No workflow found: ${taskId}`, taskId);
2327
+ }
2328
+ const specPath = this.getSpecPath(taskId, state.currentStep);
2329
+ if (existsSync4(specPath)) {
2330
+ const content = readFileSync(specPath, "utf-8");
2331
+ const { content: body, data } = matter(content);
2332
+ data.status = "rejected";
2333
+ data.updated_at = (/* @__PURE__ */ new Date()).toISOString();
2334
+ writeFileSync3(specPath, matter.stringify(body, data));
2335
+ }
2336
+ state.status = "rejected";
2337
+ this.saveState(state);
2338
+ return this.responseComplete(state, "rejected");
2339
+ }
2340
+ /**
2341
+ * Stop workflow (can resume later)
2342
+ */
2343
+ cmdStop(taskId) {
2344
+ const state = this.loadState(taskId);
2345
+ if (!state) {
2346
+ return this.responseError(`No workflow found: ${taskId}`, taskId);
2347
+ }
2348
+ state.status = "stopped";
2349
+ this.saveState(state);
2350
+ return this.responseComplete(state, "stopped");
2351
+ }
2352
+ /**
2353
+ * Get current workflow status
2354
+ */
2355
+ cmdStatus(taskId) {
2356
+ const state = this.loadState(taskId);
2357
+ if (!state) {
2358
+ return this.responseError(`No workflow found: ${taskId}`, taskId);
2359
+ }
2360
+ switch (state.status) {
2361
+ case "awaiting_spec":
2362
+ return this.responseStep(state);
2363
+ case "awaiting_clarify": {
2364
+ const questions = this.checkUnresolvedQuestions(this.getSpecPath(taskId, state.currentStep));
2365
+ return this.responseCheckpointClarify(state, questions);
2366
+ }
2367
+ case "awaiting_approve":
2368
+ return this.responseCheckpointApprove(state);
2369
+ case "completed":
2370
+ case "stopped":
2371
+ case "rejected":
2372
+ return this.responseComplete(state, state.status);
2373
+ default:
2374
+ return this.responseError(`Unknown status: ${state.status}`, taskId);
2375
+ }
2376
+ }
2377
+ };
2378
+
2379
+ // src/commands/orchestrate.ts
2380
+ function outputJSON(data) {
2381
+ console.log(JSON.stringify(data, null, 2));
2382
+ }
2383
+ function outputError(error) {
2384
+ console.log(JSON.stringify({ type: "error", error }, null, 2));
2385
+ process.exit(1);
2386
+ }
2387
+ async function orchestrateCommand(action, args) {
2388
+ try {
2389
+ const orchestrator = new Orchestrator();
2390
+ switch (action) {
2391
+ case "init": {
2392
+ const description = args[0];
2393
+ if (!description) {
2394
+ outputError("Description is required for init");
2395
+ return;
2396
+ }
2397
+ const result = orchestrator.cmdInit(description);
2398
+ outputJSON(result);
2399
+ break;
2400
+ }
2401
+ case "done": {
2402
+ const taskId = args[0];
2403
+ if (!taskId) {
2404
+ outputError("Task ID is required for done");
2405
+ return;
2406
+ }
2407
+ const result = orchestrator.cmdDone(taskId);
2408
+ outputJSON(result);
2409
+ break;
2410
+ }
2411
+ case "clarified": {
2412
+ const taskId = args[0];
2413
+ const answersJson = args[1];
2414
+ if (!taskId || !answersJson) {
2415
+ outputError("Task ID and answers JSON are required for clarified");
2416
+ return;
2417
+ }
2418
+ let answers;
2419
+ try {
2420
+ answers = JSON.parse(answersJson);
2421
+ } catch {
2422
+ outputError("Invalid JSON for answers");
2423
+ return;
2424
+ }
2425
+ const result = orchestrator.cmdClarified(taskId, answers);
2426
+ outputJSON(result);
2427
+ break;
2428
+ }
2429
+ case "approve": {
2430
+ const taskId = args[0];
2431
+ if (!taskId) {
2432
+ outputError("Task ID is required for approve");
2433
+ return;
2434
+ }
2435
+ const result = orchestrator.cmdApprove(taskId);
2436
+ outputJSON(result);
2437
+ break;
2438
+ }
2439
+ case "revise": {
2440
+ const taskId = args[0];
2441
+ const feedback = args[1];
2442
+ if (!taskId || !feedback) {
2443
+ outputError("Task ID and feedback are required for revise");
2444
+ return;
2445
+ }
2446
+ const result = orchestrator.cmdRevise(taskId, feedback);
2447
+ outputJSON(result);
2448
+ break;
2449
+ }
2450
+ case "reject": {
2451
+ const taskId = args[0];
2452
+ if (!taskId) {
2453
+ outputError("Task ID is required for reject");
2454
+ return;
2455
+ }
2456
+ const result = orchestrator.cmdReject(taskId);
2457
+ outputJSON(result);
2458
+ break;
2459
+ }
2460
+ case "stop": {
2461
+ const taskId = args[0];
2462
+ if (!taskId) {
2463
+ outputError("Task ID is required for stop");
2464
+ return;
2465
+ }
2466
+ const result = orchestrator.cmdStop(taskId);
2467
+ outputJSON(result);
2468
+ break;
2469
+ }
2470
+ case "status": {
2471
+ const taskId = args[0];
2472
+ if (!taskId) {
2473
+ outputError("Task ID is required for status");
2474
+ return;
2475
+ }
2476
+ const result = orchestrator.cmdStatus(taskId);
2477
+ outputJSON(result);
2478
+ break;
2479
+ }
2480
+ default:
2481
+ outputError(`Unknown action: ${action}. Valid actions: init, done, clarified, approve, revise, reject, stop, status`);
2482
+ }
2483
+ } catch (e) {
2484
+ outputError(e.message);
2485
+ }
2486
+ }
1524
2487
 
1525
2488
  // src/index.ts
1526
2489
  var program = new Command();
1527
- program.name("spets").description("Spec Driven Development Execution Framework").version("0.1.0");
2490
+ program.name("spets").description("Spec Driven Development Execution Framework").version("0.1.3");
1528
2491
  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
2492
  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("-p, --platform <platform>", "Platform to use (cli, github)", "cli").option("--owner <owner>", "GitHub owner (for github platform)").option("--repo <repo>", "GitHub repo (for github platform)").option("--pr <number>", "GitHub PR number (for github platform)").option("--issue <number>", "GitHub issue number (for github platform)").action(startCommand);
2493
+ 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
2494
  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("--owner <owner>", "GitHub owner (or set in .spets/config.yml)").option("--repo <repo>", "GitHub repo (or set in .spets/config.yml)").option("--pr <number>", "PR number (or set defaultPr in .spets/config.yml)").option("--issue <number>", "Issue number (or set defaultIssue in .spets/config.yml)").option("-t, --task <taskId>", "Task ID").requiredOption("--comment <comment>", "Comment body").action(githubCommand);
2495
+ 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
2496
  program.command("plugin").description("Manage plugins").argument("<action>", "Action: install, uninstall, list").argument("[name]", "Plugin name").action(pluginCommand);
2497
+ 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
2498
  program.parse();