spets 0.2.4 → 0.2.6

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
@@ -4,7 +4,7 @@ import {
4
4
  renderConfigJSON,
5
5
  runConfigInteractive,
6
6
  setConfigField
7
- } from "./chunk-BCOGSLCX.js";
7
+ } from "./chunk-P3GQOSMU.js";
8
8
  import {
9
9
  CLIDashboardRenderer,
10
10
  DashboardClient,
@@ -16,11 +16,11 @@ import {
16
16
  loadTaskMetadata,
17
17
  renderTasksJSON,
18
18
  runTasksInteractive
19
- } from "./chunk-ZRWVDWBF.js";
19
+ } from "./chunk-75EYXFBG.js";
20
20
  import {
21
21
  renderDocsJSON,
22
22
  runDocsInteractive
23
- } from "./chunk-3JIHGW47.js";
23
+ } from "./chunk-6FL7APE6.js";
24
24
  import {
25
25
  buildBudgetedKnowledgeSection,
26
26
  buildKnowledgeSummarySection,
@@ -32,31 +32,29 @@ import {
32
32
  renderKnowledgeJSON,
33
33
  runKnowledgeInteractive,
34
34
  saveKnowledge
35
- } from "./chunk-OIFVTELS.js";
35
+ } from "./chunk-R62NYMA5.js";
36
36
  import {
37
37
  clearConfigCache,
38
38
  getConfigPath,
39
- getGitHubConfig,
40
39
  getOutputsDir,
41
40
  getSpetsDir,
42
41
  getStepsDir,
43
42
  loadAllSteps,
44
43
  loadConfig,
45
44
  spetsExists
46
- } from "./chunk-2MUV3F53.js";
45
+ } from "./chunk-63Q6RNKK.js";
47
46
 
48
47
  // src/index.ts
49
48
  import { Command } from "commander";
50
- import { readFileSync as readFileSync20 } from "fs";
51
- import { dirname as dirname8, join as join17 } from "path";
49
+ import { readFileSync as readFileSync13 } from "fs";
50
+ import { dirname as dirname4, join as join13 } from "path";
52
51
  import { fileURLToPath as fileURLToPath3 } from "url";
53
52
 
54
53
  // src/commands/init.ts
55
54
  import { existsSync as existsSync2, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, cpSync, readFileSync } from "fs";
56
55
  import { join as join2, dirname } from "path";
57
56
  import { fileURLToPath } from "url";
58
- import { execSync } from "child_process";
59
- import { select, confirm } from "@inquirer/prompts";
57
+ import { select } from "@inquirer/prompts";
60
58
 
61
59
  // src/commands/plugin.ts
62
60
  import { existsSync, mkdirSync, writeFileSync, rmSync, readdirSync } from "fs";
@@ -284,7 +282,8 @@ You execute spets workflow commands. Follow orchestrator instructions exactly.
284
282
  ## Startup
285
283
 
286
284
  IF \`$ARGUMENTS\` starts with "resume":
287
- RUN \`npx spets orchestrate resume\`
285
+ Extract optional task ID from arguments (e.g. "resume abc-123" or "resume --task abc-123")
286
+ RUN \`npx spets orchestrate resume <taskId>\` (omit taskId if none provided)
288
287
  ELSE IF \`$ARGUMENTS\` starts with "list":
289
288
  RUN \`npx spets orchestrate list\`
290
289
  ELSE:
@@ -325,7 +324,8 @@ You execute spets workflow commands. Follow orchestrator instructions exactly.
325
324
  ## Startup
326
325
 
327
326
  IF \`$ARGUMENTS\` starts with "resume":
328
- RUN \`npx spets orchestrate resume\`
327
+ Extract optional task ID from arguments (e.g. "resume abc-123" or "resume --task abc-123")
328
+ RUN \`npx spets orchestrate resume <taskId>\` (omit taskId if none provided)
329
329
  ELSE IF \`$ARGUMENTS\` starts with "list":
330
330
  RUN \`npx spets orchestrate list\`
331
331
  ELSE:
@@ -366,7 +366,8 @@ You execute spets workflow commands. Follow orchestrator instructions exactly.
366
366
  ## Startup
367
367
 
368
368
  IF \`$ARGUMENTS\` starts with "resume":
369
- RUN \`npx spets orchestrate resume\`
369
+ Extract optional task ID from arguments (e.g. "resume abc-123" or "resume --task abc-123")
370
+ RUN \`npx spets orchestrate resume <taskId>\` (omit taskId if none provided)
370
371
  ELSE IF \`$ARGUMENTS\` starts with "list":
371
372
  RUN \`npx spets orchestrate list\`
372
373
  ELSE:
@@ -397,22 +398,6 @@ description: Task description for the workflow (or "resume" to list/continue pre
397
398
 
398
399
  // src/commands/init.ts
399
400
  var __dirname = dirname(fileURLToPath(import.meta.url));
400
- function getGitHubInfoFromRemote() {
401
- try {
402
- const remoteUrl = execSync("git remote get-url origin", { encoding: "utf-8" }).trim();
403
- const sshMatch = remoteUrl.match(/git@github\.com:([^/]+)\/(.+?)(?:\.git)?$/);
404
- if (sshMatch) {
405
- return { owner: sshMatch[1], repo: sshMatch[2] };
406
- }
407
- const httpsMatch = remoteUrl.match(/https:\/\/github\.com\/([^/]+)\/(.+?)(?:\.git)?$/);
408
- if (httpsMatch) {
409
- return { owner: httpsMatch[1], repo: httpsMatch[2] };
410
- }
411
- return null;
412
- } catch {
413
- return null;
414
- }
415
- }
416
401
  async function initCommand(options) {
417
402
  const cwd = process.cwd();
418
403
  const spetsDir = getSpetsDir(cwd);
@@ -429,18 +414,6 @@ async function initCommand(options) {
429
414
  mkdirSync2(join2(spetsDir, "outputs"), { recursive: true });
430
415
  mkdirSync2(join2(spetsDir, "hooks"), { recursive: true });
431
416
  mkdirSync2(join2(spetsDir, "knowledge"), { recursive: true });
432
- const templatesDir = join2(__dirname, "..", "templates");
433
- const hookTemplate = join2(__dirname, "..", "templates", "hooks", "cleanup-branch.sh");
434
- if (existsSync2(hookTemplate)) {
435
- const hookDest = join2(spetsDir, "hooks", "cleanup-branch.sh");
436
- if (!existsSync2(hookDest)) {
437
- cpSync(hookTemplate, hookDest);
438
- try {
439
- execSync(`chmod +x "${hookDest}"`);
440
- } catch {
441
- }
442
- }
443
- }
444
417
  const knowledgeGuideTemplate = join2(__dirname, "..", "templates", "knowledge", "guide.md");
445
418
  if (existsSync2(knowledgeGuideTemplate)) {
446
419
  const guideDest = join2(spetsDir, "knowledge", "guide.md");
@@ -448,10 +421,9 @@ async function initCommand(options) {
448
421
  cpSync(knowledgeGuideTemplate, guideDest);
449
422
  }
450
423
  }
451
- const githubInfo = getGitHubInfoFromRemote();
452
424
  const configPath = join2(spetsDir, "config.yml");
453
425
  if (!existsSync2(configPath)) {
454
- writeFileSync2(configPath, getDefaultConfig(githubInfo));
426
+ writeFileSync2(configPath, getDefaultConfig());
455
427
  }
456
428
  createDefaultSteps(spetsDir);
457
429
  createClaudeCommand(cwd);
@@ -477,7 +449,6 @@ function printEnhancedInitOutput(options) {
477
449
  console.log(" \u21B3 Add/remove steps by editing config.yml AND creating folders");
478
450
  console.log("");
479
451
  console.log(" \u{1F4C2} hooks/");
480
- console.log(" \u21B3 cleanup-branch.sh \u2014 Example hook script");
481
452
  console.log(" \u21B3 Hooks run at workflow events (preStep, postStep, onApprove...)");
482
453
  console.log("");
483
454
  console.log(" \u{1F4C2} knowledge/");
@@ -489,12 +460,6 @@ function printEnhancedInitOutput(options) {
489
460
  console.log("");
490
461
  console.log("\u{1F4C4} .claude/commands/spets.md \u2014 Claude Code integration");
491
462
  console.log(" \u21B3 Use /spets in Claude Code to run workflows");
492
- if (options.github) {
493
- console.log("");
494
- console.log("\u{1F419} GitHub Integration:");
495
- console.log(" .github/workflows/spets.yml \u2014 GitHub Actions workflow");
496
- console.log(" .github/ISSUE_TEMPLATE/spets-task.yml \u2014 Issue template");
497
- }
498
463
  console.log("");
499
464
  console.log("\u2501".repeat(60));
500
465
  console.log("\u{1F680} Next Steps");
@@ -506,41 +471,12 @@ function printEnhancedInitOutput(options) {
506
471
  console.log(" 2. Optionally customize templates in .spets/steps/");
507
472
  console.log(" (These control what the AI outputs look like)");
508
473
  console.log("");
509
- if (options.github) {
510
- console.log(" 3. Add CLAUDE_CODE_OAUTH_TOKEN to your repo secrets");
511
- console.log(' 4. Create an Issue using the "Spets Task" template');
512
- } else {
513
- console.log(" 3. Run your first workflow:");
514
- console.log(' spets start "describe your task here"');
515
- }
474
+ console.log(" 3. Use /spets in Claude Code to run your first workflow");
516
475
  console.log("");
517
476
  console.log('\u{1F4A1} Tip: Run "spets --help" to see all available commands');
518
477
  console.log("");
519
478
  }
520
- function getDefaultConfig(githubInfo, configOptions) {
521
- const agent = configOptions?.agent || "claude";
522
- const includeGithub = configOptions?.githubEnabled ?? !!githubInfo;
523
- const githubSection = includeGithub && githubInfo ? `
524
- # \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
525
- # GITHUB INTEGRATION (auto-detected from git remote)
526
- # \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
527
- # Enables workflow management via GitHub Issues and PRs.
528
- # Create issues with the "spets" label to trigger workflows.
529
- # Use /approve, /revise, /reject in issue comments.
530
- github:
531
- owner: ${githubInfo.owner}
532
- repo: ${githubInfo.repo}
533
- ` : `
534
- # \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
535
- # GITHUB INTEGRATION (optional)
536
- # \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
537
- # Uncomment and fill in to enable GitHub integration.
538
- # This allows workflow management via GitHub Issues and PRs.
539
- #
540
- # github:
541
- # owner: your-org # GitHub organization or username
542
- # repo: your-repo # Repository name
543
- `;
479
+ function getDefaultConfig(agent = "claude") {
544
480
  return `# \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
545
481
  # \u2551 SPETS CONFIGURATION \u2551
546
482
  # \u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563
@@ -573,7 +509,6 @@ steps:
573
509
  # \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
574
510
  # Which AI CLI to use for generating documents.
575
511
  # Options: claude, codex, gemini
576
- # You can also override per-run with: spets start --agent codex "task"
577
512
 
578
513
  agent: ${agent}
579
514
 
@@ -608,7 +543,7 @@ knowledge:
608
543
  #
609
544
  # team:
610
545
  # enabled: true
611
- ${githubSection}
546
+
612
547
  # \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
613
548
  # HOOKS (optional)
614
549
  # \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
@@ -630,8 +565,8 @@ ${githubSection}
630
565
  # preStep: "./hooks/pre-step.sh"
631
566
  # postStep: "./hooks/post-step.sh"
632
567
  # onApprove: "./hooks/on-approve.sh"
633
- # onReject: "./hooks/cleanup-branch.sh"
634
- # onComplete: "./hooks/cleanup-branch.sh"
568
+ # onReject: "./hooks/on-reject.sh"
569
+ # onComplete: "./hooks/on-complete.sh"
635
570
  `;
636
571
  }
637
572
  async function runInteractiveSetup(cwd, spetsDir, options) {
@@ -650,40 +585,11 @@ async function runInteractiveSetup(cwd, spetsDir, options) {
650
585
  { value: "gemini", name: "Gemini (Google)" }
651
586
  ]
652
587
  });
653
- const githubInfo = getGitHubInfoFromRemote();
654
- let githubEnabled = false;
655
- if (githubInfo) {
656
- githubEnabled = await confirm({
657
- message: `Enable GitHub integration? (Detected: ${githubInfo.owner}/${githubInfo.repo})`,
658
- default: false
659
- });
660
- } else {
661
- const wantGithub = await confirm({
662
- message: "Enable GitHub integration? (Manage workflows via Issues/PRs)",
663
- default: false
664
- });
665
- if (wantGithub) {
666
- console.log("");
667
- console.log("\u26A0\uFE0F No git remote detected. Add GitHub config manually to .spets/config.yml");
668
- console.log("");
669
- }
670
- }
671
588
  mkdirSync2(spetsDir, { recursive: true });
672
589
  mkdirSync2(join2(spetsDir, "steps"), { recursive: true });
673
590
  mkdirSync2(join2(spetsDir, "outputs"), { recursive: true });
674
591
  mkdirSync2(join2(spetsDir, "hooks"), { recursive: true });
675
592
  mkdirSync2(join2(spetsDir, "knowledge"), { recursive: true });
676
- const hookTemplate = join2(__dirname, "..", "templates", "hooks", "cleanup-branch.sh");
677
- if (existsSync2(hookTemplate)) {
678
- const hookDest = join2(spetsDir, "hooks", "cleanup-branch.sh");
679
- if (!existsSync2(hookDest)) {
680
- cpSync(hookTemplate, hookDest);
681
- try {
682
- execSync(`chmod +x "${hookDest}"`);
683
- } catch {
684
- }
685
- }
686
- }
687
593
  const knowledgeGuideTemplate = join2(__dirname, "..", "templates", "knowledge", "guide.md");
688
594
  const guideDest = join2(spetsDir, "knowledge", "guide.md");
689
595
  if (existsSync2(knowledgeGuideTemplate) && !existsSync2(guideDest)) {
@@ -691,14 +597,10 @@ async function runInteractiveSetup(cwd, spetsDir, options) {
691
597
  }
692
598
  const configPath = join2(spetsDir, "config.yml");
693
599
  if (!existsSync2(configPath)) {
694
- writeFileSync2(configPath, getDefaultConfig(githubInfo, { agent, githubEnabled }));
600
+ writeFileSync2(configPath, getDefaultConfig(agent));
695
601
  }
696
602
  createDefaultSteps(spetsDir);
697
603
  createClaudeCommand(cwd);
698
- if (githubEnabled && githubInfo) {
699
- createGitHubWorkflow(cwd);
700
- options.github = true;
701
- }
702
604
  printEnhancedInitOutput(options);
703
605
  }
704
606
  function createDefaultSteps(spetsDir) {
@@ -728,31 +630,6 @@ function createClaudeCommand(cwd) {
728
630
  mkdirSync2(commandDir, { recursive: true });
729
631
  writeFileSync2(join2(commandDir, "spets.md"), getClaudeSkillContent());
730
632
  }
731
- function createGitHubWorkflow(cwd) {
732
- const workflowDir = join2(cwd, ".github", "workflows");
733
- const templateDir = join2(cwd, ".github", "ISSUE_TEMPLATE");
734
- mkdirSync2(workflowDir, { recursive: true });
735
- mkdirSync2(templateDir, { recursive: true });
736
- const workflowTemplate = readFileSync(join2(__dirname, "..", "assets", "github", "workflows", "spets.yml"), "utf-8");
737
- const issueTemplate = readFileSync(join2(__dirname, "..", "assets", "github", "ISSUE_TEMPLATE", "spets-task.yml"), "utf-8");
738
- writeFileSync2(join2(workflowDir, "spets.yml"), workflowTemplate);
739
- writeFileSync2(join2(templateDir, "spets-task.yml"), issueTemplate);
740
- createGitHubLabel();
741
- }
742
- function createGitHubLabel() {
743
- try {
744
- execSync("gh --version", { stdio: "ignore" });
745
- execSync('gh label create spets --description "Spets workflow task" --color 0E8A16 --force', {
746
- stdio: "ignore"
747
- });
748
- console.log(' GitHub label "spets" created');
749
- } catch {
750
- console.log("");
751
- console.log(" Note: Could not create GitHub label automatically.");
752
- console.log(' Please create the "spets" label manually or run:');
753
- console.log(' gh label create spets --description "Spets workflow task" --color 0E8A16');
754
- }
755
- }
756
633
 
757
634
  // src/commands/status.ts
758
635
  async function statusCommand(options) {
@@ -795,7 +672,7 @@ function showAllTasks(config, cwd) {
795
672
  if (tasks.length === 0) {
796
673
  console.log("No tasks found.");
797
674
  console.log("");
798
- console.log('Start a new task with: spets start "your task description"');
675
+ console.log('Start a new task with: spets orchestrate init "your task description"');
799
676
  return;
800
677
  }
801
678
  console.log("Tasks:");
@@ -835,9 +712,6 @@ function formatDocStatus(status) {
835
712
  return icons[status] || "\u25CB";
836
713
  }
837
714
 
838
- // src/commands/start.ts
839
- import { execSync as execSync2 } from "child_process";
840
-
841
715
  // src/orchestrator/index.ts
842
716
  import { readFileSync as readFileSync10, writeFileSync as writeFileSync5, existsSync as existsSync11, readdirSync as readdirSync2 } from "fs";
843
717
  import matter from "gray-matter";
@@ -889,58 +763,6 @@ import { join as join4 } from "path";
889
763
 
890
764
  // src/core/prompts/context.ts
891
765
  import { readFileSync as readFileSync4, existsSync as existsSync5 } from "fs";
892
- function buildContextPrompt(params) {
893
- const cwd = params.cwd || process.cwd();
894
- const isFirstStep = params.stepIndex === 1;
895
- const parts = [];
896
- parts.push("# Context Gathering Phase");
897
- parts.push("");
898
- parts.push("Your task is to gather context before generating a document.");
899
- parts.push("Do NOT generate the document yet - only gather information.");
900
- parts.push("");
901
- parts.push("## Task Information");
902
- parts.push("");
903
- parts.push(`- **Task ID**: ${params.taskId}`);
904
- parts.push(`- **Current Step**: ${params.step} (${params.stepIndex}/${params.totalSteps})`);
905
- parts.push("");
906
- if (isFirstStep) {
907
- parts.push("## User Request");
908
- parts.push("");
909
- parts.push(`> ${params.description}`);
910
- parts.push("");
911
- } else if (params.previousOutput) {
912
- parts.push("## Previous Step Output");
913
- parts.push("");
914
- if (existsSync5(params.previousOutput)) {
915
- parts.push(readFileSync4(params.previousOutput, "utf-8"));
916
- }
917
- parts.push("");
918
- }
919
- parts.push("## Your Task");
920
- parts.push("");
921
- parts.push("1. **Analyze** the request/input to understand what is being asked");
922
- parts.push("2. **Search** the codebase for relevant files, patterns, conventions");
923
- parts.push("3. **Identify** key constraints, dependencies, and considerations");
924
- parts.push("");
925
- parts.push("## Output Format");
926
- parts.push("");
927
- parts.push("Output a JSON object with this structure:");
928
- parts.push("");
929
- parts.push("```json");
930
- parts.push("{");
931
- parts.push(' "summary": "Brief summary of what you understood and found",');
932
- parts.push(' "relevantFiles": ["path/to/file1.ts", "path/to/file2.ts"],');
933
- parts.push(' "keyFindings": [');
934
- parts.push(' "Finding 1: description",');
935
- parts.push(' "Finding 2: description"');
936
- parts.push(" ]");
937
- parts.push("}");
938
- parts.push("```");
939
- parts.push("");
940
- parts.push("**Important:** Output ONLY the JSON, no other text.");
941
- parts.push("");
942
- return parts.join("\n");
943
- }
944
766
 
945
767
  // src/core/prompts/explore.ts
946
768
  import { readFileSync as readFileSync5, existsSync as existsSync6 } from "fs";
@@ -984,10 +806,17 @@ function buildExplorePrompt(params) {
984
806
  }
985
807
  parts.push("## Your Task");
986
808
  parts.push("");
987
- parts.push("1. **Study** the template above to understand what output is expected");
988
- parts.push("2. **Study** the codebase \u2014 read file contents, not just paths");
989
- parts.push("3. **Identify** patterns, conventions, and constraints");
990
- parts.push("4. **Confirm** assumptions by searching \u2014 do not guess");
809
+ if (isFirstStep) {
810
+ parts.push("1. **Study** the template above to understand what output is expected");
811
+ parts.push("2. **Study** the codebase \u2014 read file contents, not just paths");
812
+ parts.push("3. **Identify** patterns, conventions, and constraints");
813
+ parts.push("4. **Confirm** assumptions by searching \u2014 do not guess");
814
+ } else {
815
+ parts.push("1. **Study** the template above to understand what output is expected");
816
+ parts.push("2. **Study** the previous step output \u2014 it already contains codebase analysis, decisions, and context");
817
+ parts.push("3. **Supplement** with targeted codebase reads only where the previous output lacks detail for this step");
818
+ parts.push("4. **Confirm** assumptions by searching \u2014 do not guess");
819
+ }
991
820
  parts.push("");
992
821
  const config = loadConfig(cwd);
