@wrongstack/cli 0.4.1 → 0.5.0
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 +1623 -133
- package/dist/index.js.map +1 -1
- package/package.json +10 -10
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
2
|
+
import * as path17 from 'path';
|
|
3
|
+
import { color, allServers, DefaultPathResolver, TOKENS, DefaultSystemPromptBuilder, ToolRegistry, createContextManagerTool, EventBus, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, SlashCommandRegistry, loadPlugins, createDelegateTool, FLEET_ROSTER, DefaultLogger, DefaultModelsRegistry, ProviderRegistry, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, AutoCompactionMiddleware, estimateRequestTokens, Agent, makeDirectorSessionFactory, Director, DefaultMultiAgentCoordinator, makeAgentSubagentRunner, resolveWstackPaths, DefaultSecretVault, migratePlaintextSecrets, DefaultConfigLoader, DefaultSessionReader, atomicWrite, AutoApprovePermissionPolicy, formatContextWindowModeList, repairToolUseAdjacency, getContextWindowMode, resolveContextWindowPolicy, formatTodosList, emptyPlan, clearPlan, savePlan, removePlanItem, formatPlan, setPlanItemStatus, addPlanItem, SpecStore, TaskGraphStore, SpecVersioning, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, DefaultTaskStore, TaskTracker, InputBuilder, projectHash, decryptConfigSecrets, encryptConfigSecrets as encryptConfigSecrets$1, DefaultPluginAPI } from '@wrongstack/core';
|
|
3
4
|
import * as crypto from 'crypto';
|
|
4
5
|
import { randomUUID } from 'crypto';
|
|
5
6
|
import * as fs14 from 'fs/promises';
|
|
6
|
-
import
|
|
7
|
-
import { DefaultSecretVault as DefaultSecretVault$1, encryptConfigSecrets } from '@wrongstack/core/security';
|
|
7
|
+
import { DefaultSecretVault as DefaultSecretVault$1, encryptConfigSecrets, decryptConfigSecrets as decryptConfigSecrets$1 } from '@wrongstack/core/security';
|
|
8
8
|
import { WebSocketServer, WebSocket } from 'ws';
|
|
9
9
|
import { writeFileSync } from 'fs';
|
|
10
10
|
import { createRequire } from 'module';
|
|
@@ -12,12 +12,15 @@ import { MCPRegistry } from '@wrongstack/mcp';
|
|
|
12
12
|
import { buildProviderFactoriesFromRegistry, makeProviderFromConfig, capabilitiesFor } from '@wrongstack/providers';
|
|
13
13
|
import { createDefaultContainer, routeImagesForModel, readClipboardImage } from '@wrongstack/runtime';
|
|
14
14
|
import { builtinToolsPack, rememberTool, forgetTool } from '@wrongstack/tools';
|
|
15
|
-
import * as
|
|
15
|
+
import * as os4 from 'os';
|
|
16
16
|
import * as readline from 'readline';
|
|
17
|
+
import { SkillInstaller } from '@wrongstack/core/skills';
|
|
17
18
|
import { createToolVisionAdapters } from '@wrongstack/runtime/vision';
|
|
18
19
|
|
|
19
20
|
var __defProp = Object.defineProperty;
|
|
21
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
20
22
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
23
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
21
24
|
var __esm = (fn, res) => function __init() {
|
|
22
25
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
23
26
|
};
|
|
@@ -25,6 +28,835 @@ var __export = (target, all) => {
|
|
|
25
28
|
for (var name in all)
|
|
26
29
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
27
30
|
};
|
|
31
|
+
var __copyProps = (to, from, except, desc) => {
|
|
32
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
33
|
+
for (let key of __getOwnPropNames(from))
|
|
34
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
35
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
36
|
+
}
|
|
37
|
+
return to;
|
|
38
|
+
};
|
|
39
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
40
|
+
|
|
41
|
+
// src/slash-commands/sdd.ts
|
|
42
|
+
var sdd_exports = {};
|
|
43
|
+
__export(sdd_exports, {
|
|
44
|
+
autoDetectTaskCompletion: () => autoDetectTaskCompletion,
|
|
45
|
+
buildSddCommand: () => buildSddCommand,
|
|
46
|
+
getActiveBuilder: () => getActiveBuilder,
|
|
47
|
+
getActiveSDDContext: () => getActiveSDDContext,
|
|
48
|
+
getActiveSDDPhase: () => getActiveSDDPhase,
|
|
49
|
+
getTaskListText: () => getTaskListText,
|
|
50
|
+
getTaskProgress: () => getTaskProgress,
|
|
51
|
+
markTaskCompleted: () => markTaskCompleted,
|
|
52
|
+
trySaveImplementationPlan: () => trySaveImplementationPlan,
|
|
53
|
+
trySaveSpecFromAIOutput: () => trySaveSpecFromAIOutput,
|
|
54
|
+
trySaveTasksFromAIOutput: () => trySaveTasksFromAIOutput
|
|
55
|
+
});
|
|
56
|
+
function getActiveSDDContext() {
|
|
57
|
+
if (!activeBuilder) return null;
|
|
58
|
+
const session = activeBuilder.getSession();
|
|
59
|
+
if (session.phase === "done") return null;
|
|
60
|
+
return activeBuilder.getAIPrompt();
|
|
61
|
+
}
|
|
62
|
+
function getActiveSDDPhase() {
|
|
63
|
+
if (!activeBuilder) return null;
|
|
64
|
+
return activeBuilder.getPhase();
|
|
65
|
+
}
|
|
66
|
+
async function trySaveSpecFromAIOutput(aiOutput) {
|
|
67
|
+
if (!activeBuilder) return false;
|
|
68
|
+
const spec = activeBuilder.tryParseSpecFromOutput(aiOutput);
|
|
69
|
+
if (!spec) return false;
|
|
70
|
+
activeBuilder.setSpec(spec);
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
async function trySaveTasksFromAIOutput(aiOutput) {
|
|
74
|
+
if (!activeBuilder) return false;
|
|
75
|
+
const session = activeBuilder.getSession();
|
|
76
|
+
if (!session.spec) return false;
|
|
77
|
+
const json = activeBuilder.extractJSONArray(aiOutput);
|
|
78
|
+
if (!json) return false;
|
|
79
|
+
let tasks;
|
|
80
|
+
try {
|
|
81
|
+
tasks = JSON.parse(json);
|
|
82
|
+
} catch {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
if (!Array.isArray(tasks) || tasks.length === 0) return false;
|
|
86
|
+
const validTasks = tasks.filter((t) => t && typeof t === "object" && typeof t.title === "string" && t.title.length > 0);
|
|
87
|
+
if (validTasks.length === 0) return false;
|
|
88
|
+
const store = new DefaultTaskStore();
|
|
89
|
+
const tracker = new TaskTracker({ store });
|
|
90
|
+
const graph = await tracker.createGraph(session.spec.id, session.spec.title);
|
|
91
|
+
for (const task of validTasks) {
|
|
92
|
+
const title = String(task.title);
|
|
93
|
+
const description = String(task.description ?? "");
|
|
94
|
+
const type = ["feature", "bugfix", "refactor", "docs", "test", "chore"].includes(String(task.type)) ? String(task.type) : "feature";
|
|
95
|
+
const priority = ["critical", "high", "medium", "low"].includes(String(task.priority)) ? String(task.priority) : "medium";
|
|
96
|
+
const estimateHours = Number(task.estimateHours) || 2;
|
|
97
|
+
const tags = Array.isArray(task.tags) ? task.tags.map(String) : [];
|
|
98
|
+
tracker.addNode({
|
|
99
|
+
title,
|
|
100
|
+
description,
|
|
101
|
+
type,
|
|
102
|
+
priority,
|
|
103
|
+
status: "pending",
|
|
104
|
+
estimateHours,
|
|
105
|
+
tags
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
activeTaskStore = store;
|
|
109
|
+
activeTaskTracker = tracker;
|
|
110
|
+
activeTaskGraphId = graph.id;
|
|
111
|
+
activeBuilder.setTaskGraphId(graph.id);
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
function getTaskProgress() {
|
|
115
|
+
if (!activeTaskTracker) return null;
|
|
116
|
+
const progress = activeTaskTracker.getProgress();
|
|
117
|
+
return {
|
|
118
|
+
total: progress.total,
|
|
119
|
+
completed: progress.completed,
|
|
120
|
+
pending: progress.pending,
|
|
121
|
+
percent: progress.percentComplete
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
function getTaskListText() {
|
|
125
|
+
if (!activeTaskTracker) return null;
|
|
126
|
+
const nodes = activeTaskTracker.getAllNodes();
|
|
127
|
+
if (nodes.length === 0) return null;
|
|
128
|
+
const lines = nodes.map((n, i) => {
|
|
129
|
+
const status = n.status === "completed" ? "\u2705" : n.status === "in_progress" ? "\u{1F504}" : "\u23F3";
|
|
130
|
+
return `${i + 1}. ${status} [${n.priority}] ${n.title}`;
|
|
131
|
+
});
|
|
132
|
+
return lines.join("\n");
|
|
133
|
+
}
|
|
134
|
+
function markTaskCompleted(taskTitle) {
|
|
135
|
+
if (!activeTaskTracker) return false;
|
|
136
|
+
const nodes = activeTaskTracker.getAllNodes({ status: ["pending", "in_progress"] });
|
|
137
|
+
const match = nodes.find(
|
|
138
|
+
(n) => n.title.toLowerCase().includes(taskTitle.toLowerCase()) || taskTitle.toLowerCase().includes(n.title.toLowerCase())
|
|
139
|
+
);
|
|
140
|
+
if (!match) return false;
|
|
141
|
+
activeTaskTracker.updateNodeStatus(match.id, "completed");
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
function autoDetectTaskCompletion(aiOutput) {
|
|
145
|
+
if (!activeTaskTracker) return 0;
|
|
146
|
+
const pending = activeTaskTracker.getAllNodes({ status: ["pending", "in_progress"] });
|
|
147
|
+
if (pending.length === 0) return 0;
|
|
148
|
+
let completed = 0;
|
|
149
|
+
const lines = aiOutput.split("\n");
|
|
150
|
+
for (const line of lines) {
|
|
151
|
+
const trimmed = line.trim();
|
|
152
|
+
const sddDoneMatch = trimmed.match(/\/sdd\s+done\s+(.+)/i);
|
|
153
|
+
if (sddDoneMatch?.[1]) {
|
|
154
|
+
const target = sddDoneMatch[1].trim();
|
|
155
|
+
const num = Number(target);
|
|
156
|
+
if (!Number.isNaN(num) && num >= 1 && num <= pending.length) {
|
|
157
|
+
const node = pending[num - 1];
|
|
158
|
+
if (node && node.status !== "completed") {
|
|
159
|
+
activeTaskTracker.updateNodeStatus(node.id, "completed");
|
|
160
|
+
completed++;
|
|
161
|
+
}
|
|
162
|
+
} else {
|
|
163
|
+
const match = pending.find(
|
|
164
|
+
(n) => n.title.toLowerCase().includes(target.toLowerCase()) || target.toLowerCase().includes(n.title.toLowerCase())
|
|
165
|
+
);
|
|
166
|
+
if (match && match.status !== "completed") {
|
|
167
|
+
activeTaskTracker.updateNodeStatus(match.id, "completed");
|
|
168
|
+
completed++;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
const checkmarkMatch = trimmed.match(/^✅\s*(?:Task:\s*)?(.+)/i);
|
|
174
|
+
if (checkmarkMatch?.[1]) {
|
|
175
|
+
const title = checkmarkMatch[1].trim();
|
|
176
|
+
const match = pending.find(
|
|
177
|
+
(n) => n.title.toLowerCase().includes(title.toLowerCase()) || title.toLowerCase().includes(n.title.toLowerCase())
|
|
178
|
+
);
|
|
179
|
+
if (match && match.status !== "completed") {
|
|
180
|
+
activeTaskTracker.updateNodeStatus(match.id, "completed");
|
|
181
|
+
completed++;
|
|
182
|
+
}
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
const taskNumMatch = trimmed.match(/Task\s+(\d+)\s*[:]\s*(?:complete|done|finished)/i);
|
|
186
|
+
if (taskNumMatch?.[1]) {
|
|
187
|
+
const num = Number(taskNumMatch[1]);
|
|
188
|
+
if (num >= 1 && num <= pending.length) {
|
|
189
|
+
const node = pending[num - 1];
|
|
190
|
+
if (node && node.status !== "completed") {
|
|
191
|
+
activeTaskTracker.updateNodeStatus(node.id, "completed");
|
|
192
|
+
completed++;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
const completedMatch = trimmed.match(/^(?:Completed|Done|Finished)\s*[:]\s*(.+)/i);
|
|
198
|
+
if (completedMatch?.[1]) {
|
|
199
|
+
const title = completedMatch[1].trim();
|
|
200
|
+
const match = pending.find(
|
|
201
|
+
(n) => n.title.toLowerCase().includes(title.toLowerCase()) || title.toLowerCase().includes(n.title.toLowerCase())
|
|
202
|
+
);
|
|
203
|
+
if (match && match.status !== "completed") {
|
|
204
|
+
activeTaskTracker.updateNodeStatus(match.id, "completed");
|
|
205
|
+
completed++;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return completed;
|
|
210
|
+
}
|
|
211
|
+
function trySaveImplementationPlan(aiOutput) {
|
|
212
|
+
if (!activeBuilder) return false;
|
|
213
|
+
const session = activeBuilder.getSession();
|
|
214
|
+
if (session.phase !== "implementation") return false;
|
|
215
|
+
const jsonMatch = aiOutput.match(/```json\s*\[/);
|
|
216
|
+
if (jsonMatch?.index && jsonMatch.index > 0) {
|
|
217
|
+
const plan = aiOutput.substring(0, jsonMatch.index).trim();
|
|
218
|
+
if (plan.length > 50) {
|
|
219
|
+
activeBuilder.setImplementation(plan);
|
|
220
|
+
return true;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (aiOutput.length > 100 && !aiOutput.includes("```json")) {
|
|
224
|
+
activeBuilder.setImplementation(aiOutput.trim());
|
|
225
|
+
return true;
|
|
226
|
+
}
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
function getActiveBuilder() {
|
|
230
|
+
return activeBuilder;
|
|
231
|
+
}
|
|
232
|
+
function buildSddCommand(opts) {
|
|
233
|
+
return {
|
|
234
|
+
name: "sdd",
|
|
235
|
+
description: "AI-driven SDD: /sdd [new|approve|execute|cancel|status|list|show|templates]",
|
|
236
|
+
async run(args) {
|
|
237
|
+
const ctx = opts.context;
|
|
238
|
+
const projectRoot = ctx?.projectRoot ?? process.cwd();
|
|
239
|
+
const specsDir = path17.join(projectRoot, ".wrongstack", "specs");
|
|
240
|
+
const graphsDir = path17.join(projectRoot, ".wrongstack", "task-graphs");
|
|
241
|
+
const specStore = new SpecStore({ baseDir: specsDir });
|
|
242
|
+
new TaskGraphStore({ baseDir: graphsDir });
|
|
243
|
+
const versioning = new SpecVersioning();
|
|
244
|
+
const [verb, ...rest] = args.trim().split(/\s+/);
|
|
245
|
+
const restJoined = rest.join(" ").trim();
|
|
246
|
+
switch (verb) {
|
|
247
|
+
case "":
|
|
248
|
+
case "help":
|
|
249
|
+
return { message: sddHelp() };
|
|
250
|
+
// ── AI-Driven Spec Session ─────────────────────────────────────────
|
|
251
|
+
case "new":
|
|
252
|
+
case "create": {
|
|
253
|
+
const forceFlag = rest.includes("--force") || rest.includes("-f");
|
|
254
|
+
const title = rest.filter((a) => !a.startsWith("-")).join(" ").trim() || "Untitled Feature";
|
|
255
|
+
if (!activeBuilder && !forceFlag) {
|
|
256
|
+
const sessionPath = path17.join(projectRoot, ".wrongstack", "sdd-session.json");
|
|
257
|
+
try {
|
|
258
|
+
const fsp = await import('fs/promises');
|
|
259
|
+
await fsp.access(sessionPath);
|
|
260
|
+
const projectContext2 = await gatherProjectContext(projectRoot);
|
|
261
|
+
const tempBuilder = new AISpecBuilder({
|
|
262
|
+
store: specStore,
|
|
263
|
+
projectContext: projectContext2,
|
|
264
|
+
sessionPath
|
|
265
|
+
});
|
|
266
|
+
const loaded = await tempBuilder.loadSession();
|
|
267
|
+
if (loaded) {
|
|
268
|
+
const existing = tempBuilder.getSession();
|
|
269
|
+
if (existing.phase !== "done") {
|
|
270
|
+
return {
|
|
271
|
+
message: [
|
|
272
|
+
`An existing SDD session was found:`,
|
|
273
|
+
` Feature: "${existing.title}"`,
|
|
274
|
+
` Phase: ${existing.phase}`,
|
|
275
|
+
` Questions: ${existing.questionCount}`,
|
|
276
|
+
"",
|
|
277
|
+
"Use /sdd resume to continue, or /sdd new --force to start fresh."
|
|
278
|
+
].join("\n")
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
} catch {
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
activeTaskStore = null;
|
|
286
|
+
activeTaskTracker = null;
|
|
287
|
+
activeTaskGraphId = null;
|
|
288
|
+
const projectContext = await gatherProjectContext(projectRoot);
|
|
289
|
+
activeBuilder = new AISpecBuilder({
|
|
290
|
+
store: specStore,
|
|
291
|
+
projectContext,
|
|
292
|
+
minQuestions: 2,
|
|
293
|
+
maxQuestions: 10,
|
|
294
|
+
sessionPath: path17.join(projectRoot, ".wrongstack", "sdd-session.json")
|
|
295
|
+
});
|
|
296
|
+
activeBuilder.startSession(title);
|
|
297
|
+
const aiPrompt = activeBuilder.getAIPrompt();
|
|
298
|
+
return {
|
|
299
|
+
message: [
|
|
300
|
+
`\u2554\u2550\u2550\u2550 SDD: AI Spec Builder \u2550\u2550\u2550\u2557`,
|
|
301
|
+
"",
|
|
302
|
+
`Feature: "${title}"`,
|
|
303
|
+
"",
|
|
304
|
+
"The AI will now ask you contextual questions.",
|
|
305
|
+
"Answer naturally \u2014 it will generate the spec when ready.",
|
|
306
|
+
"",
|
|
307
|
+
"Commands: /sdd approve \xB7 /sdd status \xB7 /sdd cancel"
|
|
308
|
+
].join("\n"),
|
|
309
|
+
runText: `[SDD SESSION ACTIVE]
|
|
310
|
+
${aiPrompt}
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
User message:
|
|
314
|
+
Start the specification interview for "${title}". Ask your first contextual question.`
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
// ── Phase Transitions ──────────────────────────────────────────────
|
|
318
|
+
case "approve":
|
|
319
|
+
case "ok":
|
|
320
|
+
case "confirm": {
|
|
321
|
+
if (!activeBuilder) {
|
|
322
|
+
return {
|
|
323
|
+
message: "No active SDD session. Use /sdd new to start one."
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
const phase = activeBuilder.getSession().phase;
|
|
327
|
+
if (phase === "questioning") {
|
|
328
|
+
const sddCtx = activeBuilder.getAIPrompt();
|
|
329
|
+
return {
|
|
330
|
+
message: "No spec generated yet. Generating now...",
|
|
331
|
+
runText: `[SDD SESSION ACTIVE]
|
|
332
|
+
${sddCtx}
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
User message:
|
|
336
|
+
Generate the complete specification now based on the conversation so far.`
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
if (phase === "spec_review") {
|
|
340
|
+
const spec = activeBuilder.getSession().spec;
|
|
341
|
+
if (!spec) {
|
|
342
|
+
return { message: "No spec to approve." };
|
|
343
|
+
}
|
|
344
|
+
await activeBuilder.saveSpec();
|
|
345
|
+
versioning.recordVersion(spec, "Initial spec approved");
|
|
346
|
+
activeBuilder.approve();
|
|
347
|
+
const implPrompt = activeBuilder.getAIPrompt();
|
|
348
|
+
return {
|
|
349
|
+
message: [
|
|
350
|
+
`\u2705 Spec "${spec.title}" approved and saved!`,
|
|
351
|
+
`ID: ${spec.id}`,
|
|
352
|
+
`Requirements: ${spec.requirements.length}`,
|
|
353
|
+
"",
|
|
354
|
+
"The AI will now generate an implementation plan and tasks."
|
|
355
|
+
].join("\n"),
|
|
356
|
+
runText: `[SDD SESSION ACTIVE]
|
|
357
|
+
${implPrompt}
|
|
358
|
+
|
|
359
|
+
---
|
|
360
|
+
User message:
|
|
361
|
+
Generate the implementation plan and tasks for the approved spec.`
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
if (phase === "task_review") {
|
|
365
|
+
activeBuilder.approve();
|
|
366
|
+
const execPrompt = activeBuilder.getAIPrompt();
|
|
367
|
+
return {
|
|
368
|
+
message: "\u2705 Tasks approved! The AI will now execute them one by one.",
|
|
369
|
+
runText: `[SDD SESSION ACTIVE]
|
|
370
|
+
${execPrompt}
|
|
371
|
+
|
|
372
|
+
---
|
|
373
|
+
User message:
|
|
374
|
+
Start executing the tasks one by one.`
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
return {
|
|
378
|
+
message: `Current phase is "${phase}". Use /sdd status to see details.`
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
// ── Task Execution ─────────────────────────────────────────────────
|
|
382
|
+
case "execute":
|
|
383
|
+
case "run": {
|
|
384
|
+
if (!activeBuilder) {
|
|
385
|
+
return {
|
|
386
|
+
message: "No active SDD session. Use /sdd new to start one."
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
const session = activeBuilder.getSession();
|
|
390
|
+
if (session.phase !== "executing" && session.phase !== "task_review") {
|
|
391
|
+
return {
|
|
392
|
+
message: `Cannot execute in phase "${session.phase}". Use /sdd approve first.`
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
const execPrompt = activeBuilder.getAIPrompt();
|
|
396
|
+
return {
|
|
397
|
+
message: "\u26A1 Starting task execution. The AI will execute tasks one by one.",
|
|
398
|
+
runText: `[SDD SESSION ACTIVE]
|
|
399
|
+
${execPrompt}
|
|
400
|
+
|
|
401
|
+
---
|
|
402
|
+
User message:
|
|
403
|
+
Start executing the tasks one by one.`
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
case "plan":
|
|
407
|
+
case "impl": {
|
|
408
|
+
if (!activeBuilder) {
|
|
409
|
+
return { message: "No active SDD session. Use /sdd new to start one." };
|
|
410
|
+
}
|
|
411
|
+
const session = activeBuilder.getSession();
|
|
412
|
+
if (!session.implementation) {
|
|
413
|
+
return {
|
|
414
|
+
message: session.phase === "implementation" ? "No implementation plan yet. The AI will generate it after /sdd approve." : "No implementation plan in this session."
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
return {
|
|
418
|
+
message: [
|
|
419
|
+
"\u2550\u2550\u2550 Implementation Plan \u2550\u2550\u2550",
|
|
420
|
+
"",
|
|
421
|
+
session.implementation
|
|
422
|
+
].join("\n")
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
case "spec": {
|
|
426
|
+
if (!activeBuilder) {
|
|
427
|
+
return { message: "No active SDD session. Use /sdd new to start one." };
|
|
428
|
+
}
|
|
429
|
+
const session = activeBuilder.getSession();
|
|
430
|
+
if (!session.spec) {
|
|
431
|
+
return {
|
|
432
|
+
message: session.phase === "questioning" ? "No spec generated yet. Keep answering the AI's questions." : "No spec in this session."
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
const spec = session.spec;
|
|
436
|
+
const lines = [
|
|
437
|
+
`\u2550\u2550\u2550 Current Spec \u2550\u2550\u2550`,
|
|
438
|
+
"",
|
|
439
|
+
`Title: ${spec.title}`,
|
|
440
|
+
`Version: ${spec.version}`,
|
|
441
|
+
`Status: ${spec.status}`,
|
|
442
|
+
"",
|
|
443
|
+
"## Overview",
|
|
444
|
+
spec.overview
|
|
445
|
+
];
|
|
446
|
+
if (spec.requirements.length > 0) {
|
|
447
|
+
lines.push("", `## Requirements (${spec.requirements.length})`);
|
|
448
|
+
for (const r of spec.requirements) {
|
|
449
|
+
const ac = r.acceptanceCriteria.length > 0 ? ` \u2192 ${r.acceptanceCriteria.join(", ")}` : "";
|
|
450
|
+
lines.push(` [${r.priority}] ${r.description}${ac}`);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
return { message: lines.join("\n") };
|
|
454
|
+
}
|
|
455
|
+
case "tasks":
|
|
456
|
+
case "task": {
|
|
457
|
+
if (!activeTaskTracker) {
|
|
458
|
+
return { message: "No tasks generated yet. Use /sdd new to start." };
|
|
459
|
+
}
|
|
460
|
+
const nodes = activeTaskTracker.getAllNodes();
|
|
461
|
+
if (nodes.length === 0) {
|
|
462
|
+
return { message: "No tasks in the current graph." };
|
|
463
|
+
}
|
|
464
|
+
const progress = activeTaskTracker.getProgress();
|
|
465
|
+
const lines = [
|
|
466
|
+
`\u2550\u2550\u2550 Task List (${progress.completed}/${progress.total} done) \u2550\u2550\u2550`,
|
|
467
|
+
""
|
|
468
|
+
];
|
|
469
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
470
|
+
const n = nodes[i];
|
|
471
|
+
const status = n.status === "completed" ? "\u2705" : n.status === "in_progress" ? "\u{1F504}" : n.status === "failed" ? "\u274C" : "\u23F3";
|
|
472
|
+
lines.push(`${i + 1}. ${status} [${n.priority}] ${n.title}`);
|
|
473
|
+
if (n.description) {
|
|
474
|
+
lines.push(` ${n.description.split("\n")[0]}`);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
return { message: lines.join("\n") };
|
|
478
|
+
}
|
|
479
|
+
case "done":
|
|
480
|
+
case "complete": {
|
|
481
|
+
if (!activeTaskTracker) {
|
|
482
|
+
return { message: "No tasks to complete." };
|
|
483
|
+
}
|
|
484
|
+
if (!restJoined) {
|
|
485
|
+
return { message: "Usage: /sdd done <task title or number>" };
|
|
486
|
+
}
|
|
487
|
+
const nodes = activeTaskTracker.getAllNodes({ status: ["pending", "in_progress"] });
|
|
488
|
+
const num = Number(restJoined);
|
|
489
|
+
let matched = false;
|
|
490
|
+
if (!Number.isNaN(num) && num >= 1 && num <= nodes.length) {
|
|
491
|
+
const node = nodes[num - 1];
|
|
492
|
+
if (node) {
|
|
493
|
+
activeTaskTracker.updateNodeStatus(node.id, "completed");
|
|
494
|
+
matched = true;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
if (!matched) {
|
|
498
|
+
const match = nodes.find(
|
|
499
|
+
(n) => n.title.toLowerCase().includes(restJoined.toLowerCase()) || restJoined.toLowerCase().includes(n.title.toLowerCase())
|
|
500
|
+
);
|
|
501
|
+
if (match) {
|
|
502
|
+
activeTaskTracker.updateNodeStatus(match.id, "completed");
|
|
503
|
+
matched = true;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
if (!matched) {
|
|
507
|
+
return { message: `No pending task matching "${restJoined}".` };
|
|
508
|
+
}
|
|
509
|
+
const remaining = activeTaskTracker.getProgress();
|
|
510
|
+
return {
|
|
511
|
+
message: `\u2705 Task completed! ${remaining.completed}/${remaining.total} done (${remaining.percentComplete}%)`
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
// ── Session Management ─────────────────────────────────────────────
|
|
515
|
+
case "status": {
|
|
516
|
+
if (!activeBuilder) {
|
|
517
|
+
return { message: "No active SDD session." };
|
|
518
|
+
}
|
|
519
|
+
const session = activeBuilder.getSession();
|
|
520
|
+
const phaseEmoji = {
|
|
521
|
+
questioning: "\u2753",
|
|
522
|
+
spec_review: "\u{1F4CB}",
|
|
523
|
+
implementation: "\u{1F3D7}\uFE0F",
|
|
524
|
+
task_review: "\u{1F4DD}",
|
|
525
|
+
executing: "\u26A1",
|
|
526
|
+
done: "\u2705"
|
|
527
|
+
};
|
|
528
|
+
const progress = getTaskProgress();
|
|
529
|
+
const lines = [
|
|
530
|
+
"\u2550\u2550\u2550 SDD Session Status \u2550\u2550\u2550",
|
|
531
|
+
"",
|
|
532
|
+
`Feature: "${session.title}"`,
|
|
533
|
+
`Phase: ${phaseEmoji[session.phase]} ${session.phase}`,
|
|
534
|
+
`Questions asked: ${session.questionCount}`
|
|
535
|
+
];
|
|
536
|
+
if (session.spec) {
|
|
537
|
+
lines.push(`Spec: ${session.spec.title} (${session.spec.requirements.length} requirements)`);
|
|
538
|
+
lines.push(` Requirements: ${session.spec.requirements.map((r) => r.description).join(", ")}`);
|
|
539
|
+
}
|
|
540
|
+
if (session.implementation) {
|
|
541
|
+
const planPreview = session.implementation.split("\n").slice(0, 3).join(" ");
|
|
542
|
+
lines.push(`Implementation: ${planPreview}${session.implementation.length > 100 ? "..." : ""}`);
|
|
543
|
+
}
|
|
544
|
+
if (progress && progress.total > 0) {
|
|
545
|
+
lines.push(`Tasks: ${progress.completed}/${progress.total} (${progress.percent}%)`);
|
|
546
|
+
}
|
|
547
|
+
lines.push("", `Session ID: ${session.id}`);
|
|
548
|
+
lines.push("Commands: /sdd plan \xB7 /sdd tasks \xB7 /sdd approve \xB7 /sdd cancel");
|
|
549
|
+
return {
|
|
550
|
+
message: lines.join("\n")
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
case "cancel": {
|
|
554
|
+
const sessionPath = path17.join(projectRoot, ".wrongstack", "sdd-session.json");
|
|
555
|
+
let deletedFromDisk = false;
|
|
556
|
+
try {
|
|
557
|
+
const fsp = await import('fs/promises');
|
|
558
|
+
await fsp.unlink(sessionPath);
|
|
559
|
+
deletedFromDisk = true;
|
|
560
|
+
} catch {
|
|
561
|
+
}
|
|
562
|
+
if (activeBuilder) {
|
|
563
|
+
const title = activeBuilder.getSession().title;
|
|
564
|
+
await activeBuilder.deleteSession();
|
|
565
|
+
activeBuilder = null;
|
|
566
|
+
activeTaskStore = null;
|
|
567
|
+
activeTaskTracker = null;
|
|
568
|
+
activeTaskGraphId = null;
|
|
569
|
+
return { message: `SDD session for "${title}" cancelled.` };
|
|
570
|
+
}
|
|
571
|
+
if (deletedFromDisk) {
|
|
572
|
+
return { message: "Stale SDD session file deleted. You can now use /sdd new." };
|
|
573
|
+
}
|
|
574
|
+
return { message: "No active SDD session." };
|
|
575
|
+
}
|
|
576
|
+
case "resume": {
|
|
577
|
+
if (activeBuilder) {
|
|
578
|
+
return { message: "An SDD session is already active. Use /sdd cancel first." };
|
|
579
|
+
}
|
|
580
|
+
const sessionPath = path17.join(projectRoot, ".wrongstack", "sdd-session.json");
|
|
581
|
+
const projectContext = await gatherProjectContext(projectRoot);
|
|
582
|
+
activeBuilder = new AISpecBuilder({
|
|
583
|
+
store: specStore,
|
|
584
|
+
projectContext,
|
|
585
|
+
minQuestions: 2,
|
|
586
|
+
maxQuestions: 10,
|
|
587
|
+
sessionPath
|
|
588
|
+
});
|
|
589
|
+
const loaded = await activeBuilder.loadSession();
|
|
590
|
+
if (!loaded) {
|
|
591
|
+
activeBuilder = null;
|
|
592
|
+
return { message: "No saved SDD session found. Use /sdd new to start one." };
|
|
593
|
+
}
|
|
594
|
+
const session = activeBuilder.getSession();
|
|
595
|
+
let taskCount = 0;
|
|
596
|
+
let completedCount = 0;
|
|
597
|
+
const taskGraphId = activeBuilder.getTaskGraphId();
|
|
598
|
+
if (taskGraphId) {
|
|
599
|
+
try {
|
|
600
|
+
const store = new DefaultTaskStore();
|
|
601
|
+
const tracker = new TaskTracker({ store });
|
|
602
|
+
const graph = await tracker.loadGraph(taskGraphId);
|
|
603
|
+
if (graph) {
|
|
604
|
+
activeTaskStore = store;
|
|
605
|
+
activeTaskTracker = tracker;
|
|
606
|
+
activeTaskGraphId = taskGraphId;
|
|
607
|
+
const progress = tracker.getProgress();
|
|
608
|
+
taskCount = progress.total;
|
|
609
|
+
completedCount = progress.completed;
|
|
610
|
+
}
|
|
611
|
+
} catch {
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
const resumePrompt = activeBuilder.getAIPrompt();
|
|
615
|
+
return {
|
|
616
|
+
message: [
|
|
617
|
+
`\u2554\u2550\u2550\u2550 SDD Session Resumed \u2550\u2550\u2550\u2557`,
|
|
618
|
+
"",
|
|
619
|
+
`Feature: "${session.title}"`,
|
|
620
|
+
`Phase: ${session.phase}`,
|
|
621
|
+
`Questions asked: ${session.questionCount}`,
|
|
622
|
+
session.spec ? `Spec: ${session.spec.title}` : "",
|
|
623
|
+
taskCount > 0 ? `Tasks: ${completedCount}/${taskCount} completed` : "",
|
|
624
|
+
"",
|
|
625
|
+
"The AI will continue from where you left off."
|
|
626
|
+
].filter(Boolean).join("\n"),
|
|
627
|
+
runText: `[SDD SESSION ACTIVE]
|
|
628
|
+
${resumePrompt}
|
|
629
|
+
|
|
630
|
+
---
|
|
631
|
+
User message:
|
|
632
|
+
Continue from where we left off. Check the session status and proceed.`
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
// ── Spec Browsing ──────────────────────────────────────────────────
|
|
636
|
+
case "list":
|
|
637
|
+
case "ls": {
|
|
638
|
+
const entries = await specStore.list();
|
|
639
|
+
if (entries.length === 0) {
|
|
640
|
+
return { message: "No specs saved. Use /sdd new to create one." };
|
|
641
|
+
}
|
|
642
|
+
const lines = entries.map((e, i) => {
|
|
643
|
+
const status = e.status === "draft" ? "\u{1F4DD}" : e.status === "approved" ? "\u2705" : "\u{1F4CB}";
|
|
644
|
+
return `${i + 1}. ${status} ${e.title} (${e.version}) \u2014 ${e.id.slice(0, 8)}...`;
|
|
645
|
+
});
|
|
646
|
+
return { message: `Saved Specs:
|
|
647
|
+
${lines.join("\n")}` };
|
|
648
|
+
}
|
|
649
|
+
case "show":
|
|
650
|
+
case "view": {
|
|
651
|
+
const spec = await findSpec(specStore, restJoined);
|
|
652
|
+
if (!spec) return { message: `Spec "${restJoined}" not found.` };
|
|
653
|
+
const parser = new SpecParser();
|
|
654
|
+
const analysis = parser.analyze(spec);
|
|
655
|
+
return {
|
|
656
|
+
message: [
|
|
657
|
+
`# ${spec.title}`,
|
|
658
|
+
`Version: ${spec.version} | Status: ${spec.status}`,
|
|
659
|
+
"",
|
|
660
|
+
"## Overview",
|
|
661
|
+
spec.overview,
|
|
662
|
+
"",
|
|
663
|
+
`## Requirements (${spec.requirements.length})`,
|
|
664
|
+
...spec.requirements.map((r) => {
|
|
665
|
+
const tags = `[${r.type}][${r.priority}]`;
|
|
666
|
+
const ac = r.acceptanceCriteria.length > 0 ? `
|
|
667
|
+
AC: ${r.acceptanceCriteria.join(", ")}` : "";
|
|
668
|
+
return `- ${tags} ${r.description}${ac}`;
|
|
669
|
+
}),
|
|
670
|
+
"",
|
|
671
|
+
renderSpecAnalysis(spec, {
|
|
672
|
+
completeness: analysis.completeness,
|
|
673
|
+
gaps: analysis.gaps,
|
|
674
|
+
risks: analysis.risks.map((r) => r.risk),
|
|
675
|
+
suggestions: analysis.suggestions
|
|
676
|
+
})
|
|
677
|
+
].join("\n")
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
case "templates": {
|
|
681
|
+
const templates = listTemplates();
|
|
682
|
+
const lines = templates.map(
|
|
683
|
+
(t) => ` ${t.id}: ${t.name} \u2014 ${t.description}`
|
|
684
|
+
);
|
|
685
|
+
return {
|
|
686
|
+
message: `Available Templates:
|
|
687
|
+
${lines.join("\n")}`
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
case "from": {
|
|
691
|
+
const templateId = restJoined || "feature";
|
|
692
|
+
const template = getTemplate(templateId);
|
|
693
|
+
if (!template) {
|
|
694
|
+
return {
|
|
695
|
+
message: `Template "${templateId}" not found.
|
|
696
|
+
Available: ${listTemplates().map((t) => t.id).join(", ")}`
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
const skeleton = templateToMarkdown(template, "New Specification");
|
|
700
|
+
const spec = await specStore.createDraft("New Specification");
|
|
701
|
+
await specStore.update(spec.id, { sections: [] });
|
|
702
|
+
return {
|
|
703
|
+
message: [
|
|
704
|
+
`Created draft spec from template "${template.name}".`,
|
|
705
|
+
`ID: ${spec.id}`,
|
|
706
|
+
"",
|
|
707
|
+
"Edit the spec through the AI conversation or /sdd show to review.",
|
|
708
|
+
"",
|
|
709
|
+
skeleton
|
|
710
|
+
].join("\n")
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
case "version":
|
|
714
|
+
case "history": {
|
|
715
|
+
const spec = await findSpec(specStore, restJoined);
|
|
716
|
+
if (!spec)
|
|
717
|
+
return { message: `Spec "${restJoined}" not found.` };
|
|
718
|
+
const history = versioning.getHistory(spec.id);
|
|
719
|
+
if (history.length === 0) {
|
|
720
|
+
return {
|
|
721
|
+
message: `No version history for "${spec.title}".`
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
const lines = history.map(
|
|
725
|
+
(v, i) => `${i + 1}. v${v.version} \u2014 ${new Date(v.timestamp).toISOString()}${v.changeDescription ? ` (${v.changeDescription})` : ""}`
|
|
726
|
+
);
|
|
727
|
+
return {
|
|
728
|
+
message: `Version History for "${spec.title}":
|
|
729
|
+
${lines.join("\n")}`
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
default:
|
|
733
|
+
return {
|
|
734
|
+
message: `Unknown command "${verb}".
|
|
735
|
+
|
|
736
|
+
${sddHelp()}`
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
function sddHelp() {
|
|
743
|
+
return [
|
|
744
|
+
"",
|
|
745
|
+
"\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557",
|
|
746
|
+
"\u2551 \u{1F680} SDD \u2014 AI-Driven Spec Builder \u2551",
|
|
747
|
+
"\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D",
|
|
748
|
+
"",
|
|
749
|
+
" \u250C\u2500 \u{1F195} 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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510",
|
|
750
|
+
" \u2502 /sdd new [title] Start a new spec session \u2502",
|
|
751
|
+
" \u2502 /sdd new --force Start fresh (skip resume check) \u2502",
|
|
752
|
+
" \u2502 /sdd resume Resume a saved session \u2502",
|
|
753
|
+
" \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\u2500\u2518",
|
|
754
|
+
"",
|
|
755
|
+
" \u250C\u2500 \u{1F504} Flow \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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",
|
|
756
|
+
" \u2502 /sdd approve Approve current phase \u2502",
|
|
757
|
+
" \u2502 /sdd spec Show current session's spec \u2502",
|
|
758
|
+
" \u2502 /sdd plan Show implementation plan \u2502",
|
|
759
|
+
" \u2502 /sdd execute Execute generated tasks \u2502",
|
|
760
|
+
" \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\u2500\u2518",
|
|
761
|
+
"",
|
|
762
|
+
" \u250C\u2500 \u{1F4CB} Task Management \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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",
|
|
763
|
+
" \u2502 /sdd tasks Show current task list \u2502",
|
|
764
|
+
" \u2502 /sdd done <N> Mark task complete (by # or name) \u2502",
|
|
765
|
+
" \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\u2500\u2518",
|
|
766
|
+
"",
|
|
767
|
+
" \u250C\u2500 \u{1F4CA} 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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510",
|
|
768
|
+
" \u2502 /sdd status Show session status \u2502",
|
|
769
|
+
" \u2502 /sdd cancel Cancel session \u2502",
|
|
770
|
+
" \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\u2500\u2518",
|
|
771
|
+
"",
|
|
772
|
+
" \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\u2500\u2500\u2510",
|
|
773
|
+
" \u2502 /sdd list List saved specs \u2502",
|
|
774
|
+
" \u2502 /sdd show <id> Show spec details \u2502",
|
|
775
|
+
" \u2502 /sdd templates List available templates \u2502",
|
|
776
|
+
" \u2502 /sdd from <tmpl> Create from template \u2502",
|
|
777
|
+
" \u2502 /sdd version <id> Show version history \u2502",
|
|
778
|
+
" \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\u2500\u2518",
|
|
779
|
+
"",
|
|
780
|
+
" \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\u2500\u2510",
|
|
781
|
+
" \u2502 \u2502",
|
|
782
|
+
" \u2502 1. /sdd new Auth System \u2502",
|
|
783
|
+
" \u2502 \u2192 AI starts asking questions \u2502",
|
|
784
|
+
" \u2502 \u2502",
|
|
785
|
+
" \u2502 2. Just type your answers naturally \u2502",
|
|
786
|
+
" \u2502 \u2192 AI continues the interview \u2502",
|
|
787
|
+
" \u2502 \u2502",
|
|
788
|
+
" \u2502 3. AI generates spec (auto-detected) \u2502",
|
|
789
|
+
" \u2502 \u2192 /sdd approve \u2502",
|
|
790
|
+
" \u2502 \u2502",
|
|
791
|
+
" \u2502 3. AI generates implementation + tasks \u2502",
|
|
792
|
+
" \u2502 \u2192 /sdd approve \u2502",
|
|
793
|
+
" \u2502 \u2502",
|
|
794
|
+
" \u2502 4. AI executes tasks one by one \u2502",
|
|
795
|
+
" \u2502 \u2192 /sdd tasks (view progress) \u2502",
|
|
796
|
+
" \u2502 \u2192 /sdd done 1 (manual completion) \u2502",
|
|
797
|
+
" \u2502 \u2502",
|
|
798
|
+
" \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\u2500\u2518",
|
|
799
|
+
""
|
|
800
|
+
].join("\n");
|
|
801
|
+
}
|
|
802
|
+
async function gatherProjectContext(projectRoot) {
|
|
803
|
+
const parts = [];
|
|
804
|
+
try {
|
|
805
|
+
const fsp = await import('fs/promises');
|
|
806
|
+
const pkgPath = path17.join(projectRoot, "package.json");
|
|
807
|
+
const pkgRaw = await fsp.readFile(pkgPath, "utf8");
|
|
808
|
+
const pkg = JSON.parse(pkgRaw);
|
|
809
|
+
parts.push(`Project: ${String(pkg.name ?? "unknown")}`);
|
|
810
|
+
parts.push(`Description: ${String(pkg.description ?? "none")}`);
|
|
811
|
+
if (pkg.dependencies) {
|
|
812
|
+
const deps = Object.keys(pkg.dependencies);
|
|
813
|
+
parts.push(`Dependencies: ${deps.slice(0, 20).join(", ")}${deps.length > 20 ? "..." : ""}`);
|
|
814
|
+
}
|
|
815
|
+
if (pkg.devDependencies) {
|
|
816
|
+
const devDeps = Object.keys(pkg.devDependencies);
|
|
817
|
+
parts.push(`Dev Dependencies: ${devDeps.slice(0, 15).join(", ")}${devDeps.length > 15 ? "..." : ""}`);
|
|
818
|
+
}
|
|
819
|
+
} catch {
|
|
820
|
+
}
|
|
821
|
+
try {
|
|
822
|
+
const fsp = await import('fs/promises');
|
|
823
|
+
const tsconfigPath = path17.join(projectRoot, "tsconfig.json");
|
|
824
|
+
await fsp.access(tsconfigPath);
|
|
825
|
+
parts.push("Language: TypeScript");
|
|
826
|
+
} catch {
|
|
827
|
+
}
|
|
828
|
+
try {
|
|
829
|
+
const fsp = await import('fs/promises');
|
|
830
|
+
const srcDir = path17.join(projectRoot, "src");
|
|
831
|
+
const entries = await fsp.readdir(srcDir, { withFileTypes: true });
|
|
832
|
+
const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
833
|
+
if (dirs.length > 0) {
|
|
834
|
+
parts.push(`Source structure: src/${dirs.join(", src/")}`);
|
|
835
|
+
}
|
|
836
|
+
} catch {
|
|
837
|
+
}
|
|
838
|
+
return parts.join("\n");
|
|
839
|
+
}
|
|
840
|
+
async function findSpec(store, idOrTitle) {
|
|
841
|
+
if (!idOrTitle) return null;
|
|
842
|
+
const byId = await store.load(idOrTitle);
|
|
843
|
+
if (byId) return byId;
|
|
844
|
+
const all = await store.list();
|
|
845
|
+
const match = all.find(
|
|
846
|
+
(e) => e.id.startsWith(idOrTitle) || e.title.toLowerCase().includes(idOrTitle.toLowerCase())
|
|
847
|
+
);
|
|
848
|
+
if (match) return store.load(match.id);
|
|
849
|
+
return null;
|
|
850
|
+
}
|
|
851
|
+
var activeBuilder, activeTaskStore, activeTaskTracker, activeTaskGraphId;
|
|
852
|
+
var init_sdd = __esm({
|
|
853
|
+
"src/slash-commands/sdd.ts"() {
|
|
854
|
+
activeBuilder = null;
|
|
855
|
+
activeTaskStore = null;
|
|
856
|
+
activeTaskTracker = null;
|
|
857
|
+
activeTaskGraphId = null;
|
|
858
|
+
}
|
|
859
|
+
});
|
|
28
860
|
function normalizeKeys(cfg) {
|
|
29
861
|
if (Array.isArray(cfg.apiKeys) && cfg.apiKeys.length > 0) {
|
|
30
862
|
return cfg.apiKeys.map((k) => ({ ...k }));
|
|
@@ -559,7 +1391,10 @@ async function runWebUI(opts) {
|
|
|
559
1391
|
} catch {
|
|
560
1392
|
return {};
|
|
561
1393
|
}
|
|
562
|
-
|
|
1394
|
+
if (!parsed.providers) return {};
|
|
1395
|
+
const keyFile = path17.join(path17.dirname(opts.globalConfigPath), ".key");
|
|
1396
|
+
const vault = new DefaultSecretVault$1({ keyFile });
|
|
1397
|
+
return decryptConfigSecrets$1(parsed.providers, vault);
|
|
563
1398
|
}
|
|
564
1399
|
async function saveProviders(providers) {
|
|
565
1400
|
if (!opts.globalConfigPath) return;
|
|
@@ -576,7 +1411,7 @@ async function runWebUI(opts) {
|
|
|
576
1411
|
parsed = {};
|
|
577
1412
|
}
|
|
578
1413
|
parsed.providers = providers;
|
|
579
|
-
const keyFile =
|
|
1414
|
+
const keyFile = path17.join(path17.dirname(opts.globalConfigPath), ".key");
|
|
580
1415
|
const vault = new DefaultSecretVault$1({ keyFile });
|
|
581
1416
|
const encrypted = encryptConfigSecrets(parsed, vault);
|
|
582
1417
|
await atomicWrite(opts.globalConfigPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
@@ -728,10 +1563,10 @@ function parseSpawnFlags(input) {
|
|
|
728
1563
|
return { description: rest.trim(), opts };
|
|
729
1564
|
}
|
|
730
1565
|
async function bootConfig(flags) {
|
|
731
|
-
const cwd = typeof flags["cwd"] === "string" ?
|
|
1566
|
+
const cwd = typeof flags["cwd"] === "string" ? path17.resolve(flags["cwd"]) : process.cwd();
|
|
732
1567
|
const pathResolver = new DefaultPathResolver(cwd);
|
|
733
1568
|
const projectRoot = pathResolver.projectRoot;
|
|
734
|
-
const userHome =
|
|
1569
|
+
const userHome = os4.homedir();
|
|
735
1570
|
const wpaths = resolveWstackPaths({ projectRoot, userHome });
|
|
736
1571
|
await ensureProjectMeta(wpaths, projectRoot);
|
|
737
1572
|
const vault = new DefaultSecretVault({ keyFile: wpaths.secretsKey });
|
|
@@ -795,7 +1630,7 @@ var ReadlineInputReader = class {
|
|
|
795
1630
|
history = [];
|
|
796
1631
|
pending = false;
|
|
797
1632
|
constructor(opts = {}) {
|
|
798
|
-
this.historyFile = opts.historyFile ??
|
|
1633
|
+
this.historyFile = opts.historyFile ?? path17.join(os4.homedir(), ".wrongstack", "history");
|
|
799
1634
|
}
|
|
800
1635
|
async loadHistory() {
|
|
801
1636
|
try {
|
|
@@ -807,7 +1642,7 @@ var ReadlineInputReader = class {
|
|
|
807
1642
|
}
|
|
808
1643
|
async saveHistory() {
|
|
809
1644
|
try {
|
|
810
|
-
await fs14.mkdir(
|
|
1645
|
+
await fs14.mkdir(path17.dirname(this.historyFile), { recursive: true });
|
|
811
1646
|
await fs14.writeFile(this.historyFile, this.history.slice(-1e3).join("\n"));
|
|
812
1647
|
} catch {
|
|
813
1648
|
}
|
|
@@ -1289,10 +2124,10 @@ async function detectPackageManager(root, declared) {
|
|
|
1289
2124
|
const name = declared.split("@")[0];
|
|
1290
2125
|
if (name) return name;
|
|
1291
2126
|
}
|
|
1292
|
-
if (await pathExists(
|
|
1293
|
-
if (await pathExists(
|
|
1294
|
-
if (await pathExists(
|
|
1295
|
-
if (await pathExists(
|
|
2127
|
+
if (await pathExists(path17.join(root, "pnpm-lock.yaml"))) return "pnpm";
|
|
2128
|
+
if (await pathExists(path17.join(root, "bun.lockb"))) return "bun";
|
|
2129
|
+
if (await pathExists(path17.join(root, "bun.lock"))) return "bun";
|
|
2130
|
+
if (await pathExists(path17.join(root, "yarn.lock"))) return "yarn";
|
|
1296
2131
|
return "npm";
|
|
1297
2132
|
}
|
|
1298
2133
|
function hasUsableScript(scripts, name) {
|
|
@@ -1313,7 +2148,7 @@ function parseMakeTargets(makefile) {
|
|
|
1313
2148
|
async function detectProjectFacts(root) {
|
|
1314
2149
|
const facts = { hints: [] };
|
|
1315
2150
|
try {
|
|
1316
|
-
const pkg = JSON.parse(await fs14.readFile(
|
|
2151
|
+
const pkg = JSON.parse(await fs14.readFile(path17.join(root, "package.json"), "utf8"));
|
|
1317
2152
|
const scripts = pkg.scripts ?? {};
|
|
1318
2153
|
const pm = await detectPackageManager(root, pkg.packageManager);
|
|
1319
2154
|
if (hasUsableScript(scripts, "build")) facts.build = `${pm} run build`;
|
|
@@ -1327,14 +2162,14 @@ async function detectProjectFacts(root) {
|
|
|
1327
2162
|
} catch {
|
|
1328
2163
|
}
|
|
1329
2164
|
try {
|
|
1330
|
-
if (!await pathExists(
|
|
2165
|
+
if (!await pathExists(path17.join(root, "pyproject.toml"))) throw new Error("not python");
|
|
1331
2166
|
facts.test ??= "pytest";
|
|
1332
2167
|
facts.lint ??= "ruff check .";
|
|
1333
2168
|
facts.hints.push("pyproject.toml");
|
|
1334
2169
|
} catch {
|
|
1335
2170
|
}
|
|
1336
2171
|
try {
|
|
1337
|
-
if (!await pathExists(
|
|
2172
|
+
if (!await pathExists(path17.join(root, "go.mod"))) throw new Error("not go");
|
|
1338
2173
|
facts.build ??= "go build ./...";
|
|
1339
2174
|
facts.test ??= "go test ./...";
|
|
1340
2175
|
facts.run ??= "go run .";
|
|
@@ -1342,7 +2177,7 @@ async function detectProjectFacts(root) {
|
|
|
1342
2177
|
} catch {
|
|
1343
2178
|
}
|
|
1344
2179
|
try {
|
|
1345
|
-
if (!await pathExists(
|
|
2180
|
+
if (!await pathExists(path17.join(root, "Cargo.toml"))) throw new Error("not rust");
|
|
1346
2181
|
facts.build ??= "cargo build";
|
|
1347
2182
|
facts.test ??= "cargo test";
|
|
1348
2183
|
facts.lint ??= "cargo clippy";
|
|
@@ -1351,7 +2186,7 @@ async function detectProjectFacts(root) {
|
|
|
1351
2186
|
} catch {
|
|
1352
2187
|
}
|
|
1353
2188
|
try {
|
|
1354
|
-
const makefile = await fs14.readFile(
|
|
2189
|
+
const makefile = await fs14.readFile(path17.join(root, "Makefile"), "utf8");
|
|
1355
2190
|
const targets = parseMakeTargets(makefile);
|
|
1356
2191
|
facts.build ??= targets.has("build") ? "make build" : "make";
|
|
1357
2192
|
if (targets.has("test")) facts.test ??= "make test";
|
|
@@ -1814,8 +2649,8 @@ function buildInitCommand(opts) {
|
|
|
1814
2649
|
description: "Create .wrongstack/AGENTS.md project context for the system prompt.",
|
|
1815
2650
|
async run(args, ctx) {
|
|
1816
2651
|
const force = args.trim() === "--force";
|
|
1817
|
-
const dir =
|
|
1818
|
-
const file =
|
|
2652
|
+
const dir = path17.join(ctx.projectRoot, ".wrongstack");
|
|
2653
|
+
const file = path17.join(dir, "AGENTS.md");
|
|
1819
2654
|
try {
|
|
1820
2655
|
await fs14.access(file);
|
|
1821
2656
|
if (!force) {
|
|
@@ -2065,7 +2900,7 @@ function buildExitCommand(opts) {
|
|
|
2065
2900
|
function buildSkillCommand(opts) {
|
|
2066
2901
|
return {
|
|
2067
2902
|
name: "skill",
|
|
2068
|
-
description: "Show skill details or list available skills.",
|
|
2903
|
+
description: "Show skill details or list available skills. Use /skill-gen to create new skills.",
|
|
2069
2904
|
async run(args) {
|
|
2070
2905
|
if (!opts.skillLoader) return { message: "No skill loader configured." };
|
|
2071
2906
|
if (!args.trim()) {
|
|
@@ -2203,6 +3038,392 @@ ${lines.join("\n")}
|
|
|
2203
3038
|
}
|
|
2204
3039
|
};
|
|
2205
3040
|
}
|
|
3041
|
+
function buildYoloCommand(opts) {
|
|
3042
|
+
return {
|
|
3043
|
+
name: "yolo",
|
|
3044
|
+
description: "Toggle or query YOLO (auto-approve) mode.",
|
|
3045
|
+
help: [
|
|
3046
|
+
"Usage:",
|
|
3047
|
+
" /yolo Show current YOLO status",
|
|
3048
|
+
" /yolo on Enable YOLO mode (auto-approve every tool call)",
|
|
3049
|
+
" /yolo off Disable YOLO mode (restore permission prompts)",
|
|
3050
|
+
" /yolo toggle Toggle YOLO mode",
|
|
3051
|
+
"",
|
|
3052
|
+
"YOLO mode skips all permission prompts and auto-approves every tool call.",
|
|
3053
|
+
"Use with caution \u2014 the agent can execute any tool without asking."
|
|
3054
|
+
].join("\n"),
|
|
3055
|
+
async run(args) {
|
|
3056
|
+
const arg = args.trim().toLowerCase();
|
|
3057
|
+
if (!opts.onYolo) {
|
|
3058
|
+
const msg2 = "YOLO toggle is not available in this session.";
|
|
3059
|
+
opts.renderer.writeWarning(msg2);
|
|
3060
|
+
return { message: msg2 };
|
|
3061
|
+
}
|
|
3062
|
+
if (!arg) {
|
|
3063
|
+
const current = opts.onYolo();
|
|
3064
|
+
const status = current ? `${color.yellow("ON")} ${color.dim("(auto-approving all tool calls)")}` : `${color.green("OFF")} ${color.dim("(permission prompts active)")}`;
|
|
3065
|
+
const msg2 = `YOLO mode: ${status}`;
|
|
3066
|
+
opts.renderer.write(msg2);
|
|
3067
|
+
return { message: msg2 };
|
|
3068
|
+
}
|
|
3069
|
+
let newState;
|
|
3070
|
+
if (arg === "on" || arg === "enable" || arg === "true" || arg === "1") {
|
|
3071
|
+
newState = true;
|
|
3072
|
+
} else if (arg === "off" || arg === "disable" || arg === "false" || arg === "0") {
|
|
3073
|
+
newState = false;
|
|
3074
|
+
} else if (arg === "toggle") {
|
|
3075
|
+
newState = !opts.onYolo();
|
|
3076
|
+
} else {
|
|
3077
|
+
const msg2 = `Unknown argument: ${arg}. Use /yolo on, /yolo off, or /yolo toggle.`;
|
|
3078
|
+
opts.renderer.writeWarning(msg2);
|
|
3079
|
+
return { message: msg2 };
|
|
3080
|
+
}
|
|
3081
|
+
opts.onYolo(newState);
|
|
3082
|
+
const label = newState ? `${color.yellow("ENABLED")} \u2014 all tool calls will be auto-approved` : `${color.green("DISABLED")} \u2014 permission prompts are active`;
|
|
3083
|
+
const msg = `YOLO mode: ${label}`;
|
|
3084
|
+
opts.renderer.write(msg);
|
|
3085
|
+
return { message: msg };
|
|
3086
|
+
}
|
|
3087
|
+
};
|
|
3088
|
+
}
|
|
3089
|
+
function buildAutonomyCommand(opts) {
|
|
3090
|
+
return {
|
|
3091
|
+
name: "autonomy",
|
|
3092
|
+
description: "Toggle or query autonomy mode (self-driving agent).",
|
|
3093
|
+
help: [
|
|
3094
|
+
"Usage:",
|
|
3095
|
+
" /autonomy Show current autonomy status",
|
|
3096
|
+
" /autonomy off Disabled \u2014 agent stops after each turn (default)",
|
|
3097
|
+
" /autonomy suggest Show next-step suggestions after each turn",
|
|
3098
|
+
" /autonomy on Auto-continue \u2014 agent picks next step and proceeds",
|
|
3099
|
+
" /autonomy toggle Cycle: off \u2192 suggest \u2192 auto \u2192 off",
|
|
3100
|
+
"",
|
|
3101
|
+
"Modes:",
|
|
3102
|
+
" off \u2014 Normal interactive mode. Agent stops and waits.",
|
|
3103
|
+
" suggest \u2014 After each turn, agent suggests next steps. You pick.",
|
|
3104
|
+
" auto \u2014 After each turn, agent picks the best next step and continues.",
|
|
3105
|
+
" Runs indefinitely until you press Esc or Ctrl+C.",
|
|
3106
|
+
"",
|
|
3107
|
+
"In auto mode the agent works autonomously. Press Esc to redirect,",
|
|
3108
|
+
"Ctrl+C to stop. The agent suggests context-aware next steps based on",
|
|
3109
|
+
"the conversation history."
|
|
3110
|
+
].join("\n"),
|
|
3111
|
+
async run(args) {
|
|
3112
|
+
const arg = args.trim().toLowerCase();
|
|
3113
|
+
if (!opts.onAutonomy) {
|
|
3114
|
+
const msg2 = "Autonomy mode is not available in this session.";
|
|
3115
|
+
opts.renderer.writeWarning(msg2);
|
|
3116
|
+
return { message: msg2 };
|
|
3117
|
+
}
|
|
3118
|
+
if (!arg) {
|
|
3119
|
+
const current = opts.onAutonomy();
|
|
3120
|
+
const labels2 = {
|
|
3121
|
+
off: `${color.green("OFF")} ${color.dim("(agent stops after each turn)")}`,
|
|
3122
|
+
suggest: `${color.cyan("SUGGEST")} ${color.dim("(shows next-step suggestions)")}`,
|
|
3123
|
+
auto: `${color.yellow("AUTO")} ${color.dim("(self-driving \u2014 Esc to redirect, Ctrl+C to stop)")}`
|
|
3124
|
+
};
|
|
3125
|
+
const msg2 = `Autonomy mode: ${labels2[current]}`;
|
|
3126
|
+
opts.renderer.write(msg2);
|
|
3127
|
+
return { message: msg2 };
|
|
3128
|
+
}
|
|
3129
|
+
let newMode;
|
|
3130
|
+
if (arg === "on" || arg === "enable" || arg === "true" || arg === "auto") {
|
|
3131
|
+
newMode = "auto";
|
|
3132
|
+
} else if (arg === "off" || arg === "disable" || arg === "false") {
|
|
3133
|
+
newMode = "off";
|
|
3134
|
+
} else if (arg === "suggest" || arg === "suggestions") {
|
|
3135
|
+
newMode = "suggest";
|
|
3136
|
+
} else if (arg === "toggle" || arg === "cycle") {
|
|
3137
|
+
const current = opts.onAutonomy() ?? "off";
|
|
3138
|
+
const cycle = ["off", "suggest", "auto"];
|
|
3139
|
+
newMode = cycle[(cycle.indexOf(current) + 1) % cycle.length] ?? "off";
|
|
3140
|
+
} else {
|
|
3141
|
+
const msg2 = `Unknown argument: ${arg}. Use /autonomy on, /autonomy off, /autonomy suggest, or /autonomy toggle.`;
|
|
3142
|
+
opts.renderer.writeWarning(msg2);
|
|
3143
|
+
return { message: msg2 };
|
|
3144
|
+
}
|
|
3145
|
+
opts.onAutonomy(newMode);
|
|
3146
|
+
const labels = {
|
|
3147
|
+
off: `${color.green("OFF")} \u2014 agent stops after each turn`,
|
|
3148
|
+
suggest: `${color.cyan("SUGGEST")} \u2014 shows next-step suggestions after each turn`,
|
|
3149
|
+
auto: `${color.yellow("AUTO")} \u2014 self-driving, agent continues automatically`
|
|
3150
|
+
};
|
|
3151
|
+
const msg = `Autonomy mode: ${labels[newMode]}`;
|
|
3152
|
+
opts.renderer.write(msg);
|
|
3153
|
+
return { message: msg };
|
|
3154
|
+
}
|
|
3155
|
+
};
|
|
3156
|
+
}
|
|
3157
|
+
|
|
3158
|
+
// src/slash-commands/mode.ts
|
|
3159
|
+
function buildModeCommand(opts) {
|
|
3160
|
+
return {
|
|
3161
|
+
name: "mode",
|
|
3162
|
+
description: "Switch or view the current mode",
|
|
3163
|
+
help: [
|
|
3164
|
+
"Usage:",
|
|
3165
|
+
" /mode Show current mode and available modes",
|
|
3166
|
+
" /mode <id> Switch to a different mode",
|
|
3167
|
+
"",
|
|
3168
|
+
"Available modes:",
|
|
3169
|
+
" default General-purpose coding assistant",
|
|
3170
|
+
" brief Fast, no-nonsense \u2014 get to the point",
|
|
3171
|
+
" teach Mentor mode \u2014 explains why, not just what",
|
|
3172
|
+
" code-reviewer, code-auditor, architect, debugger, tester, devops, refactorer",
|
|
3173
|
+
"",
|
|
3174
|
+
"Example:",
|
|
3175
|
+
" /mode brief Switch to brief mode",
|
|
3176
|
+
" /mode teach Switch to teach mode"
|
|
3177
|
+
].join("\n"),
|
|
3178
|
+
async run(args) {
|
|
3179
|
+
const modeStore = opts.modeStore;
|
|
3180
|
+
if (!modeStore) {
|
|
3181
|
+
return { message: "Mode store not available in this context." };
|
|
3182
|
+
}
|
|
3183
|
+
const modes = await modeStore.listModes();
|
|
3184
|
+
const active = await modeStore.getActiveMode();
|
|
3185
|
+
if (!args.trim()) {
|
|
3186
|
+
const lines = [`Current mode: ${active?.name ?? "none"}`, "", "Available modes:"];
|
|
3187
|
+
for (const m of modes) {
|
|
3188
|
+
const mark = m.id === active?.id ? " [active]" : "";
|
|
3189
|
+
lines.push(` ${m.id} \u2014 ${m.description}${mark}`);
|
|
3190
|
+
}
|
|
3191
|
+
return { message: lines.join("\n") };
|
|
3192
|
+
}
|
|
3193
|
+
const target = args.trim().toLowerCase();
|
|
3194
|
+
const targetMode = modes.find((m) => m.id === target);
|
|
3195
|
+
if (!targetMode) {
|
|
3196
|
+
const available = modes.map((m) => m.id).join(", ");
|
|
3197
|
+
return { message: `Unknown mode "${target}". Available: ${available}` };
|
|
3198
|
+
}
|
|
3199
|
+
await modeStore.setActiveMode(targetMode.id);
|
|
3200
|
+
return {
|
|
3201
|
+
message: `Switched to "${targetMode.name}" mode.
|
|
3202
|
+
${targetMode.description}`
|
|
3203
|
+
};
|
|
3204
|
+
}
|
|
3205
|
+
};
|
|
3206
|
+
}
|
|
3207
|
+
|
|
3208
|
+
// src/slash-commands/index.ts
|
|
3209
|
+
init_sdd();
|
|
3210
|
+
|
|
3211
|
+
// src/slash-commands/skill-generator.ts
|
|
3212
|
+
function buildSkillGeneratorCommand(opts) {
|
|
3213
|
+
return {
|
|
3214
|
+
name: "skill-gen",
|
|
3215
|
+
description: "Create a new AI skill interactively. The AI will guide you.",
|
|
3216
|
+
help: [
|
|
3217
|
+
"\u2554\u2550\u2550\u2550 Skill Generator \u2550\u2550\u2550\u2557",
|
|
3218
|
+
"",
|
|
3219
|
+
"Create new AI skills with AI guidance.",
|
|
3220
|
+
"",
|
|
3221
|
+
"Usage:",
|
|
3222
|
+
" /skill-gen Start skill creation",
|
|
3223
|
+
" /skill-gen list List existing skills",
|
|
3224
|
+
" /skill-gen edit <name> View an existing skill",
|
|
3225
|
+
"",
|
|
3226
|
+
"The AI will ask you questions and create the skill file.",
|
|
3227
|
+
"Skills are saved to .wrongstack/skills/<name>/SKILL.md"
|
|
3228
|
+
].join("\n"),
|
|
3229
|
+
async run(args) {
|
|
3230
|
+
const trimmed = args.trim();
|
|
3231
|
+
if (trimmed === "list" || trimmed === "ls") {
|
|
3232
|
+
if (!opts.skillLoader) return { message: "No skill loader configured." };
|
|
3233
|
+
const entries = await opts.skillLoader.listEntries();
|
|
3234
|
+
if (entries.length === 0) return { message: "No skills found." };
|
|
3235
|
+
const lines = entries.map((e) => {
|
|
3236
|
+
const src = e.source === "project" ? "\u{1F4C1}" : e.source === "user" ? "\u{1F464}" : "\u{1F4E6}";
|
|
3237
|
+
return ` ${src} ${e.name}
|
|
3238
|
+
${e.trigger}`;
|
|
3239
|
+
});
|
|
3240
|
+
return { message: `Available Skills:
|
|
3241
|
+
${lines.join("\n\n")}
|
|
3242
|
+
` };
|
|
3243
|
+
}
|
|
3244
|
+
if (trimmed.startsWith("edit ")) {
|
|
3245
|
+
const skillName = trimmed.slice(5).trim();
|
|
3246
|
+
if (!opts.skillLoader) return { message: "No skill loader configured." };
|
|
3247
|
+
const skill = await opts.skillLoader.find(skillName);
|
|
3248
|
+
if (!skill) return { message: `Skill "${skillName}" not found.` };
|
|
3249
|
+
const body = await opts.skillLoader.readBody(skillName);
|
|
3250
|
+
return {
|
|
3251
|
+
message: [
|
|
3252
|
+
`Skill: ${skillName}`,
|
|
3253
|
+
`Path: ${skill.path}`,
|
|
3254
|
+
"",
|
|
3255
|
+
body
|
|
3256
|
+
].join("\n")
|
|
3257
|
+
};
|
|
3258
|
+
}
|
|
3259
|
+
return {
|
|
3260
|
+
message: "\u2554\u2550\u2550\u2550 Skill Generator \u2550\u2550\u2550\u2557\n\nThe AI will guide you through creating a new skill.\nAnswer its questions naturally.",
|
|
3261
|
+
runText: "I want to create a new AI skill. Read the skill-creator skill and guide me through the process. Ask me questions one at a time \u2014 name, description, what to cover \u2014 then create the SKILL.md file."
|
|
3262
|
+
};
|
|
3263
|
+
}
|
|
3264
|
+
};
|
|
3265
|
+
}
|
|
3266
|
+
function makeInstaller(opts, projectRoot, global) {
|
|
3267
|
+
const globalRoot = path17.join(os4.homedir(), ".wrongstack");
|
|
3268
|
+
return new SkillInstaller({
|
|
3269
|
+
manifestPath: path17.join(globalRoot, "installed-skills.json"),
|
|
3270
|
+
projectSkillsDir: path17.join(projectRoot, ".wrongstack", "skills"),
|
|
3271
|
+
globalSkillsDir: path17.join(globalRoot, "skills"),
|
|
3272
|
+
projectHash: projectHash(projectRoot),
|
|
3273
|
+
skillLoader: opts.skillLoader
|
|
3274
|
+
});
|
|
3275
|
+
}
|
|
3276
|
+
function buildSkillInstallCommand(opts) {
|
|
3277
|
+
return {
|
|
3278
|
+
name: "skill-install",
|
|
3279
|
+
description: "Install skills from a GitHub repository.",
|
|
3280
|
+
argsHint: "<user/repo[@ref]> [--global]",
|
|
3281
|
+
help: [
|
|
3282
|
+
"\u2554\u2550\u2550\u2550 Skill Install \u2550\u2550\u2550\u2557",
|
|
3283
|
+
"",
|
|
3284
|
+
"Install skills from a GitHub repository.",
|
|
3285
|
+
"",
|
|
3286
|
+
"Usage:",
|
|
3287
|
+
" /skill-install <user/repo> Install from default branch (main)",
|
|
3288
|
+
" /skill-install <user/repo@ref> Install specific tag/branch/commit",
|
|
3289
|
+
" /skill-install <user/repo> --global Install to user-global skills",
|
|
3290
|
+
"",
|
|
3291
|
+
"Supports both single-skill repos (SKILL.md at root)",
|
|
3292
|
+
"and multi-skill repos (skills/ subdirectory).",
|
|
3293
|
+
"",
|
|
3294
|
+
"Examples:",
|
|
3295
|
+
" /skill-install wrongstack/awesome-skills",
|
|
3296
|
+
" /skill-install wrongstack/skills@v1.0",
|
|
3297
|
+
" /skill-install user/my-skills --global"
|
|
3298
|
+
].join("\n"),
|
|
3299
|
+
async run(args, ctx) {
|
|
3300
|
+
const parts = args.trim().split(/\s+/);
|
|
3301
|
+
const ref = parts.find((p) => !p.startsWith("--"));
|
|
3302
|
+
const isGlobal = parts.includes("--global");
|
|
3303
|
+
if (!ref) {
|
|
3304
|
+
return { message: "Usage: /skill-install <user/repo[@ref]> [--global]" };
|
|
3305
|
+
}
|
|
3306
|
+
const installer = makeInstaller(opts, ctx.projectRoot);
|
|
3307
|
+
try {
|
|
3308
|
+
const results = await installer.install(ref, { global: isGlobal });
|
|
3309
|
+
if (results.length === 0) {
|
|
3310
|
+
return { message: "No skills found in the repository." };
|
|
3311
|
+
}
|
|
3312
|
+
const scope = isGlobal ? "user-global" : "project";
|
|
3313
|
+
const lines = [`Installed ${results.length} skill(s) [${scope}]:`];
|
|
3314
|
+
for (const r of results) {
|
|
3315
|
+
lines.push(` \u2713 ${r.name} (${r.source}@${r.ref})`);
|
|
3316
|
+
lines.push(` \u2192 ${r.path}`);
|
|
3317
|
+
}
|
|
3318
|
+
return { message: lines.join("\n") };
|
|
3319
|
+
} catch (err) {
|
|
3320
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3321
|
+
opts.renderer.writeError(`Install failed: ${msg}`);
|
|
3322
|
+
return { message: `\u2717 Install failed: ${msg}` };
|
|
3323
|
+
}
|
|
3324
|
+
}
|
|
3325
|
+
};
|
|
3326
|
+
}
|
|
3327
|
+
function buildSkillUpdateCommand(opts) {
|
|
3328
|
+
return {
|
|
3329
|
+
name: "skill-update",
|
|
3330
|
+
description: "Update installed skills from their GitHub source.",
|
|
3331
|
+
argsHint: "[name|ref] [--global]",
|
|
3332
|
+
help: [
|
|
3333
|
+
"\u2554\u2550\u2550\u2550 Skill Update \u2550\u2550\u2550\u2557",
|
|
3334
|
+
"",
|
|
3335
|
+
"Update installed skills from their GitHub source.",
|
|
3336
|
+
"",
|
|
3337
|
+
"Usage:",
|
|
3338
|
+
" /skill-update Update all installed skills",
|
|
3339
|
+
" /skill-update <name> Update a specific skill",
|
|
3340
|
+
" /skill-update <user/repo@ref> Update to a different ref",
|
|
3341
|
+
" /skill-update <name> --global Update a global skill"
|
|
3342
|
+
].join("\n"),
|
|
3343
|
+
async run(args, ctx) {
|
|
3344
|
+
const parts = args.trim().split(/\s+/);
|
|
3345
|
+
const nameOrRef = parts.find((p) => !p.startsWith("--"));
|
|
3346
|
+
const isGlobal = parts.includes("--global");
|
|
3347
|
+
const installer = makeInstaller(opts, ctx.projectRoot);
|
|
3348
|
+
try {
|
|
3349
|
+
const result = await installer.update(nameOrRef, { global: isGlobal });
|
|
3350
|
+
const lines = [];
|
|
3351
|
+
if (result.updated.length > 0) {
|
|
3352
|
+
lines.push(`Updated ${result.updated.length} skill(s):`);
|
|
3353
|
+
for (const u of result.updated) {
|
|
3354
|
+
if (u.oldRef !== u.newRef) {
|
|
3355
|
+
lines.push(` \u2713 ${u.name} (${u.oldRef} \u2192 ${u.newRef})`);
|
|
3356
|
+
} else {
|
|
3357
|
+
lines.push(` \u2713 ${u.name} (refreshed)`);
|
|
3358
|
+
}
|
|
3359
|
+
}
|
|
3360
|
+
}
|
|
3361
|
+
if (result.unchanged.length > 0) {
|
|
3362
|
+
lines.push(`Up to date: ${result.unchanged.join(", ")}`);
|
|
3363
|
+
}
|
|
3364
|
+
if (result.errors.length > 0) {
|
|
3365
|
+
for (const e of result.errors) {
|
|
3366
|
+
lines.push(` \u2717 ${e.name}: ${e.error}`);
|
|
3367
|
+
}
|
|
3368
|
+
}
|
|
3369
|
+
if (lines.length === 0) {
|
|
3370
|
+
return { message: "No installed skills to update." };
|
|
3371
|
+
}
|
|
3372
|
+
return { message: lines.join("\n") };
|
|
3373
|
+
} catch (err) {
|
|
3374
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3375
|
+
return { message: `\u2717 Update failed: ${msg}` };
|
|
3376
|
+
}
|
|
3377
|
+
}
|
|
3378
|
+
};
|
|
3379
|
+
}
|
|
3380
|
+
function buildSkillUninstallCommand(opts) {
|
|
3381
|
+
return {
|
|
3382
|
+
name: "skill-uninstall",
|
|
3383
|
+
description: "Remove an installed skill.",
|
|
3384
|
+
argsHint: "<name> [--global]",
|
|
3385
|
+
help: [
|
|
3386
|
+
"\u2554\u2550\u2550\u2550 Skill Uninstall \u2550\u2550\u2550\u2557",
|
|
3387
|
+
"",
|
|
3388
|
+
"Remove an installed skill and its files.",
|
|
3389
|
+
"",
|
|
3390
|
+
"Usage:",
|
|
3391
|
+
" /skill-uninstall <name> Remove from project skills",
|
|
3392
|
+
" /skill-uninstall <name> --global Remove from user-global skills"
|
|
3393
|
+
].join("\n"),
|
|
3394
|
+
async run(args, ctx) {
|
|
3395
|
+
const parts = args.trim().split(/\s+/);
|
|
3396
|
+
const name = parts.find((p) => !p.startsWith("--"));
|
|
3397
|
+
const isGlobal = parts.includes("--global");
|
|
3398
|
+
if (!name) {
|
|
3399
|
+
const installer2 = makeInstaller(opts, ctx.projectRoot);
|
|
3400
|
+
const installed = await installer2.listInstalled();
|
|
3401
|
+
if (installed.length === 0) {
|
|
3402
|
+
return { message: "No installed skills found." };
|
|
3403
|
+
}
|
|
3404
|
+
const scope = isGlobal ? "user" : "project";
|
|
3405
|
+
const filtered = installed.filter((s) => s.scope === scope);
|
|
3406
|
+
if (filtered.length === 0) {
|
|
3407
|
+
return { message: `No installed skills found (${scope} scope).` };
|
|
3408
|
+
}
|
|
3409
|
+
const lines = [`Installed skills (${scope}):`];
|
|
3410
|
+
for (const s of filtered) {
|
|
3411
|
+
lines.push(` ${s.name} ${s.source}@${s.ref} (${s.installedAt.slice(0, 10)})`);
|
|
3412
|
+
}
|
|
3413
|
+
lines.push("", "Use /skill-uninstall <name> to remove.");
|
|
3414
|
+
return { message: lines.join("\n") };
|
|
3415
|
+
}
|
|
3416
|
+
const installer = makeInstaller(opts, ctx.projectRoot);
|
|
3417
|
+
try {
|
|
3418
|
+
await installer.uninstall(name, { global: isGlobal });
|
|
3419
|
+
return { message: `\u2713 Skill "${name}" uninstalled.` };
|
|
3420
|
+
} catch (err) {
|
|
3421
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3422
|
+
return { message: `\u2717 Uninstall failed: ${msg}` };
|
|
3423
|
+
}
|
|
3424
|
+
}
|
|
3425
|
+
};
|
|
3426
|
+
}
|
|
2206
3427
|
|
|
2207
3428
|
// src/slash-commands/index.ts
|
|
2208
3429
|
function buildBuiltinSlashCommands(opts) {
|
|
@@ -2214,6 +3435,10 @@ function buildBuiltinSlashCommands(opts) {
|
|
|
2214
3435
|
buildContextCommand(opts),
|
|
2215
3436
|
buildToolsCommand(opts),
|
|
2216
3437
|
buildSkillCommand(opts),
|
|
3438
|
+
buildSkillGeneratorCommand(opts),
|
|
3439
|
+
buildSkillInstallCommand(opts),
|
|
3440
|
+
buildSkillUpdateCommand(opts),
|
|
3441
|
+
buildSkillUninstallCommand(opts),
|
|
2217
3442
|
buildPluginCommand(opts),
|
|
2218
3443
|
buildDiagCommand(opts),
|
|
2219
3444
|
buildStatsCommand(opts),
|
|
@@ -2226,8 +3451,12 @@ function buildBuiltinSlashCommands(opts) {
|
|
|
2226
3451
|
buildMemoryCommand(opts),
|
|
2227
3452
|
buildTodosCommand(opts),
|
|
2228
3453
|
buildPlanCommand(opts),
|
|
3454
|
+
buildSddCommand(opts),
|
|
2229
3455
|
buildSaveCommand(opts),
|
|
2230
3456
|
buildLoadCommand(opts),
|
|
3457
|
+
buildYoloCommand(opts),
|
|
3458
|
+
buildAutonomyCommand(opts),
|
|
3459
|
+
buildModeCommand(opts),
|
|
2231
3460
|
buildExitCommand(opts)
|
|
2232
3461
|
];
|
|
2233
3462
|
}
|
|
@@ -2247,13 +3476,13 @@ var MANIFESTS = [
|
|
|
2247
3476
|
];
|
|
2248
3477
|
async function detectProjectKind(projectRoot) {
|
|
2249
3478
|
try {
|
|
2250
|
-
await fs14.access(
|
|
3479
|
+
await fs14.access(path17.join(projectRoot, ".wrongstack", "AGENTS.md"));
|
|
2251
3480
|
return "initialized";
|
|
2252
3481
|
} catch {
|
|
2253
3482
|
}
|
|
2254
3483
|
for (const m of MANIFESTS) {
|
|
2255
3484
|
try {
|
|
2256
|
-
await fs14.access(
|
|
3485
|
+
await fs14.access(path17.join(projectRoot, m));
|
|
2257
3486
|
return "project";
|
|
2258
3487
|
} catch {
|
|
2259
3488
|
}
|
|
@@ -2261,8 +3490,8 @@ async function detectProjectKind(projectRoot) {
|
|
|
2261
3490
|
return "empty";
|
|
2262
3491
|
}
|
|
2263
3492
|
async function scaffoldAgentsMd(projectRoot) {
|
|
2264
|
-
const dir =
|
|
2265
|
-
const file =
|
|
3493
|
+
const dir = path17.join(projectRoot, ".wrongstack");
|
|
3494
|
+
const file = path17.join(dir, "AGENTS.md");
|
|
2266
3495
|
const facts = await detectProjectFacts(projectRoot);
|
|
2267
3496
|
const body = renderAgentsTemplate(facts);
|
|
2268
3497
|
await fs14.mkdir(dir, { recursive: true });
|
|
@@ -2275,7 +3504,7 @@ async function runProjectCheck(opts) {
|
|
|
2275
3504
|
if (kind === "initialized") {
|
|
2276
3505
|
renderer.write(
|
|
2277
3506
|
`
|
|
2278
|
-
${color.green("\u2713")} Project initialized ${color.dim(`(${
|
|
3507
|
+
${color.green("\u2713")} Project initialized ${color.dim(`(${path17.join(projectRoot, ".wrongstack", "AGENTS.md")})`)}
|
|
2279
3508
|
`
|
|
2280
3509
|
);
|
|
2281
3510
|
return true;
|
|
@@ -2331,9 +3560,9 @@ async function runLaunchPrompts(opts) {
|
|
|
2331
3560
|
yolo = yoloPinned;
|
|
2332
3561
|
} else {
|
|
2333
3562
|
const answer = (await reader.readLine(
|
|
2334
|
-
` ${color.amber("?")} YOLO mode ${color.dim("(auto-approve every tool call)")} ${color.dim("[
|
|
3563
|
+
` ${color.amber("?")} YOLO mode ${color.dim("(auto-approve every tool call)")} ${color.dim("[Y/n]")} `
|
|
2335
3564
|
)).trim().toLowerCase();
|
|
2336
|
-
yolo = answer
|
|
3565
|
+
yolo = answer !== "n" && answer !== "no";
|
|
2337
3566
|
}
|
|
2338
3567
|
renderer.write(
|
|
2339
3568
|
`
|
|
@@ -2571,14 +3800,14 @@ function summarize(value, name) {
|
|
|
2571
3800
|
if (typeof v === "object" && v !== null) {
|
|
2572
3801
|
const o = v;
|
|
2573
3802
|
if (name === "edit") {
|
|
2574
|
-
const
|
|
3803
|
+
const path18 = typeof o["path"] === "string" ? o["path"] : "";
|
|
2575
3804
|
const reps = typeof o["replacements"] === "number" ? o["replacements"] : 0;
|
|
2576
|
-
return `${
|
|
3805
|
+
return `${path18} ${reps} replacement${reps === 1 ? "" : "s"}`.trim();
|
|
2577
3806
|
}
|
|
2578
3807
|
if (name === "write") {
|
|
2579
|
-
const
|
|
3808
|
+
const path18 = typeof o["path"] === "string" ? o["path"] : "";
|
|
2580
3809
|
const bytes = typeof o["bytes"] === "number" ? o["bytes"] : void 0;
|
|
2581
|
-
return bytes !== void 0 ? `${
|
|
3810
|
+
return bytes !== void 0 ? `${path18} ${bytes}B` : path18;
|
|
2582
3811
|
}
|
|
2583
3812
|
if (typeof o["count"] === "number") {
|
|
2584
3813
|
return `${o["count"]} match${o["count"] === 1 ? "" : "es"}`;
|
|
@@ -3262,7 +4491,7 @@ var diagCmd = async (_args, deps) => {
|
|
|
3262
4491
|
` modelsCache: ${deps.paths.modelsCache}`,
|
|
3263
4492
|
` cacheAge: ${isFinite(age) ? `${Math.round(age / 60)}m` : "never"}`,
|
|
3264
4493
|
` node: ${process.version}`,
|
|
3265
|
-
` os: ${
|
|
4494
|
+
` os: ${os4.platform()} ${os4.release()}`,
|
|
3266
4495
|
` provider: ${cfg.provider ?? "<unset>"}`,
|
|
3267
4496
|
` model: ${cfg.model ?? "<unset>"}`,
|
|
3268
4497
|
` tools: ${deps.toolRegistry?.list().length ?? 0}`,
|
|
@@ -3341,7 +4570,7 @@ var doctorCmd = async (_args, deps) => {
|
|
|
3341
4570
|
}
|
|
3342
4571
|
try {
|
|
3343
4572
|
await fs14.mkdir(deps.paths.projectSessions, { recursive: true });
|
|
3344
|
-
const probe =
|
|
4573
|
+
const probe = path17.join(deps.paths.projectSessions, `.probe-${Date.now()}`);
|
|
3345
4574
|
await fs14.writeFile(probe, "");
|
|
3346
4575
|
await fs14.unlink(probe);
|
|
3347
4576
|
checks.push({ name: "sessions writable", status: "ok", detail: deps.paths.projectSessions });
|
|
@@ -3444,8 +4673,8 @@ var exportCmd = async (args, deps) => {
|
|
|
3444
4673
|
return 1;
|
|
3445
4674
|
}
|
|
3446
4675
|
if (output) {
|
|
3447
|
-
await fs14.mkdir(
|
|
3448
|
-
await fs14.writeFile(
|
|
4676
|
+
await fs14.mkdir(path17.dirname(path17.resolve(deps.cwd, output)), { recursive: true });
|
|
4677
|
+
await fs14.writeFile(path17.resolve(deps.cwd, output), rendered, "utf8");
|
|
3449
4678
|
deps.renderer.write(`Wrote ${rendered.length} bytes to ${output}
|
|
3450
4679
|
`);
|
|
3451
4680
|
} else {
|
|
@@ -3505,12 +4734,12 @@ var initCmd = async (_args, deps) => {
|
|
|
3505
4734
|
await fs14.mkdir(deps.paths.globalRoot, { recursive: true });
|
|
3506
4735
|
const config = { version: 1, provider: providerId, model: modelId };
|
|
3507
4736
|
if (apiKey) config.apiKey = apiKey;
|
|
3508
|
-
const keyFile =
|
|
4737
|
+
const keyFile = path17.join(path17.dirname(deps.paths.globalConfig), ".key");
|
|
3509
4738
|
const vault = new DefaultSecretVault$1({ keyFile });
|
|
3510
4739
|
const encrypted = encryptConfigSecrets(config, vault);
|
|
3511
4740
|
await atomicWrite(deps.paths.globalConfig, JSON.stringify(encrypted, null, 2));
|
|
3512
|
-
await fs14.mkdir(
|
|
3513
|
-
const agentsFile =
|
|
4741
|
+
await fs14.mkdir(path17.join(deps.projectRoot, ".wrongstack"), { recursive: true });
|
|
4742
|
+
const agentsFile = path17.join(deps.projectRoot, ".wrongstack", "AGENTS.md");
|
|
3514
4743
|
try {
|
|
3515
4744
|
await fs14.access(agentsFile);
|
|
3516
4745
|
} catch {
|
|
@@ -3820,7 +5049,7 @@ var usageCmd = async (_args, deps) => {
|
|
|
3820
5049
|
return 0;
|
|
3821
5050
|
};
|
|
3822
5051
|
var projectsCmd = async (_args, deps) => {
|
|
3823
|
-
const projectsRoot =
|
|
5052
|
+
const projectsRoot = path17.join(deps.paths.globalRoot, "projects");
|
|
3824
5053
|
try {
|
|
3825
5054
|
const entries = await fs14.readdir(projectsRoot);
|
|
3826
5055
|
if (entries.length === 0) {
|
|
@@ -3830,7 +5059,7 @@ var projectsCmd = async (_args, deps) => {
|
|
|
3830
5059
|
for (const hash of entries) {
|
|
3831
5060
|
try {
|
|
3832
5061
|
const meta = JSON.parse(
|
|
3833
|
-
await fs14.readFile(
|
|
5062
|
+
await fs14.readFile(path17.join(projectsRoot, hash, "meta.json"), "utf8")
|
|
3834
5063
|
);
|
|
3835
5064
|
deps.renderer.write(
|
|
3836
5065
|
` ${color.dim(hash)} ${color.dim(meta.lastSeen ?? "")} ${meta.root ?? "?"}
|
|
@@ -4029,7 +5258,7 @@ var skillsCmd = async (_args, deps) => {
|
|
|
4029
5258
|
};
|
|
4030
5259
|
var versionCmd = async (_args, deps) => {
|
|
4031
5260
|
deps.renderer.write(
|
|
4032
|
-
`WrongStack ${CLI_VERSION} (apiVersion ${API_VERSION}, node ${process.version}, ${
|
|
5261
|
+
`WrongStack ${CLI_VERSION} (apiVersion ${API_VERSION}, node ${process.version}, ${os4.platform()})
|
|
4033
5262
|
`
|
|
4034
5263
|
);
|
|
4035
5264
|
return 0;
|
|
@@ -4105,31 +5334,29 @@ function fmtDuration(ms) {
|
|
|
4105
5334
|
const remMin = m - h * 60;
|
|
4106
5335
|
return `${h}h${remMin}m`;
|
|
4107
5336
|
}
|
|
4108
|
-
function fmtTaskResultLine(r,
|
|
5337
|
+
function fmtTaskResultLine(r, color30) {
|
|
4109
5338
|
const stats = `${r.iterations}it ${r.toolCalls}tc ${fmtDuration(r.durationMs)}`;
|
|
4110
5339
|
const errMsg = typeof r.error === "string" ? r.error : r.error?.message;
|
|
4111
5340
|
const errKind = typeof r.error === "object" ? r.error?.kind : void 0;
|
|
4112
5341
|
const errTail = errMsg ? ` \u2014 ${errMsg.replace(/\s+/g, " ").slice(0, 80)}${errMsg.length > 80 ? "\u2026" : ""}` : "";
|
|
4113
|
-
const errKindChip = errKind ?
|
|
4114
|
-
const errSnip = errMsg || errKind ? `${errKindChip}${
|
|
5342
|
+
const errKindChip = errKind ? color30.dim(` [${errKind}]`) : "";
|
|
5343
|
+
const errSnip = errMsg || errKind ? `${errKindChip}${color30.dim(errTail)}` : "";
|
|
4115
5344
|
switch (r.status) {
|
|
4116
5345
|
case "success":
|
|
4117
|
-
return { mark:
|
|
5346
|
+
return { mark: color30.green("\u2713"), stats, tail: "" };
|
|
4118
5347
|
case "timeout":
|
|
4119
|
-
return { mark:
|
|
5348
|
+
return { mark: color30.yellow("\u23F1"), stats: `${color30.yellow("timeout")} ${stats}`, tail: errSnip };
|
|
4120
5349
|
case "stopped":
|
|
4121
|
-
return { mark:
|
|
5350
|
+
return { mark: color30.dim("\u2298"), stats: `${color30.dim("stopped")} ${stats}`, tail: errSnip };
|
|
4122
5351
|
case "failed":
|
|
4123
|
-
return { mark:
|
|
5352
|
+
return { mark: color30.red("\u2717"), stats: `${color30.red("failed")} ${stats}`, tail: errSnip };
|
|
4124
5353
|
}
|
|
4125
5354
|
}
|
|
4126
|
-
|
|
4127
|
-
// src/boot.ts
|
|
4128
5355
|
function resolveBundledSkillsDir() {
|
|
4129
5356
|
try {
|
|
4130
5357
|
const req2 = createRequire(import.meta.url);
|
|
4131
5358
|
const corePkg = req2.resolve("@wrongstack/core/package.json");
|
|
4132
|
-
return
|
|
5359
|
+
return path17.join(path17.dirname(corePkg), "skills");
|
|
4133
5360
|
} catch {
|
|
4134
5361
|
return void 0;
|
|
4135
5362
|
}
|
|
@@ -4160,11 +5387,15 @@ async function boot(argv) {
|
|
|
4160
5387
|
});
|
|
4161
5388
|
const first = positional[0];
|
|
4162
5389
|
if (first && subcommands[first]) {
|
|
4163
|
-
const
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
5390
|
+
const container = createDefaultContainer({
|
|
5391
|
+
config,
|
|
5392
|
+
wpaths,
|
|
5393
|
+
logger,
|
|
5394
|
+
modelsRegistry,
|
|
5395
|
+
bundledSkillsDir: config.features.skills ? resolveBundledSkillsDir() : void 0
|
|
4167
5396
|
});
|
|
5397
|
+
const sessionStore = container.resolve(TOKENS.SessionStore);
|
|
5398
|
+
const skillLoader = container.resolve(TOKENS.SkillLoader);
|
|
4168
5399
|
const toolRegistryForSubcmd = new ToolRegistry();
|
|
4169
5400
|
toolRegistryForSubcmd.registerAllOrThrow(
|
|
4170
5401
|
[...builtinToolsPack.tools ?? []],
|
|
@@ -4265,6 +5496,9 @@ async function boot(argv) {
|
|
|
4265
5496
|
logger
|
|
4266
5497
|
};
|
|
4267
5498
|
}
|
|
5499
|
+
|
|
5500
|
+
// src/repl.ts
|
|
5501
|
+
init_sdd();
|
|
4268
5502
|
async function runRepl(opts) {
|
|
4269
5503
|
if (opts.banner !== false) printBanner(opts.renderer, opts.projectName);
|
|
4270
5504
|
let activeCtrl;
|
|
@@ -4308,6 +5542,58 @@ async function runRepl(opts) {
|
|
|
4308
5542
|
if (res?.message) opts.renderer.write(`${res.message}
|
|
4309
5543
|
`);
|
|
4310
5544
|
if (res?.exit) break;
|
|
5545
|
+
if (res?.runText) {
|
|
5546
|
+
const runBlocks = [{ type: "text", text: res.runText }];
|
|
5547
|
+
const runCtrl2 = new AbortController();
|
|
5548
|
+
activeCtrl = runCtrl2;
|
|
5549
|
+
try {
|
|
5550
|
+
const runResult = await opts.agent.run(runBlocks, { signal: runCtrl2.signal });
|
|
5551
|
+
if (runResult.status === "done" && runResult.finalText) {
|
|
5552
|
+
const specSaved = await trySaveSpecFromAIOutput(runResult.finalText);
|
|
5553
|
+
if (specSaved) {
|
|
5554
|
+
opts.renderer.write(
|
|
5555
|
+
`
|
|
5556
|
+
${color.cyan(" \u2713 Spec detected and saved! Use /sdd approve to continue.")}
|
|
5557
|
+
`
|
|
5558
|
+
);
|
|
5559
|
+
}
|
|
5560
|
+
const planSaved = trySaveImplementationPlan(runResult.finalText);
|
|
5561
|
+
if (planSaved) {
|
|
5562
|
+
opts.renderer.write(
|
|
5563
|
+
`
|
|
5564
|
+
${color.cyan(" \u2713 Implementation plan saved!")}
|
|
5565
|
+
`
|
|
5566
|
+
);
|
|
5567
|
+
}
|
|
5568
|
+
const tasksSaved = await trySaveTasksFromAIOutput(runResult.finalText);
|
|
5569
|
+
if (tasksSaved) {
|
|
5570
|
+
const progress = getTaskProgress();
|
|
5571
|
+
const count = progress?.total ?? 0;
|
|
5572
|
+
opts.renderer.write(
|
|
5573
|
+
`
|
|
5574
|
+
${color.cyan(` \u2713 ${count} tasks detected and saved! Use /sdd approve to execute.`)}
|
|
5575
|
+
`
|
|
5576
|
+
);
|
|
5577
|
+
}
|
|
5578
|
+
const sddPhase2 = getActiveSDDPhase();
|
|
5579
|
+
if (sddPhase2 === "executing") {
|
|
5580
|
+
const autoCompleted = autoDetectTaskCompletion(runResult.finalText);
|
|
5581
|
+
if (autoCompleted > 0) {
|
|
5582
|
+
const progress = getTaskProgress();
|
|
5583
|
+
if (progress) {
|
|
5584
|
+
opts.renderer.write(
|
|
5585
|
+
`
|
|
5586
|
+
${color.cyan(` \u2713 ${autoCompleted} task(s) auto-completed! Progress: ${progress.completed}/${progress.total} (${progress.percent}%)`)}
|
|
5587
|
+
`
|
|
5588
|
+
);
|
|
5589
|
+
}
|
|
5590
|
+
}
|
|
5591
|
+
}
|
|
5592
|
+
}
|
|
5593
|
+
} catch (runErr) {
|
|
5594
|
+
opts.renderer.writeWarning("AI auto-trigger failed. You can continue manually.");
|
|
5595
|
+
}
|
|
5596
|
+
}
|
|
4311
5597
|
} catch (err) {
|
|
4312
5598
|
opts.renderer.writeError(err instanceof Error ? err.message : String(err));
|
|
4313
5599
|
}
|
|
@@ -4320,20 +5606,47 @@ async function runRepl(opts) {
|
|
|
4320
5606
|
`));
|
|
4321
5607
|
}
|
|
4322
5608
|
const blocks = await builder.submit();
|
|
5609
|
+
const sddContext = getActiveSDDContext();
|
|
5610
|
+
const taskList = getTaskListText();
|
|
5611
|
+
const taskProgress = getTaskProgress();
|
|
5612
|
+
const sddPhase = getActiveSDDPhase();
|
|
5613
|
+
let sddPrefix = "";
|
|
5614
|
+
if (sddContext) {
|
|
5615
|
+
sddPrefix = `[SDD SESSION ACTIVE]
|
|
5616
|
+
${sddContext}`;
|
|
5617
|
+
if (taskList) {
|
|
5618
|
+
sddPrefix += `
|
|
5619
|
+
|
|
5620
|
+
**Current Task List:**
|
|
5621
|
+
${taskList}`;
|
|
5622
|
+
}
|
|
5623
|
+
if (taskProgress && taskProgress.total > 0) {
|
|
5624
|
+
sddPrefix += `
|
|
5625
|
+
**Progress:** ${taskProgress.completed}/${taskProgress.total} (${taskProgress.percent}%)`;
|
|
5626
|
+
}
|
|
5627
|
+
if (sddPhase === "executing" && taskProgress && taskProgress.percent === 100) {
|
|
5628
|
+
sddPrefix += "\n\n**All tasks completed! Provide a summary of everything implemented.**";
|
|
5629
|
+
}
|
|
5630
|
+
sddPrefix += "\n\n---\nUser message:\n";
|
|
5631
|
+
}
|
|
5632
|
+
const effectiveBlocks = sddPrefix ? [
|
|
5633
|
+
{ type: "text", text: sddPrefix },
|
|
5634
|
+
...blocks
|
|
5635
|
+
] : blocks;
|
|
4323
5636
|
const runCtrl = new AbortController();
|
|
4324
5637
|
activeCtrl = runCtrl;
|
|
4325
5638
|
try {
|
|
4326
5639
|
const startedAt = Date.now();
|
|
4327
5640
|
const before = opts.tokenCounter?.total();
|
|
4328
5641
|
const costBefore = opts.tokenCounter?.estimateCost().total ?? 0;
|
|
4329
|
-
const routed =
|
|
5642
|
+
const routed = effectiveBlocks.some((block) => block.type === "image") ? await routeImagesForModel(effectiveBlocks, {
|
|
4330
5643
|
supportsVision: opts.supportsVision ? await opts.supportsVision() : opts.agent.ctx.provider.capabilities.vision,
|
|
4331
5644
|
adapters: opts.visionAdapters ?? [],
|
|
4332
5645
|
ctx: opts.agent.ctx,
|
|
4333
5646
|
signal: runCtrl.signal,
|
|
4334
5647
|
providerId: opts.agent.ctx.provider.id,
|
|
4335
5648
|
model: opts.agent.ctx.model
|
|
4336
|
-
}) : { blocks, route: "none", convertedImages: 0 };
|
|
5649
|
+
}) : { blocks: effectiveBlocks, route: "none", convertedImages: 0 };
|
|
4337
5650
|
if (routed.route === "adapter") {
|
|
4338
5651
|
opts.renderer.write(
|
|
4339
5652
|
color.dim(
|
|
@@ -4356,6 +5669,55 @@ async function runRepl(opts) {
|
|
|
4356
5669
|
} else if (result.status === "max_iterations") {
|
|
4357
5670
|
opts.renderer.writeWarning(`Hit max iterations (${result.iterations}).`);
|
|
4358
5671
|
}
|
|
5672
|
+
if (result.status === "done" && result.finalText && sddContext) {
|
|
5673
|
+
const specSaved = await trySaveSpecFromAIOutput(result.finalText);
|
|
5674
|
+
if (specSaved) {
|
|
5675
|
+
opts.renderer.write(
|
|
5676
|
+
`
|
|
5677
|
+
${color.cyan(" \u2713 Spec detected and saved! Use /sdd approve to continue.")}
|
|
5678
|
+
`
|
|
5679
|
+
);
|
|
5680
|
+
}
|
|
5681
|
+
const planSaved = trySaveImplementationPlan(result.finalText);
|
|
5682
|
+
if (planSaved) {
|
|
5683
|
+
opts.renderer.write(
|
|
5684
|
+
`
|
|
5685
|
+
${color.cyan(" \u2713 Implementation plan saved!")}
|
|
5686
|
+
`
|
|
5687
|
+
);
|
|
5688
|
+
}
|
|
5689
|
+
const tasksSaved = await trySaveTasksFromAIOutput(result.finalText);
|
|
5690
|
+
if (tasksSaved) {
|
|
5691
|
+
const progress = getTaskProgress();
|
|
5692
|
+
const count = progress?.total ?? 0;
|
|
5693
|
+
opts.renderer.write(
|
|
5694
|
+
`
|
|
5695
|
+
${color.cyan(` \u2713 ${count} tasks detected and saved! Use /sdd approve to execute.`)}
|
|
5696
|
+
`
|
|
5697
|
+
);
|
|
5698
|
+
}
|
|
5699
|
+
const phase = getActiveSDDPhase();
|
|
5700
|
+
if (phase === "executing") {
|
|
5701
|
+
const autoCompleted = autoDetectTaskCompletion(result.finalText);
|
|
5702
|
+
if (autoCompleted > 0) {
|
|
5703
|
+
const progress = getTaskProgress();
|
|
5704
|
+
if (progress) {
|
|
5705
|
+
opts.renderer.write(
|
|
5706
|
+
`
|
|
5707
|
+
${color.cyan(` \u2713 ${autoCompleted} task(s) auto-completed! Progress: ${progress.completed}/${progress.total} (${progress.percent}%)`)}
|
|
5708
|
+
`
|
|
5709
|
+
);
|
|
5710
|
+
if (progress.percent === 100) {
|
|
5711
|
+
opts.renderer.write(
|
|
5712
|
+
`
|
|
5713
|
+
${color.green(" \u{1F389} All tasks completed! Use /sdd cancel to end the session.")}
|
|
5714
|
+
`
|
|
5715
|
+
);
|
|
5716
|
+
}
|
|
5717
|
+
}
|
|
5718
|
+
}
|
|
5719
|
+
}
|
|
5720
|
+
}
|
|
4359
5721
|
if (opts.tokenCounter && before) {
|
|
4360
5722
|
const after = opts.tokenCounter.total();
|
|
4361
5723
|
const costAfter = opts.tokenCounter.estimateCost().total;
|
|
@@ -4368,6 +5730,49 @@ ${color.dim(
|
|
|
4368
5730
|
`
|
|
4369
5731
|
);
|
|
4370
5732
|
}
|
|
5733
|
+
if (result.status === "done" && opts.getAutonomy) {
|
|
5734
|
+
const autonomy = opts.getAutonomy();
|
|
5735
|
+
if (autonomy === "auto") {
|
|
5736
|
+
const nextPrompt = 'Based on what you just did, what is the single most important next step? Just do it \u2014 execute the next logical step without asking for confirmation. If there is nothing meaningful left to do, say "DONE" and nothing else.';
|
|
5737
|
+
opts.renderer.write(color.dim("\n \u21B3 [autonomy] continuing\u2026\n"));
|
|
5738
|
+
const nextBlocks = [{ type: "text", text: nextPrompt }];
|
|
5739
|
+
const nextCtrl = new AbortController();
|
|
5740
|
+
activeCtrl = nextCtrl;
|
|
5741
|
+
try {
|
|
5742
|
+
const nextResult = await opts.agent.run(nextBlocks, { signal: nextCtrl.signal });
|
|
5743
|
+
if (nextResult.status === "done" && nextResult.finalText?.trim() === "DONE") {
|
|
5744
|
+
opts.renderer.write(color.dim("\n \u21B3 [autonomy] agent reports task complete.\n"));
|
|
5745
|
+
}
|
|
5746
|
+
if (opts.getAutonomy() === "auto" && nextResult.status === "done") {
|
|
5747
|
+
}
|
|
5748
|
+
} catch (err) {
|
|
5749
|
+
opts.renderer.writeError(
|
|
5750
|
+
`[autonomy] ${err instanceof Error ? err.message : String(err)}`
|
|
5751
|
+
);
|
|
5752
|
+
} finally {
|
|
5753
|
+
activeCtrl = void 0;
|
|
5754
|
+
}
|
|
5755
|
+
} else if (autonomy === "suggest") {
|
|
5756
|
+
const suggestPrompt = 'Based on what you just did, suggest 3 concrete next steps. Format: numbered list, one line each, no explanation. If there is nothing meaningful left, say "No further steps needed."';
|
|
5757
|
+
const suggestBlocks = [{ type: "text", text: suggestPrompt }];
|
|
5758
|
+
const suggestCtrl = new AbortController();
|
|
5759
|
+
activeCtrl = suggestCtrl;
|
|
5760
|
+
try {
|
|
5761
|
+
const suggestResult = await opts.agent.run(suggestBlocks, { signal: suggestCtrl.signal });
|
|
5762
|
+
if (suggestResult.status === "done" && suggestResult.finalText) {
|
|
5763
|
+
opts.renderer.write(
|
|
5764
|
+
`
|
|
5765
|
+
${color.cyan(" Suggested next steps:")}
|
|
5766
|
+
${suggestResult.finalText}
|
|
5767
|
+
`
|
|
5768
|
+
);
|
|
5769
|
+
}
|
|
5770
|
+
} catch {
|
|
5771
|
+
} finally {
|
|
5772
|
+
activeCtrl = void 0;
|
|
5773
|
+
}
|
|
5774
|
+
}
|
|
5775
|
+
}
|
|
4371
5776
|
} catch (err) {
|
|
4372
5777
|
opts.renderer.writeError(err instanceof Error ? err.message : String(err));
|
|
4373
5778
|
} finally {
|
|
@@ -4480,7 +5885,10 @@ async function execute(deps) {
|
|
|
4480
5885
|
switchProviderAndModel,
|
|
4481
5886
|
director,
|
|
4482
5887
|
fleetRoster,
|
|
4483
|
-
fleetStreamController
|
|
5888
|
+
fleetStreamController,
|
|
5889
|
+
getYolo,
|
|
5890
|
+
getAutonomy,
|
|
5891
|
+
skillLoader
|
|
4484
5892
|
} = deps;
|
|
4485
5893
|
let code = 0;
|
|
4486
5894
|
try {
|
|
@@ -4584,6 +5992,7 @@ async function execute(deps) {
|
|
|
4584
5992
|
banner: !flags["no-banner"],
|
|
4585
5993
|
queueStore,
|
|
4586
5994
|
yolo: !!config.yolo,
|
|
5995
|
+
getYolo,
|
|
4587
5996
|
appVersion: CLI_VERSION,
|
|
4588
5997
|
provider: config.provider,
|
|
4589
5998
|
family: banneredFamily,
|
|
@@ -4610,7 +6019,36 @@ async function execute(deps) {
|
|
|
4610
6019
|
},
|
|
4611
6020
|
fleetStreamController,
|
|
4612
6021
|
initialGoal: goalFlag,
|
|
4613
|
-
initialAsk: askFlag
|
|
6022
|
+
initialAsk: askFlag,
|
|
6023
|
+
getSDDContext: () => {
|
|
6024
|
+
const { getActiveSDDContext: getActiveSDDContext2 } = (init_sdd(), __toCommonJS(sdd_exports));
|
|
6025
|
+
return getActiveSDDContext2();
|
|
6026
|
+
},
|
|
6027
|
+
onSDDOutput: async (output) => {
|
|
6028
|
+
const { trySaveSpecFromAIOutput: trySaveSpecFromAIOutput2, trySaveImplementationPlan: trySaveImplementationPlan2, trySaveTasksFromAIOutput: trySaveTasksFromAIOutput2, autoDetectTaskCompletion: autoDetectTaskCompletion2, getTaskProgress: getTaskProgress2, getActiveSDDPhase: getActiveSDDPhase2 } = (init_sdd(), __toCommonJS(sdd_exports));
|
|
6029
|
+
const messages = [];
|
|
6030
|
+
const specSaved = await trySaveSpecFromAIOutput2(output);
|
|
6031
|
+
if (specSaved) messages.push("\u2713 Spec detected and saved! Use /sdd approve to continue.");
|
|
6032
|
+
const planSaved = trySaveImplementationPlan2(output);
|
|
6033
|
+
if (planSaved) messages.push("\u2713 Implementation plan saved!");
|
|
6034
|
+
const tasksSaved = await trySaveTasksFromAIOutput2(output);
|
|
6035
|
+
if (tasksSaved) {
|
|
6036
|
+
const progress = getTaskProgress2();
|
|
6037
|
+
const count = progress?.total ?? 0;
|
|
6038
|
+
messages.push(`\u2713 ${count} tasks detected and saved! Use /sdd approve to execute.`);
|
|
6039
|
+
}
|
|
6040
|
+
const sddPhase = getActiveSDDPhase2();
|
|
6041
|
+
if (sddPhase === "executing") {
|
|
6042
|
+
const autoCompleted = autoDetectTaskCompletion2(output);
|
|
6043
|
+
if (autoCompleted > 0) {
|
|
6044
|
+
const progress = getTaskProgress2();
|
|
6045
|
+
if (progress) {
|
|
6046
|
+
messages.push(`\u2713 ${autoCompleted} task(s) auto-completed! Progress: ${progress.completed}/${progress.total} (${progress.percent}%)`);
|
|
6047
|
+
}
|
|
6048
|
+
}
|
|
6049
|
+
}
|
|
6050
|
+
return messages;
|
|
6051
|
+
}
|
|
4614
6052
|
});
|
|
4615
6053
|
} finally {
|
|
4616
6054
|
renderer.setSilent(false);
|
|
@@ -4636,7 +6074,9 @@ async function execute(deps) {
|
|
|
4636
6074
|
supportsVision,
|
|
4637
6075
|
attachments,
|
|
4638
6076
|
effectiveMaxContext,
|
|
4639
|
-
projectName:
|
|
6077
|
+
projectName: path17.basename(projectRoot) || void 0,
|
|
6078
|
+
getAutonomy,
|
|
6079
|
+
skillLoader
|
|
4640
6080
|
});
|
|
4641
6081
|
} finally {
|
|
4642
6082
|
await webuiPromise.catch(() => void 0);
|
|
@@ -4652,7 +6092,9 @@ async function execute(deps) {
|
|
|
4652
6092
|
supportsVision,
|
|
4653
6093
|
attachments,
|
|
4654
6094
|
effectiveMaxContext,
|
|
4655
|
-
projectName:
|
|
6095
|
+
projectName: path17.basename(projectRoot) || void 0,
|
|
6096
|
+
getAutonomy,
|
|
6097
|
+
skillLoader
|
|
4656
6098
|
});
|
|
4657
6099
|
}
|
|
4658
6100
|
} finally {
|
|
@@ -4752,6 +6194,15 @@ var MultiAgentHost = class {
|
|
|
4752
6194
|
this.pending.delete(task.id);
|
|
4753
6195
|
this.emitLifecycleCompleted(task.id, result);
|
|
4754
6196
|
});
|
|
6197
|
+
this.director.fleet.filter("budget.threshold_reached", (e) => {
|
|
6198
|
+
const payload = e.payload;
|
|
6199
|
+
this.deps.events.emit("subagent.budget_warning", {
|
|
6200
|
+
subagentId: e.subagentId,
|
|
6201
|
+
kind: payload.kind,
|
|
6202
|
+
used: payload.used,
|
|
6203
|
+
limit: payload.limit
|
|
6204
|
+
});
|
|
6205
|
+
});
|
|
4755
6206
|
this.coordinator = this.director.coordinator;
|
|
4756
6207
|
} else {
|
|
4757
6208
|
this.coordinator = new DefaultMultiAgentCoordinator(coordinatorConfig, {});
|
|
@@ -4913,7 +6364,7 @@ var MultiAgentHost = class {
|
|
|
4913
6364
|
model: opts?.model,
|
|
4914
6365
|
tools: opts?.tools
|
|
4915
6366
|
};
|
|
4916
|
-
const transcriptPath = this.sessionFactory ?
|
|
6367
|
+
const transcriptPath = this.sessionFactory ? path17.join(this.sessionFactory.dir, `${subagentConfig.name}.jsonl`) : void 0;
|
|
4917
6368
|
if (this.director) {
|
|
4918
6369
|
const subagentId = await this.director.spawn(subagentConfig);
|
|
4919
6370
|
const taskId2 = randomUUID();
|
|
@@ -4982,8 +6433,16 @@ var MultiAgentHost = class {
|
|
|
4982
6433
|
description: v.description,
|
|
4983
6434
|
subagentId: v.subagentId
|
|
4984
6435
|
}));
|
|
4985
|
-
const
|
|
4986
|
-
|
|
6436
|
+
const live = [];
|
|
6437
|
+
if (this.coordinator) {
|
|
6438
|
+
const s = this.coordinator.getStatus();
|
|
6439
|
+
for (const a of s.subagents) {
|
|
6440
|
+
live.push({ subagentId: a.id, status: a.status, task: a.currentTask });
|
|
6441
|
+
}
|
|
6442
|
+
}
|
|
6443
|
+
const liveCount = live.filter((s) => s.status === "running" || s.status === "idle").length;
|
|
6444
|
+
const summary = !this.coordinator ? "No subagents have been spawned." : liveCount > 0 ? `${pending.length} pending, ${liveCount} active, ${this.results.length} completed.` : `${pending.length} pending, ${this.results.length} completed.`;
|
|
6445
|
+
return { pending, completed: this.results, live, summary };
|
|
4987
6446
|
}
|
|
4988
6447
|
/**
|
|
4989
6448
|
* Roll up per-subagent runtime cost from completed TaskResults. We don't
|
|
@@ -5065,16 +6524,16 @@ var MultiAgentHost = class {
|
|
|
5065
6524
|
}
|
|
5066
6525
|
this.opts.directorMode = true;
|
|
5067
6526
|
if (this.opts.fleetRoot && !this.opts.manifestPath) {
|
|
5068
|
-
this.opts.manifestPath =
|
|
6527
|
+
this.opts.manifestPath = path17.join(this.opts.fleetRoot, "fleet.json");
|
|
5069
6528
|
}
|
|
5070
6529
|
if (this.opts.fleetRoot && !this.opts.sharedScratchpadPath) {
|
|
5071
|
-
this.opts.sharedScratchpadPath =
|
|
6530
|
+
this.opts.sharedScratchpadPath = path17.join(this.opts.fleetRoot, "shared");
|
|
5072
6531
|
}
|
|
5073
6532
|
if (this.opts.fleetRoot && !this.opts.sessionsRoot) {
|
|
5074
|
-
this.opts.sessionsRoot =
|
|
6533
|
+
this.opts.sessionsRoot = path17.join(this.opts.fleetRoot, "subagents");
|
|
5075
6534
|
}
|
|
5076
6535
|
if (this.opts.fleetRoot && !this.opts.stateCheckpointPath) {
|
|
5077
|
-
this.opts.stateCheckpointPath =
|
|
6536
|
+
this.opts.stateCheckpointPath = path17.join(this.opts.fleetRoot, "director-state.json");
|
|
5078
6537
|
}
|
|
5079
6538
|
await this.ensureDirector();
|
|
5080
6539
|
return this.director ?? null;
|
|
@@ -5195,11 +6654,11 @@ var SessionStats = class {
|
|
|
5195
6654
|
if (e.name === "bash") this.bashCommands++;
|
|
5196
6655
|
else if (e.name === "fetch") this.fetches++;
|
|
5197
6656
|
if (!e.ok) return;
|
|
5198
|
-
const
|
|
5199
|
-
if (e.name === "read" &&
|
|
5200
|
-
else if (e.name === "edit" &&
|
|
5201
|
-
else if (e.name === "write" &&
|
|
5202
|
-
this.writtenPaths.add(
|
|
6657
|
+
const path18 = typeof input?.path === "string" ? input.path : void 0;
|
|
6658
|
+
if (e.name === "read" && path18) this.readPaths.add(path18);
|
|
6659
|
+
else if (e.name === "edit" && path18) this.editedPaths.add(path18);
|
|
6660
|
+
else if (e.name === "write" && path18) {
|
|
6661
|
+
this.writtenPaths.add(path18);
|
|
5203
6662
|
const content = typeof input?.content === "string" ? input.content : "";
|
|
5204
6663
|
this.bytesWritten += Buffer.byteLength(content, "utf8");
|
|
5205
6664
|
}
|
|
@@ -5405,36 +6864,14 @@ async function setupCompaction(params) {
|
|
|
5405
6864
|
const { compactor, events, modelsRegistry, context, config, provider, pipelines } = params;
|
|
5406
6865
|
const resolvedCaps = await capabilitiesFor(modelsRegistry, provider.id, context.model).catch(() => void 0);
|
|
5407
6866
|
const effectiveMaxContext = config.context.effectiveMaxContext ?? resolvedCaps?.maxContext ?? provider.capabilities.maxContext;
|
|
5408
|
-
|
|
5409
|
-
providerId: provider.id,
|
|
5410
|
-
model: context.model,
|
|
5411
|
-
resolvedCapsMaxContext: resolvedCaps?.maxContext,
|
|
5412
|
-
providerCapMaxContext: provider.capabilities.maxContext,
|
|
5413
|
-
configEffectiveMaxContext: config.context.effectiveMaxContext,
|
|
5414
|
-
effectiveMaxContext,
|
|
5415
|
-
resolvedCapsKeys: resolvedCaps ? Object.keys(resolvedCaps) : null
|
|
5416
|
-
});
|
|
6867
|
+
let autoCompactor;
|
|
5417
6868
|
if (config.context.autoCompact !== false) {
|
|
5418
|
-
|
|
6869
|
+
autoCompactor = new AutoCompactionMiddleware(
|
|
5419
6870
|
compactor,
|
|
5420
6871
|
effectiveMaxContext,
|
|
5421
|
-
|
|
5422
|
-
|
|
5423
|
-
|
|
5424
|
-
if (typeof m.content === "string") {
|
|
5425
|
-
total += Math.ceil(m.content.length / 4);
|
|
5426
|
-
} else if (Array.isArray(m.content)) {
|
|
5427
|
-
for (const b of m.content) {
|
|
5428
|
-
if (b.type === "text") {
|
|
5429
|
-
total += Math.ceil(b.text.length / 4);
|
|
5430
|
-
} else if (b.type === "tool_use" || b.type === "tool_result") {
|
|
5431
|
-
total += Math.ceil(JSON.stringify(b).length / 4);
|
|
5432
|
-
}
|
|
5433
|
-
}
|
|
5434
|
-
}
|
|
5435
|
-
}
|
|
5436
|
-
return total;
|
|
5437
|
-
},
|
|
6872
|
+
// Use the full API request estimator: messages + system prompt + tool definitions.
|
|
6873
|
+
// This matches what the provider actually counts as input tokens.
|
|
6874
|
+
(ctx) => estimateRequestTokens(ctx.messages, ctx.systemPrompt, ctx.tools ?? []).total,
|
|
5438
6875
|
{
|
|
5439
6876
|
warn: config.context.warnThreshold,
|
|
5440
6877
|
soft: config.context.softThreshold,
|
|
@@ -5444,7 +6881,7 @@ async function setupCompaction(params) {
|
|
|
5444
6881
|
);
|
|
5445
6882
|
pipelines.contextWindow.use({ name: "AutoCompaction", handler: autoCompactor.handler() });
|
|
5446
6883
|
}
|
|
5447
|
-
return effectiveMaxContext;
|
|
6884
|
+
return { effectiveMaxContext, autoCompactor };
|
|
5448
6885
|
}
|
|
5449
6886
|
function createAgent(params) {
|
|
5450
6887
|
return new Agent({
|
|
@@ -5552,12 +6989,12 @@ async function setupSession(params) {
|
|
|
5552
6989
|
}
|
|
5553
6990
|
const sessionRef = { current: session };
|
|
5554
6991
|
await recoveryLock.write(session.id).catch(() => void 0);
|
|
5555
|
-
const attachments = new DefaultAttachmentStore({ spoolDir:
|
|
5556
|
-
const queueStore = new QueueStore({ dir:
|
|
6992
|
+
const attachments = new DefaultAttachmentStore({ spoolDir: path17.join(wpaths.projectSessions, session.id, "attachments") });
|
|
6993
|
+
const queueStore = new QueueStore({ dir: path17.join(wpaths.projectSessions, session.id) });
|
|
5557
6994
|
const ctxSignal = new AbortController().signal;
|
|
5558
6995
|
const context = new Context({ systemPrompt, provider, session, signal: ctxSignal, tokenCounter, cwd, projectRoot, model: config.model });
|
|
5559
6996
|
if (restoredMessages.length > 0) context.state.replaceMessages(restoredMessages);
|
|
5560
|
-
const todosCheckpointPath =
|
|
6997
|
+
const todosCheckpointPath = path17.join(wpaths.projectSessions, `${session.id}.todos.json`);
|
|
5561
6998
|
if (resumeId) {
|
|
5562
6999
|
try {
|
|
5563
7000
|
const restoredTodos = await loadTodosCheckpoint(todosCheckpointPath);
|
|
@@ -5569,12 +7006,12 @@ async function setupSession(params) {
|
|
|
5569
7006
|
}
|
|
5570
7007
|
}
|
|
5571
7008
|
const detachTodosCheckpoint = attachTodosCheckpoint(context.state, todosCheckpointPath, session.id);
|
|
5572
|
-
const planPath =
|
|
7009
|
+
const planPath = path17.join(wpaths.projectSessions, `${session.id}.plan.json`);
|
|
5573
7010
|
context.state.setMeta("plan.path", planPath);
|
|
5574
7011
|
if (resumeId) {
|
|
5575
7012
|
try {
|
|
5576
|
-
const fleetRoot =
|
|
5577
|
-
const dirState = await loadDirectorState(
|
|
7013
|
+
const fleetRoot = path17.join(wpaths.projectSessions, session.id);
|
|
7014
|
+
const dirState = await loadDirectorState(path17.join(fleetRoot, "director-state.json"));
|
|
5578
7015
|
if (dirState) {
|
|
5579
7016
|
const tCounts = {};
|
|
5580
7017
|
for (const t of dirState.tasks) tCounts[t.status] = (tCounts[t.status] ?? 0) + 1;
|
|
@@ -5601,7 +7038,7 @@ function resolveBundledSkillsDir2() {
|
|
|
5601
7038
|
try {
|
|
5602
7039
|
const req2 = createRequire(import.meta.url);
|
|
5603
7040
|
const corePkg = req2.resolve("@wrongstack/core/package.json");
|
|
5604
|
-
return
|
|
7041
|
+
return path17.join(path17.dirname(corePkg), "skills");
|
|
5605
7042
|
} catch {
|
|
5606
7043
|
return void 0;
|
|
5607
7044
|
}
|
|
@@ -5688,7 +7125,7 @@ async function main(argv) {
|
|
|
5688
7125
|
modeId,
|
|
5689
7126
|
modePrompt,
|
|
5690
7127
|
modelCapabilities,
|
|
5691
|
-
planPath: () => sessionRef.current ?
|
|
7128
|
+
planPath: () => sessionRef.current ? path17.join(wpaths.projectSessions, `${sessionRef.current.id}.plan.json`) : void 0
|
|
5692
7129
|
})
|
|
5693
7130
|
);
|
|
5694
7131
|
const toolRegistry = new ToolRegistry();
|
|
@@ -5733,7 +7170,7 @@ async function main(argv) {
|
|
|
5733
7170
|
const dumpMetrics = () => {
|
|
5734
7171
|
if (!metricsSink) return;
|
|
5735
7172
|
try {
|
|
5736
|
-
const out =
|
|
7173
|
+
const out = path17.join(wpaths.projectSessions, "metrics.json");
|
|
5737
7174
|
const snap = metricsSink.snapshot();
|
|
5738
7175
|
writeFileSync(out, JSON.stringify(snap, null, 2));
|
|
5739
7176
|
} catch {
|
|
@@ -5861,7 +7298,13 @@ async function main(argv) {
|
|
|
5861
7298
|
});
|
|
5862
7299
|
const pipelines = setupPipelines({ events, logger });
|
|
5863
7300
|
const compactor = container.resolve(TOKENS.Compactor);
|
|
5864
|
-
const effectiveMaxContext = await setupCompaction({ compactor, events, modelsRegistry, context, config, provider, pipelines });
|
|
7301
|
+
const { effectiveMaxContext, autoCompactor } = await setupCompaction({ compactor, events, modelsRegistry, context, config, provider, pipelines });
|
|
7302
|
+
const refreshMaxContext = async (providerId, modelId) => {
|
|
7303
|
+
if (!autoCompactor) return;
|
|
7304
|
+
const cap = await capabilitiesFor(modelsRegistry, providerId, modelId).catch(() => void 0);
|
|
7305
|
+
const mc = cap?.maxContext ?? config.context.effectiveMaxContext ?? 2e5;
|
|
7306
|
+
autoCompactor.setMaxContext(mc);
|
|
7307
|
+
};
|
|
5865
7308
|
const updateSpinnerContext = () => {
|
|
5866
7309
|
if (effectiveMaxContext > 0 && lastInputTokens > 0) {
|
|
5867
7310
|
spinner.setContext({ used: lastInputTokens, max: effectiveMaxContext });
|
|
@@ -5924,23 +7367,20 @@ async function main(argv) {
|
|
|
5924
7367
|
}
|
|
5925
7368
|
const switchProviderAndModel = (providerId, modelId) => {
|
|
5926
7369
|
try {
|
|
5927
|
-
console.error("[DEBUG] switchProviderAndModel called with:", { providerId, modelId });
|
|
5928
7370
|
const savedCfg = config.providers?.[providerId];
|
|
5929
7371
|
const resolvedProviderId = savedCfg?.type ?? providerId;
|
|
5930
|
-
console.error("[DEBUG] switchProviderAndModel: resolvedProviderId:", resolvedProviderId, "savedCfg.type:", savedCfg?.type);
|
|
5931
7372
|
const newCfg = savedCfg ?? {
|
|
5932
7373
|
type: providerId,
|
|
5933
7374
|
apiKey: config.apiKey,
|
|
5934
7375
|
baseUrl: config.baseUrl
|
|
5935
7376
|
};
|
|
5936
7377
|
const cfgWithType = { ...newCfg, type: resolvedProviderId };
|
|
5937
|
-
console.error("[DEBUG] switchProviderAndModel: cfgWithType:", cfgWithType);
|
|
5938
7378
|
const newProvider = config.features.modelsRegistry && providerRegistry.has(resolvedProviderId) ? providerRegistry.create(cfgWithType) : makeProviderFromConfig(resolvedProviderId, cfgWithType);
|
|
5939
|
-
console.error("[DEBUG] switchProviderAndModel: new provider id:", newProvider.id, "maxContext:", newProvider.capabilities.maxContext);
|
|
5940
7379
|
context.provider = newProvider;
|
|
5941
7380
|
context.model = modelId;
|
|
5942
7381
|
config = patchConfig(config, { provider: providerId, model: modelId });
|
|
5943
7382
|
configStore.update({ provider: providerId, model: modelId });
|
|
7383
|
+
void refreshMaxContext(resolvedProviderId, modelId);
|
|
5944
7384
|
return null;
|
|
5945
7385
|
} catch (err) {
|
|
5946
7386
|
return err instanceof Error ? err.message : String(err);
|
|
@@ -5948,12 +7388,13 @@ async function main(argv) {
|
|
|
5948
7388
|
};
|
|
5949
7389
|
const directorMode = flags["director"] === true;
|
|
5950
7390
|
let director = null;
|
|
5951
|
-
|
|
5952
|
-
const
|
|
5953
|
-
const
|
|
5954
|
-
const
|
|
5955
|
-
const
|
|
5956
|
-
const
|
|
7391
|
+
let autonomyMode = "off";
|
|
7392
|
+
const fleetRoot = directorMode ? path17.join(wpaths.projectSessions, session.id) : void 0;
|
|
7393
|
+
const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] : path17.join(fleetRoot, "fleet.json") : void 0;
|
|
7394
|
+
const sharedScratchpadPath = directorMode ? path17.join(fleetRoot, "shared") : void 0;
|
|
7395
|
+
const subagentSessionsRoot = directorMode ? path17.join(fleetRoot, "subagents") : void 0;
|
|
7396
|
+
const stateCheckpointPath = directorMode ? path17.join(fleetRoot, "director-state.json") : void 0;
|
|
7397
|
+
const fleetRootForPromotion = path17.join(wpaths.projectSessions, session.id);
|
|
5957
7398
|
const multiAgentHost = new MultiAgentHost(
|
|
5958
7399
|
{
|
|
5959
7400
|
container,
|
|
@@ -6024,6 +7465,7 @@ async function main(argv) {
|
|
|
6024
7465
|
metricsSink,
|
|
6025
7466
|
healthRegistry,
|
|
6026
7467
|
planPath,
|
|
7468
|
+
modeStore,
|
|
6027
7469
|
fleetStreamController,
|
|
6028
7470
|
onSpawn: async (description, spawnOpts) => {
|
|
6029
7471
|
const { subagentId, taskId } = await multiAgentHost.spawn(description, spawnOpts);
|
|
@@ -6037,8 +7479,19 @@ async function main(argv) {
|
|
|
6037
7479
|
onAgents: () => {
|
|
6038
7480
|
const s = multiAgentHost.status();
|
|
6039
7481
|
const lines = [s.summary];
|
|
7482
|
+
const STATUS_ICON = {
|
|
7483
|
+
running: "\u25CF",
|
|
7484
|
+
idle: "\u25CB",
|
|
7485
|
+
stopped: "\u2298"
|
|
7486
|
+
};
|
|
7487
|
+
for (const a of s.live) {
|
|
7488
|
+
if (a.status === "running" || a.status === "idle") {
|
|
7489
|
+
const task = a.task ? ` \u2014 ${a.task.slice(0, 60)}` : "";
|
|
7490
|
+
lines.push(` ${STATUS_ICON[a.status] ?? "?"} ${a.subagentId.slice(0, 8)} ${a.status}${task}`);
|
|
7491
|
+
}
|
|
7492
|
+
}
|
|
6040
7493
|
for (const p of s.pending) {
|
|
6041
|
-
lines.push(` pending ${p.taskId.slice(0, 8)} \u2192 ${p.description.slice(0, 60)}`);
|
|
7494
|
+
lines.push(` \xB7 pending ${p.taskId.slice(0, 8)} \u2192 ${p.description.slice(0, 60)}`);
|
|
6042
7495
|
}
|
|
6043
7496
|
for (const r of s.completed) {
|
|
6044
7497
|
const fmt = fmtTaskResultLine(r, color);
|
|
@@ -6050,11 +7503,26 @@ async function main(argv) {
|
|
|
6050
7503
|
if (action === "status") {
|
|
6051
7504
|
const s = multiAgentHost.status();
|
|
6052
7505
|
const lines = [color.bold("Fleet status"), ` ${s.summary}`];
|
|
7506
|
+
const STATUS_ICON = {
|
|
7507
|
+
running: "\u25CF",
|
|
7508
|
+
idle: "\u25CB",
|
|
7509
|
+
stopped: "\u2298"
|
|
7510
|
+
};
|
|
7511
|
+
const liveActive = s.live.filter((a) => a.status === "running" || a.status === "idle");
|
|
7512
|
+
if (liveActive.length > 0) {
|
|
7513
|
+
lines.push("", color.dim(" Active"));
|
|
7514
|
+
for (const a of liveActive) {
|
|
7515
|
+
const task = a.task ? ` \xB7 ${a.task.slice(0, 50)}` : "";
|
|
7516
|
+
lines.push(
|
|
7517
|
+
` ${STATUS_ICON[a.status] ?? "?"} ${a.subagentId.slice(0, 8)} ${a.status}${task}`
|
|
7518
|
+
);
|
|
7519
|
+
}
|
|
7520
|
+
}
|
|
6053
7521
|
if (s.pending.length > 0) {
|
|
6054
7522
|
lines.push("", color.dim(" Pending"));
|
|
6055
7523
|
for (const p of s.pending) {
|
|
6056
7524
|
lines.push(
|
|
6057
|
-
` ${p.taskId.slice(0, 8)} \u2192 ${p.subagentId.slice(0, 8)} \xB7 ${p.description.slice(0, 60)}`
|
|
7525
|
+
` \xB7 ${p.taskId.slice(0, 8)} \u2192 ${p.subagentId.slice(0, 8)} \xB7 ${p.description.slice(0, 60)}`
|
|
6058
7526
|
);
|
|
6059
7527
|
}
|
|
6060
7528
|
}
|
|
@@ -6105,7 +7573,7 @@ async function main(argv) {
|
|
|
6105
7573
|
return `Unknown fleet action: ${action}`;
|
|
6106
7574
|
},
|
|
6107
7575
|
onFleetLog: async (subagentId, mode) => {
|
|
6108
|
-
const subagentsRoot =
|
|
7576
|
+
const subagentsRoot = path17.join(fleetRootForPromotion, "subagents");
|
|
6109
7577
|
let runDirs;
|
|
6110
7578
|
try {
|
|
6111
7579
|
runDirs = await fs14.readdir(subagentsRoot);
|
|
@@ -6114,7 +7582,7 @@ async function main(argv) {
|
|
|
6114
7582
|
}
|
|
6115
7583
|
const found = [];
|
|
6116
7584
|
for (const runId of runDirs) {
|
|
6117
|
-
const runDir =
|
|
7585
|
+
const runDir = path17.join(subagentsRoot, runId);
|
|
6118
7586
|
let files;
|
|
6119
7587
|
try {
|
|
6120
7588
|
files = await fs14.readdir(runDir);
|
|
@@ -6123,7 +7591,7 @@ async function main(argv) {
|
|
|
6123
7591
|
}
|
|
6124
7592
|
for (const f of files) {
|
|
6125
7593
|
if (!f.endsWith(".jsonl")) continue;
|
|
6126
|
-
const full =
|
|
7594
|
+
const full = path17.join(runDir, f);
|
|
6127
7595
|
try {
|
|
6128
7596
|
const stat2 = await fs14.stat(full);
|
|
6129
7597
|
found.push({
|
|
@@ -6220,7 +7688,7 @@ async function main(argv) {
|
|
|
6220
7688
|
}
|
|
6221
7689
|
const dir = await multiAgentHost.ensureDirector();
|
|
6222
7690
|
if (!dir) return "Director is not available.";
|
|
6223
|
-
const dirStatePath =
|
|
7691
|
+
const dirStatePath = path17.join(fleetRootForPromotion, "director-state.json");
|
|
6224
7692
|
const prior = await loadDirectorState(dirStatePath);
|
|
6225
7693
|
if (!prior) {
|
|
6226
7694
|
return "No prior director-state.json found \u2014 nothing to retry.";
|
|
@@ -6291,9 +7759,9 @@ async function main(argv) {
|
|
|
6291
7759
|
for (const tool of director2.tools(FLEET_ROSTER)) {
|
|
6292
7760
|
toolRegistry.register(tool);
|
|
6293
7761
|
}
|
|
6294
|
-
const mp =
|
|
6295
|
-
const sp =
|
|
6296
|
-
const ss =
|
|
7762
|
+
const mp = path17.join(fleetRootForPromotion, "fleet.json");
|
|
7763
|
+
const sp = path17.join(fleetRootForPromotion, "shared");
|
|
7764
|
+
const ss = path17.join(fleetRootForPromotion, "subagents");
|
|
6297
7765
|
const lines = [
|
|
6298
7766
|
`${color.green("\u2713")} Promoted to director mode.`,
|
|
6299
7767
|
` Roster: ${Object.keys(FLEET_ROSTER).join(", ")}`,
|
|
@@ -6320,6 +7788,22 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
6320
7788
|
}
|
|
6321
7789
|
return result.message;
|
|
6322
7790
|
},
|
|
7791
|
+
onYolo: (setTo) => {
|
|
7792
|
+
const policy = container.resolve(TOKENS.PermissionPolicy);
|
|
7793
|
+
if (setTo !== void 0) {
|
|
7794
|
+
policy.setYolo(setTo);
|
|
7795
|
+
config = patchConfig(config, { yolo: setTo });
|
|
7796
|
+
return setTo;
|
|
7797
|
+
}
|
|
7798
|
+
return policy.getYolo();
|
|
7799
|
+
},
|
|
7800
|
+
onAutonomy: (setTo) => {
|
|
7801
|
+
if (setTo !== void 0) {
|
|
7802
|
+
autonomyMode = setTo;
|
|
7803
|
+
return setTo;
|
|
7804
|
+
}
|
|
7805
|
+
return autonomyMode;
|
|
7806
|
+
},
|
|
6323
7807
|
onExit: () => {
|
|
6324
7808
|
void mcpRegistry.stopAll();
|
|
6325
7809
|
},
|
|
@@ -6382,7 +7866,13 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
6382
7866
|
switchProviderAndModel,
|
|
6383
7867
|
director: director ?? null,
|
|
6384
7868
|
fleetRoster: FLEET_ROSTER,
|
|
6385
|
-
fleetStreamController
|
|
7869
|
+
fleetStreamController,
|
|
7870
|
+
getYolo: () => {
|
|
7871
|
+
const policy = container.resolve(TOKENS.PermissionPolicy);
|
|
7872
|
+
return policy.getYolo();
|
|
7873
|
+
},
|
|
7874
|
+
getAutonomy: () => autonomyMode,
|
|
7875
|
+
skillLoader: config.features.skills ? skillLoader : void 0
|
|
6386
7876
|
});
|
|
6387
7877
|
}
|
|
6388
7878
|
async function promptRecovery(reader, renderer, abandoned, autoRecover) {
|