spets 0.1.78 → 0.1.80

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { Command } from "commander";
5
- import { readFileSync as readFileSync13 } from "fs";
6
- import { dirname as dirname7, join as join11 } from "path";
5
+ import { readFileSync as readFileSync15 } from "fs";
6
+ import { dirname as dirname8, join as join12 } from "path";
7
7
  import { fileURLToPath as fileURLToPath2 } from "url";
8
8
 
9
9
  // src/commands/init.ts
@@ -407,6 +407,7 @@ async function initCommand(options) {
407
407
  mkdirSync2(join3(spetsDir, "steps"), { recursive: true });
408
408
  mkdirSync2(join3(spetsDir, "outputs"), { recursive: true });
409
409
  mkdirSync2(join3(spetsDir, "hooks"), { recursive: true });
410
+ mkdirSync2(join3(spetsDir, "knowledge"), { recursive: true });
410
411
  const templatesDir = join3(__dirname, "..", "templates");
411
412
  const hookTemplate = join3(__dirname, "..", "templates", "hooks", "cleanup-branch.sh");
412
413
  if (existsSync3(hookTemplate)) {
@@ -417,6 +418,11 @@ async function initCommand(options) {
417
418
  } catch {
418
419
  }
419
420
  }
421
+ const knowledgeGuideTemplate = join3(__dirname, "..", "templates", "knowledge", "guide.md");
422
+ if (existsSync3(knowledgeGuideTemplate)) {
423
+ const guideDest = join3(spetsDir, "knowledge", "guide.md");
424
+ cpSync(knowledgeGuideTemplate, guideDest);
425
+ }
420
426
  const githubInfo = getGitHubInfoFromRemote();
421
427
  writeFileSync2(join3(spetsDir, "config.yml"), getDefaultConfig(githubInfo));
422
428
  createDefaultSteps(spetsDir);
@@ -428,6 +434,7 @@ async function initCommand(options) {
428
434
  console.log(" .spets/steps/01-plan/template.md - Planning step template");
429
435
  console.log(" .spets/steps/02-implement/template.md - Implementation step template");
430
436
  console.log(" .spets/hooks/cleanup-branch.sh - Example branch cleanup hook");
437
+ console.log(" .spets/knowledge/guide.md - Knowledge extraction guide");
431
438
  console.log(" .claude/commands/spets.md - Claude Code command");
432
439
  if (options.github) {
433
440
  createGitHubWorkflow(cwd);
@@ -467,6 +474,11 @@ steps:
467
474
  # Output configuration
468
475
  output:
469
476
  path: .spets/outputs
477
+
478
+ # Knowledge base configuration
479
+ knowledge:
480
+ enabled: true # Save knowledge from completed workflows
481
+ inject: true # Inject available knowledge into explore prompts
470
482
  ${githubSection}
471
483
  # Optional hooks (shell scripts)
472
484
  # hooks:
@@ -797,13 +809,102 @@ function formatDocStatus(status) {
797
809
  import { execSync as execSync2 } from "child_process";
798
810
 
799
811
  // src/orchestrator/index.ts
800
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
801
- import { join as join6, dirname as dirname2 } from "path";
802
- import matter2 from "gray-matter";
812
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync7, mkdirSync as mkdirSync5 } from "fs";
813
+ import { join as join7, dirname as dirname2 } from "path";
814
+ import matter3 from "gray-matter";
803
815
 
804
816
  // src/core/prompt-builder.ts
805
- import { readFileSync as readFileSync4, existsSync as existsSync5 } from "fs";
817
+ import { readFileSync as readFileSync5, existsSync as existsSync6 } from "fs";
818
+ import { join as join6 } from "path";
819
+
820
+ // src/core/knowledge.ts
821
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, existsSync as existsSync5, mkdirSync as mkdirSync4, readdirSync as readdirSync3 } from "fs";
806
822
  import { join as join5 } from "path";
823
+ import matter2 from "gray-matter";
824
+ var KNOWLEDGE_DIR = "knowledge";
825
+ var GUIDE_FILE = "guide.md";
826
+ function getKnowledgeDir(cwd = process.cwd()) {
827
+ return join5(getSpetsDir(cwd), KNOWLEDGE_DIR);
828
+ }
829
+ function ensureKnowledgeDir(cwd = process.cwd()) {
830
+ const knowledgeDir = getKnowledgeDir(cwd);
831
+ if (!existsSync5(knowledgeDir)) {
832
+ mkdirSync4(knowledgeDir, { recursive: true });
833
+ }
834
+ }
835
+ function listKnowledgeFiles(cwd = process.cwd()) {
836
+ const knowledgeDir = getKnowledgeDir(cwd);
837
+ if (!existsSync5(knowledgeDir)) {
838
+ return [];
839
+ }
840
+ const files = readdirSync3(knowledgeDir).filter((f) => f.endsWith(".md") && f !== GUIDE_FILE).map((f) => f.replace(/\.md$/, "")).sort();
841
+ return files;
842
+ }
843
+ function loadKnowledgeFile(filename, cwd = process.cwd()) {
844
+ const knowledgeDir = getKnowledgeDir(cwd);
845
+ const filePath = join5(knowledgeDir, `${filename}.md`);
846
+ if (!existsSync5(filePath)) {
847
+ return null;
848
+ }
849
+ const fileContent = readFileSync4(filePath, "utf-8");
850
+ const { data, content } = matter2(fileContent);
851
+ return {
852
+ filename,
853
+ content: content.trim(),
854
+ source: data.source || { taskId: "unknown", step: "unknown" },
855
+ createdAt: data.createdAt || (/* @__PURE__ */ new Date()).toISOString()
856
+ };
857
+ }
858
+ function loadKnowledgeFiles(filenames, cwd = process.cwd()) {
859
+ const entries = [];
860
+ for (const filename of filenames) {
861
+ const entry = loadKnowledgeFile(filename, cwd);
862
+ if (entry) {
863
+ entries.push(entry);
864
+ }
865
+ }
866
+ return entries;
867
+ }
868
+ function saveKnowledge(filename, content, source, cwd = process.cwd()) {
869
+ ensureKnowledgeDir(cwd);
870
+ const knowledgeDir = getKnowledgeDir(cwd);
871
+ const filePath = join5(knowledgeDir, `${filename}.md`);
872
+ const frontmatter = {
873
+ source,
874
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
875
+ };
876
+ const fileContent = matter2.stringify(content, frontmatter);
877
+ writeFileSync4(filePath, fileContent);
878
+ }
879
+ function loadGuide(cwd = process.cwd()) {
880
+ const knowledgeDir = getKnowledgeDir(cwd);
881
+ const guidePath = join5(knowledgeDir, GUIDE_FILE);
882
+ if (!existsSync5(guidePath)) {
883
+ return null;
884
+ }
885
+ const fileContent = readFileSync4(guidePath, "utf-8");
886
+ const { content } = matter2(fileContent);
887
+ return content.trim();
888
+ }
889
+ function buildAvailableKnowledgeSection(cwd = process.cwd()) {
890
+ const files = listKnowledgeFiles(cwd);
891
+ if (files.length === 0) {
892
+ return "";
893
+ }
894
+ const parts = [];
895
+ parts.push("## Available Knowledge");
896
+ parts.push("");
897
+ parts.push("\uB2E4\uC74C \uC9C0\uC2DD \uD30C\uC77C\uB4E4\uC774 \uC774 \uD504\uB85C\uC81D\uD2B8\uC5D0 \uC800\uC7A5\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4:");
898
+ for (const file of files) {
899
+ parts.push(`- ${file}`);
900
+ }
901
+ parts.push("");
902
+ parts.push("\uD604\uC7AC \uD0DC\uC2A4\uD06C\uC5D0 \uCC38\uACE0\uD560 \uC9C0\uC2DD\uC774 \uC788\uB2E4\uBA74 JSON \uCD9C\uB825\uC758 `knowledgeRequests` \uD544\uB4DC\uC5D0 \uD30C\uC77C\uBA85\uC744 \uCD94\uAC00\uD558\uC138\uC694.");
903
+ parts.push("");
904
+ return parts.join("\n");
905
+ }
906
+
907
+ // src/core/prompt-builder.ts
807
908
  function buildContextPrompt(params) {
808
909
  const cwd = params.cwd || process.cwd();
809
910
  const isFirstStep = params.stepIndex === 1;
@@ -826,8 +927,8 @@ function buildContextPrompt(params) {
826
927
  } else if (params.previousOutput) {
827
928
  parts.push("## Previous Step Output");
828
929
  parts.push("");
829
- if (existsSync5(params.previousOutput)) {
830
- parts.push(readFileSync4(params.previousOutput, "utf-8"));
930
+ if (existsSync6(params.previousOutput)) {
931
+ parts.push(readFileSync5(params.previousOutput, "utf-8"));
831
932
  }
832
933
  parts.push("");
833
934
  }
@@ -878,8 +979,8 @@ function buildExplorePrompt(params) {
878
979
  } else if (params.previousOutput) {
879
980
  parts.push("## Previous Step Output");
880
981
  parts.push("");
881
- if (existsSync5(params.previousOutput)) {
882
- parts.push(readFileSync4(params.previousOutput, "utf-8"));
982
+ if (existsSync6(params.previousOutput)) {
983
+ parts.push(readFileSync5(params.previousOutput, "utf-8"));
883
984
  }
884
985
  parts.push("");
885
986
  }
@@ -893,6 +994,13 @@ function buildExplorePrompt(params) {
893
994
  parts.push("");
894
995
  parts.push("**Key difference from context phase:** You must READ file contents, not just list them.");
895
996
  parts.push("");
997
+ const config = loadConfig(cwd);
998
+ if (config.knowledge?.inject !== false) {
999
+ const knowledgeSection = buildAvailableKnowledgeSection(cwd);
1000
+ if (knowledgeSection) {
1001
+ parts.push(knowledgeSection);
1002
+ }
1003
+ }
896
1004
  parts.push("## Output Format");
897
1005
  parts.push("");
898
1006
  parts.push("Output a JSON object with this structure:");
@@ -918,7 +1026,8 @@ function buildExplorePrompt(params) {
918
1026
  parts.push(' "dependencies": [');
919
1027
  parts.push(' "gray-matter for frontmatter parsing",');
920
1028
  parts.push(' "Internal: uses ../core/config.js"');
921
- parts.push(" ]");
1029
+ parts.push(" ],");
1030
+ parts.push(' "knowledgeRequests": ["filename1", "filename2"]');
922
1031
  parts.push("}");
923
1032
  parts.push("```");
924
1033
  parts.push("");
@@ -943,6 +1052,16 @@ function buildClarifyPrompt(params) {
943
1052
  parts.push("");
944
1053
  parts.push(params.gatheredContext);
945
1054
  parts.push("");
1055
+ if (params.loadedKnowledge && params.loadedKnowledge.length > 0) {
1056
+ parts.push("## Requested Knowledge");
1057
+ parts.push("");
1058
+ for (const entry of params.loadedKnowledge) {
1059
+ parts.push(`### ${entry.filename}`);
1060
+ parts.push("");
1061
+ parts.push(entry.content);
1062
+ parts.push("");
1063
+ }
1064
+ }
946
1065
  const hasPreviousDecisions = params.previousDecisions && params.previousDecisions.length > 0;
947
1066
  if (hasPreviousDecisions) {
948
1067
  parts.push("## Previous Decisions (All Resolved)");
@@ -1043,16 +1162,16 @@ function buildGeneratePrompt(params) {
1043
1162
  const outputsDir = getOutputsDir(cwd);
1044
1163
  const isFirstStep = params.stepIndex === 1;
1045
1164
  const prevStep = params.stepIndex > 1 ? config.steps[params.stepIndex - 2] : null;
1046
- const templatePath = join5(stepsDir, params.step, "template.md");
1047
- const outputPath = join5(outputsDir, params.taskId, `${params.step}.md`);
1048
- const template = existsSync5(templatePath) ? readFileSync4(templatePath, "utf-8") : null;
1165
+ const templatePath = join6(stepsDir, params.step, "template.md");
1166
+ const outputPath = join6(outputsDir, params.taskId, `${params.step}.md`);
1167
+ const template = existsSync6(templatePath) ? readFileSync5(templatePath, "utf-8") : null;
1049
1168
  let previousSpec = null;
1050
1169
  if (prevStep) {
1051
- const prevPath = join5(outputsDir, params.taskId, `${prevStep}.md`);
1052
- if (existsSync5(prevPath)) {
1170
+ const prevPath = join6(outputsDir, params.taskId, `${prevStep}.md`);
1171
+ if (existsSync6(prevPath)) {
1053
1172
  previousSpec = {
1054
1173
  step: prevStep,
1055
- content: readFileSync4(prevPath, "utf-8")
1174
+ content: readFileSync5(prevPath, "utf-8")
1056
1175
  };
1057
1176
  }
1058
1177
  }
@@ -1130,16 +1249,16 @@ function buildExecutePrompt(params) {
1130
1249
  const outputsDir = getOutputsDir(cwd);
1131
1250
  const isFirstStep = params.stepIndex === 1;
1132
1251
  const prevStep = params.stepIndex > 1 ? config.steps[params.stepIndex - 2] : null;
1133
- const templatePath = join5(stepsDir, params.step, "template.md");
1134
- const outputPath = join5(outputsDir, params.taskId, `${params.step}.md`);
1135
- const template = existsSync5(templatePath) ? readFileSync4(templatePath, "utf-8") : null;
1252
+ const templatePath = join6(stepsDir, params.step, "template.md");
1253
+ const outputPath = join6(outputsDir, params.taskId, `${params.step}.md`);
1254
+ const template = existsSync6(templatePath) ? readFileSync5(templatePath, "utf-8") : null;
1136
1255
  let previousSpec = null;
1137
1256
  if (prevStep) {
1138
- const prevPath = join5(outputsDir, params.taskId, `${prevStep}.md`);
1139
- if (existsSync5(prevPath)) {
1257
+ const prevPath = join6(outputsDir, params.taskId, `${prevStep}.md`);
1258
+ if (existsSync6(prevPath)) {
1140
1259
  previousSpec = {
1141
1260
  step: prevStep,
1142
- content: readFileSync4(prevPath, "utf-8")
1261
+ content: readFileSync5(prevPath, "utf-8")
1143
1262
  };
1144
1263
  }
1145
1264
  }
@@ -1194,6 +1313,16 @@ function buildExecutePrompt(params) {
1194
1313
  }
1195
1314
  parts.push("");
1196
1315
  }
1316
+ if (params.loadedKnowledge && params.loadedKnowledge.length > 0) {
1317
+ parts.push("### Referenced Knowledge");
1318
+ parts.push("");
1319
+ for (const entry of params.loadedKnowledge) {
1320
+ parts.push(`#### ${entry.filename}`);
1321
+ parts.push("");
1322
+ parts.push(entry.content);
1323
+ parts.push("");
1324
+ }
1325
+ }
1197
1326
  if (params.answers && params.answers.length > 0) {
1198
1327
  parts.push("## User Answers");
1199
1328
  parts.push("");
@@ -1255,9 +1384,9 @@ function buildExecutePrompt(params) {
1255
1384
  function buildVerifyPrompt(params) {
1256
1385
  const cwd = params.cwd || process.cwd();
1257
1386
  const stepsDir = getStepsDir(cwd);
1258
- const templatePath = join5(stepsDir, params.step, "template.md");
1259
- const template = existsSync5(templatePath) ? readFileSync4(templatePath, "utf-8") : "";
1260
- const document = existsSync5(params.documentPath) ? readFileSync4(params.documentPath, "utf-8") : "";
1387
+ const templatePath = join6(stepsDir, params.step, "template.md");
1388
+ const template = existsSync6(templatePath) ? readFileSync5(templatePath, "utf-8") : "";
1389
+ const document = existsSync6(params.documentPath) ? readFileSync5(params.documentPath, "utf-8") : "";
1261
1390
  const parts = [];
1262
1391
  parts.push("# Verify Phase - Self-Validation");
1263
1392
  parts.push("");
@@ -1361,33 +1490,33 @@ var Orchestrator = class {
1361
1490
  return getOutputsDir(this.cwd);
1362
1491
  }
1363
1492
  getStatePath(taskId) {
1364
- return join6(this.getOutputPath(), taskId, ".state.json");
1493
+ return join7(this.getOutputPath(), taskId, ".state.json");
1365
1494
  }
1366
1495
  getSpecPath(taskId, step) {
1367
- return join6(this.getOutputPath(), taskId, `${step}.md`);
1496
+ return join7(this.getOutputPath(), taskId, `${step}.md`);
1368
1497
  }
1369
1498
  getStepTemplatePath(step) {
1370
- return join6(getStepsDir(this.cwd), step, "template.md");
1499
+ return join7(getStepsDir(this.cwd), step, "template.md");
1371
1500
  }
1372
1501
  // ===========================================================================
1373
1502
  // State Management
1374
1503
  // ===========================================================================
1375
1504
  loadState(taskId) {
1376
1505
  const statePath = this.getStatePath(taskId);
1377
- if (!existsSync6(statePath)) {
1506
+ if (!existsSync7(statePath)) {
1378
1507
  return null;
1379
1508
  }
1380
- const data = JSON.parse(readFileSync5(statePath, "utf-8"));
1509
+ const data = JSON.parse(readFileSync6(statePath, "utf-8"));
1381
1510
  return data;
1382
1511
  }
1383
1512
  saveState(state) {
1384
1513
  state.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1385
1514
  const statePath = this.getStatePath(state.taskId);
1386
1515
  const dir = dirname2(statePath);
1387
- if (!existsSync6(dir)) {
1388
- mkdirSync4(dir, { recursive: true });
1516
+ if (!existsSync7(dir)) {
1517
+ mkdirSync5(dir, { recursive: true });
1389
1518
  }
1390
- writeFileSync4(statePath, JSON.stringify(state, null, 2));
1519
+ writeFileSync5(statePath, JSON.stringify(state, null, 2));
1391
1520
  }
1392
1521
  // ===========================================================================
1393
1522
  // Protocol Response Builders
@@ -1401,8 +1530,8 @@ var Orchestrator = class {
1401
1530
  let previousOutput;
1402
1531
  if (state.stepIndex > 1) {
1403
1532
  const prevStep = steps[state.stepIndex - 2];
1404
- const prevPath = join6(outputPath, state.taskId, `${prevStep}.md`);
1405
- if (existsSync6(prevPath)) {
1533
+ const prevPath = join7(outputPath, state.taskId, `${prevStep}.md`);
1534
+ if (existsSync7(prevPath)) {
1406
1535
  previousOutput = prevPath;
1407
1536
  }
1408
1537
  }
@@ -1439,6 +1568,7 @@ var Orchestrator = class {
1439
1568
  step: state.currentStep,
1440
1569
  description: state.description,
1441
1570
  gatheredContext: state.exploreOutput ? JSON.stringify(state.exploreOutput) : "",
1571
+ loadedKnowledge: state.loadedKnowledge,
1442
1572
  previousDecisions: state.decisionHistory,
1443
1573
  previousQA: state.qaHistory,
1444
1574
  // Legacy support
@@ -1467,13 +1597,13 @@ var Orchestrator = class {
1467
1597
  let previousOutput;
1468
1598
  if (state.stepIndex > 1) {
1469
1599
  const prevStep = steps[state.stepIndex - 2];
1470
- const prevPath = join6(outputPath, state.taskId, `${prevStep}.md`);
1471
- if (existsSync6(prevPath)) {
1600
+ const prevPath = join7(outputPath, state.taskId, `${prevStep}.md`);
1601
+ if (existsSync7(prevPath)) {
1472
1602
  previousOutput = prevPath;
1473
1603
  }
1474
1604
  }
1475
1605
  const templatePath = this.getStepTemplatePath(state.currentStep);
1476
- const hasTemplate = existsSync6(templatePath);
1606
+ const hasTemplate = existsSync7(templatePath);
1477
1607
  let verifyFeedback;
1478
1608
  if (state.verifyOutput && !state.verifyOutput.passed && state.verifyAttempts && state.verifyAttempts > 1) {
1479
1609
  const issues = state.verifyOutput.issues.filter((i) => i.severity === "error").map((i) => `- [${i.category}] ${i.description}${i.suggestion ? ` \u2192 ${i.suggestion}` : ""}`).join("\n");
@@ -1495,6 +1625,7 @@ ${issues}`;
1495
1625
  stepIndex: state.stepIndex,
1496
1626
  totalSteps: state.totalSteps,
1497
1627
  exploreOutput,
1628
+ loadedKnowledge: state.loadedKnowledge,
1498
1629
  answers: state.answers,
1499
1630
  revisionFeedback: state.revisionFeedback,
1500
1631
  verifyFeedback,
@@ -1514,7 +1645,7 @@ ${issues}`;
1514
1645
  context: {
1515
1646
  template: hasTemplate ? templatePath : void 0,
1516
1647
  previousOutput,
1517
- output: join6(outputPath, state.taskId, `${state.currentStep}.md`),
1648
+ output: join7(outputPath, state.taskId, `${state.currentStep}.md`),
1518
1649
  revisionFeedback: state.revisionFeedback,
1519
1650
  verifyFeedback
1520
1651
  },
@@ -1527,8 +1658,8 @@ ${issues}`;
1527
1658
  responsePhaseVerify(state) {
1528
1659
  const outputPath = this.getOutputPath();
1529
1660
  const templatePath = this.getStepTemplatePath(state.currentStep);
1530
- const hasTemplate = existsSync6(templatePath);
1531
- const documentPath = join6(outputPath, state.taskId, `${state.currentStep}.md`);
1661
+ const hasTemplate = existsSync7(templatePath);
1662
+ const documentPath = join7(outputPath, state.taskId, `${state.currentStep}.md`);
1532
1663
  const prompt = buildVerifyPrompt({
1533
1664
  taskId: state.taskId,
1534
1665
  step: state.currentStep,
@@ -1582,7 +1713,7 @@ ${issues}`;
1582
1713
  step: state.currentStep,
1583
1714
  stepIndex: state.stepIndex,
1584
1715
  totalSteps: state.totalSteps,
1585
- specPath: join6(outputPath, state.taskId, `${state.currentStep}.md`),
1716
+ specPath: join7(outputPath, state.taskId, `${state.currentStep}.md`),
1586
1717
  options: ["approve", "revise", "reject", "stop"],
1587
1718
  onComplete: {
1588
1719
  approve: `npx spets orchestrate approve ${state.taskId}`,
@@ -1592,13 +1723,49 @@ ${issues}`;
1592
1723
  }
1593
1724
  };
1594
1725
  }
1726
+ /**
1727
+ * Checkpoint: Knowledge extraction after workflow completion
1728
+ */
1729
+ responseCheckpointKnowledge(state) {
1730
+ const guide = loadGuide(this.cwd);
1731
+ const suggestedKnowledge = [];
1732
+ if (state.decisionHistory && state.decisionHistory.length > 0) {
1733
+ for (const entry of state.decisionHistory) {
1734
+ const selectedOption = entry.decision.options.find((o) => o.id === entry.answer.selectedOptionId);
1735
+ if (selectedOption && entry.answer.selectedOptionId !== "ai") {
1736
+ const filename = `decision--${entry.decision.id}-${entry.decision.decision.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 30)}`;
1737
+ const content = `**Decision:** ${entry.decision.decision}
1738
+
1739
+ **Selected:** ${selectedOption.label}
1740
+
1741
+ **Why:** ${entry.decision.why}
1742
+
1743
+ ${entry.answer.customInput ? `**User note:** ${entry.answer.customInput}` : ""}`;
1744
+ suggestedKnowledge.push({
1745
+ filename,
1746
+ content,
1747
+ reason: "\uC6CC\uD06C\uD50C\uB85C\uC6B0\uC5D0\uC11C \uC0AC\uC6A9\uC790\uAC00 \uACB0\uC815\uD55C \uC120\uD0DD"
1748
+ });
1749
+ }
1750
+ }
1751
+ }
1752
+ return {
1753
+ type: "checkpoint",
1754
+ checkpoint: "knowledge",
1755
+ taskId: state.taskId,
1756
+ guide,
1757
+ suggestedKnowledge,
1758
+ onComplete: `npx spets orchestrate knowledge-save ${state.taskId} '<entries_json>'`,
1759
+ onSkip: `npx spets orchestrate knowledge-skip ${state.taskId}`
1760
+ };
1761
+ }
1595
1762
  responseComplete(state, status) {
1596
1763
  const steps = this.getSteps();
1597
1764
  const outputPath = this.getOutputPath();
1598
1765
  const outputs = [];
1599
1766
  for (let i = 0; i < state.stepIndex; i++) {
1600
- const specPath = join6(outputPath, state.taskId, `${steps[i]}.md`);
1601
- if (existsSync6(specPath)) {
1767
+ const specPath = join7(outputPath, state.taskId, `${steps[i]}.md`);
1768
+ if (existsSync7(specPath)) {
1602
1769
  outputs.push(specPath);
1603
1770
  }
1604
1771
  }
@@ -1656,6 +1823,12 @@ ${issues}`;
1656
1823
  return this.responseError(`No workflow found: ${taskId}`, taskId);
1657
1824
  }
1658
1825
  state.exploreOutput = exploreOutput;
1826
+ if (exploreOutput.knowledgeRequests && exploreOutput.knowledgeRequests.length > 0) {
1827
+ const entries = loadKnowledgeFiles(exploreOutput.knowledgeRequests, this.cwd);
1828
+ if (entries.length > 0) {
1829
+ state.loadedKnowledge = entries;
1830
+ }
1831
+ }
1659
1832
  state.status = "phase_clarify";
1660
1833
  state.phase = "clarify";
1661
1834
  this.saveState(state);
@@ -1717,7 +1890,7 @@ ${issues}`;
1717
1890
  return this.responseError(`No workflow found: ${taskId}`, taskId);
1718
1891
  }
1719
1892
  const specPath = this.getSpecPath(taskId, state.currentStep);
1720
- if (!existsSync6(specPath)) {
1893
+ if (!existsSync7(specPath)) {
1721
1894
  return this.responseError(`Document not found: ${specPath}`, taskId, state.currentStep);
1722
1895
  }
1723
1896
  state.status = "phase_verify";
@@ -1766,12 +1939,12 @@ ${issues}`;
1766
1939
  }
1767
1940
  const steps = this.getSteps();
1768
1941
  const specPath = this.getSpecPath(taskId, state.currentStep);
1769
- if (existsSync6(specPath)) {
1770
- const content = readFileSync5(specPath, "utf-8");
1771
- const { content: body, data } = matter2(content);
1942
+ if (existsSync7(specPath)) {
1943
+ const content = readFileSync6(specPath, "utf-8");
1944
+ const { content: body, data } = matter3(content);
1772
1945
  data.status = "approved";
1773
1946
  data.updated_at = (/* @__PURE__ */ new Date()).toISOString();
1774
- writeFileSync4(specPath, matter2.stringify(body, data));
1947
+ writeFileSync5(specPath, matter3.stringify(body, data));
1775
1948
  }
1776
1949
  if (state.stepIndex < state.totalSteps) {
1777
1950
  state.currentStep = steps[state.stepIndex];
@@ -1788,11 +1961,49 @@ ${issues}`;
1788
1961
  this.saveState(state);
1789
1962
  return this.responsePhaseExplore(state);
1790
1963
  } else {
1964
+ const config = loadConfig(this.cwd);
1965
+ if (config.knowledge?.enabled) {
1966
+ state.status = "knowledge_pending";
1967
+ this.saveState(state);
1968
+ return this.responseCheckpointKnowledge(state);
1969
+ }
1791
1970
  state.status = "completed";
1792
1971
  this.saveState(state);
1793
1972
  return this.responseComplete(state, "completed");
1794
1973
  }
1795
1974
  }
1975
+ /**
1976
+ * Save knowledge entries and complete workflow
1977
+ */
1978
+ cmdKnowledgeSave(taskId, entries) {
1979
+ const state = this.loadState(taskId);
1980
+ if (!state) {
1981
+ return this.responseError(`No workflow found: ${taskId}`, taskId);
1982
+ }
1983
+ for (const entry of entries) {
1984
+ saveKnowledge(
1985
+ entry.filename,
1986
+ entry.content,
1987
+ { taskId: state.taskId, step: state.currentStep },
1988
+ this.cwd
1989
+ );
1990
+ }
1991
+ state.status = "completed";
1992
+ this.saveState(state);
1993
+ return this.responseComplete(state, "completed");
1994
+ }
1995
+ /**
1996
+ * Skip knowledge extraction and complete workflow
1997
+ */
1998
+ cmdKnowledgeSkip(taskId) {
1999
+ const state = this.loadState(taskId);
2000
+ if (!state) {
2001
+ return this.responseError(`No workflow found: ${taskId}`, taskId);
2002
+ }
2003
+ state.status = "completed";
2004
+ this.saveState(state);
2005
+ return this.responseComplete(state, "completed");
2006
+ }
1796
2007
  /**
1797
2008
  * Request revision with feedback - goes back to execute phase
1798
2009
  */
@@ -1818,12 +2029,12 @@ ${issues}`;
1818
2029
  return this.responseError(`No workflow found: ${taskId}`, taskId);
1819
2030
  }
1820
2031
  const specPath = this.getSpecPath(taskId, state.currentStep);
1821
- if (existsSync6(specPath)) {
1822
- const content = readFileSync5(specPath, "utf-8");
1823
- const { content: body, data } = matter2(content);
2032
+ if (existsSync7(specPath)) {
2033
+ const content = readFileSync6(specPath, "utf-8");
2034
+ const { content: body, data } = matter3(content);
1824
2035
  data.status = "rejected";
1825
2036
  data.updated_at = (/* @__PURE__ */ new Date()).toISOString();
1826
- writeFileSync4(specPath, matter2.stringify(body, data));
2037
+ writeFileSync5(specPath, matter3.stringify(body, data));
1827
2038
  }
1828
2039
  state.status = "rejected";
1829
2040
  this.saveState(state);
@@ -1888,6 +2099,8 @@ ${issues}`;
1888
2099
  return this.responsePhaseVerify(state);
1889
2100
  case "approve_pending":
1890
2101
  return this.responseCheckpointApprove(state);
2102
+ case "knowledge_pending":
2103
+ return this.responseCheckpointKnowledge(state);
1891
2104
  case "completed":
1892
2105
  case "stopped":
1893
2106
  case "rejected":
@@ -1899,9 +2112,9 @@ ${issues}`;
1899
2112
  };
1900
2113
 
1901
2114
  // src/core/step-executor.ts
1902
- import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
1903
- import { join as join7 } from "path";
1904
- import matter3 from "gray-matter";
2115
+ import { existsSync as existsSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
2116
+ import { join as join8 } from "path";
2117
+ import matter4 from "gray-matter";
1905
2118
  var StepExecutor = class {
1906
2119
  adapter;
1907
2120
  config;
@@ -1992,6 +2205,7 @@ var StepExecutor = class {
1992
2205
  step,
1993
2206
  description: context.description,
1994
2207
  gatheredContext: context.gatheredContext,
2208
+ previousDecisions: context.previousDecisions,
1995
2209
  previousQA: context.previousQA,
1996
2210
  cwd: this.cwd
1997
2211
  };
@@ -2048,7 +2262,7 @@ var StepExecutor = class {
2048
2262
  };
2049
2263
  const { prompt, outputPath } = buildGeneratePrompt(params);
2050
2264
  await this.adapter.ai.execute({ prompt, outputPath });
2051
- if (!existsSync7(outputPath)) {
2265
+ if (!existsSync8(outputPath)) {
2052
2266
  throw new Error(`AI did not create document at ${outputPath}`);
2053
2267
  }
2054
2268
  this.adapter.io.notify(`Document created: ${outputPath}`, "success");
@@ -2087,7 +2301,7 @@ var StepExecutor = class {
2087
2301
  };
2088
2302
  const { prompt, outputPath } = buildExecutePrompt(params);
2089
2303
  await this.adapter.ai.execute({ prompt, outputPath });
2090
- if (!existsSync7(outputPath)) {
2304
+ if (!existsSync8(outputPath)) {
2091
2305
  throw new Error(`AI did not create document at ${outputPath}`);
2092
2306
  }
2093
2307
  this.adapter.io.notify(`Document created: ${outputPath}`, "success");
@@ -2109,8 +2323,8 @@ var StepExecutor = class {
2109
2323
  const attempts = context.verifyAttempts || 1;
2110
2324
  this.adapter.io.notify(`Phase 4/5: Verifying document for ${step} (attempt ${attempts}/3)`, "info");
2111
2325
  const outputsDir = getOutputsDir(this.cwd);
2112
- const documentPath = join7(outputsDir, context.taskId, `${step}.md`);
2113
- if (!existsSync7(documentPath)) {
2326
+ const documentPath = join8(outputsDir, context.taskId, `${step}.md`);
2327
+ if (!existsSync8(documentPath)) {
2114
2328
  throw new Error(`Document not found: ${documentPath}`);
2115
2329
  }
2116
2330
  const params = {
@@ -2151,8 +2365,8 @@ var StepExecutor = class {
2151
2365
  async executeReviewPhase(step, context) {
2152
2366
  this.adapter.io.notify(`Phase 4/4: Review for ${step}`, "info");
2153
2367
  const outputsDir = getOutputsDir(this.cwd);
2154
- const outputPath = join7(outputsDir, context.taskId, `${step}.md`);
2155
- if (!existsSync7(outputPath)) {
2368
+ const outputPath = join8(outputsDir, context.taskId, `${step}.md`);
2369
+ if (!existsSync8(outputPath)) {
2156
2370
  throw new Error(`Document not found: ${outputPath}`);
2157
2371
  }
2158
2372
  const approval = await this.adapter.io.approve(
@@ -2337,17 +2551,17 @@ var StepExecutor = class {
2337
2551
  * Update document status in frontmatter
2338
2552
  */
2339
2553
  updateDocumentStatus(docPath, status) {
2340
- const content = readFileSync6(docPath, "utf-8");
2341
- const { content: body, data } = matter3(content);
2554
+ const content = readFileSync7(docPath, "utf-8");
2555
+ const { content: body, data } = matter4(content);
2342
2556
  data.status = status;
2343
2557
  data.updated_at = (/* @__PURE__ */ new Date()).toISOString();
2344
- writeFileSync5(docPath, matter3.stringify(body, data));
2558
+ writeFileSync6(docPath, matter4.stringify(body, data));
2345
2559
  }
2346
2560
  };
2347
2561
 
2348
2562
  // src/adapters/cli.ts
2349
2563
  import { spawn, spawnSync } from "child_process";
2350
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync6, existsSync as existsSync8, mkdirSync as mkdirSync5 } from "fs";
2564
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync7, existsSync as existsSync9, mkdirSync as mkdirSync6 } from "fs";
2351
2565
  import { dirname as dirname3 } from "path";
2352
2566
  import { input, select, confirm } from "@inquirer/prompts";
2353
2567
  var CLIAIAdapter = class {
@@ -2360,13 +2574,13 @@ var CLIAIAdapter = class {
2360
2574
  \u{1F4DD} Generating...`);
2361
2575
  if (params.outputPath) {
2362
2576
  const outputDir = dirname3(params.outputPath);
2363
- if (!existsSync8(outputDir)) {
2364
- mkdirSync5(outputDir, { recursive: true });
2577
+ if (!existsSync9(outputDir)) {
2578
+ mkdirSync6(outputDir, { recursive: true });
2365
2579
  }
2366
2580
  }
2367
2581
  const stdout = await this.callClaude(params.prompt);
2368
- if (params.outputPath && existsSync8(params.outputPath)) {
2369
- return readFileSync7(params.outputPath, "utf-8");
2582
+ if (params.outputPath && existsSync9(params.outputPath)) {
2583
+ return readFileSync8(params.outputPath, "utf-8");
2370
2584
  }
2371
2585
  return stdout;
2372
2586
  }
@@ -2443,14 +2657,14 @@ var CLIAIAdapter = class {
2443
2657
  try {
2444
2658
  if (p.outputPath) {
2445
2659
  const outputDir = dirname3(p.outputPath);
2446
- if (!existsSync8(outputDir)) {
2447
- mkdirSync5(outputDir, { recursive: true });
2660
+ if (!existsSync9(outputDir)) {
2661
+ mkdirSync6(outputDir, { recursive: true });
2448
2662
  }
2449
2663
  }
2450
2664
  await this.callClaude(p.prompt, false);
2451
2665
  let result = "";
2452
- if (p.outputPath && existsSync8(p.outputPath)) {
2453
- result = readFileSync7(p.outputPath, "utf-8");
2666
+ if (p.outputPath && existsSync9(p.outputPath)) {
2667
+ result = readFileSync8(p.outputPath, "utf-8");
2454
2668
  }
2455
2669
  completed++;
2456
2670
  results.set(id, { id, success: true, result });
@@ -2540,8 +2754,8 @@ Context: ${question.context}`);
2540
2754
  return answers;
2541
2755
  }
2542
2756
  async approve(specPath, stepName, stepIndex, totalSteps) {
2543
- if (existsSync8(specPath)) {
2544
- const doc = readFileSync7(specPath, "utf-8");
2757
+ if (existsSync9(specPath)) {
2758
+ const doc = readFileSync8(specPath, "utf-8");
2545
2759
  console.log("\n" + "=".repeat(60));
2546
2760
  console.log(`\u{1F4C4} ${stepName} Document (Step ${stepIndex}/${totalSteps})`);
2547
2761
  console.log("=".repeat(60));
@@ -2586,17 +2800,17 @@ Context: ${question.context}`);
2586
2800
  };
2587
2801
  var CLISystemAdapter = class {
2588
2802
  readFile(path) {
2589
- return readFileSync7(path, "utf-8");
2803
+ return readFileSync8(path, "utf-8");
2590
2804
  }
2591
2805
  writeFile(path, content) {
2592
2806
  const dir = dirname3(path);
2593
- if (!existsSync8(dir)) {
2594
- mkdirSync5(dir, { recursive: true });
2807
+ if (!existsSync9(dir)) {
2808
+ mkdirSync6(dir, { recursive: true });
2595
2809
  }
2596
- writeFileSync6(path, content);
2810
+ writeFileSync7(path, content);
2597
2811
  }
2598
2812
  fileExists(path) {
2599
- return existsSync8(path);
2813
+ return existsSync9(path);
2600
2814
  }
2601
2815
  exec(command) {
2602
2816
  try {
@@ -2627,7 +2841,7 @@ function createCLIAdapter(claudeCommand = "claude") {
2627
2841
 
2628
2842
  // src/adapters/github.ts
2629
2843
  import { spawn as spawn2, spawnSync as spawnSync2 } from "child_process";
2630
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync7, existsSync as existsSync9, mkdirSync as mkdirSync6 } from "fs";
2844
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync8, existsSync as existsSync10, mkdirSync as mkdirSync7 } from "fs";
2631
2845
  import { dirname as dirname4 } from "path";
2632
2846
  var GitHubAIAdapter = class {
2633
2847
  claudeCommand;
@@ -2639,13 +2853,13 @@ var GitHubAIAdapter = class {
2639
2853
  \u{1F4DD} Generating...`);
2640
2854
  if (params.outputPath) {
2641
2855
  const outputDir = dirname4(params.outputPath);
2642
- if (!existsSync9(outputDir)) {
2643
- mkdirSync6(outputDir, { recursive: true });
2856
+ if (!existsSync10(outputDir)) {
2857
+ mkdirSync7(outputDir, { recursive: true });
2644
2858
  }
2645
2859
  }
2646
2860
  const stdout = await this.callClaude(params.prompt);
2647
- if (params.outputPath && existsSync9(params.outputPath)) {
2648
- return readFileSync8(params.outputPath, "utf-8");
2861
+ if (params.outputPath && existsSync10(params.outputPath)) {
2862
+ return readFileSync9(params.outputPath, "utf-8");
2649
2863
  }
2650
2864
  return stdout;
2651
2865
  }
@@ -2697,14 +2911,14 @@ var GitHubAIAdapter = class {
2697
2911
  try {
2698
2912
  if (p.outputPath) {
2699
2913
  const outputDir = dirname4(p.outputPath);
2700
- if (!existsSync9(outputDir)) {
2701
- mkdirSync6(outputDir, { recursive: true });
2914
+ if (!existsSync10(outputDir)) {
2915
+ mkdirSync7(outputDir, { recursive: true });
2702
2916
  }
2703
2917
  }
2704
2918
  await this.callClaude(p.prompt);
2705
2919
  let result = "";
2706
- if (p.outputPath && existsSync9(p.outputPath)) {
2707
- result = readFileSync8(p.outputPath, "utf-8");
2920
+ if (p.outputPath && existsSync10(p.outputPath)) {
2921
+ result = readFileSync9(p.outputPath, "utf-8");
2708
2922
  }
2709
2923
  completed++;
2710
2924
  console.log(`\u2713 [${completed}/${total}] ${id}`);
@@ -2757,7 +2971,7 @@ var GitHubIOAdapter = class {
2757
2971
  return [];
2758
2972
  }
2759
2973
  async approve(specPath, stepName, stepIndex, totalSteps) {
2760
- const doc = existsSync9(specPath) ? readFileSync8(specPath, "utf-8") : "";
2974
+ const doc = existsSync10(specPath) ? readFileSync9(specPath, "utf-8") : "";
2761
2975
  const comment = this.formatApprovalComment(doc, stepName, stepIndex, totalSteps, specPath);
2762
2976
  await this.postComment(comment);
2763
2977
  console.log("\n\u23F8\uFE0F Approval request posted to GitHub.");
@@ -2843,11 +3057,13 @@ var GitHubIOAdapter = class {
2843
3057
  lines.push(`> ${d.why}`);
2844
3058
  lines.push("");
2845
3059
  lines.push("**Options:**");
3060
+ let optionNumber = 1;
2846
3061
  for (const opt of d.options) {
2847
3062
  if (opt.id === "ai") {
2848
- lines.push(`- \`${opt.id}\`: ${opt.label} \u2192 recommends \`${opt.recommendation}\` (${opt.reason})`);
3063
+ lines.push(`- **Recommended**: ${opt.label} \u2192 recommends \`${opt.recommendation}\` (${opt.reason})`);
2849
3064
  } else {
2850
- lines.push(`- \`${opt.id}\`: ${opt.label}${opt.tradeoffs ? ` - ${opt.tradeoffs}` : ""}`);
3065
+ lines.push(`- **Option ${optionNumber}**: ${opt.label}${opt.tradeoffs ? ` - ${opt.tradeoffs}` : ""}`);
3066
+ optionNumber++;
2851
3067
  }
2852
3068
  }
2853
3069
  lines.push("");
@@ -2928,17 +3144,17 @@ var GitHubIOAdapter = class {
2928
3144
  };
2929
3145
  var GitHubSystemAdapter = class {
2930
3146
  readFile(path) {
2931
- return readFileSync8(path, "utf-8");
3147
+ return readFileSync9(path, "utf-8");
2932
3148
  }
2933
3149
  writeFile(path, content) {
2934
3150
  const dir = dirname4(path);
2935
- if (!existsSync9(dir)) {
2936
- mkdirSync6(dir, { recursive: true });
3151
+ if (!existsSync10(dir)) {
3152
+ mkdirSync7(dir, { recursive: true });
2937
3153
  }
2938
- writeFileSync7(path, content);
3154
+ writeFileSync8(path, content);
2939
3155
  }
2940
3156
  fileExists(path) {
2941
- return existsSync9(path);
3157
+ return existsSync10(path);
2942
3158
  }
2943
3159
  exec(command) {
2944
3160
  try {
@@ -2969,8 +3185,193 @@ function createGitHubAdapter(config, claudeCommand = "claude") {
2969
3185
 
2970
3186
  // src/adapters/codex.ts
2971
3187
  import { spawn as spawn3 } from "child_process";
2972
- import { readFileSync as readFileSync9, existsSync as existsSync10, mkdirSync as mkdirSync7 } from "fs";
3188
+ import { readFileSync as readFileSync10, existsSync as existsSync11, mkdirSync as mkdirSync8 } from "fs";
2973
3189
  import { dirname as dirname5 } from "path";
3190
+
3191
+ // src/dashboard/client.ts
3192
+ var DashboardClient = class {
3193
+ cwd;
3194
+ config = null;
3195
+ constructor(cwd = process.cwd()) {
3196
+ this.cwd = cwd;
3197
+ }
3198
+ /**
3199
+ * Check if spets is initialized in the working directory
3200
+ */
3201
+ isInitialized() {
3202
+ return spetsExists(this.cwd);
3203
+ }
3204
+ /**
3205
+ * Get config, loading if necessary
3206
+ */
3207
+ getConfig() {
3208
+ if (!this.config) {
3209
+ this.config = loadConfig(this.cwd);
3210
+ }
3211
+ return this.config;
3212
+ }
3213
+ /**
3214
+ * List all tasks with summary data
3215
+ */
3216
+ listTasks(options = {}) {
3217
+ const limit = options.limit ?? 20;
3218
+ const allTaskIds = listTasks(this.cwd);
3219
+ const config = this.getConfig();
3220
+ const tasks = [];
3221
+ const taskIds = allTaskIds.slice(0, limit);
3222
+ for (const taskId of taskIds) {
3223
+ const summary = this.getTaskSummary(taskId, config);
3224
+ if (summary) {
3225
+ tasks.push(summary);
3226
+ }
3227
+ }
3228
+ const metadata = {
3229
+ totalTasks: allTaskIds.length,
3230
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
3231
+ truncated: allTaskIds.length > limit,
3232
+ limit
3233
+ };
3234
+ return { tasks, metadata };
3235
+ }
3236
+ /**
3237
+ * Get detailed data for a single task
3238
+ */
3239
+ getTaskDetail(taskId) {
3240
+ const config = this.getConfig();
3241
+ const state = getWorkflowState(taskId, config, this.cwd);
3242
+ const meta = loadTaskMetadata(taskId, this.cwd);
3243
+ if (!state) {
3244
+ return null;
3245
+ }
3246
+ const query = meta?.userQuery || state.userQuery || "(no query)";
3247
+ const status = this.mapWorkflowStatus(state.status);
3248
+ const progress = this.buildProgress(state, config);
3249
+ const steps = config.steps.map((stepName, index) => {
3250
+ const output = state.outputs.get(stepName);
3251
+ const isCurrent = index === state.currentStepIndex;
3252
+ let stepStatus = "pending";
3253
+ if (output) {
3254
+ stepStatus = output.status;
3255
+ }
3256
+ return {
3257
+ name: stepName,
3258
+ index: index + 1,
3259
+ status: stepStatus,
3260
+ isCurrent,
3261
+ outputPath: output ? getOutputPath(taskId, stepName, this.cwd) : void 0
3262
+ };
3263
+ });
3264
+ const task = {
3265
+ taskId,
3266
+ query,
3267
+ status,
3268
+ progress,
3269
+ createdAt: meta?.createdAt || "",
3270
+ steps
3271
+ };
3272
+ return {
3273
+ task,
3274
+ metadata: {
3275
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
3276
+ }
3277
+ };
3278
+ }
3279
+ /**
3280
+ * Build summary for a single task
3281
+ */
3282
+ getTaskSummary(taskId, config) {
3283
+ const state = getWorkflowState(taskId, config, this.cwd);
3284
+ const meta = loadTaskMetadata(taskId, this.cwd);
3285
+ if (!state) {
3286
+ return null;
3287
+ }
3288
+ const query = meta?.userQuery || state.userQuery || "(no query)";
3289
+ const status = this.mapWorkflowStatus(state.status);
3290
+ const progress = this.buildProgress(state, config);
3291
+ return {
3292
+ taskId,
3293
+ query,
3294
+ status,
3295
+ progress,
3296
+ createdAt: meta?.createdAt || ""
3297
+ };
3298
+ }
3299
+ /**
3300
+ * Build progress object from workflow state
3301
+ */
3302
+ buildProgress(state, config) {
3303
+ const totalSteps = config.steps.length;
3304
+ const currentStepIndex = state.currentStepIndex + 1;
3305
+ const completionPercent = Math.round(state.currentStepIndex / totalSteps * 100);
3306
+ return {
3307
+ currentStep: state.currentStepName,
3308
+ currentStepIndex,
3309
+ totalSteps,
3310
+ completionPercent
3311
+ };
3312
+ }
3313
+ /**
3314
+ * Map internal workflow status to dashboard status
3315
+ */
3316
+ mapWorkflowStatus(status) {
3317
+ switch (status) {
3318
+ case "completed":
3319
+ return "completed";
3320
+ case "rejected":
3321
+ return "rejected";
3322
+ case "paused":
3323
+ return "paused";
3324
+ default:
3325
+ return "in_progress";
3326
+ }
3327
+ }
3328
+ /**
3329
+ * Export dashboard data as JSON string
3330
+ */
3331
+ toJSON(options = {}) {
3332
+ const response = this.listTasks(options);
3333
+ return JSON.stringify(response, null, 2);
3334
+ }
3335
+ /**
3336
+ * Export task detail as JSON string
3337
+ */
3338
+ taskToJSON(taskId) {
3339
+ const response = this.getTaskDetail(taskId);
3340
+ if (!response) return null;
3341
+ return JSON.stringify(response, null, 2);
3342
+ }
3343
+ };
3344
+
3345
+ // src/dashboard/renderers/json.ts
3346
+ var JSONDashboardRenderer = class {
3347
+ prettyPrint;
3348
+ constructor(options = {}) {
3349
+ this.prettyPrint = options.prettyPrint ?? true;
3350
+ }
3351
+ /**
3352
+ * Render dashboard response as JSON string
3353
+ */
3354
+ render(response) {
3355
+ return this.stringify(response);
3356
+ }
3357
+ /**
3358
+ * Render task detail as JSON string
3359
+ */
3360
+ renderDetail(response) {
3361
+ return this.stringify(response);
3362
+ }
3363
+ /**
3364
+ * Render any data as JSON string
3365
+ */
3366
+ stringify(data) {
3367
+ if (this.prettyPrint) {
3368
+ return JSON.stringify(data, null, 2);
3369
+ }
3370
+ return JSON.stringify(data);
3371
+ }
3372
+ };
3373
+
3374
+ // src/adapters/codex.ts
2974
3375
  var CodexAIAdapter = class {
2975
3376
  codexCommand;
2976
3377
  constructor(codexCommand = "codex") {
@@ -2981,13 +3382,13 @@ var CodexAIAdapter = class {
2981
3382
  \u{1F4DD} Generating...`);
2982
3383
  if (params.outputPath) {
2983
3384
  const outputDir = dirname5(params.outputPath);
2984
- if (!existsSync10(outputDir)) {
2985
- mkdirSync7(outputDir, { recursive: true });
3385
+ if (!existsSync11(outputDir)) {
3386
+ mkdirSync8(outputDir, { recursive: true });
2986
3387
  }
2987
3388
  }
2988
3389
  const stdout = await this.callCodex(params.prompt);
2989
- if (params.outputPath && existsSync10(params.outputPath)) {
2990
- return readFileSync9(params.outputPath, "utf-8");
3390
+ if (params.outputPath && existsSync11(params.outputPath)) {
3391
+ return readFileSync10(params.outputPath, "utf-8");
2991
3392
  }
2992
3393
  return stdout;
2993
3394
  }
@@ -3064,14 +3465,14 @@ var CodexAIAdapter = class {
3064
3465
  try {
3065
3466
  if (p.outputPath) {
3066
3467
  const outputDir = dirname5(p.outputPath);
3067
- if (!existsSync10(outputDir)) {
3068
- mkdirSync7(outputDir, { recursive: true });
3468
+ if (!existsSync11(outputDir)) {
3469
+ mkdirSync8(outputDir, { recursive: true });
3069
3470
  }
3070
3471
  }
3071
3472
  await this.callCodex(p.prompt, false);
3072
3473
  let result = "";
3073
- if (p.outputPath && existsSync10(p.outputPath)) {
3074
- result = readFileSync9(p.outputPath, "utf-8");
3474
+ if (p.outputPath && existsSync11(p.outputPath)) {
3475
+ result = readFileSync10(p.outputPath, "utf-8");
3075
3476
  }
3076
3477
  completed++;
3077
3478
  results.set(id, { id, success: true, result });
@@ -3099,7 +3500,7 @@ function createCodexAdapter(codexCommand = "codex") {
3099
3500
 
3100
3501
  // src/adapters/opencode.ts
3101
3502
  import { spawn as spawn4 } from "child_process";
3102
- import { readFileSync as readFileSync10, existsSync as existsSync11, mkdirSync as mkdirSync8 } from "fs";
3503
+ import { readFileSync as readFileSync11, existsSync as existsSync12, mkdirSync as mkdirSync9 } from "fs";
3103
3504
  import { dirname as dirname6 } from "path";
3104
3505
  var OpenCodeAIAdapter = class {
3105
3506
  opencodeCommand;
@@ -3111,13 +3512,13 @@ var OpenCodeAIAdapter = class {
3111
3512
  \u{1F4DD} Generating...`);
3112
3513
  if (params.outputPath) {
3113
3514
  const outputDir = dirname6(params.outputPath);
3114
- if (!existsSync11(outputDir)) {
3115
- mkdirSync8(outputDir, { recursive: true });
3515
+ if (!existsSync12(outputDir)) {
3516
+ mkdirSync9(outputDir, { recursive: true });
3116
3517
  }
3117
3518
  }
3118
3519
  const stdout = await this.callOpenCode(params.prompt);
3119
- if (params.outputPath && existsSync11(params.outputPath)) {
3120
- return readFileSync10(params.outputPath, "utf-8");
3520
+ if (params.outputPath && existsSync12(params.outputPath)) {
3521
+ return readFileSync11(params.outputPath, "utf-8");
3121
3522
  }
3122
3523
  return stdout;
3123
3524
  }
@@ -3193,14 +3594,14 @@ var OpenCodeAIAdapter = class {
3193
3594
  try {
3194
3595
  if (p.outputPath) {
3195
3596
  const outputDir = dirname6(p.outputPath);
3196
- if (!existsSync11(outputDir)) {
3197
- mkdirSync8(outputDir, { recursive: true });
3597
+ if (!existsSync12(outputDir)) {
3598
+ mkdirSync9(outputDir, { recursive: true });
3198
3599
  }
3199
3600
  }
3200
3601
  await this.callOpenCode(p.prompt, false);
3201
3602
  let result = "";
3202
- if (p.outputPath && existsSync11(p.outputPath)) {
3203
- result = readFileSync10(p.outputPath, "utf-8");
3603
+ if (p.outputPath && existsSync12(p.outputPath)) {
3604
+ result = readFileSync11(p.outputPath, "utf-8");
3204
3605
  }
3205
3606
  completed++;
3206
3607
  results.set(id, { id, success: true, result });
@@ -3226,6 +3627,135 @@ function createOpenCodeAdapter(opencodeCommand = "opencode") {
3226
3627
  };
3227
3628
  }
3228
3629
 
3630
+ // src/adapters/gemini.ts
3631
+ import { spawn as spawn5 } from "child_process";
3632
+ import { readFileSync as readFileSync12, existsSync as existsSync13, mkdirSync as mkdirSync10 } from "fs";
3633
+ import { dirname as dirname7 } from "path";
3634
+ var GeminiAIAdapter = class {
3635
+ geminiCommand;
3636
+ constructor(geminiCommand = "gemini") {
3637
+ this.geminiCommand = geminiCommand;
3638
+ }
3639
+ async execute(params) {
3640
+ console.log(`
3641
+ \u{1F4DD} Generating...`);
3642
+ if (params.outputPath) {
3643
+ const outputDir = dirname7(params.outputPath);
3644
+ if (!existsSync13(outputDir)) {
3645
+ mkdirSync10(outputDir, { recursive: true });
3646
+ }
3647
+ }
3648
+ const stdout = await this.callGemini(params.prompt);
3649
+ if (params.outputPath && existsSync13(params.outputPath)) {
3650
+ return readFileSync12(params.outputPath, "utf-8");
3651
+ }
3652
+ return stdout;
3653
+ }
3654
+ async callGemini(prompt, showSpinner = true) {
3655
+ return new Promise((resolve, reject) => {
3656
+ const proc = spawn5(this.geminiCommand, [
3657
+ "--prompt",
3658
+ prompt,
3659
+ "--yolo"
3660
+ ], {
3661
+ stdio: ["pipe", "pipe", "pipe"]
3662
+ });
3663
+ let stdout = "";
3664
+ let stderr = "";
3665
+ let spinner;
3666
+ if (showSpinner) {
3667
+ const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
3668
+ let frameIndex = 0;
3669
+ spinner = setInterval(() => {
3670
+ process.stdout.write(`\r${frames[frameIndex++ % frames.length]} Gemini is working...`);
3671
+ }, 100);
3672
+ }
3673
+ const stopSpinner = (success) => {
3674
+ if (spinner) {
3675
+ clearInterval(spinner);
3676
+ process.stdout.write(`\r${success ? "\u2713" : "\u2717"} Gemini ${success ? "done" : "failed"}
3677
+ `);
3678
+ }
3679
+ };
3680
+ proc.stdout.on("data", (data) => {
3681
+ stdout += data.toString();
3682
+ });
3683
+ proc.stderr.on("data", (data) => {
3684
+ stderr += data.toString();
3685
+ });
3686
+ proc.on("close", (code) => {
3687
+ stopSpinner(code === 0);
3688
+ if (code !== 0) {
3689
+ reject(new Error(`Gemini CLI exited with code ${code}: ${stderr}`));
3690
+ } else {
3691
+ resolve(stdout);
3692
+ }
3693
+ });
3694
+ proc.on("error", (err) => {
3695
+ stopSpinner(false);
3696
+ reject(new Error(`Failed to run Gemini CLI: ${err.message}`));
3697
+ });
3698
+ });
3699
+ }
3700
+ /**
3701
+ * Execute multiple prompts in parallel with shared progress display
3702
+ */
3703
+ async executeParallel(params) {
3704
+ const total = params.length;
3705
+ let completed = 0;
3706
+ const results = /* @__PURE__ */ new Map();
3707
+ const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
3708
+ let frameIndex = 0;
3709
+ const spinner = setInterval(() => {
3710
+ const progress = `${completed}/${total}`;
3711
+ process.stdout.write(`\r${frames[frameIndex++ % frames.length]} Generating sections in parallel... [${progress}]`);
3712
+ }, 100);
3713
+ const stopSpinner = (success) => {
3714
+ clearInterval(spinner);
3715
+ const status = success ? "\u2713" : "\u2717";
3716
+ const message = success ? `All ${total} sections generated` : `Generation completed with errors`;
3717
+ process.stdout.write(`\r${status} ${message}
3718
+ `);
3719
+ };
3720
+ await Promise.all(
3721
+ params.map(async (p) => {
3722
+ const id = p.id || p.outputPath;
3723
+ try {
3724
+ if (p.outputPath) {
3725
+ const outputDir = dirname7(p.outputPath);
3726
+ if (!existsSync13(outputDir)) {
3727
+ mkdirSync10(outputDir, { recursive: true });
3728
+ }
3729
+ }
3730
+ await this.callGemini(p.prompt, false);
3731
+ let result = "";
3732
+ if (p.outputPath && existsSync13(p.outputPath)) {
3733
+ result = readFileSync12(p.outputPath, "utf-8");
3734
+ }
3735
+ completed++;
3736
+ results.set(id, { id, success: true, result });
3737
+ } catch (error) {
3738
+ completed++;
3739
+ results.set(id, {
3740
+ id,
3741
+ success: false,
3742
+ error: error instanceof Error ? error.message : String(error)
3743
+ });
3744
+ }
3745
+ })
3746
+ );
3747
+ stopSpinner(Array.from(results.values()).every((r) => r.success));
3748
+ return params.map((p) => results.get(p.id || p.outputPath));
3749
+ }
3750
+ };
3751
+ function createGeminiAdapter(geminiCommand = "gemini") {
3752
+ return {
3753
+ ai: new GeminiAIAdapter(geminiCommand),
3754
+ io: new CLIIOAdapter(),
3755
+ system: new CLISystemAdapter()
3756
+ };
3757
+ }
3758
+
3229
3759
  // src/commands/start.ts
3230
3760
  function getGitHubInfo(cwd) {
3231
3761
  const config = getGitHubConfig(cwd);
@@ -3300,6 +3830,9 @@ async function startCommand(query, options) {
3300
3830
  } else if (agentType === "opencode") {
3301
3831
  adapter = createOpenCodeAdapter();
3302
3832
  console.log("Platform: cli (opencode)");
3833
+ } else if (agentType === "gemini") {
3834
+ adapter = createGeminiAdapter();
3835
+ console.log("Platform: cli (gemini)");
3303
3836
  } else {
3304
3837
  adapter = createCLIAdapter();
3305
3838
  console.log("Platform: cli (claude)");
@@ -3345,7 +3878,8 @@ async function startCommand(query, options) {
3345
3878
  description: clarifyResp.description,
3346
3879
  stepIndex: 0,
3347
3880
  totalSteps: 0,
3348
- gatheredContext: clarifyResp.gatheredContext
3881
+ gatheredContext: clarifyResp.gatheredContext,
3882
+ previousDecisions: clarifyResp.previousDecisions
3349
3883
  };
3350
3884
  const result = await stepExecutor.executeClarifyPhase(clarifyResp.step, stepContext);
3351
3885
  const clarifyOutput = result.clarifyOutput || { ready: true, decisions: [] };
@@ -3455,15 +3989,15 @@ Resume with: spets resume --task ${taskId}`);
3455
3989
 
3456
3990
  // src/commands/resume.ts
3457
3991
  import { select as select2 } from "@inquirer/prompts";
3458
- import { existsSync as existsSync12, readdirSync as readdirSync3, readFileSync as readFileSync11 } from "fs";
3459
- import { join as join8 } from "path";
3992
+ import { existsSync as existsSync14, readdirSync as readdirSync4, readFileSync as readFileSync13 } from "fs";
3993
+ import { join as join9 } from "path";
3460
3994
  import { spawnSync as spawnSync3 } from "child_process";
3461
3995
  async function createPR(cwd, taskId, outputs) {
3462
3996
  const outputContents = [];
3463
3997
  for (const outputPath of outputs) {
3464
- const fullPath = join8(cwd, outputPath);
3465
- if (existsSync12(fullPath)) {
3466
- const content = readFileSync11(fullPath, "utf-8");
3998
+ const fullPath = join9(cwd, outputPath);
3999
+ if (existsSync14(fullPath)) {
4000
+ const content = readFileSync13(fullPath, "utf-8");
3467
4001
  outputContents.push(`## ${outputPath}
3468
4002
 
3469
4003
  ${content}`);
@@ -3485,15 +4019,15 @@ ${outputContents.join("\n\n---\n\n")}`;
3485
4019
  }
3486
4020
  function listResumableTasks(cwd) {
3487
4021
  const outputsDir = getOutputsDir(cwd);
3488
- if (!existsSync12(outputsDir)) {
4022
+ if (!existsSync14(outputsDir)) {
3489
4023
  return [];
3490
4024
  }
3491
4025
  const tasks = [];
3492
- const taskDirs = readdirSync3(outputsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
4026
+ const taskDirs = readdirSync4(outputsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
3493
4027
  const orchestrator = new Orchestrator(cwd);
3494
4028
  for (const taskId of taskDirs) {
3495
- const stateFile = join8(outputsDir, taskId, ".state.json");
3496
- if (!existsSync12(stateFile)) continue;
4029
+ const stateFile = join9(outputsDir, taskId, ".state.json");
4030
+ if (!existsSync14(stateFile)) continue;
3497
4031
  try {
3498
4032
  const response = orchestrator.cmdStatus(taskId);
3499
4033
  if (response.type === "error") continue;
@@ -3549,7 +4083,16 @@ async function resumeCommand(options) {
3549
4083
  }
3550
4084
  }
3551
4085
  const agentType = options.agent || config.agent || "claude";
3552
- const adapter = agentType === "codex" ? createCodexAdapter() : createCLIAdapter();
4086
+ let adapter;
4087
+ if (agentType === "codex") {
4088
+ adapter = createCodexAdapter();
4089
+ } else if (agentType === "opencode") {
4090
+ adapter = createOpenCodeAdapter();
4091
+ } else if (agentType === "gemini") {
4092
+ adapter = createGeminiAdapter();
4093
+ } else {
4094
+ adapter = createCLIAdapter();
4095
+ }
3553
4096
  const orchestrator = new Orchestrator(cwd);
3554
4097
  const stepExecutor = new StepExecutor(adapter, config, cwd);
3555
4098
  let response = orchestrator.cmdStatus(taskId);
@@ -3607,7 +4150,8 @@ async function resumeCommand(options) {
3607
4150
  description: clarifyResp.description,
3608
4151
  stepIndex: 0,
3609
4152
  totalSteps: 0,
3610
- gatheredContext: clarifyResp.gatheredContext
4153
+ gatheredContext: clarifyResp.gatheredContext,
4154
+ previousDecisions: clarifyResp.previousDecisions
3611
4155
  };
3612
4156
  const result = await stepExecutor.executeClarifyPhase(clarifyResp.step, stepContext);
3613
4157
  const clarifyOutput = result.clarifyOutput || { ready: true, decisions: [] };
@@ -3717,17 +4261,17 @@ Resume with: spets resume --task ${taskId}`);
3717
4261
  }
3718
4262
 
3719
4263
  // src/commands/github.ts
3720
- import { execSync as execSync4, spawn as spawn6, spawnSync as spawnSync4 } from "child_process";
3721
- import { readdirSync as readdirSync4, readFileSync as readFileSync12, existsSync as existsSync14 } from "fs";
3722
- import { join as join10 } from "path";
4264
+ import { execSync as execSync4, spawn as spawn7, spawnSync as spawnSync4 } from "child_process";
4265
+ import { readdirSync as readdirSync5, readFileSync as readFileSync14, existsSync as existsSync16 } from "fs";
4266
+ import { join as join11 } from "path";
3723
4267
 
3724
4268
  // src/hooks/runner.ts
3725
- import { spawn as spawn5 } from "child_process";
3726
- import { existsSync as existsSync13 } from "fs";
3727
- import { join as join9, isAbsolute } from "path";
4269
+ import { spawn as spawn6 } from "child_process";
4270
+ import { existsSync as existsSync15 } from "fs";
4271
+ import { join as join10, isAbsolute } from "path";
3728
4272
  async function runHook(hookPath, context, cwd = process.cwd()) {
3729
- const resolvedPath = isAbsolute(hookPath) ? hookPath : join9(getSpetsDir(cwd), hookPath);
3730
- if (!existsSync13(resolvedPath)) {
4273
+ const resolvedPath = isAbsolute(hookPath) ? hookPath : join10(getSpetsDir(cwd), hookPath);
4274
+ if (!existsSync15(resolvedPath)) {
3731
4275
  console.warn(`Hook not found: ${resolvedPath}`);
3732
4276
  return;
3733
4277
  }
@@ -3741,7 +4285,7 @@ async function runHook(hookPath, context, cwd = process.cwd()) {
3741
4285
  SPETS_CWD: cwd
3742
4286
  };
3743
4287
  return new Promise((resolve, reject) => {
3744
- const proc = spawn5(resolvedPath, [], {
4288
+ const proc = spawn6(resolvedPath, [], {
3745
4289
  cwd,
3746
4290
  env,
3747
4291
  stdio: "inherit",
@@ -3851,8 +4395,8 @@ function findTaskId(cwd, owner, repo, issueOrPr) {
3851
4395
  }
3852
4396
  }
3853
4397
  const outputsDir = getOutputsDir(cwd);
3854
- if (existsSync14(outputsDir)) {
3855
- const taskDirs = readdirSync4(outputsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort().reverse();
4398
+ if (existsSync16(outputsDir)) {
4399
+ const taskDirs = readdirSync5(outputsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort().reverse();
3856
4400
  if (taskDirs.length > 0) {
3857
4401
  return taskDirs[0];
3858
4402
  }
@@ -3991,7 +4535,8 @@ async function githubCommand(options) {
3991
4535
  description: clarifyResp.description,
3992
4536
  stepIndex: 0,
3993
4537
  totalSteps: 0,
3994
- gatheredContext: clarifyResp.gatheredContext
4538
+ gatheredContext: clarifyResp.gatheredContext,
4539
+ previousDecisions: clarifyResp.previousDecisions
3995
4540
  };
3996
4541
  const result = await stepExecutor.executeClarifyPhase(clarifyResp.step, stepContext);
3997
4542
  const clarifyOutput = result.clarifyOutput || { ready: true, decisions: [] };
@@ -4113,7 +4658,7 @@ async function postComment(config, body) {
4113
4658
  const { owner, repo, prNumber, issueNumber } = config;
4114
4659
  const args = prNumber ? ["pr", "comment", String(prNumber), "--body", body, "-R", `${owner}/${repo}`] : ["issue", "comment", String(issueNumber), "--body", body, "-R", `${owner}/${repo}`];
4115
4660
  return new Promise((resolve, reject) => {
4116
- const proc = spawn6("gh", args, { stdio: "inherit" });
4661
+ const proc = spawn7("gh", args, { stdio: "inherit" });
4117
4662
  proc.on("close", (code) => {
4118
4663
  if (code !== 0) reject(new Error(`gh failed with code ${code}`));
4119
4664
  else resolve();
@@ -4123,12 +4668,12 @@ async function postComment(config, body) {
4123
4668
  }
4124
4669
  async function createPullRequestWithAI(config, taskId, userQuery, cwd) {
4125
4670
  const { owner, repo, issueNumber } = config;
4126
- const outputsDir = join10(getOutputsDir(cwd), taskId);
4671
+ const outputsDir = join11(getOutputsDir(cwd), taskId);
4127
4672
  const outputs = [];
4128
- if (existsSync14(outputsDir)) {
4129
- const files = readdirSync4(outputsDir).filter((f) => f.endsWith(".md"));
4673
+ if (existsSync16(outputsDir)) {
4674
+ const files = readdirSync5(outputsDir).filter((f) => f.endsWith(".md"));
4130
4675
  for (const file of files) {
4131
- const content = readFileSync12(join10(outputsDir, file), "utf-8");
4676
+ const content = readFileSync14(join11(outputsDir, file), "utf-8");
4132
4677
  outputs.push({ step: file.replace(".md", ""), content });
4133
4678
  }
4134
4679
  }
@@ -4214,7 +4759,7 @@ Closes #${issueNumber}`;
4214
4759
  }
4215
4760
  async function callClaude(prompt) {
4216
4761
  return new Promise((resolve, reject) => {
4217
- const proc = spawn6("claude", ["--print"], { stdio: ["pipe", "pipe", "pipe"] });
4762
+ const proc = spawn7("claude", ["--print"], { stdio: ["pipe", "pipe", "pipe"] });
4218
4763
  let stdout = "";
4219
4764
  let stderr = "";
4220
4765
  proc.stdout.on("data", (data) => {
@@ -4500,17 +5045,229 @@ async function orchestrateCommand(action, args) {
4500
5045
  outputJSON(result);
4501
5046
  break;
4502
5047
  }
5048
+ case "knowledge-save": {
5049
+ const taskId = args[0];
5050
+ const entriesJson = args[1];
5051
+ if (!taskId) {
5052
+ outputError("Task ID is required for knowledge-save");
5053
+ return;
5054
+ }
5055
+ let entries = [];
5056
+ if (entriesJson) {
5057
+ const parsed = parseFlexibleJSON(entriesJson, {
5058
+ expectArray: true,
5059
+ arrayKeys: ["entries", "knowledge"]
5060
+ });
5061
+ if (parsed.success) {
5062
+ entries = parsed.data;
5063
+ }
5064
+ }
5065
+ const result = orchestrator.cmdKnowledgeSave(taskId, entries);
5066
+ outputJSON(result);
5067
+ break;
5068
+ }
5069
+ case "knowledge-skip": {
5070
+ const taskId = args[0];
5071
+ if (!taskId) {
5072
+ outputError("Task ID is required for knowledge-skip");
5073
+ return;
5074
+ }
5075
+ const result = orchestrator.cmdKnowledgeSkip(taskId);
5076
+ outputJSON(result);
5077
+ break;
5078
+ }
4503
5079
  default:
4504
- outputError(`Unknown action: ${action}. Valid actions: init, explore-done, clarify-done, execute-done, verify-done, clarified, approve, revise, reject, stop, status`);
5080
+ outputError(`Unknown action: ${action}. Valid actions: init, explore-done, clarify-done, execute-done, verify-done, clarified, approve, revise, reject, stop, status, knowledge-save, knowledge-skip`);
4505
5081
  }
4506
5082
  } catch (e) {
4507
5083
  outputError(e.message);
4508
5084
  }
4509
5085
  }
4510
5086
 
5087
+ // src/dashboard/renderers/cli.ts
5088
+ import { select as select3 } from "@inquirer/prompts";
5089
+ var STATUS_ICONS = {
5090
+ in_progress: "\u{1F504}",
5091
+ completed: "\u2705",
5092
+ paused: "\u23F8\uFE0F",
5093
+ rejected: "\u274C"
5094
+ };
5095
+ var STEP_ICONS = {
5096
+ pending: "\u25CB",
5097
+ draft: "\u25D0",
5098
+ approved: "\u25CF",
5099
+ rejected: "\u2717"
5100
+ };
5101
+ var CLIDashboardRenderer = class {
5102
+ /**
5103
+ * Render task list in terminal format
5104
+ */
5105
+ renderList(response) {
5106
+ const { tasks, metadata } = response;
5107
+ const lines = [];
5108
+ if (tasks.length === 0) {
5109
+ lines.push("No tasks found.");
5110
+ lines.push("");
5111
+ lines.push('Start a new task with: spets start "your task description"');
5112
+ return lines.join("\n");
5113
+ }
5114
+ lines.push("Tasks:");
5115
+ lines.push("");
5116
+ for (const task of tasks) {
5117
+ const icon = STATUS_ICONS[task.status] || "\u2753";
5118
+ const progress = `[${task.progress.currentStepIndex}/${task.progress.totalSteps}]`;
5119
+ const truncatedQuery = task.query.length > 50 ? task.query.substring(0, 47) + "..." : task.query;
5120
+ lines.push(` ${task.taskId} ${icon} ${task.status} ${progress}`);
5121
+ lines.push(` ${truncatedQuery}`);
5122
+ lines.push("");
5123
+ }
5124
+ if (metadata.truncated) {
5125
+ lines.push(` ... and ${metadata.totalTasks - metadata.limit} more tasks`);
5126
+ lines.push("");
5127
+ }
5128
+ lines.push('Use "spets dashboard -t <taskId>" for details');
5129
+ return lines.join("\n");
5130
+ }
5131
+ /**
5132
+ * Render task detail in terminal format
5133
+ */
5134
+ renderDetail(response) {
5135
+ const { task } = response;
5136
+ const lines = [];
5137
+ const icon = STATUS_ICONS[task.status] || "\u2753";
5138
+ lines.push(`Task: ${task.taskId}`);
5139
+ lines.push(`Query: ${task.query}`);
5140
+ lines.push(`Status: ${icon} ${task.status}`);
5141
+ lines.push(`Progress: ${task.progress.currentStepIndex}/${task.progress.totalSteps} (${task.progress.completionPercent}%)`);
5142
+ lines.push("");
5143
+ lines.push("Steps:");
5144
+ for (const step of task.steps) {
5145
+ const marker = step.isCurrent ? "\u2192" : " ";
5146
+ const stepIcon = STEP_ICONS[step.status] || "\u25CB";
5147
+ lines.push(` ${marker} ${step.index}. ${step.name} ${stepIcon}`);
5148
+ }
5149
+ if (task.decisionHistory && task.decisionHistory.length > 0) {
5150
+ lines.push("");
5151
+ lines.push("Decision History:");
5152
+ for (const entry of task.decisionHistory) {
5153
+ lines.push(` \u2022 ${entry.decision.decision}: ${entry.answer.selectedOptionId}`);
5154
+ }
5155
+ }
5156
+ if (task.verifyOutput) {
5157
+ lines.push("");
5158
+ lines.push("Verification Scores:");
5159
+ const scores = task.verifyOutput.score;
5160
+ lines.push(` Requirements: ${scores.requirementsCoverage}%`);
5161
+ lines.push(` Template: ${scores.templateCompliance}%`);
5162
+ lines.push(` Consistency: ${scores.consistency}%`);
5163
+ lines.push(` Completeness: ${scores.completeness}%`);
5164
+ }
5165
+ return lines.join("\n");
5166
+ }
5167
+ /**
5168
+ * Run interactive dashboard with drill-down navigation
5169
+ */
5170
+ async renderInteractive(client) {
5171
+ while (true) {
5172
+ const response = client.listTasks({ limit: 10 });
5173
+ const { tasks } = response;
5174
+ if (tasks.length === 0) {
5175
+ console.log("\nNo tasks found.");
5176
+ console.log('Start a new task with: spets start "your task description"');
5177
+ break;
5178
+ }
5179
+ const choices = tasks.map((task) => {
5180
+ const icon = STATUS_ICONS[task.status] || "\u2753";
5181
+ const progress = `[${task.progress.currentStepIndex}/${task.progress.totalSteps}]`;
5182
+ const truncatedQuery = task.query.length > 40 ? task.query.substring(0, 37) + "..." : task.query;
5183
+ return {
5184
+ value: task.taskId,
5185
+ name: `${icon} ${task.taskId} ${progress} - ${truncatedQuery}`
5186
+ };
5187
+ });
5188
+ choices.push({ value: "_exit_", name: "\u2190 Exit dashboard" });
5189
+ const selected = await select3({
5190
+ message: "Select a task to view details:",
5191
+ choices
5192
+ });
5193
+ if (selected === "_exit_") {
5194
+ break;
5195
+ }
5196
+ const detail = client.getTaskDetail(selected);
5197
+ if (detail) {
5198
+ console.log("\n" + "=".repeat(60));
5199
+ console.log(this.renderDetail(detail));
5200
+ console.log("=".repeat(60));
5201
+ const action = await select3({
5202
+ message: "What would you like to do?",
5203
+ choices: [
5204
+ { value: "back", name: "\u2190 Back to task list" },
5205
+ { value: "resume", name: "\u25B6 Resume this task" },
5206
+ { value: "exit", name: "\u2715 Exit dashboard" }
5207
+ ]
5208
+ });
5209
+ if (action === "exit") {
5210
+ break;
5211
+ }
5212
+ if (action === "resume") {
5213
+ console.log(`
5214
+ To resume this task, run:`);
5215
+ console.log(` spets resume -t ${selected}`);
5216
+ break;
5217
+ }
5218
+ }
5219
+ }
5220
+ }
5221
+ };
5222
+
5223
+ // src/commands/dashboard.ts
5224
+ async function dashboardCommand(options) {
5225
+ const cwd = process.cwd();
5226
+ if (!spetsExists(cwd)) {
5227
+ console.error('Spets not initialized. Run "spets init" first.');
5228
+ process.exit(1);
5229
+ }
5230
+ const client = new DashboardClient(cwd);
5231
+ const limit = options.limit ?? 20;
5232
+ if (options.json) {
5233
+ if (options.task) {
5234
+ const detail = client.getTaskDetail(options.task);
5235
+ if (!detail) {
5236
+ console.error(JSON.stringify({ error: `Task '${options.task}' not found.` }));
5237
+ process.exit(1);
5238
+ }
5239
+ const renderer2 = new JSONDashboardRenderer();
5240
+ console.log(renderer2.renderDetail(detail));
5241
+ } else {
5242
+ const response = client.listTasks({ limit });
5243
+ const renderer2 = new JSONDashboardRenderer();
5244
+ console.log(renderer2.render(response));
5245
+ }
5246
+ return;
5247
+ }
5248
+ if (options.task) {
5249
+ const detail = client.getTaskDetail(options.task);
5250
+ if (!detail) {
5251
+ console.error(`Task '${options.task}' not found.`);
5252
+ process.exit(1);
5253
+ }
5254
+ const renderer2 = new CLIDashboardRenderer();
5255
+ console.log(renderer2.renderDetail(detail));
5256
+ return;
5257
+ }
5258
+ if (!process.stdout.isTTY) {
5259
+ const response = client.listTasks({ limit });
5260
+ const renderer2 = new JSONDashboardRenderer();
5261
+ console.log(renderer2.render(response));
5262
+ return;
5263
+ }
5264
+ const renderer = new CLIDashboardRenderer();
5265
+ await renderer.renderInteractive(client);
5266
+ }
5267
+
4511
5268
  // src/index.ts
4512
- var __dirname2 = dirname7(fileURLToPath2(import.meta.url));
4513
- var pkg = JSON.parse(readFileSync13(join11(__dirname2, "..", "package.json"), "utf-8"));
5269
+ var __dirname2 = dirname8(fileURLToPath2(import.meta.url));
5270
+ var pkg = JSON.parse(readFileSync15(join12(__dirname2, "..", "package.json"), "utf-8"));
4514
5271
  var program = new Command();
4515
5272
  program.name("spets").description("Spec Driven Development Execution Framework").version(pkg.version);
4516
5273
  program.command("init").description("Initialize spets in current directory").option("-f, --force", "Overwrite existing config").option("--github", "Add GitHub Actions workflow for PR/Issue integration").action(initCommand);
@@ -4519,5 +5276,6 @@ program.command("start").description("Start a new workflow").argument("<query>",
4519
5276
  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);
4520
5277
  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);
4521
5278
  program.command("plugin").description("Manage plugins").argument("<action>", "Action: install, uninstall, list").argument("[name]", "Plugin name").action(pluginCommand);
5279
+ 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);
4522
5280
  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);
4523
5281
  program.parse();