@wrongstack/cli 0.5.3 → 0.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +825 -269
- package/dist/index.js.map +1 -1
- package/package.json +10 -10
package/dist/index.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import * as
|
|
3
|
-
import {
|
|
2
|
+
import * as path21 from 'path';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import * as fsp2 from 'fs/promises';
|
|
5
|
+
import { readdir, readFile } from 'fs/promises';
|
|
6
|
+
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, DefaultSessionRewinder, DefaultSessionStore, atomicWrite, AutoApprovePermissionPolicy, formatContextWindowModeList, repairToolUseAdjacency, getContextWindowMode, resolveContextWindowPolicy, formatTodosList, emptyPlan, clearPlan, savePlan, formatPlanTemplates, getPlanTemplate, addPlanItem, formatPlan, deriveTodosFromPlanItem, removePlanItem, setPlanItemStatus, SpecStore, TaskGraphStore, SpecVersioning, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, DefaultTaskStore, TaskTracker, InputBuilder, projectHash, defaultOrchestrator, decryptConfigSecrets, encryptConfigSecrets as encryptConfigSecrets$1, DefaultPluginAPI } from '@wrongstack/core';
|
|
4
7
|
import { createRequire } from 'module';
|
|
5
|
-
import * as fs3 from 'fs/promises';
|
|
6
8
|
import * as os6 from 'os';
|
|
7
9
|
import os6__default from 'os';
|
|
8
10
|
import * as crypto from 'crypto';
|
|
@@ -61,28 +63,37 @@ __export(sdd_exports, {
|
|
|
61
63
|
trySaveSpecFromAIOutput: () => trySaveSpecFromAIOutput,
|
|
62
64
|
trySaveTasksFromAIOutput: () => trySaveTasksFromAIOutput
|
|
63
65
|
});
|
|
66
|
+
function getSessionState(ctx) {
|
|
67
|
+
if (!ctx) {
|
|
68
|
+
return sddState;
|
|
69
|
+
}
|
|
70
|
+
let state = ctx.meta[SDD_META_KEY];
|
|
71
|
+
if (!state) {
|
|
72
|
+
state = new SDDState();
|
|
73
|
+
ctx.meta[SDD_META_KEY] = state;
|
|
74
|
+
}
|
|
75
|
+
return state;
|
|
76
|
+
}
|
|
64
77
|
function getActiveSDDContext() {
|
|
65
|
-
|
|
66
|
-
const session = activeBuilder.getSession();
|
|
67
|
-
if (session.phase === "done") return null;
|
|
68
|
-
return activeBuilder.getAIPrompt();
|
|
78
|
+
return sddState.getContext();
|
|
69
79
|
}
|
|
70
80
|
function getActiveSDDPhase() {
|
|
71
|
-
|
|
72
|
-
return activeBuilder.getPhase();
|
|
81
|
+
return sddState.getPhase();
|
|
73
82
|
}
|
|
74
83
|
async function trySaveSpecFromAIOutput(aiOutput) {
|
|
75
|
-
|
|
76
|
-
|
|
84
|
+
const builder = sddState.getBuilder();
|
|
85
|
+
if (!builder) return false;
|
|
86
|
+
const spec = builder.tryParseSpecFromOutput(aiOutput);
|
|
77
87
|
if (!spec) return false;
|
|
78
|
-
|
|
88
|
+
builder.setSpec(spec);
|
|
79
89
|
return true;
|
|
80
90
|
}
|
|
81
91
|
async function trySaveTasksFromAIOutput(aiOutput) {
|
|
82
|
-
|
|
83
|
-
|
|
92
|
+
const builder = sddState.getBuilder();
|
|
93
|
+
if (!builder) return false;
|
|
94
|
+
const session = builder.getSession();
|
|
84
95
|
if (!session.spec) return false;
|
|
85
|
-
const json =
|
|
96
|
+
const json = builder.extractJSONArray(aiOutput);
|
|
86
97
|
if (!json) return false;
|
|
87
98
|
let tasks;
|
|
88
99
|
try {
|
|
@@ -113,15 +124,16 @@ async function trySaveTasksFromAIOutput(aiOutput) {
|
|
|
113
124
|
tags
|
|
114
125
|
});
|
|
115
126
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
127
|
+
sddState.setTaskStore(store);
|
|
128
|
+
sddState.setTaskTracker(tracker);
|
|
129
|
+
sddState.setTaskGraphId(graph.id);
|
|
130
|
+
builder.setTaskGraphId(graph.id);
|
|
120
131
|
return true;
|
|
121
132
|
}
|
|
122
133
|
function getTaskProgress() {
|
|
123
|
-
|
|
124
|
-
|
|
134
|
+
const tracker = sddState.getTaskTracker();
|
|
135
|
+
if (!tracker) return null;
|
|
136
|
+
const progress = tracker.getProgress();
|
|
125
137
|
return {
|
|
126
138
|
total: progress.total,
|
|
127
139
|
completed: progress.completed,
|
|
@@ -130,8 +142,9 @@ function getTaskProgress() {
|
|
|
130
142
|
};
|
|
131
143
|
}
|
|
132
144
|
function getTaskListText() {
|
|
133
|
-
|
|
134
|
-
|
|
145
|
+
const tracker = sddState.getTaskTracker();
|
|
146
|
+
if (!tracker) return null;
|
|
147
|
+
const nodes = tracker.getAllNodes();
|
|
135
148
|
if (nodes.length === 0) return null;
|
|
136
149
|
const lines = nodes.map((n, i) => {
|
|
137
150
|
const status = n.status === "completed" ? "\u2705" : n.status === "in_progress" ? "\u{1F504}" : "\u23F3";
|
|
@@ -140,18 +153,20 @@ function getTaskListText() {
|
|
|
140
153
|
return lines.join("\n");
|
|
141
154
|
}
|
|
142
155
|
function markTaskCompleted(taskTitle) {
|
|
143
|
-
|
|
144
|
-
|
|
156
|
+
const tracker = sddState.getTaskTracker();
|
|
157
|
+
if (!tracker) return false;
|
|
158
|
+
const nodes = tracker.getAllNodes({ status: ["pending", "in_progress"] });
|
|
145
159
|
const match = nodes.find(
|
|
146
160
|
(n) => n.title.toLowerCase().includes(taskTitle.toLowerCase()) || taskTitle.toLowerCase().includes(n.title.toLowerCase())
|
|
147
161
|
);
|
|
148
162
|
if (!match) return false;
|
|
149
|
-
|
|
163
|
+
tracker.updateNodeStatus(match.id, "completed");
|
|
150
164
|
return true;
|
|
151
165
|
}
|
|
152
166
|
function autoDetectTaskCompletion(aiOutput) {
|
|
153
|
-
|
|
154
|
-
|
|
167
|
+
const tracker = sddState.getTaskTracker();
|
|
168
|
+
if (!tracker) return 0;
|
|
169
|
+
const pending = tracker.getAllNodes({ status: ["pending", "in_progress"] });
|
|
155
170
|
if (pending.length === 0) return 0;
|
|
156
171
|
let completed = 0;
|
|
157
172
|
const lines = aiOutput.split("\n");
|
|
@@ -164,7 +179,7 @@ function autoDetectTaskCompletion(aiOutput) {
|
|
|
164
179
|
if (!Number.isNaN(num) && num >= 1 && num <= pending.length) {
|
|
165
180
|
const node = pending[num - 1];
|
|
166
181
|
if (node && node.status !== "completed") {
|
|
167
|
-
|
|
182
|
+
tracker.updateNodeStatus(node.id, "completed");
|
|
168
183
|
completed++;
|
|
169
184
|
}
|
|
170
185
|
} else {
|
|
@@ -172,7 +187,7 @@ function autoDetectTaskCompletion(aiOutput) {
|
|
|
172
187
|
(n) => n.title.toLowerCase().includes(target.toLowerCase()) || target.toLowerCase().includes(n.title.toLowerCase())
|
|
173
188
|
);
|
|
174
189
|
if (match && match.status !== "completed") {
|
|
175
|
-
|
|
190
|
+
tracker.updateNodeStatus(match.id, "completed");
|
|
176
191
|
completed++;
|
|
177
192
|
}
|
|
178
193
|
}
|
|
@@ -185,7 +200,7 @@ function autoDetectTaskCompletion(aiOutput) {
|
|
|
185
200
|
(n) => n.title.toLowerCase().includes(title.toLowerCase()) || title.toLowerCase().includes(n.title.toLowerCase())
|
|
186
201
|
);
|
|
187
202
|
if (match && match.status !== "completed") {
|
|
188
|
-
|
|
203
|
+
tracker.updateNodeStatus(match.id, "completed");
|
|
189
204
|
completed++;
|
|
190
205
|
}
|
|
191
206
|
continue;
|
|
@@ -196,7 +211,7 @@ function autoDetectTaskCompletion(aiOutput) {
|
|
|
196
211
|
if (num >= 1 && num <= pending.length) {
|
|
197
212
|
const node = pending[num - 1];
|
|
198
213
|
if (node && node.status !== "completed") {
|
|
199
|
-
|
|
214
|
+
tracker.updateNodeStatus(node.id, "completed");
|
|
200
215
|
completed++;
|
|
201
216
|
}
|
|
202
217
|
}
|
|
@@ -209,7 +224,7 @@ function autoDetectTaskCompletion(aiOutput) {
|
|
|
209
224
|
(n) => n.title.toLowerCase().includes(title.toLowerCase()) || title.toLowerCase().includes(n.title.toLowerCase())
|
|
210
225
|
);
|
|
211
226
|
if (match && match.status !== "completed") {
|
|
212
|
-
|
|
227
|
+
tracker.updateNodeStatus(match.id, "completed");
|
|
213
228
|
completed++;
|
|
214
229
|
}
|
|
215
230
|
}
|
|
@@ -217,35 +232,37 @@ function autoDetectTaskCompletion(aiOutput) {
|
|
|
217
232
|
return completed;
|
|
218
233
|
}
|
|
219
234
|
function trySaveImplementationPlan(aiOutput) {
|
|
220
|
-
|
|
221
|
-
|
|
235
|
+
const builder = sddState.getBuilder();
|
|
236
|
+
if (!builder) return false;
|
|
237
|
+
const session = builder.getSession();
|
|
222
238
|
if (session.phase !== "implementation") return false;
|
|
223
239
|
const jsonMatch = aiOutput.match(/```json\s*\[/);
|
|
224
240
|
if (jsonMatch?.index && jsonMatch.index > 0) {
|
|
225
241
|
const plan = aiOutput.substring(0, jsonMatch.index).trim();
|
|
226
242
|
if (plan.length > 50) {
|
|
227
|
-
|
|
243
|
+
builder.setImplementation(plan);
|
|
228
244
|
return true;
|
|
229
245
|
}
|
|
230
246
|
}
|
|
231
247
|
if (aiOutput.length > 100 && !aiOutput.includes("```json")) {
|
|
232
|
-
|
|
248
|
+
builder.setImplementation(aiOutput.trim());
|
|
233
249
|
return true;
|
|
234
250
|
}
|
|
235
251
|
return false;
|
|
236
252
|
}
|
|
237
253
|
function getActiveBuilder() {
|
|
238
|
-
return
|
|
254
|
+
return sddState.getBuilder();
|
|
239
255
|
}
|
|
240
256
|
function buildSddCommand(opts) {
|
|
257
|
+
getSessionState(opts.context);
|
|
241
258
|
return {
|
|
242
259
|
name: "sdd",
|
|
243
260
|
description: "AI-driven SDD: /sdd [new|approve|execute|cancel|status|list|show|templates]",
|
|
244
261
|
async run(args) {
|
|
245
262
|
const ctx = opts.context;
|
|
246
263
|
const projectRoot = ctx?.projectRoot ?? process.cwd();
|
|
247
|
-
const specsDir =
|
|
248
|
-
const graphsDir =
|
|
264
|
+
const specsDir = path21.join(projectRoot, ".wrongstack", "specs");
|
|
265
|
+
const graphsDir = path21.join(projectRoot, ".wrongstack", "task-graphs");
|
|
249
266
|
const specStore = new SpecStore({ baseDir: specsDir });
|
|
250
267
|
new TaskGraphStore({ baseDir: graphsDir });
|
|
251
268
|
const versioning = new SpecVersioning();
|
|
@@ -260,11 +277,10 @@ function buildSddCommand(opts) {
|
|
|
260
277
|
case "create": {
|
|
261
278
|
const forceFlag = rest.includes("--force") || rest.includes("-f");
|
|
262
279
|
const title = rest.filter((a) => !a.startsWith("-")).join(" ").trim() || "Untitled Feature";
|
|
263
|
-
if (!
|
|
264
|
-
const sessionPath =
|
|
280
|
+
if (!sddState.getBuilder() && !forceFlag) {
|
|
281
|
+
const sessionPath = path21.join(projectRoot, ".wrongstack", "sdd-session.json");
|
|
265
282
|
try {
|
|
266
|
-
|
|
267
|
-
await fsp.access(sessionPath);
|
|
283
|
+
await fsp2.access(sessionPath);
|
|
268
284
|
const projectContext2 = await gatherProjectContext(projectRoot);
|
|
269
285
|
const tempBuilder = new AISpecBuilder({
|
|
270
286
|
store: specStore,
|
|
@@ -290,19 +306,18 @@ function buildSddCommand(opts) {
|
|
|
290
306
|
} catch {
|
|
291
307
|
}
|
|
292
308
|
}
|
|
293
|
-
|
|
294
|
-
activeTaskTracker = null;
|
|
295
|
-
activeTaskGraphId = null;
|
|
309
|
+
sddState.clearTaskState();
|
|
296
310
|
const projectContext = await gatherProjectContext(projectRoot);
|
|
297
|
-
|
|
311
|
+
sddState.setBuilder(new AISpecBuilder({
|
|
298
312
|
store: specStore,
|
|
299
313
|
projectContext,
|
|
300
314
|
minQuestions: 2,
|
|
301
315
|
maxQuestions: 10,
|
|
302
|
-
sessionPath:
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
|
|
316
|
+
sessionPath: path21.join(projectRoot, ".wrongstack", "sdd-session.json")
|
|
317
|
+
}));
|
|
318
|
+
const builder = sddState.getBuilder();
|
|
319
|
+
builder.startSession(title);
|
|
320
|
+
const aiPrompt = builder.getAIPrompt();
|
|
306
321
|
return {
|
|
307
322
|
message: [
|
|
308
323
|
`\u2554\u2550\u2550\u2550 SDD: AI Spec Builder \u2550\u2550\u2550\u2557`,
|
|
@@ -326,14 +341,15 @@ Start the specification interview for "${title}". Ask your first contextual ques
|
|
|
326
341
|
case "approve":
|
|
327
342
|
case "ok":
|
|
328
343
|
case "confirm": {
|
|
329
|
-
|
|
344
|
+
const builder = sddState.getBuilder();
|
|
345
|
+
if (!builder) {
|
|
330
346
|
return {
|
|
331
347
|
message: "No active SDD session. Use /sdd new to start one."
|
|
332
348
|
};
|
|
333
349
|
}
|
|
334
|
-
const phase =
|
|
350
|
+
const phase = builder.getSession().phase;
|
|
335
351
|
if (phase === "questioning") {
|
|
336
|
-
const sddCtx =
|
|
352
|
+
const sddCtx = builder.getAIPrompt();
|
|
337
353
|
return {
|
|
338
354
|
message: "No spec generated yet. Generating now...",
|
|
339
355
|
runText: `[SDD SESSION ACTIVE]
|
|
@@ -345,14 +361,14 @@ Generate the complete specification now based on the conversation so far.`
|
|
|
345
361
|
};
|
|
346
362
|
}
|
|
347
363
|
if (phase === "spec_review") {
|
|
348
|
-
const spec =
|
|
364
|
+
const spec = builder.getSession().spec;
|
|
349
365
|
if (!spec) {
|
|
350
366
|
return { message: "No spec to approve." };
|
|
351
367
|
}
|
|
352
|
-
await
|
|
368
|
+
await builder.saveSpec();
|
|
353
369
|
versioning.recordVersion(spec, "Initial spec approved");
|
|
354
|
-
|
|
355
|
-
const implPrompt =
|
|
370
|
+
builder.approve();
|
|
371
|
+
const implPrompt = builder.getAIPrompt();
|
|
356
372
|
return {
|
|
357
373
|
message: [
|
|
358
374
|
`\u2705 Spec "${spec.title}" approved and saved!`,
|
|
@@ -370,8 +386,8 @@ Generate the implementation plan and tasks for the approved spec.`
|
|
|
370
386
|
};
|
|
371
387
|
}
|
|
372
388
|
if (phase === "task_review") {
|
|
373
|
-
|
|
374
|
-
const execPrompt =
|
|
389
|
+
builder.approve();
|
|
390
|
+
const execPrompt = builder.getAIPrompt();
|
|
375
391
|
return {
|
|
376
392
|
message: "\u2705 Tasks approved! The AI will now execute them one by one.",
|
|
377
393
|
runText: `[SDD SESSION ACTIVE]
|
|
@@ -389,18 +405,19 @@ Start executing the tasks one by one.`
|
|
|
389
405
|
// ── Task Execution ─────────────────────────────────────────────────
|
|
390
406
|
case "execute":
|
|
391
407
|
case "run": {
|
|
392
|
-
|
|
408
|
+
const runBuilder = sddState.getBuilder();
|
|
409
|
+
if (!runBuilder) {
|
|
393
410
|
return {
|
|
394
411
|
message: "No active SDD session. Use /sdd new to start one."
|
|
395
412
|
};
|
|
396
413
|
}
|
|
397
|
-
const session =
|
|
414
|
+
const session = runBuilder.getSession();
|
|
398
415
|
if (session.phase !== "executing" && session.phase !== "task_review") {
|
|
399
416
|
return {
|
|
400
417
|
message: `Cannot execute in phase "${session.phase}". Use /sdd approve first.`
|
|
401
418
|
};
|
|
402
419
|
}
|
|
403
|
-
const execPrompt =
|
|
420
|
+
const execPrompt = runBuilder.getAIPrompt();
|
|
404
421
|
return {
|
|
405
422
|
message: "\u26A1 Starting task execution. The AI will execute tasks one by one.",
|
|
406
423
|
runText: `[SDD SESSION ACTIVE]
|
|
@@ -413,34 +430,36 @@ Start executing the tasks one by one.`
|
|
|
413
430
|
}
|
|
414
431
|
case "plan":
|
|
415
432
|
case "impl": {
|
|
416
|
-
|
|
433
|
+
const planBuilder = sddState.getBuilder();
|
|
434
|
+
if (!planBuilder) {
|
|
417
435
|
return { message: "No active SDD session. Use /sdd new to start one." };
|
|
418
436
|
}
|
|
419
|
-
const
|
|
420
|
-
if (!
|
|
437
|
+
const planSession = planBuilder.getSession();
|
|
438
|
+
if (!planSession.implementation) {
|
|
421
439
|
return {
|
|
422
|
-
message:
|
|
440
|
+
message: planSession.phase === "implementation" ? "No implementation plan yet. The AI will generate it after /sdd approve." : "No implementation plan in this session."
|
|
423
441
|
};
|
|
424
442
|
}
|
|
425
443
|
return {
|
|
426
444
|
message: [
|
|
427
445
|
"\u2550\u2550\u2550 Implementation Plan \u2550\u2550\u2550",
|
|
428
446
|
"",
|
|
429
|
-
|
|
447
|
+
planSession.implementation
|
|
430
448
|
].join("\n")
|
|
431
449
|
};
|
|
432
450
|
}
|
|
433
451
|
case "spec": {
|
|
434
|
-
|
|
452
|
+
const specBuilder = sddState.getBuilder();
|
|
453
|
+
if (!specBuilder) {
|
|
435
454
|
return { message: "No active SDD session. Use /sdd new to start one." };
|
|
436
455
|
}
|
|
437
|
-
const
|
|
438
|
-
if (!
|
|
456
|
+
const specSession = specBuilder.getSession();
|
|
457
|
+
if (!specSession.spec) {
|
|
439
458
|
return {
|
|
440
|
-
message:
|
|
459
|
+
message: specSession.phase === "questioning" ? "No spec generated yet. Keep answering the AI's questions." : "No spec in this session."
|
|
441
460
|
};
|
|
442
461
|
}
|
|
443
|
-
const spec =
|
|
462
|
+
const spec = specSession.spec;
|
|
444
463
|
const lines = [
|
|
445
464
|
`\u2550\u2550\u2550 Current Spec \u2550\u2550\u2550`,
|
|
446
465
|
"",
|
|
@@ -462,14 +481,15 @@ Start executing the tasks one by one.`
|
|
|
462
481
|
}
|
|
463
482
|
case "tasks":
|
|
464
483
|
case "task": {
|
|
465
|
-
|
|
484
|
+
const taskTracker = sddState.getTaskTracker();
|
|
485
|
+
if (!taskTracker) {
|
|
466
486
|
return { message: "No tasks generated yet. Use /sdd new to start." };
|
|
467
487
|
}
|
|
468
|
-
const nodes =
|
|
488
|
+
const nodes = taskTracker.getAllNodes();
|
|
469
489
|
if (nodes.length === 0) {
|
|
470
490
|
return { message: "No tasks in the current graph." };
|
|
471
491
|
}
|
|
472
|
-
const progress =
|
|
492
|
+
const progress = taskTracker.getProgress();
|
|
473
493
|
const lines = [
|
|
474
494
|
`\u2550\u2550\u2550 Task List (${progress.completed}/${progress.total} done) \u2550\u2550\u2550`,
|
|
475
495
|
""
|
|
@@ -486,19 +506,20 @@ Start executing the tasks one by one.`
|
|
|
486
506
|
}
|
|
487
507
|
case "done":
|
|
488
508
|
case "complete": {
|
|
489
|
-
|
|
509
|
+
const doneTracker = sddState.getTaskTracker();
|
|
510
|
+
if (!doneTracker) {
|
|
490
511
|
return { message: "No tasks to complete." };
|
|
491
512
|
}
|
|
492
513
|
if (!restJoined) {
|
|
493
514
|
return { message: "Usage: /sdd done <task title or number>" };
|
|
494
515
|
}
|
|
495
|
-
const nodes =
|
|
516
|
+
const nodes = doneTracker.getAllNodes({ status: ["pending", "in_progress"] });
|
|
496
517
|
const num = Number(restJoined);
|
|
497
518
|
let matched = false;
|
|
498
519
|
if (!Number.isNaN(num) && num >= 1 && num <= nodes.length) {
|
|
499
520
|
const node = nodes[num - 1];
|
|
500
521
|
if (node) {
|
|
501
|
-
|
|
522
|
+
doneTracker.updateNodeStatus(node.id, "completed");
|
|
502
523
|
matched = true;
|
|
503
524
|
}
|
|
504
525
|
}
|
|
@@ -507,24 +528,25 @@ Start executing the tasks one by one.`
|
|
|
507
528
|
(n) => n.title.toLowerCase().includes(restJoined.toLowerCase()) || restJoined.toLowerCase().includes(n.title.toLowerCase())
|
|
508
529
|
);
|
|
509
530
|
if (match) {
|
|
510
|
-
|
|
531
|
+
doneTracker.updateNodeStatus(match.id, "completed");
|
|
511
532
|
matched = true;
|
|
512
533
|
}
|
|
513
534
|
}
|
|
514
535
|
if (!matched) {
|
|
515
536
|
return { message: `No pending task matching "${restJoined}".` };
|
|
516
537
|
}
|
|
517
|
-
const remaining =
|
|
538
|
+
const remaining = doneTracker.getProgress();
|
|
518
539
|
return {
|
|
519
540
|
message: `\u2705 Task completed! ${remaining.completed}/${remaining.total} done (${remaining.percentComplete}%)`
|
|
520
541
|
};
|
|
521
542
|
}
|
|
522
543
|
// ── Session Management ─────────────────────────────────────────────
|
|
523
544
|
case "status": {
|
|
524
|
-
|
|
545
|
+
const statusBuilder = sddState.getBuilder();
|
|
546
|
+
if (!statusBuilder) {
|
|
525
547
|
return { message: "No active SDD session." };
|
|
526
548
|
}
|
|
527
|
-
const session =
|
|
549
|
+
const session = statusBuilder.getSession();
|
|
528
550
|
const phaseEmoji = {
|
|
529
551
|
questioning: "\u2753",
|
|
530
552
|
spec_review: "\u{1F4CB}",
|
|
@@ -559,21 +581,19 @@ Start executing the tasks one by one.`
|
|
|
559
581
|
};
|
|
560
582
|
}
|
|
561
583
|
case "cancel": {
|
|
562
|
-
const sessionPath =
|
|
584
|
+
const sessionPath = path21.join(projectRoot, ".wrongstack", "sdd-session.json");
|
|
563
585
|
let deletedFromDisk = false;
|
|
564
586
|
try {
|
|
565
|
-
|
|
566
|
-
await fsp.unlink(sessionPath);
|
|
587
|
+
await fsp2.unlink(sessionPath);
|
|
567
588
|
deletedFromDisk = true;
|
|
568
589
|
} catch {
|
|
569
590
|
}
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
activeTaskGraphId = null;
|
|
591
|
+
const cancelBuilder = sddState.getBuilder();
|
|
592
|
+
if (cancelBuilder) {
|
|
593
|
+
const title = cancelBuilder.getSession().title;
|
|
594
|
+
await cancelBuilder.deleteSession();
|
|
595
|
+
sddState.setBuilder(null);
|
|
596
|
+
sddState.clearTaskState();
|
|
577
597
|
return { message: `SDD session for "${title}" cancelled.` };
|
|
578
598
|
}
|
|
579
599
|
if (deletedFromDisk) {
|
|
@@ -582,36 +602,37 @@ Start executing the tasks one by one.`
|
|
|
582
602
|
return { message: "No active SDD session." };
|
|
583
603
|
}
|
|
584
604
|
case "resume": {
|
|
585
|
-
if (
|
|
605
|
+
if (sddState.getBuilder()) {
|
|
586
606
|
return { message: "An SDD session is already active. Use /sdd cancel first." };
|
|
587
607
|
}
|
|
588
|
-
const sessionPath =
|
|
608
|
+
const sessionPath = path21.join(projectRoot, ".wrongstack", "sdd-session.json");
|
|
589
609
|
const projectContext = await gatherProjectContext(projectRoot);
|
|
590
|
-
|
|
610
|
+
sddState.setBuilder(new AISpecBuilder({
|
|
591
611
|
store: specStore,
|
|
592
612
|
projectContext,
|
|
593
613
|
minQuestions: 2,
|
|
594
614
|
maxQuestions: 10,
|
|
595
615
|
sessionPath
|
|
596
|
-
});
|
|
597
|
-
const
|
|
616
|
+
}));
|
|
617
|
+
const resumeBuilder = sddState.getBuilder();
|
|
618
|
+
const loaded = await resumeBuilder.loadSession();
|
|
598
619
|
if (!loaded) {
|
|
599
|
-
|
|
620
|
+
sddState.setBuilder(null);
|
|
600
621
|
return { message: "No saved SDD session found. Use /sdd new to start one." };
|
|
601
622
|
}
|
|
602
|
-
const session =
|
|
623
|
+
const session = resumeBuilder.getSession();
|
|
603
624
|
let taskCount = 0;
|
|
604
625
|
let completedCount = 0;
|
|
605
|
-
const taskGraphId =
|
|
626
|
+
const taskGraphId = resumeBuilder.getTaskGraphId();
|
|
606
627
|
if (taskGraphId) {
|
|
607
628
|
try {
|
|
608
629
|
const store = new DefaultTaskStore();
|
|
609
630
|
const tracker = new TaskTracker({ store });
|
|
610
631
|
const graph = await tracker.loadGraph(taskGraphId);
|
|
611
632
|
if (graph) {
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
633
|
+
sddState.setTaskStore(store);
|
|
634
|
+
sddState.setTaskTracker(tracker);
|
|
635
|
+
sddState.setTaskGraphId(taskGraphId);
|
|
615
636
|
const progress = tracker.getProgress();
|
|
616
637
|
taskCount = progress.total;
|
|
617
638
|
completedCount = progress.completed;
|
|
@@ -619,7 +640,7 @@ Start executing the tasks one by one.`
|
|
|
619
640
|
} catch {
|
|
620
641
|
}
|
|
621
642
|
}
|
|
622
|
-
const resumePrompt =
|
|
643
|
+
const resumePrompt = resumeBuilder.getAIPrompt();
|
|
623
644
|
return {
|
|
624
645
|
message: [
|
|
625
646
|
`\u2554\u2550\u2550\u2550 SDD Session Resumed \u2550\u2550\u2550\u2557`,
|
|
@@ -810,9 +831,8 @@ function sddHelp() {
|
|
|
810
831
|
async function gatherProjectContext(projectRoot) {
|
|
811
832
|
const parts = [];
|
|
812
833
|
try {
|
|
813
|
-
const
|
|
814
|
-
const
|
|
815
|
-
const pkgRaw = await fsp.readFile(pkgPath, "utf8");
|
|
834
|
+
const pkgPath = path21.join(projectRoot, "package.json");
|
|
835
|
+
const pkgRaw = await fsp2.readFile(pkgPath, "utf8");
|
|
816
836
|
const pkg = JSON.parse(pkgRaw);
|
|
817
837
|
parts.push(`Project: ${String(pkg.name ?? "unknown")}`);
|
|
818
838
|
parts.push(`Description: ${String(pkg.description ?? "none")}`);
|
|
@@ -827,16 +847,14 @@ async function gatherProjectContext(projectRoot) {
|
|
|
827
847
|
} catch {
|
|
828
848
|
}
|
|
829
849
|
try {
|
|
830
|
-
const
|
|
831
|
-
|
|
832
|
-
await fsp.access(tsconfigPath);
|
|
850
|
+
const tsconfigPath = path21.join(projectRoot, "tsconfig.json");
|
|
851
|
+
await fsp2.access(tsconfigPath);
|
|
833
852
|
parts.push("Language: TypeScript");
|
|
834
853
|
} catch {
|
|
835
854
|
}
|
|
836
855
|
try {
|
|
837
|
-
const
|
|
838
|
-
const
|
|
839
|
-
const entries = await fsp.readdir(srcDir, { withFileTypes: true });
|
|
856
|
+
const srcDir = path21.join(projectRoot, "src");
|
|
857
|
+
const entries = await fsp2.readdir(srcDir, { withFileTypes: true });
|
|
840
858
|
const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
841
859
|
if (dirs.length > 0) {
|
|
842
860
|
parts.push(`Source structure: src/${dirs.join(", src/")}`);
|
|
@@ -856,13 +874,55 @@ async function findSpec(store, idOrTitle) {
|
|
|
856
874
|
if (match) return store.load(match.id);
|
|
857
875
|
return null;
|
|
858
876
|
}
|
|
859
|
-
var
|
|
877
|
+
var SDD_META_KEY, SDDState, sddState;
|
|
860
878
|
var init_sdd = __esm({
|
|
861
879
|
"src/slash-commands/sdd.ts"() {
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
880
|
+
SDD_META_KEY = "sdd.state";
|
|
881
|
+
SDDState = class {
|
|
882
|
+
builder = null;
|
|
883
|
+
taskStore = null;
|
|
884
|
+
taskTracker = null;
|
|
885
|
+
taskGraphId = null;
|
|
886
|
+
getBuilder() {
|
|
887
|
+
return this.builder;
|
|
888
|
+
}
|
|
889
|
+
setBuilder(b) {
|
|
890
|
+
this.builder = b;
|
|
891
|
+
}
|
|
892
|
+
getTaskStore() {
|
|
893
|
+
return this.taskStore;
|
|
894
|
+
}
|
|
895
|
+
setTaskStore(s) {
|
|
896
|
+
this.taskStore = s;
|
|
897
|
+
}
|
|
898
|
+
getTaskTracker() {
|
|
899
|
+
return this.taskTracker;
|
|
900
|
+
}
|
|
901
|
+
setTaskTracker(t) {
|
|
902
|
+
this.taskTracker = t;
|
|
903
|
+
}
|
|
904
|
+
getTaskGraphId() {
|
|
905
|
+
return this.taskGraphId;
|
|
906
|
+
}
|
|
907
|
+
setTaskGraphId(id) {
|
|
908
|
+
this.taskGraphId = id;
|
|
909
|
+
}
|
|
910
|
+
clearTaskState() {
|
|
911
|
+
this.taskStore = null;
|
|
912
|
+
this.taskTracker = null;
|
|
913
|
+
this.taskGraphId = null;
|
|
914
|
+
}
|
|
915
|
+
getContext() {
|
|
916
|
+
if (!this.builder) return null;
|
|
917
|
+
const session = this.builder.getSession();
|
|
918
|
+
if (session.phase === "done") return null;
|
|
919
|
+
return this.builder.getAIPrompt();
|
|
920
|
+
}
|
|
921
|
+
getPhase() {
|
|
922
|
+
return this.builder?.getPhase() ?? null;
|
|
923
|
+
}
|
|
924
|
+
};
|
|
925
|
+
sddState = new SDDState();
|
|
866
926
|
}
|
|
867
927
|
});
|
|
868
928
|
function normalizeKeys(cfg) {
|
|
@@ -916,7 +976,7 @@ __export(update_check_exports, {
|
|
|
916
976
|
getUpdateNotification: () => getUpdateNotification
|
|
917
977
|
});
|
|
918
978
|
function cachePath(homeFn = defaultHomeDir2) {
|
|
919
|
-
return
|
|
979
|
+
return path21.join(homeFn(), ".wrongstack", "update-cache.json");
|
|
920
980
|
}
|
|
921
981
|
function currentVersion() {
|
|
922
982
|
const req2 = createRequire(import.meta.url);
|
|
@@ -943,7 +1003,7 @@ function isNewer(a, b) {
|
|
|
943
1003
|
}
|
|
944
1004
|
async function readCache(homeFn = defaultHomeDir2) {
|
|
945
1005
|
try {
|
|
946
|
-
const raw = await
|
|
1006
|
+
const raw = await fsp2.readFile(cachePath(homeFn), "utf8");
|
|
947
1007
|
const entry = JSON.parse(raw);
|
|
948
1008
|
if (Date.now() - entry.timestamp > CACHE_TTL_MS) return null;
|
|
949
1009
|
return entry;
|
|
@@ -953,9 +1013,9 @@ async function readCache(homeFn = defaultHomeDir2) {
|
|
|
953
1013
|
}
|
|
954
1014
|
async function writeCache(entry, homeFn = defaultHomeDir2) {
|
|
955
1015
|
try {
|
|
956
|
-
const dir =
|
|
957
|
-
await
|
|
958
|
-
await
|
|
1016
|
+
const dir = path21.dirname(cachePath(homeFn));
|
|
1017
|
+
await fsp2.mkdir(dir, { recursive: true });
|
|
1018
|
+
await fsp2.writeFile(cachePath(homeFn), JSON.stringify(entry, null, 2), "utf8");
|
|
959
1019
|
} catch {
|
|
960
1020
|
}
|
|
961
1021
|
}
|
|
@@ -1513,7 +1573,7 @@ async function runWebUI(opts) {
|
|
|
1513
1573
|
if (!opts.globalConfigPath) return {};
|
|
1514
1574
|
let raw;
|
|
1515
1575
|
try {
|
|
1516
|
-
raw = await
|
|
1576
|
+
raw = await fsp2.readFile(opts.globalConfigPath, "utf8");
|
|
1517
1577
|
} catch {
|
|
1518
1578
|
return {};
|
|
1519
1579
|
}
|
|
@@ -1524,7 +1584,7 @@ async function runWebUI(opts) {
|
|
|
1524
1584
|
return {};
|
|
1525
1585
|
}
|
|
1526
1586
|
if (!parsed.providers) return {};
|
|
1527
|
-
const keyFile =
|
|
1587
|
+
const keyFile = path21.join(path21.dirname(opts.globalConfigPath), ".key");
|
|
1528
1588
|
const vault = new DefaultSecretVault$1({ keyFile });
|
|
1529
1589
|
return decryptConfigSecrets$1(parsed.providers, vault);
|
|
1530
1590
|
}
|
|
@@ -1532,7 +1592,7 @@ async function runWebUI(opts) {
|
|
|
1532
1592
|
if (!opts.globalConfigPath) return;
|
|
1533
1593
|
let raw;
|
|
1534
1594
|
try {
|
|
1535
|
-
raw = await
|
|
1595
|
+
raw = await fsp2.readFile(opts.globalConfigPath, "utf8");
|
|
1536
1596
|
} catch {
|
|
1537
1597
|
raw = "{}";
|
|
1538
1598
|
}
|
|
@@ -1543,7 +1603,7 @@ async function runWebUI(opts) {
|
|
|
1543
1603
|
parsed = {};
|
|
1544
1604
|
}
|
|
1545
1605
|
parsed.providers = providers;
|
|
1546
|
-
const keyFile =
|
|
1606
|
+
const keyFile = path21.join(path21.dirname(opts.globalConfigPath), ".key");
|
|
1547
1607
|
const vault = new DefaultSecretVault$1({ keyFile });
|
|
1548
1608
|
const encrypted = encryptConfigSecrets(parsed, vault);
|
|
1549
1609
|
await atomicWrite(opts.globalConfigPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
@@ -1621,7 +1681,8 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
1621
1681
|
"prompt",
|
|
1622
1682
|
"metrics",
|
|
1623
1683
|
"webui",
|
|
1624
|
-
"no-check"
|
|
1684
|
+
"no-check",
|
|
1685
|
+
"director"
|
|
1625
1686
|
]);
|
|
1626
1687
|
function parseArgs(argv) {
|
|
1627
1688
|
const flags = {};
|
|
@@ -1727,7 +1788,7 @@ function parseSpawnFlags(input) {
|
|
|
1727
1788
|
return { description: rest.trim(), opts };
|
|
1728
1789
|
}
|
|
1729
1790
|
async function bootConfig(flags) {
|
|
1730
|
-
const cwd = typeof flags["cwd"] === "string" ?
|
|
1791
|
+
const cwd = typeof flags["cwd"] === "string" ? path21.resolve(flags["cwd"]) : process.cwd();
|
|
1731
1792
|
const pathResolver = new DefaultPathResolver(cwd);
|
|
1732
1793
|
const projectRoot = pathResolver.projectRoot;
|
|
1733
1794
|
const userHome = os6.homedir();
|
|
@@ -1778,13 +1839,13 @@ function flagsToConfigPatch(flags) {
|
|
|
1778
1839
|
}
|
|
1779
1840
|
async function ensureProjectMeta(paths, projectRoot) {
|
|
1780
1841
|
try {
|
|
1781
|
-
await
|
|
1842
|
+
await fsp2.mkdir(paths.projectDir, { recursive: true });
|
|
1782
1843
|
const meta = {
|
|
1783
1844
|
hash: paths.projectHash,
|
|
1784
1845
|
root: projectRoot,
|
|
1785
1846
|
lastSeen: (/* @__PURE__ */ new Date()).toISOString()
|
|
1786
1847
|
};
|
|
1787
|
-
await
|
|
1848
|
+
await fsp2.writeFile(paths.projectMeta, JSON.stringify(meta, null, 2));
|
|
1788
1849
|
} catch {
|
|
1789
1850
|
}
|
|
1790
1851
|
}
|
|
@@ -1794,11 +1855,11 @@ var ReadlineInputReader = class {
|
|
|
1794
1855
|
history = [];
|
|
1795
1856
|
pending = false;
|
|
1796
1857
|
constructor(opts = {}) {
|
|
1797
|
-
this.historyFile = opts.historyFile ??
|
|
1858
|
+
this.historyFile = opts.historyFile ?? path21.join(os6.homedir(), ".wrongstack", "history");
|
|
1798
1859
|
}
|
|
1799
1860
|
async loadHistory() {
|
|
1800
1861
|
try {
|
|
1801
|
-
const raw = await
|
|
1862
|
+
const raw = await fsp2.readFile(this.historyFile, "utf8");
|
|
1802
1863
|
this.history = raw.split("\n").filter(Boolean).slice(-1e3);
|
|
1803
1864
|
} catch {
|
|
1804
1865
|
this.history = [];
|
|
@@ -1806,8 +1867,8 @@ var ReadlineInputReader = class {
|
|
|
1806
1867
|
}
|
|
1807
1868
|
async saveHistory() {
|
|
1808
1869
|
try {
|
|
1809
|
-
await
|
|
1810
|
-
await
|
|
1870
|
+
await fsp2.mkdir(path21.dirname(this.historyFile), { recursive: true });
|
|
1871
|
+
await fsp2.writeFile(this.historyFile, this.history.slice(-1e3).join("\n"));
|
|
1811
1872
|
} catch {
|
|
1812
1873
|
}
|
|
1813
1874
|
}
|
|
@@ -2033,23 +2094,23 @@ function assertSafeToDelete(filename, parentDir) {
|
|
|
2033
2094
|
if (PROTECTED_BASENAMES.has(filename)) {
|
|
2034
2095
|
throw new Error(`Refusing to delete protected file: ${filename}`);
|
|
2035
2096
|
}
|
|
2036
|
-
if (filename !==
|
|
2097
|
+
if (filename !== path21.basename(filename)) {
|
|
2037
2098
|
throw new Error(`Refusing to delete path with traversal: ${filename}`);
|
|
2038
2099
|
}
|
|
2039
2100
|
if (!filename.startsWith("config.json.") || !filename.endsWith(".bak")) {
|
|
2040
2101
|
throw new Error(`Refusing to delete unknown file: ${filename}`);
|
|
2041
2102
|
}
|
|
2042
|
-
const resolvedParent =
|
|
2103
|
+
const resolvedParent = path21.resolve(parentDir);
|
|
2043
2104
|
if (!resolvedParent.endsWith(".wrongstack")) {
|
|
2044
2105
|
throw new Error(`Unexpected parent directory for bak prune: ${resolvedParent}`);
|
|
2045
2106
|
}
|
|
2046
2107
|
}
|
|
2047
2108
|
async function safeDelete(filePath) {
|
|
2048
|
-
const dir =
|
|
2049
|
-
const filename =
|
|
2109
|
+
const dir = path21.dirname(filePath);
|
|
2110
|
+
const filename = path21.basename(filePath);
|
|
2050
2111
|
try {
|
|
2051
2112
|
assertSafeToDelete(filename, dir);
|
|
2052
|
-
await
|
|
2113
|
+
await fsp2.unlink(filePath);
|
|
2053
2114
|
} catch (err) {
|
|
2054
2115
|
if (err instanceof Error && err.message.startsWith("Refusing")) {
|
|
2055
2116
|
process.stderr.write(`[config-history] SAFETY: ${err.message}
|
|
@@ -2091,26 +2152,26 @@ function diffSummary(oldCfg, newCfg) {
|
|
|
2091
2152
|
}
|
|
2092
2153
|
var defaultHomeDir = () => os6__default.homedir();
|
|
2093
2154
|
function historyDir(homeFn = defaultHomeDir) {
|
|
2094
|
-
return
|
|
2155
|
+
return path21.join(homeFn(), ".wrongstack", "config.history", "entries");
|
|
2095
2156
|
}
|
|
2096
2157
|
function historyIndexPath(homeFn = defaultHomeDir) {
|
|
2097
|
-
return
|
|
2158
|
+
return path21.join(homeFn(), ".wrongstack", "config.history", "index.json");
|
|
2098
2159
|
}
|
|
2099
2160
|
function configPath(homeFn = defaultHomeDir) {
|
|
2100
|
-
return
|
|
2161
|
+
return path21.join(homeFn(), ".wrongstack", "config.json");
|
|
2101
2162
|
}
|
|
2102
2163
|
function backupLastPath(homeFn = defaultHomeDir) {
|
|
2103
|
-
return
|
|
2164
|
+
return path21.join(homeFn(), ".wrongstack", "config.json.last");
|
|
2104
2165
|
}
|
|
2105
2166
|
function entryId(ts) {
|
|
2106
2167
|
return ts.replace(/[:.]/g, "-").slice(0, 19);
|
|
2107
2168
|
}
|
|
2108
2169
|
async function ensureHistoryDir(homeFn = defaultHomeDir) {
|
|
2109
|
-
await
|
|
2170
|
+
await fsp2.mkdir(historyDir(homeFn), { recursive: true });
|
|
2110
2171
|
}
|
|
2111
2172
|
async function readIndex(homeFn = defaultHomeDir) {
|
|
2112
2173
|
try {
|
|
2113
|
-
const raw = await
|
|
2174
|
+
const raw = await fsp2.readFile(historyIndexPath(homeFn), "utf8");
|
|
2114
2175
|
return JSON.parse(raw);
|
|
2115
2176
|
} catch {
|
|
2116
2177
|
return { version: 1, entries: [] };
|
|
@@ -2118,7 +2179,7 @@ async function readIndex(homeFn = defaultHomeDir) {
|
|
|
2118
2179
|
}
|
|
2119
2180
|
async function writeIndex(idx, homeFn = defaultHomeDir) {
|
|
2120
2181
|
await ensureHistoryDir(homeFn);
|
|
2121
|
-
await
|
|
2182
|
+
await fsp2.writeFile(historyIndexPath(homeFn), JSON.stringify(idx, null, 2), "utf8");
|
|
2122
2183
|
}
|
|
2123
2184
|
async function backupCurrent(homeFn = defaultHomeDir) {
|
|
2124
2185
|
const cfg = configPath(homeFn);
|
|
@@ -2126,7 +2187,7 @@ async function backupCurrent(homeFn = defaultHomeDir) {
|
|
|
2126
2187
|
const ts = Date.now();
|
|
2127
2188
|
let content;
|
|
2128
2189
|
try {
|
|
2129
|
-
content = await
|
|
2190
|
+
content = await fsp2.readFile(cfg, "utf8");
|
|
2130
2191
|
} catch {
|
|
2131
2192
|
}
|
|
2132
2193
|
if (content !== void 0) {
|
|
@@ -2137,17 +2198,17 @@ async function backupCurrent(homeFn = defaultHomeDir) {
|
|
|
2137
2198
|
}
|
|
2138
2199
|
if (content !== void 0) {
|
|
2139
2200
|
try {
|
|
2140
|
-
const bakPath =
|
|
2201
|
+
const bakPath = path21.join(homeFn(), ".wrongstack", `config.json.${ts}.bak`);
|
|
2141
2202
|
await atomicWrite(bakPath, content);
|
|
2142
2203
|
} catch {
|
|
2143
2204
|
}
|
|
2144
2205
|
}
|
|
2145
2206
|
try {
|
|
2146
|
-
const dir =
|
|
2147
|
-
const files = await
|
|
2207
|
+
const dir = path21.join(homeFn(), ".wrongstack");
|
|
2208
|
+
const files = await fsp2.readdir(dir);
|
|
2148
2209
|
const baks = files.filter((f) => f.startsWith("config.json.") && f.endsWith(".bak")).sort().reverse();
|
|
2149
2210
|
for (const f of baks.slice(10)) {
|
|
2150
|
-
await safeDelete(
|
|
2211
|
+
await safeDelete(path21.join(dir, f));
|
|
2151
2212
|
}
|
|
2152
2213
|
} catch {
|
|
2153
2214
|
}
|
|
@@ -2163,8 +2224,8 @@ async function appendHistory(oldCfg, newCfg, description, homeFn = defaultHomeDi
|
|
|
2163
2224
|
snapshotMasked: maskConfigSecrets(newCfg),
|
|
2164
2225
|
diffSummary: diffSummary(oldCfg, newCfg)
|
|
2165
2226
|
};
|
|
2166
|
-
await
|
|
2167
|
-
|
|
2227
|
+
await fsp2.writeFile(
|
|
2228
|
+
path21.join(historyDir(homeFn), `${id}.json`),
|
|
2168
2229
|
JSON.stringify(entry, null, 2),
|
|
2169
2230
|
"utf8"
|
|
2170
2231
|
);
|
|
@@ -2179,7 +2240,7 @@ async function listHistory(homeFn = defaultHomeDir) {
|
|
|
2179
2240
|
}
|
|
2180
2241
|
async function getHistoryEntry(id, homeFn = defaultHomeDir) {
|
|
2181
2242
|
try {
|
|
2182
|
-
const raw = await
|
|
2243
|
+
const raw = await fsp2.readFile(path21.join(historyDir(homeFn), `${id}.json`), "utf8");
|
|
2183
2244
|
return JSON.parse(raw);
|
|
2184
2245
|
} catch {
|
|
2185
2246
|
return null;
|
|
@@ -2191,7 +2252,7 @@ async function restoreFromHistory(id, homeFn = defaultHomeDir) {
|
|
|
2191
2252
|
await backupCurrent(homeFn);
|
|
2192
2253
|
let oldCfg = {};
|
|
2193
2254
|
try {
|
|
2194
|
-
const raw = await
|
|
2255
|
+
const raw = await fsp2.readFile(configPath(homeFn), "utf8");
|
|
2195
2256
|
oldCfg = JSON.parse(raw);
|
|
2196
2257
|
} catch {
|
|
2197
2258
|
}
|
|
@@ -2213,13 +2274,13 @@ async function restoreLast(homeFn = defaultHomeDir) {
|
|
|
2213
2274
|
const cfg = configPath(homeFn);
|
|
2214
2275
|
let oldCfg = {};
|
|
2215
2276
|
try {
|
|
2216
|
-
const raw = await
|
|
2277
|
+
const raw = await fsp2.readFile(cfg, "utf8");
|
|
2217
2278
|
oldCfg = JSON.parse(raw);
|
|
2218
2279
|
} catch {
|
|
2219
2280
|
}
|
|
2220
2281
|
let lastCfg = {};
|
|
2221
2282
|
try {
|
|
2222
|
-
const raw = await
|
|
2283
|
+
const raw = await fsp2.readFile(last, "utf8");
|
|
2223
2284
|
lastCfg = JSON.parse(raw);
|
|
2224
2285
|
} catch {
|
|
2225
2286
|
return { ok: false, error: "No prior backup found" };
|
|
@@ -2497,7 +2558,7 @@ async function resolveModelSelection(answer, models, provider, _registry, render
|
|
|
2497
2558
|
}
|
|
2498
2559
|
async function pathExists(file) {
|
|
2499
2560
|
try {
|
|
2500
|
-
await
|
|
2561
|
+
await fsp2.access(file);
|
|
2501
2562
|
return true;
|
|
2502
2563
|
} catch {
|
|
2503
2564
|
return false;
|
|
@@ -2508,10 +2569,10 @@ async function detectPackageManager(root, declared) {
|
|
|
2508
2569
|
const name = declared.split("@")[0];
|
|
2509
2570
|
if (name) return name;
|
|
2510
2571
|
}
|
|
2511
|
-
if (await pathExists(
|
|
2512
|
-
if (await pathExists(
|
|
2513
|
-
if (await pathExists(
|
|
2514
|
-
if (await pathExists(
|
|
2572
|
+
if (await pathExists(path21.join(root, "pnpm-lock.yaml"))) return "pnpm";
|
|
2573
|
+
if (await pathExists(path21.join(root, "bun.lockb"))) return "bun";
|
|
2574
|
+
if (await pathExists(path21.join(root, "bun.lock"))) return "bun";
|
|
2575
|
+
if (await pathExists(path21.join(root, "yarn.lock"))) return "yarn";
|
|
2515
2576
|
return "npm";
|
|
2516
2577
|
}
|
|
2517
2578
|
function hasUsableScript(scripts, name) {
|
|
@@ -2532,7 +2593,7 @@ function parseMakeTargets(makefile) {
|
|
|
2532
2593
|
async function detectProjectFacts(root) {
|
|
2533
2594
|
const facts = { hints: [] };
|
|
2534
2595
|
try {
|
|
2535
|
-
const pkg = JSON.parse(await
|
|
2596
|
+
const pkg = JSON.parse(await fsp2.readFile(path21.join(root, "package.json"), "utf8"));
|
|
2536
2597
|
const scripts = pkg.scripts ?? {};
|
|
2537
2598
|
const pm = await detectPackageManager(root, pkg.packageManager);
|
|
2538
2599
|
if (hasUsableScript(scripts, "build")) facts.build = `${pm} run build`;
|
|
@@ -2546,14 +2607,14 @@ async function detectProjectFacts(root) {
|
|
|
2546
2607
|
} catch {
|
|
2547
2608
|
}
|
|
2548
2609
|
try {
|
|
2549
|
-
if (!await pathExists(
|
|
2610
|
+
if (!await pathExists(path21.join(root, "pyproject.toml"))) throw new Error("not python");
|
|
2550
2611
|
facts.test ??= "pytest";
|
|
2551
2612
|
facts.lint ??= "ruff check .";
|
|
2552
2613
|
facts.hints.push("pyproject.toml");
|
|
2553
2614
|
} catch {
|
|
2554
2615
|
}
|
|
2555
2616
|
try {
|
|
2556
|
-
if (!await pathExists(
|
|
2617
|
+
if (!await pathExists(path21.join(root, "go.mod"))) throw new Error("not go");
|
|
2557
2618
|
facts.build ??= "go build ./...";
|
|
2558
2619
|
facts.test ??= "go test ./...";
|
|
2559
2620
|
facts.run ??= "go run .";
|
|
@@ -2561,7 +2622,7 @@ async function detectProjectFacts(root) {
|
|
|
2561
2622
|
} catch {
|
|
2562
2623
|
}
|
|
2563
2624
|
try {
|
|
2564
|
-
if (!await pathExists(
|
|
2625
|
+
if (!await pathExists(path21.join(root, "Cargo.toml"))) throw new Error("not rust");
|
|
2565
2626
|
facts.build ??= "cargo build";
|
|
2566
2627
|
facts.test ??= "cargo test";
|
|
2567
2628
|
facts.lint ??= "cargo clippy";
|
|
@@ -2570,7 +2631,7 @@ async function detectProjectFacts(root) {
|
|
|
2570
2631
|
} catch {
|
|
2571
2632
|
}
|
|
2572
2633
|
try {
|
|
2573
|
-
const makefile = await
|
|
2634
|
+
const makefile = await fsp2.readFile(path21.join(root, "Makefile"), "utf8");
|
|
2574
2635
|
const targets = parseMakeTargets(makefile);
|
|
2575
2636
|
facts.build ??= targets.has("build") ? "make build" : "make";
|
|
2576
2637
|
if (targets.has("test")) facts.test ??= "make test";
|
|
@@ -3206,10 +3267,10 @@ function buildInitCommand(opts) {
|
|
|
3206
3267
|
description: "Create .wrongstack/AGENTS.md project context for the system prompt.",
|
|
3207
3268
|
async run(args, ctx) {
|
|
3208
3269
|
const force = args.trim() === "--force";
|
|
3209
|
-
const dir =
|
|
3210
|
-
const file =
|
|
3270
|
+
const dir = path21.join(ctx.projectRoot, ".wrongstack");
|
|
3271
|
+
const file = path21.join(dir, "AGENTS.md");
|
|
3211
3272
|
try {
|
|
3212
|
-
await
|
|
3273
|
+
await fsp2.access(file);
|
|
3213
3274
|
if (!force) {
|
|
3214
3275
|
const msg2 = `AGENTS.md already exists at ${file}. Use "/init --force" to overwrite.`;
|
|
3215
3276
|
opts.renderer.writeWarning(msg2);
|
|
@@ -3219,8 +3280,8 @@ function buildInitCommand(opts) {
|
|
|
3219
3280
|
}
|
|
3220
3281
|
const detected = await detectProjectFacts(ctx.projectRoot);
|
|
3221
3282
|
const body = renderAgentsTemplate(detected);
|
|
3222
|
-
await
|
|
3223
|
-
await
|
|
3283
|
+
await fsp2.mkdir(dir, { recursive: true });
|
|
3284
|
+
await fsp2.writeFile(file, body, "utf8");
|
|
3224
3285
|
if (detected.hints.length > 0) {
|
|
3225
3286
|
const msg2 = `Wrote ${file}
|
|
3226
3287
|
Pre-filled: ${detected.hints.join(", ")}. Edit the file with project context and instructions the system prompt should carry.`;
|
|
@@ -3318,7 +3379,7 @@ function buildMetricsCommand(opts) {
|
|
|
3318
3379
|
function buildPlanCommand(opts) {
|
|
3319
3380
|
return {
|
|
3320
3381
|
name: "plan",
|
|
3321
|
-
description: "Strategic plan board: /plan [show|add <title>|done <id|#>|remove <id|#>|clear]",
|
|
3382
|
+
description: "Strategic plan board: /plan [show|add <title>|start <id|#>|done <id|#>|remove <id|#>|promote <id|#> [subtask ...]|derive <id|#>|template [list|use <name>]|clear]",
|
|
3322
3383
|
async run(args) {
|
|
3323
3384
|
const planPath = opts.planPath;
|
|
3324
3385
|
if (!planPath) return { message: "Plan storage is not configured for this session." };
|
|
@@ -3362,6 +3423,58 @@ ${formatPlan(updated)}` };
|
|
|
3362
3423
|
await savePlan(planPath, updated);
|
|
3363
3424
|
return { message: formatPlan(updated) };
|
|
3364
3425
|
}
|
|
3426
|
+
case "promote": {
|
|
3427
|
+
if (!restJoined) return { message: "Usage: /plan promote <id|index> [subtask ...]" };
|
|
3428
|
+
const [target, ...subtasks] = restJoined.split(/\s+/);
|
|
3429
|
+
if (!target) return { message: "Usage: /plan promote <id|index> [subtask ...]" };
|
|
3430
|
+
const derived = deriveTodosFromPlanItem(plan, target, subtasks.length > 0 ? subtasks : void 0);
|
|
3431
|
+
if (!derived) return { message: `No plan item matched "${target}".` };
|
|
3432
|
+
await savePlan(planPath, derived.plan);
|
|
3433
|
+
if (ctx) {
|
|
3434
|
+
ctx.state.replaceTodos(derived.todos);
|
|
3435
|
+
}
|
|
3436
|
+
return {
|
|
3437
|
+
message: `Promoted to ${derived.todos.length} todo(s):
|
|
3438
|
+
${formatTodosList(derived.todos)}
|
|
3439
|
+
|
|
3440
|
+
${formatPlan(derived.plan)}`
|
|
3441
|
+
};
|
|
3442
|
+
}
|
|
3443
|
+
case "derive": {
|
|
3444
|
+
if (!restJoined) return { message: "Usage: /plan derive <id|index>" };
|
|
3445
|
+
const derived = deriveTodosFromPlanItem(plan, restJoined);
|
|
3446
|
+
if (!derived) return { message: `No plan item matched "${restJoined}".` };
|
|
3447
|
+
await savePlan(planPath, derived.plan);
|
|
3448
|
+
if (ctx) {
|
|
3449
|
+
ctx.state.replaceTodos(derived.todos);
|
|
3450
|
+
}
|
|
3451
|
+
return {
|
|
3452
|
+
message: `Derived ${derived.todos.length} todo(s):
|
|
3453
|
+
${formatTodosList(derived.todos)}
|
|
3454
|
+
|
|
3455
|
+
${formatPlan(derived.plan)}`
|
|
3456
|
+
};
|
|
3457
|
+
}
|
|
3458
|
+
case "template": {
|
|
3459
|
+
const subVerb = rest[0] ?? "";
|
|
3460
|
+
const subRest = rest.slice(1).join(" ").trim();
|
|
3461
|
+
if (subVerb === "" || subVerb === "list") {
|
|
3462
|
+
return { message: formatPlanTemplates() };
|
|
3463
|
+
}
|
|
3464
|
+
if (subVerb === "use") {
|
|
3465
|
+
if (!subRest) return { message: "Usage: /plan template use <template-name>" };
|
|
3466
|
+
const template = getPlanTemplate(subRest);
|
|
3467
|
+
if (!template) return { message: `Unknown template "${subRest}". Use /plan template list to see available templates.` };
|
|
3468
|
+
let updated = plan;
|
|
3469
|
+
for (const item of template.items) {
|
|
3470
|
+
({ plan: updated } = addPlanItem(updated, item.title, item.details));
|
|
3471
|
+
}
|
|
3472
|
+
await savePlan(planPath, updated);
|
|
3473
|
+
return { message: `Applied template "${template.name}" (${template.items.length} items):
|
|
3474
|
+
${formatPlan(updated)}` };
|
|
3475
|
+
}
|
|
3476
|
+
return { message: `Unknown template subcommand "${subVerb}". Try: list | use <name>` };
|
|
3477
|
+
}
|
|
3365
3478
|
case "clear": {
|
|
3366
3479
|
const updated = clearPlan(plan);
|
|
3367
3480
|
await savePlan(planPath, updated);
|
|
@@ -3369,7 +3482,7 @@ ${formatPlan(updated)}` };
|
|
|
3369
3482
|
}
|
|
3370
3483
|
default:
|
|
3371
3484
|
return {
|
|
3372
|
-
message: `Unknown subcommand "${verb}". Try: show | add <title> | start <id|#> | done <id|#> | remove <id|#> | clear`
|
|
3485
|
+
message: `Unknown subcommand "${verb}". Try: show | add <title> | start <id|#> | done <id|#> | remove <id|#> | promote <id|#> | derive <id|#> | template [list|use <name>] | clear`
|
|
3373
3486
|
};
|
|
3374
3487
|
}
|
|
3375
3488
|
}
|
|
@@ -3827,12 +3940,231 @@ ${lines.join("\n\n")}
|
|
|
3827
3940
|
}
|
|
3828
3941
|
};
|
|
3829
3942
|
}
|
|
3943
|
+
function buildSecurityCommand(opts) {
|
|
3944
|
+
return {
|
|
3945
|
+
name: "security",
|
|
3946
|
+
description: "Security scanning: scan, audit, report",
|
|
3947
|
+
argsHint: "[scan|audit|report] [options]",
|
|
3948
|
+
help: `
|
|
3949
|
+
# /security \u2014 Security Scanner
|
|
3950
|
+
|
|
3951
|
+
Automated security scanning with tech stack detection.
|
|
3952
|
+
|
|
3953
|
+
## Commands
|
|
3954
|
+
|
|
3955
|
+
### /security scan [options]
|
|
3956
|
+
Run a full security scan on the current project.
|
|
3957
|
+
Options:
|
|
3958
|
+
--depth quick|standard|deep Scan depth (default: standard)
|
|
3959
|
+
--format markdown|json|html Report format (default: markdown)
|
|
3960
|
+
|
|
3961
|
+
### /security audit
|
|
3962
|
+
Run dependency audit + security scan.
|
|
3963
|
+
|
|
3964
|
+
### /security report [id]
|
|
3965
|
+
List or view security reports.
|
|
3966
|
+
|
|
3967
|
+
## Examples
|
|
3968
|
+
|
|
3969
|
+
/security scan
|
|
3970
|
+
/security scan --depth deep --format html
|
|
3971
|
+
/security audit
|
|
3972
|
+
/security report
|
|
3973
|
+
`,
|
|
3974
|
+
async run(args, ctx) {
|
|
3975
|
+
const parts = args.trim().split(/\s+/);
|
|
3976
|
+
const subcommand = parts[0] || "";
|
|
3977
|
+
switch (subcommand) {
|
|
3978
|
+
case "scan":
|
|
3979
|
+
return handleScan(parts.slice(1).join(" "), ctx, opts);
|
|
3980
|
+
case "audit":
|
|
3981
|
+
return handleAudit(ctx, opts);
|
|
3982
|
+
case "report":
|
|
3983
|
+
return handleReport(parts[1] || "");
|
|
3984
|
+
default:
|
|
3985
|
+
return { message: getHelpMessage() };
|
|
3986
|
+
}
|
|
3987
|
+
}
|
|
3988
|
+
};
|
|
3989
|
+
}
|
|
3990
|
+
async function handleScan(args, ctx, opts) {
|
|
3991
|
+
const options = parseArgs2(args);
|
|
3992
|
+
const projectRoot = ctx.projectRoot || opts.projectRoot;
|
|
3993
|
+
try {
|
|
3994
|
+
const orchestratorContext = ctx.provider ? ctx : { provider: opts.llmProvider, model: opts.llmModel };
|
|
3995
|
+
if (!orchestratorContext.provider) {
|
|
3996
|
+
return { message: "\u274C Security scan requires an active LLM provider. No provider configured." };
|
|
3997
|
+
}
|
|
3998
|
+
const result = await defaultOrchestrator.run(orchestratorContext, {
|
|
3999
|
+
projectRoot,
|
|
4000
|
+
scanOptions: {
|
|
4001
|
+
depth: options.depth || "standard",
|
|
4002
|
+
includeSecrets: true,
|
|
4003
|
+
includeInjection: true,
|
|
4004
|
+
includeConfig: true
|
|
4005
|
+
},
|
|
4006
|
+
reportOptions: {
|
|
4007
|
+
format: options.format || "markdown"
|
|
4008
|
+
}
|
|
4009
|
+
});
|
|
4010
|
+
const summary = result.scanResult.summary;
|
|
4011
|
+
const status = summary.total === 0 ? "\u2705 No issues found" : `\u26A0\uFE0F Found ${summary.total} issues`;
|
|
4012
|
+
const reportContent = result.synthesizedReport || `# Security Scan Complete
|
|
4013
|
+
|
|
4014
|
+
**Project:** ${projectRoot}
|
|
4015
|
+
**Tech Stack:** ${result.detectionResult.detectedStacks[0]?.stack || "unknown"}
|
|
4016
|
+
**Scanned Files:** ${result.scanResult.scannedFiles}
|
|
4017
|
+
**Duration:** ${result.scanResult.scanDurationMs}ms
|
|
4018
|
+
|
|
4019
|
+
## Summary
|
|
4020
|
+
|
|
4021
|
+
| Severity | Count |
|
|
4022
|
+
|----------|-------|
|
|
4023
|
+
| \u{1F534} Critical | ${summary.critical} |
|
|
4024
|
+
| \u{1F7E0} High | ${summary.high} |
|
|
4025
|
+
| \u{1F7E1} Medium | ${summary.medium} |
|
|
4026
|
+
| \u{1F7E2} Low | ${summary.low} |
|
|
4027
|
+
|
|
4028
|
+
**Status:** ${status}
|
|
4029
|
+
|
|
4030
|
+
**Report:** ${result.reportPath}
|
|
4031
|
+
`;
|
|
4032
|
+
return {
|
|
4033
|
+
message: reportContent,
|
|
4034
|
+
metadata: {
|
|
4035
|
+
scanResult: result.scanResult,
|
|
4036
|
+
reportPath: result.reportPath,
|
|
4037
|
+
techStack: result.detectionResult.detectedStacks[0]
|
|
4038
|
+
}
|
|
4039
|
+
};
|
|
4040
|
+
} catch (error) {
|
|
4041
|
+
return { message: `\u274C Scan failed: ${error}` };
|
|
4042
|
+
}
|
|
4043
|
+
}
|
|
4044
|
+
async function handleAudit(ctx, opts) {
|
|
4045
|
+
const projectRoot = ctx.projectRoot || opts.projectRoot;
|
|
4046
|
+
try {
|
|
4047
|
+
const orchestratorContext = ctx.provider ? ctx : { provider: opts.llmProvider, model: opts.llmModel };
|
|
4048
|
+
if (!orchestratorContext.provider) {
|
|
4049
|
+
return { message: "\u274C Security audit requires an active LLM provider. No provider configured." };
|
|
4050
|
+
}
|
|
4051
|
+
const result = await defaultOrchestrator.run(orchestratorContext, {
|
|
4052
|
+
projectRoot,
|
|
4053
|
+
reportOptions: { format: "markdown" }
|
|
4054
|
+
});
|
|
4055
|
+
const summary = result.scanResult.summary;
|
|
4056
|
+
const depIssues = summary.critical + summary.high;
|
|
4057
|
+
const reportContent = result.synthesizedReport || `# Security Audit Complete
|
|
4058
|
+
|
|
4059
|
+
**Project:** ${projectRoot}
|
|
4060
|
+
**Tech Stack:** ${result.detectionResult.detectedStacks[0]?.stack || "unknown"}
|
|
4061
|
+
|
|
4062
|
+
## Dependency Health
|
|
4063
|
+
|
|
4064
|
+
| Status | Count |
|
|
4065
|
+
|--------|-------|
|
|
4066
|
+
| Critical Issues | ${summary.critical} |
|
|
4067
|
+
| High Priority | ${summary.high} |
|
|
4068
|
+
| Medium Priority | ${summary.medium} |
|
|
4069
|
+
| Low Priority | ${summary.low} |
|
|
4070
|
+
|
|
4071
|
+
${depIssues === 0 ? "\u2705 No known vulnerabilities detected" : `\u26A0\uFE0F ${depIssues} vulnerabilities need attention`}
|
|
4072
|
+
|
|
4073
|
+
**Full Report:** ${result.reportPath}
|
|
4074
|
+
`;
|
|
4075
|
+
return {
|
|
4076
|
+
message: reportContent,
|
|
4077
|
+
metadata: {
|
|
4078
|
+
scanResult: result.scanResult,
|
|
4079
|
+
reportPath: result.reportPath
|
|
4080
|
+
}
|
|
4081
|
+
};
|
|
4082
|
+
} catch (error) {
|
|
4083
|
+
return { message: `\u274C Audit failed: ${error}` };
|
|
4084
|
+
}
|
|
4085
|
+
}
|
|
4086
|
+
async function handleReport(reportId) {
|
|
4087
|
+
const reportsDir = "security-reports";
|
|
4088
|
+
try {
|
|
4089
|
+
const files = await readdir(reportsDir);
|
|
4090
|
+
const reports = files.filter((f) => f.startsWith("security-report-") && (f.endsWith(".md") || f.endsWith(".json"))).sort().reverse();
|
|
4091
|
+
if (!reportId) {
|
|
4092
|
+
if (reports.length === 0) {
|
|
4093
|
+
return { message: "\u{1F4ED} No security reports found. Run `/security scan` first." };
|
|
4094
|
+
}
|
|
4095
|
+
const list = reports.map((r, i) => {
|
|
4096
|
+
const date = r.replace("security-report-", "").replace(/\.(md|json)$/, "");
|
|
4097
|
+
return ` ${i + 1}. ${date}`;
|
|
4098
|
+
}).join("\n");
|
|
4099
|
+
return { message: `# Available Security Reports
|
|
4100
|
+
|
|
4101
|
+
${list}
|
|
4102
|
+
|
|
4103
|
+
Use \`/security report <number>\` to view a specific report.` };
|
|
4104
|
+
}
|
|
4105
|
+
const index = parseInt(reportId, 10) - 1;
|
|
4106
|
+
if (!isNaN(index) && reports[index]) {
|
|
4107
|
+
const content = await readFile(join(reportsDir, reports[index]), "utf-8");
|
|
4108
|
+
return { message: `# Security Report
|
|
4109
|
+
|
|
4110
|
+
${content}` };
|
|
4111
|
+
}
|
|
4112
|
+
const match = reports.find((r) => r.includes(reportId));
|
|
4113
|
+
if (match) {
|
|
4114
|
+
const content = await readFile(join(reportsDir, match), "utf-8");
|
|
4115
|
+
return { message: `# Security Report
|
|
4116
|
+
|
|
4117
|
+
${content}` };
|
|
4118
|
+
}
|
|
4119
|
+
return { message: `\u274C Report "${reportId}" not found. Use \`/security report\` to see available reports.` };
|
|
4120
|
+
} catch {
|
|
4121
|
+
return { message: "\u{1F4ED} No security reports found. Run `/security scan` first." };
|
|
4122
|
+
}
|
|
4123
|
+
}
|
|
4124
|
+
function parseArgs2(args) {
|
|
4125
|
+
const result = {};
|
|
4126
|
+
const parts = args.split(/\s+/);
|
|
4127
|
+
for (let i = 0; i < parts.length; i++) {
|
|
4128
|
+
const part = parts[i];
|
|
4129
|
+
if (!part || !part.startsWith("--")) continue;
|
|
4130
|
+
const key = part.slice(2);
|
|
4131
|
+
const next = parts[i + 1];
|
|
4132
|
+
if (next && !next.startsWith("--")) {
|
|
4133
|
+
result[key] = next;
|
|
4134
|
+
i++;
|
|
4135
|
+
} else {
|
|
4136
|
+
result[key] = "true";
|
|
4137
|
+
}
|
|
4138
|
+
}
|
|
4139
|
+
return result;
|
|
4140
|
+
}
|
|
4141
|
+
function getHelpMessage() {
|
|
4142
|
+
return `# /security \u2014 Security Scanner
|
|
4143
|
+
|
|
4144
|
+
**Available Commands:**
|
|
4145
|
+
|
|
4146
|
+
1. **/security scan** \u2014 Run full security scan
|
|
4147
|
+
\`/security scan --depth deep --format html\`
|
|
4148
|
+
|
|
4149
|
+
2. **/security audit** \u2014 Run dependency audit + security scan
|
|
4150
|
+
|
|
4151
|
+
3. **/security report** \u2014 List available reports
|
|
4152
|
+
|
|
4153
|
+
**Features:**
|
|
4154
|
+
- Automatic tech stack detection
|
|
4155
|
+
- Dynamic security skill generation
|
|
4156
|
+
- Secrets, injection, and config vulnerability scanning
|
|
4157
|
+
- Markdown/JSON/HTML reports
|
|
4158
|
+
- .gitignore auto-update
|
|
4159
|
+
|
|
4160
|
+
Run \`/security scan\` to start.`;
|
|
4161
|
+
}
|
|
3830
4162
|
function makeInstaller(opts, projectRoot, global) {
|
|
3831
|
-
const globalRoot =
|
|
4163
|
+
const globalRoot = path21.join(os6.homedir(), ".wrongstack");
|
|
3832
4164
|
return new SkillInstaller({
|
|
3833
|
-
manifestPath:
|
|
3834
|
-
projectSkillsDir:
|
|
3835
|
-
globalSkillsDir:
|
|
4165
|
+
manifestPath: path21.join(globalRoot, "installed-skills.json"),
|
|
4166
|
+
projectSkillsDir: path21.join(projectRoot, ".wrongstack", "skills"),
|
|
4167
|
+
globalSkillsDir: path21.join(globalRoot, "skills"),
|
|
3836
4168
|
projectHash: projectHash(projectRoot),
|
|
3837
4169
|
skillLoader: opts.skillLoader
|
|
3838
4170
|
});
|
|
@@ -4024,7 +4356,8 @@ function buildBuiltinSlashCommands(opts) {
|
|
|
4024
4356
|
buildExitCommand(opts),
|
|
4025
4357
|
buildCommitCommand(),
|
|
4026
4358
|
buildGitcheckCommand(),
|
|
4027
|
-
buildPushCommand()
|
|
4359
|
+
buildPushCommand(),
|
|
4360
|
+
buildSecurityCommand(opts)
|
|
4028
4361
|
];
|
|
4029
4362
|
}
|
|
4030
4363
|
|
|
@@ -4043,13 +4376,13 @@ var MANIFESTS = [
|
|
|
4043
4376
|
];
|
|
4044
4377
|
async function detectProjectKind(projectRoot) {
|
|
4045
4378
|
try {
|
|
4046
|
-
await
|
|
4379
|
+
await fsp2.access(path21.join(projectRoot, ".wrongstack", "AGENTS.md"));
|
|
4047
4380
|
return "initialized";
|
|
4048
4381
|
} catch {
|
|
4049
4382
|
}
|
|
4050
4383
|
for (const m of MANIFESTS) {
|
|
4051
4384
|
try {
|
|
4052
|
-
await
|
|
4385
|
+
await fsp2.access(path21.join(projectRoot, m));
|
|
4053
4386
|
return "project";
|
|
4054
4387
|
} catch {
|
|
4055
4388
|
}
|
|
@@ -4057,12 +4390,12 @@ async function detectProjectKind(projectRoot) {
|
|
|
4057
4390
|
return "empty";
|
|
4058
4391
|
}
|
|
4059
4392
|
async function scaffoldAgentsMd(projectRoot) {
|
|
4060
|
-
const dir =
|
|
4061
|
-
const file =
|
|
4393
|
+
const dir = path21.join(projectRoot, ".wrongstack");
|
|
4394
|
+
const file = path21.join(dir, "AGENTS.md");
|
|
4062
4395
|
const facts = await detectProjectFacts(projectRoot);
|
|
4063
4396
|
const body = renderAgentsTemplate(facts);
|
|
4064
|
-
await
|
|
4065
|
-
await
|
|
4397
|
+
await fsp2.mkdir(dir, { recursive: true });
|
|
4398
|
+
await fsp2.writeFile(file, body, "utf8");
|
|
4066
4399
|
return file;
|
|
4067
4400
|
}
|
|
4068
4401
|
async function runProjectCheck(opts) {
|
|
@@ -4071,7 +4404,7 @@ async function runProjectCheck(opts) {
|
|
|
4071
4404
|
if (kind === "initialized") {
|
|
4072
4405
|
renderer.write(
|
|
4073
4406
|
`
|
|
4074
|
-
${color.green("\u2713")} Project initialized ${color.dim(`(${
|
|
4407
|
+
${color.green("\u2713")} Project initialized ${color.dim(`(${path21.join(projectRoot, ".wrongstack", "AGENTS.md")})`)}
|
|
4075
4408
|
`
|
|
4076
4409
|
);
|
|
4077
4410
|
return true;
|
|
@@ -4098,10 +4431,10 @@ async function runProjectCheck(opts) {
|
|
|
4098
4431
|
}
|
|
4099
4432
|
return true;
|
|
4100
4433
|
}
|
|
4101
|
-
const gitDir =
|
|
4434
|
+
const gitDir = path21.join(projectRoot, ".git");
|
|
4102
4435
|
let hasGit = false;
|
|
4103
4436
|
try {
|
|
4104
|
-
await
|
|
4437
|
+
await fsp2.access(gitDir);
|
|
4105
4438
|
hasGit = true;
|
|
4106
4439
|
} catch {
|
|
4107
4440
|
}
|
|
@@ -4399,14 +4732,14 @@ function summarize(value, name) {
|
|
|
4399
4732
|
if (typeof v === "object" && v !== null) {
|
|
4400
4733
|
const o = v;
|
|
4401
4734
|
if (name === "edit") {
|
|
4402
|
-
const
|
|
4735
|
+
const path22 = typeof o["path"] === "string" ? o["path"] : "";
|
|
4403
4736
|
const reps = typeof o["replacements"] === "number" ? o["replacements"] : 0;
|
|
4404
|
-
return `${
|
|
4737
|
+
return `${path22} ${reps} replacement${reps === 1 ? "" : "s"}`.trim();
|
|
4405
4738
|
}
|
|
4406
4739
|
if (name === "write") {
|
|
4407
|
-
const
|
|
4740
|
+
const path22 = typeof o["path"] === "string" ? o["path"] : "";
|
|
4408
4741
|
const bytes = typeof o["bytes"] === "number" ? o["bytes"] : void 0;
|
|
4409
|
-
return bytes !== void 0 ? `${
|
|
4742
|
+
return bytes !== void 0 ? `${path22} ${bytes}B` : path22;
|
|
4410
4743
|
}
|
|
4411
4744
|
if (typeof o["count"] === "number") {
|
|
4412
4745
|
return `${o["count"]} match${o["count"] === 1 ? "" : "es"}`;
|
|
@@ -5002,7 +5335,7 @@ async function readKeyInput(deps, intent) {
|
|
|
5002
5335
|
async function loadProviders(deps) {
|
|
5003
5336
|
let raw;
|
|
5004
5337
|
try {
|
|
5005
|
-
raw = await
|
|
5338
|
+
raw = await fsp2.readFile(deps.globalConfigPath, "utf8");
|
|
5006
5339
|
} catch {
|
|
5007
5340
|
return {};
|
|
5008
5341
|
}
|
|
@@ -5018,7 +5351,7 @@ async function loadProviders(deps) {
|
|
|
5018
5351
|
async function mutateProviders(deps, mutator) {
|
|
5019
5352
|
let raw;
|
|
5020
5353
|
try {
|
|
5021
|
-
raw = await
|
|
5354
|
+
raw = await fsp2.readFile(deps.globalConfigPath, "utf8");
|
|
5022
5355
|
} catch {
|
|
5023
5356
|
raw = "{}";
|
|
5024
5357
|
}
|
|
@@ -5218,7 +5551,7 @@ var doctorCmd = async (_args, deps) => {
|
|
|
5218
5551
|
});
|
|
5219
5552
|
}
|
|
5220
5553
|
try {
|
|
5221
|
-
await
|
|
5554
|
+
await fsp2.access(deps.paths.secretsKey);
|
|
5222
5555
|
checks.push({ name: "secret vault", status: "ok", detail: deps.paths.secretsKey });
|
|
5223
5556
|
} catch {
|
|
5224
5557
|
checks.push({
|
|
@@ -5228,10 +5561,10 @@ var doctorCmd = async (_args, deps) => {
|
|
|
5228
5561
|
});
|
|
5229
5562
|
}
|
|
5230
5563
|
try {
|
|
5231
|
-
await
|
|
5232
|
-
const probe =
|
|
5233
|
-
await
|
|
5234
|
-
await
|
|
5564
|
+
await fsp2.mkdir(deps.paths.projectSessions, { recursive: true });
|
|
5565
|
+
const probe = path21.join(deps.paths.projectSessions, `.probe-${Date.now()}`);
|
|
5566
|
+
await fsp2.writeFile(probe, "");
|
|
5567
|
+
await fsp2.unlink(probe);
|
|
5235
5568
|
checks.push({ name: "sessions writable", status: "ok", detail: deps.paths.projectSessions });
|
|
5236
5569
|
} catch (err) {
|
|
5237
5570
|
checks.push({
|
|
@@ -5332,8 +5665,8 @@ var exportCmd = async (args, deps) => {
|
|
|
5332
5665
|
return 1;
|
|
5333
5666
|
}
|
|
5334
5667
|
if (output) {
|
|
5335
|
-
await
|
|
5336
|
-
await
|
|
5668
|
+
await fsp2.mkdir(path21.dirname(path21.resolve(deps.cwd, output)), { recursive: true });
|
|
5669
|
+
await fsp2.writeFile(path21.resolve(deps.cwd, output), rendered, "utf8");
|
|
5337
5670
|
deps.renderer.write(`Wrote ${rendered.length} bytes to ${output}
|
|
5338
5671
|
`);
|
|
5339
5672
|
} else {
|
|
@@ -5390,17 +5723,17 @@ var initCmd = async (_args, deps) => {
|
|
|
5390
5723
|
} else {
|
|
5391
5724
|
deps.renderer.writeInfo(`Found API key in env (${provider.envVars.join(" / ")}).`);
|
|
5392
5725
|
}
|
|
5393
|
-
await
|
|
5726
|
+
await fsp2.mkdir(deps.paths.globalRoot, { recursive: true });
|
|
5394
5727
|
const config = { version: 1, provider: providerId, model: modelId };
|
|
5395
5728
|
if (apiKey) config.apiKey = apiKey;
|
|
5396
|
-
const keyFile =
|
|
5729
|
+
const keyFile = path21.join(path21.dirname(deps.paths.globalConfig), ".key");
|
|
5397
5730
|
const vault = new DefaultSecretVault$1({ keyFile });
|
|
5398
5731
|
const encrypted = encryptConfigSecrets(config, vault);
|
|
5399
5732
|
await atomicWrite(deps.paths.globalConfig, JSON.stringify(encrypted, null, 2));
|
|
5400
|
-
await
|
|
5401
|
-
const agentsFile =
|
|
5733
|
+
await fsp2.mkdir(path21.join(deps.projectRoot, ".wrongstack"), { recursive: true });
|
|
5734
|
+
const agentsFile = path21.join(deps.projectRoot, ".wrongstack", "AGENTS.md");
|
|
5402
5735
|
try {
|
|
5403
|
-
await
|
|
5736
|
+
await fsp2.access(agentsFile);
|
|
5404
5737
|
} catch {
|
|
5405
5738
|
const detected2 = await detectProjectFacts(deps.projectRoot);
|
|
5406
5739
|
await atomicWrite(agentsFile, renderAgentsTemplate(detected2));
|
|
@@ -5476,7 +5809,7 @@ async function addMcpServer(args, deps) {
|
|
|
5476
5809
|
serverCfg.enabled = enable;
|
|
5477
5810
|
let existing = {};
|
|
5478
5811
|
try {
|
|
5479
|
-
existing = JSON.parse(await
|
|
5812
|
+
existing = JSON.parse(await fsp2.readFile(deps.paths.globalConfig, "utf8"));
|
|
5480
5813
|
} catch {
|
|
5481
5814
|
}
|
|
5482
5815
|
const mcpServers = existing.mcpServers ?? {};
|
|
@@ -5496,7 +5829,7 @@ async function addMcpServer(args, deps) {
|
|
|
5496
5829
|
async function removeMcpServer(name, deps) {
|
|
5497
5830
|
let existing = {};
|
|
5498
5831
|
try {
|
|
5499
|
-
existing = JSON.parse(await
|
|
5832
|
+
existing = JSON.parse(await fsp2.readFile(deps.paths.globalConfig, "utf8"));
|
|
5500
5833
|
} catch {
|
|
5501
5834
|
deps.renderer.writeError("No config file found.\n");
|
|
5502
5835
|
return 1;
|
|
@@ -5617,7 +5950,7 @@ function renderConfiguredPlugins(config) {
|
|
|
5617
5950
|
}
|
|
5618
5951
|
async function readConfig(file) {
|
|
5619
5952
|
try {
|
|
5620
|
-
return JSON.parse(await
|
|
5953
|
+
return JSON.parse(await fsp2.readFile(file, "utf8"));
|
|
5621
5954
|
} catch {
|
|
5622
5955
|
return {};
|
|
5623
5956
|
}
|
|
@@ -5708,9 +6041,9 @@ var usageCmd = async (_args, deps) => {
|
|
|
5708
6041
|
return 0;
|
|
5709
6042
|
};
|
|
5710
6043
|
var projectsCmd = async (_args, deps) => {
|
|
5711
|
-
const projectsRoot =
|
|
6044
|
+
const projectsRoot = path21.join(deps.paths.globalRoot, "projects");
|
|
5712
6045
|
try {
|
|
5713
|
-
const entries = await
|
|
6046
|
+
const entries = await fsp2.readdir(projectsRoot);
|
|
5714
6047
|
if (entries.length === 0) {
|
|
5715
6048
|
deps.renderer.write("No projects tracked.\n");
|
|
5716
6049
|
return 0;
|
|
@@ -5718,7 +6051,7 @@ var projectsCmd = async (_args, deps) => {
|
|
|
5718
6051
|
for (const hash of entries) {
|
|
5719
6052
|
try {
|
|
5720
6053
|
const meta = JSON.parse(
|
|
5721
|
-
await
|
|
6054
|
+
await fsp2.readFile(path21.join(projectsRoot, hash, "meta.json"), "utf8")
|
|
5722
6055
|
);
|
|
5723
6056
|
deps.renderer.write(
|
|
5724
6057
|
` ${color.dim(hash)} ${color.dim(meta.lastSeen ?? "")} ${meta.root ?? "?"}
|
|
@@ -5861,9 +6194,210 @@ function redactKeys(obj) {
|
|
|
5861
6194
|
}
|
|
5862
6195
|
return out;
|
|
5863
6196
|
}
|
|
6197
|
+
var sessionsFleetCmd = async (args, deps) => {
|
|
6198
|
+
const runId = args.find((a) => !a.startsWith("-"));
|
|
6199
|
+
if (runId) {
|
|
6200
|
+
return showFleetRun(runId, deps);
|
|
6201
|
+
}
|
|
6202
|
+
return listFleetRuns(deps);
|
|
6203
|
+
};
|
|
6204
|
+
async function listFleetRuns(deps) {
|
|
6205
|
+
let entries = [];
|
|
6206
|
+
try {
|
|
6207
|
+
entries = await fsp2.readdir(deps.paths.projectSessions);
|
|
6208
|
+
} catch {
|
|
6209
|
+
deps.renderer.writeError(`Cannot read projectSessions: ${deps.paths.projectSessions}
|
|
6210
|
+
`);
|
|
6211
|
+
return 1;
|
|
6212
|
+
}
|
|
6213
|
+
const runs = [];
|
|
6214
|
+
for (const id of entries) {
|
|
6215
|
+
const runDir = path21.join(deps.paths.projectSessions, id);
|
|
6216
|
+
let stat3;
|
|
6217
|
+
try {
|
|
6218
|
+
stat3 = await fsp2.stat(runDir);
|
|
6219
|
+
} catch {
|
|
6220
|
+
continue;
|
|
6221
|
+
}
|
|
6222
|
+
if (!stat3.isDirectory()) continue;
|
|
6223
|
+
let manifest = false;
|
|
6224
|
+
let checkpoint = false;
|
|
6225
|
+
let subagentCount = 0;
|
|
6226
|
+
let subagentsDir;
|
|
6227
|
+
try {
|
|
6228
|
+
await fsp2.access(path21.join(runDir, "fleet.json"));
|
|
6229
|
+
manifest = true;
|
|
6230
|
+
} catch {
|
|
6231
|
+
}
|
|
6232
|
+
try {
|
|
6233
|
+
await fsp2.access(path21.join(runDir, "checkpoint.json"));
|
|
6234
|
+
checkpoint = true;
|
|
6235
|
+
} catch {
|
|
6236
|
+
}
|
|
6237
|
+
try {
|
|
6238
|
+
subagentsDir = path21.join(runDir, "subagents");
|
|
6239
|
+
const files = await fsp2.readdir(subagentsDir);
|
|
6240
|
+
subagentCount = files.filter((f) => f.endsWith(".jsonl")).length;
|
|
6241
|
+
} catch {
|
|
6242
|
+
}
|
|
6243
|
+
runs.push({ id, manifest, checkpoint, subagents: subagentCount });
|
|
6244
|
+
}
|
|
6245
|
+
if (runs.length === 0) {
|
|
6246
|
+
deps.renderer.write("No fleet runs found.\n");
|
|
6247
|
+
return 0;
|
|
6248
|
+
}
|
|
6249
|
+
deps.renderer.write(color.bold("\nFleet Runs\n") + "\n");
|
|
6250
|
+
for (const r of runs.sort((a, b) => b.id.localeCompare(a.id))) {
|
|
6251
|
+
const checkpointFlag = r.checkpoint ? color.green("\u2713") : color.dim("\u25CB");
|
|
6252
|
+
const manifestFlag = r.manifest ? color.green("\u2713") : color.dim("\u25CB");
|
|
6253
|
+
const subagentInfo = r.subagents > 0 ? color.dim(` ${r.subagents} subagent jsonl`) : "";
|
|
6254
|
+
deps.renderer.write(
|
|
6255
|
+
` ${color.bold(r.id)} ${checkpointFlag} checkpoint ${manifestFlag} manifest${subagentInfo}
|
|
6256
|
+
`
|
|
6257
|
+
);
|
|
6258
|
+
}
|
|
6259
|
+
deps.renderer.write(
|
|
6260
|
+
`
|
|
6261
|
+
${color.dim("Run `wrongstack sessions fleet <runId>` for details.")}
|
|
6262
|
+
`
|
|
6263
|
+
);
|
|
6264
|
+
return 0;
|
|
6265
|
+
}
|
|
6266
|
+
async function showFleetRun(runId, deps) {
|
|
6267
|
+
const runDir = path21.join(deps.paths.projectSessions, runId);
|
|
6268
|
+
let stat3;
|
|
6269
|
+
try {
|
|
6270
|
+
stat3 = await fsp2.stat(runDir);
|
|
6271
|
+
} catch {
|
|
6272
|
+
deps.renderer.writeError(`Fleet run not found: ${runId}
|
|
6273
|
+
`);
|
|
6274
|
+
return 1;
|
|
6275
|
+
}
|
|
6276
|
+
if (!stat3.isDirectory()) {
|
|
6277
|
+
deps.renderer.writeError(`Not a directory: ${runId}
|
|
6278
|
+
`);
|
|
6279
|
+
return 1;
|
|
6280
|
+
}
|
|
6281
|
+
deps.renderer.write(color.bold(`
|
|
6282
|
+
Fleet Run: ${runId}
|
|
6283
|
+
`) + "\n");
|
|
6284
|
+
const manifestPath = path21.join(runDir, "fleet.json");
|
|
6285
|
+
let manifestData = null;
|
|
6286
|
+
try {
|
|
6287
|
+
manifestData = await fsp2.readFile(manifestPath, "utf8");
|
|
6288
|
+
const manifest = JSON.parse(manifestData);
|
|
6289
|
+
const subagents = manifest.subagents ?? [];
|
|
6290
|
+
const tasks = manifest.tasks ?? [];
|
|
6291
|
+
const completed = tasks.filter((t) => t.status === "completed" || t.status === "failed" || t.status === "timeout" || t.status === "stopped");
|
|
6292
|
+
deps.renderer.write(
|
|
6293
|
+
` ${color.green("\u2713")} fleet.json \u2014 ${subagents.length} subagent(s), ${completed.length}/${tasks.length} tasks done
|
|
6294
|
+
`
|
|
6295
|
+
);
|
|
6296
|
+
} catch {
|
|
6297
|
+
deps.renderer.write(` ${color.dim("\u25CB")} fleet.json \u2014 not found
|
|
6298
|
+
`);
|
|
6299
|
+
}
|
|
6300
|
+
const checkpointPath = path21.join(runDir, "checkpoint.json");
|
|
6301
|
+
let checkpointData = null;
|
|
6302
|
+
try {
|
|
6303
|
+
checkpointData = await fsp2.readFile(checkpointPath, "utf8");
|
|
6304
|
+
const snap = JSON.parse(checkpointData);
|
|
6305
|
+
const lockPath = `${checkpointPath}.lock`;
|
|
6306
|
+
let lockStatus = color.dim("\u25CB no lock");
|
|
6307
|
+
try {
|
|
6308
|
+
const lockRaw = await fsp2.readFile(lockPath, "utf8");
|
|
6309
|
+
const lock = JSON.parse(lockRaw);
|
|
6310
|
+
lockStatus = `${color.yellow("\u25B8")} lock held by pid ${lock.pid} on ${lock.hostname} (started ${lock.startedAt})`;
|
|
6311
|
+
} catch {
|
|
6312
|
+
lockStatus = color.green("\u2713 no lock (safe to resume)");
|
|
6313
|
+
}
|
|
6314
|
+
deps.renderer.write(
|
|
6315
|
+
` ${color.green("\u2713")} checkpoint.json \u2014 updated ${snap.updatedAt}, ${snap.spawnCount} spawns, ${snap.tasks?.length ?? 0} tasks tracked
|
|
6316
|
+
${lockStatus}
|
|
6317
|
+
`
|
|
6318
|
+
);
|
|
6319
|
+
} catch {
|
|
6320
|
+
deps.renderer.write(` ${color.dim("\u25CB")} checkpoint.json \u2014 not found
|
|
6321
|
+
`);
|
|
6322
|
+
}
|
|
6323
|
+
if (checkpointData) {
|
|
6324
|
+
try {
|
|
6325
|
+
const snap = JSON.parse(checkpointData);
|
|
6326
|
+
if (snap.subagents?.length) {
|
|
6327
|
+
deps.renderer.write("\n Subagents:\n");
|
|
6328
|
+
for (const s of snap.subagents) {
|
|
6329
|
+
deps.renderer.write(
|
|
6330
|
+
` ${color.cyan(s.id)} ${s.name ? `${s.name} ` : ""}${s.provider ? `(${s.provider}/${s.model})` : ""} spawned ${s.spawnedAt}
|
|
6331
|
+
`
|
|
6332
|
+
);
|
|
6333
|
+
}
|
|
6334
|
+
}
|
|
6335
|
+
if (snap.tasks?.length) {
|
|
6336
|
+
deps.renderer.write("\n Tasks:\n");
|
|
6337
|
+
for (const t of snap.tasks) {
|
|
6338
|
+
deps.renderer.write(
|
|
6339
|
+
` ${color.dim(t.taskId)} ${t.status} ${t.description ? t.description.slice(0, 50) : "(no description)"}
|
|
6340
|
+
`
|
|
6341
|
+
);
|
|
6342
|
+
}
|
|
6343
|
+
}
|
|
6344
|
+
} catch {
|
|
6345
|
+
}
|
|
6346
|
+
}
|
|
6347
|
+
const subagentsDir = path21.join(runDir, "subagents");
|
|
6348
|
+
let subagentFiles = [];
|
|
6349
|
+
try {
|
|
6350
|
+
subagentFiles = await fsp2.readdir(subagentsDir);
|
|
6351
|
+
subagentFiles = subagentFiles.filter((f) => f.endsWith(".jsonl"));
|
|
6352
|
+
} catch {
|
|
6353
|
+
}
|
|
6354
|
+
if (subagentFiles.length > 0) {
|
|
6355
|
+
deps.renderer.write(`
|
|
6356
|
+
Subagent transcripts (${subagentFiles.length}):
|
|
6357
|
+
`);
|
|
6358
|
+
for (const f of subagentFiles.sort()) {
|
|
6359
|
+
const filePath = path21.join(subagentsDir, f);
|
|
6360
|
+
let size;
|
|
6361
|
+
try {
|
|
6362
|
+
const s = await fsp2.stat(filePath);
|
|
6363
|
+
size = s.size;
|
|
6364
|
+
} catch {
|
|
6365
|
+
size = 0;
|
|
6366
|
+
}
|
|
6367
|
+
const sizeStr = size > 1024 * 1024 ? `${(size / 1024 / 1024).toFixed(1)}MB` : `${(size / 1024).toFixed(0)}KB`;
|
|
6368
|
+
deps.renderer.write(` ${color.dim(f)} ${color.dim(sizeStr)}
|
|
6369
|
+
`);
|
|
6370
|
+
}
|
|
6371
|
+
} else {
|
|
6372
|
+
deps.renderer.write(`
|
|
6373
|
+
${color.dim("\u25CB")} No subagent transcripts
|
|
6374
|
+
`);
|
|
6375
|
+
}
|
|
6376
|
+
const sharedDir = path21.join(runDir, "shared");
|
|
6377
|
+
try {
|
|
6378
|
+
const files = await fsp2.readdir(sharedDir);
|
|
6379
|
+
deps.renderer.write(`
|
|
6380
|
+
Shared scratchpad: ${files.length} file(s)
|
|
6381
|
+
`);
|
|
6382
|
+
} catch {
|
|
6383
|
+
deps.renderer.write(`
|
|
6384
|
+
${color.dim("\u25CB")} No shared scratchpad
|
|
6385
|
+
`);
|
|
6386
|
+
}
|
|
6387
|
+
deps.renderer.write(
|
|
6388
|
+
`
|
|
6389
|
+
${color.dim("Resume: wrongstack --resume " + runId)}
|
|
6390
|
+
`
|
|
6391
|
+
);
|
|
6392
|
+
return 0;
|
|
6393
|
+
}
|
|
5864
6394
|
|
|
5865
6395
|
// src/subcommands/handlers/sessions-config.ts
|
|
5866
|
-
var sessionsCmd = async (
|
|
6396
|
+
var sessionsCmd = async (args, deps) => {
|
|
6397
|
+
const sub = args[0];
|
|
6398
|
+
if (sub === "fleet") {
|
|
6399
|
+
return sessionsFleetCmd(args.slice(1), deps);
|
|
6400
|
+
}
|
|
5867
6401
|
if (!deps.sessionStore) {
|
|
5868
6402
|
deps.renderer.writeError("No session store available.");
|
|
5869
6403
|
return 1;
|
|
@@ -5995,7 +6529,7 @@ function parseRewindFlags(args) {
|
|
|
5995
6529
|
var rewindCmd = async (args, deps) => {
|
|
5996
6530
|
const flags = parseRewindFlags(args);
|
|
5997
6531
|
const wpaths = resolveWstackPaths({ projectRoot: deps.projectRoot });
|
|
5998
|
-
const sessionsDir =
|
|
6532
|
+
const sessionsDir = path21.join(wpaths.globalRoot, "sessions");
|
|
5999
6533
|
const rewind = new DefaultSessionRewinder(sessionsDir);
|
|
6000
6534
|
let sessionId = args.find((a) => !a.startsWith("--"));
|
|
6001
6535
|
if (!sessionId) {
|
|
@@ -6205,22 +6739,22 @@ function fmtDuration(ms) {
|
|
|
6205
6739
|
const remMin = m - h * 60;
|
|
6206
6740
|
return `${h}h${remMin}m`;
|
|
6207
6741
|
}
|
|
6208
|
-
function fmtTaskResultLine(r,
|
|
6742
|
+
function fmtTaskResultLine(r, color33) {
|
|
6209
6743
|
const stats = `${r.iterations}it ${r.toolCalls}tc ${fmtDuration(r.durationMs)}`;
|
|
6210
6744
|
const errMsg = typeof r.error === "string" ? r.error : r.error?.message;
|
|
6211
6745
|
const errKind = typeof r.error === "object" ? r.error?.kind : void 0;
|
|
6212
6746
|
const errTail = errMsg ? ` \u2014 ${errMsg.replace(/\s+/g, " ").slice(0, 80)}${errMsg.length > 80 ? "\u2026" : ""}` : "";
|
|
6213
|
-
const errKindChip = errKind ?
|
|
6214
|
-
const errSnip = errMsg || errKind ? `${errKindChip}${
|
|
6747
|
+
const errKindChip = errKind ? color33.dim(` [${errKind}]`) : "";
|
|
6748
|
+
const errSnip = errMsg || errKind ? `${errKindChip}${color33.dim(errTail)}` : "";
|
|
6215
6749
|
switch (r.status) {
|
|
6216
6750
|
case "success":
|
|
6217
|
-
return { mark:
|
|
6751
|
+
return { mark: color33.green("\u2713"), stats, tail: "" };
|
|
6218
6752
|
case "timeout":
|
|
6219
|
-
return { mark:
|
|
6753
|
+
return { mark: color33.yellow("\u23F1"), stats: `${color33.yellow("timeout")} ${stats}`, tail: errSnip };
|
|
6220
6754
|
case "stopped":
|
|
6221
|
-
return { mark:
|
|
6755
|
+
return { mark: color33.dim("\u2298"), stats: `${color33.dim("stopped")} ${stats}`, tail: errSnip };
|
|
6222
6756
|
case "failed":
|
|
6223
|
-
return { mark:
|
|
6757
|
+
return { mark: color33.red("\u2717"), stats: `${color33.red("failed")} ${stats}`, tail: errSnip };
|
|
6224
6758
|
}
|
|
6225
6759
|
}
|
|
6226
6760
|
|
|
@@ -6230,7 +6764,7 @@ function resolveBundledSkillsDir() {
|
|
|
6230
6764
|
try {
|
|
6231
6765
|
const req2 = createRequire(import.meta.url);
|
|
6232
6766
|
const corePkg = req2.resolve("@wrongstack/core/package.json");
|
|
6233
|
-
return
|
|
6767
|
+
return path21.join(path21.dirname(corePkg), "skills");
|
|
6234
6768
|
} catch {
|
|
6235
6769
|
return void 0;
|
|
6236
6770
|
}
|
|
@@ -6956,7 +7490,7 @@ async function execute(deps) {
|
|
|
6956
7490
|
supportsVision,
|
|
6957
7491
|
attachments,
|
|
6958
7492
|
effectiveMaxContext,
|
|
6959
|
-
projectName:
|
|
7493
|
+
projectName: path21.basename(projectRoot) || void 0,
|
|
6960
7494
|
getAutonomy,
|
|
6961
7495
|
skillLoader
|
|
6962
7496
|
});
|
|
@@ -6974,7 +7508,7 @@ async function execute(deps) {
|
|
|
6974
7508
|
supportsVision,
|
|
6975
7509
|
attachments,
|
|
6976
7510
|
effectiveMaxContext,
|
|
6977
|
-
projectName:
|
|
7511
|
+
projectName: path21.basename(projectRoot) || void 0,
|
|
6978
7512
|
getAutonomy,
|
|
6979
7513
|
skillLoader
|
|
6980
7514
|
});
|
|
@@ -7057,12 +7591,18 @@ var MultiAgentHost = class {
|
|
|
7057
7591
|
// by passing maxToolCalls/maxIterations through the spawn tool.
|
|
7058
7592
|
};
|
|
7059
7593
|
if (this.opts.directorMode) {
|
|
7594
|
+
const defaultScratchpad = this.opts.sharedScratchpadPath || (this.opts.sessionsRoot && this.opts.directorRunId ? path21.join(this.opts.sessionsRoot, this.opts.directorRunId, "shared") : void 0);
|
|
7060
7595
|
this.director = new Director({
|
|
7061
7596
|
config: coordinatorConfig,
|
|
7062
7597
|
manifestPath: this.opts.manifestPath,
|
|
7063
|
-
sharedScratchpadPath:
|
|
7598
|
+
sharedScratchpadPath: defaultScratchpad,
|
|
7064
7599
|
stateCheckpointPath: this.opts.stateCheckpointPath,
|
|
7065
7600
|
sessionWriter: this.opts.sessionWriter,
|
|
7601
|
+
directorBudget: this.opts.directorBudget,
|
|
7602
|
+
maxBudgetExtensions: this.opts.maxBudgetExtensions,
|
|
7603
|
+
checkpointDebounceMs: this.opts.checkpointDebounceMs,
|
|
7604
|
+
sessionsRoot: this.opts.sessionsRoot,
|
|
7605
|
+
directorRunId: this.opts.directorRunId,
|
|
7066
7606
|
// Autonomy: allow nested directors a few levels deep. Default
|
|
7067
7607
|
// is 2 (root + one tier of workers), which trips the moment a
|
|
7068
7608
|
// worker tries to recurse into "let me delegate the parser
|
|
@@ -7246,7 +7786,7 @@ var MultiAgentHost = class {
|
|
|
7246
7786
|
model: opts?.model,
|
|
7247
7787
|
tools: opts?.tools
|
|
7248
7788
|
};
|
|
7249
|
-
const transcriptPath = this.sessionFactory ?
|
|
7789
|
+
const transcriptPath = this.sessionFactory ? path21.join(this.sessionFactory.dir, `${subagentConfig.name}.jsonl`) : void 0;
|
|
7250
7790
|
if (this.director) {
|
|
7251
7791
|
const subagentId = await this.director.spawn(subagentConfig);
|
|
7252
7792
|
const taskId2 = randomUUID();
|
|
@@ -7310,7 +7850,16 @@ var MultiAgentHost = class {
|
|
|
7310
7850
|
});
|
|
7311
7851
|
}
|
|
7312
7852
|
status() {
|
|
7313
|
-
const
|
|
7853
|
+
const activeSubagentIds = /* @__PURE__ */ new Set();
|
|
7854
|
+
if (this.coordinator) {
|
|
7855
|
+
const s = this.coordinator.getStatus();
|
|
7856
|
+
for (const a of s.subagents) {
|
|
7857
|
+
if (a.status === "running" || a.status === "idle") {
|
|
7858
|
+
activeSubagentIds.add(a.id);
|
|
7859
|
+
}
|
|
7860
|
+
}
|
|
7861
|
+
}
|
|
7862
|
+
const pending = Array.from(this.pending.entries()).filter(([, v]) => activeSubagentIds.has(v.subagentId)).map(([taskId, v]) => ({
|
|
7314
7863
|
taskId,
|
|
7315
7864
|
description: v.description,
|
|
7316
7865
|
subagentId: v.subagentId
|
|
@@ -7406,16 +7955,16 @@ var MultiAgentHost = class {
|
|
|
7406
7955
|
}
|
|
7407
7956
|
this.opts.directorMode = true;
|
|
7408
7957
|
if (this.opts.fleetRoot && !this.opts.manifestPath) {
|
|
7409
|
-
this.opts.manifestPath =
|
|
7958
|
+
this.opts.manifestPath = path21.join(this.opts.fleetRoot, "fleet.json");
|
|
7410
7959
|
}
|
|
7411
7960
|
if (this.opts.fleetRoot && !this.opts.sharedScratchpadPath) {
|
|
7412
|
-
this.opts.sharedScratchpadPath =
|
|
7961
|
+
this.opts.sharedScratchpadPath = path21.join(this.opts.fleetRoot, "shared");
|
|
7413
7962
|
}
|
|
7414
7963
|
if (this.opts.fleetRoot && !this.opts.sessionsRoot) {
|
|
7415
|
-
this.opts.sessionsRoot =
|
|
7964
|
+
this.opts.sessionsRoot = path21.join(this.opts.fleetRoot, "subagents");
|
|
7416
7965
|
}
|
|
7417
7966
|
if (this.opts.fleetRoot && !this.opts.stateCheckpointPath) {
|
|
7418
|
-
this.opts.stateCheckpointPath =
|
|
7967
|
+
this.opts.stateCheckpointPath = path21.join(this.opts.fleetRoot, "director-state.json");
|
|
7419
7968
|
}
|
|
7420
7969
|
await this.ensureDirector();
|
|
7421
7970
|
return this.director ?? null;
|
|
@@ -7536,11 +8085,11 @@ var SessionStats = class {
|
|
|
7536
8085
|
if (e.name === "bash") this.bashCommands++;
|
|
7537
8086
|
else if (e.name === "fetch") this.fetches++;
|
|
7538
8087
|
if (!e.ok) return;
|
|
7539
|
-
const
|
|
7540
|
-
if (e.name === "read" &&
|
|
7541
|
-
else if (e.name === "edit" &&
|
|
7542
|
-
else if (e.name === "write" &&
|
|
7543
|
-
this.writtenPaths.add(
|
|
8088
|
+
const path22 = typeof input?.path === "string" ? input.path : void 0;
|
|
8089
|
+
if (e.name === "read" && path22) this.readPaths.add(path22);
|
|
8090
|
+
else if (e.name === "edit" && path22) this.editedPaths.add(path22);
|
|
8091
|
+
else if (e.name === "write" && path22) {
|
|
8092
|
+
this.writtenPaths.add(path22);
|
|
7544
8093
|
const content = typeof input?.content === "string" ? input.content : "";
|
|
7545
8094
|
this.bytesWritten += Buffer.byteLength(content, "utf8");
|
|
7546
8095
|
}
|
|
@@ -7871,12 +8420,12 @@ async function setupSession(params) {
|
|
|
7871
8420
|
}
|
|
7872
8421
|
const sessionRef = { current: session };
|
|
7873
8422
|
await recoveryLock.write(session.id).catch(() => void 0);
|
|
7874
|
-
const attachments = new DefaultAttachmentStore({ spoolDir:
|
|
7875
|
-
const queueStore = new QueueStore({ dir:
|
|
8423
|
+
const attachments = new DefaultAttachmentStore({ spoolDir: path21.join(wpaths.projectSessions, session.id, "attachments") });
|
|
8424
|
+
const queueStore = new QueueStore({ dir: path21.join(wpaths.projectSessions, session.id) });
|
|
7876
8425
|
const ctxSignal = new AbortController().signal;
|
|
7877
8426
|
const context = new Context({ systemPrompt, provider, session, signal: ctxSignal, tokenCounter, cwd, projectRoot, model: config.model });
|
|
7878
8427
|
if (restoredMessages.length > 0) context.state.replaceMessages(restoredMessages);
|
|
7879
|
-
const todosCheckpointPath =
|
|
8428
|
+
const todosCheckpointPath = path21.join(wpaths.projectSessions, `${session.id}.todos.json`);
|
|
7880
8429
|
if (resumeId) {
|
|
7881
8430
|
try {
|
|
7882
8431
|
const restoredTodos = await loadTodosCheckpoint(todosCheckpointPath);
|
|
@@ -7888,12 +8437,13 @@ async function setupSession(params) {
|
|
|
7888
8437
|
}
|
|
7889
8438
|
}
|
|
7890
8439
|
const detachTodosCheckpoint = attachTodosCheckpoint(context.state, todosCheckpointPath, session.id);
|
|
7891
|
-
const planPath =
|
|
8440
|
+
const planPath = path21.join(wpaths.projectSessions, `${session.id}.plan.json`);
|
|
7892
8441
|
context.state.setMeta("plan.path", planPath);
|
|
8442
|
+
let dirState;
|
|
7893
8443
|
if (resumeId) {
|
|
7894
8444
|
try {
|
|
7895
|
-
const fleetRoot =
|
|
7896
|
-
|
|
8445
|
+
const fleetRoot = path21.join(wpaths.projectSessions, session.id);
|
|
8446
|
+
dirState = await loadDirectorState(path21.join(fleetRoot, "director-state.json"));
|
|
7897
8447
|
if (dirState) {
|
|
7898
8448
|
const tCounts = {};
|
|
7899
8449
|
for (const t of dirState.tasks) tCounts[t.status] = (tCounts[t.status] ?? 0) + 1;
|
|
@@ -7912,7 +8462,7 @@ async function setupSession(params) {
|
|
|
7912
8462
|
} catch {
|
|
7913
8463
|
}
|
|
7914
8464
|
}
|
|
7915
|
-
return { session, sessionRef, context, restoredMessages, attachments, recoveryLock, queueStore, planPath, detachTodosCheckpoint };
|
|
8465
|
+
return { session, sessionRef, context, restoredMessages, attachments, recoveryLock, queueStore, planPath, detachTodosCheckpoint, priorFleetState: dirState ?? void 0 };
|
|
7916
8466
|
}
|
|
7917
8467
|
|
|
7918
8468
|
// src/index.ts
|
|
@@ -7920,7 +8470,7 @@ function resolveBundledSkillsDir2() {
|
|
|
7920
8470
|
try {
|
|
7921
8471
|
const req2 = createRequire(import.meta.url);
|
|
7922
8472
|
const corePkg = req2.resolve("@wrongstack/core/package.json");
|
|
7923
|
-
return
|
|
8473
|
+
return path21.join(path21.dirname(corePkg), "skills");
|
|
7924
8474
|
} catch {
|
|
7925
8475
|
return void 0;
|
|
7926
8476
|
}
|
|
@@ -8027,7 +8577,7 @@ async function main(argv) {
|
|
|
8027
8577
|
modeId,
|
|
8028
8578
|
modePrompt,
|
|
8029
8579
|
modelCapabilities,
|
|
8030
|
-
planPath: () => sessionRef.current ?
|
|
8580
|
+
planPath: () => sessionRef.current ? path21.join(wpaths.projectSessions, `${sessionRef.current.id}.plan.json`) : void 0
|
|
8031
8581
|
})
|
|
8032
8582
|
);
|
|
8033
8583
|
const toolRegistry = new ToolRegistry();
|
|
@@ -8055,7 +8605,7 @@ async function main(argv) {
|
|
|
8055
8605
|
name: "session-store",
|
|
8056
8606
|
check: async () => {
|
|
8057
8607
|
try {
|
|
8058
|
-
await
|
|
8608
|
+
await fsp2.access(wpaths.projectSessions);
|
|
8059
8609
|
return { status: "healthy" };
|
|
8060
8610
|
} catch (e) {
|
|
8061
8611
|
return { status: "unhealthy", detail: e instanceof Error ? e.message : "access denied" };
|
|
@@ -8072,7 +8622,7 @@ async function main(argv) {
|
|
|
8072
8622
|
const dumpMetrics = () => {
|
|
8073
8623
|
if (!metricsSink) return;
|
|
8074
8624
|
try {
|
|
8075
|
-
const out =
|
|
8625
|
+
const out = path21.join(wpaths.projectSessions, "metrics.json");
|
|
8076
8626
|
const snap = metricsSink.snapshot();
|
|
8077
8627
|
writeFileSync(out, JSON.stringify(snap, null, 2));
|
|
8078
8628
|
} catch {
|
|
@@ -8189,6 +8739,7 @@ async function main(argv) {
|
|
|
8189
8739
|
const queueStore = sessResult.queueStore;
|
|
8190
8740
|
const planPath = sessResult.planPath;
|
|
8191
8741
|
const detachTodosCheckpoint = sessResult.detachTodosCheckpoint;
|
|
8742
|
+
const priorFleetState = sessResult.priorFleetState;
|
|
8192
8743
|
const stats = new SessionStats(events, tokenCounter);
|
|
8193
8744
|
const errorRing = [];
|
|
8194
8745
|
events.on("error", (e) => {
|
|
@@ -8288,15 +8839,15 @@ async function main(argv) {
|
|
|
8288
8839
|
return err instanceof Error ? err.message : String(err);
|
|
8289
8840
|
}
|
|
8290
8841
|
};
|
|
8291
|
-
const directorMode = flags["director"] === true;
|
|
8842
|
+
const directorMode = flags["director"] === true || typeof flags["resume"] === "string";
|
|
8292
8843
|
let director = null;
|
|
8293
8844
|
let autonomyMode = "off";
|
|
8294
|
-
const fleetRoot = directorMode ?
|
|
8295
|
-
const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] :
|
|
8296
|
-
const sharedScratchpadPath = directorMode ?
|
|
8297
|
-
const subagentSessionsRoot = directorMode ?
|
|
8298
|
-
const stateCheckpointPath = directorMode ?
|
|
8299
|
-
const fleetRootForPromotion =
|
|
8845
|
+
const fleetRoot = directorMode ? path21.join(wpaths.projectSessions, session.id) : void 0;
|
|
8846
|
+
const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] : path21.join(fleetRoot, "fleet.json") : void 0;
|
|
8847
|
+
const sharedScratchpadPath = directorMode ? path21.join(fleetRoot, "shared") : void 0;
|
|
8848
|
+
const subagentSessionsRoot = directorMode ? path21.join(fleetRoot, "subagents") : void 0;
|
|
8849
|
+
const stateCheckpointPath = directorMode ? path21.join(fleetRoot, "director-state.json") : void 0;
|
|
8850
|
+
const fleetRootForPromotion = path21.join(wpaths.projectSessions, session.id);
|
|
8300
8851
|
const multiAgentHost = new MultiAgentHost(
|
|
8301
8852
|
{
|
|
8302
8853
|
container,
|
|
@@ -8336,6 +8887,7 @@ async function main(argv) {
|
|
|
8336
8887
|
if (directorMode) {
|
|
8337
8888
|
director = await multiAgentHost.ensureDirector();
|
|
8338
8889
|
if (director) {
|
|
8890
|
+
if (priorFleetState) director.setCheckpointState(priorFleetState);
|
|
8339
8891
|
for (const tool of director.tools(FLEET_ROSTER)) {
|
|
8340
8892
|
toolRegistry.register(tool);
|
|
8341
8893
|
}
|
|
@@ -8364,11 +8916,15 @@ async function main(argv) {
|
|
|
8364
8916
|
renderer,
|
|
8365
8917
|
memoryStore,
|
|
8366
8918
|
context,
|
|
8919
|
+
cwd,
|
|
8920
|
+
projectRoot,
|
|
8367
8921
|
metricsSink,
|
|
8368
8922
|
healthRegistry,
|
|
8369
8923
|
planPath,
|
|
8370
8924
|
modeStore,
|
|
8371
8925
|
fleetStreamController,
|
|
8926
|
+
llmProvider: provider,
|
|
8927
|
+
llmModel: config.model,
|
|
8372
8928
|
onSpawn: async (description, spawnOpts) => {
|
|
8373
8929
|
const { subagentId, taskId } = await multiAgentHost.spawn(description, spawnOpts);
|
|
8374
8930
|
const tags = [];
|
|
@@ -8475,32 +9031,32 @@ async function main(argv) {
|
|
|
8475
9031
|
return `Unknown fleet action: ${action}`;
|
|
8476
9032
|
},
|
|
8477
9033
|
onFleetLog: async (subagentId, mode) => {
|
|
8478
|
-
const subagentsRoot =
|
|
9034
|
+
const subagentsRoot = path21.join(fleetRootForPromotion, "subagents");
|
|
8479
9035
|
let runDirs;
|
|
8480
9036
|
try {
|
|
8481
|
-
runDirs = await
|
|
9037
|
+
runDirs = await fsp2.readdir(subagentsRoot);
|
|
8482
9038
|
} catch {
|
|
8483
9039
|
return "No fleet transcripts on disk \u2014 no subagents have been spawned for this session.";
|
|
8484
9040
|
}
|
|
8485
9041
|
const found = [];
|
|
8486
9042
|
for (const runId of runDirs) {
|
|
8487
|
-
const runDir =
|
|
9043
|
+
const runDir = path21.join(subagentsRoot, runId);
|
|
8488
9044
|
let files;
|
|
8489
9045
|
try {
|
|
8490
|
-
files = await
|
|
9046
|
+
files = await fsp2.readdir(runDir);
|
|
8491
9047
|
} catch {
|
|
8492
9048
|
continue;
|
|
8493
9049
|
}
|
|
8494
9050
|
for (const f of files) {
|
|
8495
9051
|
if (!f.endsWith(".jsonl")) continue;
|
|
8496
|
-
const full =
|
|
9052
|
+
const full = path21.join(runDir, f);
|
|
8497
9053
|
try {
|
|
8498
|
-
const
|
|
9054
|
+
const stat3 = await fsp2.stat(full);
|
|
8499
9055
|
found.push({
|
|
8500
9056
|
runId,
|
|
8501
9057
|
subagentId: f.replace(/\.jsonl$/, ""),
|
|
8502
9058
|
file: full,
|
|
8503
|
-
size:
|
|
9059
|
+
size: stat3.size
|
|
8504
9060
|
});
|
|
8505
9061
|
} catch {
|
|
8506
9062
|
}
|
|
@@ -8534,7 +9090,7 @@ async function main(argv) {
|
|
|
8534
9090
|
].join("\n");
|
|
8535
9091
|
}
|
|
8536
9092
|
const t = matches[0];
|
|
8537
|
-
const raw = await
|
|
9093
|
+
const raw = await fsp2.readFile(t.file, "utf8");
|
|
8538
9094
|
if (mode === "raw") return raw;
|
|
8539
9095
|
const lines = raw.split("\n").filter((l) => l.trim());
|
|
8540
9096
|
const counts = {};
|
|
@@ -8590,7 +9146,7 @@ async function main(argv) {
|
|
|
8590
9146
|
}
|
|
8591
9147
|
const dir = await multiAgentHost.ensureDirector();
|
|
8592
9148
|
if (!dir) return "Director is not available.";
|
|
8593
|
-
const dirStatePath =
|
|
9149
|
+
const dirStatePath = path21.join(fleetRootForPromotion, "director-state.json");
|
|
8594
9150
|
const prior = await loadDirectorState(dirStatePath);
|
|
8595
9151
|
if (!prior) {
|
|
8596
9152
|
return "No prior director-state.json found \u2014 nothing to retry.";
|
|
@@ -8661,9 +9217,9 @@ async function main(argv) {
|
|
|
8661
9217
|
for (const tool of director2.tools(FLEET_ROSTER)) {
|
|
8662
9218
|
toolRegistry.register(tool);
|
|
8663
9219
|
}
|
|
8664
|
-
const mp =
|
|
8665
|
-
const sp =
|
|
8666
|
-
const ss =
|
|
9220
|
+
const mp = path21.join(fleetRootForPromotion, "fleet.json");
|
|
9221
|
+
const sp = path21.join(fleetRootForPromotion, "shared");
|
|
9222
|
+
const ss = path21.join(fleetRootForPromotion, "subagents");
|
|
8667
9223
|
const lines = [
|
|
8668
9224
|
`${color.green("\u2713")} Promoted to director mode.`,
|
|
8669
9225
|
` Roster: ${Object.keys(FLEET_ROSTER).join(", ")}`,
|