@wrongstack/cli 0.6.5 → 0.6.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +814 -78
- package/dist/index.js.map +1 -1
- package/package.json +10 -10
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import * as path23 from 'path';
|
|
|
3
3
|
import { join } from 'path';
|
|
4
4
|
import * as fsp2 from 'fs/promises';
|
|
5
5
|
import { readdir, readFile } from 'fs/promises';
|
|
6
|
-
import { color, DefaultPathResolver, TOKENS, DefaultSystemPromptBuilder, makeAutonomyPromptContributor, ToolRegistry, createContextManagerTool, EventBus, SlashCommandRegistry, createDelegateTool, FLEET_ROSTER, createMcpControlTool, EternalAutonomyEngine, DefaultLogger, DefaultModelsRegistry, ProviderRegistry, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, AutoCompactionMiddleware, estimateRequestTokens, Agent, loadPlugins, FleetManager, makeDirectorSessionFactory, Director, makeAgentSubagentRunner, NULL_FLEET_BUS, resolveWstackPaths, DefaultSecretVault, migratePlaintextSecrets, DefaultConfigLoader, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, atomicWrite, DefaultPluginAPI, AutoApprovePermissionPolicy, formatContextWindowModeList, repairToolUseAdjacency, getContextWindowMode, resolveContextWindowPolicy, formatTodosList, emptyPlan, clearPlan, savePlan, formatPlanTemplates, getPlanTemplate, addPlanItem, formatPlan, deriveTodosFromPlanItem, removePlanItem, setPlanItemStatus, SpecStore, TaskGraphStore,
|
|
6
|
+
import { color, DefaultPathResolver, TOKENS, DefaultSystemPromptBuilder, makeAutonomyPromptContributor, ToolRegistry, createContextManagerTool, EventBus, SlashCommandRegistry, createDelegateTool, FLEET_ROSTER, createMcpControlTool, EternalAutonomyEngine, DefaultLogger, DefaultModelsRegistry, ProviderRegistry, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, AutoCompactionMiddleware, estimateRequestTokens, Agent, loadPlugins, FleetManager, makeDirectorSessionFactory, Director, makeAgentSubagentRunner, NULL_FLEET_BUS, resolveWstackPaths, DefaultSecretVault, migratePlaintextSecrets, DefaultConfigLoader, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, atomicWrite, DefaultPluginAPI, AutoApprovePermissionPolicy, formatContextWindowModeList, repairToolUseAdjacency, getContextWindowMode, resolveContextWindowPolicy, formatTodosList, emptyPlan, clearPlan, savePlan, formatPlanTemplates, getPlanTemplate, addPlanItem, formatPlan, deriveTodosFromPlanItem, removePlanItem, setPlanItemStatus, SpecStore, TaskGraphStore, analyzeCriticalPath, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, DefaultTaskStore, TaskTracker, renderProgress, renderTaskGraph, loadGoal, goalFilePath, summarizeUsage, saveGoal, emptyGoal, buildGoalPreamble, formatGoal, InputBuilder, projectHash, defaultOrchestrator, decryptConfigSecrets, encryptConfigSecrets as encryptConfigSecrets$1, SpecVersioning, ParallelEternalEngine, allServers as allServers$1 } from '@wrongstack/core';
|
|
7
7
|
import { createRequire } from 'module';
|
|
8
8
|
import * as os6 from 'os';
|
|
9
9
|
import os6__default from 'os';
|
|
@@ -53,14 +53,18 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
53
53
|
// src/slash-commands/sdd.ts
|
|
54
54
|
var sdd_exports = {};
|
|
55
55
|
__export(sdd_exports, {
|
|
56
|
+
advanceToNextTask: () => advanceToNextTask,
|
|
56
57
|
autoDetectTaskCompletion: () => autoDetectTaskCompletion,
|
|
57
58
|
buildSddCommand: () => buildSddCommand,
|
|
58
59
|
getActiveBuilder: () => getActiveBuilder,
|
|
59
60
|
getActiveSDDContext: () => getActiveSDDContext,
|
|
60
61
|
getActiveSDDPhase: () => getActiveSDDPhase,
|
|
62
|
+
getCurrentExecutingContext: () => getCurrentExecutingContext,
|
|
63
|
+
getCurrentTask: () => getCurrentTask,
|
|
61
64
|
getTaskListText: () => getTaskListText,
|
|
62
65
|
getTaskProgress: () => getTaskProgress,
|
|
63
66
|
markTaskCompleted: () => markTaskCompleted,
|
|
67
|
+
renderTaskListWithProgress: () => renderTaskListWithProgress,
|
|
64
68
|
trySaveImplementationPlan: () => trySaveImplementationPlan,
|
|
65
69
|
trySaveSpecFromAIOutput: () => trySaveSpecFromAIOutput,
|
|
66
70
|
trySaveTasksFromAIOutput: () => trySaveTasksFromAIOutput
|
|
@@ -106,6 +110,27 @@ async function trySaveTasksFromAIOutput(aiOutput) {
|
|
|
106
110
|
if (!Array.isArray(tasks) || tasks.length === 0) return false;
|
|
107
111
|
const validTasks = tasks.filter((t) => t && typeof t === "object" && typeof t.title === "string" && t.title.length > 0);
|
|
108
112
|
if (validTasks.length === 0) return false;
|
|
113
|
+
const existingTracker = sddState.getTaskTracker();
|
|
114
|
+
if (existingTracker) {
|
|
115
|
+
for (const task of validTasks) {
|
|
116
|
+
const title = String(task.title);
|
|
117
|
+
const description = String(task.description ?? "");
|
|
118
|
+
const type = ["feature", "bugfix", "refactor", "docs", "test", "chore"].includes(String(task.type)) ? String(task.type) : "feature";
|
|
119
|
+
const priority = ["critical", "high", "medium", "low"].includes(String(task.priority)) ? String(task.priority) : "medium";
|
|
120
|
+
const estimateHours = Number(task.estimateHours) || 2;
|
|
121
|
+
const tags = Array.isArray(task.tags) ? task.tags.map(String) : [];
|
|
122
|
+
existingTracker.addNode({
|
|
123
|
+
title,
|
|
124
|
+
description,
|
|
125
|
+
type,
|
|
126
|
+
priority,
|
|
127
|
+
status: "pending",
|
|
128
|
+
estimateHours,
|
|
129
|
+
tags
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
109
134
|
const store = new DefaultTaskStore();
|
|
110
135
|
const tracker = new TaskTracker({ store });
|
|
111
136
|
const graph = await tracker.createGraph(session.spec.id, session.spec.title);
|
|
@@ -135,14 +160,47 @@ async function trySaveTasksFromAIOutput(aiOutput) {
|
|
|
135
160
|
function getTaskProgress() {
|
|
136
161
|
const tracker = sddState.getTaskTracker();
|
|
137
162
|
if (!tracker) return null;
|
|
138
|
-
|
|
163
|
+
return tracker.getProgress();
|
|
164
|
+
}
|
|
165
|
+
function getCurrentTask() {
|
|
166
|
+
const tracker = sddState.getTaskTracker();
|
|
167
|
+
if (!tracker) return null;
|
|
168
|
+
const nodes = tracker.getAllNodes({ status: ["in_progress"] });
|
|
169
|
+
if (nodes.length === 0) return null;
|
|
170
|
+
const n = nodes[0];
|
|
139
171
|
return {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
172
|
+
id: n.id,
|
|
173
|
+
title: n.title,
|
|
174
|
+
description: n.description,
|
|
175
|
+
priority: n.priority,
|
|
176
|
+
estimateHours: n.estimateHours ?? 0,
|
|
177
|
+
tags: n.tags ?? [],
|
|
178
|
+
startedAt: n.startedAt
|
|
144
179
|
};
|
|
145
180
|
}
|
|
181
|
+
function advanceToNextTask() {
|
|
182
|
+
const tracker = sddState.getTaskTracker();
|
|
183
|
+
if (!tracker) return false;
|
|
184
|
+
const pending = tracker.getAllNodes({ status: ["pending"] });
|
|
185
|
+
for (const n of pending) {
|
|
186
|
+
if (tracker.canStart(n.id)) {
|
|
187
|
+
tracker.updateNodeStatus(n.id, "in_progress");
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
function formatElapsed(ms) {
|
|
194
|
+
if (ms < 1e3) return `${ms}ms`;
|
|
195
|
+
const s = Math.floor(ms / 1e3);
|
|
196
|
+
if (s < 60) return `${s}s`;
|
|
197
|
+
const m = Math.floor(s / 60);
|
|
198
|
+
const remS = s % 60;
|
|
199
|
+
if (m < 60) return remS > 0 ? `${m}m ${remS}s` : `${m}m`;
|
|
200
|
+
const h = Math.floor(m / 60);
|
|
201
|
+
const remM = m % 60;
|
|
202
|
+
return `${h}h ${remM}m`;
|
|
203
|
+
}
|
|
146
204
|
function getTaskListText() {
|
|
147
205
|
const tracker = sddState.getTaskTracker();
|
|
148
206
|
if (!tracker) return null;
|
|
@@ -154,6 +212,58 @@ function getTaskListText() {
|
|
|
154
212
|
});
|
|
155
213
|
return lines.join("\n");
|
|
156
214
|
}
|
|
215
|
+
function renderTaskListWithProgress() {
|
|
216
|
+
const tracker = sddState.getTaskTracker();
|
|
217
|
+
if (!tracker) return null;
|
|
218
|
+
const nodes = tracker.getAllNodes();
|
|
219
|
+
if (nodes.length === 0) return null;
|
|
220
|
+
const progress = tracker.getProgress();
|
|
221
|
+
const phase = sddState.getPhase();
|
|
222
|
+
const phaseLabel = {
|
|
223
|
+
questioning: "\u2753 Questioning",
|
|
224
|
+
spec_review: "\u{1F4CB} Spec Review",
|
|
225
|
+
implementation: "\u{1F3D7}\uFE0F Implementation",
|
|
226
|
+
task_review: "\u{1F4DD} Task Review",
|
|
227
|
+
executing: "\u26A1 Executing",
|
|
228
|
+
done: "\u2705 Done"
|
|
229
|
+
};
|
|
230
|
+
const lines = [
|
|
231
|
+
`**${phaseLabel[phase ?? ""] ?? phase} \u2014 Task Status**`,
|
|
232
|
+
"",
|
|
233
|
+
renderProgress(progress),
|
|
234
|
+
""
|
|
235
|
+
];
|
|
236
|
+
const sorted = [...nodes].sort((a, b) => {
|
|
237
|
+
const order = { in_progress: 0, pending: 1, review: 2, blocked: 3, failed: 4, completed: 5 };
|
|
238
|
+
return (order[a.status] ?? 6) - (order[b.status] ?? 6);
|
|
239
|
+
});
|
|
240
|
+
for (let i = 0; i < sorted.length; i++) {
|
|
241
|
+
const n = sorted[i];
|
|
242
|
+
const status = n.status === "completed" ? "\u2705" : n.status === "in_progress" ? "\u{1F504}" : n.status === "failed" ? "\u274C" : n.status === "blocked" ? "\u{1F6AB}" : n.status === "review" ? "\u{1F441}" : "\u23F3";
|
|
243
|
+
const title = n.title.length > 50 ? n.title.slice(0, 49) + "\u2026" : n.title;
|
|
244
|
+
let elapsed = "";
|
|
245
|
+
if (n.status === "in_progress" && n.startedAt) {
|
|
246
|
+
elapsed = ` \xB7 ${formatElapsed(Date.now() - n.startedAt)}`;
|
|
247
|
+
}
|
|
248
|
+
lines.push(`${i + 1}. ${status} ${title}${elapsed}`);
|
|
249
|
+
}
|
|
250
|
+
return lines.join("\n");
|
|
251
|
+
}
|
|
252
|
+
function getCurrentExecutingContext() {
|
|
253
|
+
const tracker = sddState.getTaskTracker();
|
|
254
|
+
if (!tracker) return null;
|
|
255
|
+
const nodes = tracker.getAllNodes({ status: ["in_progress"] });
|
|
256
|
+
if (nodes.length === 0) return null;
|
|
257
|
+
const n = nodes[0];
|
|
258
|
+
const elapsed = n.startedAt ? ` \xB7 elapsed: ${formatElapsed(Date.now() - n.startedAt)}` : "";
|
|
259
|
+
const progress = tracker.getProgress();
|
|
260
|
+
return [
|
|
261
|
+
`**NOW EXECUTING:** "${n.title}"${elapsed}`,
|
|
262
|
+
`Description: ${n.description.split("\n")[0] ?? "(none)"}`,
|
|
263
|
+
`Priority: ${n.priority} \xB7 Est: ${n.estimateHours ?? 0}h \xB7 Tags: ${(n.tags ?? []).join(", ") || "none"}`,
|
|
264
|
+
`Progress: ${progress.completed}/${progress.total} tasks (${progress.percentComplete}%)`
|
|
265
|
+
].join("\n");
|
|
266
|
+
}
|
|
157
267
|
function markTaskCompleted(taskTitle) {
|
|
158
268
|
const tracker = sddState.getTaskTracker();
|
|
159
269
|
if (!tracker) return false;
|
|
@@ -238,25 +348,31 @@ function trySaveImplementationPlan(aiOutput) {
|
|
|
238
348
|
if (!builder) return false;
|
|
239
349
|
const session = builder.getSession();
|
|
240
350
|
if (session.phase !== "implementation") return false;
|
|
351
|
+
const current = session.implementation ?? "";
|
|
241
352
|
const jsonMatch = aiOutput.match(/```json\s*\[/);
|
|
242
353
|
if (jsonMatch?.index && jsonMatch.index > 0) {
|
|
243
354
|
const plan = aiOutput.substring(0, jsonMatch.index).trim();
|
|
244
|
-
if (plan.length > 50) {
|
|
355
|
+
if (plan.length > 50 && plan !== current && !isExplanatoryText(plan)) {
|
|
245
356
|
builder.setImplementation(plan);
|
|
246
357
|
return true;
|
|
247
358
|
}
|
|
248
359
|
}
|
|
249
|
-
if (aiOutput.length > 100 && !aiOutput.includes("```json")) {
|
|
360
|
+
if (aiOutput.length > 100 && !aiOutput.includes("```json") && aiOutput !== current && !isExplanatoryText(aiOutput)) {
|
|
250
361
|
builder.setImplementation(aiOutput.trim());
|
|
251
362
|
return true;
|
|
252
363
|
}
|
|
253
364
|
return false;
|
|
254
365
|
}
|
|
366
|
+
function isExplanatoryText(text) {
|
|
367
|
+
const lower = text.toLowerCase();
|
|
368
|
+
return lower.startsWith("i'") || lower.startsWith("i will") || lower.startsWith("let me") || lower.startsWith("here's my") || lower.startsWith("here is my") || lower.startsWith("i'm going to") || lower.startsWith("first, let me") || lower.startsWith("sure") || lower.startsWith("of course") || lower.startsWith("okay") || lower.startsWith("ok,") || lower.startsWith("sounds good") || lower.startsWith("no problem") || // Skip if mostly code-like with minimal prose
|
|
369
|
+
text.split("\n").length < 3 && !text.includes(".");
|
|
370
|
+
}
|
|
255
371
|
function getActiveBuilder() {
|
|
256
372
|
return sddState.getBuilder();
|
|
257
373
|
}
|
|
258
374
|
function buildSddCommand(opts) {
|
|
259
|
-
getSessionState(opts.context);
|
|
375
|
+
const sessionState = getSessionState(opts.context);
|
|
260
376
|
return {
|
|
261
377
|
name: "sdd",
|
|
262
378
|
description: "AI-driven SDD: /sdd [new|approve|execute|cancel|status|list|show|templates]",
|
|
@@ -267,7 +383,7 @@ function buildSddCommand(opts) {
|
|
|
267
383
|
const graphsDir = path23.join(projectRoot, ".wrongstack", "task-graphs");
|
|
268
384
|
const specStore = new SpecStore({ baseDir: specsDir });
|
|
269
385
|
new TaskGraphStore({ baseDir: graphsDir });
|
|
270
|
-
const versioning =
|
|
386
|
+
const versioning = sddState.getVersioning();
|
|
271
387
|
const [verb, ...rest] = args.trim().split(/\s+/);
|
|
272
388
|
const restJoined = rest.join(" ").trim();
|
|
273
389
|
switch (verb) {
|
|
@@ -279,7 +395,7 @@ function buildSddCommand(opts) {
|
|
|
279
395
|
case "create": {
|
|
280
396
|
const forceFlag = rest.includes("--force") || rest.includes("-f");
|
|
281
397
|
const title = rest.filter((a) => !a.startsWith("-")).join(" ").trim() || "Untitled Feature";
|
|
282
|
-
if (!
|
|
398
|
+
if (!sessionState.getBuilder() && !forceFlag) {
|
|
283
399
|
const sessionPath = path23.join(projectRoot, ".wrongstack", "sdd-session.json");
|
|
284
400
|
try {
|
|
285
401
|
await fsp2.access(sessionPath);
|
|
@@ -317,6 +433,8 @@ function buildSddCommand(opts) {
|
|
|
317
433
|
maxQuestions: 10,
|
|
318
434
|
sessionPath: path23.join(projectRoot, ".wrongstack", "sdd-session.json")
|
|
319
435
|
}));
|
|
436
|
+
sddState.setSessionStartTime(Date.now());
|
|
437
|
+
sddState.setPhaseStartTime(Date.now());
|
|
320
438
|
const builder = sddState.getBuilder();
|
|
321
439
|
builder.startSession(title);
|
|
322
440
|
const aiPrompt = builder.getAIPrompt();
|
|
@@ -370,6 +488,7 @@ Generate the complete specification now based on the conversation so far.`
|
|
|
370
488
|
await builder.saveSpec();
|
|
371
489
|
versioning.recordVersion(spec, "Initial spec approved");
|
|
372
490
|
builder.approve();
|
|
491
|
+
sddState.setPhaseStartTime(Date.now());
|
|
373
492
|
const implPrompt = builder.getAIPrompt();
|
|
374
493
|
return {
|
|
375
494
|
message: [
|
|
@@ -389,6 +508,8 @@ Generate the implementation plan and tasks for the approved spec.`
|
|
|
389
508
|
}
|
|
390
509
|
if (phase === "task_review") {
|
|
391
510
|
builder.approve();
|
|
511
|
+
sddState.setPhaseStartTime(Date.now());
|
|
512
|
+
advanceToNextTask();
|
|
392
513
|
const execPrompt = builder.getAIPrompt();
|
|
393
514
|
return {
|
|
394
515
|
message: "\u2705 Tasks approved! The AI will now execute them one by one.",
|
|
@@ -400,6 +521,24 @@ User message:
|
|
|
400
521
|
Start executing the tasks one by one.`
|
|
401
522
|
};
|
|
402
523
|
}
|
|
524
|
+
if (phase === "implementation") {
|
|
525
|
+
const session = builder.getSession();
|
|
526
|
+
const plan = session.implementation;
|
|
527
|
+
if (!plan) {
|
|
528
|
+
return {
|
|
529
|
+
message: "No implementation plan yet. The AI is still generating it. Try again shortly."
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
return {
|
|
533
|
+
message: [
|
|
534
|
+
`\u256D\u2500\u2500\u2500 Implementation Plan \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E`,
|
|
535
|
+
"",
|
|
536
|
+
...plan.split("\n").map((l) => ` ${l}`),
|
|
537
|
+
"",
|
|
538
|
+
`\u2570${"\u2500".repeat(55)}\u256F`
|
|
539
|
+
].join("\n")
|
|
540
|
+
};
|
|
541
|
+
}
|
|
403
542
|
return {
|
|
404
543
|
message: `Current phase is "${phase}". Use /sdd status to see details.`
|
|
405
544
|
};
|
|
@@ -492,18 +631,46 @@ Start executing the tasks one by one.`
|
|
|
492
631
|
return { message: "No tasks in the current graph." };
|
|
493
632
|
}
|
|
494
633
|
const progress = taskTracker.getProgress();
|
|
634
|
+
const builder = sddState.getBuilder();
|
|
635
|
+
const phase = builder?.getPhase() ?? "unknown";
|
|
636
|
+
const phaseLabel = {
|
|
637
|
+
questioning: "\u2753 Questioning",
|
|
638
|
+
spec_review: "\u{1F4CB} Spec Review",
|
|
639
|
+
implementation: "\u{1F3D7}\uFE0F Implementation",
|
|
640
|
+
task_review: "\u{1F4DD} Task Review",
|
|
641
|
+
executing: "\u26A1 Executing",
|
|
642
|
+
done: "\u2705 Done"
|
|
643
|
+
};
|
|
495
644
|
const lines = [
|
|
496
|
-
`\
|
|
497
|
-
""
|
|
645
|
+
`\u256D\u2500\u2500\u2500 ${phaseLabel[phase] ?? phase} \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E`,
|
|
646
|
+
"",
|
|
647
|
+
renderProgress(progress),
|
|
648
|
+
"",
|
|
649
|
+
` # Status Priority Task`,
|
|
650
|
+
` ${"\u2500".repeat(49)}`
|
|
498
651
|
];
|
|
499
|
-
|
|
500
|
-
const
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
652
|
+
const sorted = [...nodes].sort((a, b) => {
|
|
653
|
+
const order = { in_progress: 0, pending: 1, review: 2, blocked: 3, failed: 4, completed: 5 };
|
|
654
|
+
return (order[a.status] ?? 6) - (order[b.status] ?? 6);
|
|
655
|
+
});
|
|
656
|
+
for (let i = 0; i < sorted.length; i++) {
|
|
657
|
+
const n = sorted[i];
|
|
658
|
+
const status = n.status === "completed" ? "\u2705" : n.status === "in_progress" ? "\u{1F504}" : n.status === "failed" ? "\u274C" : n.status === "blocked" ? "\u{1F6AB}" : n.status === "review" ? "\u{1F441}" : "\u23F3";
|
|
659
|
+
const num = `${i + 1}`.padStart(3);
|
|
660
|
+
const prio = n.priority.slice(0, 4).padEnd(5);
|
|
661
|
+
const title = n.title.length > 36 ? n.title.slice(0, 35) + "\u2026" : n.title;
|
|
662
|
+
const elapsed = n.status === "in_progress" && n.startedAt ? ` (${formatElapsed(Date.now() - n.startedAt)})` : "";
|
|
663
|
+
lines.push(` ${num} ${status} ${prio} ${title}${elapsed}`);
|
|
664
|
+
if (n.description && n.status !== "completed") {
|
|
665
|
+
const first = n.description.split("\n")[0];
|
|
666
|
+
const truncated = first.length > 42 ? first.slice(0, 41) + "\u2026" : first;
|
|
667
|
+
lines.push(` \u21B3 ${truncated}`);
|
|
505
668
|
}
|
|
506
669
|
}
|
|
670
|
+
lines.push("");
|
|
671
|
+
lines.push(` Commands: /sdd done <N> \xB7 /sdd skip <N> \xB7 /sdd fail <N> \xB7 /sdd review <N>`);
|
|
672
|
+
lines.push(` /sdd next \xB7 /sdd status \xB7 /sdd edit <N> \xB7 /sdd approve`);
|
|
673
|
+
lines.push(`\u2570${"\u2500".repeat(54)}\u256F`);
|
|
507
674
|
return { message: lines.join("\n") };
|
|
508
675
|
}
|
|
509
676
|
case "done":
|
|
@@ -539,9 +706,194 @@ Start executing the tasks one by one.`
|
|
|
539
706
|
}
|
|
540
707
|
const remaining = doneTracker.getProgress();
|
|
541
708
|
return {
|
|
542
|
-
message: `\u2705 Task
|
|
709
|
+
message: `\u2705 Task marked done! (${remaining.completed}/${remaining.total} \u2014 ${remaining.percentComplete}%)`
|
|
543
710
|
};
|
|
544
711
|
}
|
|
712
|
+
case "skip": {
|
|
713
|
+
const skipTracker = sddState.getTaskTracker();
|
|
714
|
+
if (!skipTracker) return { message: "No tasks to skip." };
|
|
715
|
+
if (!restJoined) return { message: "Usage: /sdd skip <task title or number>" };
|
|
716
|
+
const nodes = skipTracker.getAllNodes({ status: ["pending", "in_progress", "blocked"] });
|
|
717
|
+
const num = Number(restJoined);
|
|
718
|
+
let matched = false;
|
|
719
|
+
if (!Number.isNaN(num) && num >= 1 && num <= nodes.length) {
|
|
720
|
+
const node = nodes[num - 1];
|
|
721
|
+
if (node) {
|
|
722
|
+
skipTracker.updateNodeStatus(node.id, "pending");
|
|
723
|
+
matched = true;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
if (!matched) {
|
|
727
|
+
const match = nodes.find(
|
|
728
|
+
(n) => n.title.toLowerCase().includes(restJoined.toLowerCase()) || restJoined.toLowerCase().includes(n.title.toLowerCase())
|
|
729
|
+
);
|
|
730
|
+
if (match) {
|
|
731
|
+
skipTracker.updateNodeStatus(match.id, "pending");
|
|
732
|
+
matched = true;
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
if (!matched) return { message: `No task matching "${restJoined}".` };
|
|
736
|
+
const progress = skipTracker.getProgress();
|
|
737
|
+
return {
|
|
738
|
+
message: `\u23ED Task skipped \u2014 moved to pending. (${progress.completed}/${progress.total} \u2014 ${progress.percentComplete}%)`
|
|
739
|
+
};
|
|
740
|
+
}
|
|
741
|
+
case "fail": {
|
|
742
|
+
const failTracker = sddState.getTaskTracker();
|
|
743
|
+
if (!failTracker) return { message: "No tasks to fail." };
|
|
744
|
+
if (!restJoined) return { message: "Usage: /sdd fail <task title or number>" };
|
|
745
|
+
const nodes = failTracker.getAllNodes({ status: ["pending", "in_progress"] });
|
|
746
|
+
const num = Number(restJoined);
|
|
747
|
+
let matched = false;
|
|
748
|
+
if (!Number.isNaN(num) && num >= 1 && num <= nodes.length) {
|
|
749
|
+
const node = nodes[num - 1];
|
|
750
|
+
if (node) {
|
|
751
|
+
failTracker.updateNodeStatus(node.id, "failed");
|
|
752
|
+
matched = true;
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
if (!matched) {
|
|
756
|
+
const match = nodes.find(
|
|
757
|
+
(n) => n.title.toLowerCase().includes(restJoined.toLowerCase()) || restJoined.toLowerCase().includes(n.title.toLowerCase())
|
|
758
|
+
);
|
|
759
|
+
if (match) {
|
|
760
|
+
failTracker.updateNodeStatus(match.id, "failed");
|
|
761
|
+
matched = true;
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
if (!matched) return { message: `No pending/in-progress task matching "${restJoined}".` };
|
|
765
|
+
const progress = failTracker.getProgress();
|
|
766
|
+
return {
|
|
767
|
+
message: `\u274C Task marked as failed. (${progress.failed} failed \xB7 ${progress.completed}/${progress.total} done)`
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
case "review": {
|
|
771
|
+
const reviewTracker = sddState.getTaskTracker();
|
|
772
|
+
if (!reviewTracker) return { message: "No tasks to review." };
|
|
773
|
+
if (!restJoined) return { message: "Usage: /sdd review <task title or number>" };
|
|
774
|
+
const nodes = reviewTracker.getAllNodes();
|
|
775
|
+
const num = Number(restJoined);
|
|
776
|
+
let matched = false;
|
|
777
|
+
const sorted = [...nodes].sort((a, b) => {
|
|
778
|
+
const order = { in_progress: 0, pending: 1, review: 2, blocked: 3, failed: 4, completed: 5 };
|
|
779
|
+
return (order[a.status] ?? 6) - (order[b.status] ?? 6);
|
|
780
|
+
});
|
|
781
|
+
if (!Number.isNaN(num) && num >= 1 && num <= sorted.length) {
|
|
782
|
+
const node = sorted[num - 1];
|
|
783
|
+
if (node) {
|
|
784
|
+
reviewTracker.updateNodeStatus(node.id, "review");
|
|
785
|
+
matched = true;
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
if (!matched) {
|
|
789
|
+
const match = nodes.find(
|
|
790
|
+
(n) => n.title.toLowerCase().includes(restJoined.toLowerCase()) || restJoined.toLowerCase().includes(n.title.toLowerCase())
|
|
791
|
+
);
|
|
792
|
+
if (match) {
|
|
793
|
+
reviewTracker.updateNodeStatus(match.id, "review");
|
|
794
|
+
matched = true;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
if (!matched) return { message: `No task matching "${restJoined}".` };
|
|
798
|
+
const progress = reviewTracker.getProgress();
|
|
799
|
+
return {
|
|
800
|
+
message: `\u{1F441} Task sent to review. (${progress.review} in review)`
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
case "edit": {
|
|
804
|
+
const editTracker = sddState.getTaskTracker();
|
|
805
|
+
if (!editTracker) return { message: "No tasks to edit." };
|
|
806
|
+
if (!restJoined) return { message: "Usage: /sdd edit <N> <new title or description>" };
|
|
807
|
+
const parts = restJoined.split(/\s+/);
|
|
808
|
+
const num = Number(parts[0]);
|
|
809
|
+
if (Number.isNaN(num)) return { message: "Usage: /sdd edit <N> <new title or description>" };
|
|
810
|
+
const nodes = editTracker.getAllNodes();
|
|
811
|
+
if (num < 1 || num > nodes.length) return { message: `Task #${num} not found.` };
|
|
812
|
+
const node = nodes[num - 1];
|
|
813
|
+
if (!node) return { message: `Task #${num} not found.` };
|
|
814
|
+
const newContent = parts.slice(1).join(" ");
|
|
815
|
+
if (!newContent) return { message: "Provide new title or description content." };
|
|
816
|
+
if (newContent.length < 60) {
|
|
817
|
+
editTracker.updateNode(node.id, { title: newContent });
|
|
818
|
+
} else {
|
|
819
|
+
editTracker.updateNode(node.id, { description: newContent });
|
|
820
|
+
}
|
|
821
|
+
return { message: `\u270F\uFE0F Task #${num} updated: "${newContent.slice(0, 50)}${newContent.length > 50 ? "\u2026" : ""}"` };
|
|
822
|
+
}
|
|
823
|
+
case "undo": {
|
|
824
|
+
const undoTracker = sddState.getTaskTracker();
|
|
825
|
+
if (!undoTracker) {
|
|
826
|
+
return { message: "No tasks to undo." };
|
|
827
|
+
}
|
|
828
|
+
const completed = undoTracker.getAllNodes({ status: ["completed"] });
|
|
829
|
+
if (completed.length === 0) {
|
|
830
|
+
return { message: "No completed tasks to undo." };
|
|
831
|
+
}
|
|
832
|
+
const last = completed[completed.length - 1];
|
|
833
|
+
undoTracker.updateNodeStatus(last.id, "pending");
|
|
834
|
+
const progress = undoTracker.getProgress();
|
|
835
|
+
return {
|
|
836
|
+
message: `\u21A9 Undo: "${last.title}" back to pending. (${progress.completed}/${progress.total} \u2014 ${progress.percentComplete}%)`
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
// ── Next Task Preview ─────────────────────────────────────────────
|
|
840
|
+
case "next": {
|
|
841
|
+
const nextTracker = sddState.getTaskTracker();
|
|
842
|
+
if (!nextTracker) {
|
|
843
|
+
return { message: "No tasks generated yet. Use /sdd new to start." };
|
|
844
|
+
}
|
|
845
|
+
const pending = nextTracker.getAllNodes({ status: ["pending", "in_progress"] });
|
|
846
|
+
if (pending.length === 0) {
|
|
847
|
+
const allDone = nextTracker.getProgress();
|
|
848
|
+
if (allDone.completed === allDone.total) {
|
|
849
|
+
return { message: "\u{1F389} All tasks completed! Run /sdd status for the full summary." };
|
|
850
|
+
}
|
|
851
|
+
return { message: "No pending tasks." };
|
|
852
|
+
}
|
|
853
|
+
const next = pending.find((n) => nextTracker.canStart(n.id));
|
|
854
|
+
if (!next) {
|
|
855
|
+
const blocked = pending.filter((n) => {
|
|
856
|
+
const blockers2 = nextTracker.getBlockers(n.id);
|
|
857
|
+
return blockers2.some((id) => nextTracker.getNode(id)?.status !== "completed");
|
|
858
|
+
});
|
|
859
|
+
if (blocked.length > 0) {
|
|
860
|
+
return {
|
|
861
|
+
message: [
|
|
862
|
+
`\u{1F6AB} ${blocked.length} task(s) blocked \u2014 waiting on dependencies:`,
|
|
863
|
+
...blocked.map((b, i) => {
|
|
864
|
+
const blockers2 = nextTracker.getBlockers(b.id);
|
|
865
|
+
const blockerNames = blockers2.map((id) => nextTracker.getNode(id)?.title ?? "?").join(", ");
|
|
866
|
+
return ` ${i + 1}. ${b.title} (blocked by: ${blockerNames})`;
|
|
867
|
+
})
|
|
868
|
+
].join("\n")
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
return { message: "No next task found." };
|
|
872
|
+
}
|
|
873
|
+
const progress = nextTracker.getProgress();
|
|
874
|
+
const blockers = nextTracker.getBlockers(next.id);
|
|
875
|
+
const blockedBy = blockers.filter((id) => nextTracker.getNode(id)?.status !== "completed").map((id) => nextTracker.getNode(id)?.title ?? "?").join(", ");
|
|
876
|
+
const lines = [
|
|
877
|
+
`\u256D\u2500\u2500\u2500 NEXT TASK \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E`,
|
|
878
|
+
"",
|
|
879
|
+
` \u{1F504} ${next.title}`
|
|
880
|
+
];
|
|
881
|
+
if (next.description) {
|
|
882
|
+
const first = next.description.split("\n")[0];
|
|
883
|
+
lines.push(` \u21B3 ${first}`);
|
|
884
|
+
}
|
|
885
|
+
const taskElapsed = next.startedAt ? ` \u23F1 ${formatElapsed(Date.now() - next.startedAt)}` : "";
|
|
886
|
+
lines.push(` Priority: ${next.priority} | Est: ${next.estimateHours}h | Tags: ${(next.tags ?? []).join(", ") || "none"}${taskElapsed}`);
|
|
887
|
+
if (blockedBy) {
|
|
888
|
+
lines.push(` Blocked by: ${blockedBy}`);
|
|
889
|
+
}
|
|
890
|
+
lines.push("");
|
|
891
|
+
lines.push(` \u2500\u2500 Progress: ${progress.completed}/${progress.total} (${progress.percentComplete}%) \u2500\u2500`);
|
|
892
|
+
lines.push("");
|
|
893
|
+
lines.push(` Run /sdd done <task title or number> when done.`);
|
|
894
|
+
lines.push(`\u2570${"\u2500".repeat(55)}\u256F`);
|
|
895
|
+
return { message: lines.join("\n") };
|
|
896
|
+
}
|
|
545
897
|
// ── Session Management ─────────────────────────────────────────────
|
|
546
898
|
case "status": {
|
|
547
899
|
const statusBuilder = sddState.getBuilder();
|
|
@@ -557,31 +909,119 @@ Start executing the tasks one by one.`
|
|
|
557
909
|
executing: "\u26A1",
|
|
558
910
|
done: "\u2705"
|
|
559
911
|
};
|
|
912
|
+
const phaseLabel = {
|
|
913
|
+
questioning: "Questioning",
|
|
914
|
+
spec_review: "Spec Review",
|
|
915
|
+
implementation: "Implementation",
|
|
916
|
+
task_review: "Task Review",
|
|
917
|
+
executing: "Executing",
|
|
918
|
+
done: "Done"
|
|
919
|
+
};
|
|
560
920
|
const progress = getTaskProgress();
|
|
921
|
+
const sessionElapsed = sddState.getSessionElapsed();
|
|
922
|
+
const phaseElapsed = sddState.getPhaseElapsed();
|
|
561
923
|
const lines = [
|
|
562
|
-
|
|
924
|
+
`\u256D\u2500\u2500\u2500 SDD: ${session.title} \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E`,
|
|
563
925
|
"",
|
|
564
|
-
`
|
|
565
|
-
`
|
|
566
|
-
`Questions asked: ${session.questionCount}`
|
|
926
|
+
` ${phaseEmoji[session.phase]} Phase: ${phaseLabel[session.phase]} \u23F1 ${formatElapsed(phaseElapsed)}`,
|
|
927
|
+
` \u23F1 Session: ${formatElapsed(sessionElapsed)} | \u275D Questions: ${session.questionCount}`
|
|
567
928
|
];
|
|
568
929
|
if (session.spec) {
|
|
569
|
-
lines.push(
|
|
570
|
-
lines.push(`
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
const
|
|
574
|
-
|
|
930
|
+
lines.push("");
|
|
931
|
+
lines.push(` \u{1F4CB} Spec: ${session.spec.title}`);
|
|
932
|
+
lines.push(` ${session.spec.requirements.length} requirements`);
|
|
933
|
+
const reqs = session.spec.requirements.slice(0, 4);
|
|
934
|
+
for (const r of reqs) {
|
|
935
|
+
lines.push(` \u2022 [${r.priority}] ${r.description.length > 42 ? r.description.slice(0, 41) + "\u2026" : r.description}`);
|
|
936
|
+
}
|
|
937
|
+
if (session.spec.requirements.length > 4) {
|
|
938
|
+
lines.push(` + ${session.spec.requirements.length - 4} more requirements`);
|
|
939
|
+
}
|
|
575
940
|
}
|
|
576
941
|
if (progress && progress.total > 0) {
|
|
577
|
-
lines.push(
|
|
942
|
+
lines.push("");
|
|
943
|
+
lines.push(renderProgress(progress));
|
|
944
|
+
lines.push(` Task breakdown:`);
|
|
945
|
+
if (progress.inProgress > 0) lines.push(` \u{1F504} ${progress.inProgress} in progress`);
|
|
946
|
+
if (progress.pending > 0) lines.push(` \u23F3 ${progress.pending} pending`);
|
|
947
|
+
if (progress.blocked > 0) lines.push(` \u{1F6AB} ${progress.blocked} blocked`);
|
|
948
|
+
if (progress.failed > 0) lines.push(` \u274C ${progress.failed} failed`);
|
|
949
|
+
if (progress.review > 0) lines.push(` \u{1F441} ${progress.review} in review`);
|
|
950
|
+
const tracker = sddState.getTaskTracker();
|
|
951
|
+
if (tracker) {
|
|
952
|
+
const pending = tracker.getAllNodes({ status: ["pending", "in_progress"] });
|
|
953
|
+
const nextTasks = pending.filter((n) => n.status === "pending" && tracker.canStart(n.id)).slice(0, 3);
|
|
954
|
+
if (nextTasks.length > 0) {
|
|
955
|
+
lines.push("");
|
|
956
|
+
lines.push(` Up next:`);
|
|
957
|
+
nextTasks.forEach((t, i) => {
|
|
958
|
+
lines.push(` ${i + 1}. ${t.title}`);
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
lines.push("");
|
|
963
|
+
lines.push(` Commands: /sdd tasks \xB7 /sdd next \xB7 /sdd approve \xB7 /sdd cancel`);
|
|
964
|
+
} else {
|
|
965
|
+
lines.push("");
|
|
966
|
+
lines.push(` Commands: /sdd plan \xB7 /sdd approve \xB7 /sdd cancel`);
|
|
578
967
|
}
|
|
579
|
-
lines.push(""
|
|
580
|
-
lines.push("
|
|
968
|
+
lines.push(`\u2570${"\u2500".repeat(56)}\u256F`);
|
|
969
|
+
lines.push("");
|
|
970
|
+
lines.push(` Session ID: ${session.id.slice(0, 8)}\u2026`);
|
|
581
971
|
return {
|
|
582
972
|
message: lines.join("\n")
|
|
583
973
|
};
|
|
584
974
|
}
|
|
975
|
+
// ── Task Graph Visualization ──────────────────────────────────────
|
|
976
|
+
case "graph": {
|
|
977
|
+
const graphTracker = sddState.getTaskTracker();
|
|
978
|
+
if (!graphTracker) {
|
|
979
|
+
return { message: "No tasks generated yet. Use /sdd new to start." };
|
|
980
|
+
}
|
|
981
|
+
const graphId = sddState.getTaskGraphId();
|
|
982
|
+
if (!graphId) {
|
|
983
|
+
const nodes2 = graphTracker.getAllNodes();
|
|
984
|
+
if (nodes2.length === 0) {
|
|
985
|
+
return { message: "No tasks in the current graph." };
|
|
986
|
+
}
|
|
987
|
+
const progress2 = graphTracker.getProgress();
|
|
988
|
+
const lines2 = [renderProgress(progress2), ""];
|
|
989
|
+
const sorted2 = [...nodes2].sort((a, b) => {
|
|
990
|
+
const order = { in_progress: 0, pending: 1, review: 2, blocked: 3, failed: 4, completed: 5 };
|
|
991
|
+
return (order[a.status] ?? 6) - (order[b.status] ?? 6);
|
|
992
|
+
});
|
|
993
|
+
for (let i = 0; i < sorted2.length; i++) {
|
|
994
|
+
const n = sorted2[i];
|
|
995
|
+
const status = n.status === "completed" ? "\u2705" : n.status === "in_progress" ? "\u{1F504}" : n.status === "failed" ? "\u274C" : n.status === "blocked" ? "\u{1F6AB}" : n.status === "review" ? "\u{1F441}" : "\u23F3";
|
|
996
|
+
lines2.push(`${i + 1}. ${status} [${n.priority}] ${n.title}`);
|
|
997
|
+
}
|
|
998
|
+
return { message: lines2.join("\n") };
|
|
999
|
+
}
|
|
1000
|
+
try {
|
|
1001
|
+
const graphStore2 = new TaskGraphStore({ baseDir: path23.join(projectRoot, ".wrongstack", "task-graphs") });
|
|
1002
|
+
const stored = await graphStore2.load(graphId);
|
|
1003
|
+
if (stored) {
|
|
1004
|
+
return { message: renderTaskGraph(stored, { compact: false }) };
|
|
1005
|
+
}
|
|
1006
|
+
} catch {
|
|
1007
|
+
}
|
|
1008
|
+
const nodes = graphTracker.getAllNodes();
|
|
1009
|
+
if (nodes.length === 0) {
|
|
1010
|
+
return { message: "No tasks in the current graph." };
|
|
1011
|
+
}
|
|
1012
|
+
const progress = graphTracker.getProgress();
|
|
1013
|
+
const lines = [renderProgress(progress), ""];
|
|
1014
|
+
const sorted = [...nodes].sort((a, b) => {
|
|
1015
|
+
const order = { in_progress: 0, pending: 1, review: 2, blocked: 3, failed: 4, completed: 5 };
|
|
1016
|
+
return (order[a.status] ?? 6) - (order[b.status] ?? 6);
|
|
1017
|
+
});
|
|
1018
|
+
for (let i = 0; i < sorted.length; i++) {
|
|
1019
|
+
const n = sorted[i];
|
|
1020
|
+
const status = n.status === "completed" ? "\u2705" : n.status === "in_progress" ? "\u{1F504}" : n.status === "failed" ? "\u274C" : n.status === "blocked" ? "\u{1F6AB}" : n.status === "review" ? "\u{1F441}" : "\u23F3";
|
|
1021
|
+
lines.push(`${i + 1}. ${status} [${n.priority}] ${n.title}`);
|
|
1022
|
+
}
|
|
1023
|
+
return { message: lines.join("\n") };
|
|
1024
|
+
}
|
|
585
1025
|
case "cancel": {
|
|
586
1026
|
const sessionPath = path23.join(projectRoot, ".wrongstack", "sdd-session.json");
|
|
587
1027
|
let deletedFromDisk = false;
|
|
@@ -590,6 +1030,14 @@ Start executing the tasks one by one.`
|
|
|
590
1030
|
deletedFromDisk = true;
|
|
591
1031
|
} catch {
|
|
592
1032
|
}
|
|
1033
|
+
try {
|
|
1034
|
+
await fsp2.rm(path23.join(projectRoot, ".wrongstack", "specs"), { recursive: true, force: true });
|
|
1035
|
+
} catch {
|
|
1036
|
+
}
|
|
1037
|
+
try {
|
|
1038
|
+
await fsp2.rm(path23.join(projectRoot, ".wrongstack", "task-graphs"), { recursive: true, force: true });
|
|
1039
|
+
} catch {
|
|
1040
|
+
}
|
|
593
1041
|
const cancelBuilder = sddState.getBuilder();
|
|
594
1042
|
if (cancelBuilder) {
|
|
595
1043
|
const title = cancelBuilder.getSession().title;
|
|
@@ -760,6 +1208,73 @@ Available: ${listTemplates().map((t) => t.id).join(", ")}`
|
|
|
760
1208
|
${lines.join("\n")}`
|
|
761
1209
|
};
|
|
762
1210
|
}
|
|
1211
|
+
case "critical":
|
|
1212
|
+
case "bottleneck": {
|
|
1213
|
+
const critTracker = sddState.getTaskTracker();
|
|
1214
|
+
if (!critTracker) {
|
|
1215
|
+
return { message: "No tasks generated yet. Use /sdd new to start." };
|
|
1216
|
+
}
|
|
1217
|
+
const graphId = sddState.getTaskGraphId();
|
|
1218
|
+
if (!graphId) {
|
|
1219
|
+
return { message: "No task graph found. Generate tasks first." };
|
|
1220
|
+
}
|
|
1221
|
+
try {
|
|
1222
|
+
const graphStore2 = new TaskGraphStore({ baseDir: path23.join(projectRoot, ".wrongstack", "task-graphs") });
|
|
1223
|
+
const graph = await graphStore2.load(graphId);
|
|
1224
|
+
if (!graph) {
|
|
1225
|
+
return { message: "Could not load task graph." };
|
|
1226
|
+
}
|
|
1227
|
+
const analysis = analyzeCriticalPath(graph);
|
|
1228
|
+
const lines = [
|
|
1229
|
+
`\u256D\u2500\u2500\u2500 Critical Path Analysis \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E`,
|
|
1230
|
+
"",
|
|
1231
|
+
` Critical path length: ${analysis.criticalPath.length} tasks`,
|
|
1232
|
+
` Estimated total time: ${analysis.totalHours}h`,
|
|
1233
|
+
""
|
|
1234
|
+
];
|
|
1235
|
+
if (analysis.criticalPath.length > 0) {
|
|
1236
|
+
lines.push(` \u{1F534} Critical path:`);
|
|
1237
|
+
analysis.criticalPath.forEach((taskId, i) => {
|
|
1238
|
+
const node = graph.nodes.get(taskId);
|
|
1239
|
+
if (node) {
|
|
1240
|
+
lines.push(` ${i + 1}. ${node.title} [${node.priority}] \u2014 ${node.estimateHours}h`);
|
|
1241
|
+
}
|
|
1242
|
+
});
|
|
1243
|
+
}
|
|
1244
|
+
if (analysis.bottlenecks.length > 0) {
|
|
1245
|
+
lines.push("");
|
|
1246
|
+
lines.push(` \u{1F6AB} Bottlenecks (blocking most downstream):`);
|
|
1247
|
+
analysis.bottlenecks.forEach((bt) => {
|
|
1248
|
+
const node = graph.nodes.get(bt.taskId);
|
|
1249
|
+
if (node) {
|
|
1250
|
+
lines.push(` \u2022 ${node.title} (blocks ${bt.blockedCount} task(s))`);
|
|
1251
|
+
}
|
|
1252
|
+
});
|
|
1253
|
+
}
|
|
1254
|
+
if (analysis.parallelGroups.length > 0) {
|
|
1255
|
+
lines.push("");
|
|
1256
|
+
lines.push(` \u26A1 Parallel groups (can run concurrently):`);
|
|
1257
|
+
analysis.parallelGroups.forEach((group, i) => {
|
|
1258
|
+
const names = group.map((id) => graph.nodes.get(id)?.title ?? "?").join(" | ");
|
|
1259
|
+
lines.push(` Group ${i + 1}: ${names}`);
|
|
1260
|
+
});
|
|
1261
|
+
}
|
|
1262
|
+
if (analysis.readyTasks.length > 0) {
|
|
1263
|
+
lines.push("");
|
|
1264
|
+
lines.push(` \u2705 Ready to start now:`);
|
|
1265
|
+
analysis.readyTasks.forEach((taskId) => {
|
|
1266
|
+
const node = graph.nodes.get(taskId);
|
|
1267
|
+
if (node) {
|
|
1268
|
+
lines.push(` \u2022 ${node.title}`);
|
|
1269
|
+
}
|
|
1270
|
+
});
|
|
1271
|
+
}
|
|
1272
|
+
lines.push(`\u2570${"\u2500".repeat(55)}\u256F`);
|
|
1273
|
+
return { message: lines.join("\n") };
|
|
1274
|
+
} catch {
|
|
1275
|
+
return { message: "Could not analyze critical path." };
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
763
1278
|
default:
|
|
764
1279
|
return {
|
|
765
1280
|
message: `Unknown command "${verb}".
|
|
@@ -788,45 +1303,67 @@ function sddHelp() {
|
|
|
788
1303
|
" \u2502 /sdd spec Show current session's spec \u2502",
|
|
789
1304
|
" \u2502 /sdd plan Show implementation plan \u2502",
|
|
790
1305
|
" \u2502 /sdd execute Execute generated tasks \u2502",
|
|
791
|
-
" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\
|
|
1306
|
+
" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518",
|
|
792
1307
|
"",
|
|
793
|
-
" \u250C\u2500 \
|
|
794
|
-
" \u2502 /sdd
|
|
795
|
-
" \u2502 /sdd
|
|
796
|
-
" \
|
|
1308
|
+
" \u250C\u2500 \u23F8 Goal Lifecycle \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510",
|
|
1309
|
+
" \u2502 /sdd goal Show current goal + journal \u2502",
|
|
1310
|
+
" \u2502 /sdd goal set <text> Set autonomous mission \u2502",
|
|
1311
|
+
" \u2502 /sdd goal pause Pause at end of current iteration \u2502",
|
|
1312
|
+
" \u2502 /sdd goal resume Resume a paused goal \u2502",
|
|
1313
|
+
" \u2502 /sdd goal journal [N] Show recent journal entries \u2502",
|
|
1314
|
+
" \u2502 /sdd goal clear Clear goal + stop eternal mode \u2502",
|
|
1315
|
+
" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518",
|
|
1316
|
+
"",
|
|
1317
|
+
" \u250C\u2500 \u{1F4E1} Eternal Stage \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510",
|
|
1318
|
+
" \u2502 decide \u2192 execute \u2192 reflect \u2192 sleep | paused | stopped \u2502",
|
|
1319
|
+
" \u2502 Stage shown in real-time during /sdd goal mode \u2502",
|
|
1320
|
+
" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518",
|
|
797
1321
|
"",
|
|
798
|
-
" \u250C\u2500 \u{
|
|
799
|
-
" \u2502 /sdd
|
|
1322
|
+
" \u250C\u2500 \u{1F527} Task Lifecycle \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510",
|
|
1323
|
+
" \u2502 /sdd tasks Show task list + progress bar \u2502",
|
|
1324
|
+
" \u2502 /sdd next Show next executable task \u2502",
|
|
1325
|
+
" \u2502 /sdd done <N> Complete a task \u2502",
|
|
1326
|
+
" \u2502 /sdd skip <N> Skip a task (back to pending) \u2502",
|
|
1327
|
+
" \u2502 /sdd fail <N> Mark task as failed \u2502",
|
|
1328
|
+
" \u2502 /sdd review <N> Send task to review \u2502",
|
|
1329
|
+
" \u2502 /sdd edit <N> <txt> Edit task title or description \u2502",
|
|
1330
|
+
" \u2502 /sdd undo Undo last completion \u2502",
|
|
1331
|
+
" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518",
|
|
1332
|
+
"",
|
|
1333
|
+
" \u250C\u2500 \u{1F4CA} Session Info \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510",
|
|
1334
|
+
" \u2502 /sdd status Full session status + tasks preview \u2502",
|
|
800
1335
|
" \u2502 /sdd cancel Cancel session \u2502",
|
|
801
|
-
" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\
|
|
1336
|
+
" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518",
|
|
802
1337
|
"",
|
|
803
|
-
" \u250C\u2500 \u{1F4C1} Spec History \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\
|
|
1338
|
+
" \u250C\u2500 \u{1F4C1} Spec History \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510",
|
|
804
1339
|
" \u2502 /sdd list List saved specs \u2502",
|
|
805
1340
|
" \u2502 /sdd show <id> Show spec details \u2502",
|
|
806
1341
|
" \u2502 /sdd templates List available templates \u2502",
|
|
807
1342
|
" \u2502 /sdd from <tmpl> Create from template \u2502",
|
|
808
1343
|
" \u2502 /sdd version <id> Show version history \u2502",
|
|
809
|
-
" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\
|
|
1344
|
+
" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518",
|
|
810
1345
|
"",
|
|
811
|
-
" \u250C\u2500 \u{1F4A1} Quick Start \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\
|
|
812
|
-
" \u2502
|
|
813
|
-
" \u2502 1. /sdd new Auth System
|
|
814
|
-
" \u2502 \u2192 AI starts asking questions
|
|
815
|
-
" \u2502
|
|
816
|
-
" \u2502 2. Just type your answers naturally
|
|
817
|
-
" \u2502 \u2192 AI continues the interview
|
|
818
|
-
" \u2502
|
|
819
|
-
" \u2502 3. AI generates spec (auto-detected)
|
|
820
|
-
" \u2502 \u2192 /sdd approve
|
|
821
|
-
" \u2502
|
|
822
|
-
" \u2502 3. AI generates implementation + tasks
|
|
823
|
-
" \u2502 \u2192 /sdd approve
|
|
824
|
-
" \u2502
|
|
825
|
-
" \u2502 4. AI executes tasks one by one
|
|
826
|
-
" \u2502 \u2192 /sdd tasks (view progress)
|
|
827
|
-
" \u2502 \u2192 /sdd done 1 (
|
|
828
|
-
" \u2502
|
|
829
|
-
" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\
|
|
1346
|
+
" \u250C\u2500 \u{1F4A1} Quick Start \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510",
|
|
1347
|
+
" \u2502 \u2502",
|
|
1348
|
+
" \u2502 1. /sdd new Auth System \u2502",
|
|
1349
|
+
" \u2502 \u2192 AI starts asking questions \u2502",
|
|
1350
|
+
" \u2502 \u2502",
|
|
1351
|
+
" \u2502 2. Just type your answers naturally \u2502",
|
|
1352
|
+
" \u2502 \u2192 AI continues the interview \u2502",
|
|
1353
|
+
" \u2502 \u2502",
|
|
1354
|
+
" \u2502 3. AI generates spec (auto-detected) \u2502",
|
|
1355
|
+
" \u2502 \u2192 /sdd approve \u2502",
|
|
1356
|
+
" \u2502 \u2502",
|
|
1357
|
+
" \u2502 3. AI generates implementation + tasks \u2502",
|
|
1358
|
+
" \u2502 \u2192 /sdd approve \u2502",
|
|
1359
|
+
" \u2502 \u2502",
|
|
1360
|
+
" \u2502 4. AI executes tasks one by one \u2502",
|
|
1361
|
+
" \u2502 \u2192 /sdd tasks (view progress) \u2502",
|
|
1362
|
+
" \u2502 \u2192 /sdd done 1 (mark task complete) \u2502",
|
|
1363
|
+
" \u2502 \u2502",
|
|
1364
|
+
" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518",
|
|
1365
|
+
"",
|
|
1366
|
+
" Tip: tasks are shown with progress bar after each AI turn.",
|
|
830
1367
|
""
|
|
831
1368
|
].join("\n");
|
|
832
1369
|
}
|
|
@@ -885,6 +1422,9 @@ var init_sdd = __esm({
|
|
|
885
1422
|
taskStore = null;
|
|
886
1423
|
taskTracker = null;
|
|
887
1424
|
taskGraphId = null;
|
|
1425
|
+
sessionStartTime = Date.now();
|
|
1426
|
+
phaseStartTime = Date.now();
|
|
1427
|
+
versioning = null;
|
|
888
1428
|
getBuilder() {
|
|
889
1429
|
return this.builder;
|
|
890
1430
|
}
|
|
@@ -909,6 +1449,27 @@ var init_sdd = __esm({
|
|
|
909
1449
|
setTaskGraphId(id) {
|
|
910
1450
|
this.taskGraphId = id;
|
|
911
1451
|
}
|
|
1452
|
+
getSessionStartTime() {
|
|
1453
|
+
return this.sessionStartTime;
|
|
1454
|
+
}
|
|
1455
|
+
setSessionStartTime(t) {
|
|
1456
|
+
this.sessionStartTime = t;
|
|
1457
|
+
}
|
|
1458
|
+
setPhaseStartTime(t) {
|
|
1459
|
+
this.phaseStartTime = t;
|
|
1460
|
+
}
|
|
1461
|
+
getPhaseStartTime() {
|
|
1462
|
+
return this.phaseStartTime;
|
|
1463
|
+
}
|
|
1464
|
+
getSessionElapsed() {
|
|
1465
|
+
return Date.now() - this.sessionStartTime;
|
|
1466
|
+
}
|
|
1467
|
+
getPhaseElapsed() {
|
|
1468
|
+
return Date.now() - this.phaseStartTime;
|
|
1469
|
+
}
|
|
1470
|
+
getVersioning() {
|
|
1471
|
+
return this.versioning ?? (this.versioning = new SpecVersioning());
|
|
1472
|
+
}
|
|
912
1473
|
clearTaskState() {
|
|
913
1474
|
this.taskStore = null;
|
|
914
1475
|
this.taskTracker = null;
|
|
@@ -4262,7 +4823,7 @@ function buildAutonomyCommand(opts) {
|
|
|
4262
4823
|
" /autonomy off Disabled \u2014 agent stops after each turn (default)",
|
|
4263
4824
|
" /autonomy suggest Show next-step suggestions after each turn",
|
|
4264
4825
|
" /autonomy on Auto-continue \u2014 agent picks next step and proceeds",
|
|
4265
|
-
" /autonomy eternal
|
|
4826
|
+
" /autonomy eternal Goal-driven loop \u2014 runs forever against /goal",
|
|
4266
4827
|
" /autonomy parallel Parallel mode \u2014 4-8 agents per tick, fan-out parallelism",
|
|
4267
4828
|
" /autonomy stop Stop eternal mode (no-op for other modes)",
|
|
4268
4829
|
" /autonomy toggle Cycle: off \u2192 suggest \u2192 auto \u2192 eternal \u2192 parallel \u2192 off",
|
|
@@ -4278,6 +4839,9 @@ function buildAutonomyCommand(opts) {
|
|
|
4278
4839
|
" spawns N agents, awaits results, aggregates. Requires /goal.",
|
|
4279
4840
|
" Force-enables YOLO. Runs until /autonomy stop or Ctrl+C twice.",
|
|
4280
4841
|
"",
|
|
4842
|
+
"Eternal stage flow: decide \u2192 execute \u2192 reflect \u2192 sleep | paused | stopped",
|
|
4843
|
+
"Stage shown in real-time. Use /goal pause to pause, /goal resume to continue.",
|
|
4844
|
+
"",
|
|
4281
4845
|
"In auto/eternal/parallel modes the agent works autonomously. Press Esc to redirect,",
|
|
4282
4846
|
"Ctrl+C to stop the active iteration. /autonomy stop ends the eternal loop."
|
|
4283
4847
|
].join("\n"),
|
|
@@ -4294,7 +4858,7 @@ function buildAutonomyCommand(opts) {
|
|
|
4294
4858
|
off: `${color.green("OFF")} ${color.dim("(agent stops after each turn)")}`,
|
|
4295
4859
|
suggest: `${color.cyan("SUGGEST")} ${color.dim("(shows next-step suggestions)")}`,
|
|
4296
4860
|
auto: `${color.yellow("AUTO")} ${color.dim("(self-driving \u2014 Esc to redirect, Ctrl+C to stop)")}`,
|
|
4297
|
-
eternal: `${color.red("ETERNAL")} ${color.dim("(
|
|
4861
|
+
eternal: `${color.red("ETERNAL")} ${color.dim("(goal-driven loop \u2014 YOLO, until /autonomy stop)")}`,
|
|
4298
4862
|
"eternal-parallel": `${color.magenta("PARALLEL")} ${color.dim("(4-8 subagents per tick \u2014 fan-out, until /autonomy stop)")}`
|
|
4299
4863
|
};
|
|
4300
4864
|
const lines = [`Autonomy mode: ${labels2[current] ?? current}`];
|
|
@@ -4426,7 +4990,9 @@ var KNOWN_VERBS = /* @__PURE__ */ new Set([
|
|
|
4426
4990
|
"clear",
|
|
4427
4991
|
"reset",
|
|
4428
4992
|
"journal",
|
|
4429
|
-
"log"
|
|
4993
|
+
"log",
|
|
4994
|
+
"pause",
|
|
4995
|
+
"resume"
|
|
4430
4996
|
]);
|
|
4431
4997
|
function buildGoalCommand(opts) {
|
|
4432
4998
|
return {
|
|
@@ -4437,9 +5003,14 @@ function buildGoalCommand(opts) {
|
|
|
4437
5003
|
" /goal Show current goal + recent journal",
|
|
4438
5004
|
" /goal set <text> Set a new goal (overwrites previous)",
|
|
4439
5005
|
" /goal clear Clear the goal (stops eternal mode if running)",
|
|
5006
|
+
" /goal pause Pause at end of current iteration (no-op if already paused)",
|
|
5007
|
+
" /goal resume Resume a paused goal (no-op if not paused)",
|
|
4440
5008
|
" /goal status Same as /goal (alias)",
|
|
4441
5009
|
" /goal journal [N] Show last N journal entries (default 25)",
|
|
4442
5010
|
"",
|
|
5011
|
+
"Stage flow: decide \u2192 execute \u2192 reflect \u2192 sleep | paused | stopped",
|
|
5012
|
+
"Pausing stops after current iteration completes. Resume continues from next iteration.",
|
|
5013
|
+
"",
|
|
4443
5014
|
"Goals live in <projectRoot>/.wrongstack/goal.json and persist across sessions.",
|
|
4444
5015
|
"A goal is the prerequisite for /autonomy eternal \u2014 the engine consults it on",
|
|
4445
5016
|
"every iteration to decide what to do next."
|
|
@@ -4528,6 +5099,42 @@ ${lines.join("\n")}`;
|
|
|
4528
5099
|
opts.renderer.write(msg);
|
|
4529
5100
|
return { message: msg };
|
|
4530
5101
|
}
|
|
5102
|
+
case "pause": {
|
|
5103
|
+
const current = await loadGoal(goalPath);
|
|
5104
|
+
if (!current) {
|
|
5105
|
+
const msg2 = "No goal set \u2014 nothing to pause.";
|
|
5106
|
+
opts.renderer.writeWarning(msg2);
|
|
5107
|
+
return { message: msg2 };
|
|
5108
|
+
}
|
|
5109
|
+
if (current.goalState === "paused") {
|
|
5110
|
+
const msg2 = `${color.dim("Already paused.")} Use /goal resume to continue.`;
|
|
5111
|
+
opts.renderer.write(msg2);
|
|
5112
|
+
return { message: msg2 };
|
|
5113
|
+
}
|
|
5114
|
+
const paused = { ...current, goalState: "paused" };
|
|
5115
|
+
await saveGoal(goalPath, paused);
|
|
5116
|
+
const msg = `${color.cyan("Goal paused.")} Current iteration will finish, then the loop stops. Use /goal resume to continue.`;
|
|
5117
|
+
opts.renderer.write(msg);
|
|
5118
|
+
return { message: msg };
|
|
5119
|
+
}
|
|
5120
|
+
case "resume": {
|
|
5121
|
+
const current = await loadGoal(goalPath);
|
|
5122
|
+
if (!current) {
|
|
5123
|
+
const msg2 = "No goal set \u2014 cannot resume.";
|
|
5124
|
+
opts.renderer.writeWarning(msg2);
|
|
5125
|
+
return { message: msg2 };
|
|
5126
|
+
}
|
|
5127
|
+
if (current.goalState !== "paused") {
|
|
5128
|
+
const msg2 = `${color.dim("Not paused.")} Use /goal set <text> to create or update a goal first.`;
|
|
5129
|
+
opts.renderer.writeWarning(msg2);
|
|
5130
|
+
return { message: msg2 };
|
|
5131
|
+
}
|
|
5132
|
+
const resumed = { ...current, goalState: "active" };
|
|
5133
|
+
await saveGoal(goalPath, resumed);
|
|
5134
|
+
const msg = `${color.green("Goal resumed.")} Loop will continue from the next iteration.`;
|
|
5135
|
+
opts.renderer.write(msg);
|
|
5136
|
+
return { message: msg };
|
|
5137
|
+
}
|
|
4531
5138
|
default: {
|
|
4532
5139
|
const msg = `Unknown subcommand "${verb}". Try: show | set <text> | clear | journal [N]`;
|
|
4533
5140
|
opts.renderer.writeWarning(msg);
|
|
@@ -6316,6 +6923,85 @@ var TerminalRenderer = class {
|
|
|
6316
6923
|
this.out.write("\x1B[2J\x1B[H");
|
|
6317
6924
|
this.lineStart = true;
|
|
6318
6925
|
}
|
|
6926
|
+
/**
|
|
6927
|
+
* Write a flashy agent completion banner for delegate tool results.
|
|
6928
|
+
* Renders a box like:
|
|
6929
|
+
* ┌─────────────────────────────────────┐
|
|
6930
|
+
* │ ✓ [role] done in 4m 32s │
|
|
6931
|
+
* │ 127 iterations · 341 tools │
|
|
6932
|
+
* │ Found 14 bugs across 6 files... │
|
|
6933
|
+
* └─────────────────────────────────────┘
|
|
6934
|
+
*/
|
|
6935
|
+
writeAgentSummary(summary, ok) {
|
|
6936
|
+
if (this.silent) return;
|
|
6937
|
+
if (!this.lineStart) this.out.write("\n");
|
|
6938
|
+
const lines = summary.split("\n");
|
|
6939
|
+
const icon = ok ? theme2.success("\u2713") : theme2.error("\u2718");
|
|
6940
|
+
const firstLine = `${icon} ${lines[0] ?? summary}`;
|
|
6941
|
+
const body = lines.slice(1);
|
|
6942
|
+
const maxWidth = Math.min(process.stdout.columns ?? 80, 120);
|
|
6943
|
+
const contentWidth = Math.max(
|
|
6944
|
+
firstLine.length,
|
|
6945
|
+
body.reduce((a, l) => Math.max(a, l.length), 0)
|
|
6946
|
+
);
|
|
6947
|
+
const boxWidth = Math.min(Math.max(contentWidth + 4, 44), maxWidth);
|
|
6948
|
+
const thick = "\u2501".repeat(boxWidth - 2);
|
|
6949
|
+
const thin = "\u2500".repeat(boxWidth - 2);
|
|
6950
|
+
this.out.write(`
|
|
6951
|
+
${theme2.primary("\u250C")}${thick}${theme2.primary("\u2510")}
|
|
6952
|
+
`);
|
|
6953
|
+
const centre = (s) => {
|
|
6954
|
+
const inner = ` ${s} `;
|
|
6955
|
+
const padLen = Math.max(0, boxWidth - 2 - s.length);
|
|
6956
|
+
const left = Math.floor(padLen / 2);
|
|
6957
|
+
const right = padLen - left;
|
|
6958
|
+
return `${" ".repeat(left)}${inner}${" ".repeat(right)}`;
|
|
6959
|
+
};
|
|
6960
|
+
this.out.write(` ${theme2.primary("\u2502")}${centre(firstLine)}${theme2.primary("\u2502")}
|
|
6961
|
+
`);
|
|
6962
|
+
for (const l of body) {
|
|
6963
|
+
this.out.write(
|
|
6964
|
+
` ${theme2.primary("\u2502")} ${l}${" ".repeat(Math.max(0, boxWidth - 3 - l.length))}${theme2.primary("\u2502")}
|
|
6965
|
+
`
|
|
6966
|
+
);
|
|
6967
|
+
}
|
|
6968
|
+
this.out.write(` ${theme2.primary("\u2514")}${thin}${theme2.primary("\u2518")}
|
|
6969
|
+
`);
|
|
6970
|
+
this.lineStart = true;
|
|
6971
|
+
}
|
|
6972
|
+
/**
|
|
6973
|
+
* Render subagent completion banners from a RunResult.
|
|
6974
|
+
* Uses `delegateSummaries` when available (populated by delegate tool),
|
|
6975
|
+
* otherwise falls back to scanning message history.
|
|
6976
|
+
*/
|
|
6977
|
+
writeDelegateSummaries(result) {
|
|
6978
|
+
if (this.silent) return;
|
|
6979
|
+
if (result.delegateSummaries) {
|
|
6980
|
+
for (const { summary, ok } of result.delegateSummaries) {
|
|
6981
|
+
this.writeAgentSummary(summary, ok);
|
|
6982
|
+
}
|
|
6983
|
+
return;
|
|
6984
|
+
}
|
|
6985
|
+
if (!result.messages) return;
|
|
6986
|
+
for (const msg of result.messages) {
|
|
6987
|
+
const m = msg;
|
|
6988
|
+
if (!Array.isArray(m.content)) continue;
|
|
6989
|
+
for (const block of m.content) {
|
|
6990
|
+
const b = block;
|
|
6991
|
+
if (b.type !== "tool_result" || b.name !== "delegate") continue;
|
|
6992
|
+
let obj;
|
|
6993
|
+
try {
|
|
6994
|
+
obj = typeof b.content === "string" ? JSON.parse(b.content) : b.content;
|
|
6995
|
+
} catch {
|
|
6996
|
+
continue;
|
|
6997
|
+
}
|
|
6998
|
+
const o = obj;
|
|
6999
|
+
if (o.summary) {
|
|
7000
|
+
this.writeAgentSummary(o.summary, o.ok ?? true);
|
|
7001
|
+
}
|
|
7002
|
+
}
|
|
7003
|
+
}
|
|
7004
|
+
}
|
|
6319
7005
|
};
|
|
6320
7006
|
function renderMarkdown(s) {
|
|
6321
7007
|
let out = s;
|
|
@@ -8626,11 +9312,13 @@ async function runRepl(opts) {
|
|
|
8626
9312
|
await renderGoalBanner(opts);
|
|
8627
9313
|
let activeCtrl;
|
|
8628
9314
|
let interrupts = 0;
|
|
9315
|
+
let exiting = false;
|
|
8629
9316
|
const onSigint = () => {
|
|
8630
9317
|
interrupts++;
|
|
8631
9318
|
if (interrupts >= 2) {
|
|
8632
9319
|
opts.renderer.writeWarning("Exiting.");
|
|
8633
|
-
|
|
9320
|
+
exiting = true;
|
|
9321
|
+
return;
|
|
8634
9322
|
}
|
|
8635
9323
|
if (opts.getAutonomy?.() === "eternal" || opts.getAutonomy?.() === "eternal-parallel") {
|
|
8636
9324
|
opts.getEternalEngine?.()?.stop();
|
|
@@ -8651,6 +9339,7 @@ async function runRepl(opts) {
|
|
|
8651
9339
|
const builder = new InputBuilder({ store: opts.attachments });
|
|
8652
9340
|
try {
|
|
8653
9341
|
for (; ; ) {
|
|
9342
|
+
if (exiting) break;
|
|
8654
9343
|
if (opts.getAutonomy?.() === "eternal") {
|
|
8655
9344
|
const engine = opts.getEternalEngine?.();
|
|
8656
9345
|
if (!engine) {
|
|
@@ -8787,9 +9476,22 @@ ${color.cyan(` \u2713 ${count} tasks detected and saved! Use /sdd approve to ex
|
|
|
8787
9476
|
if (progress) {
|
|
8788
9477
|
opts.renderer.write(
|
|
8789
9478
|
`
|
|
8790
|
-
${color.cyan(` \u2713 ${autoCompleted} task(s) auto-completed! Progress: ${progress.completed}/${progress.total} (${progress.
|
|
9479
|
+
${color.cyan(` \u2713 ${autoCompleted} task(s) auto-completed! Progress: ${progress.completed}/${progress.total} (${progress.percentComplete}%)`)}
|
|
8791
9480
|
`
|
|
8792
9481
|
);
|
|
9482
|
+
const taskList2 = renderTaskListWithProgress();
|
|
9483
|
+
if (taskList2) {
|
|
9484
|
+
opts.renderer.write(`
|
|
9485
|
+
${color.dim(taskList2)}
|
|
9486
|
+
`);
|
|
9487
|
+
}
|
|
9488
|
+
}
|
|
9489
|
+
} else {
|
|
9490
|
+
const taskList2 = renderTaskListWithProgress();
|
|
9491
|
+
if (taskList2) {
|
|
9492
|
+
opts.renderer.write(`
|
|
9493
|
+
${color.dim(taskList2)}
|
|
9494
|
+
`);
|
|
8793
9495
|
}
|
|
8794
9496
|
}
|
|
8795
9497
|
}
|
|
@@ -8818,6 +9520,14 @@ ${color.cyan(` \u2713 ${autoCompleted} task(s) auto-completed! Progress: ${prog
|
|
|
8818
9520
|
if (sddContext) {
|
|
8819
9521
|
sddPrefix = `[SDD SESSION ACTIVE]
|
|
8820
9522
|
${sddContext}`;
|
|
9523
|
+
if (sddPhase === "executing") {
|
|
9524
|
+
const currentCtx = getCurrentExecutingContext();
|
|
9525
|
+
if (currentCtx) {
|
|
9526
|
+
sddPrefix += `
|
|
9527
|
+
|
|
9528
|
+
${currentCtx}`;
|
|
9529
|
+
}
|
|
9530
|
+
}
|
|
8821
9531
|
if (taskList) {
|
|
8822
9532
|
sddPrefix += `
|
|
8823
9533
|
|
|
@@ -8826,9 +9536,9 @@ ${taskList}`;
|
|
|
8826
9536
|
}
|
|
8827
9537
|
if (taskProgress && taskProgress.total > 0) {
|
|
8828
9538
|
sddPrefix += `
|
|
8829
|
-
**Progress:** ${taskProgress.completed}/${taskProgress.total} (${taskProgress.
|
|
9539
|
+
**Progress:** ${taskProgress.completed}/${taskProgress.total} (${taskProgress.percentComplete}%)`;
|
|
8830
9540
|
}
|
|
8831
|
-
if (sddPhase === "executing" && taskProgress && taskProgress.
|
|
9541
|
+
if (sddPhase === "executing" && taskProgress && taskProgress.percentComplete === 100) {
|
|
8832
9542
|
sddPrefix += "\n\n**All tasks completed! Provide a summary of everything implemented.**";
|
|
8833
9543
|
}
|
|
8834
9544
|
sddPrefix += "\n\n---\nUser message:\n";
|
|
@@ -8908,10 +9618,10 @@ ${color.cyan(` \u2713 ${count} tasks detected and saved! Use /sdd approve to ex
|
|
|
8908
9618
|
if (progress) {
|
|
8909
9619
|
opts.renderer.write(
|
|
8910
9620
|
`
|
|
8911
|
-
${color.cyan(` \u2713 ${autoCompleted} task(s) auto-completed! Progress: ${progress.completed}/${progress.total} (${progress.
|
|
9621
|
+
${color.cyan(` \u2713 ${autoCompleted} task(s) auto-completed! Progress: ${progress.completed}/${progress.total} (${progress.percentComplete}%)`)}
|
|
8912
9622
|
`
|
|
8913
9623
|
);
|
|
8914
|
-
if (progress.
|
|
9624
|
+
if (progress.percentComplete === 100) {
|
|
8915
9625
|
opts.renderer.write(
|
|
8916
9626
|
`
|
|
8917
9627
|
${color.green(" \u{1F389} All tasks completed! Use /sdd cancel to end the session.")}
|
|
@@ -8919,6 +9629,20 @@ ${color.green(" \u{1F389} All tasks completed! Use /sdd cancel to end the sessi
|
|
|
8919
9629
|
);
|
|
8920
9630
|
}
|
|
8921
9631
|
}
|
|
9632
|
+
advanceToNextTask();
|
|
9633
|
+
const taskList2 = renderTaskListWithProgress();
|
|
9634
|
+
if (taskList2) {
|
|
9635
|
+
opts.renderer.write(`
|
|
9636
|
+
${color.dim(taskList2)}
|
|
9637
|
+
`);
|
|
9638
|
+
}
|
|
9639
|
+
} else {
|
|
9640
|
+
const taskList2 = renderTaskListWithProgress();
|
|
9641
|
+
if (taskList2) {
|
|
9642
|
+
opts.renderer.write(`
|
|
9643
|
+
${color.dim(taskList2)}
|
|
9644
|
+
`);
|
|
9645
|
+
}
|
|
8922
9646
|
}
|
|
8923
9647
|
}
|
|
8924
9648
|
}
|
|
@@ -9079,10 +9803,10 @@ var EMPTY = "\u2591";
|
|
|
9079
9803
|
function renderContextChip(used, max) {
|
|
9080
9804
|
const ratio = Math.max(0, Math.min(1, used / max));
|
|
9081
9805
|
const pct2 = Math.round(ratio * 100);
|
|
9082
|
-
const bar =
|
|
9806
|
+
const bar = renderProgress2(ratio, 6);
|
|
9083
9807
|
return `${bar} ${pct2}% (${fmtTok(used)}/${fmtTok(max)})`;
|
|
9084
9808
|
}
|
|
9085
|
-
function
|
|
9809
|
+
function renderProgress2(ratio, width) {
|
|
9086
9810
|
const clamped = Math.max(0, Math.min(1, ratio));
|
|
9087
9811
|
const filled = clamped === 0 ? 0 : Math.max(1, Math.round(clamped * width));
|
|
9088
9812
|
const capped = Math.min(width, filled);
|
|
@@ -9140,6 +9864,7 @@ async function execute(deps) {
|
|
|
9140
9864
|
getEternalEngine,
|
|
9141
9865
|
getParallelEngine,
|
|
9142
9866
|
subscribeEternalIteration,
|
|
9867
|
+
subscribeEternalStage,
|
|
9143
9868
|
skillLoader
|
|
9144
9869
|
} = deps;
|
|
9145
9870
|
let code = 0;
|
|
@@ -9220,6 +9945,8 @@ async function execute(deps) {
|
|
|
9220
9945
|
renderer.writeWarning(`Hit max iterations (${result.iterations}).`);
|
|
9221
9946
|
}
|
|
9222
9947
|
if (result.finalText) renderer.write("\n" + result.finalText + "\n");
|
|
9948
|
+
const r = result;
|
|
9949
|
+
renderer.writeDelegateSummaries(r);
|
|
9223
9950
|
renderer.write(
|
|
9224
9951
|
"\n" + color.dim(
|
|
9225
9952
|
`[in: ${fmtTok(usage.input)} out: ${fmtTok(usage.output)} iters: ${usage.iterations} cost: ${usage.cost.toFixed(4)} ${(usage.elapsedMs / 1e3).toFixed(1)}s]`
|
|
@@ -9250,12 +9977,17 @@ async function execute(deps) {
|
|
|
9250
9977
|
getAutonomy,
|
|
9251
9978
|
getEternalEngine,
|
|
9252
9979
|
subscribeEternalIteration,
|
|
9980
|
+
subscribeEternalStage,
|
|
9253
9981
|
appVersion: CLI_VERSION,
|
|
9254
9982
|
provider: config.provider,
|
|
9255
9983
|
family: banneredFamily,
|
|
9256
9984
|
keyTail: banneredKeyTail,
|
|
9257
9985
|
getPickableProviders,
|
|
9258
9986
|
switchProviderAndModel,
|
|
9987
|
+
switchAutonomy: (mode) => {
|
|
9988
|
+
onAutonomy?.(mode);
|
|
9989
|
+
return null;
|
|
9990
|
+
},
|
|
9259
9991
|
effectiveMaxContext,
|
|
9260
9992
|
// Default OFF so the terminal's native scrollback works for chat
|
|
9261
9993
|
// history out of the box (mouse wheel / Shift+PgUp). Users who hit
|
|
@@ -9302,7 +10034,7 @@ async function execute(deps) {
|
|
|
9302
10034
|
if (autoCompleted > 0) {
|
|
9303
10035
|
const progress = getTaskProgress2();
|
|
9304
10036
|
if (progress) {
|
|
9305
|
-
messages.push(`\u2713 ${autoCompleted} task(s) auto-completed! Progress: ${progress.completed}/${progress.total} (${progress.
|
|
10037
|
+
messages.push(`\u2713 ${autoCompleted} task(s) auto-completed! Progress: ${progress.completed}/${progress.total} (${progress.percentComplete}%)`);
|
|
9306
10038
|
}
|
|
9307
10039
|
}
|
|
9308
10040
|
}
|
|
@@ -9618,8 +10350,7 @@ var MultiAgentHost = class {
|
|
|
9618
10350
|
}
|
|
9619
10351
|
subagentToolRegistry(allow) {
|
|
9620
10352
|
if (!allow || allow.length === 0) return this.deps.toolRegistry;
|
|
9621
|
-
const
|
|
9622
|
-
const sub = new cloneCtor();
|
|
10353
|
+
const sub = this.deps.toolRegistry.clone();
|
|
9623
10354
|
for (const t of this.filterTools(allow)) sub.register(t);
|
|
9624
10355
|
return sub;
|
|
9625
10356
|
}
|
|
@@ -10117,10 +10848,10 @@ function renderContextChip2(ctx) {
|
|
|
10117
10848
|
const ratio = Math.max(0, Math.min(1, ctx.used / ctx.max));
|
|
10118
10849
|
const pct2 = Math.round(ratio * 100);
|
|
10119
10850
|
const chipColor = ratio >= 0.85 ? color.red : ratio >= 0.65 ? color.yellow : color.cyan;
|
|
10120
|
-
const bar =
|
|
10851
|
+
const bar = renderProgress3(ratio, 8);
|
|
10121
10852
|
return color.dim("ctx ") + chipColor(bar) + chipColor(` ${pct2}%`) + color.dim(` (${fmtTok(ctx.used)}/${fmtTok(ctx.max)})`);
|
|
10122
10853
|
}
|
|
10123
|
-
function
|
|
10854
|
+
function renderProgress3(ratio, width) {
|
|
10124
10855
|
const clamped = Math.max(0, Math.min(1, ratio));
|
|
10125
10856
|
const filled = clamped === 0 ? 0 : Math.max(1, Math.round(clamped * width));
|
|
10126
10857
|
const capped = Math.min(width, filled);
|
|
@@ -10483,6 +11214,7 @@ function resolveBundledSkillsDir2() {
|
|
|
10483
11214
|
return void 0;
|
|
10484
11215
|
}
|
|
10485
11216
|
}
|
|
11217
|
+
var stageListeners = /* @__PURE__ */ new Set();
|
|
10486
11218
|
async function main(argv) {
|
|
10487
11219
|
const ctx = await boot(argv);
|
|
10488
11220
|
if (typeof ctx === "number") return ctx;
|
|
@@ -11488,6 +12220,10 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
11488
12220
|
eternalListeners.add(fn);
|
|
11489
12221
|
return () => eternalListeners.delete(fn);
|
|
11490
12222
|
},
|
|
12223
|
+
subscribeEternalStage: (fn) => {
|
|
12224
|
+
stageListeners.add(fn);
|
|
12225
|
+
return () => stageListeners.delete(fn);
|
|
12226
|
+
},
|
|
11491
12227
|
skillLoader: config.features.skills ? skillLoader : void 0
|
|
11492
12228
|
});
|
|
11493
12229
|
}
|