@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 CHANGED
@@ -1,8 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import * as path20 from 'path';
3
- import { color, allServers, DefaultPathResolver, TOKENS, DefaultSystemPromptBuilder, ToolRegistry, createContextManagerTool, EventBus, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, SlashCommandRegistry, loadPlugins, createDelegateTool, FLEET_ROSTER, DefaultLogger, DefaultModelsRegistry, ProviderRegistry, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, AutoCompactionMiddleware, estimateRequestTokens, Agent, makeDirectorSessionFactory, Director, DefaultMultiAgentCoordinator, makeAgentSubagentRunner, resolveWstackPaths, DefaultSecretVault, migratePlaintextSecrets, DefaultConfigLoader, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, atomicWrite, AutoApprovePermissionPolicy, formatContextWindowModeList, repairToolUseAdjacency, getContextWindowMode, resolveContextWindowPolicy, formatTodosList, emptyPlan, clearPlan, savePlan, removePlanItem, formatPlan, setPlanItemStatus, addPlanItem, SpecStore, TaskGraphStore, SpecVersioning, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, DefaultTaskStore, TaskTracker, InputBuilder, projectHash, decryptConfigSecrets, encryptConfigSecrets as encryptConfigSecrets$1, DefaultPluginAPI } from '@wrongstack/core';
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
- if (!activeBuilder) return null;
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
- if (!activeBuilder) return null;
72
- return activeBuilder.getPhase();
81
+ return sddState.getPhase();
73
82
  }
74
83
  async function trySaveSpecFromAIOutput(aiOutput) {
75
- if (!activeBuilder) return false;
76
- const spec = activeBuilder.tryParseSpecFromOutput(aiOutput);
84
+ const builder = sddState.getBuilder();
85
+ if (!builder) return false;
86
+ const spec = builder.tryParseSpecFromOutput(aiOutput);
77
87
  if (!spec) return false;
78
- activeBuilder.setSpec(spec);
88
+ builder.setSpec(spec);
79
89
  return true;
80
90
  }
81
91
  async function trySaveTasksFromAIOutput(aiOutput) {
82
- if (!activeBuilder) return false;
83
- const session = activeBuilder.getSession();
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 = activeBuilder.extractJSONArray(aiOutput);
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
- activeTaskStore = store;
117
- activeTaskTracker = tracker;
118
- activeTaskGraphId = graph.id;
119
- activeBuilder.setTaskGraphId(graph.id);
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
- if (!activeTaskTracker) return null;
124
- const progress = activeTaskTracker.getProgress();
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
- if (!activeTaskTracker) return null;
134
- const nodes = activeTaskTracker.getAllNodes();
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
- if (!activeTaskTracker) return false;
144
- const nodes = activeTaskTracker.getAllNodes({ status: ["pending", "in_progress"] });
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
- activeTaskTracker.updateNodeStatus(match.id, "completed");
163
+ tracker.updateNodeStatus(match.id, "completed");
150
164
  return true;
151
165
  }
152
166
  function autoDetectTaskCompletion(aiOutput) {
153
- if (!activeTaskTracker) return 0;
154
- const pending = activeTaskTracker.getAllNodes({ status: ["pending", "in_progress"] });
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
- activeTaskTracker.updateNodeStatus(node.id, "completed");
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
- activeTaskTracker.updateNodeStatus(match.id, "completed");
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
- activeTaskTracker.updateNodeStatus(match.id, "completed");
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
- activeTaskTracker.updateNodeStatus(node.id, "completed");
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
- activeTaskTracker.updateNodeStatus(match.id, "completed");
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
- if (!activeBuilder) return false;
221
- const session = activeBuilder.getSession();
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
- activeBuilder.setImplementation(plan);
243
+ builder.setImplementation(plan);
228
244
  return true;
229
245
  }
230
246
  }
231
247
  if (aiOutput.length > 100 && !aiOutput.includes("```json")) {
232
- activeBuilder.setImplementation(aiOutput.trim());
248
+ builder.setImplementation(aiOutput.trim());
233
249
  return true;
234
250
  }
