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/README.md +46 -2
- package/assets/github/workflows/spets.yml +54 -1
- package/dist/index.js +908 -150
- package/package.json +1 -1
- package/templates/knowledge/guide.md +37 -0
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
|
|
6
|
-
import { dirname as
|
|
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
|
|
801
|
-
import { join as
|
|
802
|
-
import
|
|
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
|
|
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 (
|
|
830
|
-
parts.push(
|
|
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 (
|
|
882
|
-
parts.push(
|
|
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 =
|
|
1047
|
-
const outputPath =
|
|
1048
|
-
const template =
|
|
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 =
|
|
1052
|
-
if (
|
|
1170
|
+
const prevPath = join6(outputsDir, params.taskId, `${prevStep}.md`);
|
|
1171
|
+
if (existsSync6(prevPath)) {
|
|
1053
1172
|
previousSpec = {
|
|
1054
1173
|
step: prevStep,
|
|
1055
|
-
content:
|
|
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 =
|
|
1134
|
-
const outputPath =
|
|
1135
|
-
const template =
|
|
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 =
|
|
1139
|
-
if (
|
|
1257
|
+
const prevPath = join6(outputsDir, params.taskId, `${prevStep}.md`);
|
|
1258
|
+
if (existsSync6(prevPath)) {
|
|
1140
1259
|
previousSpec = {
|
|
1141
1260
|
step: prevStep,
|
|
1142
|
-
content:
|
|
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 =
|
|
1259
|
-
const template =
|
|
1260
|
-
const document =
|
|
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
|
|
1493
|
+
return join7(this.getOutputPath(), taskId, ".state.json");
|
|
1365
1494
|
}
|
|
1366
1495
|
getSpecPath(taskId, step) {
|
|
1367
|
-
return
|
|
1496
|
+
return join7(this.getOutputPath(), taskId, `${step}.md`);
|
|
1368
1497
|
}
|
|
1369
1498
|
getStepTemplatePath(step) {
|
|
1370
|
-
return
|
|
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 (!
|
|
1506
|
+
if (!existsSync7(statePath)) {
|
|
1378
1507
|
return null;
|
|
1379
1508
|
}
|
|
1380
|
-
const data = JSON.parse(
|
|
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 (!
|
|
1388
|
-
|
|
1516
|
+
if (!existsSync7(dir)) {
|
|
1517
|
+
mkdirSync5(dir, { recursive: true });
|
|
1389
1518
|
}
|
|
1390
|
-
|
|
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 =
|
|
1405
|
-
if (
|
|
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 =
|
|
1471
|
-
if (
|
|
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 =
|
|
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:
|
|
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 =
|
|
1531
|
-
const documentPath =
|
|
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:
|
|
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 =
|
|
1601
|
-
if (
|
|
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 (!
|
|
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 (
|
|
1770
|
-
const content =
|
|
1771
|
-
const { content: body, data } =
|
|
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
|
-
|
|
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 (
|
|
1822
|
-
const content =
|
|
1823
|
-
const { content: body, data } =
|
|
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
|
-
|
|
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
|
|
1903
|
-
import { join as
|
|
1904
|
-
import
|
|
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 (!
|
|
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 (!
|
|
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 =
|
|
2113
|
-
if (!
|
|
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 =
|
|
2155
|
-
if (!
|
|
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 =
|
|
2341
|
-
const { content: body, data } =
|
|
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
|
-
|
|
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
|
|
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 (!
|
|
2364
|
-
|
|
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 &&
|
|
2369
|
-
return
|
|
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 (!
|
|
2447
|
-
|
|
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 &&
|
|
2453
|
-
result =
|
|
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 (
|
|
2544
|
-
const doc =
|
|
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
|
|
2803
|
+
return readFileSync8(path, "utf-8");
|
|
2590
2804
|
}
|
|
2591
2805
|
writeFile(path, content) {
|
|
2592
2806
|
const dir = dirname3(path);
|
|
2593
|
-
if (!
|
|
2594
|
-
|
|
2807
|
+
if (!existsSync9(dir)) {
|
|
2808
|
+
mkdirSync6(dir, { recursive: true });
|
|
2595
2809
|
}
|
|
2596
|
-
|
|
2810
|
+
writeFileSync7(path, content);
|
|
2597
2811
|
}
|
|
2598
2812
|
fileExists(path) {
|
|
2599
|
-
return
|
|
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
|
|
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 (!
|
|
2643
|
-
|
|
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 &&
|
|
2648
|
-
return
|
|
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 (!
|
|
2701
|
-
|
|
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 &&
|
|
2707
|
-
result =
|
|
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 =
|
|
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(`-
|
|
3063
|
+
lines.push(`- **Recommended**: ${opt.label} \u2192 recommends \`${opt.recommendation}\` (${opt.reason})`);
|
|
2849
3064
|
} else {
|
|
2850
|
-
lines.push(`-
|
|
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
|
|
3147
|
+
return readFileSync9(path, "utf-8");
|
|
2932
3148
|
}
|
|
2933
3149
|
writeFile(path, content) {
|
|
2934
3150
|
const dir = dirname4(path);
|
|
2935
|
-
if (!
|
|
2936
|
-
|
|
3151
|
+
if (!existsSync10(dir)) {
|
|
3152
|
+
mkdirSync7(dir, { recursive: true });
|
|
2937
3153
|
}
|
|
2938
|
-
|
|
3154
|
+
writeFileSync8(path, content);
|
|
2939
3155
|
}
|
|
2940
3156
|
fileExists(path) {
|
|
2941
|
-
return
|
|
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
|
|
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 (!
|
|
2985
|
-
|
|
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 &&
|
|
2990
|
-
return
|
|
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 (!
|
|
3068
|
-
|
|
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 &&
|
|
3074
|
-
result =
|
|
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
|
|
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 (!
|
|
3115
|
-
|
|
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 &&
|
|
3120
|
-
return
|
|
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 (!
|
|
3197
|
-
|
|
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 &&
|
|
3203
|
-
result =
|
|
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
|
|
3459
|
-
import { join as
|
|
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 =
|
|
3465
|
-
if (
|
|
3466
|
-
const content =
|
|
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 (!
|
|
4022
|
+
if (!existsSync14(outputsDir)) {
|
|
3489
4023
|
return [];
|
|
3490
4024
|
}
|
|
3491
4025
|
const tasks = [];
|
|
3492
|
-
const taskDirs =
|
|
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 =
|
|
3496
|
-
if (!
|
|
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
|
-
|
|
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
|
|
3721
|
-
import { readdirSync as
|
|
3722
|
-
import { join as
|
|
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
|
|
3726
|
-
import { existsSync as
|
|
3727
|
-
import { join as
|
|
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 :
|
|
3730
|
-
if (!
|
|
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 =
|
|
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 (
|
|
3855
|
-
const taskDirs =
|
|
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 =
|
|
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 =
|
|
4671
|
+
const outputsDir = join11(getOutputsDir(cwd), taskId);
|
|
4127
4672
|
const outputs = [];
|
|
4128
|
-
if (
|
|
4129
|
-
const files =
|
|
4673
|
+
if (existsSync16(outputsDir)) {
|
|
4674
|
+
const files = readdirSync5(outputsDir).filter((f) => f.endsWith(".md"));
|
|
4130
4675
|
for (const file of files) {
|
|
4131
|
-
const content =
|
|
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 =
|
|
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 =
|
|
4513
|
-
var pkg = JSON.parse(
|
|
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();
|