993
822
  if (config.knowledge?.inject !== false) {
@@ -1202,93 +1031,6 @@ function buildClarifyPrompt(params) {
1202
1031
  // src/core/prompts/generate.ts
1203
1032
  import { readFileSync as readFileSync6, existsSync as existsSync7 } from "fs";
1204
1033
  import { join as join6 } from "path";
1205
- function buildGeneratePrompt(params) {
1206
- const cwd = params.cwd || process.cwd();
1207
- const config = loadConfig(cwd);
1208
- const stepsDir = getStepsDir(cwd);
1209
- const outputsDir = getOutputsDir(cwd);
1210
- const isFirstStep = params.stepIndex === 1;
1211
- const prevStep = params.stepIndex > 1 ? config.steps[params.stepIndex - 2] : null;
1212
- const templatePath = join6(stepsDir, params.step, "template.md");
1213
- const outputPath = join6(outputsDir, params.taskId, `${params.step}.md`);
1214
- const template = existsSync7(templatePath) ? readFileSync6(templatePath, "utf-8") : null;
1215
- let previousSpec = null;
1216
- if (prevStep) {
1217
- const prevPath = join6(outputsDir, params.taskId, `${prevStep}.md`);
1218
- if (existsSync7(prevPath)) {
1219
- previousSpec = {
1220
- step: prevStep,
1221
- content: readFileSync6(prevPath, "utf-8")
1222
- };
1223
- }
1224
- }
1225
- const parts = [];
1226
- parts.push("# Document Generation Phase");
1227
- parts.push("");
1228
- parts.push("Generate the document based on the gathered context and user answers.");
1229
- parts.push("");
1230
- if (template) {
1231
- parts.push("## Document Template");
1232
- parts.push("");
1233
- parts.push("**CRITICAL: You MUST follow this template structure completely.**");
1234
- parts.push("");
1235
- parts.push(template);
1236
- parts.push("");
1237
- }
1238
- parts.push("## Gathered Context");
1239
- parts.push("");
1240
- parts.push(params.gatheredContext);
1241
- parts.push("");
1242
- if (params.answers && params.answers.length > 0) {
1243
- parts.push("## User Answers");
1244
- parts.push("");
1245
- for (const answer of params.answers) {
1246
- parts.push(`- **${answer.questionId}**: ${answer.answer}`);
1247
- }
1248
- parts.push("");
1249
- }
1250
- if (isFirstStep) {
1251
- parts.push("## User Request");
1252
- parts.push("");
1253
- parts.push(`> ${params.description}`);
1254
- parts.push("");
1255
- } else if (previousSpec) {
1256
- parts.push(`## Input: ${previousSpec.step}.md`);
1257
- parts.push("");
1258
- parts.push(previousSpec.content);
1259
- parts.push("");
1260
- }
1261
- parts.push("## Task Context");
1262
- parts.push("");
1263
- parts.push(`- **Task ID**: ${params.taskId}`);
1264
- parts.push(`- **Current Step**: ${params.step} (${params.stepIndex}/${params.totalSteps})`);
1265
- parts.push("");
1266
- if (params.revisionFeedback) {
1267
- parts.push("## Revision Feedback");
1268
- parts.push("");
1269
- parts.push("Previous version was not approved. Please revise based on this feedback:");
1270
- parts.push("");
1271
- parts.push(`> ${params.revisionFeedback}`);
1272
- parts.push("");
1273
- }
1274
- parts.push("## Output Instructions");
1275
- parts.push("");
1276
- parts.push(`Generate the document and save to: \`${outputPath}\``);
1277
- parts.push("");
1278
- parts.push("**Required frontmatter:**");
1279
- parts.push("```yaml");
1280
- parts.push("---");
1281
- parts.push(`id: ${params.taskId}`);
1282
- parts.push(`step: ${params.step}`);
1283
- parts.push("status: pending_approval");
1284
- parts.push("---");
1285
- parts.push("```");
1286
- parts.push("");
1287
- return {
1288
- prompt: parts.join("\n"),
1289
- outputPath
1290
- };
1291
- }
1292
1034
 
1293
1035
  // src/core/prompts/execute.ts
1294
1036
  import { readFileSync as readFileSync7, existsSync as existsSync8 } from "fs";
@@ -1425,6 +1167,7 @@ function buildExecutePrompt(params) {
1425
1167
  parts.push("- **Verify your work.** If the output makes claims, make sure they are true.");
1426
1168
  parts.push("- **Follow the patterns** found during exploration.");
1427
1169
  parts.push("- **Use your full capabilities.** Whatever this output requires \u2014 do it.");
1170
+ parts.push("- **Be context-efficient.** Use the explored context above as your primary reference. Use Grep to locate code before reading. Never re-read a file you have already read in this session.");
1428
1171
  parts.push("");
1429
1172
  parts.push("## Output");
1430
1173
  parts.push("");
@@ -1967,7 +1710,7 @@ function buildCompleteResponse(cwd, state, status) {
1967
1710
  }
1968
1711
  const messages = {
1969
1712
  completed: "Workflow completed successfully",
1970
- stopped: `Workflow stopped. Resume with: spets resume --task ${state.taskId}`,
1713
+ stopped: `Workflow stopped. Resume with: spets orchestrate resume ${state.taskId}`,
1971
1714
  rejected: "Workflow rejected"
1972
1715
  };
1973
1716
  return {
@@ -2383,3063 +2126,75 @@ var Orchestrator = class {
2383
2126
  }
2384
2127
  };
2385
2128
 
2386
- // src/core/step-executor.ts
2387
- import { existsSync as existsSync12 } from "fs";
2388
- import { join as join10 } from "path";
2389
-
2390
- // src/core/parsers.ts
2391
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync6 } from "fs";
2392
- import matter2 from "gray-matter";
2393
- function parseContextOutput(response) {
2394
- try {
2395
- const jsonMatch = response.match(/\{[\s\S]*\}/);
2396
- if (jsonMatch) {
2397
- return JSON.parse(jsonMatch[0]);
2398
- }
2399
- } catch {
2400
- }
2401
- return {
2402
- summary: response,
2403
- relevantFiles: [],
2404
- keyFindings: []
2405
- };
2129
+ // src/commands/orchestrate.ts
2130
+ function outputJSON(data) {
2131
+ console.log(JSON.stringify(data, null, 2));
2406
2132
  }
2407
- function parseClarifyOutput(response) {
2408
- try {
2409
- const jsonMatch = response.match(/\{[\s\S]*\}/);
2410
- if (jsonMatch) {
2411
- const parsed = JSON.parse(jsonMatch[0]);
2412
- return {
2413
- ready: parsed.ready ?? false,
2414
- decisions: Array.isArray(parsed.decisions) ? parsed.decisions : [],
2415
- summary: parsed.summary
2416
- };
2417
- }
2418
- } catch {
2419
- }
2420
- return { ready: true, decisions: [] };
2133
+ function outputError(error) {
2134
+ console.log(JSON.stringify({ type: "error", error }, null, 2));
2135
+ process.exit(1);
2421
2136
  }
2422
- function parseExploreOutput(response) {
2137
+ function parseFlexibleJSON(json, options = {}) {
2138
+ const { expectArray = false, arrayKeys = [], defaultValue, allowStringFallback = false } = options;
2423
2139
  try {
2424
- const jsonMatch = response.match(/\{[\s\S]*\}/);
2425
- if (jsonMatch) {
2426
- const parsed = JSON.parse(jsonMatch[0]);
2427
- if (typeof parsed.summary === "string") {
2428
- return {
2429
- summary: parsed.summary || "",
2430
- relevantFiles: Array.isArray(parsed.relevantFiles) ? parsed.relevantFiles : [],
2431
- patterns: Array.isArray(parsed.patterns) ? parsed.patterns : [],
2432
- constraints: Array.isArray(parsed.constraints) ? parsed.constraints : [],
2433
- dependencies: Array.isArray(parsed.dependencies) ? parsed.dependencies : []
2434
- };
2140
+ const parsed = JSON.parse(json);
2141
+ if (expectArray) {
2142
+ if (Array.isArray(parsed)) {
2143
+ return { success: true, data: parsed };
2144
+ }
2145
+ if (typeof parsed === "object" && parsed !== null) {
2146
+ for (const key of arrayKeys) {
2147
+ if (Array.isArray(parsed[key])) {
2148
+ return { success: true, data: parsed[key] };
2149
+ }
2150
+ }
2151
+ for (const key of ["data", "items", "results", "list"]) {
2152
+ if (Array.isArray(parsed[key])) {
2153
+ return { success: true, data: parsed[key] };
2154
+ }
2155
+ }
2156
+ const values = Object.values(parsed);
2157
+ const arrays = values.filter(Array.isArray);
2158
+ if (arrays.length === 1) {
2159
+ return { success: true, data: arrays[0] };
2160
+ }
2435
2161
  }
2162
+ return { success: true, data: [] };
2436
2163
  }
2437
- } catch {
2438
- }
2439
- return {
2440
- summary: response,
2441
- relevantFiles: [],
2442
- patterns: [],
2443
- constraints: [],
2444
- dependencies: []
2445
- };
2446
- }
2447
- function parseVerifyOutput(response) {
2448
- try {
2449
- const jsonMatch = response.match(/\{[\s\S]*\}/);
2450
- if (jsonMatch) {
2451
- const parsed = JSON.parse(jsonMatch[0]);
2452
- if (typeof parsed.passed === "boolean" && parsed.score) {
2453
- return {
2454
- passed: parsed.passed,
2455
- score: {
2456
- accuracy: parsed.score.accuracy ?? 0,
2457
- completeness: parsed.score.completeness ?? 0,
2458
- consistency: parsed.score.consistency ?? 0
2459
- },
2460
- issues: Array.isArray(parsed.issues) ? parsed.issues : [],
2461
- summary: parsed.summary || ""
2462
- };
2164
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
2165
+ const keys = Object.keys(parsed);
2166
+ if (keys.length === 1) {
2167
+ const innerValue = parsed[keys[0]];
2168
+ if (typeof innerValue === "object" && innerValue !== null && !Array.isArray(innerValue)) {
2169
+ const innerKeys = Object.keys(innerValue);
2170
+ if (innerKeys.length > 1) {
2171
+ return { success: true, data: innerValue };
2172
+ }
2173
+ }
2463
2174
  }
2175
+ return { success: true, data: parsed };
2464
2176
  }
2177
+ return { success: true, data: parsed };
2465
2178
  } catch {
2179
+ if (allowStringFallback && defaultValue !== void 0) {
2180
+ return { success: true, data: defaultValue };
2181
+ }
2182
+ return { success: false, error: "Invalid JSON format" };
2466
2183
  }
2467
- return {
2468
- passed: false,
2469
- score: {
2470
- accuracy: 0,
2471
- completeness: 0,
2472
- consistency: 0
2473
- },
2474
- issues: [{
2475
- severity: "error",
2476
- category: "parsing",
2477
- description: "Failed to parse verification output",
2478
- suggestion: "Ensure AI returns valid JSON"
2479
- }],
2480
- summary: "Verification parsing failed"
2481
- };
2482
2184
  }
2483
- function parseKnowledgeOutput(response) {
2185
+ async function orchestrateCommand(action, args) {
2484
2186
  try {
2485
- const jsonMatch = response.match(/\{[\s\S]*\}/);
2486
- if (jsonMatch) {
2487
- const parsed = JSON.parse(jsonMatch[0]);
2488
- const entries = Array.isArray(parsed.entries) ? parsed.entries : [];
2489
- return {
2490
- entries: entries.map((e) => ({
2491
- filename: e.filename || "",
2492
- content: e.content || "",
2493
- reason: e.reason || "",
2494
- action: e.action === "update" ? "update" : "create"
2495
- }))
2496
- };
2497
- }
2498
- const arrayMatch = response.match(/\[[\s\S]*\]/);
2499
- if (arrayMatch) {
2500
- const parsed = JSON.parse(arrayMatch[0]);
2501
- if (Array.isArray(parsed)) {
2502
- return {
2503
- entries: parsed.map((e) => ({
2504
- filename: e.filename || "",
2505
- content: e.content || "",
2506
- reason: e.reason || "",
2507
- action: e.action === "update" ? "update" : "create"
2508
- }))
2509
- };
2510
- }
2511
- }
2512
- } catch {
2513
- }
2514
- return { entries: [] };
2515
- }
2516
- function updateDocumentStatus(docPath, status) {
2517
- const content = readFileSync11(docPath, "utf-8");
2518
- const { content: body, data } = matter2(content);
2519
- data.status = status;
2520
- data.updated_at = (/* @__PURE__ */ new Date()).toISOString();
2521
- writeFileSync6(docPath, matter2.stringify(body, data));
2522
- }
2523
-
2524
- // src/core/step-executor.ts
2525
- var StepExecutor = class {
2526
- adapter;
2527
- config;
2528
- cwd;
2529
- constructor(adapter, config, cwd = process.cwd()) {
2530
- this.adapter = adapter;
2531
- this.config = config;
2532
- this.cwd = cwd;
2533
- }
2534
- // ==========================================================================
2535
- // Phase 1: Context Gathering
2536
- // ==========================================================================
2537
- /**
2538
- * Execute context phase - AI gathers context
2539
- * Returns gathered context as string
2540
- * @deprecated Use executeExplorePhase instead
2541
- */
2542
- async executeContextPhase(step, context) {
2543
- this.adapter.io.notify(`Phase 1/4: Gathering context for ${step}`, "info");
2544
- const params = {
2545
- taskId: context.taskId,
2546
- step,
2547
- description: context.description,
2548
- stepIndex: context.stepIndex,
2549
- totalSteps: context.totalSteps,
2550
- previousOutput: context.previousOutput,
2551
- cwd: this.cwd
2552
- };
2553
- const prompt = buildContextPrompt(params);
2554
- const response = await this.adapter.ai.execute({
2555
- prompt,
2556
- outputPath: ""
2557
- // No file output needed
2558
- });
2559
- const contextOutput = parseContextOutput(response || "");
2560
- return {
2561
- phase: "context",
2562
- context: JSON.stringify(contextOutput)
2563
- };
2564
- }
2565
- // ==========================================================================
2566
- // Phase 1 (New): Explore - Deep Codebase Analysis
2567
- // ==========================================================================
2568
- /**
2569
- * Execute explore phase - AI deeply analyzes codebase
2570
- * Returns detailed exploration output with patterns, constraints, etc.
2571
- */
2572
- async executeExplorePhase(step, context) {
2573
- this.adapter.io.notify(`Phase 1/5: Exploring codebase for ${step}`, "info");
2574
- const params = {
2575
- taskId: context.taskId,
2576
- step,
2577
- description: context.description,
2578
- stepIndex: context.stepIndex,
2579
- totalSteps: context.totalSteps,
2580
- previousOutput: context.previousOutput,
2581
- cwd: this.cwd
2582
- };
2583
- let exploreOutput;
2584
- if (this.adapter.ai.executeExplore) {
2585
- exploreOutput = await this.adapter.ai.executeExplore(params);
2586
- } else {
2587
- const prompt = buildExplorePrompt(params);
2588
- const response = await this.adapter.ai.execute({
2589
- prompt,
2590
- outputPath: ""
2591
- // No file output needed
2592
- });
2593
- exploreOutput = parseExploreOutput(response || "");
2594
- }
2595
- this.adapter.io.notify(`Found ${exploreOutput.relevantFiles.length} relevant files, ${exploreOutput.patterns.length} patterns`, "info");
2596
- return {
2597
- phase: "explore",
2598
- exploreOutput,
2599
- context: JSON.stringify(exploreOutput)
2600
- // Also set legacy context
2601
- };
2602
- }
2603
- // ==========================================================================
2604
- // Phase 2: Clarify (Question Generation)
2605
- // ==========================================================================
2606
- /**
2607
- * Execute clarify phase - AI generates questions
2608
- * Returns questions (may be empty)
2609
- */
2610
- async executeClarifyPhase(step, context) {
2611
- this.adapter.io.notify(`Phase 2/4: Generating questions for ${step}`, "info");
2612
- if (!context.gatheredContext) {
2613
- throw new Error("Context phase must be completed before clarify phase");
2614
- }
2615
- const params = {
2616
- taskId: context.taskId,
2617
- step,
2618
- description: context.description,
2619
- gatheredContext: context.gatheredContext,
2620
- previousDecisions: context.previousDecisions,
2621
- previousQA: context.previousQA,
2622
- cwd: this.cwd
2623
- };
2624
- let clarifyOutput;
2625
- if (this.adapter.ai.executeClarify) {
2626
- clarifyOutput = await this.adapter.ai.executeClarify(params);
2627
- } else {
2628
- const prompt = buildClarifyPrompt(params);
2629
- const response = await this.adapter.ai.execute({
2630
- prompt,
2631
- outputPath: ""
2632
- // No file output needed
2633
- });
2634
- clarifyOutput = parseClarifyOutput(response || "");
2635
- }
2636
- if (clarifyOutput.decisions.length > 0) {
2637
- this.adapter.io.notify(`${clarifyOutput.decisions.length} decision(s) need input`, "warning");
2638
- } else {
2639
- this.adapter.io.notify("No decisions needed, proceeding to execute", "info");
2640
- }
2641
- return {
2642
- phase: "clarify",
2643
- clarifyOutput
2644
- };
2645
- }
2646
- /**
2647
- * Ask questions to human
2648
- * Returns answers (or pending if async mode)
2649
- */
2650
- async askQuestions(questions) {
2651
- const answers = await this.adapter.io.askMultiple(questions);
2652
- if (answers.length === 0) {
2653
- return { phase: "clarify", questions, pending: true };
2654
- }
2655
- return { phase: "clarify", questions: [] };
2656
- }
2657
- // ==========================================================================
2658
- // Phase 3: Generate Document
2659
- // ==========================================================================
2660
- /**
2661
- * Execute generate phase - AI creates document
2662
- * @deprecated Use executeDraftPhase instead
2663
- */
2664
- async executeGeneratePhase(step, context) {
2665
- this.adapter.io.notify(`Phase 3/4: Generating document for ${step}`, "info");
2666
- if (!context.gatheredContext) {
2667
- throw new Error("Context phase must be completed before generate phase");
2668
- }
2669
- const params = {
2670
- taskId: context.taskId,
2671
- step,
2672
- description: context.description,
2673
- stepIndex: context.stepIndex,
2674
- totalSteps: context.totalSteps,
2675
- gatheredContext: context.gatheredContext,
2676
- answers: context.answers,
2677
- revisionFeedback: context.revisionFeedback,
2678
- cwd: this.cwd
2679
- };
2680
- const { prompt, outputPath } = buildGeneratePrompt(params);
2681
- await this.adapter.ai.execute({ prompt, outputPath });
2682
- if (!existsSync12(outputPath)) {
2683
- throw new Error(`AI did not create document at ${outputPath}`);
2684
- }
2685
- this.adapter.io.notify(`Document created: ${outputPath}`, "success");
2686
- return { phase: "generate" };
2687
- }
2688
- // ==========================================================================
2689
- // Phase 3 (New): Execute - Document/Code Generation with Explore Output
2690
- // ==========================================================================
2691
- /**
2692
- * Execute the execute phase - AI creates document/code using explore output
2693
- */
2694
- async executeExecutePhase(step, context) {
2695
- const attemptInfo = context.verifyAttempts && context.verifyAttempts > 1 ? ` (attempt ${context.verifyAttempts}/3)` : "";
2696
- this.adapter.io.notify(`Phase 3/5: Executing for ${step}${attemptInfo}`, "info");
2697
- if (!context.exploreOutput && !context.gatheredContext) {
2698
- throw new Error("Explore phase must be completed before draft phase");
2699
- }
2700
- const exploreOutput = context.exploreOutput || {
2701
- summary: context.gatheredContext || "",
2702
- relevantFiles: [],
2703
- patterns: [],
2704
- constraints: [],
2705
- dependencies: []
2706
- };
2707
- const params = {
2708
- taskId: context.taskId,
2709
- step,
2710
- description: context.description,
2711
- stepIndex: context.stepIndex,
2712
- totalSteps: context.totalSteps,
2713
- exploreOutput,
2714
- answers: context.answers,
2715
- revisionFeedback: context.revisionFeedback,
2716
- verifyFeedback: context.verifyFeedback,
2717
- cwd: this.cwd
2718
- };
2719
- const { prompt, outputPath } = buildExecutePrompt(params);
2720
- await this.adapter.ai.execute({ prompt, outputPath });
2721
- if (!existsSync12(outputPath)) {
2722
- throw new Error(`AI did not create document at ${outputPath}`);
2723
- }
2724
- this.adapter.io.notify(`Document created: ${outputPath}`, "success");
2725
- return { phase: "execute" };
2726
- }
2727
- /**
2728
- * @deprecated Use executeExecutePhase instead
2729
- */
2730
- async executeDraftPhase(step, context) {
2731
- return this.executeExecutePhase(step, context);
2732
- }
2733
- // ==========================================================================
2734
- // Phase 4 (New): Verify - Self-Validation
2735
- // ==========================================================================
2736
- /**
2737
- * Execute verify phase - AI validates the generated document
2738
- */
2739
- async executeVerifyPhase(step, context) {
2740
- const attempts = context.verifyAttempts || 1;
2741
- this.adapter.io.notify(`Phase 4/5: Verifying document for ${step} (attempt ${attempts}/3)`, "info");
2742
- const outputsDir = getOutputsDir(this.cwd);
2743
- const documentPath = join10(outputsDir, context.taskId, `${step}.md`);
2744
- if (!existsSync12(documentPath)) {
2745
- throw new Error(`Document not found: ${documentPath}`);
2746
- }
2747
- const params = {
2748
- taskId: context.taskId,
2749
- step,
2750
- description: context.description,
2751
- stepIndex: context.stepIndex,
2752
- totalSteps: context.totalSteps,
2753
- documentPath,
2754
- verifyAttempts: attempts,
2755
- cwd: this.cwd
2756
- };
2757
- let verifyOutput;
2758
- if (this.adapter.ai.executeVerify) {
2759
- verifyOutput = await this.adapter.ai.executeVerify(params);
2760
- } else {
2761
- const prompt = buildVerifyPrompt(params);
2762
- const response = await this.adapter.ai.execute({
2763
- prompt,
2764
- outputPath: ""
2765
- // No file output needed
2766
- });
2767
- verifyOutput = parseVerifyOutput(response || "");
2768
- }
2769
- if (verifyOutput.passed) {
2770
- this.adapter.io.notify(`Verification passed! Scores: ${JSON.stringify(verifyOutput.score)}`, "success");
2771
- } else {
2772
- const errorCount = verifyOutput.issues.filter((i) => i.severity === "error").length;
2773
- const warningCount = verifyOutput.issues.filter((i) => i.severity === "warning").length;
2774
- this.adapter.io.notify(`Verification failed: ${errorCount} errors, ${warningCount} warnings`, "warning");
2775
- }
2776
- return {
2777
- phase: "verify",
2778
- verifyOutput
2779
- };
2780
- }
2781
- // ==========================================================================
2782
- // Knowledge Extraction Phase
2783
- // ==========================================================================
2784
- /**
2785
- * Execute knowledge extraction phase - AI extracts knowledge from workflow
2786
- */
2787
- async executeKnowledgePhase(phaseResponse) {
2788
- this.adapter.io.notify("Extracting knowledge from workflow...", "info");
2789
- const params = {
2790
- taskId: phaseResponse.taskId,
2791
- description: phaseResponse.description,
2792
- decisionHistory: phaseResponse.workflowSummary.decisionHistory,
2793
- exploreOutputs: phaseResponse.workflowSummary.exploreOutputs,
2794
- guide: phaseResponse.guide,
2795
- cwd: this.cwd
2796
- };
2797
- let knowledgeOutput;
2798
- if (this.adapter.ai.executeKnowledge) {
2799
- knowledgeOutput = await this.adapter.ai.executeKnowledge(params);
2800
- } else {
2801
- const prompt = buildKnowledgeExtractPrompt(params);
2802
- const response = await this.adapter.ai.execute({
2803
- prompt,
2804
- outputPath: ""
2805
- // No file output needed
2806
- });
2807
- knowledgeOutput = parseKnowledgeOutput(response || "");
2808
- }
2809
- if (knowledgeOutput.entries.length > 0) {
2810
- this.adapter.io.notify(`Extracted ${knowledgeOutput.entries.length} knowledge entries`, "success");
2811
- } else {
2812
- this.adapter.io.notify("No knowledge entries to save", "info");
2813
- }
2814
- return {
2815
- phase: "knowledge",
2816
- knowledgeOutput
2817
- };
2818
- }
2819
- // ==========================================================================
2820
- // Phase 4: Review (Approval)
2821
- // ==========================================================================
2822
- /**
2823
- * Execute review phase - human approves/revises/rejects
2824
- */
2825
- async executeReviewPhase(step, context) {
2826
- this.adapter.io.notify(`Phase 4/4: Review for ${step}`, "info");
2827
- const outputsDir = getOutputsDir(this.cwd);
2828
- const outputPath = join10(outputsDir, context.taskId, `${step}.md`);
2829
- if (!existsSync12(outputPath)) {
2830
- throw new Error(`Document not found: ${outputPath}`);
2831
- }
2832
- const approval = await this.adapter.io.approve(
2833
- outputPath,
2834
- step,
2835
- context.stepIndex,
2836
- context.totalSteps
2837
- );
2838
- if (approval.pending) {
2839
- return { phase: "review", pending: true };
2840
- }
2841
- if (approval.action === "approve") {
2842
- updateDocumentStatus(outputPath, "approved");
2843
- this.adapter.io.notify(`Step ${step} approved`, "success");
2844
- return { phase: "review", approved: true };
2845
- }
2846
- if (approval.action === "stop") {
2847
- this.adapter.io.notify(`Workflow stopped at step ${step}`, "info");
2848
- return { phase: "review", stopped: true };
2849
- }
2850
- if (approval.action === "reject") {
2851
- updateDocumentStatus(outputPath, "rejected");
2852
- this.adapter.io.notify(`Step ${step} rejected`, "error");
2853
- return { phase: "review", rejected: true };
2854
- }
2855
- this.adapter.io.notify(`Revision requested for step ${step}`, "info");
2856
- return {
2857
- phase: "review",
2858
- revisionFeedback: approval.feedback || "Please revise the document"
2859
- };
2860
- }
2861
- // ==========================================================================
2862
- // Full Step Execution (convenience method)
2863
- // ==========================================================================
2864
- /**
2865
- * Execute all phases of a step
2866
- * For synchronous environments (CLI) - runs through all phases
2867
- * For async environments (GitHub) - may return pending
2868
- *
2869
- * @deprecated Use individual phase methods for 4-phase control
2870
- */
2871
- async execute(step, context) {
2872
- let attempts = 0;
2873
- const maxAttempts = 10;
2874
- let currentContext = { ...context };
2875
- while (attempts < maxAttempts) {
2876
- attempts++;
2877
- if (!currentContext.gatheredContext) {
2878
- const contextResult = await this.executeContextPhase(step, currentContext);
2879
- currentContext.gatheredContext = contextResult.context;
2880
- }
2881
- const clarifyResult = await this.executeClarifyPhase(step, currentContext);
2882
- if (clarifyResult.questions && clarifyResult.questions.length > 0) {
2883
- const answers = await this.adapter.io.askMultiple(clarifyResult.questions);
2884
- if (answers.length === 0) {
2885
- return { phase: "clarify", questions: clarifyResult.questions, pending: true };
2886
- }
2887
- currentContext.answers = answers;
2888
- }
2889
- await this.executeGeneratePhase(step, currentContext);
2890
- const reviewResult = await this.executeReviewPhase(step, currentContext);
2891
- if (reviewResult.pending) {
2892
- return reviewResult;
2893
- }
2894
- if (reviewResult.approved || reviewResult.rejected || reviewResult.stopped) {
2895
- return reviewResult;
2896
- }
2897
- currentContext.revisionFeedback = reviewResult.revisionFeedback;
2898
- this.adapter.io.notify(`Revising step ${step}...`, "info");
2899
- }
2900
- throw new Error(`Maximum revision attempts (${maxAttempts}) exceeded for step ${step}`);
2901
- }
2902
- };
2903
-
2904
- // src/adapters/cli.ts
2905
- import { spawn, spawnSync } from "child_process";
2906
- import { readFileSync as readFileSync12, writeFileSync as writeFileSync7, existsSync as existsSync13, mkdirSync as mkdirSync4 } from "fs";
2907
- import { dirname as dirname3 } from "path";
2908
- import { input, select as select2, confirm as confirm2, editor } from "@inquirer/prompts";
2909
-
2910
- // src/adapters/base.ts
2911
- var BaseAIAdapter = class {
2912
- // ==========================================================================
2913
- // Phase Methods - Default implementations using prompt builders + parsers
2914
- // ==========================================================================
2915
- /**
2916
- * Execute explore phase
2917
- * Builds prompt → calls execute() → parses response
2918
- */
2919
- async executeExplore(params) {
2920
- const prompt = buildExplorePrompt(params);
2921
- const response = await this.execute({ prompt, outputPath: "" });
2922
- return this.parseExploreOutput(response || "");
2923
- }
2924
- /**
2925
- * Execute clarify phase
2926
- * Builds prompt → calls execute() → parses response
2927
- */
2928
- async executeClarify(params) {
2929
- const prompt = buildClarifyPrompt(params);
2930
- const response = await this.execute({ prompt, outputPath: "" });
2931
- return this.parseClarifyOutput(response || "");
2932
- }
2933
- /**
2934
- * Execute verify phase
2935
- * Builds prompt → calls execute() → parses response
2936
- */
2937
- async executeVerify(params) {
2938
- const prompt = buildVerifyPrompt(params);
2939
- const response = await this.execute({ prompt, outputPath: "" });
2940
- return this.parseVerifyOutput(response || "");
2941
- }
2942
- /**
2943
- * Execute knowledge extraction phase
2944
- * Builds prompt → calls execute() → parses response
2945
- */
2946
- async executeKnowledge(params) {
2947
- const prompt = buildKnowledgeExtractPrompt(params);
2948
- const response = await this.execute({ prompt, outputPath: "" });
2949
- return this.parseKnowledgeOutput(response || "");
2950
- }
2951
- // ==========================================================================
2952
- // Parsers - Override in subclasses for AI-specific formats
2953
- // ==========================================================================
2954
- /**
2955
- * Parse explore phase output (JSON)
2956
- * Override in subclass if AI returns different format
2957
- */
2958
- parseExploreOutput(response) {
2959
- try {
2960
- const jsonMatch = response.match(/\{[\s\S]*\}/);
2961
- if (jsonMatch) {
2962
- const parsed = JSON.parse(jsonMatch[0]);
2963
- if (typeof parsed.summary === "string") {
2964
- return {
2965
- summary: parsed.summary || "",
2966
- relevantFiles: Array.isArray(parsed.relevantFiles) ? parsed.relevantFiles : [],
2967
- patterns: Array.isArray(parsed.patterns) ? parsed.patterns : [],
2968
- constraints: Array.isArray(parsed.constraints) ? parsed.constraints : [],
2969
- dependencies: Array.isArray(parsed.dependencies) ? parsed.dependencies : [],
2970
- knowledgeRequests: Array.isArray(parsed.knowledgeRequests) ? parsed.knowledgeRequests : void 0
2971
- };
2972
- }
2973
- }
2974
- } catch {
2975
- }
2976
- return {
2977
- summary: response,
2978
- relevantFiles: [],
2979
- patterns: [],
2980
- constraints: [],
2981
- dependencies: []
2982
- };
2983
- }
2984
- /**
2985
- * Parse clarify phase output (JSON)
2986
- * Expected format: { ready: boolean, decisions: Decision[], summary?: string }
2987
- * Override in subclass if AI returns different format
2988
- */
2989
- parseClarifyOutput(response) {
2990
- try {
2991
- const jsonMatch = response.match(/\{[\s\S]*\}/);
2992
- if (jsonMatch) {
2993
- const parsed = JSON.parse(jsonMatch[0]);
2994
- return {
2995
- ready: parsed.ready ?? false,
2996
- decisions: Array.isArray(parsed.decisions) ? parsed.decisions : [],
2997
- summary: parsed.summary
2998
- };
2999
- }
3000
- } catch {
3001
- }
3002
- return { ready: true, decisions: [] };
3003
- }
3004
- /**
3005
- * Parse verify phase output (JSON)
3006
- * Override in subclass if AI returns different format
3007
- */
3008
- parseVerifyOutput(response) {
3009
- try {
3010
- const jsonMatch = response.match(/\{[\s\S]*\}/);
3011
- if (jsonMatch) {
3012
- const parsed = JSON.parse(jsonMatch[0]);
3013
- if (typeof parsed.passed === "boolean" && parsed.score) {
3014
- return {
3015
- passed: parsed.passed,
3016
- score: {
3017
- accuracy: parsed.score.accuracy ?? 0,
3018
- completeness: parsed.score.completeness ?? 0,
3019
- consistency: parsed.score.consistency ?? 0
3020
- },
3021
- issues: Array.isArray(parsed.issues) ? parsed.issues : [],
3022
- summary: parsed.summary || ""
3023
- };
3024
- }
3025
- }
3026
- } catch {
3027
- }
3028
- return {
3029
- passed: false,
3030
- score: {
3031
- accuracy: 0,
3032
- completeness: 0,
3033
- consistency: 0
3034
- },
3035
- issues: [{
3036
- severity: "error",
3037
- category: "parsing",
3038
- description: "Failed to parse verification output",
3039
- suggestion: "Ensure AI returns valid JSON"
3040
- }],
3041
- summary: "Verification parsing failed"
3042
- };
3043
- }
3044
- /**
3045
- * Parse knowledge extraction output (JSON)
3046
- * Override in subclass if AI returns different format
3047
- */
3048
- parseKnowledgeOutput(response) {
3049
- try {
3050
- const jsonMatch = response.match(/\{[\s\S]*\}/);
3051
- if (jsonMatch) {
3052
- const parsed = JSON.parse(jsonMatch[0]);
3053
- const entries = Array.isArray(parsed.entries) ? parsed.entries : [];
3054
- return {
3055
- entries: entries.map((e) => ({
3056
- filename: e.filename || "",
3057
- content: e.content || "",
3058
- reason: e.reason || ""
3059
- }))
3060
- };
3061
- }
3062
- const arrayMatch = response.match(/\[[\s\S]*\]/);
3063
- if (arrayMatch) {
3064
- const parsed = JSON.parse(arrayMatch[0]);
3065
- if (Array.isArray(parsed)) {
3066
- return {
3067
- entries: parsed.map((e) => ({
3068
- filename: e.filename || "",
3069
- content: e.content || "",
3070
- reason: e.reason || ""
3071
- }))
3072
- };
3073
- }
3074
- }
3075
- } catch {
3076
- }
3077
- return { entries: [] };
3078
- }
3079
- };
3080
-
3081
- // src/adapters/cli.ts
3082
- var CLIAIAdapter = class extends BaseAIAdapter {
3083
- claudeCommand;
3084
- constructor(claudeCommand = "claude") {
3085
- super();
3086
- this.claudeCommand = claudeCommand;
3087
- }
3088
- async execute(params) {
3089
- console.log(`
3090
- \u{1F4DD} Generating...`);
3091
- if (params.outputPath) {
3092
- const outputDir = dirname3(params.outputPath);
3093
- if (!existsSync13(outputDir)) {
3094
- mkdirSync4(outputDir, { recursive: true });
3095
- }
3096
- }
3097
- const stdout = await this.callClaude(params.prompt);
3098
- if (params.outputPath && existsSync13(params.outputPath)) {
3099
- return readFileSync12(params.outputPath, "utf-8");
3100
- }
3101
- return stdout;
3102
- }
3103
- async callClaude(prompt, showSpinner = true) {
3104
- return new Promise((resolve, reject) => {
3105
- const proc = spawn(this.claudeCommand, [
3106
- "--permission-mode",
3107
- "bypassPermissions"
3108
- ], {
3109
- stdio: ["pipe", "pipe", "pipe"]
3110
- });
3111
- proc.stdin.write(prompt);
3112
- proc.stdin.end();
3113
- let stdout = "";
3114
- let stderr = "";
3115
- let spinner;
3116
- if (showSpinner) {
3117
- const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
3118
- let frameIndex = 0;
3119
- spinner = setInterval(() => {
3120
- process.stdout.write(`\r${frames[frameIndex++ % frames.length]} Claude is working...`);
3121
- }, 100);
3122
- }
3123
- const stopSpinner = (success) => {
3124
- if (spinner) {
3125
- clearInterval(spinner);
3126
- process.stdout.write(`\r${success ? "\u2713" : "\u2717"} Claude ${success ? "done" : "failed"}
3127
- `);
3128
- }
3129
- };
3130
- proc.stdout.on("data", (data) => {
3131
- stdout += data.toString();
3132
- });
3133
- proc.stderr.on("data", (data) => {
3134
- stderr += data.toString();
3135
- });
3136
- proc.on("close", (code) => {
3137
- stopSpinner(code === 0);
3138
- if (code !== 0) {
3139
- reject(new Error(`Claude CLI exited with code ${code}: ${stderr}`));
3140
- } else {
3141
- resolve(stdout);
3142
- }
3143
- });
3144
- proc.on("error", (err) => {
3145
- stopSpinner(false);
3146
- reject(new Error(`Failed to run Claude CLI: ${err.message}`));
3147
- });
3148
- });
3149
- }
3150
- /**
3151
- * Execute multiple prompts in parallel with shared progress display
3152
- */
3153
- async executeParallel(params) {
3154
- const total = params.length;
3155
- let completed = 0;
3156
- const results = /* @__PURE__ */ new Map();
3157
- const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
3158
- let frameIndex = 0;
3159
- const spinner = setInterval(() => {
3160
- const progress = `${completed}/${total}`;
3161
- process.stdout.write(`\r${frames[frameIndex++ % frames.length]} Generating sections in parallel... [${progress}]`);
3162
- }, 100);
3163
- const stopSpinner = (success) => {
3164
- clearInterval(spinner);
3165
- const status = success ? "\u2713" : "\u2717";
3166
- const message = success ? `All ${total} sections generated` : `Generation completed with errors`;
3167
- process.stdout.write(`\r${status} ${message}
3168
- `);
3169
- };
3170
- await Promise.all(
3171
- params.map(async (p) => {
3172
- const id = p.id || p.outputPath;
3173
- try {
3174
- if (p.outputPath) {
3175
- const outputDir = dirname3(p.outputPath);
3176
- if (!existsSync13(outputDir)) {
3177
- mkdirSync4(outputDir, { recursive: true });
3178
- }
3179
- }
3180
- await this.callClaude(p.prompt, false);
3181
- let result = "";
3182
- if (p.outputPath && existsSync13(p.outputPath)) {
3183
- result = readFileSync12(p.outputPath, "utf-8");
3184
- }
3185
- completed++;
3186
- results.set(id, { id, success: true, result });
3187
- } catch (error) {
3188
- completed++;
3189
- results.set(id, {
3190
- id,
3191
- success: false,
3192
- error: error instanceof Error ? error.message : String(error)
3193
- });
3194
- }
3195
- })
3196
- );
3197
- stopSpinner(Array.from(results.values()).every((r) => r.success));
3198
- return params.map((p) => results.get(p.id || p.outputPath));
3199
- }
3200
- };
3201
- var CLIIOAdapter = class {
3202
- async ask(question) {
3203
- if (question.context) {
3204
- console.log(`
3205
- Context: ${question.context}`);
3206
- }
3207
- if (question.options && question.options.length > 0) {
3208
- const answer2 = await select2({
3209
- message: question.question,
3210
- choices: question.options.map((opt) => ({ value: opt, name: opt }))
3211
- });
3212
- return answer2;
3213
- }
3214
- const answer = await input({
3215
- message: question.question
3216
- });
3217
- return answer;
3218
- }
3219
- async askMultiple(questions) {
3220
- const answers = [];
3221
- console.log("\n\u2753 Please answer the following questions:\n");
3222
- for (const q of questions) {
3223
- const answer = await this.ask(q);
3224
- answers.push({
3225
- questionId: q.id,
3226
- answer
3227
- });
3228
- console.log("");
3229
- }
3230
- return answers;
3231
- }
3232
- async askDecisions(decisions) {
3233
- const answers = [];
3234
- console.log("\n\u{1F914} Please make the following decisions:\n");
3235
- for (const d of decisions) {
3236
- console.log(`
3237
- \u{1F4CB} ${d.decision}`);
3238
- console.log(` Why: ${d.why}
3239
- `);
3240
- const choices = d.options.map((opt) => {
3241
- let name = opt.label;
3242
- if (opt.id === "ai" && opt.recommendation) {
3243
- name = `${opt.label} \u2192 ${opt.recommendation} (${opt.reason})`;
3244
- } else if (opt.tradeoffs) {
3245
- name = `${opt.label} - ${opt.tradeoffs}`;
3246
- }
3247
- return { value: opt.id, name };
3248
- });
3249
- choices.push({ value: "_custom_", name: "\u270F\uFE0F Other (custom input)" });
3250
- const selected = await select2({
3251
- message: "Choose an option:",
3252
- choices
3253
- });
3254
- if (selected === "_custom_") {
3255
- const customInput = await input({
3256
- message: "Enter your custom input:"
3257
- });
3258
- answers.push({
3259
- decisionId: d.id,
3260
- selectedOptionId: "_custom_",
3261
- customInput
3262
- });
3263
- } else {
3264
- answers.push({
3265
- decisionId: d.id,
3266
- selectedOptionId: selected
3267
- });
3268
- }
3269
- }
3270
- return answers;
3271
- }
3272
- async approve(specPath, stepName, stepIndex, totalSteps) {
3273
- if (existsSync13(specPath)) {
3274
- const doc = readFileSync12(specPath, "utf-8");
3275
- console.log("\n" + "=".repeat(60));
3276
- console.log(`\u{1F4C4} ${stepName} Document (Step ${stepIndex}/${totalSteps})`);
3277
- console.log("=".repeat(60));
3278
- console.log(doc);
3279
- console.log("=".repeat(60) + "\n");
3280
- }
3281
- const action = await select2({
3282
- message: "What would you like to do?",
3283
- choices: [
3284
- { value: "approve", name: "\u2705 Approve - Continue to next step" },
3285
- { value: "revise", name: "\u270F\uFE0F Revise - Request changes" },
3286
- { value: "reject", name: "\u274C Reject - Stop workflow" },
3287
- { value: "stop", name: "\u23F8\uFE0F Stop - Save and exit (can resume later)" }
3288
- ]
3289
- });
3290
- if (action === "revise") {
3291
- const feedback = await input({
3292
- message: "What changes would you like?"
3293
- });
3294
- return { action: "revise", feedback };
3295
- }
3296
- if (action === "reject") {
3297
- const confirmed = await confirm2({
3298
- message: "Are you sure you want to reject? This will stop the workflow.",
3299
- default: false
3300
- });
3301
- if (!confirmed) {
3302
- return this.approve(specPath, stepName, stepIndex, totalSteps);
3303
- }
3304
- }
3305
- return { action };
3306
- }
3307
- notify(message, type) {
3308
- const icons = {
3309
- info: "\u2139\uFE0F",
3310
- success: "\u2705",
3311
- warning: "\u26A0\uFE0F",
3312
- error: "\u274C"
3313
- };
3314
- console.log(`${icons[type]} ${message}`);
3315
- }
3316
- async askKnowledge(checkpoint) {
3317
- console.log("\n" + "=".repeat(60));
3318
- console.log("\u{1F4DA} Knowledge Extraction");
3319
- console.log("=".repeat(60));
3320
- if (checkpoint.guide) {
3321
- console.log("\n\u{1F4D6} Guide:");
3322
- console.log(checkpoint.guide);
3323
- }
3324
- if (checkpoint.suggestedKnowledge.length === 0) {
3325
- console.log("\nNo knowledge suggestions from this workflow.");
3326
- const shouldAdd = await confirm2({
3327
- message: "Would you like to add custom knowledge entries?",
3328
- default: false
3329
- });
3330
- if (!shouldAdd) {
3331
- return { entries: [], skipped: true };
3332
- }
3333
- } else {
3334
- console.log("\n\u{1F4A1} Suggested knowledge from this workflow:\n");
3335
- for (const suggestion of checkpoint.suggestedKnowledge) {
3336
- console.log(` \u{1F4DD} ${suggestion.filename}`);
3337
- console.log(` Reason: ${suggestion.reason}`);
3338
- console.log(` Preview: ${suggestion.content.substring(0, 100)}${suggestion.content.length > 100 ? "..." : ""}`);
3339
- console.log("");
3340
- }
3341
- }
3342
- const action = await select2({
3343
- message: "What would you like to do?",
3344
- choices: [
3345
- { value: "save_suggested", name: "\u2705 Save suggested knowledge" },
3346
- { value: "edit", name: "\u270F\uFE0F Edit and save (opens editor)" },
3347
- { value: "skip", name: "\u23ED\uFE0F Skip - don't save any knowledge" }
3348
- ]
3349
- });
3350
- if (action === "skip") {
3351
- return { entries: [], skipped: true };
3352
- }
3353
- if (action === "save_suggested") {
3354
- return {
3355
- entries: checkpoint.suggestedKnowledge.map((s) => ({
3356
- filename: s.filename,
3357
- content: s.content
3358
- })),
3359
- skipped: false
3360
- };
3361
- }
3362
- const entries = [];
3363
- for (const suggestion of checkpoint.suggestedKnowledge) {
3364
- const shouldSave = await confirm2({
3365
- message: `Save "${suggestion.filename}"?`,
3366
- default: true
3367
- });
3368
- if (shouldSave) {
3369
- const filename = await input({
3370
- message: "Filename (without .md):",
3371
- default: suggestion.filename
3372
- });
3373
- const content = await editor({
3374
- message: "Content (save and close editor):",
3375
- default: suggestion.content
3376
- });
3377
- entries.push({ filename, content });
3378
- }
3379
- }
3380
- let addMore = await confirm2({
3381
- message: "Add more custom knowledge entries?",
3382
- default: false
3383
- });
3384
- while (addMore) {
3385
- const filename = await input({
3386
- message: "Filename (without .md):"
3387
- });
3388
- const content = await editor({
3389
- message: "Content (save and close editor):"
3390
- });
3391
- if (filename && content) {
3392
- entries.push({ filename, content });
3393
- }
3394
- addMore = await confirm2({
3395
- message: "Add another entry?",
3396
- default: false
3397
- });
3398
- }
3399
- return { entries, skipped: entries.length === 0 };
3400
- }
3401
- };
3402
- var CLISystemAdapter = class {
3403
- readFile(path) {
3404
- return readFileSync12(path, "utf-8");
3405
- }
3406
- writeFile(path, content) {
3407
- const dir = dirname3(path);
3408
- if (!existsSync13(dir)) {
3409
- mkdirSync4(dir, { recursive: true });
3410
- }
3411
- writeFileSync7(path, content);
3412
- }
3413
- fileExists(path) {
3414
- return existsSync13(path);
3415
- }
3416
- exec(command) {
3417
- try {
3418
- const result = spawnSync("sh", ["-c", command], {
3419
- encoding: "utf-8"
3420
- });
3421
- return {
3422
- stdout: result.stdout || "",
3423
- stderr: result.stderr || "",
3424
- exitCode: result.status || 0
3425
- };
3426
- } catch (e) {
3427
- return {
3428
- stdout: "",
3429
- stderr: e.message,
3430
- exitCode: 1
3431
- };
3432
- }
3433
- }
3434
- };
3435
- function createCLIAdapter(claudeCommand = "claude") {
3436
- return {
3437
- ai: new CLIAIAdapter(claudeCommand),
3438
- io: new CLIIOAdapter(),
3439
- system: new CLISystemAdapter()
3440
- };
3441
- }
3442
-
3443
- // src/adapters/github.ts
3444
- import { spawn as spawn2, spawnSync as spawnSync2 } from "child_process";
3445
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync8, existsSync as existsSync14, mkdirSync as mkdirSync5 } from "fs";
3446
- import { dirname as dirname4 } from "path";
3447
- var GitHubAIAdapter = class extends BaseAIAdapter {
3448
- claudeCommand;
3449
- constructor(claudeCommand = "claude") {
3450
- super();
3451
- this.claudeCommand = claudeCommand;
3452
- }
3453
- async execute(params) {
3454
- console.log(`
3455
- \u{1F4DD} Generating...`);
3456
- if (params.outputPath) {
3457
- const outputDir = dirname4(params.outputPath);
3458
- if (!existsSync14(outputDir)) {
3459
- mkdirSync5(outputDir, { recursive: true });
3460
- }
3461
- }
3462
- const stdout = await this.callClaude(params.prompt);
3463
- if (params.outputPath && existsSync14(params.outputPath)) {
3464
- return readFileSync13(params.outputPath, "utf-8");
3465
- }
3466
- return stdout;
3467
- }
3468
- async callClaude(prompt) {
3469
- return new Promise((resolve, reject) => {
3470
- const proc = spawn2(this.claudeCommand, [
3471
- "--permission-mode",
3472
- "bypassPermissions"
3473
- ], {
3474
- stdio: ["pipe", "pipe", "pipe"]
3475
- });
3476
- proc.stdin.write(prompt);
3477
- proc.stdin.end();
3478
- let stdout = "";
3479
- let stderr = "";
3480
- proc.stdout.on("data", (data) => {
3481
- const chunk = data.toString();
3482
- stdout += chunk;
3483
- process.stdout.write(chunk);
3484
- });
3485
- proc.stderr.on("data", (data) => {
3486
- stderr += data.toString();
3487
- });
3488
- proc.on("close", (code) => {
3489
- if (code !== 0) {
3490
- reject(new Error(`Claude CLI exited with code ${code}: ${stderr}`));
3491
- } else {
3492
- resolve(stdout);
3493
- }
3494
- });
3495
- proc.on("error", (err) => {
3496
- reject(new Error(`Failed to run Claude CLI: ${err.message}`));
3497
- });
3498
- });
3499
- }
3500
- /**
3501
- * Execute multiple prompts in parallel
3502
- * In GitHub Actions, we show progress in logs
3503
- */
3504
- async executeParallel(params) {
3505
- const total = params.length;
3506
- let completed = 0;
3507
- const results = /* @__PURE__ */ new Map();
3508
- console.log(`
3509
- \u{1F680} Starting parallel generation of ${total} sections...`);
3510
- await Promise.all(
3511
- params.map(async (p) => {
3512
- const id = p.id || p.outputPath;
3513
- try {
3514
- if (p.outputPath) {
3515
- const outputDir = dirname4(p.outputPath);
3516
- if (!existsSync14(outputDir)) {
3517
- mkdirSync5(outputDir, { recursive: true });
3518
- }
3519
- }
3520
- await this.callClaude(p.prompt);
3521
- let result = "";
3522
- if (p.outputPath && existsSync14(p.outputPath)) {
3523
- result = readFileSync13(p.outputPath, "utf-8");
3524
- }
3525
- completed++;
3526
- console.log(`\u2713 [${completed}/${total}] ${id}`);
3527
- results.set(id, { id, success: true, result });
3528
- } catch (error) {
3529
- completed++;
3530
- console.log(`\u2717 [${completed}/${total}] ${id} - FAILED`);
3531
- results.set(id, {
3532
- id,
3533
- success: false,
3534
- error: error instanceof Error ? error.message : String(error)
3535
- });
3536
- }
3537
- })
3538
- );
3539
- const successCount = Array.from(results.values()).filter((r) => r.success).length;
3540
- console.log(`
3541
- \u{1F4CA} Parallel generation complete: ${successCount}/${total} succeeded
3542
- `);
3543
- return params.map((p) => results.get(p.id || p.outputPath));
3544
- }
3545
- };
3546
- var GitHubIOAdapter = class {
3547
- config;
3548
- taskId;
3549
- constructor(config) {
3550
- this.config = config;
3551
- }
3552
- setTaskId(taskId) {
3553
- this.taskId = taskId;
3554
- }
3555
- async ask(question) {
3556
- console.log(`\u{1F4DD} Question queued: ${question.question}`);
3557
- return "";
3558
- }
3559
- async askMultiple(questions) {
3560
- const comment = this.formatQuestionsComment(questions);
3561
- await this.postComment(comment);
3562
- console.log("\n\u23F8\uFE0F Questions posted to GitHub. Waiting for answers...");
3563
- console.log(" Answer with /answer command on the Issue/PR.");
3564
- this.pendingQuestions = questions;
3565
- return [];
3566
- }
3567
- async askDecisions(decisions) {
3568
- const comment = this.formatDecisionsComment(decisions);
3569
- await this.postComment(comment);
3570
- console.log("\n\u23F8\uFE0F Decisions posted to GitHub. Waiting for input...");
3571
- console.log(" Answer with /decide command on the Issue/PR.");
3572
- this.pendingDecisions = decisions;
3573
- return [];
3574
- }
3575
- async approve(specPath, stepName, stepIndex, totalSteps) {
3576
- const doc = existsSync14(specPath) ? readFileSync13(specPath, "utf-8") : "";
3577
- const comment = this.formatApprovalComment(doc, stepName, stepIndex, totalSteps, specPath);
3578
- await this.postComment(comment);
3579
- console.log("\n\u23F8\uFE0F Approval request posted to GitHub.");
3580
- console.log(" Comment /approve, /revise <feedback>, or /reject on the Issue/PR.");
3581
- return { action: "stop", pending: true };
3582
- }
3583
- /**
3584
- * Check if there are pending decisions awaiting external input
3585
- */
3586
- hasPendingDecisions() {
3587
- return this.pendingDecisions.length > 0;
3588
- }
3589
- /**
3590
- * Get pending decisions for serialization
3591
- */
3592
- getPendingDecisions() {
3593
- return this.pendingDecisions;
3594
- }
3595
- pendingDecisions = [];
3596
- /**
3597
- * @deprecated Use hasPendingDecisions instead
3598
- */
3599
- hasPendingQuestions() {
3600
- return this.pendingQuestions.length > 0;
3601
- }
3602
- /**
3603
- * @deprecated Use getPendingDecisions instead
3604
- */
3605
- getPendingQuestions() {
3606
- return this.pendingQuestions;
3607
- }
3608
- pendingQuestions = [];
3609
- notify(message, type) {
3610
- const icons = {
3611
- info: "\u2139\uFE0F",
3612
- success: "\u2705",
3613
- warning: "\u26A0\uFE0F",
3614
- error: "\u274C"
3615
- };
3616
- console.log(`${icons[type]} ${message}`);
3617
- }
3618
- async askKnowledge(checkpoint) {
3619
- const comment = this.formatKnowledgeComment(checkpoint);
3620
- await this.postComment(comment);
3621
- console.log("\n\u23F8\uFE0F Knowledge extraction posted to GitHub.");
3622
- console.log(" Comment /knowledge-save or /knowledge-skip on the Issue/PR.");
3623
- return { entries: [], skipped: false, pending: true };
3624
- }
3625
- formatKnowledgeComment(checkpoint) {
3626
- const lines = [
3627
- "## \u{1F4DA} Spets: Knowledge Extraction",
3628
- "",
3629
- `> Task ID: \`${this.taskId}\``,
3630
- "",
3631
- "The workflow is complete! Would you like to save any learnings for future workflows?",
3632
- ""
3633
- ];
3634
- if (checkpoint.suggestedKnowledge.length > 0) {
3635
- lines.push("### \u{1F4A1} Suggested Knowledge");
3636
- lines.push("");
3637
- for (const suggestion of checkpoint.suggestedKnowledge) {
3638
- lines.push(`**${suggestion.filename}**`);
3639
- lines.push(`> ${suggestion.reason}`);
3640
- lines.push("");
3641
- lines.push("<details>");
3642
- lines.push("<summary>Preview content</summary>");
3643
- lines.push("");
3644
- lines.push(suggestion.content);
3645
- lines.push("");
3646
- lines.push("</details>");
3647
- lines.push("");
3648
- }
3649
- } else {
3650
- lines.push("No knowledge suggestions from this workflow.");
3651
- lines.push("");
3652
- }
3653
- lines.push("---");
3654
- lines.push("");
3655
- lines.push("**Commands:**");
3656
- lines.push("| Command | Description |");
3657
- lines.push("|---------|-------------|");
3658
- lines.push("| `/knowledge-save` | Save the suggested knowledge |");
3659
- lines.push("| `/knowledge-skip` | Skip knowledge extraction |");
3660
- lines.push("");
3661
- lines.push("To save custom knowledge:");
3662
- lines.push("```");
3663
- lines.push("/knowledge-save");
3664
- lines.push("filename1: your-knowledge-title");
3665
- lines.push("content1: The knowledge content here");
3666
- lines.push("```");
3667
- return lines.join("\n");
3668
- }
3669
- formatQuestionsComment(questions) {
3670
- const lines = [
3671
- "## \u{1F4CB} Spets: Questions Need Answers",
3672
- "",
3673
- `> Task ID: \`${this.taskId}\``,
3674
- "",
3675
- "Please answer the following questions:",
3676
- ""
3677
- ];
3678
- for (let i = 0; i < questions.length; i++) {
3679
- const q = questions[i];
3680
- lines.push(`### Q${i + 1}: ${q.question}`);
3681
- if (q.context) {
3682
- lines.push(`> ${q.context}`);
3683
- }
3684
- lines.push("");
3685
- }
3686
- lines.push("---");
3687
- lines.push("");
3688
- lines.push("**How to answer:**");
3689
- lines.push("");
3690
- lines.push("```");
3691
- lines.push("/answer");
3692
- for (let i = 0; i < questions.length; i++) {
3693
- lines.push(`Q${i + 1}: <your answer for question ${i + 1}>`);
3694
- }
3695
- lines.push("```");
3696
- return lines.join("\n");
3697
- }
3698
- formatDecisionsComment(decisions) {
3699
- const lines = [
3700
- "## \u{1F914} Spets: Decisions Needed",
3701
- "",
3702
- `> Task ID: \`${this.taskId}\``,
3703
- "",
3704
- "Please make the following decisions:",
3705
- ""
3706
- ];
3707
- for (let i = 0; i < decisions.length; i++) {
3708
- const d = decisions[i];
3709
- lines.push(`### Q${i + 1}: ${d.decision}`);
3710
- lines.push(`> ${d.why}`);
3711
- lines.push("");
3712
- lines.push("**Options:**");
3713
- let optNum = 1;
3714
- for (const opt of d.options) {
3715
- if (opt.id === "ai") {
3716
- lines.push(`- **Recommended:** ${opt.recommendation || "AI choice"}${opt.reason ? ` (${opt.reason})` : ""}`);
3717
- } else {
3718
- lines.push(`${optNum}. ${opt.label}${opt.tradeoffs ? ` - ${opt.tradeoffs}` : ""}`);
3719
- optNum++;
3720
- }
3721
- }
3722
- lines.push("");
3723
- }
3724
- lines.push("---");
3725
- lines.push("");
3726
- lines.push("**Respond naturally:**");
3727
- lines.push("");
3728
- lines.push("```");
3729
- lines.push("/decide");
3730
- for (let i = 0; i < decisions.length; i++) {
3731
- lines.push(`Q${i + 1}: <your choice or thoughts>`);
3732
- }
3733
- lines.push("```");
3734
- lines.push("");
3735
- lines.push("Examples:");
3736
- lines.push("- `Q1: I like the first option`");
3737
- lines.push("- `Q1: go with the recommended`");
3738
- lines.push("- `Q1: elaborate on option 2`");
3739
- lines.push("- `Q1: something else - I want to use X instead`");
3740
- return lines.join("\n");
3741
- }
3742
- formatApprovalComment(doc, stepName, stepIndex, totalSteps, specPath) {
3743
- const relativePath = specPath.replace(process.cwd() + "/", "");
3744
- const { owner, repo, branch } = this.config;
3745
- const fileLink = branch ? `[\`${relativePath}\`](https://github.com/${owner}/${repo}/blob/${branch}/${relativePath})` : `\`${relativePath}\``;
3746
- const content = doc;
3747
- const isLastStep = stepIndex === totalSteps;
3748
- const approveDescription = isLastStep ? "Approve and complete workflow" : "Approve and continue to next step";
3749
- const lines = [
3750
- `## \u{1F4C4} Spets: ${stepName} (${stepIndex}/${totalSteps}) - Review Required`,
3751
- "",
3752
- `> Task ID: \`${this.taskId}\``,
3753
- `> Output: ${fileLink}`,
3754
- "",
3755
- // GitHub requires blank lines around markdown content inside HTML tags
3756
- // See: https://github.github.com/gfm/#html-blocks
3757
- "<details>",
3758
- "<summary>\u{1F4DD} View Document</summary>",
3759
- "",
3760
- "",
3761
- content,
3762
- "",
3763
- "</details>",
3764
- "",
3765
- "---",
3766
- "",
3767
- "**Commands:**",
3768
- "| Command | Description |",
3769
- "|---------|-------------|",
3770
- `| \`/approve\` | ${approveDescription} |`,
3771
- "| `/revise <feedback>` | Request changes with feedback |",
3772
- "| `/reject` | Reject and stop workflow |",
3773
- "",
3774
- "Example: `/revise Please add more details about error handling`"
3775
- ];
3776
- return lines.join("\n");
3777
- }
3778
- async postComment(body) {
3779
- const { owner, repo, prNumber, issueNumber } = this.config;
3780
- const target = prNumber ? `pr ${prNumber}` : `issue ${issueNumber}`;
3781
- const args = prNumber ? ["pr", "comment", String(prNumber), "--body", body, "-R", `${owner}/${repo}`] : ["issue", "comment", String(issueNumber), "--body", body, "-R", `${owner}/${repo}`];
3782
- return new Promise((resolve, reject) => {
3783
- const proc = spawn2("gh", args, { stdio: ["pipe", "pipe", "pipe"] });
3784
- let stderr = "";
3785
- proc.stderr.on("data", (data) => {
3786
- stderr += data.toString();
3787
- });
3788
- proc.on("close", (code) => {
3789
- if (code !== 0) {
3790
- reject(new Error(`Failed to post comment to ${target}: ${stderr}`));
3791
- } else {
3792
- resolve();
3793
- }
3794
- });
3795
- proc.on("error", (err) => {
3796
- reject(new Error(`Failed to run gh: ${err.message}`));
3797
- });
3798
- });
3799
- }
3800
- };
3801
- var GitHubSystemAdapter = class {
3802
- readFile(path) {
3803
- return readFileSync13(path, "utf-8");
3804
- }
3805
- writeFile(path, content) {
3806
- const dir = dirname4(path);
3807
- if (!existsSync14(dir)) {
3808
- mkdirSync5(dir, { recursive: true });
3809
- }
3810
- writeFileSync8(path, content);
3811
- }
3812
- fileExists(path) {
3813
- return existsSync14(path);
3814
- }
3815
- exec(command) {
3816
- try {
3817
- const result = spawnSync2("sh", ["-c", command], {
3818
- encoding: "utf-8"
3819
- });
3820
- return {
3821
- stdout: result.stdout || "",
3822
- stderr: result.stderr || "",
3823
- exitCode: result.status || 0
3824
- };
3825
- } catch (e) {
3826
- return {
3827
- stdout: "",
3828
- stderr: e.message,
3829
- exitCode: 1
3830
- };
3831
- }
3832
- }
3833
- };
3834
- function createGitHubAdapter(config, claudeCommand = "claude") {
3835
- return {
3836
- ai: new GitHubAIAdapter(claudeCommand),
3837
- io: new GitHubIOAdapter(config),
3838
- system: new GitHubSystemAdapter()
3839
- };
3840
- }
3841
-
3842
- // src/adapters/codex.ts
3843
- import { spawn as spawn3 } from "child_process";
3844
- import { readFileSync as readFileSync14, existsSync as existsSync15, mkdirSync as mkdirSync6 } from "fs";
3845
- import { dirname as dirname5 } from "path";
3846
- var CodexAIAdapter = class extends BaseAIAdapter {
3847
- codexCommand;
3848
- constructor(codexCommand = "codex") {
3849
- super();
3850
- this.codexCommand = codexCommand;
3851
- }
3852
- async execute(params) {
3853
- console.log(`
3854
- \u{1F4DD} Generating...`);
3855
- if (params.outputPath) {
3856
- const outputDir = dirname5(params.outputPath);
3857
- if (!existsSync15(outputDir)) {
3858
- mkdirSync6(outputDir, { recursive: true });
3859
- }
3860
- }
3861
- const stdout = await this.callCodex(params.prompt);
3862
- if (params.outputPath && existsSync15(params.outputPath)) {
3863
- return readFileSync14(params.outputPath, "utf-8");
3864
- }
3865
- return stdout;
3866
- }
3867
- async callCodex(prompt, showSpinner = true) {
3868
- return new Promise((resolve, reject) => {
3869
- const proc = spawn3(this.codexCommand, [
3870
- "exec",
3871
- "--full-auto"
3872
- ], {
3873
- stdio: ["pipe", "pipe", "pipe"]
3874
- });
3875
- proc.stdin.write(prompt);
3876
- proc.stdin.end();
3877
- let stdout = "";
3878
- let stderr = "";
3879
- let spinner;
3880
- if (showSpinner) {
3881
- const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
3882
- let frameIndex = 0;
3883
- spinner = setInterval(() => {
3884
- process.stdout.write(`\r${frames[frameIndex++ % frames.length]} Codex is working...`);
3885
- }, 100);
3886
- }
3887
- const stopSpinner = (success) => {
3888
- if (spinner) {
3889
- clearInterval(spinner);
3890
- process.stdout.write(`\r${success ? "\u2713" : "\u2717"} Codex ${success ? "done" : "failed"}
3891
- `);
3892
- }
3893
- };
3894
- proc.stdout.on("data", (data) => {
3895
- stdout += data.toString();
3896
- });
3897
- proc.stderr.on("data", (data) => {
3898
- stderr += data.toString();
3899
- });
3900
- proc.on("close", (code) => {
3901
- stopSpinner(code === 0);
3902
- if (code !== 0) {
3903
- reject(new Error(`Codex CLI exited with code ${code}: ${stderr}`));
3904
- } else {
3905
- resolve(stdout);
3906
- }
3907
- });
3908
- proc.on("error", (err) => {
3909
- stopSpinner(false);
3910
- reject(new Error(`Failed to run Codex CLI: ${err.message}`));
3911
- });
3912
- });
3913
- }
3914
- /**
3915
- * Execute multiple prompts in parallel with shared progress display
3916
- */
3917
- async executeParallel(params) {
3918
- const total = params.length;
3919
- let completed = 0;
3920
- const results = /* @__PURE__ */ new Map();
3921
- const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
3922
- let frameIndex = 0;
3923
- const spinner = setInterval(() => {
3924
- const progress = `${completed}/${total}`;
3925
- process.stdout.write(`\r${frames[frameIndex++ % frames.length]} Generating sections in parallel... [${progress}]`);
3926
- }, 100);
3927
- const stopSpinner = (success) => {
3928
- clearInterval(spinner);
3929
- const status = success ? "\u2713" : "\u2717";
3930
- const message = success ? `All ${total} sections generated` : `Generation completed with errors`;
3931
- process.stdout.write(`\r${status} ${message}
3932
- `);
3933
- };
3934
- await Promise.all(
3935
- params.map(async (p) => {
3936
- const id = p.id || p.outputPath;
3937
- try {
3938
- if (p.outputPath) {
3939
- const outputDir = dirname5(p.outputPath);
3940
- if (!existsSync15(outputDir)) {
3941
- mkdirSync6(outputDir, { recursive: true });
3942
- }
3943
- }
3944
- await this.callCodex(p.prompt, false);
3945
- let result = "";
3946
- if (p.outputPath && existsSync15(p.outputPath)) {
3947
- result = readFileSync14(p.outputPath, "utf-8");
3948
- }
3949
- completed++;
3950
- results.set(id, { id, success: true, result });
3951
- } catch (error) {
3952
- completed++;
3953
- results.set(id, {
3954
- id,
3955
- success: false,
3956
- error: error instanceof Error ? error.message : String(error)
3957
- });
3958
- }
3959
- })
3960
- );
3961
- stopSpinner(Array.from(results.values()).every((r) => r.success));
3962
- return params.map((p) => results.get(p.id || p.outputPath));
3963
- }
3964
- };
3965
- function createCodexAdapter(codexCommand = "codex") {
3966
- return {
3967
- ai: new CodexAIAdapter(codexCommand),
3968
- io: new CLIIOAdapter(),
3969
- system: new CLISystemAdapter()
3970
- };
3971
- }
3972
-
3973
- // src/adapters/gemini.ts
3974
- import { spawn as spawn4 } from "child_process";
3975
- import { readFileSync as readFileSync15, existsSync as existsSync16, mkdirSync as mkdirSync7 } from "fs";
3976
- import { dirname as dirname6 } from "path";
3977
- var GeminiAIAdapter = class extends BaseAIAdapter {
3978
- geminiCommand;
3979
- constructor(geminiCommand = "gemini") {
3980
- super();
3981
- this.geminiCommand = geminiCommand;
3982
- }
3983
- async execute(params) {
3984
- console.log(`
3985
- \u{1F4DD} Generating...`);
3986
- if (params.outputPath) {
3987
- const outputDir = dirname6(params.outputPath);
3988
- if (!existsSync16(outputDir)) {
3989
- mkdirSync7(outputDir, { recursive: true });
3990
- }
3991
- }
3992
- const stdout = await this.callGemini(params.prompt);
3993
- if (params.outputPath && existsSync16(params.outputPath)) {
3994
- return readFileSync15(params.outputPath, "utf-8");
3995
- }
3996
- return stdout;
3997
- }
3998
- async callGemini(prompt, showSpinner = true) {
3999
- return new Promise((resolve, reject) => {
4000
- const proc = spawn4(this.geminiCommand, [
4001
- "--prompt",
4002
- prompt,
4003
- "--yolo"
4004
- ], {
4005
- stdio: ["pipe", "pipe", "pipe"]
4006
- });
4007
- let stdout = "";
4008
- let stderr = "";
4009
- let spinner;
4010
- if (showSpinner) {
4011
- const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
4012
- let frameIndex = 0;
4013
- spinner = setInterval(() => {
4014
- process.stdout.write(`\r${frames[frameIndex++ % frames.length]} Gemini is working...`);
4015
- }, 100);
4016
- }
4017
- const stopSpinner = (success) => {
4018
- if (spinner) {
4019
- clearInterval(spinner);
4020
- process.stdout.write(`\r${success ? "\u2713" : "\u2717"} Gemini ${success ? "done" : "failed"}
4021
- `);
4022
- }
4023
- };
4024
- proc.stdout.on("data", (data) => {
4025
- stdout += data.toString();
4026
- });
4027
- proc.stderr.on("data", (data) => {
4028
- stderr += data.toString();
4029
- });
4030
- proc.on("close", (code) => {
4031
- stopSpinner(code === 0);
4032
- if (code !== 0) {
4033
- reject(new Error(`Gemini CLI exited with code ${code}: ${stderr}`));
4034
- } else {
4035
- resolve(stdout);
4036
- }
4037
- });
4038
- proc.on("error", (err) => {
4039
- stopSpinner(false);
4040
- reject(new Error(`Failed to run Gemini CLI: ${err.message}`));
4041
- });
4042
- });
4043
- }
4044
- /**
4045
- * Execute multiple prompts in parallel with shared progress display
4046
- */
4047
- async executeParallel(params) {
4048
- const total = params.length;
4049
- let completed = 0;
4050
- const results = /* @__PURE__ */ new Map();
4051
- const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
4052
- let frameIndex = 0;
4053
- const spinner = setInterval(() => {
4054
- const progress = `${completed}/${total}`;
4055
- process.stdout.write(`\r${frames[frameIndex++ % frames.length]} Generating sections in parallel... [${progress}]`);
4056
- }, 100);
4057
- const stopSpinner = (success) => {
4058
- clearInterval(spinner);
4059
- const status = success ? "\u2713" : "\u2717";
4060
- const message = success ? `All ${total} sections generated` : `Generation completed with errors`;
4061
- process.stdout.write(`\r${status} ${message}
4062
- `);
4063
- };
4064
- await Promise.all(
4065
- params.map(async (p) => {
4066
- const id = p.id || p.outputPath;
4067
- try {
4068
- if (p.outputPath) {
4069
- const outputDir = dirname6(p.outputPath);
4070
- if (!existsSync16(outputDir)) {
4071
- mkdirSync7(outputDir, { recursive: true });
4072
- }
4073
- }
4074
- await this.callGemini(p.prompt, false);
4075
- let result = "";
4076
- if (p.outputPath && existsSync16(p.outputPath)) {
4077
- result = readFileSync15(p.outputPath, "utf-8");
4078
- }
4079
- completed++;
4080
- results.set(id, { id, success: true, result });
4081
- } catch (error) {
4082
- completed++;
4083
- results.set(id, {
4084
- id,
4085
- success: false,
4086
- error: error instanceof Error ? error.message : String(error)
4087
- });
4088
- }
4089
- })
4090
- );
4091
- stopSpinner(Array.from(results.values()).every((r) => r.success));
4092
- return params.map((p) => results.get(p.id || p.outputPath));
4093
- }
4094
- };
4095
- function createGeminiAdapter(geminiCommand = "gemini") {
4096
- return {
4097
- ai: new GeminiAIAdapter(geminiCommand),
4098
- io: new CLIIOAdapter(),
4099
- system: new CLISystemAdapter()
4100
- };
4101
- }
4102
-
4103
- // src/commands/start.ts
4104
- function getGitHubInfo(cwd) {
4105
- const config = getGitHubConfig(cwd);
4106
- if (config?.owner && config?.repo) {
4107
- return { owner: config.owner, repo: config.repo };
4108
- }
4109
- try {
4110
- const remoteUrl = execSync2("git remote get-url origin", { encoding: "utf-8", cwd }).trim();
4111
- const sshMatch = remoteUrl.match(/git@github\.com:([^/]+)\/(.+?)(?:\.git)?$/);
4112
- if (sshMatch) {
4113
- return { owner: sshMatch[1], repo: sshMatch[2] };
4114
- }
4115
- const httpsMatch = remoteUrl.match(/https:\/\/github\.com\/([^/]+)\/(.+?)(?:\.git)?$/);
4116
- if (httpsMatch) {
4117
- return { owner: httpsMatch[1], repo: httpsMatch[2] };
4118
- }
4119
- } catch {
4120
- }
4121
- return null;
4122
- }
4123
- function getCurrentBranch() {
4124
- try {
4125
- return execSync2("git branch --show-current", { encoding: "utf-8" }).trim() || void 0;
4126
- } catch {
4127
- return void 0;
4128
- }
4129
- }
4130
- async function startCommand(query, options) {
4131
- const cwd = process.cwd();
4132
- if (!spetsExists(cwd)) {
4133
- console.error("\u274C Spets not initialized in this directory.\n\n Run: spets init\n Or: spets init -i (interactive setup)\n");
4134
- process.exit(1);
4135
- }
4136
- const config = loadConfig(cwd);
4137
- let adapter;
4138
- let isGitHubMode = false;
4139
- if (options.github || options.issue !== void 0 || options.pr !== void 0) {
4140
- const githubInfo = getGitHubInfo(cwd);
4141
- if (!githubInfo) {
4142
- console.error("Could not determine GitHub owner/repo. Set in .spets/config.yml or add git remote.");
4143
- process.exit(1);
4144
- }
4145
- let issueNumber;
4146
- let prNumber;
4147
- if (options.issue !== void 0) {
4148
- if (typeof options.issue === "string") {
4149
- issueNumber = parseInt(options.issue, 10);
4150
- }
4151
- }
4152
- if (options.pr !== void 0) {
4153
- if (typeof options.pr === "string") {
4154
- prNumber = parseInt(options.pr, 10);
4155
- }
4156
- }
4157
- const githubConfig = {
4158
- owner: githubInfo.owner,
4159
- repo: githubInfo.repo,
4160
- issueNumber,
4161
- prNumber,
4162
- branch: getCurrentBranch()
4163
- };
4164
- adapter = createGitHubAdapter(githubConfig);
4165
- isGitHubMode = true;
4166
- console.log(`Platform: github (${githubInfo.owner}/${githubInfo.repo})`);
4167
- if (issueNumber) console.log(`Issue: #${issueNumber}`);
4168
- if (prNumber) console.log(`PR: #${prNumber}`);
4169
- } else {
4170
- const agentType = options.agent || config.agent || "claude";
4171
- if (agentType === "codex") {
4172
- adapter = createCodexAdapter();
4173
- console.log("Platform: cli (codex)");
4174
- } else if (agentType === "gemini") {
4175
- adapter = createGeminiAdapter();
4176
- console.log("Platform: cli (gemini)");
4177
- } else {
4178
- adapter = createCLIAdapter();
4179
- console.log("Platform: cli (claude)");
4180
- }
4181
- }
4182
- const orchestrator = new Orchestrator(cwd);
4183
- const stepExecutor = new StepExecutor(adapter, config, cwd);
4184
- let response = orchestrator.cmdInit(query);
4185
- let taskId;
4186
- console.log(`
4187
- \u{1F680} Starting workflow: ${query}
4188
- `);
4189
- if (response.type === "phase" && response.phase === "explore") {
4190
- taskId = response.taskId;
4191
- if (isGitHubMode && "io" in adapter && "setTaskId" in adapter.io) {
4192
- adapter.io.setTaskId(taskId);
4193
- }
4194
- }
4195
- try {
4196
- while (response.type !== "complete" && response.type !== "error") {
4197
- if (response.type === "phase") {
4198
- const phaseResponse = response;
4199
- taskId = phaseResponse.taskId;
4200
- if (phaseResponse.phase === "explore") {
4201
- const exploreResp = phaseResponse;
4202
- console.log(`
4203
- --- Step ${exploreResp.stepIndex}/${exploreResp.totalSteps}: ${exploreResp.step} ---`);
4204
- console.log("Phase 1/5: Exploring codebase...\n");
4205
- const stepContext = {
4206
- taskId: exploreResp.taskId,
4207
- description: exploreResp.description,
4208
- stepIndex: exploreResp.stepIndex,
4209
- totalSteps: exploreResp.totalSteps,
4210
- previousOutput: exploreResp.context.previousOutput
4211
- };
4212
- const result = await stepExecutor.executeExplorePhase(exploreResp.step, stepContext);
4213
- response = orchestrator.cmdExploreDone(taskId, result.exploreOutput);
4214
- } else if (phaseResponse.phase === "clarify") {
4215
- const clarifyResp = phaseResponse;
4216
- console.log("Phase 2/5: Generating questions...\n");
4217
- const stepContext = {
4218
- taskId: clarifyResp.taskId,
4219
- description: clarifyResp.description,
4220
- stepIndex: 0,
4221
- totalSteps: 0,
4222
- gatheredContext: clarifyResp.gatheredContext,
4223
- previousDecisions: clarifyResp.previousDecisions
4224
- };
4225
- const result = await stepExecutor.executeClarifyPhase(clarifyResp.step, stepContext);
4226
- const clarifyOutput = result.clarifyOutput || { ready: true, decisions: [] };
4227
- response = orchestrator.cmdClarifyDone(taskId, clarifyOutput);
4228
- } else if (phaseResponse.phase === "execute") {
4229
- const executeResp = phaseResponse;
4230
- const attemptInfo = executeResp.context.verifyFeedback ? " (auto-fix)" : "";
4231
- console.log(`Phase 3/5: Executing${attemptInfo}...
4232
- `);
4233
- const stepContext = {
4234
- taskId: executeResp.taskId,
4235
- description: executeResp.description,
4236
- stepIndex: executeResp.stepIndex,
4237
- totalSteps: executeResp.totalSteps,
4238
- exploreOutput: executeResp.exploreOutput,
4239
- answers: executeResp.answers,
4240
- revisionFeedback: executeResp.context.revisionFeedback,
4241
- verifyFeedback: executeResp.context.verifyFeedback
4242
- };
4243
- await stepExecutor.executeExecutePhase(executeResp.step, stepContext);
4244
- response = orchestrator.cmdExecuteDone(taskId);
4245
- } else if (phaseResponse.phase === "verify") {
4246
- const verifyResp = phaseResponse;
4247
- console.log(`Phase 4/5: Verifying document (attempt ${verifyResp.verifyAttempts}/${verifyResp.maxAttempts})...
4248
- `);
4249
- const stepContext = {
4250
- taskId: verifyResp.taskId,
4251
- description: verifyResp.description,
4252
- stepIndex: verifyResp.stepIndex,
4253
- totalSteps: verifyResp.totalSteps,
4254
- verifyAttempts: verifyResp.verifyAttempts
4255
- };
4256
- const result = await stepExecutor.executeVerifyPhase(verifyResp.step, stepContext);
4257
- response = orchestrator.cmdVerifyDone(taskId, result.verifyOutput);
4258
- } else if (phaseResponse.phase === "knowledge") {
4259
- const knowledgeResp = phaseResponse;
4260
- console.log("\n\u{1F4DA} Extracting knowledge...\n");
4261
- const result = await stepExecutor.executeKnowledgePhase(knowledgeResp);
4262
- response = orchestrator.cmdKnowledgeExtractDone(taskId, result.knowledgeOutput);
4263
- }
4264
- } else if (response.type === "checkpoint") {
4265
- taskId = response.taskId;
4266
- if (response.checkpoint === "clarify") {
4267
- const clarifyCheckpoint = response;
4268
- console.log("Phase 2/5: Decisions needed\n");
4269
- const answers = await adapter.io.askDecisions(clarifyCheckpoint.decisions);
4270
- if (answers.length === 0) {
4271
- console.log(`
4272
- \u23F8\uFE0F Workflow paused. Task ID: ${taskId}`);
4273
- console.log(" Resume with: spets resume --task", taskId);
4274
- return;
4275
- }
4276
- response = orchestrator.cmdClarified(taskId, answers);
4277
- } else if (response.checkpoint === "approve") {
4278
- const approveCheckpoint = response;
4279
- console.log("Phase 5/5: Review document\n");
4280
- const approval = await adapter.io.approve(
4281
- approveCheckpoint.specPath,
4282
- approveCheckpoint.step,
4283
- approveCheckpoint.stepIndex,
4284
- approveCheckpoint.totalSteps
4285
- );
4286
- if (approval.pending) {
4287
- console.log(`
4288
- \u23F8\uFE0F Workflow paused. Task ID: ${taskId}`);
4289
- console.log(" Resume with: spets resume --task", taskId);
4290
- return;
4291
- }
4292
- if (approval.action === "approve") {
4293
- response = orchestrator.cmdApprove(taskId);
4294
- } else if (approval.action === "revise") {
4295
- response = orchestrator.cmdRevise(taskId, approval.feedback || "");
4296
- } else if (approval.action === "reject") {
4297
- response = orchestrator.cmdReject(taskId);
4298
- } else {
4299
- response = orchestrator.cmdStop(taskId);
4300
- }
4301
- } else if (response.checkpoint === "knowledge") {
4302
- const knowledgeCheckpoint = response;
4303
- console.log("\n\u{1F4DA} Knowledge Extraction\n");
4304
- const result = await adapter.io.askKnowledge(knowledgeCheckpoint);
4305
- if (result.pending) {
4306
- console.log(`
4307
- \u23F8\uFE0F Workflow paused. Task ID: ${taskId}`);
4308
- console.log(" Resume with: spets resume --task", taskId);
4309
- return;
4310
- }
4311
- if (result.skipped) {
4312
- response = orchestrator.cmdKnowledgeSkip(taskId);
4313
- } else {
4314
- response = orchestrator.cmdKnowledgeSave(taskId, result.entries);
4315
- }
4316
- }
4317
- } else {
4318
- throw new Error(`Unknown response type: ${response.type}`);
4319
- }
4320
- }
4321
- if (response.type === "complete") {
4322
- const completeResponse = response;
4323
- if (completeResponse.status === "completed") {
4324
- console.log("\n\u2705 Workflow completed!");
4325
- console.log("\nOutputs:");
4326
- for (const output of completeResponse.outputs) {
4327
- console.log(` ${output}`);
4328
- }
4329
- } else if (completeResponse.status === "stopped") {
4330
- console.log(`
4331
- \u23F8\uFE0F ${completeResponse.message}`);
4332
- } else if (completeResponse.status === "rejected") {
4333
- console.log("\n\u274C Workflow rejected");
4334
- }
4335
- } else if (response.type === "error") {
4336
- console.error(`
4337
- \u274C Error: ${response.error}`);
4338
- process.exit(1);
4339
- }
4340
- console.log("");
4341
- } catch (error) {
4342
- console.error("\n\u274C Workflow failed:", error instanceof Error ? error.message : error);
4343
- if (taskId) {
4344
- console.log(`
4345
- Resume with: spets resume --task ${taskId}`);
4346
- }
4347
- process.exit(1);
4348
- }
4349
- }
4350
-
4351
- // src/commands/resume.ts
4352
- import { select as select3 } from "@inquirer/prompts";
4353
- import { existsSync as existsSync17, readdirSync as readdirSync3, readFileSync as readFileSync16 } from "fs";
4354
- import { join as join11 } from "path";
4355
- import { spawnSync as spawnSync3 } from "child_process";
4356
- async function createPR(cwd, taskId, outputs) {
4357
- const outputContents = [];
4358
- for (const outputPath of outputs) {
4359
- const fullPath = join11(cwd, outputPath);
4360
- if (existsSync17(fullPath)) {
4361
- const content = readFileSync16(fullPath, "utf-8");
4362
- outputContents.push(`## ${outputPath}
4363
-
4364
- ${content}`);
4365
- }
4366
- }
4367
- const title = `Spets: ${taskId}`;
4368
- const body = `# Workflow Outputs
4369
-
4370
- ${outputContents.join("\n\n---\n\n")}`;
4371
- const result = spawnSync3("gh", ["pr", "create", "--title", title, "--body", body], {
4372
- cwd,
4373
- encoding: "utf-8",
4374
- stdio: ["inherit", "pipe", "pipe"]
4375
- });
4376
- if (result.status !== 0) {
4377
- throw new Error(result.stderr || "Failed to create PR");
4378
- }
4379
- console.log(result.stdout.trim());
4380
- }
4381
- function listResumableTasks(cwd) {
4382
- const outputsDir = getOutputsDir(cwd);
4383
- if (!existsSync17(outputsDir)) {
4384
- return [];
4385
- }
4386
- const tasks = [];
4387
- const taskDirs = readdirSync3(outputsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
4388
- const orchestrator = new Orchestrator(cwd);
4389
- for (const taskId of taskDirs) {
4390
- const stateFile = join11(outputsDir, taskId, ".state.json");
4391
- if (!existsSync17(stateFile)) continue;
4392
- try {
4393
- const response = orchestrator.cmdStatus(taskId);
4394
- if (response.type === "error") continue;
4395
- if (response.type === "phase" || response.type === "checkpoint") {
4396
- const phaseResponse = response;
4397
- tasks.push({
4398
- taskId,
4399
- status: response.type === "checkpoint" ? "awaiting_input" : "in_progress",
4400
- currentStep: "step" in response ? response.step : response.step,
4401
- description: "description" in response ? response.description : taskId
4402
- });
4403
- } else if (response.type === "complete") {
4404
- const completeResponse = response;
4405
- if (completeResponse.status === "stopped") {
4406
- tasks.push({
4407
- taskId,
4408
- status: "stopped",
4409
- currentStep: "paused",
4410
- description: taskId
4411
- });
4412
- }
4413
- }
4414
- } catch {
4415
- }
4416
- }
4417
- return tasks;
4418
- }
4419
- async function resumeCommand(options) {
4420
- const cwd = process.cwd();
4421
- if (!spetsExists(cwd)) {
4422
- console.error('Spets not initialized. Run "spets init" first.');
4423
- process.exit(1);
4424
- }
4425
- const config = loadConfig(cwd);
4426
- let taskId = options.task;
4427
- if (!taskId) {
4428
- const tasks = listResumableTasks(cwd);
4429
- if (tasks.length === 0) {
4430
- console.log("No tasks to resume.");
4431
- console.log('Start a new task with: spets start "your task description"');
4432
- return;
4433
- }
4434
- if (tasks.length === 1) {
4435
- taskId = tasks[0].taskId;
4436
- } else {
4437
- taskId = await select3({
4438
- message: "Select task to resume:",
4439
- choices: tasks.map((t) => ({
4440
- value: t.taskId,
4441
- name: `${t.taskId} [${t.status}] - ${t.currentStep} - ${t.description.substring(0, 40)}${t.description.length > 40 ? "..." : ""}`
4442
- }))
4443
- });
4444
- }
4445
- }
4446
- const agentType = options.agent || config.agent || "claude";
4447
- let adapter;
4448
- if (agentType === "codex") {
4449
- adapter = createCodexAdapter();
4450
- } else if (agentType === "gemini") {
4451
- adapter = createGeminiAdapter();
4452
- } else {
4453
- adapter = createCLIAdapter();
4454
- }
4455
- const orchestrator = new Orchestrator(cwd);
4456
- const stepExecutor = new StepExecutor(adapter, config, cwd);
4457
- let response = orchestrator.cmdStatus(taskId);
4458
- if (response.type === "error") {
4459
- console.error(`Task '${taskId}' not found or invalid.`);
4460
- process.exit(1);
4461
- }
4462
- if (response.type === "complete") {
4463
- const completeResponse = response;
4464
- if (completeResponse.status === "completed") {
4465
- console.log("Task already completed.");
4466
- return;
4467
- }
4468
- if (completeResponse.status === "rejected") {
4469
- console.log("Task was rejected. Cannot resume.");
4470
- return;
4471
- }
4472
- response = orchestrator.cmdResume(taskId);
4473
- }
4474
- console.log(`
4475
- \u{1F504} Resuming task: ${taskId}
4476
- `);
4477
- if (options.approve) {
4478
- response = orchestrator.cmdApprove(taskId);
4479
- console.log("Approved current step.");
4480
- }
4481
- if (options.revise) {
4482
- response = orchestrator.cmdRevise(taskId, options.revise);
4483
- console.log(`Revising with feedback: ${options.revise}`);
4484
- }
4485
- try {
4486
- while (response.type !== "complete" && response.type !== "error") {
4487
- if (response.type === "phase") {
4488
- const phaseResponse = response;
4489
- taskId = phaseResponse.taskId;
4490
- if (phaseResponse.phase === "explore") {
4491
- const exploreResp = phaseResponse;
4492
- console.log(`
4493
- --- Step ${exploreResp.stepIndex}/${exploreResp.totalSteps}: ${exploreResp.step} ---`);
4494
- console.log("Phase 1/5: Exploring codebase...\n");
4495
- const stepContext = {
4496
- taskId: exploreResp.taskId,
4497
- description: exploreResp.description,
4498
- stepIndex: exploreResp.stepIndex,
4499
- totalSteps: exploreResp.totalSteps,
4500
- previousOutput: exploreResp.context.previousOutput
4501
- };
4502
- const result = await stepExecutor.executeExplorePhase(exploreResp.step, stepContext);
4503
- response = orchestrator.cmdExploreDone(taskId, result.exploreOutput);
4504
- } else if (phaseResponse.phase === "clarify") {
4505
- const clarifyResp = phaseResponse;
4506
- console.log("Phase 2/5: Generating questions...\n");
4507
- const stepContext = {
4508
- taskId: clarifyResp.taskId,
4509
- description: clarifyResp.description,
4510
- stepIndex: 0,
4511
- totalSteps: 0,
4512
- gatheredContext: clarifyResp.gatheredContext,
4513
- previousDecisions: clarifyResp.previousDecisions
4514
- };
4515
- const result = await stepExecutor.executeClarifyPhase(clarifyResp.step, stepContext);
4516
- const clarifyOutput = result.clarifyOutput || { ready: true, decisions: [] };
4517
- response = orchestrator.cmdClarifyDone(taskId, clarifyOutput);
4518
- } else if (phaseResponse.phase === "execute") {
4519
- const executeResp = phaseResponse;
4520
- const attemptInfo = executeResp.context.verifyFeedback ? " (auto-fix)" : "";
4521
- console.log(`Phase 3/5: Executing${attemptInfo}...
4522
- `);
4523
- const stepContext = {
4524
- taskId: executeResp.taskId,
4525
- description: executeResp.description,
4526
- stepIndex: executeResp.stepIndex,
4527
- totalSteps: executeResp.totalSteps,
4528
- exploreOutput: executeResp.exploreOutput,
4529
- answers: executeResp.answers,
4530
- revisionFeedback: executeResp.context.revisionFeedback,
4531
- verifyFeedback: executeResp.context.verifyFeedback
4532
- };
4533
- await stepExecutor.executeExecutePhase(executeResp.step, stepContext);
4534
- response = orchestrator.cmdExecuteDone(taskId);
4535
- } else if (phaseResponse.phase === "verify") {
4536
- const verifyResp = phaseResponse;
4537
- console.log(`Phase 4/5: Verifying document (attempt ${verifyResp.verifyAttempts}/${verifyResp.maxAttempts})...
4538
- `);
4539
- const stepContext = {
4540
- taskId: verifyResp.taskId,
4541
- description: verifyResp.description,
4542
- stepIndex: verifyResp.stepIndex,
4543
- totalSteps: verifyResp.totalSteps,
4544
- verifyAttempts: verifyResp.verifyAttempts
4545
- };
4546
- const result = await stepExecutor.executeVerifyPhase(verifyResp.step, stepContext);
4547
- response = orchestrator.cmdVerifyDone(taskId, result.verifyOutput);
4548
- }
4549
- } else if (response.type === "checkpoint") {
4550
- taskId = response.taskId;
4551
- if (response.checkpoint === "clarify") {
4552
- const clarifyCheckpoint = response;
4553
- console.log("Phase 2/5: Decisions needed\n");
4554
- const answers = await adapter.io.askDecisions(clarifyCheckpoint.decisions);
4555
- if (answers.length === 0) {
4556
- adapter.io.notify("No answers provided", "warning");
4557
- response = orchestrator.cmdStatus(taskId);
4558
- } else {
4559
- response = orchestrator.cmdClarified(taskId, answers);
4560
- }
4561
- } else if (response.checkpoint === "approve") {
4562
- const approveCheckpoint = response;
4563
- console.log("Phase 5/5: Review document\n");
4564
- const approval = await adapter.io.approve(
4565
- approveCheckpoint.specPath,
4566
- approveCheckpoint.step,
4567
- approveCheckpoint.stepIndex,
4568
- approveCheckpoint.totalSteps
4569
- );
4570
- if (approval.action === "approve") {
4571
- response = orchestrator.cmdApprove(taskId);
4572
- } else if (approval.action === "revise") {
4573
- response = orchestrator.cmdRevise(taskId, approval.feedback || "");
4574
- } else if (approval.action === "reject") {
4575
- response = orchestrator.cmdReject(taskId);
4576
- } else {
4577
- response = orchestrator.cmdStop(taskId);
4578
- }
4579
- } else if (response.checkpoint === "knowledge") {
4580
- const knowledgeCheckpoint = response;
4581
- console.log("\n\u{1F4DA} Knowledge Extraction\n");
4582
- const result = await adapter.io.askKnowledge(knowledgeCheckpoint);
4583
- if (result.pending) {
4584
- console.log(`
4585
- \u23F8\uFE0F Workflow paused. Task ID: ${taskId}`);
4586
- console.log(" Resume with: spets resume --task", taskId);
4587
- return;
4588
- }
4589
- if (result.skipped) {
4590
- response = orchestrator.cmdKnowledgeSkip(taskId);
4591
- } else {
4592
- response = orchestrator.cmdKnowledgeSave(taskId, result.entries);
4593
- }
4594
- }
4595
- } else {
4596
- throw new Error(`Unknown response type: ${response.type}`);
4597
- }
4598
- }
4599
- if (response.type === "complete") {
4600
- const completeResponse = response;
4601
- if (completeResponse.status === "completed") {
4602
- console.log("\n\u2705 Workflow completed!");
4603
- console.log("\nOutputs:");
4604
- for (const output of completeResponse.outputs) {
4605
- console.log(` ${output}`);
4606
- }
4607
- if (options.pr) {
4608
- console.log("\n\u{1F500} Creating Pull Request...");
4609
- try {
4610
- await createPR(cwd, taskId, completeResponse.outputs);
4611
- } catch (err) {
4612
- console.error("Failed to create PR:", err.message);
4613
- }
4614
- }
4615
- } else if (completeResponse.status === "stopped") {
4616
- console.log(`
4617
- \u23F8\uFE0F ${completeResponse.message}`);
4618
- } else if (completeResponse.status === "rejected") {
4619
- console.log("\n\u274C Workflow rejected");
4620
- }
4621
- } else if (response.type === "error") {
4622
- console.error(`
4623
- \u274C Error: ${response.error}`);
4624
- process.exit(1);
4625
- }
4626
- console.log("");
4627
- } catch (error) {
4628
- console.error("\n\u274C Workflow failed:", error instanceof Error ? error.message : error);
4629
- if (taskId) {
4630
- console.log(`
4631
- Resume with: spets resume --task ${taskId}`);
4632
- }
4633
- process.exit(1);
4634
- }
4635
- }
4636
-
4637
- // src/hooks/runner.ts
4638
- import { spawn as spawn5 } from "child_process";
4639
- import { existsSync as existsSync18 } from "fs";
4640
- import { join as join12, isAbsolute } from "path";
4641
- async function runHook(hookPath, context, cwd = process.cwd()) {
4642
- const resolvedPath = isAbsolute(hookPath) ? hookPath : join12(getSpetsDir(cwd), hookPath);
4643
- if (!existsSync18(resolvedPath)) {
4644
- console.warn(`Hook not found: ${resolvedPath}`);
4645
- return;
4646
- }
4647
- const env = {
4648
- ...process.env,
4649
- SPETS_TASK_ID: context.taskId,
4650
- SPETS_STEP_NAME: context.stepName,
4651
- SPETS_STEP_INDEX: String(context.stepIndex),
4652
- SPETS_OUTPUT_PATH: context.outputPath,
4653
- SPETS_BRANCH: context.branch || "",
4654
- SPETS_CWD: cwd
4655
- };
4656
- return new Promise((resolve, reject) => {
4657
- const proc = spawn5(resolvedPath, [], {
4658
- cwd,
4659
- env,
4660
- stdio: "inherit",
4661
- shell: true
4662
- });
4663
- proc.on("close", (code) => {
4664
- if (code !== 0) {
4665
- console.warn(`Hook exited with code ${code}: ${resolvedPath}`);
4666
- }
4667
- resolve();
4668
- });
4669
- proc.on("error", (err) => {
4670
- console.warn(`Failed to run hook: ${err.message}`);
4671
- resolve();
4672
- });
4673
- });
4674
- }
4675
-
4676
- // src/core/git.ts
4677
- import { execSync as execSync3 } from "child_process";
4678
- function getCurrentBranch2(cwd = process.cwd()) {
4679
- try {
4680
- return execSync3("git branch --show-current", { encoding: "utf-8", cwd }).trim();
4681
- } catch {
4682
- return void 0;
4683
- }
4684
- }
4685
-
4686
- // src/commands/github/parse.ts
4687
- function parseGitHubCommand(comment) {
4688
- const trimmed = comment.trim();
4689
- if (trimmed === "/approve") {
4690
- return { command: "approve" };
4691
- }
4692
- if (trimmed === "/approve --pr") {
4693
- return { command: "approve", createPR: true };
4694
- }
4695
- if (trimmed === "/approve --issue") {
4696
- return { command: "approve", createIssue: true };
4697
- }
4698
- if (trimmed === "/reject") {
4699
- return { command: "reject" };
4700
- }
4701
- const reviseMatch = trimmed.match(/^\/revise\s+(.+)$/s);
4702
- if (reviseMatch) {
4703
- return { command: "revise", feedback: reviseMatch[1].trim() };
4704
- }
4705
- const answerMatch = trimmed.match(/^\/answer\s*\n([\s\S]+)$/m);
4706
- if (answerMatch) {
4707
- const answersText = answerMatch[1];
4708
- const answers = {};
4709
- const answerLines = answersText.split("\n");
4710
- for (const line of answerLines) {
4711
- const match = line.match(/^(Q\d+):\s*(.+)$/i);
4712
- if (match) {
4713
- answers[match[1].toLowerCase()] = match[2].trim();
4714
- }
4715
- }
4716
- return { command: "answer", answers };
4717
- }
4718
- const decideMatch = trimmed.match(/^\/decide\s*\n([\s\S]+)$/m);
4719
- if (decideMatch) {
4720
- const decisionsText = decideMatch[1];
4721
- const decisions = {};
4722
- const decisionLines = decisionsText.split("\n");
4723
- for (const line of decisionLines) {
4724
- const match = line.match(/^([QD]\d+)[:.]\s*(.+)$/i);
4725
- if (match) {
4726
- const key = match[1].toLowerCase().replace("d", "q");
4727
- decisions[key] = match[2].trim();
4728
- }
4729
- }
4730
- return { command: "decide", decisions };
4731
- }
4732
- return { command: null };
4733
- }
4734
-
4735
- // src/commands/github/git-info.ts
4736
- import { execSync as execSync4 } from "child_process";
4737
- import { existsSync as existsSync19, readdirSync as readdirSync4 } from "fs";
4738
- function getGitHubInfo2(cwd) {
4739
- const config = getGitHubConfig(cwd);
4740
- if (config?.owner && config?.repo) {
4741
- return { owner: config.owner, repo: config.repo };
4742
- }
4743
- try {
4744
- const remoteUrl = execSync4("git remote get-url origin", { encoding: "utf-8", cwd }).trim();
4745
- const sshMatch = remoteUrl.match(/git@github\.com:([^/]+)\/(.+?)(?:\.git)?$/);
4746
- if (sshMatch) {
4747
- return { owner: sshMatch[1], repo: sshMatch[2] };
4748
- }
4749
- const httpsMatch = remoteUrl.match(/https:\/\/github\.com\/([^/]+)\/(.+?)(?:\.git)?$/);
4750
- if (httpsMatch) {
4751
- return { owner: httpsMatch[1], repo: httpsMatch[2] };
4752
- }
4753
- } catch {
4754
- }
4755
- return null;
4756
- }
4757
- function findTaskId(cwd, owner, repo, issueOrPr) {
4758
- if (issueOrPr) {
4759
- try {
4760
- const commentsJson = execSync4(
4761
- `gh api repos/${owner}/${repo}/issues/${issueOrPr}/comments --jq '.[].body'`,
4762
- { encoding: "utf-8" }
4763
- );
4764
- const taskMatch = commentsJson.match(/Task ID: `([^`]+)`/);
4765
- if (taskMatch) {
4766
- return taskMatch[1];
4767
- }
4768
- } catch {
4769
- }
4770
- }
4771
- const outputsDir = getOutputsDir(cwd);
4772
- if (existsSync19(outputsDir)) {
4773
- const taskDirs = readdirSync4(outputsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort().reverse();
4774
- if (taskDirs.length > 0) {
4775
- return taskDirs[0];
4776
- }
4777
- }
4778
- return void 0;
4779
- }
4780
-
4781
- // src/commands/github/pull-request.ts
4782
- import { spawnSync as spawnSync4 } from "child_process";
4783
- import { existsSync as existsSync20, readdirSync as readdirSync5, readFileSync as readFileSync17 } from "fs";
4784
- import { join as join13 } from "path";
4785
-
4786
- // src/commands/github/decisions.ts
4787
- import { spawn as spawn6 } from "child_process";
4788
- async function interpretDecisionResponses(decisions, userResponses, config) {
4789
- const results = [];
4790
- for (let i = 0; i < decisions.length; i++) {
4791
- const decision = decisions[i];
4792
- const key = `q${i + 1}`;
4793
- const userResponse = userResponses[key] || "";
4794
- if (!userResponse) {
4795
- results.push({
4796
- decisionId: decision.id,
4797
- selectedOptionId: "ai"
4798
- });
4799
- continue;
4800
- }
4801
- const optionsText = decision.options.filter((o) => o.id !== "ai").map((o, idx) => `${idx + 1}. ${o.label}: ${o.description || ""}`).join("\n");
4802
- const aiOption = decision.options.find((o) => o.id === "ai");
4803
- const prompt = `You are interpreting a user's decision response.
4804
-
4805
- Decision: ${decision.decision}
4806
- Why: ${decision.why}
4807
-
4808
- Options:
4809
- ${optionsText}
4810
- ${aiOption ? `AI Recommended: ${aiOption.recommendation || "AI choice"} (${aiOption.reason || ""})` : ""}
4811
-
4812
- User's response: "${userResponse}"
4813
-
4814
- Interpret the user's intent and respond with ONLY a JSON object:
4815
- {
4816
- "action": "select" | "elaborate" | "alternative",
4817
- "optionId": "<option id if selecting, or which option to elaborate on>",
4818
- "customInput": "<any custom input if alternative>"
4819
- }
4820
-
4821
- Examples:
4822
- - "I like the first option" \u2192 {"action": "select", "optionId": "opt1"}
4823
- - "go with recommended" \u2192 {"action": "select", "optionId": "ai"}
4824
- - "elaborate on option 2" \u2192 {"action": "elaborate", "optionId": "opt2"}
4825
- - "something else - use gRPC" \u2192 {"action": "alternative", "customInput": "use gRPC"}
4826
- - "the second one" \u2192 {"action": "select", "optionId": "opt2"}
4827
-
4828
- Output ONLY the JSON, no explanation.`;
4829
- try {
4830
- const response = await callClaude(prompt);
4831
- const jsonMatch = response.match(/\{[\s\S]*\}/);
4832
- if (jsonMatch) {
4833
- const parsed = JSON.parse(jsonMatch[0]);
4834
- if (parsed.action === "elaborate") {
4835
- results.push({
4836
- decisionId: decision.id,
4837
- selectedOptionId: "",
4838
- followUp: "elaborate",
4839
- followUpTarget: parsed.optionId
4840
- });
4841
- } else if (parsed.action === "alternative") {
4842
- results.push({
4843
- decisionId: decision.id,
4844
- selectedOptionId: "_custom_",
4845
- customInput: parsed.customInput || userResponse
4846
- });
4847
- } else {
4848
- const optionId = parsed.optionId || "ai";
4849
- let finalOptionId = optionId;
4850
- if (/^opt\d+$/.test(optionId)) {
4851
- finalOptionId = optionId;
4852
- } else if (/^\d+$/.test(optionId)) {
4853
- const idx = parseInt(optionId, 10) - 1;
4854
- const nonAiOptions = decision.options.filter((o) => o.id !== "ai");
4855
- if (idx >= 0 && idx < nonAiOptions.length) {
4856
- finalOptionId = nonAiOptions[idx].id;
4857
- }
4858
- }
4859
- results.push({
4860
- decisionId: decision.id,
4861
- selectedOptionId: finalOptionId
4862
- });
4863
- }
4864
- } else {
4865
- results.push({
4866
- decisionId: decision.id,
4867
- selectedOptionId: "_custom_",
4868
- customInput: userResponse
4869
- });
4870
- }
4871
- } catch (error) {
4872
- console.error(`Failed to interpret response for ${decision.id}:`, error);
4873
- results.push({
4874
- decisionId: decision.id,
4875
- selectedOptionId: "_custom_",
4876
- customInput: userResponse
4877
- });
4878
- }
4879
- }
4880
- return results;
4881
- }
4882
- async function postFollowUpComment(config, decision, followUpType, taskId, targetOptionId) {
4883
- const { owner, repo, prNumber, issueNumber } = config;
4884
- let body;
4885
- if (followUpType === "elaborate") {
4886
- const targetOption = decision.options.find((o) => o.id === targetOptionId);
4887
- body = `## \u{1F4DD} Spets: More Details
4888
-
4889
- > Task ID: \`${taskId}\`
4890
- > Question: ${decision.decision}
4891
-
4892
- ### ${targetOption?.label || "Option"}
4893
-
4894
- ${targetOption?.description || "No additional description available."}
4895
-
4896
- ${targetOption?.tradeoffs ? `**Tradeoffs:** ${targetOption.tradeoffs}` : ""}
4897
-
4898
- ---
4899
-
4900
- Please respond with your decision:
4901
- \`\`\`
4902
- /decide
4903
- Q1: <your choice after seeing the details>
4904
- \`\`\``;
4905
- } else {
4906
- body = `## \u{1F4A1} Spets: Custom Input Needed
4907
-
4908
- > Task ID: \`${taskId}\`
4909
- > Question: ${decision.decision}
4910
-
4911
- You indicated you'd like a different approach. Please describe what you want:
4912
-
4913
- \`\`\`
4914
- /decide
4915
- Q1: <describe your preferred approach>
4916
- \`\`\``;
4917
- }
4918
- const args = prNumber ? ["pr", "comment", String(prNumber), "--body", body, "-R", `${owner}/${repo}`] : ["issue", "comment", String(issueNumber), "--body", body, "-R", `${owner}/${repo}`];
4919
- return new Promise((resolve, reject) => {
4920
- const proc = spawn6("gh", args, { stdio: ["pipe", "pipe", "pipe"] });
4921
- let stderr = "";
4922
- proc.stderr.on("data", (data) => {
4923
- stderr += data.toString();
4924
- });
4925
- proc.on("close", (code) => {
4926
- if (code !== 0) reject(new Error(`Failed to post follow-up: ${stderr}`));
4927
- else resolve();
4928
- });
4929
- proc.on("error", reject);
4930
- });
4931
- }
4932
- async function callClaude(prompt) {
4933
- return new Promise((resolve, reject) => {
4934
- const proc = spawn6("claude", ["--print"], { stdio: ["pipe", "pipe", "pipe"] });
4935
- let stdout = "";
4936
- let stderr = "";
4937
- proc.stdout.on("data", (data) => {
4938
- stdout += data.toString();
4939
- });
4940
- proc.stderr.on("data", (data) => {
4941
- stderr += data.toString();
4942
- });
4943
- proc.stdin.write(prompt);
4944
- proc.stdin.end();
4945
- proc.on("close", (code) => {
4946
- if (code !== 0) reject(new Error(`Claude failed: ${stderr}`));
4947
- else resolve(stdout);
4948
- });
4949
- proc.on("error", reject);
4950
- });
4951
- }
4952
-
4953
- // src/commands/github/pull-request.ts
4954
- async function createPullRequestWithAI(config, taskId, userQuery, cwd) {
4955
- const { owner, repo, issueNumber } = config;
4956
- const outputsDir = join13(getOutputsDir(cwd), taskId);
4957
- const outputs = [];
4958
- if (existsSync20(outputsDir)) {
4959
- const files = readdirSync5(outputsDir).filter((f) => f.endsWith(".md"));
4960
- for (const file of files) {
4961
- const content = readFileSync17(join13(outputsDir, file), "utf-8");
4962
- outputs.push({ step: file.replace(".md", ""), content });
4963
- }
4964
- }
4965
- const prContent = await generatePRContent(userQuery, outputs, issueNumber);
4966
- const result = spawnSync4("gh", [
4967
- "pr",
4968
- "create",
4969
- "--repo",
4970
- `${owner}/${repo}`,
4971
- "--title",
4972
- prContent.title,
4973
- "--body",
4974
- prContent.body,
4975
- "--head",
4976
- config.branch || "HEAD"
4977
- ], { encoding: "utf-8" });
4978
- if (result.status !== 0) {
4979
- throw new Error(`Failed to create PR: ${result.stderr}`);
4980
- }
4981
- console.log(`PR created: ${result.stdout.trim()}`);
4982
- const match = result.stdout.match(/\/pull\/(\d+)/);
4983
- if (!match) {
4984
- throw new Error("Failed to parse PR number from gh output");
4985
- }
4986
- return parseInt(match[1], 10);
4987
- }
4988
- async function generatePRContent(userQuery, outputs, issueNumber) {
4989
- const outputsSummary = outputs.map((o) => `### ${o.step}
4990
- ${o.content}`).join("\n\n---\n\n");
4991
- const prompt = `You are generating a Pull Request title and body.
4992
-
4993
- ## Original Request
4994
- ${userQuery}
4995
-
4996
- ## Workflow Outputs
4997
- ${outputsSummary}
4998
-
4999
- Generate a PR title and body:
5000
- 1. Title: max 72 chars
5001
- 2. Body: Summary section + Changes section
5002
- ${issueNumber ? `End with "Closes #${issueNumber}"` : ""}
5003
-
5004
- Output:
5005
- \`\`\`pr
5006
- TITLE: <title>
5007
- BODY:
5008
- <body>
5009
- \`\`\``;
5010
- try {
5011
- const response = await callClaude(prompt);
5012
- return parsePRResponse(response, userQuery, issueNumber);
5013
- } catch {
5014
- return getFallbackPRContent(userQuery, issueNumber);
5015
- }
5016
- }
5017
- function parsePRResponse(response, userQuery, issueNumber) {
5018
- const prMatch = response.match(/```pr\n([\s\S]*?)```/);
5019
- if (!prMatch) {
5020
- return getFallbackPRContent(userQuery, issueNumber);
5021
- }
5022
- const content = prMatch[1];
5023
- const titleMatch = content.match(/TITLE:\s*(.+)/);
5024
- const bodyMatch = content.match(/BODY:\n([\s\S]*)/);
5025
- if (!titleMatch || !bodyMatch) {
5026
- return getFallbackPRContent(userQuery, issueNumber);
5027
- }
5028
- return {
5029
- title: titleMatch[1].trim(),
5030
- body: bodyMatch[1].trim()
5031
- };
5032
- }
5033
- function getFallbackPRContent(userQuery, issueNumber) {
5034
- const title = userQuery.slice(0, 72);
5035
- let body = `## Summary
5036
-
5037
- ${userQuery}`;
5038
- if (issueNumber) {
5039
- body += `
5040
-
5041
- Closes #${issueNumber}`;
5042
- }
5043
- return { title, body };
5044
- }
5045
-
5046
- // src/commands/github/comments.ts
5047
- import { spawn as spawn7, spawnSync as spawnSync5 } from "child_process";
5048
- async function postComment(config, body) {
5049
- const { owner, repo, prNumber, issueNumber } = config;
5050
- const args = prNumber ? ["pr", "comment", String(prNumber), "--body", body, "-R", `${owner}/${repo}`] : ["issue", "comment", String(issueNumber), "--body", body, "-R", `${owner}/${repo}`];
5051
- return new Promise((resolve, reject) => {
5052
- const proc = spawn7("gh", args, { stdio: "inherit" });
5053
- proc.on("close", (code) => {
5054
- if (code !== 0) reject(new Error(`gh failed with code ${code}`));
5055
- else resolve();
5056
- });
5057
- proc.on("error", reject);
5058
- });
5059
- }
5060
- async function postCompletionComment(config, taskId, prCreated) {
5061
- const lines = [
5062
- "## \u2705 Spets Workflow Completed",
5063
- "",
5064
- `Task \`${taskId}\` has been completed successfully!`,
5065
- "",
5066
- "All steps have been approved."
5067
- ];
5068
- if (prCreated) {
5069
- lines.push("", "\u{1F500} A Pull Request has been created automatically.");
5070
- }
5071
- await postComment(config, lines.join("\n"));
5072
- }
5073
- async function postRejectionComment(config, taskId) {
5074
- const comment = [
5075
- "## \u274C Spets Workflow Rejected",
5076
- "",
5077
- `Task \`${taskId}\` was rejected.`
5078
- ].join("\n");
5079
- await postComment(config, comment);
5080
- }
5081
- async function createOrUpdateIssue(config, taskId, userQuery, stepName) {
5082
- const { owner, repo, issueNumber, branch } = config;
5083
- const body = `## Spets Workflow Update
5084
-
5085
- - Task ID: \`${taskId}\`
5086
- - Current Step: **${stepName}** \u2705 Approved
5087
-
5088
- ### Description
5089
- ${userQuery}`;
5090
- if (issueNumber) {
5091
- spawnSync5("gh", [
5092
- "issue",
5093
- "comment",
5094
- String(issueNumber),
5095
- "--repo",
5096
- `${owner}/${repo}`,
5097
- "--body",
5098
- body
5099
- ], { encoding: "utf-8" });
5100
- } else {
5101
- spawnSync5("gh", [
5102
- "issue",
5103
- "create",
5104
- "--repo",
5105
- `${owner}/${repo}`,
5106
- "--title",
5107
- branch || userQuery.slice(0, 50),
5108
- "--body",
5109
- body,
5110
- "--label",
5111
- "spets"
5112
- ], { encoding: "utf-8" });
5113
- }
5114
- }
5115
-
5116
- // src/commands/github/command.ts
5117
- async function githubCommand(options) {
5118
- const cwd = process.cwd();
5119
- if (!spetsExists(cwd)) {
5120
- console.error("Spets not initialized.");
5121
- process.exit(1);
5122
- }
5123
- const githubInfo = getGitHubInfo2(cwd);
5124
- if (!githubInfo) {
5125
- console.error("Could not determine GitHub owner/repo. Set in .spets/config.yml or add git remote.");
5126
- process.exit(1);
5127
- }
5128
- const { owner, repo } = githubInfo;
5129
- const { pr, issue, comment, task } = options;
5130
- if (!pr && !issue) {
5131
- console.error("Either --pr or --issue is required");
5132
- process.exit(1);
5133
- }
5134
- const parsed = parseGitHubCommand(comment);
5135
- if (!parsed.command) {
5136
- console.log("No spets command found in comment. Ignoring.");
5137
- return;
5138
- }
5139
- console.log(`Received command: ${parsed.command}`);
5140
- const taskId = task || findTaskId(cwd, owner, repo, issue || pr);
5141
- if (!taskId) {
5142
- console.error("Could not determine task ID. Use --task option.");
5143
- process.exit(1);
5144
- }
5145
- console.log(`Task ID: ${taskId}`);
5146
- const config = loadConfig(cwd);
5147
- const orchestrator = new Orchestrator(cwd);
5148
- let response = orchestrator.cmdStatus(taskId);
5149
- if (response.type === "error") {
5150
- console.error(`Task '${taskId}' not found or invalid.`);
5151
- process.exit(1);
5152
- }
5153
- const githubConfig = {
5154
- owner,
5155
- repo,
5156
- prNumber: pr ? parseInt(pr, 10) : void 0,
5157
- issueNumber: issue ? parseInt(issue, 10) : void 0,
5158
- branch: getCurrentBranch2(cwd)
5159
- };
5160
- const adapter = createGitHubAdapter(githubConfig);
5161
- adapter.io.setTaskId(taskId);
5162
- switch (parsed.command) {
5163
- case "approve": {
5164
- console.log("Approving current step...");
5165
- response = orchestrator.cmdApprove(taskId);
5166
- if (parsed.createPR && response.type === "complete") {
5167
- console.log("Creating PR with AI-generated content...");
5168
- const completeResp = response;
5169
- await createPullRequestWithAI(githubConfig, taskId, completeResp.message || "", cwd);
5170
- }
5171
- if (parsed.createIssue && response.type === "phase") {
5172
- console.log("Creating/Updating Issue...");
5173
- const phaseResp = response;
5174
- await createOrUpdateIssue(githubConfig, taskId, phaseResp.description || "", phaseResp.step);
5175
- }
5176
- break;
5177
- }
5178
- case "revise": {
5179
- console.log(`Revising with feedback: ${parsed.feedback}`);
5180
- response = orchestrator.cmdRevise(taskId, parsed.feedback || "");
5181
- break;
5182
- }
5183
- case "reject": {
5184
- console.log("Rejecting workflow");
5185
- response = orchestrator.cmdReject(taskId);
5186
- await postRejectionComment(githubConfig, taskId);
5187
- if (config.hooks?.onReject) {
5188
- console.log("Running onReject hook...");
5189
- const hookContext = {
5190
- taskId,
5191
- stepName: "rejected",
5192
- stepIndex: 0,
5193
- outputPath: getOutputsDir(cwd),
5194
- document: "",
5195
- branch: getCurrentBranch2(cwd)
5196
- };
5197
- await runHook(config.hooks.onReject, hookContext, cwd);
5198
- }
5199
- return;
5200
- }
5201
- case "answer": {
5202
- console.log("Processing answers");
5203
- const answers = Object.entries(parsed.answers || {}).map(([id, answer]) => ({
5204
- questionId: id,
5205
- answer
5206
- }));
5207
- response = orchestrator.cmdClarified(taskId, answers);
5208
- break;
5209
- }
5210
- case "decide": {
5211
- console.log("Processing decisions with AI interpretation...");
5212
- const currentState = orchestrator.cmdStatus(taskId);
5213
- if (currentState.type !== "checkpoint" || currentState.checkpoint !== "clarify") {
5214
- console.error("No pending decisions found. Task may not be in clarify state.");
5215
- process.exit(1);
5216
- }
5217
- const pendingDecisions = currentState.decisions;
5218
- const userResponses = parsed.decisions || {};
5219
- const interpretedAnswers = await interpretDecisionResponses(pendingDecisions, userResponses, githubConfig);
5220
- const followUps = interpretedAnswers.filter((a) => a.followUp);
5221
- if (followUps.length > 0) {
5222
- for (const followUp of followUps) {
5223
- const decision = pendingDecisions.find((d) => d.id === followUp.decisionId);
5224
- if (decision && followUp.followUp) {
5225
- await postFollowUpComment(githubConfig, decision, followUp.followUp, taskId);
5226
- }
5227
- }
5228
- console.log("\n\u23F8\uFE0F Follow-up questions posted. Waiting for /decide response.");
5229
- return;
5230
- }
5231
- const decisionAnswers = interpretedAnswers.map((a) => ({
5232
- decisionId: a.decisionId,
5233
- selectedOptionId: a.selectedOptionId,
5234
- customInput: a.customInput
5235
- }));
5236
- response = orchestrator.cmdClarified(taskId, decisionAnswers);
5237
- break;
5238
- }
5239
- }
5240
- const stepExecutor = new StepExecutor(adapter, config, cwd);
5241
- while (response.type !== "complete" && response.type !== "error") {
5242
- if (response.type === "phase") {
5243
- const phaseResponse = response;
5244
- if (phaseResponse.phase === "explore") {
5245
- const exploreResp = phaseResponse;
5246
- console.log(`
5247
- --- Step ${exploreResp.stepIndex}/${exploreResp.totalSteps}: ${exploreResp.step} ---`);
5248
- console.log("Phase 1/5: Exploring codebase...\n");
5249
- const stepContext = {
5250
- taskId: exploreResp.taskId,
5251
- description: exploreResp.description,
5252
- stepIndex: exploreResp.stepIndex,
5253
- totalSteps: exploreResp.totalSteps,
5254
- previousOutput: exploreResp.context.previousOutput
5255
- };
5256
- const result = await stepExecutor.executeExplorePhase(exploreResp.step, stepContext);
5257
- response = orchestrator.cmdExploreDone(taskId, result.exploreOutput);
5258
- } else if (phaseResponse.phase === "clarify") {
5259
- const clarifyResp = phaseResponse;
5260
- console.log("Phase 2/5: Generating questions...\n");
5261
- const stepContext = {
5262
- taskId: clarifyResp.taskId,
5263
- description: clarifyResp.description,
5264
- stepIndex: 0,
5265
- totalSteps: 0,
5266
- gatheredContext: clarifyResp.gatheredContext,
5267
- previousDecisions: clarifyResp.previousDecisions
5268
- };
5269
- const result = await stepExecutor.executeClarifyPhase(clarifyResp.step, stepContext);
5270
- const clarifyOutput = result.clarifyOutput || { ready: true, decisions: [] };
5271
- response = orchestrator.cmdClarifyDone(taskId, clarifyOutput);
5272
- } else if (phaseResponse.phase === "execute") {
5273
- const executeResp = phaseResponse;
5274
- const attemptInfo = executeResp.context.verifyFeedback ? " (auto-fix)" : "";
5275
- console.log(`Phase 3/5: Executing${attemptInfo}...
5276
- `);
5277
- const stepContext = {
5278
- taskId: executeResp.taskId,
5279
- description: executeResp.description,
5280
- stepIndex: executeResp.stepIndex,
5281
- totalSteps: executeResp.totalSteps,
5282
- exploreOutput: executeResp.exploreOutput,
5283
- answers: executeResp.answers,
5284
- revisionFeedback: executeResp.context.revisionFeedback,
5285
- verifyFeedback: executeResp.context.verifyFeedback
5286
- };
5287
- await stepExecutor.executeExecutePhase(executeResp.step, stepContext);
5288
- response = orchestrator.cmdExecuteDone(taskId);
5289
- } else if (phaseResponse.phase === "verify") {
5290
- const verifyResp = phaseResponse;
5291
- console.log(`Phase 4/5: Verifying document (attempt ${verifyResp.verifyAttempts}/${verifyResp.maxAttempts})...
5292
- `);
5293
- const stepContext = {
5294
- taskId: verifyResp.taskId,
5295
- description: verifyResp.description,
5296
- stepIndex: verifyResp.stepIndex,
5297
- totalSteps: verifyResp.totalSteps,
5298
- verifyAttempts: verifyResp.verifyAttempts
5299
- };
5300
- const result = await stepExecutor.executeVerifyPhase(verifyResp.step, stepContext);
5301
- response = orchestrator.cmdVerifyDone(taskId, result.verifyOutput);
5302
- } else if (phaseResponse.phase === "knowledge") {
5303
- const knowledgeResp = phaseResponse;
5304
- console.log("Extracting knowledge from workflow...\n");
5305
- const result = await stepExecutor.executeKnowledgePhase(knowledgeResp);
5306
- if (result.knowledgeOutput) {
5307
- response = orchestrator.cmdKnowledgeExtractDone(taskId, result.knowledgeOutput);
5308
- } else {
5309
- response = orchestrator.cmdKnowledgeSkip(taskId);
5310
- }
5311
- }
5312
- } else if (response.type === "checkpoint") {
5313
- if (response.checkpoint === "clarify") {
5314
- const clarifyCheckpoint = response;
5315
- console.log("Phase 2/5: Decisions needed\n");
5316
- await adapter.io.askDecisions(clarifyCheckpoint.decisions);
5317
- console.log(`
5318
- \u23F8\uFE0F Workflow paused. Waiting for /decide on GitHub.`);
5319
- return;
5320
- } else if (response.checkpoint === "approve") {
5321
- const approveCheckpoint = response;
5322
- console.log("Phase 5/5: Document ready for review\n");
5323
- await adapter.io.approve(
5324
- approveCheckpoint.specPath,
5325
- approveCheckpoint.step,
5326
- approveCheckpoint.stepIndex,
5327
- approveCheckpoint.totalSteps
5328
- );
5329
- console.log(`
5330
- \u23F8\uFE0F Workflow paused. Waiting for /approve or /revise on GitHub.`);
5331
- return;
5332
- }
5333
- } else {
5334
- console.error(`Unknown response type: ${response.type}`);
5335
- process.exit(1);
5336
- }
5337
- }
5338
- if (response.type === "complete") {
5339
- const completeResponse = response;
5340
- if (completeResponse.status === "completed") {
5341
- console.log("\u2705 Workflow completed!");
5342
- if (config.hooks?.onComplete) {
5343
- console.log("Running onComplete hook...");
5344
- const hookContext = {
5345
- taskId,
5346
- stepName: "complete",
5347
- stepIndex: config.steps.length,
5348
- outputPath: getOutputsDir(cwd),
5349
- document: "",
5350
- branch: getCurrentBranch2(cwd)
5351
- };
5352
- await runHook(config.hooks.onComplete, hookContext, cwd);
5353
- }
5354
- console.log("Creating PR...");
5355
- let prCreated = false;
5356
- try {
5357
- const state = orchestrator.cmdStatus(taskId);
5358
- const description = state.type !== "error" ? state.description || "" : "";
5359
- await createPullRequestWithAI(githubConfig, taskId, description, cwd);
5360
- prCreated = true;
5361
- } catch (err) {
5362
- console.error("Failed to create PR:", err.message);
5363
- }
5364
- await postCompletionComment(githubConfig, taskId, prCreated);
5365
- } else if (completeResponse.status === "rejected") {
5366
- console.log("\u274C Workflow rejected");
5367
- }
5368
- } else if (response.type === "error") {
5369
- console.error(`Error: ${response.error}`);
5370
- process.exit(1);
5371
- }
5372
- }
5373
-
5374
- // src/commands/orchestrate.ts
5375
- function outputJSON(data) {
5376
- console.log(JSON.stringify(data, null, 2));
5377
- }
5378
- function outputError(error) {
5379
- console.log(JSON.stringify({ type: "error", error }, null, 2));
5380
- process.exit(1);
5381
- }
5382
- function parseFlexibleJSON(json, options = {}) {
5383
- const { expectArray = false, arrayKeys = [], defaultValue, allowStringFallback = false } = options;
5384
- try {
5385
- const parsed = JSON.parse(json);
5386
- if (expectArray) {
5387
- if (Array.isArray(parsed)) {
5388
- return { success: true, data: parsed };
5389
- }
5390
- if (typeof parsed === "object" && parsed !== null) {
5391
- for (const key of arrayKeys) {
5392
- if (Array.isArray(parsed[key])) {
5393
- return { success: true, data: parsed[key] };
5394
- }
5395
- }
5396
- for (const key of ["data", "items", "results", "list"]) {
5397
- if (Array.isArray(parsed[key])) {
5398
- return { success: true, data: parsed[key] };
5399
- }
5400
- }
5401
- const values = Object.values(parsed);
5402
- const arrays = values.filter(Array.isArray);
5403
- if (arrays.length === 1) {
5404
- return { success: true, data: arrays[0] };
5405
- }
5406
- }
5407
- return { success: true, data: [] };
5408
- }
5409
- if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
5410
- const keys = Object.keys(parsed);
5411
- if (keys.length === 1) {
5412
- const innerValue = parsed[keys[0]];
5413
- if (typeof innerValue === "object" && innerValue !== null && !Array.isArray(innerValue)) {
5414
- const innerKeys = Object.keys(innerValue);
5415
- if (innerKeys.length > 1) {
5416
- return { success: true, data: innerValue };
5417
- }
5418
- }
5419
- }
5420
- return { success: true, data: parsed };
5421
- }
5422
- return { success: true, data: parsed };
5423
- } catch {
5424
- if (allowStringFallback && defaultValue !== void 0) {
5425
- return { success: true, data: defaultValue };
5426
- }
5427
- return { success: false, error: "Invalid JSON format" };
5428
- }
5429
- }
5430
- async function orchestrateCommand(action, args) {
5431
- try {
5432
- const orchestrator = new Orchestrator();
5433
- switch (action) {
5434
- case "init": {
5435
- const description = args[0];
5436
- if (!description) {
5437
- outputError("Description is required for init");
5438
- return;
5439
- }
5440
- const result = orchestrator.cmdInit(description);
5441
- outputJSON(result);
5442
- break;
2187
+ const orchestrator = new Orchestrator();
2188
+ switch (action) {
2189
+ case "init": {
2190
+ const description = args[0];
2191
+ if (!description) {
2192
+ outputError("Description is required for init");
2193
+ return;
2194
+ }
2195
+ const result = orchestrator.cmdInit(description);
2196
+ outputJSON(result);
2197
+ break;
5443
2198
  }
5444
2199
  case "explore-done": {
5445
2200
  const taskId = args[0];
@@ -5723,7 +2478,7 @@ async function dashboardCommand(options) {
5723
2478
  }
5724
2479
 
5725
2480
  // src/ui/hub.ts
5726
- import { select as select4 } from "@inquirer/prompts";
2481
+ import { select as select2 } from "@inquirer/prompts";
5727
2482
  var MAIN_MENU_CHOICES = [
5728
2483
  { value: "config", name: "\u2699\uFE0F Config - View and edit configuration" },
5729
2484
  { value: "tasks", name: "\u{1F4CB} Tasks - Browse workflow tasks" },
@@ -5743,7 +2498,7 @@ var TUIHub = class {
5743
2498
  console.log("\n\u{1F680} Spets TUI Hub");
5744
2499
  console.log("Interactive management for your SDD workflow\n");
5745
2500
  while (true) {
5746
- const action = await select4({
2501
+ const action = await select2({
5747
2502
  message: "What would you like to do?",
5748
2503
  choices: MAIN_MENU_CHOICES
5749
2504
  });
@@ -5845,7 +2600,7 @@ async function uiConfigCommand(action, args, options) {
5845
2600
  console.log(renderConfigJSON(cwd));
5846
2601
  return;
5847
2602
  }
5848
- const { runConfigInteractive: runConfigInteractive2 } = await import("./config-FMQ5APSF.js");
2603
+ const { runConfigInteractive: runConfigInteractive2 } = await import("./config-WVNAEABV.js");
5849
2604
  await runConfigInteractive2(cwd);
5850
2605
  }
5851
2606
  async function uiTasksCommand(taskId, options) {
@@ -5863,7 +2618,7 @@ async function uiTasksCommand(taskId, options) {
5863
2618
  console.log(renderTasksJSON(taskId, cwd));
5864
2619
  return;
5865
2620
  }
5866
- const { runTasksInteractive: runTasksInteractive2 } = await import("./tasks-2IAXZVDS.js");
2621
+ const { runTasksInteractive: runTasksInteractive2 } = await import("./tasks-EKOBEJ45.js");
5867
2622
  await runTasksInteractive2(cwd);
5868
2623
  }
5869
2624
  async function uiDocsCommand(docName, options) {
@@ -5885,7 +2640,7 @@ async function uiDocsCommand(docName, options) {
5885
2640
  console.log(renderDocsJSON(void 0, cwd));
5886
2641
  return;
5887
2642
  }
5888
- const { runDocsInteractive: runDocsInteractive2 } = await import("./docs-FNCV4EQF.js");
2643
+ const { runDocsInteractive: runDocsInteractive2 } = await import("./docs-KTGUMHHF.js");
5889
2644
  await runDocsInteractive2(cwd);
5890
2645
  }
5891
2646
  async function uiKnowledgeCommand(entryName, options) {
@@ -5907,15 +2662,15 @@ async function uiKnowledgeCommand(entryName, options) {
5907
2662
  console.log(renderKnowledgeJSON(void 0, cwd));
5908
2663
  return;
5909
2664
  }
5910
- const { runKnowledgeInteractive: runKnowledgeInteractive2 } = await import("./knowledge-TVXZATJX.js");
2665
+ const { runKnowledgeInteractive: runKnowledgeInteractive2 } = await import("./knowledge-EVKAWXD4.js");
5911
2666
  await runKnowledgeInteractive2(cwd);
5912
2667
  }
5913
2668
 
5914
2669
  // src/server/index.ts
5915
2670
  import express from "express";
5916
2671
  import { createServer } from "http";
5917
- import { join as join16, dirname as dirname7 } from "path";
5918
- import { existsSync as existsSync23 } from "fs";
2672
+ import { join as join12, dirname as dirname3 } from "path";
2673
+ import { existsSync as existsSync14 } from "fs";
5919
2674
  import { fileURLToPath as fileURLToPath2 } from "url";
5920
2675
 
5921
2676
  // src/server/routes/index.ts
@@ -5983,7 +2738,7 @@ tasksRouter.get("/:id/state", (req, res) => {
5983
2738
 
5984
2739
  // src/server/routes/config.ts
5985
2740
  import { Router as Router2 } from "express";
5986
- import { readFileSync as readFileSync18, writeFileSync as writeFileSync9 } from "fs";
2741
+ import { readFileSync as readFileSync11, writeFileSync as writeFileSync6 } from "fs";
5987
2742
  var configRouter = Router2();
5988
2743
  configRouter.get("/", (req, res) => {
5989
2744
  try {
@@ -5998,7 +2753,7 @@ configRouter.get("/raw", (req, res) => {
5998
2753
  try {
5999
2754
  const cwd = req.app.locals.cwd;
6000
2755
  const configPath = getConfigPath(cwd);
6001
- const raw = readFileSync18(configPath, "utf-8");
2756
+ const raw = readFileSync11(configPath, "utf-8");
6002
2757
  res.json({ content: raw });
6003
2758
  } catch (err) {
6004
2759
  res.status(500).json({ error: err.message });
@@ -6013,7 +2768,7 @@ configRouter.put("/", (req, res) => {
6013
2768
  return;
6014
2769
  }
6015
2770
  const configPath = getConfigPath(cwd);
6016
- writeFileSync9(configPath, content);
2771
+ writeFileSync6(configPath, content);
6017
2772
  clearConfigCache();
6018
2773
  const config = loadConfig(cwd);
6019
2774
  res.json(config);
@@ -6024,8 +2779,8 @@ configRouter.put("/", (req, res) => {
6024
2779
 
6025
2780
  // src/server/routes/steps.ts
6026
2781
  import { Router as Router3 } from "express";
6027
- import { readFileSync as readFileSync19, writeFileSync as writeFileSync10, mkdirSync as mkdirSync8, rmSync as rmSync2, existsSync as existsSync21 } from "fs";
6028
- import { join as join14 } from "path";
2782
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync7, mkdirSync as mkdirSync4, rmSync as rmSync2, existsSync as existsSync12 } from "fs";
2783
+ import { join as join10 } from "path";
6029
2784
  var stepsRouter = Router3();
6030
2785
  stepsRouter.get("/", (req, res) => {
6031
2786
  try {
@@ -6041,12 +2796,12 @@ stepsRouter.get("/:name/template", (req, res) => {
6041
2796
  try {
6042
2797
  const cwd = req.app.locals.cwd;
6043
2798
  const stepsDir = getStepsDir(cwd);
6044
- const templatePath = join14(stepsDir, req.params.name, "template.md");
6045
- if (!existsSync21(templatePath)) {
2799
+ const templatePath = join10(stepsDir, req.params.name, "template.md");
2800
+ if (!existsSync12(templatePath)) {
6046
2801
  res.status(404).json({ error: "Template not found" });
6047
2802
  return;
6048
2803
  }
6049
- const content = readFileSync19(templatePath, "utf-8");
2804
+ const content = readFileSync12(templatePath, "utf-8");
6050
2805
  res.json({ name: req.params.name, content });
6051
2806
  } catch (err) {
6052
2807
  res.status(500).json({ error: err.message });
@@ -6061,8 +2816,8 @@ stepsRouter.put("/:name/template", (req, res) => {
6061
2816
  return;
6062
2817
  }
6063
2818
  const stepsDir = getStepsDir(cwd);
6064
- const templatePath = join14(stepsDir, req.params.name, "template.md");
6065
- writeFileSync10(templatePath, content);
2819
+ const templatePath = join10(stepsDir, req.params.name, "template.md");
2820
+ writeFileSync7(templatePath, content);
6066
2821
  res.json({ name: req.params.name, content });
6067
2822
  } catch (err) {
6068
2823
  res.status(500).json({ error: err.message });
@@ -6077,9 +2832,9 @@ stepsRouter.post("/", (req, res) => {
6077
2832
  return;
6078
2833
  }
6079
2834
  const stepsDir = getStepsDir(cwd);
6080
- const stepDir = join14(stepsDir, name);
6081
- mkdirSync8(stepDir, { recursive: true });
6082
- writeFileSync10(join14(stepDir, "template.md"), template || `# ${name}
2835
+ const stepDir = join10(stepsDir, name);
2836
+ mkdirSync4(stepDir, { recursive: true });
2837
+ writeFileSync7(join10(stepDir, "template.md"), template || `# ${name}
6083
2838
 
6084
2839
  Define your template here.
6085
2840
  `);
@@ -6088,13 +2843,13 @@ Define your template here.
6088
2843
  if (!config.steps.includes(name)) {
6089
2844
  config.steps.push(name);
6090
2845
  const configPath = getConfigPath(cwd);
6091
- const raw = readFileSync19(configPath, "utf-8");
2846
+ const raw = readFileSync12(configPath, "utf-8");
6092
2847
  const updatedYaml = raw.replace(
6093
2848
  /steps:[\s\S]*?(?=\n\w|\n$|$)/,
6094
2849
  `steps:
6095
2850
  ${config.steps.map((s) => ` - ${s}`).join("\n")}`
6096
2851
  );
6097
- writeFileSync10(configPath, updatedYaml);
2852
+ writeFileSync7(configPath, updatedYaml);
6098
2853
  clearConfigCache();
6099
2854
  }
6100
2855
  res.json({ name, created: true });
@@ -6107,21 +2862,21 @@ stepsRouter.delete("/:name", (req, res) => {
6107
2862
  const cwd = req.app.locals.cwd;
6108
2863
  const name = req.params.name;
6109
2864
  const stepsDir = getStepsDir(cwd);
6110
- const stepDir = join14(stepsDir, name);
6111
- if (existsSync21(stepDir)) {
2865
+ const stepDir = join10(stepsDir, name);
2866
+ if (existsSync12(stepDir)) {
6112
2867
  rmSync2(stepDir, { recursive: true });
6113
2868
  }
6114
2869
  clearConfigCache();
6115
2870
  const config = loadConfig(cwd);
6116
2871
  const updatedSteps = config.steps.filter((s) => s !== name);
6117
2872
  const configPath = getConfigPath(cwd);
6118
- const raw = readFileSync19(configPath, "utf-8");
2873
+ const raw = readFileSync12(configPath, "utf-8");
6119
2874
  const updatedYaml = raw.replace(
6120
2875
  /steps:[\s\S]*?(?=\n\w|\n$|$)/,
6121
2876
  `steps:
6122
2877
  ${updatedSteps.map((s) => ` - ${s}`).join("\n")}`
6123
2878
  );
6124
- writeFileSync10(configPath, updatedYaml);
2879
+ writeFileSync7(configPath, updatedYaml);
6125
2880
  clearConfigCache();
6126
2881
  res.json({ name, deleted: true });
6127
2882
  } catch (err) {
@@ -6137,13 +2892,13 @@ stepsRouter.put("/reorder", (req, res) => {
6137
2892
  return;
6138
2893
  }
6139
2894
  const configPath = getConfigPath(cwd);
6140
- const raw = readFileSync19(configPath, "utf-8");
2895
+ const raw = readFileSync12(configPath, "utf-8");
6141
2896
  const updatedYaml = raw.replace(
6142
2897
  /steps:[\s\S]*?(?=\n\w|\n$|$)/,
6143
2898
  `steps:
6144
2899
  ${steps.map((s) => ` - ${s}`).join("\n")}`
6145
2900
  );
6146
- writeFileSync10(configPath, updatedYaml);
2901
+ writeFileSync7(configPath, updatedYaml);
6147
2902
  clearConfigCache();
6148
2903
  res.json({ steps });
6149
2904
  } catch (err) {
@@ -6153,8 +2908,8 @@ ${steps.map((s) => ` - ${s}`).join("\n")}`
6153
2908
 
6154
2909
  // src/server/routes/knowledge.ts
6155
2910
  import { Router as Router4 } from "express";
6156
- import { unlinkSync, existsSync as existsSync22 } from "fs";
6157
- import { join as join15 } from "path";
2911
+ import { unlinkSync, existsSync as existsSync13 } from "fs";
2912
+ import { join as join11 } from "path";
6158
2913
  var knowledgeRouter = Router4();
6159
2914
  knowledgeRouter.get("/", (req, res) => {
6160
2915
  try {
@@ -6222,8 +2977,8 @@ knowledgeRouter.delete("/:name", (req, res) => {
6222
2977
  try {
6223
2978
  const cwd = req.app.locals.cwd;
6224
2979
  const knowledgeDir = getKnowledgeDir(cwd);
6225
- const filePath = join15(knowledgeDir, `${req.params.name}.md`);
6226
- if (!existsSync22(filePath)) {
2980
+ const filePath = join11(knowledgeDir, `${req.params.name}.md`);
2981
+ if (!existsSync13(filePath)) {
6227
2982
  res.status(404).json({ error: "Knowledge entry not found" });
6228
2983
  return;
6229
2984
  }
@@ -6325,7 +3080,7 @@ workflowRouter.get("/:id/status", (req, res) => {
6325
3080
 
6326
3081
  // src/server/routes/terminal.ts
6327
3082
  import { Router as Router6 } from "express";
6328
- import { spawn as spawn8 } from "child_process";
3083
+ import { spawn } from "child_process";
6329
3084
  var sessions = /* @__PURE__ */ new Map();
6330
3085
  var terminalRouter = Router6();
6331
3086
  terminalRouter.post("/spawn", (req, res) => {
@@ -6337,7 +3092,7 @@ terminalRouter.post("/spawn", (req, res) => {
6337
3092
  return;
6338
3093
  }
6339
3094
  const sessionId = `term-${Date.now().toString(36)}`;
6340
- const child = spawn8(command, args || [], {
3095
+ const child = spawn(command, args || [], {
6341
3096
  cwd,
6342
3097
  stdio: "pipe",
6343
3098
  env: { ...process.env, FORCE_COLOR: "1" }
@@ -6376,9 +3131,9 @@ terminalRouter.post("/:sessionId/input", (req, res) => {
6376
3131
  res.status(404).json({ error: "Session not found" });
6377
3132
  return;
6378
3133
  }
6379
- const { input: input2 } = req.body;
3134
+ const { input } = req.body;
6380
3135
  if (session.process.stdin?.writable) {
6381
- session.process.stdin.write(input2);
3136
+ session.process.stdin.write(input);
6382
3137
  }
6383
3138
  res.json({ ok: true });
6384
3139
  });
@@ -6435,14 +3190,14 @@ function startServer(options) {
6435
3190
  app.locals.cwd = cwd;
6436
3191
  app.use(express.json());
6437
3192
  app.use("/api", apiRouter);
6438
- const __dirname3 = dirname7(fileURLToPath2(import.meta.url));
6439
- const webDistPath = join16(__dirname3, "..", "web", "dist");
6440
- const webDistDevPath = join16(__dirname3, "..", "..", "web", "dist");
6441
- const staticPath = existsSync23(webDistPath) ? webDistPath : webDistDevPath;
6442
- if (existsSync23(staticPath)) {
3193
+ const __dirname3 = dirname3(fileURLToPath2(import.meta.url));
3194
+ const webDistPath = join12(__dirname3, "..", "web", "dist");
3195
+ const webDistDevPath = join12(__dirname3, "..", "..", "web", "dist");
3196
+ const staticPath = existsSync14(webDistPath) ? webDistPath : webDistDevPath;
3197
+ if (existsSync14(staticPath)) {
6443
3198
  app.use(express.static(staticPath));
6444
3199
  app.get("*", (_req, res) => {
6445
- res.sendFile(join16(staticPath, "index.html"));
3200
+ res.sendFile(join12(staticPath, "index.html"));
6446
3201
  });
6447
3202
  } else {
6448
3203
  app.get("*", (_req, res) => {
@@ -6488,15 +3243,14 @@ async function webCommand(options) {
6488
3243
  }
6489
3244
 
6490
3245
  // src/index.ts
6491
- var __dirname2 = dirname8(fileURLToPath3(import.meta.url));
6492
- var pkg = JSON.parse(readFileSync20(join17(__dirname2, "..", "package.json"), "utf-8"));
3246
+ var __dirname2 = dirname4(fileURLToPath3(import.meta.url));
3247
+ var pkg = JSON.parse(readFileSync13(join13(__dirname2, "..", "package.json"), "utf-8"));
6493
3248
  var program = new Command();
6494
3249
  program.name("spets").description("Spec Driven Development Execution Framework").version(pkg.version).addHelpText("after", `
6495
3250
  Examples:
6496
3251
  $ spets init Initialize spets in current directory
6497
3252
  $ spets init -i Interactive setup wizard
6498
- $ spets start "add login page" Start a new workflow
6499
- $ spets resume Resume paused workflow
3253
+ $ spets orchestrate init "task" Start workflow via orchestrator
6500
3254
  $ spets dashboard View all tasks
6501
3255
 
6502
3256
  Configuration:
@@ -6506,11 +3260,8 @@ Configuration:
6506
3260
 
6507
3261
  Learn more: https://github.com/anthropics/spets
6508
3262
  `);
6509
- program.command("init").description("Initialize spets in current directory").option("-f, --force", "Overwrite existing config").option("-i, --interactive", "Interactive setup wizard").option("--github", "Add GitHub Actions workflow for PR/Issue integration").action(initCommand);
3263
+ program.command("init").description("Initialize spets in current directory").option("-f, --force", "Overwrite existing config").option("-i, --interactive", "Interactive setup wizard").action(initCommand);
6510
3264
  program.command("status").description("Show current workflow status").option("-t, --task <taskId>", "Show status for specific task").action(statusCommand);
6511
- 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)").option("--agent <agent>", "AI agent to use (claude or codex)").action(startCommand);
6512
- 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").option("--pr", "Create PR on workflow completion (use with --approve)").option("--agent <agent>", "AI agent to use (claude or codex)").action(resumeCommand);
6513
- 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);
6514
3265
  program.command("plugin").description("Manage plugins").argument("<action>", "Action: install, uninstall, list").argument("[name]", "Plugin name").action(pluginCommand);
6515
3266
  program.command("dashboard").description("Interactive task dashboard with drill-down").option("-t, --task <taskId>", "Show details for specific task").option("--json", "Output as JSON (for external consumers)").option("-l, --limit <number>", "Limit number of tasks shown", "20").action(dashboardCommand);
6516
3267
  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);