@wrongstack/cli 0.6.4 → 0.6.6
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 +2086 -368
- 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,20 +348,26 @@ 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
|
}
|
|
@@ -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) {
|
|
@@ -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}%)`
|
|
710
|
+
};
|
|
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)`
|
|
543
801
|
};
|
|
544
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;
|
|
@@ -760,6 +1200,73 @@ Available: ${listTemplates().map((t) => t.id).join(", ")}`
|
|
|
760
1200
|
${lines.join("\n")}`
|
|
761
1201
|
};
|
|
762
1202
|
}
|
|
1203
|
+
case "critical":
|
|
1204
|
+
case "bottleneck": {
|
|
1205
|
+
const critTracker = sddState.getTaskTracker();
|
|
1206
|
+
if (!critTracker) {
|
|
1207
|
+
return { message: "No tasks generated yet. Use /sdd new to start." };
|
|
1208
|
+
}
|
|
1209
|
+
const graphId = sddState.getTaskGraphId();
|
|
1210
|
+
if (!graphId) {
|
|
1211
|
+
return { message: "No task graph found. Generate tasks first." };
|
|
1212
|
+
}
|
|
1213
|
+
try {
|
|
1214
|
+
const graphStore2 = new TaskGraphStore({ baseDir: path23.join(projectRoot, ".wrongstack", "task-graphs") });
|
|
1215
|
+
const graph = await graphStore2.load(graphId);
|
|
1216
|
+
if (!graph) {
|
|
1217
|
+
return { message: "Could not load task graph." };
|
|
1218
|
+
}
|
|
1219
|
+
const analysis = analyzeCriticalPath(graph);
|
|
1220
|
+
const lines = [
|
|
1221
|
+
`\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`,
|
|
1222
|
+
"",
|
|
1223
|
+
` Critical path length: ${analysis.criticalPath.length} tasks`,
|
|
1224
|
+
` Estimated total time: ${analysis.totalHours}h`,
|
|
1225
|
+
""
|
|
1226
|
+
];
|
|
1227
|
+
if (analysis.criticalPath.length > 0) {
|
|
1228
|
+
lines.push(` \u{1F534} Critical path:`);
|
|
1229
|
+
analysis.criticalPath.forEach((taskId, i) => {
|
|
1230
|
+
const node = graph.nodes.get(taskId);
|
|
1231
|
+
if (node) {
|
|
1232
|
+
lines.push(` ${i + 1}. ${node.title} [${node.priority}] \u2014 ${node.estimateHours}h`);
|
|
1233
|
+
}
|
|
1234
|
+
});
|
|
1235
|
+
}
|
|
1236
|
+
if (analysis.bottlenecks.length > 0) {
|
|
1237
|
+
lines.push("");
|
|
1238
|
+
lines.push(` \u{1F6AB} Bottlenecks (blocking most downstream):`);
|
|
1239
|
+
analysis.bottlenecks.forEach((bt) => {
|
|
1240
|
+
const node = graph.nodes.get(bt.taskId);
|
|
1241
|
+
if (node) {
|
|
1242
|
+
lines.push(` \u2022 ${node.title} (blocks ${bt.blockedCount} task(s))`);
|
|
1243
|
+
}
|
|
1244
|
+
});
|
|
1245
|
+
}
|
|
1246
|
+
if (analysis.parallelGroups.length > 0) {
|
|
1247
|
+
lines.push("");
|
|
1248
|
+
lines.push(` \u26A1 Parallel groups (can run concurrently):`);
|
|
1249
|
+
analysis.parallelGroups.forEach((group, i) => {
|
|
1250
|
+
const names = group.map((id) => graph.nodes.get(id)?.title ?? "?").join(" | ");
|
|
1251
|
+
lines.push(` Group ${i + 1}: ${names}`);
|
|
1252
|
+
});
|
|
1253
|
+
}
|
|
1254
|
+
if (analysis.readyTasks.length > 0) {
|
|
1255
|
+
lines.push("");
|
|
1256
|
+
lines.push(` \u2705 Ready to start now:`);
|
|
1257
|
+
analysis.readyTasks.forEach((taskId) => {
|
|
1258
|
+
const node = graph.nodes.get(taskId);
|
|
1259
|
+
if (node) {
|
|
1260
|
+
lines.push(` \u2022 ${node.title}`);
|
|
1261
|
+
}
|
|
1262
|
+
});
|
|
1263
|
+
}
|
|
1264
|
+
lines.push(`\u2570${"\u2500".repeat(55)}\u256F`);
|
|
1265
|
+
return { message: lines.join("\n") };
|
|
1266
|
+
} catch {
|
|
1267
|
+
return { message: "Could not analyze critical path." };
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
763
1270
|
default:
|
|
764
1271
|
return {
|
|
765
1272
|
message: `Unknown command "${verb}".
|
|
@@ -788,45 +1295,67 @@ function sddHelp() {
|
|
|
788
1295
|
" \u2502 /sdd spec Show current session's spec \u2502",
|
|
789
1296
|
" \u2502 /sdd plan Show implementation plan \u2502",
|
|
790
1297
|
" \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\
|
|
1298
|
+
" \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
1299
|
"",
|
|
793
|
-
" \u250C\u2500 \
|
|
794
|
-
" \u2502 /sdd
|
|
795
|
-
" \u2502 /sdd
|
|
796
|
-
" \
|
|
1300
|
+
" \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",
|
|
1301
|
+
" \u2502 /sdd goal Show current goal + journal \u2502",
|
|
1302
|
+
" \u2502 /sdd goal set <text> Set autonomous mission \u2502",
|
|
1303
|
+
" \u2502 /sdd goal pause Pause at end of current iteration \u2502",
|
|
1304
|
+
" \u2502 /sdd goal resume Resume a paused goal \u2502",
|
|
1305
|
+
" \u2502 /sdd goal journal [N] Show recent journal entries \u2502",
|
|
1306
|
+
" \u2502 /sdd goal clear Clear goal + stop eternal mode \u2502",
|
|
1307
|
+
" \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",
|
|
1308
|
+
"",
|
|
1309
|
+
" \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",
|
|
1310
|
+
" \u2502 decide \u2192 execute \u2192 reflect \u2192 sleep | paused | stopped \u2502",
|
|
1311
|
+
" \u2502 Stage shown in real-time during /sdd goal mode \u2502",
|
|
1312
|
+
" \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
1313
|
"",
|
|
798
|
-
" \u250C\u2500 \u{
|
|
799
|
-
" \u2502 /sdd
|
|
1314
|
+
" \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",
|
|
1315
|
+
" \u2502 /sdd tasks Show task list + progress bar \u2502",
|
|
1316
|
+
" \u2502 /sdd next Show next executable task \u2502",
|
|
1317
|
+
" \u2502 /sdd done <N> Complete a task \u2502",
|
|
1318
|
+
" \u2502 /sdd skip <N> Skip a task (back to pending) \u2502",
|
|
1319
|
+
" \u2502 /sdd fail <N> Mark task as failed \u2502",
|
|
1320
|
+
" \u2502 /sdd review <N> Send task to review \u2502",
|
|
1321
|
+
" \u2502 /sdd edit <N> <txt> Edit task title or description \u2502",
|
|
1322
|
+
" \u2502 /sdd undo Undo last completion \u2502",
|
|
1323
|
+
" \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",
|
|
1324
|
+
"",
|
|
1325
|
+
" \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",
|
|
1326
|
+
" \u2502 /sdd status Full session status + tasks preview \u2502",
|
|
800
1327
|
" \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\
|
|
1328
|
+
" \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
1329
|
"",
|
|
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\
|
|
1330
|
+
" \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
1331
|
" \u2502 /sdd list List saved specs \u2502",
|
|
805
1332
|
" \u2502 /sdd show <id> Show spec details \u2502",
|
|
806
1333
|
" \u2502 /sdd templates List available templates \u2502",
|
|
807
1334
|
" \u2502 /sdd from <tmpl> Create from template \u2502",
|
|
808
1335
|
" \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\
|
|
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",
|
|
810
1337
|
"",
|
|
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\
|
|
1338
|
+
" \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",
|
|
1339
|
+
" \u2502 \u2502",
|
|
1340
|
+
" \u2502 1. /sdd new Auth System \u2502",
|
|
1341
|
+
" \u2502 \u2192 AI starts asking questions \u2502",
|
|
1342
|
+
" \u2502 \u2502",
|
|
1343
|
+
" \u2502 2. Just type your answers naturally \u2502",
|
|
1344
|
+
" \u2502 \u2192 AI continues the interview \u2502",
|
|
1345
|
+
" \u2502 \u2502",
|
|
1346
|
+
" \u2502 3. AI generates spec (auto-detected) \u2502",
|
|
1347
|
+
" \u2502 \u2192 /sdd approve \u2502",
|
|
1348
|
+
" \u2502 \u2502",
|
|
1349
|
+
" \u2502 3. AI generates implementation + tasks \u2502",
|
|
1350
|
+
" \u2502 \u2192 /sdd approve \u2502",
|
|
1351
|
+
" \u2502 \u2502",
|
|
1352
|
+
" \u2502 4. AI executes tasks one by one \u2502",
|
|
1353
|
+
" \u2502 \u2192 /sdd tasks (view progress) \u2502",
|
|
1354
|
+
" \u2502 \u2192 /sdd done 1 (mark task complete) \u2502",
|
|
1355
|
+
" \u2502 \u2502",
|
|
1356
|
+
" \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",
|
|
1357
|
+
"",
|
|
1358
|
+
" Tip: tasks are shown with progress bar after each AI turn.",
|
|
830
1359
|
""
|
|
831
1360
|
].join("\n");
|
|
832
1361
|
}
|
|
@@ -885,6 +1414,9 @@ var init_sdd = __esm({
|
|
|
885
1414
|
taskStore = null;
|
|
886
1415
|
taskTracker = null;
|
|
887
1416
|
taskGraphId = null;
|
|
1417
|
+
sessionStartTime = Date.now();
|
|
1418
|
+
phaseStartTime = Date.now();
|
|
1419
|
+
versioning = null;
|
|
888
1420
|
getBuilder() {
|
|
889
1421
|
return this.builder;
|
|
890
1422
|
}
|
|
@@ -909,6 +1441,27 @@ var init_sdd = __esm({
|
|
|
909
1441
|
setTaskGraphId(id) {
|
|
910
1442
|
this.taskGraphId = id;
|
|
911
1443
|
}
|
|
1444
|
+
getSessionStartTime() {
|
|
1445
|
+
return this.sessionStartTime;
|
|
1446
|
+
}
|
|
1447
|
+
setSessionStartTime(t) {
|
|
1448
|
+
this.sessionStartTime = t;
|
|
1449
|
+
}
|
|
1450
|
+
setPhaseStartTime(t) {
|
|
1451
|
+
this.phaseStartTime = t;
|
|
1452
|
+
}
|
|
1453
|
+
getPhaseStartTime() {
|
|
1454
|
+
return this.phaseStartTime;
|
|
1455
|
+
}
|
|
1456
|
+
getSessionElapsed() {
|
|
1457
|
+
return Date.now() - this.sessionStartTime;
|
|
1458
|
+
}
|
|
1459
|
+
getPhaseElapsed() {
|
|
1460
|
+
return Date.now() - this.phaseStartTime;
|
|
1461
|
+
}
|
|
1462
|
+
getVersioning() {
|
|
1463
|
+
return this.versioning ?? (this.versioning = new SpecVersioning());
|
|
1464
|
+
}
|
|
912
1465
|
clearTaskState() {
|
|
913
1466
|
this.taskStore = null;
|
|
914
1467
|
this.taskTracker = null;
|
|
@@ -1687,6 +2240,7 @@ ${diff}`;
|
|
|
1687
2240
|
// src/arg-parser.ts
|
|
1688
2241
|
var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
1689
2242
|
"yolo",
|
|
2243
|
+
"force-all-yolo",
|
|
1690
2244
|
"verbose",
|
|
1691
2245
|
"trace",
|
|
1692
2246
|
"help",
|
|
@@ -2886,8 +3440,9 @@ function buildClearCommand(opts) {
|
|
|
2886
3440
|
" /clear",
|
|
2887
3441
|
"",
|
|
2888
3442
|
"Wipes everything in the current REPL state: messages, todos, read-file tracking,",
|
|
2889
|
-
"file mtimes, meta. Memory store entries (all scopes) are cleared too.
|
|
2890
|
-
"
|
|
3443
|
+
"file mtimes, meta. Memory store entries (all scopes) are cleared too. Chat",
|
|
3444
|
+
"history on disk is reset. The terminal is wiped.",
|
|
3445
|
+
"Use this when you want a fresh conversation without restarting `wstack`."
|
|
2891
3446
|
].join("\n"),
|
|
2892
3447
|
async run(_args, ctx) {
|
|
2893
3448
|
if (ctx) {
|
|
@@ -2897,6 +3452,12 @@ function buildClearCommand(opts) {
|
|
|
2897
3452
|
ctx.fileMtimes.clear();
|
|
2898
3453
|
for (const key of Object.keys(ctx.meta)) ctx.state.deleteMeta(key);
|
|
2899
3454
|
}
|
|
3455
|
+
if (ctx?.session) {
|
|
3456
|
+
await ctx.session.clearSession();
|
|
3457
|
+
}
|
|
3458
|
+
if (opts.sessionStore) {
|
|
3459
|
+
await opts.sessionStore.clearHistory(ctx?.session.id ?? "");
|
|
3460
|
+
}
|
|
2900
3461
|
await opts.memoryStore?.clear();
|
|
2901
3462
|
opts.onClear?.();
|
|
2902
3463
|
opts.renderer.clear();
|
|
@@ -3230,118 +3791,245 @@ function buildStatsCommand(opts) {
|
|
|
3230
3791
|
}
|
|
3231
3792
|
};
|
|
3232
3793
|
}
|
|
3233
|
-
|
|
3234
|
-
// src/slash-commands/fleet.ts
|
|
3235
3794
|
function buildFleetCommand(opts) {
|
|
3236
3795
|
return {
|
|
3237
3796
|
name: "fleet",
|
|
3238
|
-
description: "Inspect
|
|
3797
|
+
description: "Inspect and control the agent fleet (subagents, parallel slots).",
|
|
3239
3798
|
help: [
|
|
3240
3799
|
"Usage:",
|
|
3241
|
-
" /fleet
|
|
3242
|
-
" /fleet status
|
|
3243
|
-
" /fleet
|
|
3244
|
-
" /fleet
|
|
3245
|
-
" /fleet
|
|
3246
|
-
" /fleet
|
|
3247
|
-
" /fleet
|
|
3248
|
-
"
|
|
3249
|
-
"
|
|
3250
|
-
" /fleet retry all Re-assign every interrupted task at once.",
|
|
3251
|
-
" /fleet log List subagent transcripts available on disk.",
|
|
3252
|
-
" /fleet log <id> Print a compact summary of a subagent transcript.",
|
|
3253
|
-
" /fleet log <id> raw Dump the full per-subagent JSONL.",
|
|
3254
|
-
" /fleet stream on|off Show/hide subagent activity in the main history.",
|
|
3255
|
-
" /fleet help Show this help."
|
|
3800
|
+
" /fleet Show fleet status (default)",
|
|
3801
|
+
" /fleet status Same as /fleet (verbose status)",
|
|
3802
|
+
" /fleet spawn <role> [count] Spawn N subagents of a role (default 1)",
|
|
3803
|
+
" /fleet terminate <subagentId> Stop a specific subagent by id",
|
|
3804
|
+
" /fleet kill Stop all running subagents",
|
|
3805
|
+
" /fleet usage Token and cost breakdown across the fleet",
|
|
3806
|
+
" /fleet journal Show recent journal entries from /goal journal",
|
|
3807
|
+
"",
|
|
3808
|
+
"Works during /autonomy parallel mode and standalone director sessions."
|
|
3256
3809
|
].join("\n"),
|
|
3257
3810
|
async run(args) {
|
|
3258
|
-
|
|
3259
|
-
const
|
|
3260
|
-
const
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
}
|
|
3269
|
-
case "kill": {
|
|
3270
|
-
if (!target) return { message: "Usage: /fleet kill <subagent-id>" };
|
|
3271
|
-
return { message: await opts.onFleet("kill", target) };
|
|
3272
|
-
}
|
|
3273
|
-
case "concurrency": {
|
|
3274
|
-
return { message: await opts.onFleet("concurrency", target) };
|
|
3275
|
-
}
|
|
3276
|
-
case "retry": {
|
|
3277
|
-
if (!opts.onFleetRetry) {
|
|
3278
|
-
return { message: "Retry is only available when director mode is active." };
|
|
3279
|
-
}
|
|
3280
|
-
const msg = await opts.onFleetRetry(target);
|
|
3281
|
-
return { message: msg };
|
|
3282
|
-
}
|
|
3283
|
-
case "log": {
|
|
3284
|
-
if (!opts.onFleetLog) {
|
|
3285
|
-
return { message: "Log inspection is only available when a fleet root is configured." };
|
|
3811
|
+
const parts = args.trim().split(/\s+/);
|
|
3812
|
+
const cmd = parts[0]?.toLowerCase() ?? "";
|
|
3813
|
+
const subargs = parts.slice(1);
|
|
3814
|
+
if (!cmd || cmd === "status" || cmd === "info" || cmd === "manifest") {
|
|
3815
|
+
if (opts.onFleetStatus) {
|
|
3816
|
+
const status = opts.onFleetStatus();
|
|
3817
|
+
if (!status) {
|
|
3818
|
+
const msg4 = `${color.amber("\u26A0 No fleet active.")} Start /autonomy parallel first, or pass --director to a session.`;
|
|
3819
|
+
opts.renderer.write(msg4);
|
|
3820
|
+
return { message: msg4 };
|
|
3286
3821
|
}
|
|
3287
|
-
const
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3822
|
+
const lines = [];
|
|
3823
|
+
lines.push(`${color.bold("Fleet Status")}`);
|
|
3824
|
+
lines.push(
|
|
3825
|
+
color.dim(
|
|
3826
|
+
` coordinator: ${status.coordinatorId} \xB7 pending: ${status.pendingTasks} \xB7 done: ${status.completedTasks}`
|
|
3827
|
+
)
|
|
3828
|
+
);
|
|
3829
|
+
if (status.subagents.length === 0) {
|
|
3830
|
+
lines.push(color.dim(" No active subagents."));
|
|
3831
|
+
} else {
|
|
3832
|
+
lines.push("");
|
|
3833
|
+
lines.push(
|
|
3834
|
+
` ${color.bold("ID").padEnd(36)} ${color.bold("NAME").padEnd(16)} ${color.bold("STATUS").padEnd(10)} ${color.bold("TASK")}`
|
|
3835
|
+
);
|
|
3836
|
+
lines.push(color.dim(" " + "\u2500".repeat(80)));
|
|
3837
|
+
for (const sa of status.subagents) {
|
|
3838
|
+
const id = sa.id?.padEnd(36) ?? "".padEnd(36);
|
|
3839
|
+
const name = (sa.name ?? "worker").padEnd(16);
|
|
3840
|
+
const statusColor = sa.status === "running" ? color.green(sa.status.padEnd(10)) : sa.status === "idle" ? color.dim(sa.status.padEnd(10)) : color.dim(sa.status.padEnd(10));
|
|
3841
|
+
const task = sa.currentTask ?? color.dim("\u2014");
|
|
3842
|
+
lines.push(` ${id} ${name} ${statusColor} ${task}`);
|
|
3843
|
+
}
|
|
3295
3844
|
}
|
|
3296
|
-
const
|
|
3297
|
-
|
|
3298
|
-
|
|
3845
|
+
const msg3 = lines.join("\n");
|
|
3846
|
+
opts.renderer.write(msg3);
|
|
3847
|
+
return { message: msg3 };
|
|
3848
|
+
}
|
|
3849
|
+
if (opts.onFleet) {
|
|
3850
|
+
const msg3 = await opts.onFleet(cmd || "status", void 0);
|
|
3851
|
+
return { message: msg3 };
|
|
3852
|
+
}
|
|
3853
|
+
const msg2 = `${color.amber("\u26A0 No fleet active.")} Start /autonomy parallel first, or pass --director to a session.`;
|
|
3854
|
+
opts.renderer.write(msg2);
|
|
3855
|
+
return { message: msg2 };
|
|
3856
|
+
}
|
|
3857
|
+
if (cmd === "usage" || cmd === "cost" || cmd === "tokens") {
|
|
3858
|
+
if (opts.onFleetUsage) {
|
|
3859
|
+
const usage = opts.onFleetUsage();
|
|
3860
|
+
if (!usage) {
|
|
3861
|
+
const msg4 = `${color.amber("\u26A0 No fleet usage data.")} Start /autonomy parallel first.`;
|
|
3862
|
+
opts.renderer.write(msg4);
|
|
3863
|
+
return { message: msg4 };
|
|
3299
3864
|
}
|
|
3300
|
-
|
|
3301
|
-
|
|
3865
|
+
const totalCost = usage.total?.cost ?? 0;
|
|
3866
|
+
const totalIn = usage.total?.input ?? 0;
|
|
3867
|
+
const totalOut = usage.total?.output ?? 0;
|
|
3868
|
+
const lines = [];
|
|
3869
|
+
lines.push(`${color.bold("Fleet Usage")}`);
|
|
3870
|
+
lines.push(
|
|
3871
|
+
` ${color.dim("Total:")} ${color.green(`${totalCost.toFixed(4)}`)} \xB7 ${color.cyan(totalIn.toLocaleString())} in \xB7 ${color.cyan(totalOut.toLocaleString())} out`
|
|
3872
|
+
);
|
|
3873
|
+
const subagents = Object.values(usage.perSubagent);
|
|
3874
|
+
if (subagents.length > 0) {
|
|
3875
|
+
lines.push("");
|
|
3876
|
+
for (const sa of subagents) {
|
|
3877
|
+
const name = (sa.subagentId ?? "?").padEnd(20);
|
|
3878
|
+
const cost = `${(sa.cost ?? 0).toFixed(4)}`.padStart(10);
|
|
3879
|
+
const tokens = `${sa.input ?? 0} in / ${sa.output ?? 0} out`.padEnd(30);
|
|
3880
|
+
lines.push(` ${color.dim(name)} ${color.cyan(cost)} ${color.dim(tokens)}`);
|
|
3881
|
+
}
|
|
3302
3882
|
}
|
|
3303
|
-
const
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
return { message: `Fleet streaming ${enabled ? "enabled" : "disabled"}.` };
|
|
3883
|
+
const msg3 = lines.join("\n");
|
|
3884
|
+
opts.renderer.write(msg3);
|
|
3885
|
+
return { message: msg3 };
|
|
3307
3886
|
}
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
return {
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
" /fleet status pending + completed tasks per subagent",
|
|
3316
|
-
" /fleet usage iterations, tool calls, duration roll-up",
|
|
3317
|
-
" /fleet kill <id> terminate a subagent",
|
|
3318
|
-
" /fleet manifest director manifest (requires --director)"
|
|
3319
|
-
].join("\n")
|
|
3320
|
-
};
|
|
3321
|
-
default:
|
|
3322
|
-
return {
|
|
3323
|
-
message: `Unknown subcommand "${verb}". Try: status | usage | kill <id> | manifest | help`
|
|
3324
|
-
};
|
|
3887
|
+
if (opts.onFleet) {
|
|
3888
|
+
const msg3 = await opts.onFleet("usage", void 0);
|
|
3889
|
+
return { message: msg3 };
|
|
3890
|
+
}
|
|
3891
|
+
const msg2 = `${color.amber("\u26A0 No fleet usage data.")} Start /autonomy parallel first.`;
|
|
3892
|
+
opts.renderer.write(msg2);
|
|
3893
|
+
return { message: msg2 };
|
|
3325
3894
|
}
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3895
|
+
if (cmd === "retry") {
|
|
3896
|
+
if (opts.onFleetRetry) {
|
|
3897
|
+
const targetId = subargs[0];
|
|
3898
|
+
const msg3 = await opts.onFleetRetry(targetId);
|
|
3899
|
+
return { message: msg3 };
|
|
3900
|
+
}
|
|
3901
|
+
if (opts.onFleet) {
|
|
3902
|
+
const msg3 = await opts.onFleet("retry", subargs[0]);
|
|
3903
|
+
return { message: msg3 };
|
|
3904
|
+
}
|
|
3905
|
+
const msg2 = `Retry is only available when director mode is active.`;
|
|
3906
|
+
opts.renderer.writeWarning(msg2);
|
|
3907
|
+
return { message: msg2 };
|
|
3908
|
+
}
|
|
3909
|
+
if (cmd === "journal" || cmd === "log") {
|
|
3910
|
+
if (opts.onFleetLog) {
|
|
3911
|
+
const subagentId = subargs[0];
|
|
3912
|
+
const mode = subargs[1] === "raw" ? "raw" : "summary";
|
|
3913
|
+
const msg3 = await opts.onFleetLog(subagentId, mode);
|
|
3914
|
+
return { message: msg3 };
|
|
3915
|
+
}
|
|
3916
|
+
if (opts.onFleet) {
|
|
3917
|
+
const msg3 = await opts.onFleet("log", subargs[0]);
|
|
3918
|
+
return { message: msg3 };
|
|
3919
|
+
}
|
|
3920
|
+
const msg2 = `${color.dim("No journal entries yet.")}`;
|
|
3921
|
+
opts.renderer.write(msg2);
|
|
3922
|
+
return { message: msg2 };
|
|
3923
|
+
}
|
|
3924
|
+
if (cmd === "kill" || cmd === "stop-all") {
|
|
3925
|
+
const targetId = subargs[0];
|
|
3926
|
+
if (!targetId) {
|
|
3927
|
+
const msg3 = `Usage: /fleet kill <subagent-id>`;
|
|
3928
|
+
opts.renderer.writeWarning(msg3);
|
|
3929
|
+
return { message: msg3 };
|
|
3930
|
+
}
|
|
3931
|
+
if (opts.onFleetKill) {
|
|
3932
|
+
const killed = opts.onFleetKill();
|
|
3933
|
+
const msg3 = `${color.red("\u2717 Killed")} ${killed} subagent(s).`;
|
|
3934
|
+
opts.renderer.write(msg3);
|
|
3935
|
+
return { message: msg3 };
|
|
3936
|
+
}
|
|
3937
|
+
if (opts.onFleet) {
|
|
3938
|
+
const msg3 = await opts.onFleet("kill", targetId);
|
|
3939
|
+
return { message: msg3 };
|
|
3940
|
+
}
|
|
3941
|
+
const msg2 = `${color.amber("\u26A0 /fleet kill is not wired in this session.")}`;
|
|
3942
|
+
opts.renderer.writeWarning(msg2);
|
|
3943
|
+
return { message: msg2 };
|
|
3944
|
+
}
|
|
3945
|
+
if (cmd === "terminate" || cmd === "stop") {
|
|
3946
|
+
const targetId = subargs[0];
|
|
3947
|
+
if (!targetId) {
|
|
3948
|
+
const msg2 = `${color.amber("\u26A0 /fleet terminate requires a subagentId.")} Use /fleet to see active ids.`;
|
|
3949
|
+
opts.renderer.writeWarning(msg2);
|
|
3950
|
+
return { message: msg2 };
|
|
3951
|
+
}
|
|
3952
|
+
if (!opts.onFleetTerminate) {
|
|
3953
|
+
const msg2 = `${color.amber("\u26A0 /fleet terminate is not wired in this session.")}`;
|
|
3954
|
+
opts.renderer.writeWarning(msg2);
|
|
3955
|
+
return { message: msg2 };
|
|
3956
|
+
}
|
|
3957
|
+
const ok = opts.onFleetTerminate(targetId);
|
|
3958
|
+
if (ok) {
|
|
3959
|
+
const msg2 = `${color.green("\u2713 Terminated")} subagent ${color.bold(targetId)}.`;
|
|
3960
|
+
opts.renderer.write(msg2);
|
|
3961
|
+
return { message: msg2 };
|
|
3962
|
+
} else {
|
|
3963
|
+
const msg2 = `${color.red("\u2717 Failed")} to terminate ${color.bold(targetId)}. Subagent may already be stopped.`;
|
|
3964
|
+
opts.renderer.writeWarning(msg2);
|
|
3965
|
+
return { message: msg2 };
|
|
3966
|
+
}
|
|
3967
|
+
}
|
|
3968
|
+
if (cmd === "spawn" || cmd === "add") {
|
|
3969
|
+
const role = subargs[0] ?? "worker";
|
|
3970
|
+
const count = Math.min(16, Math.max(1, parseInt(subargs[1] ?? "1", 10) || 1));
|
|
3971
|
+
if (!opts.onFleetSpawn) {
|
|
3972
|
+
const msg3 = `${color.amber("\u26A0 /fleet spawn is not wired in this session.")}`;
|
|
3973
|
+
opts.renderer.writeWarning(msg3);
|
|
3974
|
+
return { message: msg3 };
|
|
3975
|
+
}
|
|
3976
|
+
const spawned = [];
|
|
3977
|
+
let msg2;
|
|
3978
|
+
for (let i = 0; i < count; i++) {
|
|
3979
|
+
try {
|
|
3980
|
+
const id = await opts.onFleetSpawn(role);
|
|
3981
|
+
spawned.push(id);
|
|
3982
|
+
} catch (err) {
|
|
3983
|
+
const msg3 = `${color.red("\u2717 Spawn failed")} for slot ${i + 1}: ${err instanceof Error ? err.message : String(err)}`;
|
|
3984
|
+
opts.renderer.writeWarning(msg3);
|
|
3985
|
+
}
|
|
3986
|
+
}
|
|
3987
|
+
if (spawned.length === count) {
|
|
3988
|
+
msg2 = `${color.green("\u2713 Spawned")} ${count} subagent(s) of role ${color.bold(role)}.`;
|
|
3989
|
+
opts.renderer.write(msg2);
|
|
3990
|
+
} else {
|
|
3991
|
+
msg2 = `${color.amber("\u26A0 Spawned")} ${spawned.length}/${count} subagent(s). Check /fleet for details.`;
|
|
3992
|
+
opts.renderer.writeWarning(msg2);
|
|
3993
|
+
}
|
|
3994
|
+
return { message: msg2 };
|
|
3995
|
+
}
|
|
3996
|
+
if (cmd === "help" || cmd === "?") {
|
|
3997
|
+
const msg2 = [
|
|
3998
|
+
`${color.bold("Fleet Commands")}`,
|
|
3999
|
+
` ${color.dim("/fleet")} Show fleet status (default)`,
|
|
4000
|
+
` ${color.dim("/fleet status")} Same as /fleet (verbose status)`,
|
|
4001
|
+
` ${color.dim("/fleet spawn <role> [count]")} Spawn N subagents of a role (default 1)`,
|
|
4002
|
+
` ${color.dim("/fleet terminate <subagentId>")} Stop a specific subagent by id`,
|
|
4003
|
+
` ${color.dim("/fleet kill")} Stop all running subagents`,
|
|
4004
|
+
` ${color.dim("/fleet usage")} Token and cost breakdown across the fleet`,
|
|
4005
|
+
` ${color.dim("/fleet journal")} Show recent journal entries from /goal journal`
|
|
4006
|
+
].join("\n");
|
|
4007
|
+
opts.renderer.write(msg2);
|
|
4008
|
+
return { message: msg2 };
|
|
4009
|
+
}
|
|
4010
|
+
const valid = ["status", "usage", "spawn", "terminate", "kill", "retry", "journal"];
|
|
4011
|
+
const msg = `Unknown subcommand "${cmd}". Valid subcommands: ${valid.join(", ")}. Run /fleet with no args to see status, or /fleet help for usage.`;
|
|
4012
|
+
opts.renderer.writeWarning(msg);
|
|
4013
|
+
return { message: msg };
|
|
4014
|
+
}
|
|
4015
|
+
};
|
|
4016
|
+
}
|
|
4017
|
+
function buildHealthCommand(opts) {
|
|
4018
|
+
return {
|
|
4019
|
+
name: "health",
|
|
4020
|
+
description: "Run health checks (requires --metrics flag).",
|
|
4021
|
+
async run() {
|
|
4022
|
+
if (!opts.healthRegistry)
|
|
4023
|
+
return { message: "Health checks not enabled. Restart with --metrics." };
|
|
4024
|
+
const result = await opts.healthRegistry.run();
|
|
4025
|
+
const lines = [
|
|
4026
|
+
`${statusIcon(result.status)} overall: ${result.status}`,
|
|
4027
|
+
...result.checks.map((c) => {
|
|
4028
|
+
const detail = c.detail ? color.dim(` \u2014 ${c.detail}`) : "";
|
|
4029
|
+
return ` ${statusIcon(c.status)} ${c.name}: ${c.status}${detail}`;
|
|
4030
|
+
})
|
|
4031
|
+
];
|
|
4032
|
+
return { message: lines.join("\n") };
|
|
3345
4033
|
}
|
|
3346
4034
|
};
|
|
3347
4035
|
}
|
|
@@ -4127,9 +4815,10 @@ function buildAutonomyCommand(opts) {
|
|
|
4127
4815
|
" /autonomy off Disabled \u2014 agent stops after each turn (default)",
|
|
4128
4816
|
" /autonomy suggest Show next-step suggestions after each turn",
|
|
4129
4817
|
" /autonomy on Auto-continue \u2014 agent picks next step and proceeds",
|
|
4130
|
-
" /autonomy eternal
|
|
4818
|
+
" /autonomy eternal Goal-driven loop \u2014 runs forever against /goal",
|
|
4819
|
+
" /autonomy parallel Parallel mode \u2014 4-8 agents per tick, fan-out parallelism",
|
|
4131
4820
|
" /autonomy stop Stop eternal mode (no-op for other modes)",
|
|
4132
|
-
" /autonomy toggle Cycle: off \u2192 suggest \u2192 auto \u2192 eternal \u2192 off",
|
|
4821
|
+
" /autonomy toggle Cycle: off \u2192 suggest \u2192 auto \u2192 eternal \u2192 parallel \u2192 off",
|
|
4133
4822
|
"",
|
|
4134
4823
|
"Modes:",
|
|
4135
4824
|
" off \u2014 Normal interactive mode. Agent stops and waits.",
|
|
@@ -4138,8 +4827,14 @@ function buildAutonomyCommand(opts) {
|
|
|
4138
4827
|
" Runs indefinitely until you press Esc or Ctrl+C.",
|
|
4139
4828
|
" eternal \u2014 Goal-driven sense/decide/execute/reflect loop. Requires /goal.",
|
|
4140
4829
|
" Force-enables YOLO. Runs until /autonomy stop or Ctrl+C twice.",
|
|
4830
|
+
" parallel \u2014 Fan-out 4\u20138 subagents per tick. Each tick decomposes the goal,",
|
|
4831
|
+
" spawns N agents, awaits results, aggregates. Requires /goal.",
|
|
4832
|
+
" Force-enables YOLO. Runs until /autonomy stop or Ctrl+C twice.",
|
|
4833
|
+
"",
|
|
4834
|
+
"Eternal stage flow: decide \u2192 execute \u2192 reflect \u2192 sleep | paused | stopped",
|
|
4835
|
+
"Stage shown in real-time. Use /goal pause to pause, /goal resume to continue.",
|
|
4141
4836
|
"",
|
|
4142
|
-
"In auto/eternal modes the agent works autonomously. Press Esc to redirect,",
|
|
4837
|
+
"In auto/eternal/parallel modes the agent works autonomously. Press Esc to redirect,",
|
|
4143
4838
|
"Ctrl+C to stop the active iteration. /autonomy stop ends the eternal loop."
|
|
4144
4839
|
].join("\n"),
|
|
4145
4840
|
async run(args) {
|
|
@@ -4155,9 +4850,10 @@ function buildAutonomyCommand(opts) {
|
|
|
4155
4850
|
off: `${color.green("OFF")} ${color.dim("(agent stops after each turn)")}`,
|
|
4156
4851
|
suggest: `${color.cyan("SUGGEST")} ${color.dim("(shows next-step suggestions)")}`,
|
|
4157
4852
|
auto: `${color.yellow("AUTO")} ${color.dim("(self-driving \u2014 Esc to redirect, Ctrl+C to stop)")}`,
|
|
4158
|
-
eternal: `${color.red("ETERNAL")} ${color.dim("(
|
|
4853
|
+
eternal: `${color.red("ETERNAL")} ${color.dim("(goal-driven loop \u2014 YOLO, until /autonomy stop)")}`,
|
|
4854
|
+
"eternal-parallel": `${color.magenta("PARALLEL")} ${color.dim("(4-8 subagents per tick \u2014 fan-out, until /autonomy stop)")}`
|
|
4159
4855
|
};
|
|
4160
|
-
const lines = [`Autonomy mode: ${labels2[current]}`];
|
|
4856
|
+
const lines = [`Autonomy mode: ${labels2[current] ?? current}`];
|
|
4161
4857
|
try {
|
|
4162
4858
|
const goal = await loadGoal(goalFilePath(opts.projectRoot));
|
|
4163
4859
|
if (goal) {
|
|
@@ -4189,6 +4885,8 @@ function buildAutonomyCommand(opts) {
|
|
|
4189
4885
|
opts.renderer.writeWarning(msg3);
|
|
4190
4886
|
return { message: msg3 };
|
|
4191
4887
|
}
|
|
4888
|
+
opts.getEternalEngine?.()?.stop();
|
|
4889
|
+
opts.getParallelEngine?.()?.stop();
|
|
4192
4890
|
opts.onEternalStop();
|
|
4193
4891
|
opts.onAutonomy("off");
|
|
4194
4892
|
let summaryLine = "";
|
|
@@ -4206,7 +4904,7 @@ function buildAutonomyCommand(opts) {
|
|
|
4206
4904
|
}
|
|
4207
4905
|
} catch {
|
|
4208
4906
|
}
|
|
4209
|
-
const msg2 = `${color.amber("Eternal mode stop requested.")} The current iteration will finish, then the loop exits.${summaryLine}`;
|
|
4907
|
+
const msg2 = `${color.amber("Eternal/parallel mode stop requested.")} The current iteration will finish, then the loop exits.${summaryLine}`;
|
|
4210
4908
|
opts.renderer.write(msg2);
|
|
4211
4909
|
return { message: msg2 };
|
|
4212
4910
|
}
|
|
@@ -4219,19 +4917,21 @@ function buildAutonomyCommand(opts) {
|
|
|
4219
4917
|
newMode = "suggest";
|
|
4220
4918
|
} else if (arg === "eternal" || arg === "forever" || arg === "infinite" || arg === "sittinsene") {
|
|
4221
4919
|
newMode = "eternal";
|
|
4920
|
+
} else if (arg === "parallel" || arg === "eternal-parallel" || arg === "fanout") {
|
|
4921
|
+
newMode = "eternal-parallel";
|
|
4222
4922
|
} else if (arg === "toggle" || arg === "cycle") {
|
|
4223
4923
|
const current = opts.onAutonomy() ?? "off";
|
|
4224
4924
|
const cycle = ["off", "suggest", "auto", "eternal"];
|
|
4225
4925
|
newMode = cycle[(cycle.indexOf(current) + 1) % cycle.length] ?? "off";
|
|
4226
4926
|
} else {
|
|
4227
|
-
const msg2 = `Unknown argument: ${arg}. Use /autonomy on, off, suggest, eternal, stop, or toggle.`;
|
|
4927
|
+
const msg2 = `Unknown argument: ${arg}. Use /autonomy on, off, suggest, eternal, parallel, stop, or toggle.`;
|
|
4228
4928
|
opts.renderer.writeWarning(msg2);
|
|
4229
4929
|
return { message: msg2 };
|
|
4230
4930
|
}
|
|
4231
|
-
if (newMode === "eternal") {
|
|
4931
|
+
if (newMode === "eternal" || newMode === "eternal-parallel") {
|
|
4232
4932
|
const goal = await loadGoal(goalFilePath(opts.projectRoot));
|
|
4233
4933
|
if (!goal) {
|
|
4234
|
-
const msg3 = `${color.red("Eternal mode requires a goal.")} Run \`/goal set <mission>\` first.`;
|
|
4934
|
+
const msg3 = `${color.red("Eternal/parallel mode requires a goal.")} Run \`/goal set <mission>\` first.`;
|
|
4235
4935
|
opts.renderer.writeWarning(msg3);
|
|
4236
4936
|
return { message: msg3 };
|
|
4237
4937
|
}
|
|
@@ -4248,14 +4948,15 @@ function buildAutonomyCommand(opts) {
|
|
|
4248
4948
|
}
|
|
4249
4949
|
if (opts.onYolo) opts.onYolo(true);
|
|
4250
4950
|
opts.onAutonomy(newMode);
|
|
4251
|
-
opts.onEternalStart();
|
|
4252
|
-
const
|
|
4951
|
+
opts.onEternalStart(newMode);
|
|
4952
|
+
const modeLabel = newMode === "eternal-parallel" ? `${color.magenta("PARALLEL")} mode` : `${color.red("ETERNAL")} mode`;
|
|
4953
|
+
const msg2 = `Autonomy mode: ${modeLabel} \u2014 engine launching against goal: ${color.bold(goal.goal)}
|
|
4253
4954
|
${color.dim("YOLO forced ON. Use /autonomy stop to end. Journal at /goal journal.")}`;
|
|
4254
4955
|
opts.renderer.write(msg2);
|
|
4255
4956
|
return { message: msg2 };
|
|
4256
4957
|
}
|
|
4257
4958
|
const previous = opts.onAutonomy();
|
|
4258
|
-
if (previous === "eternal" && opts.onEternalStop) {
|
|
4959
|
+
if ((previous === "eternal" || previous === "eternal-parallel") && opts.onEternalStop) {
|
|
4259
4960
|
opts.onEternalStop();
|
|
4260
4961
|
}
|
|
4261
4962
|
opts.onAutonomy(newMode);
|
|
@@ -4263,7 +4964,8 @@ ${color.dim("YOLO forced ON. Use /autonomy stop to end. Journal at /goal journal
|
|
|
4263
4964
|
off: `${color.green("OFF")} \u2014 agent stops after each turn`,
|
|
4264
4965
|
suggest: `${color.cyan("SUGGEST")} \u2014 shows next-step suggestions after each turn`,
|
|
4265
4966
|
auto: `${color.yellow("AUTO")} \u2014 self-driving, agent continues automatically`,
|
|
4266
|
-
eternal: `${color.red("ETERNAL")} \u2014 goal-driven sittin-sene loop
|
|
4967
|
+
eternal: `${color.red("ETERNAL")} \u2014 goal-driven sittin-sene loop`,
|
|
4968
|
+
"eternal-parallel": `${color.magenta("PARALLEL")} \u2014 fan-out 4-8 subagents per tick`
|
|
4267
4969
|
};
|
|
4268
4970
|
const msg = `Autonomy mode: ${labels[newMode]}`;
|
|
4269
4971
|
opts.renderer.write(msg);
|
|
@@ -4280,7 +4982,9 @@ var KNOWN_VERBS = /* @__PURE__ */ new Set([
|
|
|
4280
4982
|
"clear",
|
|
4281
4983
|
"reset",
|
|
4282
4984
|
"journal",
|
|
4283
|
-
"log"
|
|
4985
|
+
"log",
|
|
4986
|
+
"pause",
|
|
4987
|
+
"resume"
|
|
4284
4988
|
]);
|
|
4285
4989
|
function buildGoalCommand(opts) {
|
|
4286
4990
|
return {
|
|
@@ -4291,9 +4995,14 @@ function buildGoalCommand(opts) {
|
|
|
4291
4995
|
" /goal Show current goal + recent journal",
|
|
4292
4996
|
" /goal set <text> Set a new goal (overwrites previous)",
|
|
4293
4997
|
" /goal clear Clear the goal (stops eternal mode if running)",
|
|
4998
|
+
" /goal pause Pause at end of current iteration (no-op if already paused)",
|
|
4999
|
+
" /goal resume Resume a paused goal (no-op if not paused)",
|
|
4294
5000
|
" /goal status Same as /goal (alias)",
|
|
4295
5001
|
" /goal journal [N] Show last N journal entries (default 25)",
|
|
4296
5002
|
"",
|
|
5003
|
+
"Stage flow: decide \u2192 execute \u2192 reflect \u2192 sleep | paused | stopped",
|
|
5004
|
+
"Pausing stops after current iteration completes. Resume continues from next iteration.",
|
|
5005
|
+
"",
|
|
4297
5006
|
"Goals live in <projectRoot>/.wrongstack/goal.json and persist across sessions.",
|
|
4298
5007
|
"A goal is the prerequisite for /autonomy eternal \u2014 the engine consults it on",
|
|
4299
5008
|
"every iteration to decide what to do next."
|
|
@@ -4382,6 +5091,42 @@ ${lines.join("\n")}`;
|
|
|
4382
5091
|
opts.renderer.write(msg);
|
|
4383
5092
|
return { message: msg };
|
|
4384
5093
|
}
|
|
5094
|
+
case "pause": {
|
|
5095
|
+
const current = await loadGoal(goalPath);
|
|
5096
|
+
if (!current) {
|
|
5097
|
+
const msg2 = "No goal set \u2014 nothing to pause.";
|
|
5098
|
+
opts.renderer.writeWarning(msg2);
|
|
5099
|
+
return { message: msg2 };
|
|
5100
|
+
}
|
|
5101
|
+
if (current.goalState === "paused") {
|
|
5102
|
+
const msg2 = `${color.dim("Already paused.")} Use /goal resume to continue.`;
|
|
5103
|
+
opts.renderer.write(msg2);
|
|
5104
|
+
return { message: msg2 };
|
|
5105
|
+
}
|
|
5106
|
+
const paused = { ...current, goalState: "paused" };
|
|
5107
|
+
await saveGoal(goalPath, paused);
|
|
5108
|
+
const msg = `${color.cyan("Goal paused.")} Current iteration will finish, then the loop stops. Use /goal resume to continue.`;
|
|
5109
|
+
opts.renderer.write(msg);
|
|
5110
|
+
return { message: msg };
|
|
5111
|
+
}
|
|
5112
|
+
case "resume": {
|
|
5113
|
+
const current = await loadGoal(goalPath);
|
|
5114
|
+
if (!current) {
|
|
5115
|
+
const msg2 = "No goal set \u2014 cannot resume.";
|
|
5116
|
+
opts.renderer.writeWarning(msg2);
|
|
5117
|
+
return { message: msg2 };
|
|
5118
|
+
}
|
|
5119
|
+
if (current.goalState !== "paused") {
|
|
5120
|
+
const msg2 = `${color.dim("Not paused.")} Use /goal set <text> to create or update a goal first.`;
|
|
5121
|
+
opts.renderer.writeWarning(msg2);
|
|
5122
|
+
return { message: msg2 };
|
|
5123
|
+
}
|
|
5124
|
+
const resumed = { ...current, goalState: "active" };
|
|
5125
|
+
await saveGoal(goalPath, resumed);
|
|
5126
|
+
const msg = `${color.green("Goal resumed.")} Loop will continue from the next iteration.`;
|
|
5127
|
+
opts.renderer.write(msg);
|
|
5128
|
+
return { message: msg };
|
|
5129
|
+
}
|
|
4385
5130
|
default: {
|
|
4386
5131
|
const msg = `Unknown subcommand "${verb}". Try: show | set <text> | clear | journal [N]`;
|
|
4387
5132
|
opts.renderer.writeWarning(msg);
|
|
@@ -4652,178 +5397,993 @@ ${depIssues === 0 ? "\u2705 No known vulnerabilities detected" : `\u26A0\uFE0F $
|
|
|
4652
5397
|
return { message: `\u274C Audit failed: ${error}` };
|
|
4653
5398
|
}
|
|
4654
5399
|
}
|
|
4655
|
-
async function handleReport(reportId) {
|
|
4656
|
-
const reportsDir = "security-reports";
|
|
4657
|
-
try {
|
|
4658
|
-
const files = await readdir(reportsDir);
|
|
4659
|
-
const reports = files.filter((f) => f.startsWith("security-report-") && (f.endsWith(".md") || f.endsWith(".json"))).sort().reverse();
|
|
4660
|
-
if (!reportId) {
|
|
4661
|
-
if (reports.length === 0) {
|
|
4662
|
-
return { message: "\u{1F4ED} No security reports found. Run `/security scan` first." };
|
|
4663
|
-
}
|
|
4664
|
-
const list = reports.map((r, i) => {
|
|
4665
|
-
const date = r.replace("security-report-", "").replace(/\.(md|json)$/, "");
|
|
4666
|
-
return ` ${i + 1}. ${date}`;
|
|
4667
|
-
}).join("\n");
|
|
4668
|
-
return { message: `# Available Security Reports
|
|
5400
|
+
async function handleReport(reportId) {
|
|
5401
|
+
const reportsDir = "security-reports";
|
|
5402
|
+
try {
|
|
5403
|
+
const files = await readdir(reportsDir);
|
|
5404
|
+
const reports = files.filter((f) => f.startsWith("security-report-") && (f.endsWith(".md") || f.endsWith(".json"))).sort().reverse();
|
|
5405
|
+
if (!reportId) {
|
|
5406
|
+
if (reports.length === 0) {
|
|
5407
|
+
return { message: "\u{1F4ED} No security reports found. Run `/security scan` first." };
|
|
5408
|
+
}
|
|
5409
|
+
const list = reports.map((r, i) => {
|
|
5410
|
+
const date = r.replace("security-report-", "").replace(/\.(md|json)$/, "");
|
|
5411
|
+
return ` ${i + 1}. ${date}`;
|
|
5412
|
+
}).join("\n");
|
|
5413
|
+
return { message: `# Available Security Reports
|
|
5414
|
+
|
|
5415
|
+
${list}
|
|
5416
|
+
|
|
5417
|
+
Use \`/security report <number>\` to view a specific report.` };
|
|
5418
|
+
}
|
|
5419
|
+
const index = parseInt(reportId, 10) - 1;
|
|
5420
|
+
if (!isNaN(index) && reports[index]) {
|
|
5421
|
+
const content = await readFile(join(reportsDir, reports[index]), "utf-8");
|
|
5422
|
+
return { message: `# Security Report
|
|
5423
|
+
|
|
5424
|
+
${content}` };
|
|
5425
|
+
}
|
|
5426
|
+
const match = reports.find((r) => r.includes(reportId));
|
|
5427
|
+
if (match) {
|
|
5428
|
+
const content = await readFile(join(reportsDir, match), "utf-8");
|
|
5429
|
+
return { message: `# Security Report
|
|
5430
|
+
|
|
5431
|
+
${content}` };
|
|
5432
|
+
}
|
|
5433
|
+
return { message: `\u274C Report "${reportId}" not found. Use \`/security report\` to see available reports.` };
|
|
5434
|
+
} catch {
|
|
5435
|
+
return { message: "\u{1F4ED} No security reports found. Run `/security scan` first." };
|
|
5436
|
+
}
|
|
5437
|
+
}
|
|
5438
|
+
function parseArgs2(args) {
|
|
5439
|
+
const result = {};
|
|
5440
|
+
const parts = args.split(/\s+/);
|
|
5441
|
+
for (let i = 0; i < parts.length; i++) {
|
|
5442
|
+
const part = parts[i];
|
|
5443
|
+
if (!part || !part.startsWith("--")) continue;
|
|
5444
|
+
const key = part.slice(2);
|
|
5445
|
+
const next = parts[i + 1];
|
|
5446
|
+
if (next && !next.startsWith("--")) {
|
|
5447
|
+
result[key] = next;
|
|
5448
|
+
i++;
|
|
5449
|
+
} else {
|
|
5450
|
+
result[key] = "true";
|
|
5451
|
+
}
|
|
5452
|
+
}
|
|
5453
|
+
return result;
|
|
5454
|
+
}
|
|
5455
|
+
function getHelpMessage() {
|
|
5456
|
+
return `# /security \u2014 Security Scanner
|
|
5457
|
+
|
|
5458
|
+
**Available Commands:**
|
|
5459
|
+
|
|
5460
|
+
1. **/security scan** \u2014 Run full security scan
|
|
5461
|
+
\`/security scan --depth deep --format html\`
|
|
5462
|
+
|
|
5463
|
+
2. **/security audit** \u2014 Run dependency audit + security scan
|
|
5464
|
+
|
|
5465
|
+
3. **/security report** \u2014 List available reports
|
|
5466
|
+
|
|
5467
|
+
**Features:**
|
|
5468
|
+
- Automatic tech stack detection
|
|
5469
|
+
- Dynamic security skill generation
|
|
5470
|
+
- Secrets, injection, and config vulnerability scanning
|
|
5471
|
+
- Markdown/JSON/HTML reports
|
|
5472
|
+
- .gitignore auto-update
|
|
5473
|
+
|
|
5474
|
+
Run \`/security scan\` to start.`;
|
|
5475
|
+
}
|
|
5476
|
+
var CONFIG_ENV = "WRONGSTACK_STATUSLINE_CONFIG";
|
|
5477
|
+
var DEFAULTS = {
|
|
5478
|
+
todos: true,
|
|
5479
|
+
plan: true,
|
|
5480
|
+
fleet: true,
|
|
5481
|
+
git: true,
|
|
5482
|
+
elapsed: true,
|
|
5483
|
+
context: true,
|
|
5484
|
+
cost: true
|
|
5485
|
+
};
|
|
5486
|
+
function resolveConfigPath() {
|
|
5487
|
+
return process.env[CONFIG_ENV] ?? path23.join(process.env.HOME ?? "", ".wrongstack", "statusline.json");
|
|
5488
|
+
}
|
|
5489
|
+
async function loadStatuslineConfig() {
|
|
5490
|
+
const p = resolveConfigPath();
|
|
5491
|
+
try {
|
|
5492
|
+
const raw = await fsp2.readFile(p, "utf8");
|
|
5493
|
+
return { ...DEFAULTS, ...JSON.parse(raw) };
|
|
5494
|
+
} catch {
|
|
5495
|
+
return { ...DEFAULTS };
|
|
5496
|
+
}
|
|
5497
|
+
}
|
|
5498
|
+
async function saveStatuslineConfig(cfg) {
|
|
5499
|
+
const p = resolveConfigPath();
|
|
5500
|
+
await fsp2.mkdir(path23.dirname(p), { recursive: true });
|
|
5501
|
+
await atomicWrite(p, JSON.stringify(cfg, null, 2));
|
|
5502
|
+
}
|
|
5503
|
+
function buildStatuslineCommand(deps) {
|
|
5504
|
+
return {
|
|
5505
|
+
name: "statusline",
|
|
5506
|
+
aliases: ["sl"],
|
|
5507
|
+
description: "Customize status bar chips: /statusline [item] [on|off] or /statusline reset",
|
|
5508
|
+
help: [
|
|
5509
|
+
"Usage: /statusline [item] [on|off]",
|
|
5510
|
+
" /statusline \u2014 show current config",
|
|
5511
|
+
" /statusline <item> on \u2014 enable a chip",
|
|
5512
|
+
" /statusline <item> off \u2014 disable a chip",
|
|
5513
|
+
" /statusline reset \u2014 restore defaults",
|
|
5514
|
+
"",
|
|
5515
|
+
"Available items: todos, plan, fleet, git, elapsed, context, cost",
|
|
5516
|
+
"Persistent across sessions (saved to ~/.wrongstack/statusline.json)."
|
|
5517
|
+
].join("\n"),
|
|
5518
|
+
async run(args) {
|
|
5519
|
+
const cfg = await deps.getConfig();
|
|
5520
|
+
const trimmed = args.trim();
|
|
5521
|
+
const parts = trimmed.split(/\s+/);
|
|
5522
|
+
const [item, action] = parts;
|
|
5523
|
+
if (!item) {
|
|
5524
|
+
const lines = ["StatusBar chips:"];
|
|
5525
|
+
const items = [
|
|
5526
|
+
"todos",
|
|
5527
|
+
"plan",
|
|
5528
|
+
"fleet",
|
|
5529
|
+
"git",
|
|
5530
|
+
"elapsed",
|
|
5531
|
+
"context",
|
|
5532
|
+
"cost"
|
|
5533
|
+
];
|
|
5534
|
+
for (const k of items) {
|
|
5535
|
+
const val = cfg[k];
|
|
5536
|
+
if (val === void 0) continue;
|
|
5537
|
+
lines.push(` ${val ? "\u25CF" : "\u25CB"} ${k}`);
|
|
5538
|
+
}
|
|
5539
|
+
return { message: lines.join("\n") };
|
|
5540
|
+
}
|
|
5541
|
+
if (item === "reset") {
|
|
5542
|
+
await deps.setConfig({ ...DEFAULTS });
|
|
5543
|
+
deps.setHiddenItems([]);
|
|
5544
|
+
return { message: "StatusBar config reset to defaults." };
|
|
5545
|
+
}
|
|
5546
|
+
const validItems = [
|
|
5547
|
+
"todos",
|
|
5548
|
+
"plan",
|
|
5549
|
+
"fleet",
|
|
5550
|
+
"git",
|
|
5551
|
+
"elapsed",
|
|
5552
|
+
"context",
|
|
5553
|
+
"cost"
|
|
5554
|
+
];
|
|
5555
|
+
if (!validItems.includes(item)) {
|
|
5556
|
+
return {
|
|
5557
|
+
message: `Unknown item "${item}". Available: ${validItems.join(", ")}. Usage: /statusline <item> on|off`
|
|
5558
|
+
};
|
|
5559
|
+
}
|
|
5560
|
+
const onOff = action?.toLowerCase();
|
|
5561
|
+
if (!onOff || onOff !== "on" && onOff !== "off") {
|
|
5562
|
+
return { message: `Usage: /statusline ${item} on|off` };
|
|
5563
|
+
}
|
|
5564
|
+
const next = { ...cfg, [item]: onOff === "on" };
|
|
5565
|
+
await deps.setConfig(next);
|
|
5566
|
+
if (onOff === "off") {
|
|
5567
|
+
deps.setHiddenItems([...deps.hiddenItems, item]);
|
|
5568
|
+
} else {
|
|
5569
|
+
deps.setHiddenItems(deps.hiddenItems.filter((i) => i !== item));
|
|
5570
|
+
}
|
|
5571
|
+
return { message: `statusline ${item}: ${onOff}` };
|
|
5572
|
+
}
|
|
5573
|
+
};
|
|
5574
|
+
}
|
|
5575
|
+
|
|
5576
|
+
// src/slash-commands/fix-classifier.ts
|
|
5577
|
+
var TS = ["typescript-strict"];
|
|
5578
|
+
var BH = ["bug-hunter"];
|
|
5579
|
+
var SS = ["security-scanner"];
|
|
5580
|
+
var NM = ["node-modern"];
|
|
5581
|
+
var RM = ["react-modern"];
|
|
5582
|
+
var P = [
|
|
5583
|
+
// ── TypeScript ──────────────────────────────────────────────────────────────
|
|
5584
|
+
{
|
|
5585
|
+
pat: /\bTS\d+\b/,
|
|
5586
|
+
cat: "ts",
|
|
5587
|
+
sub: "typescript",
|
|
5588
|
+
lang: "typescript",
|
|
5589
|
+
hints: TS,
|
|
5590
|
+
detail: "TypeScript error",
|
|
5591
|
+
code: (m) => m[0],
|
|
5592
|
+
conf: 1
|
|
5593
|
+
},
|
|
5594
|
+
{
|
|
5595
|
+
pat: /\btypescript\b.*\berror\b|\berror\b.*\btypescript\b/i,
|
|
5596
|
+
cat: "ts",
|
|
5597
|
+
sub: "typescript",
|
|
5598
|
+
lang: "typescript",
|
|
5599
|
+
hints: TS,
|
|
5600
|
+
detail: "TypeScript error",
|
|
5601
|
+
conf: 0.9
|
|
5602
|
+
},
|
|
5603
|
+
{
|
|
5604
|
+
pat: /\b: any\b|\bas any\b/,
|
|
5605
|
+
cat: "ts",
|
|
5606
|
+
sub: "unsafe-any",
|
|
5607
|
+
lang: "typescript",
|
|
5608
|
+
hints: TS,
|
|
5609
|
+
detail: "Unsafe `any` cast \u2014 type safety violation",
|
|
5610
|
+
conf: 0.85
|
|
5611
|
+
},
|
|
5612
|
+
{
|
|
5613
|
+
pat: /\bnoimplicit|\bstrict\b.*\bcheck\b|\btsconfig\b/i,
|
|
5614
|
+
cat: "ts",
|
|
5615
|
+
sub: "strict-mode",
|
|
5616
|
+
lang: "typescript",
|
|
5617
|
+
hints: TS,
|
|
5618
|
+
detail: "TypeScript strict mode configuration issue",
|
|
5619
|
+
conf: 0.8
|
|
5620
|
+
},
|
|
5621
|
+
// ── Rust ───────────────────────────────────────────────────────────────────
|
|
5622
|
+
{
|
|
5623
|
+
pat: /\bE\d{4,}\b/,
|
|
5624
|
+
cat: "runtime",
|
|
5625
|
+
sub: "panic",
|
|
5626
|
+
lang: "rust",
|
|
5627
|
+
hints: BH,
|
|
5628
|
+
detail: "Rust error",
|
|
5629
|
+
conf: 1,
|
|
5630
|
+
code: (m) => m[0]
|
|
5631
|
+
},
|
|
5632
|
+
{
|
|
5633
|
+
pat: /\bthread.*panicked|panicked at /i,
|
|
5634
|
+
cat: "runtime",
|
|
5635
|
+
sub: "panic",
|
|
5636
|
+
lang: "rust",
|
|
5637
|
+
hints: BH,
|
|
5638
|
+
detail: "Rust panic",
|
|
5639
|
+
conf: 1
|
|
5640
|
+
},
|
|
5641
|
+
{
|
|
5642
|
+
pat: /\brustc.*error|compilation failed.*rust/i,
|
|
5643
|
+
cat: "compile",
|
|
5644
|
+
sub: "rust-compile",
|
|
5645
|
+
lang: "rust",
|
|
5646
|
+
hints: BH,
|
|
5647
|
+
detail: "Rust compiler error",
|
|
5648
|
+
conf: 1
|
|
5649
|
+
},
|
|
5650
|
+
// ── Go ──────────────────────────────────────────────────────────────────────
|
|
5651
|
+
{
|
|
5652
|
+
pat: /\bgo build\b.*fail|golang.*error/i,
|
|
5653
|
+
cat: "compile",
|
|
5654
|
+
sub: "go-compile",
|
|
5655
|
+
lang: "go",
|
|
5656
|
+
hints: BH,
|
|
5657
|
+
detail: "Go build error",
|
|
5658
|
+
conf: 0.95
|
|
5659
|
+
},
|
|
5660
|
+
{
|
|
5661
|
+
pat: /\bnil pointer|nil dereference|invalid memory address/i,
|
|
5662
|
+
cat: "runtime",
|
|
5663
|
+
sub: "nil-pointer",
|
|
5664
|
+
lang: "go",
|
|
5665
|
+
hints: BH,
|
|
5666
|
+
detail: "Go nil pointer dereference",
|
|
5667
|
+
conf: 0.9
|
|
5668
|
+
},
|
|
5669
|
+
// ── Python ────────────────────────────────────────────────────────────────
|
|
5670
|
+
{
|
|
5671
|
+
pat: /\btraceback \(most recent call last\)/i,
|
|
5672
|
+
cat: "runtime",
|
|
5673
|
+
sub: "python-traceback",
|
|
5674
|
+
lang: "python",
|
|
5675
|
+
hints: BH,
|
|
5676
|
+
detail: "Python runtime error / traceback",
|
|
5677
|
+
conf: 1
|
|
5678
|
+
},
|
|
5679
|
+
{
|
|
5680
|
+
pat: /\bpython.*error|python.*exception|modulenot founderror|importerror/i,
|
|
5681
|
+
cat: "runtime",
|
|
5682
|
+
sub: "python-traceback",
|
|
5683
|
+
lang: "python",
|
|
5684
|
+
hints: BH,
|
|
5685
|
+
detail: "Python runtime error",
|
|
5686
|
+
conf: 1
|
|
5687
|
+
},
|
|
5688
|
+
{
|
|
5689
|
+
pat: /\battributeerror\b|\btypeerror\b.*python|python.*type error/i,
|
|
5690
|
+
cat: "runtime",
|
|
5691
|
+
sub: "python-type",
|
|
5692
|
+
lang: "python",
|
|
5693
|
+
hints: BH,
|
|
5694
|
+
detail: "Python type/error",
|
|
5695
|
+
conf: 0.9
|
|
5696
|
+
},
|
|
5697
|
+
{
|
|
5698
|
+
pat: /\bpip install|requirement.*not found|package.*not found.*python/i,
|
|
5699
|
+
cat: "dep",
|
|
5700
|
+
sub: "python-dep",
|
|
5701
|
+
lang: "python",
|
|
5702
|
+
hints: BH,
|
|
5703
|
+
detail: "Python dependency error",
|
|
5704
|
+
conf: 0.9
|
|
5705
|
+
},
|
|
5706
|
+
{
|
|
5707
|
+
pat: /\bpylint|pyright|mypy.*error|flake8/i,
|
|
5708
|
+
cat: "lint",
|
|
5709
|
+
sub: "python-lint",
|
|
5710
|
+
lang: "python",
|
|
5711
|
+
hints: BH,
|
|
5712
|
+
detail: "Python linter error",
|
|
5713
|
+
conf: 0.85
|
|
5714
|
+
},
|
|
5715
|
+
// ── Ruby ──────────────────────────────────────────────────────────────────
|
|
5716
|
+
{
|
|
5717
|
+
pat: /\b(nomethoderror|noconversion|undefined method|private method|rbenv|rubygems)/i,
|
|
5718
|
+
cat: "runtime",
|
|
5719
|
+
sub: "ruby-error",
|
|
5720
|
+
lang: "ruby",
|
|
5721
|
+
hints: BH,
|
|
5722
|
+
detail: (m) => `Ruby error: ${m[1] ?? m[0]}`,
|
|
5723
|
+
conf: 0.9
|
|
5724
|
+
},
|
|
5725
|
+
{
|
|
5726
|
+
pat: /\bgem install|bundler.*error|gemspec.*error/i,
|
|
5727
|
+
cat: "dep",
|
|
5728
|
+
sub: "ruby-dep",
|
|
5729
|
+
lang: "ruby",
|
|
5730
|
+
hints: BH,
|
|
5731
|
+
detail: "Ruby gem/bundler error",
|
|
5732
|
+
conf: 0.85
|
|
5733
|
+
},
|
|
5734
|
+
// ── Java / Kotlin ────────────────────────────────────────────────────────
|
|
5735
|
+
{
|
|
5736
|
+
pat: /\bnullpointerexception|npe\b/i,
|
|
5737
|
+
cat: "runtime",
|
|
5738
|
+
sub: "null-pointer",
|
|
5739
|
+
lang: "java",
|
|
5740
|
+
hints: BH,
|
|
5741
|
+
detail: "NullPointerException",
|
|
5742
|
+
conf: 1
|
|
5743
|
+
},
|
|
5744
|
+
{
|
|
5745
|
+
pat: /\bjava\.lang\.|exception in thread|java\.util\.|java\.io\./i,
|
|
5746
|
+
cat: "runtime",
|
|
5747
|
+
sub: "java-exception",
|
|
5748
|
+
lang: "java",
|
|
5749
|
+
hints: BH,
|
|
5750
|
+
detail: "Java/Kotlin runtime exception",
|
|
5751
|
+
conf: 1
|
|
5752
|
+
},
|
|
5753
|
+
{
|
|
5754
|
+
pat: /\b(maven|gradle|ant).*error|dependency.*not found|compile.*fail.*java/i,
|
|
5755
|
+
cat: "dep",
|
|
5756
|
+
sub: "java-build",
|
|
5757
|
+
lang: "java",
|
|
5758
|
+
hints: BH,
|
|
5759
|
+
detail: "Java build/dependency error",
|
|
5760
|
+
conf: 0.9
|
|
5761
|
+
},
|
|
5762
|
+
{
|
|
5763
|
+
pat: /\bkotlin\b.*\berror\b|\bkotlin compiler\b/i,
|
|
5764
|
+
cat: "compile",
|
|
5765
|
+
sub: "kotlin-compile",
|
|
5766
|
+
lang: "kotlin",
|
|
5767
|
+
hints: BH,
|
|
5768
|
+
detail: "Kotlin compiler error",
|
|
5769
|
+
conf: 0.95
|
|
5770
|
+
},
|
|
5771
|
+
// ── C / C++ ─────────────────────────────────────────────────────────────
|
|
5772
|
+
{
|
|
5773
|
+
pat: /\bc\d+\b/i,
|
|
5774
|
+
cat: "compile",
|
|
5775
|
+
sub: "c-compile",
|
|
5776
|
+
lang: "c",
|
|
5777
|
+
hints: BH,
|
|
5778
|
+
detail: "C/C++ compiler error",
|
|
5779
|
+
conf: 1,
|
|
5780
|
+
code: (m) => m[0]
|
|
5781
|
+
},
|
|
5782
|
+
{
|
|
5783
|
+
pat: /\b(gcc|g\+\+|clang|msvc|visual studio).*error|fatal error c\d+/i,
|
|
5784
|
+
cat: "compile",
|
|
5785
|
+
sub: "c-compile",
|
|
5786
|
+
lang: "c",
|
|
5787
|
+
hints: BH,
|
|
5788
|
+
detail: "C/C++ compiler error",
|
|
5789
|
+
conf: 1
|
|
5790
|
+
},
|
|
5791
|
+
{
|
|
5792
|
+
pat: /\bsegmentation fault|segfault|sigsegv|core dumped/i,
|
|
5793
|
+
cat: "runtime",
|
|
5794
|
+
sub: "segfault",
|
|
5795
|
+
lang: "c",
|
|
5796
|
+
hints: BH,
|
|
5797
|
+
detail: "Segmentation fault (C/C++)",
|
|
5798
|
+
conf: 1
|
|
5799
|
+
},
|
|
5800
|
+
// ── C# ──────────────────────────────────────────────────────────────────
|
|
5801
|
+
{
|
|
5802
|
+
pat: /\b(csharp|dotnet|\.net).*error|cs\d+\b|nullable warning/i,
|
|
5803
|
+
cat: "compile",
|
|
5804
|
+
sub: "csharp-compile",
|
|
5805
|
+
lang: "csharp",
|
|
5806
|
+
hints: BH,
|
|
5807
|
+
detail: "C# / .NET compile error",
|
|
5808
|
+
conf: 0.9
|
|
5809
|
+
},
|
|
5810
|
+
// ── PHP ────────────────────────────────────────────────────────────────
|
|
5811
|
+
{
|
|
5812
|
+
pat: /\bphp.*error|fatal error.*php|parse error.*php/i,
|
|
5813
|
+
cat: "runtime",
|
|
5814
|
+
sub: "php-error",
|
|
5815
|
+
lang: "php",
|
|
5816
|
+
hints: BH,
|
|
5817
|
+
detail: "PHP runtime/parse error",
|
|
5818
|
+
conf: 1
|
|
5819
|
+
},
|
|
5820
|
+
// ── Scala ─────────────────────────────────────────────────────────────
|
|
5821
|
+
{
|
|
5822
|
+
pat: /\bscala.*error|type mismatch.*scala|could not find.*scala/i,
|
|
5823
|
+
cat: "compile",
|
|
5824
|
+
sub: "scala-compile",
|
|
5825
|
+
lang: "scala",
|
|
5826
|
+
hints: BH,
|
|
5827
|
+
detail: "Scala compile error",
|
|
5828
|
+
conf: 0.9
|
|
5829
|
+
},
|
|
5830
|
+
// ── Node.js / JavaScript Runtime ────────────────────────────────────────
|
|
5831
|
+
{
|
|
5832
|
+
pat: /\b(node:|node\.js|err_)/i,
|
|
5833
|
+
cat: "runtime",
|
|
5834
|
+
sub: "node-runtime",
|
|
5835
|
+
lang: "javascript",
|
|
5836
|
+
hints: NM,
|
|
5837
|
+
detail: "Node.js runtime error",
|
|
5838
|
+
conf: 1
|
|
5839
|
+
},
|
|
5840
|
+
{
|
|
5841
|
+
pat: /\bcannot read property|cannot set property|cannot call method/i,
|
|
5842
|
+
cat: "runtime",
|
|
5843
|
+
sub: "undefined-call",
|
|
5844
|
+
lang: "javascript",
|
|
5845
|
+
hints: NM,
|
|
5846
|
+
detail: "JavaScript undefined access error",
|
|
5847
|
+
conf: 0.95
|
|
5848
|
+
},
|
|
5849
|
+
{
|
|
5850
|
+
pat: /\beconnrefused|etimedout|enotfound|econnreset|dns lookup/i,
|
|
5851
|
+
cat: "infra",
|
|
5852
|
+
sub: "network",
|
|
5853
|
+
lang: "javascript",
|
|
5854
|
+
hints: NM,
|
|
5855
|
+
detail: "Node.js network error",
|
|
5856
|
+
conf: 0.95
|
|
5857
|
+
},
|
|
5858
|
+
{
|
|
5859
|
+
pat: /\beacces|eisdir|eperm/i,
|
|
5860
|
+
cat: "infra",
|
|
5861
|
+
sub: "file-system",
|
|
5862
|
+
lang: "javascript",
|
|
5863
|
+
hints: NM,
|
|
5864
|
+
detail: "File system / OS error",
|
|
5865
|
+
conf: 0.95
|
|
5866
|
+
},
|
|
5867
|
+
// ── React / Next.js ──────────────────────────────────────────────────────
|
|
5868
|
+
{
|
|
5869
|
+
pat: /\breact-dom|react\.development|invalid hook call/i,
|
|
5870
|
+
cat: "runtime",
|
|
5871
|
+
sub: "react-error",
|
|
5872
|
+
lang: "javascript",
|
|
5873
|
+
hints: RM,
|
|
5874
|
+
detail: "React runtime error",
|
|
5875
|
+
conf: 0.9
|
|
5876
|
+
},
|
|
5877
|
+
{
|
|
5878
|
+
pat: /\bnext\.js|nextjs|error in.*next|getstaticpaths|getserversideprops/i,
|
|
5879
|
+
cat: "runtime",
|
|
5880
|
+
sub: "nextjs-error",
|
|
5881
|
+
lang: "javascript",
|
|
5882
|
+
hints: RM,
|
|
5883
|
+
detail: "Next.js error",
|
|
5884
|
+
conf: 0.95
|
|
5885
|
+
},
|
|
5886
|
+
// ── Security — BEFORE generic catch-alls ─────────────────────────────
|
|
5887
|
+
{
|
|
5888
|
+
pat: /\b(sql injection|xss|csrf|injection)\b/i,
|
|
5889
|
+
cat: "security",
|
|
5890
|
+
sub: "injection",
|
|
5891
|
+
lang: void 0,
|
|
5892
|
+
hints: SS,
|
|
5893
|
+
detail: "Injection vulnerability",
|
|
5894
|
+
conf: 1
|
|
5895
|
+
},
|
|
5896
|
+
{
|
|
5897
|
+
pat: /\b(secret|apikey|api_key|token|password|credential|jwt)\b/i,
|
|
5898
|
+
cat: "security",
|
|
5899
|
+
sub: "secret-exposure",
|
|
5900
|
+
lang: void 0,
|
|
5901
|
+
hints: SS,
|
|
5902
|
+
detail: "Secret / credential exposure",
|
|
5903
|
+
conf: 1
|
|
5904
|
+
},
|
|
5905
|
+
{
|
|
5906
|
+
pat: /\b(eval|innerhtml|document\.write| dangerouslysetinnerhtml)\b/i,
|
|
5907
|
+
cat: "security",
|
|
5908
|
+
sub: "injection",
|
|
5909
|
+
lang: void 0,
|
|
5910
|
+
hints: SS,
|
|
5911
|
+
detail: "Injection vulnerability",
|
|
5912
|
+
conf: 1
|
|
5913
|
+
},
|
|
5914
|
+
{
|
|
5915
|
+
pat: /\bcors.*misconfig|access-control-allow-origin/i,
|
|
5916
|
+
cat: "security",
|
|
5917
|
+
sub: "cors-misconfig",
|
|
5918
|
+
lang: void 0,
|
|
5919
|
+
hints: SS,
|
|
5920
|
+
detail: "CORS misconfiguration",
|
|
5921
|
+
conf: 0.9
|
|
5922
|
+
},
|
|
5923
|
+
{
|
|
5924
|
+
pat: /\bapikey\b|\bapi_key\b|\bhardcoded\b.*\bkey\b/i,
|
|
5925
|
+
cat: "security",
|
|
5926
|
+
sub: "secret-exposure",
|
|
5927
|
+
lang: void 0,
|
|
5928
|
+
hints: SS,
|
|
5929
|
+
detail: "Secret / credential exposure",
|
|
5930
|
+
conf: 1
|
|
5931
|
+
},
|
|
5932
|
+
// ── Null/undefined access — BEFORE generic typeerror catch-all ─────────────
|
|
5933
|
+
{
|
|
5934
|
+
pat: /\b(null is not|null.*not.*function|undefined is not|is not a function)\b/i,
|
|
5935
|
+
cat: "runtime",
|
|
5936
|
+
sub: "null-undefined-access",
|
|
5937
|
+
lang: "javascript",
|
|
5938
|
+
hints: BH,
|
|
5939
|
+
detail: "Null/undefined access error",
|
|
5940
|
+
conf: 0.9
|
|
5941
|
+
},
|
|
5942
|
+
// ── Generic JS errors ────────────────────────────────────────────────
|
|
5943
|
+
{
|
|
5944
|
+
pat: /\b(typeerror|referenceerror|syntaxerror|urierror|rangeerror|evalerror)\b/i,
|
|
5945
|
+
cat: "runtime",
|
|
5946
|
+
sub: "js-error",
|
|
5947
|
+
lang: "javascript",
|
|
5948
|
+
hints: NM,
|
|
5949
|
+
detail: "JavaScript runtime error",
|
|
5950
|
+
conf: 0.95
|
|
5951
|
+
},
|
|
5952
|
+
// ── Dependency / Import ───────────────────────────────────────────────
|
|
5953
|
+
{
|
|
5954
|
+
pat: /\bcannot find module|modulenotfounderror|no such module|missing module/i,
|
|
5955
|
+
cat: "dep",
|
|
5956
|
+
sub: "module-not-found",
|
|
5957
|
+
lang: void 0,
|
|
5958
|
+
hints: BH,
|
|
5959
|
+
detail: "Module / import resolution failure",
|
|
5960
|
+
conf: 0.9
|
|
5961
|
+
},
|
|
5962
|
+
{
|
|
5963
|
+
pat: /\bfailed to resolve|resolves to|dependency.*not found/i,
|
|
5964
|
+
cat: "dep",
|
|
5965
|
+
sub: "module-not-found",
|
|
5966
|
+
lang: void 0,
|
|
5967
|
+
hints: BH,
|
|
5968
|
+
detail: "Dependency resolution failure",
|
|
5969
|
+
conf: 0.85
|
|
5970
|
+
},
|
|
5971
|
+
// ── Lint ────────────────────────────────────────────────────────────────
|
|
5972
|
+
{
|
|
5973
|
+
pat: /\b(lint|warning|eslint|prettier|ruff|pylint|golangci-lint).*(error|fail|warn)/i,
|
|
5974
|
+
cat: "lint",
|
|
5975
|
+
sub: "linter-error",
|
|
5976
|
+
lang: void 0,
|
|
5977
|
+
hints: BH,
|
|
5978
|
+
detail: "Linter error / warning",
|
|
5979
|
+
conf: 0.85
|
|
5980
|
+
},
|
|
5981
|
+
// ── Performance ────────────────────────────────────────────────────────
|
|
5982
|
+
{
|
|
5983
|
+
pat: /\b(memory leak|oom|out of memory|heap overflow|stack overflow|infinite loop|bottleneck|performance issue)\b/i,
|
|
5984
|
+
cat: "perf",
|
|
5985
|
+
sub: "performance-issue",
|
|
5986
|
+
lang: void 0,
|
|
5987
|
+
hints: BH,
|
|
5988
|
+
detail: "Performance / memory issue",
|
|
5989
|
+
conf: 0.85
|
|
5990
|
+
},
|
|
5991
|
+
// ── Logic / wrong behavior ─────────────────────────────────────────────
|
|
5992
|
+
{
|
|
5993
|
+
pat: /\b(wrong|incorrect|unexpected|silent fail|bug|defect|logic error)\b/i,
|
|
5994
|
+
cat: "logic",
|
|
5995
|
+
sub: "wrong-behavior",
|
|
5996
|
+
lang: void 0,
|
|
5997
|
+
hints: BH,
|
|
5998
|
+
detail: "Logic / behavioral bug",
|
|
5999
|
+
conf: 0.8
|
|
6000
|
+
},
|
|
6001
|
+
{
|
|
6002
|
+
pat: /\b(off.?by.?one|boundary error|index error)\b/i,
|
|
6003
|
+
cat: "logic",
|
|
6004
|
+
sub: "wrong-behavior",
|
|
6005
|
+
lang: void 0,
|
|
6006
|
+
hints: BH,
|
|
6007
|
+
detail: "Off-by-one error",
|
|
6008
|
+
conf: 0.9
|
|
6009
|
+
},
|
|
6010
|
+
// ── C memory-safety (specific, after generic memory leak patterns above) ─
|
|
6011
|
+
{
|
|
6012
|
+
pat: /\b(heap-buffer-overflow|use-after-free|double-free)\b/i,
|
|
6013
|
+
cat: "perf",
|
|
6014
|
+
sub: "memory-safety",
|
|
6015
|
+
lang: "c",
|
|
6016
|
+
hints: BH,
|
|
6017
|
+
detail: "Memory safety issue (C/C++)",
|
|
6018
|
+
conf: 1
|
|
6019
|
+
},
|
|
6020
|
+
// ── Infra — AFTER more specific categories ─────────────────────────────
|
|
6021
|
+
{
|
|
6022
|
+
pat: /\b(env|environment|config|dotenv|yml|yaml|json.*config|docker|k8s|kubernetes)\b/i,
|
|
6023
|
+
cat: "infra",
|
|
6024
|
+
sub: "config-error",
|
|
6025
|
+
lang: void 0,
|
|
6026
|
+
hints: BH,
|
|
6027
|
+
detail: "Infrastructure / configuration error",
|
|
6028
|
+
conf: 0.8
|
|
6029
|
+
},
|
|
6030
|
+
{
|
|
6031
|
+
pat: /\b(git.*conflict|merge conflict|rebase.*fail|branch.*error|git.*error)\b/i,
|
|
6032
|
+
cat: "infra",
|
|
6033
|
+
sub: "git-error",
|
|
6034
|
+
lang: void 0,
|
|
6035
|
+
hints: BH,
|
|
6036
|
+
detail: "Git error",
|
|
6037
|
+
conf: 0.9
|
|
6038
|
+
},
|
|
6039
|
+
{
|
|
6040
|
+
pat: /\b(ci.?cd|pipeline|github action|circleci|jenkins|gitlab ci)\b/i,
|
|
6041
|
+
cat: "infra",
|
|
6042
|
+
sub: "config-error",
|
|
6043
|
+
lang: void 0,
|
|
6044
|
+
hints: BH,
|
|
6045
|
+
detail: "CI/CD pipeline error",
|
|
6046
|
+
conf: 0.9
|
|
6047
|
+
}
|
|
6048
|
+
];
|
|
6049
|
+
function classifyError(input) {
|
|
6050
|
+
const s = input.trim();
|
|
6051
|
+
for (const p of P) {
|
|
6052
|
+
const m = p.pat.exec(s);
|
|
6053
|
+
if (!m) continue;
|
|
6054
|
+
const detailStr = typeof p.detail === "function" ? p.detail(m) : p.detail;
|
|
6055
|
+
return {
|
|
6056
|
+
category: p.cat,
|
|
6057
|
+
subcategory: p.sub,
|
|
6058
|
+
language: p.lang ?? "unknown",
|
|
6059
|
+
framework: p.fw,
|
|
6060
|
+
skillHints: p.hints,
|
|
6061
|
+
errorCode: p.code ? p.code(m) : extractCode(s),
|
|
6062
|
+
confidence: p.conf ?? 0.8,
|
|
6063
|
+
detail: detailStr
|
|
6064
|
+
};
|
|
6065
|
+
}
|
|
6066
|
+
return {
|
|
6067
|
+
category: "general",
|
|
6068
|
+
subcategory: "unknown",
|
|
6069
|
+
language: "unknown",
|
|
6070
|
+
skillHints: BH,
|
|
6071
|
+
confidence: 0.3,
|
|
6072
|
+
detail: "General problem (unclassified)"
|
|
6073
|
+
};
|
|
6074
|
+
}
|
|
6075
|
+
function extractCode(s) {
|
|
6076
|
+
const ts = /\bTS\d+\b|\bCS\d+\b/.exec(s);
|
|
6077
|
+
if (ts) return ts[0];
|
|
6078
|
+
const rust = /\bE\d{4,}\b/.exec(s);
|
|
6079
|
+
if (rust) return rust[0];
|
|
6080
|
+
const c = /\bc\d+\b/i.exec(s);
|
|
6081
|
+
if (c) return c[0];
|
|
6082
|
+
return void 0;
|
|
6083
|
+
}
|
|
6084
|
+
function needsSubagent(c) {
|
|
6085
|
+
return c.confidence < 0.85;
|
|
6086
|
+
}
|
|
6087
|
+
function isSimpleFix(c) {
|
|
6088
|
+
return c.category === "ts" && c.confidence >= 0.9 || c.category === "runtime" && c.subcategory === "null-undefined-access" && c.confidence >= 0.85;
|
|
6089
|
+
}
|
|
6090
|
+
|
|
6091
|
+
// src/slash-commands/fix.ts
|
|
6092
|
+
function buildDirective(cli, errorText) {
|
|
6093
|
+
const lang = cli.language === "unknown" ? "" : ` (language: ${cli.language})`;
|
|
6094
|
+
switch (cli.category) {
|
|
6095
|
+
case "ts":
|
|
6096
|
+
return [
|
|
6097
|
+
`## Fix: TypeScript Error${lang}`,
|
|
6098
|
+
"",
|
|
6099
|
+
"```",
|
|
6100
|
+
`${errorText}`,
|
|
6101
|
+
"```",
|
|
6102
|
+
"",
|
|
6103
|
+
"Your task:",
|
|
6104
|
+
`1. Search for the error location in the codebase (grep for the error code "${cli.errorCode ?? ""}" or relevant type names)`,
|
|
6105
|
+
"2. Read the source file(s)",
|
|
6106
|
+
"3. Identify the root cause",
|
|
6107
|
+
"4. Fix with strict types \u2014 no `as any` or `@ts-ignore`",
|
|
6108
|
+
"5. Verify with `typecheck` or `tsc --noEmit`"
|
|
6109
|
+
].join("\n");
|
|
6110
|
+
case "security":
|
|
6111
|
+
return [
|
|
6112
|
+
`## Fix: Security Issue${lang}`,
|
|
6113
|
+
"",
|
|
6114
|
+
"```",
|
|
6115
|
+
`${errorText}`,
|
|
6116
|
+
"```",
|
|
6117
|
+
"",
|
|
6118
|
+
"Your task:",
|
|
6119
|
+
"1. Locate the vulnerable code (grep for hardcoded secrets, eval, innerHTML, SQL concatenation)",
|
|
6120
|
+
"2. Classify severity: critical / high / medium",
|
|
6121
|
+
"3. Apply the fix:",
|
|
6122
|
+
" - Secrets \u2192 rotate + use env vars or a secret manager",
|
|
6123
|
+
" - Injection \u2192 parameterized queries, safe DOM APIs",
|
|
6124
|
+
" - Auth \u2192 fix token handling",
|
|
6125
|
+
'4. Check for similar issues: `grep -r "password" --include="*.ts"` etc.',
|
|
6126
|
+
"5. Run security scan if available"
|
|
6127
|
+
].join("\n");
|
|
6128
|
+
case "runtime":
|
|
6129
|
+
return [
|
|
6130
|
+
`## Fix: Runtime Error${lang}`,
|
|
6131
|
+
"",
|
|
6132
|
+
"```",
|
|
6133
|
+
`${errorText}`,
|
|
6134
|
+
"```",
|
|
6135
|
+
"",
|
|
6136
|
+
"Your task:",
|
|
6137
|
+
"1. Locate the crash site (grep for error message or stack trace context)",
|
|
6138
|
+
"2. Read the relevant file(s)",
|
|
6139
|
+
"3. Identify root cause: null/undefined access, async race, wrong type, etc.",
|
|
6140
|
+
"4. Fix the error",
|
|
6141
|
+
"5. Verify \u2014 re-run or typecheck"
|
|
6142
|
+
].join("\n");
|
|
6143
|
+
case "compile":
|
|
6144
|
+
return [
|
|
6145
|
+
`## Fix: Compiler Error${lang}`,
|
|
6146
|
+
"",
|
|
6147
|
+
"```",
|
|
6148
|
+
`${errorText}`,
|
|
6149
|
+
"```",
|
|
6150
|
+
"",
|
|
6151
|
+
"Your task:",
|
|
6152
|
+
"1. Locate the file(s) with compile errors",
|
|
6153
|
+
"2. Read the error output carefully",
|
|
6154
|
+
"3. Fix the compile error",
|
|
6155
|
+
"4. Re-compile to verify"
|
|
6156
|
+
].join("\n");
|
|
6157
|
+
case "dep":
|
|
6158
|
+
return [
|
|
6159
|
+
`## Fix: Dependency / Import Error${lang}`,
|
|
6160
|
+
"",
|
|
6161
|
+
"```",
|
|
6162
|
+
`${errorText}`,
|
|
6163
|
+
"```",
|
|
6164
|
+
"",
|
|
6165
|
+
"Your task:",
|
|
6166
|
+
"1. Identify the missing module or failed import",
|
|
6167
|
+
"2. Fix: install the package, add to imports, or correct the path",
|
|
6168
|
+
"3. Verify imports resolve"
|
|
6169
|
+
].join("\n");
|
|
6170
|
+
case "infra":
|
|
6171
|
+
return [
|
|
6172
|
+
`## Fix: Infrastructure / Config Error${lang}`,
|
|
6173
|
+
"",
|
|
6174
|
+
"```",
|
|
6175
|
+
`${errorText}`,
|
|
6176
|
+
"```",
|
|
6177
|
+
"",
|
|
6178
|
+
"Your task:",
|
|
6179
|
+
"1. Locate the config file or infrastructure setup",
|
|
6180
|
+
"2. Identify the misconfiguration",
|
|
6181
|
+
"3. Fix the config",
|
|
6182
|
+
"4. Verify"
|
|
6183
|
+
].join("\n");
|
|
6184
|
+
case "perf":
|
|
6185
|
+
return [
|
|
6186
|
+
`## Fix: Performance / Memory Issue${lang}`,
|
|
6187
|
+
"",
|
|
6188
|
+
"```",
|
|
6189
|
+
`${errorText}`,
|
|
6190
|
+
"```",
|
|
6191
|
+
"",
|
|
6192
|
+
"Your task:",
|
|
6193
|
+
"1. Profile or locate the bottleneck",
|
|
6194
|
+
"2. Identify root cause",
|
|
6195
|
+
"3. Fix: memoize, batch, lazy-load, remove leak, fix loop",
|
|
6196
|
+
"4. Verify performance improvement"
|
|
6197
|
+
].join("\n");
|
|
6198
|
+
default:
|
|
6199
|
+
return [
|
|
6200
|
+
`## Fix: Problem Reported${lang}`,
|
|
6201
|
+
"",
|
|
6202
|
+
"```",
|
|
6203
|
+
`${errorText}`,
|
|
6204
|
+
"```",
|
|
6205
|
+
"",
|
|
6206
|
+
"Your task:",
|
|
6207
|
+
"1. Analyze the problem description",
|
|
6208
|
+
"2. Locate relevant files",
|
|
6209
|
+
"3. Identify the root cause",
|
|
6210
|
+
"4. Apply the fix",
|
|
6211
|
+
"5. Verify the fix works"
|
|
6212
|
+
].join("\n");
|
|
6213
|
+
}
|
|
6214
|
+
}
|
|
6215
|
+
function delegateRoleFor(cli) {
|
|
6216
|
+
switch (cli.category) {
|
|
6217
|
+
case "ts":
|
|
6218
|
+
return "typescript-strict";
|
|
6219
|
+
case "security":
|
|
6220
|
+
return "security-scanner";
|
|
6221
|
+
case "perf":
|
|
6222
|
+
return "refactor-planner";
|
|
6223
|
+
default:
|
|
6224
|
+
return "bug-hunter";
|
|
6225
|
+
}
|
|
6226
|
+
}
|
|
6227
|
+
function skillLabel(skillHints) {
|
|
6228
|
+
return skillHints.map((s) => `\`${s}\``).join(", ");
|
|
6229
|
+
}
|
|
6230
|
+
function categoryLabel(cli) {
|
|
6231
|
+
if (cli.language !== "unknown" && cli.language !== "unknown") {
|
|
6232
|
+
return `${cli.subcategory} (${cli.category}, ${cli.language})`;
|
|
6233
|
+
}
|
|
6234
|
+
return `${cli.subcategory} (${cli.category})`;
|
|
6235
|
+
}
|
|
6236
|
+
function buildFixCommand(opts) {
|
|
6237
|
+
return {
|
|
6238
|
+
name: "fix",
|
|
6239
|
+
description: "Classify a bug/error (any language), activate the right skill, and fix it \u2014 inline or via subagent.",
|
|
6240
|
+
argsHint: "<error message or problem description>",
|
|
6241
|
+
help: `
|
|
6242
|
+
# /fix \u2014 Problem Solver
|
|
4669
6243
|
|
|
4670
|
-
|
|
6244
|
+
Classifies an error, bug, or problem description from **any language/framework**,
|
|
6245
|
+
activates the right skill, and drives a focused fix workflow.
|
|
4671
6246
|
|
|
4672
|
-
|
|
4673
|
-
}
|
|
4674
|
-
const index = parseInt(reportId, 10) - 1;
|
|
4675
|
-
if (!isNaN(index) && reports[index]) {
|
|
4676
|
-
const content = await readFile(join(reportsDir, reports[index]), "utf-8");
|
|
4677
|
-
return { message: `# Security Report
|
|
6247
|
+
## Usage
|
|
4678
6248
|
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
if (match) {
|
|
4683
|
-
const content = await readFile(join(reportsDir, match), "utf-8");
|
|
4684
|
-
return { message: `# Security Report
|
|
6249
|
+
\`\`\`
|
|
6250
|
+
/fix <error message or problem description>
|
|
6251
|
+
\`\`\`
|
|
4685
6252
|
|
|
4686
|
-
|
|
4687
|
-
}
|
|
4688
|
-
return { message: `\u274C Report "${reportId}" not found. Use \`/security report\` to see available reports.` };
|
|
4689
|
-
} catch {
|
|
4690
|
-
return { message: "\u{1F4ED} No security reports found. Run `/security scan` first." };
|
|
4691
|
-
}
|
|
4692
|
-
}
|
|
4693
|
-
function parseArgs2(args) {
|
|
4694
|
-
const result = {};
|
|
4695
|
-
const parts = args.split(/\s+/);
|
|
4696
|
-
for (let i = 0; i < parts.length; i++) {
|
|
4697
|
-
const part = parts[i];
|
|
4698
|
-
if (!part || !part.startsWith("--")) continue;
|
|
4699
|
-
const key = part.slice(2);
|
|
4700
|
-
const next = parts[i + 1];
|
|
4701
|
-
if (next && !next.startsWith("--")) {
|
|
4702
|
-
result[key] = next;
|
|
4703
|
-
i++;
|
|
4704
|
-
} else {
|
|
4705
|
-
result[key] = "true";
|
|
4706
|
-
}
|
|
4707
|
-
}
|
|
4708
|
-
return result;
|
|
4709
|
-
}
|
|
4710
|
-
function getHelpMessage() {
|
|
4711
|
-
return `# /security \u2014 Security Scanner
|
|
6253
|
+
## Supported languages & frameworks
|
|
4712
6254
|
|
|
4713
|
-
|
|
6255
|
+
TypeScript, Rust, Go, Python, Ruby, Java, Kotlin, Swift, C/C++, C#,
|
|
6256
|
+
PHP, Scala, Perl, Haskell, Elixir, Node.js, React, Next.js, Vue, Angular,
|
|
6257
|
+
Docker, Git, CI/CD, and more.
|
|
4714
6258
|
|
|
4715
|
-
|
|
4716
|
-
\`/security scan --depth deep --format html\`
|
|
6259
|
+
## Classification \u2192 skill mapping
|
|
4717
6260
|
|
|
4718
|
-
|
|
6261
|
+
| Error type | Language(s) | Skill activated |
|
|
6262
|
+
|---------------------|--------------------------|-----------------------|
|
|
6263
|
+
| TypeScript | TypeScript | \`typescript-strict\` |
|
|
6264
|
+
| Runtime / crash | Any | \`bug-hunter\` |
|
|
6265
|
+
| Security / secrets | Any | \`security-scanner\` |
|
|
6266
|
+
| Compiler error | Rust, Go, C/C++, Python | \`bug-hunter\` |
|
|
6267
|
+
| Dependency / import | Any | \`bug-hunter\` |
|
|
6268
|
+
| Performance / leak | Any | \`bug-hunter\` + \`refactor-planner\` |
|
|
6269
|
+
| Infrastructure | Config, Docker, Git, CI | \`bug-hunter\` |
|
|
6270
|
+
| React / Next.js | JavaScript | \`react-modern\` |
|
|
6271
|
+
| Node.js | JavaScript | \`node-modern\` |
|
|
4719
6272
|
|
|
4720
|
-
|
|
6273
|
+
## Auto-delegation
|
|
4721
6274
|
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
- Secrets, injection, and config vulnerability scanning
|
|
4726
|
-
- Markdown/JSON/HTML reports
|
|
4727
|
-
- .gitignore auto-update
|
|
6275
|
+
When the error confidence is low (< 0.85) or the problem spans multiple files,
|
|
6276
|
+
\`/fix\` automatically delegates to the matching specialist subagent via the
|
|
6277
|
+
\`onFix\` callback. Set \`onFix\` in \`SlashCommandContext\` to enable this.
|
|
4728
6278
|
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
}
|
|
4744
|
-
async function loadStatuslineConfig() {
|
|
4745
|
-
const p = resolveConfigPath();
|
|
4746
|
-
try {
|
|
4747
|
-
const raw = await fsp2.readFile(p, "utf8");
|
|
4748
|
-
return { ...DEFAULTS, ...JSON.parse(raw) };
|
|
4749
|
-
} catch {
|
|
4750
|
-
return { ...DEFAULTS };
|
|
4751
|
-
}
|
|
4752
|
-
}
|
|
4753
|
-
async function saveStatuslineConfig(cfg) {
|
|
4754
|
-
const p = resolveConfigPath();
|
|
4755
|
-
await fsp2.mkdir(path23.dirname(p), { recursive: true });
|
|
4756
|
-
await atomicWrite(p, JSON.stringify(cfg, null, 2));
|
|
4757
|
-
}
|
|
4758
|
-
function buildStatuslineCommand(deps) {
|
|
4759
|
-
return {
|
|
4760
|
-
name: "statusline",
|
|
4761
|
-
aliases: ["sl"],
|
|
4762
|
-
description: "Customize status bar chips: /statusline [item] [on|off] or /statusline reset",
|
|
4763
|
-
help: [
|
|
4764
|
-
"Usage: /statusline [item] [on|off]",
|
|
4765
|
-
" /statusline \u2014 show current config",
|
|
4766
|
-
" /statusline <item> on \u2014 enable a chip",
|
|
4767
|
-
" /statusline <item> off \u2014 disable a chip",
|
|
4768
|
-
" /statusline reset \u2014 restore defaults",
|
|
4769
|
-
"",
|
|
4770
|
-
"Available items: todos, plan, fleet, git, elapsed, context, cost",
|
|
4771
|
-
"Persistent across sessions (saved to ~/.wrongstack/statusline.json)."
|
|
4772
|
-
].join("\n"),
|
|
4773
|
-
async run(args) {
|
|
4774
|
-
const cfg = await deps.getConfig();
|
|
6279
|
+
## Examples
|
|
6280
|
+
|
|
6281
|
+
\`\`\`
|
|
6282
|
+
/fix TS2345: Argument of type 'string | null' is not assignable
|
|
6283
|
+
/fix TypeError: Cannot read property 'map' of undefined
|
|
6284
|
+
/fix error[E0503]: expected something but found E0503 in src/lib.rs
|
|
6285
|
+
/fix Segmentation fault (core dumped) at main.rs:42
|
|
6286
|
+
/fix AttributeError: 'NoneType' object has no attribute 'encode' (Python)
|
|
6287
|
+
/fix react-dom.development.js:172 Error: Invalid hook call
|
|
6288
|
+
/fix Security: hardcoded API key in config.ts
|
|
6289
|
+
/fix ERRO1014: SQL injection vulnerability in query builder
|
|
6290
|
+
\`\`\`
|
|
6291
|
+
`,
|
|
6292
|
+
async run(args, _ctx) {
|
|
4775
6293
|
const trimmed = args.trim();
|
|
4776
|
-
|
|
4777
|
-
const [item, action] = parts;
|
|
4778
|
-
if (!item) {
|
|
4779
|
-
const lines = ["StatusBar chips:"];
|
|
4780
|
-
const items = [
|
|
4781
|
-
"todos",
|
|
4782
|
-
"plan",
|
|
4783
|
-
"fleet",
|
|
4784
|
-
"git",
|
|
4785
|
-
"elapsed",
|
|
4786
|
-
"context",
|
|
4787
|
-
"cost"
|
|
4788
|
-
];
|
|
4789
|
-
for (const k of items) {
|
|
4790
|
-
const val = cfg[k];
|
|
4791
|
-
if (val === void 0) continue;
|
|
4792
|
-
lines.push(` ${val ? "\u25CF" : "\u25CB"} ${k}`);
|
|
4793
|
-
}
|
|
4794
|
-
return { message: lines.join("\n") };
|
|
4795
|
-
}
|
|
4796
|
-
if (item === "reset") {
|
|
4797
|
-
await deps.setConfig({ ...DEFAULTS });
|
|
4798
|
-
deps.setHiddenItems([]);
|
|
4799
|
-
return { message: "StatusBar config reset to defaults." };
|
|
4800
|
-
}
|
|
4801
|
-
const validItems = [
|
|
4802
|
-
"todos",
|
|
4803
|
-
"plan",
|
|
4804
|
-
"fleet",
|
|
4805
|
-
"git",
|
|
4806
|
-
"elapsed",
|
|
4807
|
-
"context",
|
|
4808
|
-
"cost"
|
|
4809
|
-
];
|
|
4810
|
-
if (!validItems.includes(item)) {
|
|
6294
|
+
if (!trimmed) {
|
|
4811
6295
|
return {
|
|
4812
|
-
message:
|
|
6296
|
+
message: [
|
|
6297
|
+
`${color.bold("/fix \u2014 Problem Solver")}`,
|
|
6298
|
+
"",
|
|
6299
|
+
"Classifies an error from any language/framework and activates the right skill.",
|
|
6300
|
+
"",
|
|
6301
|
+
"Usage:",
|
|
6302
|
+
" /fix <error message or problem description>",
|
|
6303
|
+
"",
|
|
6304
|
+
"Examples:",
|
|
6305
|
+
` /fix ${color.dim('TS2345: Argument of type "string | null" is not assignable')}`,
|
|
6306
|
+
` /fix ${color.dim("TypeError: Cannot read property 'map' of undefined")}`,
|
|
6307
|
+
` /fix ${color.dim("error[E0503]: expected something but found E0503 in src/lib.rs")}`,
|
|
6308
|
+
` /fix ${color.dim("AttributeError: 'NoneType' object has no attribute 'encode'")}`,
|
|
6309
|
+
` /fix ${color.dim("react-dom.development.js:172 Error: Invalid hook call")}`,
|
|
6310
|
+
` /fix ${color.dim("Security: hardcoded API key in config.ts")}`,
|
|
6311
|
+
"",
|
|
6312
|
+
"Run `/help fix` for full documentation."
|
|
6313
|
+
].join("\n")
|
|
4813
6314
|
};
|
|
4814
6315
|
}
|
|
4815
|
-
const
|
|
4816
|
-
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
|
|
4820
|
-
|
|
4821
|
-
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
|
|
6316
|
+
const cli = classifyError(trimmed);
|
|
6317
|
+
const delegate = needsSubagent(cli);
|
|
6318
|
+
const delegateRole = delegate ? delegateRoleFor(cli) : void 0;
|
|
6319
|
+
const metadata = {
|
|
6320
|
+
skillHints: cli.skillHints,
|
|
6321
|
+
delegateRequested: delegate,
|
|
6322
|
+
delegateRole
|
|
6323
|
+
};
|
|
6324
|
+
if (isSimpleFix(cli) || !delegate && opts.onFix) {
|
|
6325
|
+
const runText2 = [
|
|
6326
|
+
"",
|
|
6327
|
+
`${color.bold("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550 /fix \u2014 Problem Solver \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557")}`,
|
|
6328
|
+
"",
|
|
6329
|
+
`**Classification:** ${categoryLabel(cli)}`,
|
|
6330
|
+
`**Confidence:** ${Math.round(cli.confidence * 100)}%`,
|
|
6331
|
+
`**Error code:** \`${cli.errorCode ?? "n/a"}\``,
|
|
6332
|
+
"",
|
|
6333
|
+
`**Skills activated:** ${skillLabel(cli.skillHints)}`,
|
|
6334
|
+
"",
|
|
6335
|
+
`**Next step:** ${delegate ? "Delegating to `" + delegateRole + "` subagent..." : "Applying inline fix..."}`,
|
|
6336
|
+
""
|
|
6337
|
+
].join("\n") + "\n---\n" + buildDirective(cli, trimmed);
|
|
6338
|
+
if (opts.onFix) {
|
|
6339
|
+
const injected = await opts.onFix(trimmed);
|
|
6340
|
+
if (injected?.message) {
|
|
6341
|
+
return {
|
|
6342
|
+
message: injected.message,
|
|
6343
|
+
runText: injected.runText ?? runText2,
|
|
6344
|
+
metadata
|
|
6345
|
+
};
|
|
6346
|
+
}
|
|
6347
|
+
}
|
|
6348
|
+
return { message: `${color.green("\u2713")} [${categoryLabel(cli)}] \u2014 Skills: ${skillLabel(cli.skillHints)}`, runText: runText2, metadata };
|
|
6349
|
+
}
|
|
6350
|
+
if (delegate && delegateRole) {
|
|
6351
|
+
const taskPrompt = [
|
|
6352
|
+
`${color.bold("/fix \u2014 Auto-delegated fix")}`,
|
|
6353
|
+
"",
|
|
6354
|
+
`**Problem classification:** ${categoryLabel(cli)} (confidence: ${Math.round(cli.confidence * 100)}%)`,
|
|
6355
|
+
`**Error code:** \`${cli.errorCode ?? "n/a"}\``,
|
|
6356
|
+
`**Skills activated:** ${skillLabel(cli.skillHints)}`,
|
|
6357
|
+
"",
|
|
6358
|
+
buildDirective(cli, trimmed)
|
|
6359
|
+
].join("\n");
|
|
6360
|
+
metadata.delegateTask = taskPrompt;
|
|
6361
|
+
const runText2 = [
|
|
6362
|
+
"",
|
|
6363
|
+
`${color.bold("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550 /fix \u2014 Auto-delegated \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557")}`,
|
|
6364
|
+
"",
|
|
6365
|
+
`**Delegating to:** \`${delegateRole}\` subagent`,
|
|
6366
|
+
`**Classification:** ${categoryLabel(cli)}`,
|
|
6367
|
+
`**Confidence:** ${Math.round(cli.confidence * 100)}% \u2014 delegation triggered (confidence < 85%)`,
|
|
6368
|
+
`**Skills activated:** ${skillLabel(cli.skillHints)}`,
|
|
6369
|
+
"",
|
|
6370
|
+
"The specialist subagent will now fix the issue. Results will be reported when complete."
|
|
6371
|
+
].join("\n");
|
|
6372
|
+
return { message: `${color.green("\u2713")} Delegating to \`${delegateRole}\`...`, runText: runText2, metadata };
|
|
4825
6373
|
}
|
|
4826
|
-
|
|
6374
|
+
const runText = [
|
|
6375
|
+
"",
|
|
6376
|
+
`${color.bold("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550 /fix \u2014 Problem Solver \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557")}`,
|
|
6377
|
+
"",
|
|
6378
|
+
`**Classification:** ${categoryLabel(cli)} (confidence: ${Math.round(cli.confidence * 100)}%)`,
|
|
6379
|
+
`**Skills activated:** ${skillLabel(cli.skillHints)}`,
|
|
6380
|
+
""
|
|
6381
|
+
].join("\n") + "\n---\n" + buildDirective(cli, trimmed);
|
|
6382
|
+
return {
|
|
6383
|
+
message: `${color.green("\u2713")} [${categoryLabel(cli)}] \u2014 Skills: ${skillLabel(cli.skillHints)}`,
|
|
6384
|
+
runText,
|
|
6385
|
+
metadata
|
|
6386
|
+
};
|
|
4827
6387
|
}
|
|
4828
6388
|
};
|
|
4829
6389
|
}
|
|
@@ -5028,6 +6588,7 @@ function buildBuiltinSlashCommands(opts) {
|
|
|
5028
6588
|
buildGitcheckCommand(),
|
|
5029
6589
|
buildPushCommand(),
|
|
5030
6590
|
buildSecurityCommand(opts),
|
|
6591
|
+
buildFixCommand(opts),
|
|
5031
6592
|
buildStatuslineCommand({
|
|
5032
6593
|
cwd: opts.cwd,
|
|
5033
6594
|
hiddenItems: opts.statuslineHiddenItems ?? [],
|
|
@@ -7482,22 +9043,22 @@ function fmtDuration(ms) {
|
|
|
7482
9043
|
const remMin = m - h * 60;
|
|
7483
9044
|
return `${h}h${remMin}m`;
|
|
7484
9045
|
}
|
|
7485
|
-
function fmtTaskResultLine(r,
|
|
9046
|
+
function fmtTaskResultLine(r, color38) {
|
|
7486
9047
|
const stats = `${r.iterations}it ${r.toolCalls}tc ${fmtDuration(r.durationMs)}`;
|
|
7487
9048
|
const errMsg = typeof r.error === "string" ? r.error : r.error?.message;
|
|
7488
9049
|
const errKind = typeof r.error === "object" ? r.error?.kind : void 0;
|
|
7489
9050
|
const errTail = errMsg ? ` \u2014 ${errMsg.replace(/\s+/g, " ").slice(0, 80)}${errMsg.length > 80 ? "\u2026" : ""}` : "";
|
|
7490
|
-
const errKindChip = errKind ?
|
|
7491
|
-
const errSnip = errMsg || errKind ? `${errKindChip}${
|
|
9051
|
+
const errKindChip = errKind ? color38.dim(` [${errKind}]`) : "";
|
|
9052
|
+
const errSnip = errMsg || errKind ? `${errKindChip}${color38.dim(errTail)}` : "";
|
|
7492
9053
|
switch (r.status) {
|
|
7493
9054
|
case "success":
|
|
7494
|
-
return { mark:
|
|
9055
|
+
return { mark: color38.green("\u2713"), stats, tail: "" };
|
|
7495
9056
|
case "timeout":
|
|
7496
|
-
return { mark:
|
|
9057
|
+
return { mark: color38.yellow("\u23F1"), stats: `${color38.yellow("timeout")} ${stats}`, tail: errSnip };
|
|
7497
9058
|
case "stopped":
|
|
7498
|
-
return { mark:
|
|
9059
|
+
return { mark: color38.dim("\u2298"), stats: `${color38.dim("stopped")} ${stats}`, tail: errSnip };
|
|
7499
9060
|
case "failed":
|
|
7500
|
-
return { mark:
|
|
9061
|
+
return { mark: color38.red("\u2717"), stats: `${color38.red("failed")} ${stats}`, tail: errSnip };
|
|
7501
9062
|
}
|
|
7502
9063
|
}
|
|
7503
9064
|
|
|
@@ -7670,10 +9231,12 @@ async function runRepl(opts) {
|
|
|
7670
9231
|
opts.renderer.writeWarning("Exiting.");
|
|
7671
9232
|
process.exit(130);
|
|
7672
9233
|
}
|
|
7673
|
-
|
|
7674
|
-
|
|
7675
|
-
|
|
7676
|
-
opts.
|
|
9234
|
+
if (opts.getAutonomy?.() === "eternal" || opts.getAutonomy?.() === "eternal-parallel") {
|
|
9235
|
+
opts.getEternalEngine?.()?.stop();
|
|
9236
|
+
opts.getParallelEngine?.()?.stop();
|
|
9237
|
+
opts.onAutonomy?.("off");
|
|
9238
|
+
opts.renderer.writeWarning("Engine stop requested. Press Ctrl+C again to exit.");
|
|
9239
|
+
interrupts = 0;
|
|
7677
9240
|
return;
|
|
7678
9241
|
}
|
|
7679
9242
|
if (activeCtrl) {
|
|
@@ -7722,6 +9285,39 @@ async function runRepl(opts) {
|
|
|
7722
9285
|
await new Promise((resolve4) => setTimeout(resolve4, 250));
|
|
7723
9286
|
continue;
|
|
7724
9287
|
}
|
|
9288
|
+
} else if (opts.getAutonomy?.() === "eternal-parallel") {
|
|
9289
|
+
const engine = opts.getParallelEngine?.();
|
|
9290
|
+
if (!engine) {
|
|
9291
|
+
opts.renderer.writeWarning("Parallel mode set but no engine wired \u2014 falling back to off.");
|
|
9292
|
+
} else {
|
|
9293
|
+
const beforeGoal = await loadGoalSafe(opts);
|
|
9294
|
+
const beforeIter = beforeGoal?.iterations ?? 0;
|
|
9295
|
+
opts.renderer.write(
|
|
9296
|
+
color.magenta(`
|
|
9297
|
+
\u21B3 [parallel #${beforeIter + 1}] launching fan-out\u2026
|
|
9298
|
+
`)
|
|
9299
|
+
);
|
|
9300
|
+
interrupts = 0;
|
|
9301
|
+
try {
|
|
9302
|
+
const ok = await engine.runOneIteration();
|
|
9303
|
+
const afterGoal = await loadGoalSafe(opts);
|
|
9304
|
+
const last = afterGoal?.journal[afterGoal.journal.length - 1];
|
|
9305
|
+
if (last) {
|
|
9306
|
+
const mark = last.status === "success" ? color.green("\u2713") : last.status === "failure" ? color.red("\u2717") : color.amber("\u2298");
|
|
9307
|
+
const tail = last.note ? color.dim(` \u2014 ${last.note.slice(0, 80)}`) : "";
|
|
9308
|
+
opts.renderer.write(
|
|
9309
|
+
` ${mark} ${color.dim(`#${last.iteration}`)} ${color.dim(`[${last.source}]`)} ${last.task}${tail}
|
|
9310
|
+
`
|
|
9311
|
+
);
|
|
9312
|
+
}
|
|
9313
|
+
} catch (err) {
|
|
9314
|
+
opts.renderer.writeError(
|
|
9315
|
+
`[parallel] ${err instanceof Error ? err.message : String(err)}`
|
|
9316
|
+
);
|
|
9317
|
+
}
|
|
9318
|
+
await new Promise((resolve4) => setTimeout(resolve4, 250));
|
|
9319
|
+
continue;
|
|
9320
|
+
}
|
|
7725
9321
|
}
|
|
7726
9322
|
let raw;
|
|
7727
9323
|
try {
|
|
@@ -7790,9 +9386,22 @@ ${color.cyan(` \u2713 ${count} tasks detected and saved! Use /sdd approve to ex
|
|
|
7790
9386
|
if (progress) {
|
|
7791
9387
|
opts.renderer.write(
|
|
7792
9388
|
`
|
|
7793
|
-
${color.cyan(` \u2713 ${autoCompleted} task(s) auto-completed! Progress: ${progress.completed}/${progress.total} (${progress.
|
|
9389
|
+
${color.cyan(` \u2713 ${autoCompleted} task(s) auto-completed! Progress: ${progress.completed}/${progress.total} (${progress.percentComplete}%)`)}
|
|
7794
9390
|
`
|
|
7795
9391
|
);
|
|
9392
|
+
const taskList2 = renderTaskListWithProgress();
|
|
9393
|
+
if (taskList2) {
|
|
9394
|
+
opts.renderer.write(`
|
|
9395
|
+
${color.dim(taskList2)}
|
|
9396
|
+
`);
|
|
9397
|
+
}
|
|
9398
|
+
}
|
|
9399
|
+
} else {
|
|
9400
|
+
const taskList2 = renderTaskListWithProgress();
|
|
9401
|
+
if (taskList2) {
|
|
9402
|
+
opts.renderer.write(`
|
|
9403
|
+
${color.dim(taskList2)}
|
|
9404
|
+
`);
|
|
7796
9405
|
}
|
|
7797
9406
|
}
|
|
7798
9407
|
}
|
|
@@ -7821,6 +9430,14 @@ ${color.cyan(` \u2713 ${autoCompleted} task(s) auto-completed! Progress: ${prog
|
|
|
7821
9430
|
if (sddContext) {
|
|
7822
9431
|
sddPrefix = `[SDD SESSION ACTIVE]
|
|
7823
9432
|
${sddContext}`;
|
|
9433
|
+
if (sddPhase === "executing") {
|
|
9434
|
+
const currentCtx = getCurrentExecutingContext();
|
|
9435
|
+
if (currentCtx) {
|
|
9436
|
+
sddPrefix += `
|
|
9437
|
+
|
|
9438
|
+
${currentCtx}`;
|
|
9439
|
+
}
|
|
9440
|
+
}
|
|
7824
9441
|
if (taskList) {
|
|
7825
9442
|
sddPrefix += `
|
|
7826
9443
|
|
|
@@ -7829,9 +9446,9 @@ ${taskList}`;
|
|
|
7829
9446
|
}
|
|
7830
9447
|
if (taskProgress && taskProgress.total > 0) {
|
|
7831
9448
|
sddPrefix += `
|
|
7832
|
-
**Progress:** ${taskProgress.completed}/${taskProgress.total} (${taskProgress.
|
|
9449
|
+
**Progress:** ${taskProgress.completed}/${taskProgress.total} (${taskProgress.percentComplete}%)`;
|
|
7833
9450
|
}
|
|
7834
|
-
if (sddPhase === "executing" && taskProgress && taskProgress.
|
|
9451
|
+
if (sddPhase === "executing" && taskProgress && taskProgress.percentComplete === 100) {
|
|
7835
9452
|
sddPrefix += "\n\n**All tasks completed! Provide a summary of everything implemented.**";
|
|
7836
9453
|
}
|
|
7837
9454
|
sddPrefix += "\n\n---\nUser message:\n";
|
|
@@ -7911,10 +9528,10 @@ ${color.cyan(` \u2713 ${count} tasks detected and saved! Use /sdd approve to ex
|
|
|
7911
9528
|
if (progress) {
|
|
7912
9529
|
opts.renderer.write(
|
|
7913
9530
|
`
|
|
7914
|
-
${color.cyan(` \u2713 ${autoCompleted} task(s) auto-completed! Progress: ${progress.completed}/${progress.total} (${progress.
|
|
9531
|
+
${color.cyan(` \u2713 ${autoCompleted} task(s) auto-completed! Progress: ${progress.completed}/${progress.total} (${progress.percentComplete}%)`)}
|
|
7915
9532
|
`
|
|
7916
9533
|
);
|
|
7917
|
-
if (progress.
|
|
9534
|
+
if (progress.percentComplete === 100) {
|
|
7918
9535
|
opts.renderer.write(
|
|
7919
9536
|
`
|
|
7920
9537
|
${color.green(" \u{1F389} All tasks completed! Use /sdd cancel to end the session.")}
|
|
@@ -7922,6 +9539,20 @@ ${color.green(" \u{1F389} All tasks completed! Use /sdd cancel to end the sessi
|
|
|
7922
9539
|
);
|
|
7923
9540
|
}
|
|
7924
9541
|
}
|
|
9542
|
+
advanceToNextTask();
|
|
9543
|
+
const taskList2 = renderTaskListWithProgress();
|
|
9544
|
+
if (taskList2) {
|
|
9545
|
+
opts.renderer.write(`
|
|
9546
|
+
${color.dim(taskList2)}
|
|
9547
|
+
`);
|
|
9548
|
+
}
|
|
9549
|
+
} else {
|
|
9550
|
+
const taskList2 = renderTaskListWithProgress();
|
|
9551
|
+
if (taskList2) {
|
|
9552
|
+
opts.renderer.write(`
|
|
9553
|
+
${color.dim(taskList2)}
|
|
9554
|
+
`);
|
|
9555
|
+
}
|
|
7925
9556
|
}
|
|
7926
9557
|
}
|
|
7927
9558
|
}
|
|
@@ -8082,10 +9713,10 @@ var EMPTY = "\u2591";
|
|
|
8082
9713
|
function renderContextChip(used, max) {
|
|
8083
9714
|
const ratio = Math.max(0, Math.min(1, used / max));
|
|
8084
9715
|
const pct2 = Math.round(ratio * 100);
|
|
8085
|
-
const bar =
|
|
9716
|
+
const bar = renderProgress2(ratio, 6);
|
|
8086
9717
|
return `${bar} ${pct2}% (${fmtTok(used)}/${fmtTok(max)})`;
|
|
8087
9718
|
}
|
|
8088
|
-
function
|
|
9719
|
+
function renderProgress2(ratio, width) {
|
|
8089
9720
|
const clamped = Math.max(0, Math.min(1, ratio));
|
|
8090
9721
|
const filled = clamped === 0 ? 0 : Math.max(1, Math.round(clamped * width));
|
|
8091
9722
|
const capped = Math.min(width, filled);
|
|
@@ -8139,8 +9770,11 @@ async function execute(deps) {
|
|
|
8139
9770
|
setStatuslineHiddenItems,
|
|
8140
9771
|
getYolo,
|
|
8141
9772
|
getAutonomy,
|
|
9773
|
+
onAutonomy,
|
|
8142
9774
|
getEternalEngine,
|
|
9775
|
+
getParallelEngine,
|
|
8143
9776
|
subscribeEternalIteration,
|
|
9777
|
+
subscribeEternalStage,
|
|
8144
9778
|
skillLoader
|
|
8145
9779
|
} = deps;
|
|
8146
9780
|
let code = 0;
|
|
@@ -8251,12 +9885,17 @@ async function execute(deps) {
|
|
|
8251
9885
|
getAutonomy,
|
|
8252
9886
|
getEternalEngine,
|
|
8253
9887
|
subscribeEternalIteration,
|
|
9888
|
+
subscribeEternalStage,
|
|
8254
9889
|
appVersion: CLI_VERSION,
|
|
8255
9890
|
provider: config.provider,
|
|
8256
9891
|
family: banneredFamily,
|
|
8257
9892
|
keyTail: banneredKeyTail,
|
|
8258
9893
|
getPickableProviders,
|
|
8259
9894
|
switchProviderAndModel,
|
|
9895
|
+
switchAutonomy: (mode) => {
|
|
9896
|
+
onAutonomy?.(mode);
|
|
9897
|
+
return null;
|
|
9898
|
+
},
|
|
8260
9899
|
effectiveMaxContext,
|
|
8261
9900
|
// Default OFF so the terminal's native scrollback works for chat
|
|
8262
9901
|
// history out of the box (mouse wheel / Shift+PgUp). Users who hit
|
|
@@ -8303,7 +9942,7 @@ async function execute(deps) {
|
|
|
8303
9942
|
if (autoCompleted > 0) {
|
|
8304
9943
|
const progress = getTaskProgress2();
|
|
8305
9944
|
if (progress) {
|
|
8306
|
-
messages.push(`\u2713 ${autoCompleted} task(s) auto-completed! Progress: ${progress.completed}/${progress.total} (${progress.
|
|
9945
|
+
messages.push(`\u2713 ${autoCompleted} task(s) auto-completed! Progress: ${progress.completed}/${progress.total} (${progress.percentComplete}%)`);
|
|
8307
9946
|
}
|
|
8308
9947
|
}
|
|
8309
9948
|
}
|
|
@@ -8338,7 +9977,9 @@ async function execute(deps) {
|
|
|
8338
9977
|
projectName: path23.basename(projectRoot) || void 0,
|
|
8339
9978
|
projectRoot,
|
|
8340
9979
|
getAutonomy,
|
|
9980
|
+
onAutonomy,
|
|
8341
9981
|
getEternalEngine,
|
|
9982
|
+
getParallelEngine,
|
|
8342
9983
|
skillLoader
|
|
8343
9984
|
});
|
|
8344
9985
|
} finally {
|
|
@@ -8357,6 +9998,9 @@ async function execute(deps) {
|
|
|
8357
9998
|
effectiveMaxContext,
|
|
8358
9999
|
projectName: path23.basename(projectRoot) || void 0,
|
|
8359
10000
|
getAutonomy,
|
|
10001
|
+
onAutonomy,
|
|
10002
|
+
getEternalEngine,
|
|
10003
|
+
getParallelEngine,
|
|
8360
10004
|
skillLoader
|
|
8361
10005
|
});
|
|
8362
10006
|
}
|
|
@@ -8567,8 +10211,15 @@ var MultiAgentHost = class {
|
|
|
8567
10211
|
outputBytes: e.outputBytes
|
|
8568
10212
|
});
|
|
8569
10213
|
});
|
|
10214
|
+
const offSummaryBridge = events.on("subagent.iteration_summary", (e) => {
|
|
10215
|
+
hostEvents.emit("subagent.iteration_summary", {
|
|
10216
|
+
...e,
|
|
10217
|
+
subagentId: subCfg.id ?? subCfg.name ?? "subagent"
|
|
10218
|
+
});
|
|
10219
|
+
});
|
|
8570
10220
|
const dispose = async () => {
|
|
8571
10221
|
offToolBridge();
|
|
10222
|
+
offSummaryBridge();
|
|
8572
10223
|
try {
|
|
8573
10224
|
await subSession.close?.();
|
|
8574
10225
|
} catch {
|
|
@@ -9106,10 +10757,10 @@ function renderContextChip2(ctx) {
|
|
|
9106
10757
|
const ratio = Math.max(0, Math.min(1, ctx.used / ctx.max));
|
|
9107
10758
|
const pct2 = Math.round(ratio * 100);
|
|
9108
10759
|
const chipColor = ratio >= 0.85 ? color.red : ratio >= 0.65 ? color.yellow : color.cyan;
|
|
9109
|
-
const bar =
|
|
10760
|
+
const bar = renderProgress3(ratio, 8);
|
|
9110
10761
|
return color.dim("ctx ") + chipColor(bar) + chipColor(` ${pct2}%`) + color.dim(` (${fmtTok(ctx.used)}/${fmtTok(ctx.max)})`);
|
|
9111
10762
|
}
|
|
9112
|
-
function
|
|
10763
|
+
function renderProgress3(ratio, width) {
|
|
9113
10764
|
const clamped = Math.max(0, Math.min(1, ratio));
|
|
9114
10765
|
const filled = clamped === 0 ? 0 : Math.max(1, Math.round(clamped * width));
|
|
9115
10766
|
const capped = Math.min(width, filled);
|
|
@@ -9472,6 +11123,7 @@ function resolveBundledSkillsDir2() {
|
|
|
9472
11123
|
return void 0;
|
|
9473
11124
|
}
|
|
9474
11125
|
}
|
|
11126
|
+
var stageListeners = /* @__PURE__ */ new Set();
|
|
9475
11127
|
async function main(argv) {
|
|
9476
11128
|
const ctx = await boot(argv);
|
|
9477
11129
|
if (typeof ctx === "number") return ctx;
|
|
@@ -9517,6 +11169,7 @@ async function main(argv) {
|
|
|
9517
11169
|
modelsRegistry,
|
|
9518
11170
|
permission: {
|
|
9519
11171
|
yolo: config.yolo,
|
|
11172
|
+
forceAllYolo: flags["force-all-yolo"] === true,
|
|
9520
11173
|
promptDelegate: makePromptDelegate(reader)
|
|
9521
11174
|
},
|
|
9522
11175
|
compactor: { preserveK: config.context.preserveK, eliseThreshold: config.context.eliseThreshold },
|
|
@@ -9754,6 +11407,7 @@ async function main(argv) {
|
|
|
9754
11407
|
let director = null;
|
|
9755
11408
|
let autonomyMode = "off";
|
|
9756
11409
|
let eternalEngine = null;
|
|
11410
|
+
let parallelEngine = null;
|
|
9757
11411
|
const eternalListeners = /* @__PURE__ */ new Set();
|
|
9758
11412
|
const broadcastEternalIteration = (entry) => {
|
|
9759
11413
|
for (const fn of eternalListeners) {
|
|
@@ -10024,6 +11678,48 @@ async function main(argv) {
|
|
|
10024
11678
|
}
|
|
10025
11679
|
return `Unknown fleet action: ${action}`;
|
|
10026
11680
|
},
|
|
11681
|
+
onFleetStatus: () => {
|
|
11682
|
+
if (!director) return null;
|
|
11683
|
+
return director.status();
|
|
11684
|
+
},
|
|
11685
|
+
onFleetUsage: () => {
|
|
11686
|
+
if (!director) return null;
|
|
11687
|
+
return director.snapshot();
|
|
11688
|
+
},
|
|
11689
|
+
onFleetKill: () => {
|
|
11690
|
+
if (!director) return 0;
|
|
11691
|
+
const s = director.status();
|
|
11692
|
+
let killed = 0;
|
|
11693
|
+
for (const sa of s.subagents) {
|
|
11694
|
+
if (sa.status === "running" || sa.status === "idle") {
|
|
11695
|
+
try {
|
|
11696
|
+
director.terminate(sa.id);
|
|
11697
|
+
killed++;
|
|
11698
|
+
} catch {
|
|
11699
|
+
}
|
|
11700
|
+
}
|
|
11701
|
+
}
|
|
11702
|
+
return killed;
|
|
11703
|
+
},
|
|
11704
|
+
onFleetTerminate: (subagentId) => {
|
|
11705
|
+
if (!director) return false;
|
|
11706
|
+
try {
|
|
11707
|
+
director.terminate(subagentId);
|
|
11708
|
+
return true;
|
|
11709
|
+
} catch {
|
|
11710
|
+
return false;
|
|
11711
|
+
}
|
|
11712
|
+
},
|
|
11713
|
+
onFleetSpawn: async (role) => {
|
|
11714
|
+
if (!director) throw new Error("No director active \u2014 start with --director or use /autonomy parallel.");
|
|
11715
|
+
const cfg = FLEET_ROSTER[role] ?? {
|
|
11716
|
+
id: `manual-${Date.now()}`,
|
|
11717
|
+
name: role,
|
|
11718
|
+
maxIterations: 50,
|
|
11719
|
+
maxToolCalls: 200
|
|
11720
|
+
};
|
|
11721
|
+
return director.spawn(cfg);
|
|
11722
|
+
},
|
|
10027
11723
|
onFleetLog: async (subagentId, mode) => {
|
|
10028
11724
|
const subagentsRoot = path23.join(fleetRootForPromotion, "subagents");
|
|
10029
11725
|
let runDirs;
|
|
@@ -10272,25 +11968,35 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
10272
11968
|
}
|
|
10273
11969
|
return autonomyMode;
|
|
10274
11970
|
},
|
|
10275
|
-
onEternalStart: () => {
|
|
10276
|
-
|
|
10277
|
-
|
|
10278
|
-
|
|
10279
|
-
|
|
10280
|
-
|
|
10281
|
-
|
|
10282
|
-
|
|
10283
|
-
|
|
10284
|
-
|
|
10285
|
-
|
|
10286
|
-
|
|
10287
|
-
|
|
10288
|
-
|
|
11971
|
+
onEternalStart: (mode) => {
|
|
11972
|
+
const effectiveMode = mode ?? "eternal";
|
|
11973
|
+
if (effectiveMode === "eternal-parallel") {
|
|
11974
|
+
if (!parallelEngine) {
|
|
11975
|
+
parallelEngine = new ParallelEternalEngine({
|
|
11976
|
+
agent,
|
|
11977
|
+
projectRoot,
|
|
11978
|
+
compactor: container.resolve(TOKENS.Compactor),
|
|
11979
|
+
maxContextTokens: effectiveMaxContext > 0 ? effectiveMaxContext : void 0,
|
|
11980
|
+
onIteration: broadcastEternalIteration
|
|
11981
|
+
});
|
|
11982
|
+
}
|
|
11983
|
+
void parallelEngine.prime?.();
|
|
11984
|
+
} else {
|
|
11985
|
+
if (!eternalEngine) {
|
|
11986
|
+
eternalEngine = new EternalAutonomyEngine({
|
|
11987
|
+
agent,
|
|
11988
|
+
projectRoot,
|
|
11989
|
+
compactor: container.resolve(TOKENS.Compactor),
|
|
11990
|
+
maxContextTokens: effectiveMaxContext > 0 ? effectiveMaxContext : void 0,
|
|
11991
|
+
onIteration: broadcastEternalIteration
|
|
11992
|
+
});
|
|
11993
|
+
}
|
|
11994
|
+
void eternalEngine.prime();
|
|
10289
11995
|
}
|
|
10290
|
-
void eternalEngine.prime();
|
|
10291
11996
|
},
|
|
10292
11997
|
onEternalStop: () => {
|
|
10293
11998
|
eternalEngine?.stop();
|
|
11999
|
+
parallelEngine?.stop();
|
|
10294
12000
|
},
|
|
10295
12001
|
onExit: () => {
|
|
10296
12002
|
void mcpRegistry.stopAll();
|
|
@@ -10410,11 +12116,23 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
10410
12116
|
return policy.getYolo();
|
|
10411
12117
|
},
|
|
10412
12118
|
getAutonomy: () => autonomyMode,
|
|
12119
|
+
onAutonomy: (setTo) => {
|
|
12120
|
+
if (setTo !== void 0) {
|
|
12121
|
+
autonomyMode = setTo;
|
|
12122
|
+
return setTo;
|
|
12123
|
+
}
|
|
12124
|
+
return autonomyMode;
|
|
12125
|
+
},
|
|
10413
12126
|
getEternalEngine: () => eternalEngine,
|
|
12127
|
+
getParallelEngine: () => parallelEngine,
|
|
10414
12128
|
subscribeEternalIteration: (fn) => {
|
|
10415
12129
|
eternalListeners.add(fn);
|
|
10416
12130
|
return () => eternalListeners.delete(fn);
|
|
10417
12131
|
},
|
|
12132
|
+
subscribeEternalStage: (fn) => {
|
|
12133
|
+
stageListeners.add(fn);
|
|
12134
|
+
return () => stageListeners.delete(fn);
|
|
12135
|
+
},
|
|
10418
12136
|
skillLoader: config.features.skills ? skillLoader : void 0
|
|
10419
12137
|
});
|
|
10420
12138
|
}
|