spets 0.1.87 → 0.1.89
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.
|
@@ -80,68 +80,11 @@ function listTasks(cwd = process.cwd()) {
|
|
|
80
80
|
}
|
|
81
81
|
return readdirSync(outputsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort().reverse();
|
|
82
82
|
}
|
|
83
|
-
function getStateCachePath(taskId, cwd = process.cwd()) {
|
|
84
|
-
return join(getTaskDir(taskId, cwd), ".state-cache.json");
|
|
85
|
-
}
|
|
86
|
-
function loadStateCache(taskId, cwd = process.cwd()) {
|
|
87
|
-
const cachePath = getStateCachePath(taskId, cwd);
|
|
88
|
-
if (!existsSync(cachePath)) {
|
|
89
|
-
return null;
|
|
90
|
-
}
|
|
91
|
-
try {
|
|
92
|
-
const cached = JSON.parse(readFileSync(cachePath, "utf-8"));
|
|
93
|
-
return cached;
|
|
94
|
-
} catch {
|
|
95
|
-
return null;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
function saveStateCache(taskId, state, cwd = process.cwd()) {
|
|
99
|
-
const cachePath = getStateCachePath(taskId, cwd);
|
|
100
|
-
const stepStatuses = {};
|
|
101
|
-
for (const [stepName, output] of state.outputs.entries()) {
|
|
102
|
-
stepStatuses[stepName] = output.status;
|
|
103
|
-
}
|
|
104
|
-
const cached = {
|
|
105
|
-
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
106
|
-
taskId: state.taskId,
|
|
107
|
-
userQuery: state.userQuery,
|
|
108
|
-
currentStepIndex: state.currentStepIndex,
|
|
109
|
-
currentStepName: state.currentStepName,
|
|
110
|
-
status: state.status,
|
|
111
|
-
stepStatuses
|
|
112
|
-
};
|
|
113
|
-
try {
|
|
114
|
-
writeFileSync(cachePath, JSON.stringify(cached, null, 2));
|
|
115
|
-
} catch {
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
function isStateCacheValid(cached, maxAgeMs = 5e3) {
|
|
119
|
-
const age = Date.now() - new Date(cached.lastUpdated).getTime();
|
|
120
|
-
return age < maxAgeMs;
|
|
121
|
-
}
|
|
122
83
|
function getWorkflowState(taskId, config, cwd = process.cwd()) {
|
|
123
84
|
const taskDir = getTaskDir(taskId, cwd);
|
|
124
85
|
if (!existsSync(taskDir)) {
|
|
125
86
|
return null;
|
|
126
87
|
}
|
|
127
|
-
const cached = loadStateCache(taskId, cwd);
|
|
128
|
-
if (cached && isStateCacheValid(cached)) {
|
|
129
|
-
const outputs2 = /* @__PURE__ */ new Map();
|
|
130
|
-
for (const [stepName, status] of Object.entries(cached.stepStatuses)) {
|
|
131
|
-
outputs2.set(stepName, {
|
|
132
|
-
path: getOutputPath(taskId, stepName, cwd),
|
|
133
|
-
status
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
return {
|
|
137
|
-
taskId: cached.taskId,
|
|
138
|
-
userQuery: cached.userQuery,
|
|
139
|
-
currentStepIndex: cached.currentStepIndex,
|
|
140
|
-
currentStepName: cached.currentStepName,
|
|
141
|
-
status: cached.status,
|
|
142
|
-
outputs: outputs2
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
88
|
const outputs = /* @__PURE__ */ new Map();
|
|
146
89
|
let lastApprovedIndex = -1;
|
|
147
90
|
let currentStatus = "in_progress";
|
|
@@ -176,7 +119,7 @@ function getWorkflowState(taskId, config, cwd = process.cwd()) {
|
|
|
176
119
|
if (currentDoc?.frontmatter.status === "draft" && currentDoc.frontmatter.open_questions?.length) {
|
|
177
120
|
currentStatus = "paused";
|
|
178
121
|
}
|
|
179
|
-
|
|
122
|
+
return {
|
|
180
123
|
taskId,
|
|
181
124
|
userQuery,
|
|
182
125
|
currentStepIndex,
|
|
@@ -184,8 +127,6 @@ function getWorkflowState(taskId, config, cwd = process.cwd()) {
|
|
|
184
127
|
status: currentStatus,
|
|
185
128
|
outputs
|
|
186
129
|
};
|
|
187
|
-
saveStateCache(taskId, state, cwd);
|
|
188
|
-
return state;
|
|
189
130
|
}
|
|
190
131
|
function loadTaskMetadata(taskId, cwd = process.cwd()) {
|
|
191
132
|
const metaPath = join(getTaskDir(taskId, cwd), ".meta.json");
|
package/dist/index.js
CHANGED
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
loadTaskMetadata,
|
|
16
16
|
renderTasksJSON,
|
|
17
17
|
runTasksInteractive
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-DVDS7BTW.js";
|
|
19
19
|
import {
|
|
20
20
|
renderDocsJSON,
|
|
21
21
|
runDocsInteractive
|
|
@@ -1263,6 +1263,100 @@ function buildVerifyPrompt(params) {
|
|
|
1263
1263
|
parts.push("");
|
|
1264
1264
|
return parts.join("\n");
|
|
1265
1265
|
}
|
|
1266
|
+
function buildKnowledgeExtractPrompt(params) {
|
|
1267
|
+
const parts = [];
|
|
1268
|
+
parts.push("# Knowledge Extraction Phase");
|
|
1269
|
+
parts.push("");
|
|
1270
|
+
parts.push("Your task is to extract reusable knowledge from this workflow.");
|
|
1271
|
+
parts.push("This knowledge will be saved and used to improve future workflows.");
|
|
1272
|
+
parts.push("");
|
|
1273
|
+
parts.push("## Task Information");
|
|
1274
|
+
parts.push("");
|
|
1275
|
+
parts.push(`- **Task ID**: ${params.taskId}`);
|
|
1276
|
+
parts.push(`- **Description**: ${params.description}`);
|
|
1277
|
+
parts.push("");
|
|
1278
|
+
if (params.guide) {
|
|
1279
|
+
parts.push("## Knowledge Guide");
|
|
1280
|
+
parts.push("");
|
|
1281
|
+
parts.push(params.guide);
|
|
1282
|
+
parts.push("");
|
|
1283
|
+
}
|
|
1284
|
+
if (params.decisionHistory && params.decisionHistory.length > 0) {
|
|
1285
|
+
parts.push("## Decisions Made");
|
|
1286
|
+
parts.push("");
|
|
1287
|
+
for (const entry of params.decisionHistory) {
|
|
1288
|
+
const selectedOption = entry.decision.options.find((o) => o.id === entry.answer.selectedOptionId);
|
|
1289
|
+
parts.push(`### ${entry.decision.decision}`);
|
|
1290
|
+
parts.push(`**Why asked:** ${entry.decision.why}`);
|
|
1291
|
+
parts.push(`**Selected:** ${selectedOption?.label || entry.answer.selectedOptionId}`);
|
|
1292
|
+
if (entry.answer.customInput) {
|
|
1293
|
+
parts.push(`**User note:** ${entry.answer.customInput}`);
|
|
1294
|
+
}
|
|
1295
|
+
parts.push("");
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
if (params.exploreOutputs && params.exploreOutputs.length > 0) {
|
|
1299
|
+
parts.push("## Patterns and Constraints Found");
|
|
1300
|
+
parts.push("");
|
|
1301
|
+
for (const explore of params.exploreOutputs) {
|
|
1302
|
+
parts.push(`### Step: ${explore.step}`);
|
|
1303
|
+
if (explore.output.patterns.length > 0) {
|
|
1304
|
+
parts.push("**Patterns:**");
|
|
1305
|
+
for (const pattern of explore.output.patterns) {
|
|
1306
|
+
parts.push(`- ${pattern}`);
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
if (explore.output.constraints.length > 0) {
|
|
1310
|
+
parts.push("**Constraints:**");
|
|
1311
|
+
for (const constraint of explore.output.constraints) {
|
|
1312
|
+
parts.push(`- ${constraint}`);
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
parts.push("");
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
parts.push("## Your Task");
|
|
1319
|
+
parts.push("");
|
|
1320
|
+
parts.push("Extract knowledge that would be useful for future workflows:");
|
|
1321
|
+
parts.push("");
|
|
1322
|
+
parts.push("**What to extract:**");
|
|
1323
|
+
parts.push("- User preferences and decisions that apply broadly");
|
|
1324
|
+
parts.push("- Codebase patterns that should be followed");
|
|
1325
|
+
parts.push("- Constraints that must be respected");
|
|
1326
|
+
parts.push("- Architecture decisions and their rationale");
|
|
1327
|
+
parts.push("");
|
|
1328
|
+
parts.push("**What NOT to extract:**");
|
|
1329
|
+
parts.push("- Task-specific implementation details");
|
|
1330
|
+
parts.push("- One-time decisions");
|
|
1331
|
+
parts.push("- Temporary workarounds");
|
|
1332
|
+
parts.push("");
|
|
1333
|
+
parts.push("## Output Format");
|
|
1334
|
+
parts.push("");
|
|
1335
|
+
parts.push("Output a JSON object with this structure:");
|
|
1336
|
+
parts.push("");
|
|
1337
|
+
parts.push("```json");
|
|
1338
|
+
parts.push("{");
|
|
1339
|
+
parts.push(' "entries": [');
|
|
1340
|
+
parts.push(" {");
|
|
1341
|
+
parts.push(' "filename": "pattern--error-handling",');
|
|
1342
|
+
parts.push(' "content": "The knowledge content in markdown format...",');
|
|
1343
|
+
parts.push(' "reason": "Why this is worth saving"');
|
|
1344
|
+
parts.push(" }");
|
|
1345
|
+
parts.push(" ]");
|
|
1346
|
+
parts.push("}");
|
|
1347
|
+
parts.push("```");
|
|
1348
|
+
parts.push("");
|
|
1349
|
+
parts.push("**Filename tips:**");
|
|
1350
|
+
parts.push("- Use kebab-case");
|
|
1351
|
+
parts.push("- Optional prefixes: `pref--` (preference), `rule--` (rule), `pattern--` (pattern)");
|
|
1352
|
+
parts.push("- Put key terms first for easier discovery");
|
|
1353
|
+
parts.push("");
|
|
1354
|
+
parts.push('If there is no meaningful knowledge to extract, return: `{"entries": []}`');
|
|
1355
|
+
parts.push("");
|
|
1356
|
+
parts.push("**Important:** Output ONLY the JSON, no other text.");
|
|
1357
|
+
parts.push("");
|
|
1358
|
+
return parts.join("\n");
|
|
1359
|
+
}
|
|
1266
1360
|
|
|
1267
1361
|
// src/orchestrator/index.ts
|
|
1268
1362
|
var Orchestrator = class {
|
|
@@ -1479,6 +1573,35 @@ ${issues}`;
|
|
|
1479
1573
|
onComplete: `npx spets orchestrate verify-done ${state.taskId}`
|
|
1480
1574
|
};
|
|
1481
1575
|
}
|
|
1576
|
+
/**
|
|
1577
|
+
* Phase 5: Knowledge extraction
|
|
1578
|
+
* AI automatically extracts and saves knowledge from the workflow
|
|
1579
|
+
*/
|
|
1580
|
+
responsePhaseKnowledge(state) {
|
|
1581
|
+
const guide = loadGuide(this.cwd);
|
|
1582
|
+
const prompt = buildKnowledgeExtractPrompt({
|
|
1583
|
+
taskId: state.taskId,
|
|
1584
|
+
description: state.description,
|
|
1585
|
+
decisionHistory: state.decisionHistory,
|
|
1586
|
+
exploreOutputs: state.exploreOutput ? [{ step: state.currentStep, output: state.exploreOutput }] : void 0,
|
|
1587
|
+
guide,
|
|
1588
|
+
cwd: this.cwd
|
|
1589
|
+
});
|
|
1590
|
+
return {
|
|
1591
|
+
type: "phase",
|
|
1592
|
+
phase: "knowledge",
|
|
1593
|
+
taskId: state.taskId,
|
|
1594
|
+
description: state.description,
|
|
1595
|
+
prompt,
|
|
1596
|
+
workflowSummary: {
|
|
1597
|
+
decisionHistory: state.decisionHistory,
|
|
1598
|
+
exploreOutputs: state.exploreOutput ? [{ step: state.currentStep, output: state.exploreOutput }] : void 0,
|
|
1599
|
+
totalSteps: state.totalSteps
|
|
1600
|
+
},
|
|
1601
|
+
guide,
|
|
1602
|
+
onComplete: `npx spets orchestrate knowledge-extract-done ${state.taskId}`
|
|
1603
|
+
};
|
|
1604
|
+
}
|
|
1482
1605
|
/**
|
|
1483
1606
|
* Checkpoint: Decisions need user input
|
|
1484
1607
|
*/
|
|
@@ -1754,9 +1877,10 @@ ${entry.answer.customInput ? `**User note:** ${entry.answer.customInput}` : ""}`
|
|
|
1754
1877
|
} else {
|
|
1755
1878
|
const config = loadConfig(this.cwd);
|
|
1756
1879
|
if (config.knowledge?.enabled) {
|
|
1757
|
-
state.status = "
|
|
1880
|
+
state.status = "phase_knowledge";
|
|
1881
|
+
state.phase = "review";
|
|
1758
1882
|
this.saveState(state);
|
|
1759
|
-
return this.
|
|
1883
|
+
return this.responsePhaseKnowledge(state);
|
|
1760
1884
|
}
|
|
1761
1885
|
state.status = "completed";
|
|
1762
1886
|
this.saveState(state);
|
|
@@ -1795,6 +1919,26 @@ ${entry.answer.customInput ? `**User note:** ${entry.answer.customInput}` : ""}`
|
|
|
1795
1919
|
this.saveState(state);
|
|
1796
1920
|
return this.responseComplete(state, "completed");
|
|
1797
1921
|
}
|
|
1922
|
+
/**
|
|
1923
|
+
* AI completed knowledge extraction - save entries and finish
|
|
1924
|
+
*/
|
|
1925
|
+
cmdKnowledgeExtractDone(taskId, output) {
|
|
1926
|
+
const state = this.loadState(taskId);
|
|
1927
|
+
if (!state) {
|
|
1928
|
+
return this.responseError(`No workflow found: ${taskId}`, taskId);
|
|
1929
|
+
}
|
|
1930
|
+
for (const entry of output.entries) {
|
|
1931
|
+
saveKnowledge(
|
|
1932
|
+
entry.filename,
|
|
1933
|
+
entry.content,
|
|
1934
|
+
{ taskId: state.taskId, step: state.currentStep },
|
|
1935
|
+
this.cwd
|
|
1936
|
+
);
|
|
1937
|
+
}
|
|
1938
|
+
state.status = "completed";
|
|
1939
|
+
this.saveState(state);
|
|
1940
|
+
return this.responseComplete(state, "completed");
|
|
1941
|
+
}
|
|
1798
1942
|
/**
|
|
1799
1943
|
* Request revision with feedback - goes back to execute phase
|
|
1800
1944
|
*/
|
|
@@ -1890,8 +2034,8 @@ ${entry.answer.customInput ? `**User note:** ${entry.answer.customInput}` : ""}`
|
|
|
1890
2034
|
return this.responsePhaseVerify(state);
|
|
1891
2035
|
case "approve_pending":
|
|
1892
2036
|
return this.responseCheckpointApprove(state);
|
|
1893
|
-
case "
|
|
1894
|
-
return this.
|
|
2037
|
+
case "phase_knowledge":
|
|
2038
|
+
return this.responsePhaseKnowledge(state);
|
|
1895
2039
|
case "completed":
|
|
1896
2040
|
case "stopped":
|
|
1897
2041
|
case "rejected":
|
|
@@ -2148,6 +2292,38 @@ var StepExecutor = class {
|
|
|
2148
2292
|
};
|
|
2149
2293
|
}
|
|
2150
2294
|
// ==========================================================================
|
|
2295
|
+
// Knowledge Extraction Phase
|
|
2296
|
+
// ==========================================================================
|
|
2297
|
+
/**
|
|
2298
|
+
* Execute knowledge extraction phase - AI extracts knowledge from workflow
|
|
2299
|
+
*/
|
|
2300
|
+
async executeKnowledgePhase(phaseResponse) {
|
|
2301
|
+
this.adapter.io.notify("Extracting knowledge from workflow...", "info");
|
|
2302
|
+
const prompt = buildKnowledgeExtractPrompt({
|
|
2303
|
+
taskId: phaseResponse.taskId,
|
|
2304
|
+
description: phaseResponse.description,
|
|
2305
|
+
decisionHistory: phaseResponse.workflowSummary.decisionHistory,
|
|
2306
|
+
exploreOutputs: phaseResponse.workflowSummary.exploreOutputs,
|
|
2307
|
+
guide: phaseResponse.guide,
|
|
2308
|
+
cwd: this.cwd
|
|
2309
|
+
});
|
|
2310
|
+
const response = await this.adapter.ai.execute({
|
|
2311
|
+
prompt,
|
|
2312
|
+
outputPath: ""
|
|
2313
|
+
// No file output needed
|
|
2314
|
+
});
|
|
2315
|
+
const knowledgeOutput = this.parseKnowledgeOutput(response || "");
|
|
2316
|
+
if (knowledgeOutput.entries.length > 0) {
|
|
2317
|
+
this.adapter.io.notify(`Extracted ${knowledgeOutput.entries.length} knowledge entries`, "success");
|
|
2318
|
+
} else {
|
|
2319
|
+
this.adapter.io.notify("No knowledge entries to save", "info");
|
|
2320
|
+
}
|
|
2321
|
+
return {
|
|
2322
|
+
phase: "knowledge",
|
|
2323
|
+
knowledgeOutput
|
|
2324
|
+
};
|
|
2325
|
+
}
|
|
2326
|
+
// ==========================================================================
|
|
2151
2327
|
// Phase 4: Review (Approval)
|
|
2152
2328
|
// ==========================================================================
|
|
2153
2329
|
/**
|
|
@@ -2338,6 +2514,40 @@ var StepExecutor = class {
|
|
|
2338
2514
|
summary: "Verification parsing failed"
|
|
2339
2515
|
};
|
|
2340
2516
|
}
|
|
2517
|
+
/**
|
|
2518
|
+
* Parse knowledge extraction output (JSON)
|
|
2519
|
+
*/
|
|
2520
|
+
parseKnowledgeOutput(response) {
|
|
2521
|
+
try {
|
|
2522
|
+
const jsonMatch = response.match(/\{[\s\S]*\}/);
|
|
2523
|
+
if (jsonMatch) {
|
|
2524
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
2525
|
+
const entries = Array.isArray(parsed.entries) ? parsed.entries : [];
|
|
2526
|
+
return {
|
|
2527
|
+
entries: entries.map((e) => ({
|
|
2528
|
+
filename: e.filename || "",
|
|
2529
|
+
content: e.content || "",
|
|
2530
|
+
reason: e.reason || ""
|
|
2531
|
+
}))
|
|
2532
|
+
};
|
|
2533
|
+
}
|
|
2534
|
+
const arrayMatch = response.match(/\[[\s\S]*\]/);
|
|
2535
|
+
if (arrayMatch) {
|
|
2536
|
+
const parsed = JSON.parse(arrayMatch[0]);
|
|
2537
|
+
if (Array.isArray(parsed)) {
|
|
2538
|
+
return {
|
|
2539
|
+
entries: parsed.map((e) => ({
|
|
2540
|
+
filename: e.filename || "",
|
|
2541
|
+
content: e.content || "",
|
|
2542
|
+
reason: e.reason || ""
|
|
2543
|
+
}))
|
|
2544
|
+
};
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2547
|
+
} catch {
|
|
2548
|
+
}
|
|
2549
|
+
return { entries: [] };
|
|
2550
|
+
}
|
|
2341
2551
|
/**
|
|
2342
2552
|
* Update document status in frontmatter
|
|
2343
2553
|
*/
|
|
@@ -2980,37 +3190,37 @@ var GitHubIOAdapter = class {
|
|
|
2980
3190
|
];
|
|
2981
3191
|
for (let i = 0; i < decisions.length; i++) {
|
|
2982
3192
|
const d = decisions[i];
|
|
2983
|
-
lines.push(`###
|
|
3193
|
+
lines.push(`### Q${i + 1}: ${d.decision}`);
|
|
2984
3194
|
lines.push(`> ${d.why}`);
|
|
2985
3195
|
lines.push("");
|
|
2986
3196
|
lines.push("**Options:**");
|
|
3197
|
+
let optNum = 1;
|
|
2987
3198
|
for (const opt of d.options) {
|
|
2988
3199
|
if (opt.id === "ai") {
|
|
2989
|
-
lines.push(`- **Recommended
|
|
3200
|
+
lines.push(`- **Recommended:** ${opt.recommendation || "AI choice"}${opt.reason ? ` (${opt.reason})` : ""}`);
|
|
2990
3201
|
} else {
|
|
2991
|
-
lines.push(
|
|
3202
|
+
lines.push(`${optNum}. ${opt.label}${opt.tradeoffs ? ` - ${opt.tradeoffs}` : ""}`);
|
|
3203
|
+
optNum++;
|
|
2992
3204
|
}
|
|
2993
3205
|
}
|
|
2994
3206
|
lines.push("");
|
|
2995
3207
|
}
|
|
2996
3208
|
lines.push("---");
|
|
2997
3209
|
lines.push("");
|
|
2998
|
-
lines.push("**
|
|
3210
|
+
lines.push("**Respond naturally:**");
|
|
2999
3211
|
lines.push("");
|
|
3000
3212
|
lines.push("```");
|
|
3001
3213
|
lines.push("/decide");
|
|
3002
3214
|
for (let i = 0; i < decisions.length; i++) {
|
|
3003
|
-
|
|
3004
|
-
lines.push(`D${i + 1}: ${d.decision}`);
|
|
3005
|
-
for (const opt of d.options) {
|
|
3006
|
-
if (opt.id === "ai") {
|
|
3007
|
-
lines.push(` - AI Recommended`);
|
|
3008
|
-
} else {
|
|
3009
|
-
lines.push(` - ${opt.label}`);
|
|
3010
|
-
}
|
|
3011
|
-
}
|
|
3215
|
+
lines.push(`Q${i + 1}: <your choice or thoughts>`);
|
|
3012
3216
|
}
|
|
3013
3217
|
lines.push("```");
|
|
3218
|
+
lines.push("");
|
|
3219
|
+
lines.push("Examples:");
|
|
3220
|
+
lines.push("- `Q1: I like the first option`");
|
|
3221
|
+
lines.push("- `Q1: go with the recommended`");
|
|
3222
|
+
lines.push("- `Q1: elaborate on option 2`");
|
|
3223
|
+
lines.push("- `Q1: something else - I want to use X instead`");
|
|
3014
3224
|
return lines.join("\n");
|
|
3015
3225
|
}
|
|
3016
3226
|
formatApprovalComment(doc, stepName, stepIndex, totalSteps, specPath) {
|
|
@@ -3659,6 +3869,11 @@ async function startCommand(query, options) {
|
|
|
3659
3869
|
};
|
|
3660
3870
|
const result = await stepExecutor.executeVerifyPhase(verifyResp.step, stepContext);
|
|
3661
3871
|
response = orchestrator.cmdVerifyDone(taskId, result.verifyOutput);
|
|
3872
|
+
} else if (phaseResponse.phase === "knowledge") {
|
|
3873
|
+
const knowledgeResp = phaseResponse;
|
|
3874
|
+
console.log("\n\u{1F4DA} Extracting knowledge...\n");
|
|
3875
|
+
const result = await stepExecutor.executeKnowledgePhase(knowledgeResp);
|
|
3876
|
+
response = orchestrator.cmdKnowledgeExtractDone(taskId, result.knowledgeOutput);
|
|
3662
3877
|
}
|
|
3663
3878
|
} else if (response.type === "checkpoint") {
|
|
3664
3879
|
taskId = response.taskId;
|
|
@@ -4127,9 +4342,10 @@ function parseGitHubCommand(comment) {
|
|
|
4127
4342
|
const decisions = {};
|
|
4128
4343
|
const decisionLines = decisionsText.split("\n");
|
|
4129
4344
|
for (const line of decisionLines) {
|
|
4130
|
-
const match = line.match(/^(
|
|
4345
|
+
const match = line.match(/^([QD]\d+)[:.]\s*(.+)$/i);
|
|
4131
4346
|
if (match) {
|
|
4132
|
-
|
|
4347
|
+
const key = match[1].toLowerCase().replace("d", "q");
|
|
4348
|
+
decisions[key] = match[2].trim();
|
|
4133
4349
|
}
|
|
4134
4350
|
}
|
|
4135
4351
|
return { command: "decide", decisions };
|
|
@@ -4272,13 +4488,30 @@ async function githubCommand(options) {
|
|
|
4272
4488
|
break;
|
|
4273
4489
|
}
|
|
4274
4490
|
case "decide": {
|
|
4275
|
-
console.log("Processing decisions");
|
|
4276
|
-
const
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4491
|
+
console.log("Processing decisions with AI interpretation...");
|
|
4492
|
+
const currentState = orchestrator.cmdStatus(taskId);
|
|
4493
|
+
if (currentState.type !== "checkpoint" || currentState.checkpoint !== "clarify") {
|
|
4494
|
+
console.error("No pending decisions found. Task may not be in clarify state.");
|
|
4495
|
+
process.exit(1);
|
|
4496
|
+
}
|
|
4497
|
+
const pendingDecisions = currentState.decisions;
|
|
4498
|
+
const userResponses = parsed.decisions || {};
|
|
4499
|
+
const interpretedAnswers = await interpretDecisionResponses(pendingDecisions, userResponses, githubConfig);
|
|
4500
|
+
const followUps = interpretedAnswers.filter((a) => a.followUp);
|
|
4501
|
+
if (followUps.length > 0) {
|
|
4502
|
+
for (const followUp of followUps) {
|
|
4503
|
+
const decision = pendingDecisions.find((d) => d.id === followUp.decisionId);
|
|
4504
|
+
if (decision && followUp.followUp) {
|
|
4505
|
+
await postFollowUpComment(githubConfig, decision, followUp.followUp, taskId);
|
|
4506
|
+
}
|
|
4507
|
+
}
|
|
4508
|
+
console.log("\n\u23F8\uFE0F Follow-up questions posted. Waiting for /decide response.");
|
|
4509
|
+
return;
|
|
4510
|
+
}
|
|
4511
|
+
const decisionAnswers = interpretedAnswers.map((a) => ({
|
|
4512
|
+
decisionId: a.decisionId,
|
|
4513
|
+
selectedOptionId: a.selectedOptionId,
|
|
4514
|
+
customInput: a.customInput
|
|
4282
4515
|
}));
|
|
4283
4516
|
response = orchestrator.cmdClarified(taskId, decisionAnswers);
|
|
4284
4517
|
break;
|
|
@@ -4532,6 +4765,150 @@ Closes #${issueNumber}`;
|
|
|
4532
4765
|
}
|
|
4533
4766
|
return { title, body };
|
|
4534
4767
|
}
|
|
4768
|
+
async function interpretDecisionResponses(decisions, userResponses, config) {
|
|
4769
|
+
const results = [];
|
|
4770
|
+
for (let i = 0; i < decisions.length; i++) {
|
|
4771
|
+
const decision = decisions[i];
|
|
4772
|
+
const key = `q${i + 1}`;
|
|
4773
|
+
const userResponse = userResponses[key] || "";
|
|
4774
|
+
if (!userResponse) {
|
|
4775
|
+
results.push({
|
|
4776
|
+
decisionId: decision.id,
|
|
4777
|
+
selectedOptionId: "ai"
|
|
4778
|
+
});
|
|
4779
|
+
continue;
|
|
4780
|
+
}
|
|
4781
|
+
const optionsText = decision.options.filter((o) => o.id !== "ai").map((o, idx) => `${idx + 1}. ${o.label}: ${o.description || ""}`).join("\n");
|
|
4782
|
+
const aiOption = decision.options.find((o) => o.id === "ai");
|
|
4783
|
+
const prompt = `You are interpreting a user's decision response.
|
|
4784
|
+
|
|
4785
|
+
Decision: ${decision.decision}
|
|
4786
|
+
Why: ${decision.why}
|
|
4787
|
+
|
|
4788
|
+
Options:
|
|
4789
|
+
${optionsText}
|
|
4790
|
+
${aiOption ? `AI Recommended: ${aiOption.recommendation || "AI choice"} (${aiOption.reason || ""})` : ""}
|
|
4791
|
+
|
|
4792
|
+
User's response: "${userResponse}"
|
|
4793
|
+
|
|
4794
|
+
Interpret the user's intent and respond with ONLY a JSON object:
|
|
4795
|
+
{
|
|
4796
|
+
"action": "select" | "elaborate" | "alternative",
|
|
4797
|
+
"optionId": "<option id if selecting, or which option to elaborate on>",
|
|
4798
|
+
"customInput": "<any custom input if alternative>"
|
|
4799
|
+
}
|
|
4800
|
+
|
|
4801
|
+
Examples:
|
|
4802
|
+
- "I like the first option" \u2192 {"action": "select", "optionId": "opt1"}
|
|
4803
|
+
- "go with recommended" \u2192 {"action": "select", "optionId": "ai"}
|
|
4804
|
+
- "elaborate on option 2" \u2192 {"action": "elaborate", "optionId": "opt2"}
|
|
4805
|
+
- "something else - use gRPC" \u2192 {"action": "alternative", "customInput": "use gRPC"}
|
|
4806
|
+
- "the second one" \u2192 {"action": "select", "optionId": "opt2"}
|
|
4807
|
+
|
|
4808
|
+
Output ONLY the JSON, no explanation.`;
|
|
4809
|
+
try {
|
|
4810
|
+
const response = await callClaude(prompt);
|
|
4811
|
+
const jsonMatch = response.match(/\{[\s\S]*\}/);
|
|
4812
|
+
if (jsonMatch) {
|
|
4813
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
4814
|
+
if (parsed.action === "elaborate") {
|
|
4815
|
+
results.push({
|
|
4816
|
+
decisionId: decision.id,
|
|
4817
|
+
selectedOptionId: "",
|
|
4818
|
+
followUp: "elaborate",
|
|
4819
|
+
followUpTarget: parsed.optionId
|
|
4820
|
+
});
|
|
4821
|
+
} else if (parsed.action === "alternative") {
|
|
4822
|
+
results.push({
|
|
4823
|
+
decisionId: decision.id,
|
|
4824
|
+
selectedOptionId: "_custom_",
|
|
4825
|
+
customInput: parsed.customInput || userResponse
|
|
4826
|
+
});
|
|
4827
|
+
} else {
|
|
4828
|
+
const optionId = parsed.optionId || "ai";
|
|
4829
|
+
let finalOptionId = optionId;
|
|
4830
|
+
if (/^opt\d+$/.test(optionId)) {
|
|
4831
|
+
finalOptionId = optionId;
|
|
4832
|
+
} else if (/^\d+$/.test(optionId)) {
|
|
4833
|
+
const idx = parseInt(optionId, 10) - 1;
|
|
4834
|
+
const nonAiOptions = decision.options.filter((o) => o.id !== "ai");
|
|
4835
|
+
if (idx >= 0 && idx < nonAiOptions.length) {
|
|
4836
|
+
finalOptionId = nonAiOptions[idx].id;
|
|
4837
|
+
}
|
|
4838
|
+
}
|
|
4839
|
+
results.push({
|
|
4840
|
+
decisionId: decision.id,
|
|
4841
|
+
selectedOptionId: finalOptionId
|
|
4842
|
+
});
|
|
4843
|
+
}
|
|
4844
|
+
} else {
|
|
4845
|
+
results.push({
|
|
4846
|
+
decisionId: decision.id,
|
|
4847
|
+
selectedOptionId: "_custom_",
|
|
4848
|
+
customInput: userResponse
|
|
4849
|
+
});
|
|
4850
|
+
}
|
|
4851
|
+
} catch (error) {
|
|
4852
|
+
console.error(`Failed to interpret response for ${decision.id}:`, error);
|
|
4853
|
+
results.push({
|
|
4854
|
+
decisionId: decision.id,
|
|
4855
|
+
selectedOptionId: "_custom_",
|
|
4856
|
+
customInput: userResponse
|
|
4857
|
+
});
|
|
4858
|
+
}
|
|
4859
|
+
}
|
|
4860
|
+
return results;
|
|
4861
|
+
}
|
|
4862
|
+
async function postFollowUpComment(config, decision, followUpType, taskId, targetOptionId) {
|
|
4863
|
+
const { owner, repo, prNumber, issueNumber } = config;
|
|
4864
|
+
let body;
|
|
4865
|
+
if (followUpType === "elaborate") {
|
|
4866
|
+
const targetOption = decision.options.find((o) => o.id === targetOptionId);
|
|
4867
|
+
body = `## \u{1F4DD} Spets: More Details
|
|
4868
|
+
|
|
4869
|
+
> Task ID: \`${taskId}\`
|
|
4870
|
+
> Question: ${decision.decision}
|
|
4871
|
+
|
|
4872
|
+
### ${targetOption?.label || "Option"}
|
|
4873
|
+
|
|
4874
|
+
${targetOption?.description || "No additional description available."}
|
|
4875
|
+
|
|
4876
|
+
${targetOption?.tradeoffs ? `**Tradeoffs:** ${targetOption.tradeoffs}` : ""}
|
|
4877
|
+
|
|
4878
|
+
---
|
|
4879
|
+
|
|
4880
|
+
Please respond with your decision:
|
|
4881
|
+
\`\`\`
|
|
4882
|
+
/decide
|
|
4883
|
+
Q1: <your choice after seeing the details>
|
|
4884
|
+
\`\`\``;
|
|
4885
|
+
} else {
|
|
4886
|
+
body = `## \u{1F4A1} Spets: Custom Input Needed
|
|
4887
|
+
|
|
4888
|
+
> Task ID: \`${taskId}\`
|
|
4889
|
+
> Question: ${decision.decision}
|
|
4890
|
+
|
|
4891
|
+
You indicated you'd like a different approach. Please describe what you want:
|
|
4892
|
+
|
|
4893
|
+
\`\`\`
|
|
4894
|
+
/decide
|
|
4895
|
+
Q1: <describe your preferred approach>
|
|
4896
|
+
\`\`\``;
|
|
4897
|
+
}
|
|
4898
|
+
const args = prNumber ? ["pr", "comment", String(prNumber), "--body", body, "-R", `${owner}/${repo}`] : ["issue", "comment", String(issueNumber), "--body", body, "-R", `${owner}/${repo}`];
|
|
4899
|
+
return new Promise((resolve, reject) => {
|
|
4900
|
+
const proc = spawn7("gh", args, { stdio: ["pipe", "pipe", "pipe"] });
|
|
4901
|
+
let stderr = "";
|
|
4902
|
+
proc.stderr.on("data", (data) => {
|
|
4903
|
+
stderr += data.toString();
|
|
4904
|
+
});
|
|
4905
|
+
proc.on("close", (code) => {
|
|
4906
|
+
if (code !== 0) reject(new Error(`Failed to post follow-up: ${stderr}`));
|
|
4907
|
+
else resolve();
|
|
4908
|
+
});
|
|
4909
|
+
proc.on("error", reject);
|
|
4910
|
+
});
|
|
4911
|
+
}
|
|
4535
4912
|
async function callClaude(prompt) {
|
|
4536
4913
|
return new Promise((resolve, reject) => {
|
|
4537
4914
|
const proc = spawn7("claude", ["--print"], { stdio: ["pipe", "pipe", "pipe"] });
|
|
@@ -4851,8 +5228,32 @@ async function orchestrateCommand(action, args) {
|
|
|
4851
5228
|
outputJSON(result);
|
|
4852
5229
|
break;
|
|
4853
5230
|
}
|
|
5231
|
+
case "knowledge-extract-done": {
|
|
5232
|
+
const taskId = args[0];
|
|
5233
|
+
const entriesJson = args[1];
|
|
5234
|
+
if (!taskId) {
|
|
5235
|
+
outputError("Task ID is required for knowledge-extract-done");
|
|
5236
|
+
return;
|
|
5237
|
+
}
|
|
5238
|
+
let output = { entries: [] };
|
|
5239
|
+
if (entriesJson) {
|
|
5240
|
+
const parsed = parseFlexibleJSON(entriesJson, {
|
|
5241
|
+
arrayKeys: ["entries"]
|
|
5242
|
+
});
|
|
5243
|
+
if (parsed.success) {
|
|
5244
|
+
if (Array.isArray(parsed.data)) {
|
|
5245
|
+
output = { entries: parsed.data };
|
|
5246
|
+
} else {
|
|
5247
|
+
output = { entries: parsed.data.entries || [] };
|
|
5248
|
+
}
|
|
5249
|
+
}
|
|
5250
|
+
}
|
|
5251
|
+
const result = orchestrator.cmdKnowledgeExtractDone(taskId, output);
|
|
5252
|
+
outputJSON(result);
|
|
5253
|
+
break;
|
|
5254
|
+
}
|
|
4854
5255
|
default:
|
|
4855
|
-
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`);
|
|
5256
|
+
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, knowledge-extract-done`);
|
|
4856
5257
|
}
|
|
4857
5258
|
} catch (e) {
|
|
4858
5259
|
outputError(e.message);
|
|
@@ -5045,7 +5446,7 @@ async function uiTasksCommand(taskId, options) {
|
|
|
5045
5446
|
console.log(renderTasksJSON(taskId, cwd));
|
|
5046
5447
|
return;
|
|
5047
5448
|
}
|
|
5048
|
-
const { runTasksInteractive: runTasksInteractive2 } = await import("./tasks-
|
|
5449
|
+
const { runTasksInteractive: runTasksInteractive2 } = await import("./tasks-3SJVPAZR.js");
|
|
5049
5450
|
await runTasksInteractive2(cwd);
|
|
5050
5451
|
}
|
|
5051
5452
|
async function uiDocsCommand(docName, options) {
|