235
251
  return false;
236
252
  }
237
253
  function getActiveBuilder() {
238
- return activeBuilder;
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 = path20.join(projectRoot, ".wrongstack", "specs");
248
- const graphsDir = path20.join(projectRoot, ".wrongstack", "task-graphs");
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 (!activeBuilder && !forceFlag) {
264
- const sessionPath = path20.join(projectRoot, ".wrongstack", "sdd-session.json");
280
+ if (!sddState.getBuilder() && !forceFlag) {
281
+ const sessionPath = path21.join(projectRoot, ".wrongstack", "sdd-session.json");
265
282
  try {
266
- const fsp = await import('fs/promises');
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
- activeTaskStore = null;
294
- activeTaskTracker = null;
295
- activeTaskGraphId = null;
309
+ sddState.clearTaskState();
296
310
  const projectContext = await gatherProjectContext(projectRoot);
297
- activeBuilder = new AISpecBuilder({
311
+ sddState.setBuilder(new AISpecBuilder({
298
312
  store: specStore,
299
313
  projectContext,
300
314
  minQuestions: 2,
301
315
  maxQuestions: 10,
302
- sessionPath: path20.join(projectRoot, ".wrongstack", "sdd-session.json")
303
- });
304
- activeBuilder.startSession(title);
305
- const aiPrompt = activeBuilder.getAIPrompt();
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
- if (!activeBuilder) {
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 = activeBuilder.getSession().phase;
350
+ const phase = builder.getSession().phase;
335
351
  if (phase === "questioning") {
336
- const sddCtx = activeBuilder.getAIPrompt();
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 = activeBuilder.getSession().spec;
364
+ const spec = builder.getSession().spec;
349
365
  if (!spec) {
350
366
  return { message: "No spec to approve." };
351
367
  }
352
- await activeBuilder.saveSpec();
368
+ await builder.saveSpec();
353
369
  versioning.recordVersion(spec, "Initial spec approved");
354
- activeBuilder.approve();
355
- const implPrompt = activeBuilder.getAIPrompt();
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
- activeBuilder.approve();
374
- const execPrompt = activeBuilder.getAIPrompt();
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
- if (!activeBuilder) {
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 = activeBuilder.getSession();
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 = activeBuilder.getAIPrompt();
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
- if (!activeBuilder) {
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 session = activeBuilder.getSession();
420
- if (!session.implementation) {
437
+ const planSession = planBuilder.getSession();
438
+ if (!planSession.implementation) {
421
439
  return {
422
- message: session.phase === "implementation" ? "No implementation plan yet. The AI will generate it after /sdd approve." : "No implementation plan in this session."
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
- session.implementation
447
+ planSession.implementation
430
448
  ].join("\n")
431
449
  };
432
450
  }
433
451
  case "spec": {
434
- if (!activeBuilder) {
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 session = activeBuilder.getSession();
438
- if (!session.spec) {
456
+ const specSession = specBuilder.getSession();
457
+ if (!specSession.spec) {
439
458
  return {
440
- message: session.phase === "questioning" ? "No spec generated yet. Keep answering the AI's questions." : "No spec in this session."
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 = session.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
- if (!activeTaskTracker) {
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 = activeTaskTracker.getAllNodes();
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 = activeTaskTracker.getProgress();
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
- if (!activeTaskTracker) {
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 = activeTaskTracker.getAllNodes({ status: ["pending", "in_progress"] });
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
- activeTaskTracker.updateNodeStatus(node.id, "completed");
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
- activeTaskTracker.updateNodeStatus(match.id, "completed");
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 = activeTaskTracker.getProgress();
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
- if (!activeBuilder) {
545
+ const statusBuilder = sddState.getBuilder();
546
+ if (!statusBuilder) {
525
547
  return { message: "No active SDD session." };
526
548
  }
527
- const session = activeBuilder.getSession();
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 = path20.join(projectRoot, ".wrongstack", "sdd-session.json");
584
+ const sessionPath = path21.join(projectRoot, ".wrongstack", "sdd-session.json");
563
585
  let deletedFromDisk = false;
564
586
  try {
565
- const fsp = await import('fs/promises');
566
- await fsp.unlink(sessionPath);
587
+ await fsp2.unlink(sessionPath);
567
588
  deletedFromDisk = true;
568
589
  } catch {
569
590
  }
570
- if (activeBuilder) {
571
- const title = activeBuilder.getSession().title;
572
- await activeBuilder.deleteSession();
573
- activeBuilder = null;
574
- activeTaskStore = null;
575
- activeTaskTracker = null;
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 (activeBuilder) {
605
+ if (sddState.getBuilder()) {
586
606
  return { message: "An SDD session is already active. Use /sdd cancel first." };
587
607
  }
588
- const sessionPath = path20.join(projectRoot, ".wrongstack", "sdd-session.json");
608
+ const sessionPath = path21.join(projectRoot, ".wrongstack", "sdd-session.json");
589
609
  const projectContext = await gatherProjectContext(projectRoot);
590
- activeBuilder = new AISpecBuilder({
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 loaded = await activeBuilder.loadSession();
616
+ }));
617
+ const resumeBuilder = sddState.getBuilder();
618
+ const loaded = await resumeBuilder.loadSession();
598
619
  if (!loaded) {
599
- activeBuilder = null;
620
+ sddState.setBuilder(null);
600
621
  return { message: "No saved SDD session found. Use /sdd new to start one." };
601
622
  }
602
- const session = activeBuilder.getSession();
623
+ const session = resumeBuilder.getSession();
603
624
  let taskCount = 0;
604
625
  let completedCount = 0;
605
- const taskGraphId = activeBuilder.getTaskGraphId();
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
- activeTaskStore = store;
613
- activeTaskTracker = tracker;
614
- activeTaskGraphId = taskGraphId;
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 = activeBuilder.getAIPrompt();
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 fsp = await import('fs/promises');
814
- const pkgPath = path20.join(projectRoot, "package.json");
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 fsp = await import('fs/promises');
831
- const tsconfigPath = path20.join(projectRoot, "tsconfig.json");
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 fsp = await import('fs/promises');
838
- const srcDir = path20.join(projectRoot, "src");
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 activeBuilder, activeTaskStore, activeTaskTracker, activeTaskGraphId;
877
+ var SDD_META_KEY, SDDState, sddState;
860
878
  var init_sdd = __esm({
861
879
  "src/slash-commands/sdd.ts"() {
862
- activeBuilder = null;
863
- activeTaskStore = null;
864
- activeTaskTracker = null;
865
- activeTaskGraphId = null;
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 path20.join(homeFn(), ".wrongstack", "update-cache.json");
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 fs3.readFile(cachePath(homeFn), "utf8");
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 = path20.dirname(cachePath(homeFn));
957
- await fs3.mkdir(dir, { recursive: true });
958
- await fs3.writeFile(cachePath(homeFn), JSON.stringify(entry, null, 2), "utf8");
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 fs3.readFile(opts.globalConfigPath, "utf8");
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 = path20.join(path20.dirname(opts.globalConfigPath), ".key");
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 fs3.readFile(opts.globalConfigPath, "utf8");
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 = path20.join(path20.dirname(opts.globalConfigPath), ".key");
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" ? path20.resolve(flags["cwd"]) : process.cwd();
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 fs3.mkdir(paths.projectDir, { recursive: true });
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 fs3.writeFile(paths.projectMeta, JSON.stringify(meta, null, 2));
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 ?? path20.join(os6.homedir(), ".wrongstack", "history");
1858
+ this.historyFile = opts.historyFile ?? path21.join(os6.homedir(), ".wrongstack", "history");
1798
1859
  }
1799
1860
  async loadHistory() {
1800
1861
  try {
1801
- const raw = await fs3.readFile(this.historyFile, "utf8");
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 fs3.mkdir(path20.dirname(this.historyFile), { recursive: true });
1810
- await fs3.writeFile(this.historyFile, this.history.slice(-1e3).join("\n"));
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 !== path20.basename(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 = path20.resolve(parentDir);
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 = path20.dirname(filePath);
2049
- const filename = path20.basename(filePath);
2109
+ const dir = path21.dirname(filePath);
2110
+ const filename = path21.basename(filePath);
2050
2111
  try {
2051
2112
  assertSafeToDelete(filename, dir);
2052
- await fs3.unlink(filePath);
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 path20.join(homeFn(), ".wrongstack", "config.history", "entries");
2155
+ return path21.join(homeFn(), ".wrongstack", "config.history", "entries");
2095
2156
  }
2096
2157
  function historyIndexPath(homeFn = defaultHomeDir) {
2097
- return path20.join(homeFn(), ".wrongstack", "config.history", "index.json");
2158
+ return path21.join(homeFn(), ".wrongstack", "config.history", "index.json");
2098
2159
  }
2099
2160
  function configPath(homeFn = defaultHomeDir) {
2100
- return path20.join(homeFn(), ".wrongstack", "config.json");
2161
+ return path21.join(homeFn(), ".wrongstack", "config.json");
2101
2162
  }
2102
2163
  function backupLastPath(homeFn = defaultHomeDir) {
2103
- return path20.join(homeFn(), ".wrongstack", "config.json.last");
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 fs3.mkdir(historyDir(homeFn), { recursive: true });
2170
+ await fsp2.mkdir(historyDir(homeFn), { recursive: true });
2110
2171
  }
2111
2172
  async function readIndex(homeFn = defaultHomeDir) {
2112
2173
  try {
2113
- const raw = await fs3.readFile(historyIndexPath(homeFn), "utf8");
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 fs3.writeFile(historyIndexPath(homeFn), JSON.stringify(idx, null, 2), "utf8");
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 fs3.readFile(cfg, "utf8");
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 = path20.join(homeFn(), ".wrongstack", `config.json.${ts}.bak`);
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 = path20.join(homeFn(), ".wrongstack");
2147
- const files = await fs3.readdir(dir);
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(path20.join(dir, f));
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 fs3.writeFile(
2167
- path20.join(historyDir(homeFn), `${id}.json`),
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 fs3.readFile(path20.join(historyDir(homeFn), `${id}.json`), "utf8");
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 fs3.readFile(configPath(homeFn), "utf8");
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 fs3.readFile(cfg, "utf8");
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 fs3.readFile(last, "utf8");
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 fs3.access(file);
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(path20.join(root, "pnpm-lock.yaml"))) return "pnpm";
2512
- if (await pathExists(path20.join(root, "bun.lockb"))) return "bun";
2513
- if (await pathExists(path20.join(root, "bun.lock"))) return "bun";
2514
- if (await pathExists(path20.join(root, "yarn.lock"))) return "yarn";
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 fs3.readFile(path20.join(root, "package.json"), "utf8"));
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(path20.join(root, "pyproject.toml"))) throw new Error("not python");
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(path20.join(root, "go.mod"))) throw new Error("not go");
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(path20.join(root, "Cargo.toml"))) throw new Error("not rust");
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 fs3.readFile(path20.join(root, "Makefile"), "utf8");
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 = path20.join(ctx.projectRoot, ".wrongstack");
3210
- const file = path20.join(dir, "AGENTS.md");
3270
+ const dir = path21.join(ctx.projectRoot, ".wrongstack");
3271
+ const file = path21.join(dir, "AGENTS.md");
3211
3272
  try {
3212
- await fs3.access(file);
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 fs3.mkdir(dir, { recursive: true });
3223
- await fs3.writeFile(file, body, "utf8");
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 = path20.join(os6.homedir(), ".wrongstack");
4163
+ const globalRoot = path21.join(os6.homedir(), ".wrongstack");
3832
4164
  return new SkillInstaller({
3833
- manifestPath: path20.join(globalRoot, "installed-skills.json"),
3834
- projectSkillsDir: path20.join(projectRoot, ".wrongstack", "skills"),
3835
- globalSkillsDir: path20.join(globalRoot, "skills"),
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 fs3.access(path20.join(projectRoot, ".wrongstack", "AGENTS.md"));
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 fs3.access(path20.join(projectRoot, m));
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 = path20.join(projectRoot, ".wrongstack");
4061
- const file = path20.join(dir, "AGENTS.md");
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 fs3.mkdir(dir, { recursive: true });
4065
- await fs3.writeFile(file, body, "utf8");
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(`(${path20.join(projectRoot, ".wrongstack", "AGENTS.md")})`)}
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 = path20.join(projectRoot, ".git");
4434
+ const gitDir = path21.join(projectRoot, ".git");
4102
4435
  let hasGit = false;
4103
4436
  try {
4104
- await fs3.access(gitDir);
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 path21 = typeof o["path"] === "string" ? o["path"] : "";
4735
+ const path22 = typeof o["path"] === "string" ? o["path"] : "";
4403
4736
  const reps = typeof o["replacements"] === "number" ? o["replacements"] : 0;
4404
- return `${path21} ${reps} replacement${reps === 1 ? "" : "s"}`.trim();
4737
+ return `${path22} ${reps} replacement${reps === 1 ? "" : "s"}`.trim();
4405
4738
  }
4406
4739
  if (name === "write") {
4407
- const path21 = typeof o["path"] === "string" ? o["path"] : "";
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 ? `${path21} ${bytes}B` : path21;
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 fs3.readFile(deps.globalConfigPath, "utf8");
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 fs3.readFile(deps.globalConfigPath, "utf8");
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 fs3.access(deps.paths.secretsKey);
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 fs3.mkdir(deps.paths.projectSessions, { recursive: true });
5232
- const probe = path20.join(deps.paths.projectSessions, `.probe-${Date.now()}`);
5233
- await fs3.writeFile(probe, "");
5234
- await fs3.unlink(probe);
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 fs3.mkdir(path20.dirname(path20.resolve(deps.cwd, output)), { recursive: true });
5336
- await fs3.writeFile(path20.resolve(deps.cwd, output), rendered, "utf8");
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 fs3.mkdir(deps.paths.globalRoot, { recursive: true });
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 = path20.join(path20.dirname(deps.paths.globalConfig), ".key");
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 fs3.mkdir(path20.join(deps.projectRoot, ".wrongstack"), { recursive: true });
5401
- const agentsFile = path20.join(deps.projectRoot, ".wrongstack", "AGENTS.md");
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 fs3.access(agentsFile);
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 fs3.readFile(deps.paths.globalConfig, "utf8"));
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 fs3.readFile(deps.paths.globalConfig, "utf8"));
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 fs3.readFile(file, "utf8"));
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 = path20.join(deps.paths.globalRoot, "projects");
6044
+ const projectsRoot = path21.join(deps.paths.globalRoot, "projects");
5712
6045
  try {
5713
- const entries = await fs3.readdir(projectsRoot);
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 fs3.readFile(path20.join(projectsRoot, hash, "meta.json"), "utf8")
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 (_args, deps) => {
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 = path20.join(wpaths.globalRoot, "sessions");
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, color32) {
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 ? color32.dim(` [${errKind}]`) : "";
6214
- const errSnip = errMsg || errKind ? `${errKindChip}${color32.dim(errTail)}` : "";
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: color32.green("\u2713"), stats, tail: "" };
6751
+ return { mark: color33.green("\u2713"), stats, tail: "" };
6218
6752
  case "timeout":
6219
- return { mark: color32.yellow("\u23F1"), stats: `${color32.yellow("timeout")} ${stats}`, tail: errSnip };
6753
+ return { mark: color33.yellow("\u23F1"), stats: `${color33.yellow("timeout")} ${stats}`, tail: errSnip };
6220
6754
  case "stopped":
6221
- return { mark: color32.dim("\u2298"), stats: `${color32.dim("stopped")} ${stats}`, tail: errSnip };
6755
+ return { mark: color33.dim("\u2298"), stats: `${color33.dim("stopped")} ${stats}`, tail: errSnip };
6222
6756
  case "failed":
6223
- return { mark: color32.red("\u2717"), stats: `${color32.red("failed")} ${stats}`, tail: errSnip };
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 path20.join(path20.dirname(corePkg), "skills");
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: path20.basename(projectRoot) || void 0,
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: path20.basename(projectRoot) || void 0,
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: this.opts.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 ? path20.join(this.sessionFactory.dir, `${subagentConfig.name}.jsonl`) : void 0;
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 pending = Array.from(this.pending.entries()).map(([taskId, v]) => ({
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 = path20.join(this.opts.fleetRoot, "fleet.json");
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 = path20.join(this.opts.fleetRoot, "shared");
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 = path20.join(this.opts.fleetRoot, "subagents");
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 = path20.join(this.opts.fleetRoot, "director-state.json");
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 path21 = typeof input?.path === "string" ? input.path : void 0;
7540
- if (e.name === "read" && path21) this.readPaths.add(path21);
7541
- else if (e.name === "edit" && path21) this.editedPaths.add(path21);
7542
- else if (e.name === "write" && path21) {
7543
- this.writtenPaths.add(path21);
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: path20.join(wpaths.projectSessions, session.id, "attachments") });
7875
- const queueStore = new QueueStore({ dir: path20.join(wpaths.projectSessions, session.id) });
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 = path20.join(wpaths.projectSessions, `${session.id}.todos.json`);
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 = path20.join(wpaths.projectSessions, `${session.id}.plan.json`);
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 = path20.join(wpaths.projectSessions, session.id);
7896
- const dirState = await loadDirectorState(path20.join(fleetRoot, "director-state.json"));
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 path20.join(path20.dirname(corePkg), "skills");
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 ? path20.join(wpaths.projectSessions, `${sessionRef.current.id}.plan.json`) : void 0
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 fs3.access(wpaths.projectSessions);
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 = path20.join(wpaths.projectSessions, "metrics.json");
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 ? path20.join(wpaths.projectSessions, session.id) : void 0;
8295
- const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] : path20.join(fleetRoot, "fleet.json") : void 0;
8296
- const sharedScratchpadPath = directorMode ? path20.join(fleetRoot, "shared") : void 0;
8297
- const subagentSessionsRoot = directorMode ? path20.join(fleetRoot, "subagents") : void 0;
8298
- const stateCheckpointPath = directorMode ? path20.join(fleetRoot, "director-state.json") : void 0;
8299
- const fleetRootForPromotion = path20.join(wpaths.projectSessions, session.id);
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 = path20.join(fleetRootForPromotion, "subagents");
9034
+ const subagentsRoot = path21.join(fleetRootForPromotion, "subagents");
8479
9035
  let runDirs;
8480
9036
  try {
8481
- runDirs = await fs3.readdir(subagentsRoot);
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 = path20.join(subagentsRoot, runId);
9043
+ const runDir = path21.join(subagentsRoot, runId);
8488
9044
  let files;
8489
9045
  try {
8490
- files = await fs3.readdir(runDir);
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 = path20.join(runDir, f);
9052
+ const full = path21.join(runDir, f);
8497
9053
  try {
8498
- const stat2 = await fs3.stat(full);
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: stat2.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 fs3.readFile(t.file, "utf8");
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 = path20.join(fleetRootForPromotion, "director-state.json");
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 = path20.join(fleetRootForPromotion, "fleet.json");
8665
- const sp = path20.join(fleetRootForPromotion, "shared");
8666
- const ss = path20.join(fleetRootForPromotion, "subagents");
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(", ")}`,