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
- const state = {
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-FRBU44MW.js";
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 = "knowledge_pending";
1880
+ state.status = "phase_knowledge";
1881
+ state.phase = "review";
1758
1882
  this.saveState(state);
1759
- return this.responseCheckpointKnowledge(state);
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 "knowledge_pending":
1894
- return this.responseCheckpointKnowledge(state);
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(`### D${i + 1}: ${d.decision}`);
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**${opt.reason ? ` (${opt.reason})` : ""}`);
3200
+ lines.push(`- **Recommended:** ${opt.recommendation || "AI choice"}${opt.reason ? ` (${opt.reason})` : ""}`);
2990
3201
  } else {
2991
- lines.push(`- ${opt.label}${opt.tradeoffs ? ` - ${opt.tradeoffs}` : ""}`);
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("**How to decide:**");
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
- const d = decisions[i];
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(/^(D\d+)[:.]\s*([^#]+)/i);
4345
+ const match = line.match(/^([QD]\d+)[:.]\s*(.+)$/i);
4131
4346
  if (match) {
4132
- decisions[match[1].toLowerCase()] = match[2].trim();
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 decisionAnswers = Object.entries(parsed.decisions || {}).sort(([a], [b]) => a.localeCompare(b)).map(([id, value]) => ({
4277
- decisionId: id,
4278
- selectedOptionId: value,
4279
- // Will be matched against option IDs, or used as custom input
4280
- customInput: value
4281
- // Also provide as custom input in case it doesn't match an option
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-CFMYWYED.js");
5449
+ const { runTasksInteractive: runTasksInteractive2 } = await import("./tasks-3SJVPAZR.js");
5049
5450
  await runTasksInteractive2(cwd);
5050
5451
  }
5051
5452
  async function uiDocsCommand(docName, options) {
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  renderTasksJSON,
3
3
  runTasksInteractive
4
- } from "./chunk-FRBU44MW.js";
4
+ } from "./chunk-DVDS7BTW.js";
5
5
  import "./chunk-VQV22N3Y.js";
6
6
  export {
7
7
  renderTasksJSON,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spets",
3
- "version": "0.1.87",
3
+ "version": "0.1.89",
4
4
  "description": "Spec Driven Development Execution Framework",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",