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/README.md +24 -101
- package/dist/{chunk-2MUV3F53.js → chunk-63Q6RNKK.js} +1 -6
- package/dist/{chunk-3JIHGW47.js → chunk-6FL7APE6.js} +1 -1
- package/dist/{chunk-ZRWVDWBF.js → chunk-75EYXFBG.js} +7 -7
- package/dist/{chunk-BCOGSLCX.js → chunk-P3GQOSMU.js} +18 -23
- package/dist/{chunk-OIFVTELS.js → chunk-R62NYMA5.js} +1 -1
- package/dist/{config-FMQ5APSF.js → config-WVNAEABV.js} +2 -2
- package/dist/{docs-FNCV4EQF.js → docs-KTGUMHHF.js} +2 -2
- package/dist/index.js +140 -3389
- package/dist/{knowledge-TVXZATJX.js → knowledge-EVKAWXD4.js} +2 -2
- package/dist/{tasks-2IAXZVDS.js → tasks-EKOBEJ45.js} +2 -2
- package/package.json +1 -1
- package/web/dist/assets/{index-D7PKBhmU.js → index-DkQph699.js} +1 -1
- package/web/dist/index.html +1 -1
- package/assets/github/ISSUE_TEMPLATE/spets-task.yml +0 -23
- package/assets/github/workflows/spets.yml +0 -248
- package/templates/hooks/cleanup-branch.sh +0 -37
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
renderConfigJSON,
|
|
5
5
|
runConfigInteractive,
|
|
6
6
|
setConfigField
|
|
7
|
-
} from "./chunk-
|
|
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-
|
|
19
|
+
} from "./chunk-75EYXFBG.js";
|
|
20
20
|
import {
|
|
21
21
|
renderDocsJSON,
|
|
22
22
|
runDocsInteractive
|
|
23
|
-
} from "./chunk-
|
|
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-
|
|
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-
|
|
45
|
+
} from "./chunk-63Q6RNKK.js";
|
|
47
46
|
|
|
48
47
|
// src/index.ts
|
|
49
48
|
import { Command } from "commander";
|
|
50
|
-
import { readFileSync as
|
|
51
|
-
import { dirname as
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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/
|
|
634
|
-
# onComplete: "./hooks/
|
|
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(
|
|
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
|
|
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
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
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
|
|
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/
|
|
2387
|
-
|
|
2388
|
-
|
|
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
|
|
2408
|
-
|
|
2409
|
-
|
|
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
|
|
2137
|
+
function parseFlexibleJSON(json, options = {}) {
|
|
2138
|
+
const { expectArray = false, arrayKeys = [], defaultValue, allowStringFallback = false } = options;
|
|
2423
2139
|
try {
|
|
2424
|
-
const
|
|
2425
|
-
if (
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
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
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
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
|
|
2185
|
+
async function orchestrateCommand(action, args) {
|
|
2484
2186
|
try {
|
|
2485
|
-
const
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
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
|
|
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
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
|
5918
|
-
import { existsSync as
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
|
6028
|
-
import { join as
|
|
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 =
|
|
6045
|
-
if (!
|
|
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 =
|
|
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 =
|
|
6065
|
-
|
|
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 =
|
|
6081
|
-
|
|
6082
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
6111
|
-
if (
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
6157
|
-
import { join as
|
|
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 =
|
|
6226
|
-
if (!
|
|
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
|
|
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 =
|
|
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
|
|
3134
|
+
const { input } = req.body;
|
|
6380
3135
|
if (session.process.stdin?.writable) {
|
|
6381
|
-
session.process.stdin.write(
|
|
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 =
|
|
6439
|
-
const webDistPath =
|
|
6440
|
-
const webDistDevPath =
|
|
6441
|
-
const staticPath =
|
|
6442
|
-
if (
|
|
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(
|
|
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 =
|
|
6492
|
-
var pkg = JSON.parse(
|
|
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
|
|
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").
|
|
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);
|