@wrongstack/cli 0.7.5 → 0.7.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,21 +1,23 @@
1
1
  #!/usr/bin/env node
2
- import * as path23 from 'path';
2
+ import * as path24 from 'path';
3
3
  import { join } from 'path';
4
4
  import * as fsp2 from 'fs/promises';
5
5
  import { readdir, readFile } from 'fs/promises';
6
- import { color, DefaultPathResolver, TOKENS, DefaultSystemPromptBuilder, makeAutonomyPromptContributor, ToolRegistry, createContextManagerTool, EventBus, SlashCommandRegistry, createDelegateTool, FLEET_ROSTER, createMcpControlTool, EternalAutonomyEngine, DefaultLogger, DefaultModelsRegistry, ProviderRegistry, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, AutoCompactionMiddleware, estimateRequestTokens, Agent, loadPlugins, FleetManager, makeDirectorSessionFactory, Director, AutoApprovePermissionPolicy, makeLLMClassifier, resolveWstackPaths, DefaultSecretVault, migratePlaintextSecrets, DefaultConfigLoader, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, atomicWrite, DefaultPluginAPI, makeAgentSubagentRunner, NULL_FLEET_BUS, formatContextWindowModeList, repairToolUseAdjacency, getContextWindowMode, resolveContextWindowPolicy, AGENTS_BY_PHASE, dispatchAgent, formatTodosList, emptyPlan, clearPlan, savePlan, formatPlanTemplates, getPlanTemplate, addPlanItem, formatPlan, deriveTodosFromPlanItem, removePlanItem, setPlanItemStatus, SpecStore, TaskGraphStore, analyzeCriticalPath, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, DefaultTaskStore, TaskTracker, renderProgress, renderTaskGraph, loadGoal, goalFilePath, summarizeUsage, saveGoal, emptyGoal, buildGoalPreamble, formatGoal, InputBuilder, FsError, ERROR_CODES, projectHash, WrongStackError, defaultOrchestrator, decryptConfigSecrets, encryptConfigSecrets as encryptConfigSecrets$1, SpecVersioning, ParallelEternalEngine, allServers as allServers$1 } from '@wrongstack/core';
6
+ import { color, DefaultPathResolver, TOKENS, DefaultSystemPromptBuilder, makeAutonomyPromptContributor, ToolRegistry, createContextManagerTool, EventBus, SlashCommandRegistry, createDelegateTool, FLEET_ROSTER, createMcpControlTool, EternalAutonomyEngine, DefaultLogger, DefaultModelsRegistry, ProviderRegistry, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, AutoCompactionMiddleware, estimateRequestTokens, Agent, loadPlugins, FleetManager, makeDirectorSessionFactory, Director, AutoApprovePermissionPolicy, makeLLMClassifier, resolveWstackPaths, DefaultSecretVault, migratePlaintextSecrets, DefaultConfigLoader, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, atomicWrite, DefaultPluginAPI, makeAgentSubagentRunner, NULL_FLEET_BUS, formatContextWindowModeList, repairToolUseAdjacency, getContextWindowMode, resolveContextWindowPolicy, AGENTS_BY_PHASE, dispatchAgent, formatTodosList, emptyPlan, clearPlan, savePlan, formatPlanTemplates, getPlanTemplate, addPlanItem, formatPlan, deriveTodosFromPlanItem, removePlanItem, setPlanItemStatus, SpecStore, TaskGraphStore, analyzeCriticalPath, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, DefaultTaskStore, TaskTracker, renderProgress, renderTaskGraph, loadGoal, goalFilePath, summarizeUsage, saveGoal, emptyGoal, buildGoalPreamble, formatGoal, pendingBtwCount, setBtwNote, InputBuilder, FsError, ERROR_CODES, projectHash, WrongStackError, defaultOrchestrator, decryptConfigSecrets, encryptConfigSecrets as encryptConfigSecrets$1, SpecVersioning, ParallelEternalEngine, allServers as allServers$1 } from '@wrongstack/core';
7
7
  import { createRequire } from 'module';
8
8
  import * as os6 from 'os';
9
9
  import os6__default from 'os';
10
10
  import * as crypto2 from 'crypto';
11
11
  import { randomUUID } from 'crypto';
12
- import { DefaultSecretVault as DefaultSecretVault$1, encryptConfigSecrets, decryptConfigSecrets as decryptConfigSecrets$1 } from '@wrongstack/core/security';
12
+ import { DefaultSecretVault as DefaultSecretVault$1, encryptConfigSecrets, isSecretField, decryptConfigSecrets as decryptConfigSecrets$1 } from '@wrongstack/core/security';
13
13
  import { WebSocketServer, WebSocket } from 'ws';
14
14
  import { MCPRegistry } from '@wrongstack/mcp';
15
15
  import { buildProviderFactoriesFromRegistry, makeProviderFromConfig, capabilitiesFor } from '@wrongstack/providers';
16
16
  import { createDefaultContainer, routeImagesForModel, readClipboardImage } from '@wrongstack/runtime';
17
17
  import { builtinToolsPack, rememberTool, forgetTool } from '@wrongstack/tools';
18
18
  import * as readline from 'readline';
19
+ import * as fs4 from 'fs';
20
+ import { writeFileSync } from 'fs';
19
21
  import { spawn } from 'child_process';
20
22
  import { SkillInstaller } from '@wrongstack/core/skills';
21
23
  import { WrongStackACPServer } from '@wrongstack/acp/agent';
@@ -24,7 +26,6 @@ import { ACP_AGENTS, SubagentBudget } from '@wrongstack/core/coordination';
24
26
  import { allServers } from '@wrongstack/core/infrastructure';
25
27
  import { createToolVisionAdapters } from '@wrongstack/runtime/vision';
26
28
  import { ToolExecutor } from '@wrongstack/core/execution';
27
- import { writeFileSync } from 'fs';
28
29
 
29
30
  var __defProp = Object.defineProperty;
30
31
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -64,8 +65,10 @@ __export(sdd_exports, {
64
65
  getActiveSDDPhase: () => getActiveSDDPhase,
65
66
  getCurrentExecutingContext: () => getCurrentExecutingContext,
66
67
  getCurrentTask: () => getCurrentTask,
68
+ getTaskGraphId: () => getTaskGraphId,
67
69
  getTaskListText: () => getTaskListText,
68
70
  getTaskProgress: () => getTaskProgress,
71
+ getTaskTracker: () => getTaskTracker,
69
72
  markTaskCompleted: () => markTaskCompleted,
70
73
  renderTaskListWithProgress: () => renderTaskListWithProgress,
71
74
  trySaveImplementationPlan: () => trySaveImplementationPlan,
@@ -380,10 +383,9 @@ function buildSddCommand(opts) {
380
383
  name: "sdd",
381
384
  description: "AI-driven SDD: /sdd [new|approve|execute|cancel|status|list|show|templates]",
382
385
  async run(args) {
383
- const ctx = opts.context;
384
- const projectRoot = ctx?.projectRoot ?? process.cwd();
385
- const specsDir = path23.join(projectRoot, ".wrongstack", "specs");
386
- const graphsDir = path23.join(projectRoot, ".wrongstack", "task-graphs");
386
+ opts.context;
387
+ const specsDir = opts.paths.projectSpecs;
388
+ const graphsDir = opts.paths.projectTaskGraphs;
387
389
  const specStore = new SpecStore({ baseDir: specsDir });
388
390
  new TaskGraphStore({ baseDir: graphsDir });
389
391
  const versioning = sddState.getVersioning();
@@ -399,10 +401,10 @@ function buildSddCommand(opts) {
399
401
  const forceFlag = rest.includes("--force") || rest.includes("-f");
400
402
  const title = rest.filter((a) => !a.startsWith("-")).join(" ").trim() || "Untitled Feature";
401
403
  if (!sessionState.getBuilder() && !forceFlag) {
402
- const sessionPath = path23.join(projectRoot, ".wrongstack", "sdd-session.json");
404
+ const sessionPath = opts.paths.projectSddSession;
403
405
  try {
404
406
  await fsp2.access(sessionPath);
405
- const projectContext2 = await gatherProjectContext(projectRoot);
407
+ const projectContext2 = await gatherProjectContext(opts.context?.projectRoot ?? process.cwd());
406
408
  const tempBuilder = new AISpecBuilder({
407
409
  store: specStore,
408
410
  projectContext: projectContext2,
@@ -428,13 +430,13 @@ function buildSddCommand(opts) {
428
430
  }
429
431
  }
430
432
  sddState.clearTaskState();
431
- const projectContext = await gatherProjectContext(projectRoot);
433
+ const projectContext = await gatherProjectContext(opts.context?.projectRoot ?? process.cwd());
432
434
  sddState.setBuilder(new AISpecBuilder({
433
435
  store: specStore,
434
436
  projectContext,
435
437
  minQuestions: 2,
436
438
  maxQuestions: 10,
437
- sessionPath: path23.join(projectRoot, ".wrongstack", "sdd-session.json")
439
+ sessionPath: opts.paths.projectSddSession
438
440
  }));
439
441
  sddState.setSessionStartTime(Date.now());
440
442
  sddState.setPhaseStartTime(Date.now());
@@ -547,8 +549,16 @@ Start executing the tasks one by one.`
547
549
  };
548
550
  }
549
551
  // ── Task Execution ─────────────────────────────────────────────────
550
- case "execute":
551
- case "run": {
552
+ case "run":
553
+ case "execute": {
554
+ if (opts.onSddParallelRun) {
555
+ const slotsArg = restJoined.trim();
556
+ const slots = slotsArg ? Number.parseInt(slotsArg, 10) : void 0;
557
+ const message = await opts.onSddParallelRun(
558
+ slots && Number.isFinite(slots) ? { parallelSlots: Math.min(16, Math.max(1, slots)) } : {}
559
+ );
560
+ return { message };
561
+ }
552
562
  const runBuilder = sddState.getBuilder();
553
563
  if (!runBuilder) {
554
564
  return {
@@ -572,6 +582,21 @@ User message:
572
582
  Start executing the tasks one by one.`
573
583
  };
574
584
  }
585
+ case "parallel": {
586
+ if (!opts.onSddParallelRun) {
587
+ return { message: "SDD parallel run is not available in this session." };
588
+ }
589
+ const slotsArg = restJoined.trim();
590
+ const slots = slotsArg ? Number.parseInt(slotsArg, 10) : void 0;
591
+ const message = await opts.onSddParallelRun(
592
+ slots && Number.isFinite(slots) ? { parallelSlots: Math.min(16, Math.max(1, slots)) } : {}
593
+ );
594
+ return { message };
595
+ }
596
+ case "stop": {
597
+ opts.onSddParallelStop?.();
598
+ return { message: "SDD parallel run stopped." };
599
+ }
575
600
  case "plan":
576
601
  case "impl": {
577
602
  const planBuilder = sddState.getBuilder();
@@ -1001,7 +1026,7 @@ Start executing the tasks one by one.`
1001
1026
  return { message: lines2.join("\n") };
1002
1027
  }
1003
1028
  try {
1004
- const graphStore2 = new TaskGraphStore({ baseDir: path23.join(projectRoot, ".wrongstack", "task-graphs") });
1029
+ const graphStore2 = new TaskGraphStore({ baseDir: opts.paths.projectTaskGraphs });
1005
1030
  const stored = await graphStore2.load(graphId);
1006
1031
  if (stored) {
1007
1032
  return { message: renderTaskGraph(stored, { compact: false }) };
@@ -1026,7 +1051,7 @@ Start executing the tasks one by one.`
1026
1051
  return { message: lines.join("\n") };
1027
1052
  }
1028
1053
  case "cancel": {
1029
- const sessionPath = path23.join(projectRoot, ".wrongstack", "sdd-session.json");
1054
+ const sessionPath = opts.paths.projectSddSession;
1030
1055
  let deletedFromDisk = false;
1031
1056
  try {
1032
1057
  await fsp2.unlink(sessionPath);
@@ -1034,11 +1059,11 @@ Start executing the tasks one by one.`
1034
1059
  } catch {
1035
1060
  }
1036
1061
  try {
1037
- await fsp2.rm(path23.join(projectRoot, ".wrongstack", "specs"), { recursive: true, force: true });
1062
+ await fsp2.rm(opts.paths.projectSpecs, { recursive: true, force: true });
1038
1063
  } catch {
1039
1064
  }
1040
1065
  try {
1041
- await fsp2.rm(path23.join(projectRoot, ".wrongstack", "task-graphs"), { recursive: true, force: true });
1066
+ await fsp2.rm(opts.paths.projectTaskGraphs, { recursive: true, force: true });
1042
1067
  } catch {
1043
1068
  }
1044
1069
  const cancelBuilder = sddState.getBuilder();
@@ -1058,8 +1083,8 @@ Start executing the tasks one by one.`
1058
1083
  if (sddState.getBuilder()) {
1059
1084
  return { message: "An SDD session is already active. Use /sdd cancel first." };
1060
1085
  }
1061
- const sessionPath = path23.join(projectRoot, ".wrongstack", "sdd-session.json");
1062
- const projectContext = await gatherProjectContext(projectRoot);
1086
+ const sessionPath = opts.paths.projectSddSession;
1087
+ const projectContext = await gatherProjectContext(opts.context?.projectRoot ?? process.cwd());
1063
1088
  sddState.setBuilder(new AISpecBuilder({
1064
1089
  store: specStore,
1065
1090
  projectContext,
@@ -1222,7 +1247,7 @@ ${lines.join("\n")}`
1222
1247
  return { message: "No task graph found. Generate tasks first." };
1223
1248
  }
1224
1249
  try {
1225
- const graphStore2 = new TaskGraphStore({ baseDir: path23.join(projectRoot, ".wrongstack", "task-graphs") });
1250
+ const graphStore2 = new TaskGraphStore({ baseDir: opts.paths.projectTaskGraphs });
1226
1251
  const graph = await graphStore2.load(graphId);
1227
1252
  if (!graph) {
1228
1253
  return { message: "Could not load task graph." };
@@ -1373,7 +1398,7 @@ function sddHelp() {
1373
1398
  async function gatherProjectContext(projectRoot) {
1374
1399
  const parts = [];
1375
1400
  try {
1376
- const pkgPath = path23.join(projectRoot, "package.json");
1401
+ const pkgPath = path24.join(projectRoot, "package.json");
1377
1402
  const pkgRaw = await fsp2.readFile(pkgPath, "utf8");
1378
1403
  const pkg = JSON.parse(pkgRaw);
1379
1404
  parts.push(`Project: ${String(pkg.name ?? "unknown")}`);
@@ -1389,13 +1414,13 @@ async function gatherProjectContext(projectRoot) {
1389
1414
  } catch {
1390
1415
  }
1391
1416
  try {
1392
- const tsconfigPath = path23.join(projectRoot, "tsconfig.json");
1417
+ const tsconfigPath = path24.join(projectRoot, "tsconfig.json");
1393
1418
  await fsp2.access(tsconfigPath);
1394
1419
  parts.push("Language: TypeScript");
1395
1420
  } catch {
1396
1421
  }
1397
1422
  try {
1398
- const srcDir = path23.join(projectRoot, "src");
1423
+ const srcDir = path24.join(projectRoot, "src");
1399
1424
  const entries = await fsp2.readdir(srcDir, { withFileTypes: true });
1400
1425
  const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
1401
1426
  if (dirs.length > 0) {
@@ -1416,6 +1441,12 @@ async function findSpec(store, idOrTitle) {
1416
1441
  if (match) return store.load(match.id);
1417
1442
  return null;
1418
1443
  }
1444
+ function getTaskGraphId() {
1445
+ return sddState.getTaskGraphId();
1446
+ }
1447
+ function getTaskTracker() {
1448
+ return sddState.getTaskTracker();
1449
+ }
1419
1450
  var SDD_META_KEY, SDDState, sddState;
1420
1451
  var init_sdd = __esm({
1421
1452
  "src/slash-commands/sdd.ts"() {
@@ -1545,7 +1576,7 @@ __export(update_check_exports, {
1545
1576
  getUpdateNotification: () => getUpdateNotification
1546
1577
  });
1547
1578
  function cachePath(homeFn = defaultHomeDir2) {
1548
- return path23.join(homeFn(), ".wrongstack", "update-cache.json");
1579
+ return path24.join(homeFn(), ".wrongstack", "update-cache.json");
1549
1580
  }
1550
1581
  function currentVersion() {
1551
1582
  const req2 = createRequire(import.meta.url);
@@ -1582,7 +1613,7 @@ async function readCache(homeFn = defaultHomeDir2) {
1582
1613
  }
1583
1614
  async function writeCache(entry, homeFn = defaultHomeDir2) {
1584
1615
  try {
1585
- const dir = path23.dirname(cachePath(homeFn));
1616
+ const dir = path24.dirname(cachePath(homeFn));
1586
1617
  await fsp2.mkdir(dir, { recursive: true });
1587
1618
  await fsp2.writeFile(cachePath(homeFn), JSON.stringify(entry, null, 2), "utf8");
1588
1619
  } catch {
@@ -1821,7 +1852,21 @@ async function runWebUI(opts) {
1821
1852
  const client = { ws, sessionId: opts.session.id };
1822
1853
  clients.set(ws, client);
1823
1854
  console.log("[WebUI] Client connected");
1855
+ let msgCount = 0;
1856
+ let windowResetAt = Date.now() + 6e4;
1824
1857
  ws.on("message", async (data) => {
1858
+ const now = Date.now();
1859
+ if (now > windowResetAt) {
1860
+ msgCount = 0;
1861
+ windowResetAt = now + 6e4;
1862
+ }
1863
+ if (++msgCount > 60) {
1864
+ send(ws, {
1865
+ type: "error",
1866
+ payload: { phase: "rate_limit", message: "Too many messages. Please wait." }
1867
+ });
1868
+ return;
1869
+ }
1825
1870
  try {
1826
1871
  const msg = JSON.parse(data.toString());
1827
1872
  await handleMessage(ws, client, msg);
@@ -2172,7 +2217,7 @@ async function runWebUI(opts) {
2172
2217
  return {};
2173
2218
  }
2174
2219
  if (!parsed.providers) return {};
2175
- const keyFile = path23.join(path23.dirname(opts.globalConfigPath), ".key");
2220
+ const keyFile = path24.join(path24.dirname(opts.globalConfigPath), ".key");
2176
2221
  const vault = new DefaultSecretVault$1({ keyFile });
2177
2222
  return decryptConfigSecrets$1(parsed.providers, vault);
2178
2223
  }
@@ -2205,7 +2250,7 @@ async function runWebUI(opts) {
2205
2250
  parsed = {};
2206
2251
  }
2207
2252
  parsed.providers = providers;
2208
- const keyFile = path23.join(path23.dirname(opts.globalConfigPath), ".key");
2253
+ const keyFile = path24.join(path24.dirname(opts.globalConfigPath), ".key");
2209
2254
  const vault = new DefaultSecretVault$1({ keyFile });
2210
2255
  const encrypted = encryptConfigSecrets(parsed, vault);
2211
2256
  await atomicWrite(opts.globalConfigPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
@@ -2409,7 +2454,7 @@ function parseSpawnFlags(input) {
2409
2454
  return { description: rest.trim(), opts };
2410
2455
  }
2411
2456
  async function bootConfig(flags) {
2412
- const cwd = typeof flags["cwd"] === "string" ? path23.resolve(flags["cwd"]) : process.cwd();
2457
+ const cwd = typeof flags["cwd"] === "string" ? path24.resolve(flags["cwd"]) : process.cwd();
2413
2458
  const pathResolver = new DefaultPathResolver(cwd);
2414
2459
  const projectRoot = pathResolver.projectRoot;
2415
2460
  const userHome = os6.homedir();
@@ -2476,7 +2521,7 @@ var ReadlineInputReader = class {
2476
2521
  history = [];
2477
2522
  pending = false;
2478
2523
  constructor(opts = {}) {
2479
- this.historyFile = opts.historyFile ?? path23.join(os6.homedir(), ".wrongstack", "history");
2524
+ this.historyFile = opts.historyFile ?? path24.join(os6.homedir(), ".wrongstack", "history");
2480
2525
  }
2481
2526
  async loadHistory() {
2482
2527
  try {
@@ -2488,7 +2533,7 @@ var ReadlineInputReader = class {
2488
2533
  }
2489
2534
  async saveHistory() {
2490
2535
  try {
2491
- await fsp2.mkdir(path23.dirname(this.historyFile), { recursive: true });
2536
+ await fsp2.mkdir(path24.dirname(this.historyFile), { recursive: true });
2492
2537
  await fsp2.writeFile(this.historyFile, this.history.slice(-1e3).join("\n"));
2493
2538
  } catch {
2494
2539
  }
@@ -2715,20 +2760,20 @@ function assertSafeToDelete(filename, parentDir) {
2715
2760
  if (PROTECTED_BASENAMES.has(filename)) {
2716
2761
  throw new Error(`Refusing to delete protected file: ${filename}`);
2717
2762
  }
2718
- if (filename !== path23.basename(filename)) {
2763
+ if (filename !== path24.basename(filename)) {
2719
2764
  throw new Error(`Refusing to delete path with traversal: ${filename}`);
2720
2765
  }
2721
2766
  if (!filename.startsWith("config.json.") || !filename.endsWith(".bak")) {
2722
2767
  throw new Error(`Refusing to delete unknown file: ${filename}`);
2723
2768
  }
2724
- const resolvedParent = path23.resolve(parentDir);
2769
+ const resolvedParent = path24.resolve(parentDir);
2725
2770
  if (!resolvedParent.endsWith(".wrongstack")) {
2726
2771
  throw new Error(`Unexpected parent directory for bak prune: ${resolvedParent}`);
2727
2772
  }
2728
2773
  }
2729
2774
  async function safeDelete(filePath) {
2730
- const dir = path23.dirname(filePath);
2731
- const filename = path23.basename(filePath);
2775
+ const dir = path24.dirname(filePath);
2776
+ const filename = path24.basename(filePath);
2732
2777
  try {
2733
2778
  assertSafeToDelete(filename, dir);
2734
2779
  await fsp2.unlink(filePath);
@@ -2743,7 +2788,7 @@ function maskConfigSecrets(cfg) {
2743
2788
  if (typeof cfg !== "object" || cfg === null) return {};
2744
2789
  const out = {};
2745
2790
  for (const [k, v] of Object.entries(cfg)) {
2746
- if (k === "apiKey" || k === "apiKeys" || k === "secret" || k === "secrets") {
2791
+ if (isSecretField(k)) {
2747
2792
  out[k] = "[REDACTED]";
2748
2793
  } else if (typeof v === "object" && v !== null && !Array.isArray(v)) {
2749
2794
  out[k] = maskConfigSecrets(v);
@@ -2760,7 +2805,7 @@ function diffSummary(oldCfg, newCfg) {
2760
2805
  const o = JSON.stringify(oldCfg[k]);
2761
2806
  const n = JSON.stringify(newCfg[k]);
2762
2807
  if (o !== n) {
2763
- if (k === "apiKey" || k === "apiKeys" || k === "secret") {
2808
+ if (isSecretField(k)) {
2764
2809
  changes.push(`${k}: [CHANGED]`);
2765
2810
  } else if (typeof newCfg[k] !== "object") {
2766
2811
  changes.push(`${k}: ${oldCfg[k] ?? "(unset)"} \u2192 ${newCfg[k]}`);
@@ -2773,16 +2818,16 @@ function diffSummary(oldCfg, newCfg) {
2773
2818
  }
2774
2819
  var defaultHomeDir = () => os6__default.homedir();
2775
2820
  function historyDir(homeFn = defaultHomeDir) {
2776
- return path23.join(homeFn(), ".wrongstack", "config.history", "entries");
2821
+ return path24.join(homeFn(), ".wrongstack", "config.history", "entries");
2777
2822
  }
2778
2823
  function historyIndexPath(homeFn = defaultHomeDir) {
2779
- return path23.join(homeFn(), ".wrongstack", "config.history", "index.json");
2824
+ return path24.join(homeFn(), ".wrongstack", "config.history", "index.json");
2780
2825
  }
2781
2826
  function configPath(homeFn = defaultHomeDir) {
2782
- return path23.join(homeFn(), ".wrongstack", "config.json");
2827
+ return path24.join(homeFn(), ".wrongstack", "config.json");
2783
2828
  }
2784
2829
  function backupLastPath(homeFn = defaultHomeDir) {
2785
- return path23.join(homeFn(), ".wrongstack", "config.json.last");
2830
+ return path24.join(homeFn(), ".wrongstack", "config.json.last");
2786
2831
  }
2787
2832
  function entryId(ts) {
2788
2833
  return ts.replace(/[:.]/g, "-").slice(0, 19);
@@ -2837,17 +2882,17 @@ async function backupCurrent(homeFn = defaultHomeDir) {
2837
2882
  }
2838
2883
  if (content !== void 0) {
2839
2884
  try {
2840
- const bakPath = path23.join(homeFn(), ".wrongstack", `config.json.${ts}.bak`);
2885
+ const bakPath = path24.join(homeFn(), ".wrongstack", `config.json.${ts}.bak`);
2841
2886
  await atomicWrite(bakPath, content);
2842
2887
  } catch {
2843
2888
  }
2844
2889
  }
2845
2890
  try {
2846
- const dir = path23.join(homeFn(), ".wrongstack");
2891
+ const dir = path24.join(homeFn(), ".wrongstack");
2847
2892
  const files = await fsp2.readdir(dir);
2848
2893
  const baks = files.filter((f) => f.startsWith("config.json.") && f.endsWith(".bak")).sort().reverse();
2849
2894
  for (const f of baks.slice(10)) {
2850
- await safeDelete(path23.join(dir, f));
2895
+ await safeDelete(path24.join(dir, f));
2851
2896
  }
2852
2897
  } catch {
2853
2898
  }
@@ -2865,7 +2910,7 @@ async function appendHistory(oldCfg, newCfg, description, homeFn = defaultHomeDi
2865
2910
  };
2866
2911
  try {
2867
2912
  await fsp2.writeFile(
2868
- path23.join(historyDir(homeFn), `${id}.json`),
2913
+ path24.join(historyDir(homeFn), `${id}.json`),
2869
2914
  JSON.stringify(entry, null, 2),
2870
2915
  "utf8"
2871
2916
  );
@@ -2873,7 +2918,7 @@ async function appendHistory(oldCfg, newCfg, description, homeFn = defaultHomeDi
2873
2918
  throw new FsError({
2874
2919
  message: err instanceof Error ? err.message : String(err),
2875
2920
  code: ERROR_CODES.FS_WRITE_FAILED,
2876
- path: path23.join(historyDir(homeFn), `${id}.json`),
2921
+ path: path24.join(historyDir(homeFn), `${id}.json`),
2877
2922
  cause: err
2878
2923
  });
2879
2924
  }
@@ -2888,7 +2933,7 @@ async function listHistory(homeFn = defaultHomeDir) {
2888
2933
  }
2889
2934
  async function getHistoryEntry(id, homeFn = defaultHomeDir) {
2890
2935
  try {
2891
- const raw = await fsp2.readFile(path23.join(historyDir(homeFn), `${id}.json`), "utf8");
2936
+ const raw = await fsp2.readFile(path24.join(historyDir(homeFn), `${id}.json`), "utf8");
2892
2937
  return JSON.parse(raw);
2893
2938
  } catch {
2894
2939
  return null;
@@ -2948,10 +2993,10 @@ var theme = { primary: color.amber };
2948
2993
  async function saveToGlobalConfig(configPath2, provider, model, homeFn = () => process.env.HOME ?? __require("os").homedir()) {
2949
2994
  try {
2950
2995
  const { atomicWrite: atomicWrite8 } = await import('@wrongstack/core');
2951
- const fs20 = await import('fs/promises');
2996
+ const fs21 = await import('fs/promises');
2952
2997
  let existing = {};
2953
2998
  try {
2954
- const raw = await fs20.readFile(configPath2, "utf8");
2999
+ const raw = await fs21.readFile(configPath2, "utf8");
2955
3000
  existing = JSON.parse(raw);
2956
3001
  } catch {
2957
3002
  }
@@ -3240,6 +3285,7 @@ var GROUPS = [
3240
3285
  items: [
3241
3286
  { key: "Esc (while busy)", blurb: "soft interrupt \u2014 next message carries a STEERING preamble" },
3242
3287
  { key: "/steer <text>", blurb: "mid-flight redirect, works when Esc is eaten by tmux" },
3288
+ { key: "/btw <note>", blurb: "non-aborting nudge \u2014 agent folds it in at its next step" },
3243
3289
  { key: "Ctrl+C \xD7 1 / \xD7 2 / \xD7 3", blurb: "cancel iteration \xB7 force-exit Ink \xB7 hard exit(130)" }
3244
3290
  ]
3245
3291
  },
@@ -3265,7 +3311,8 @@ var GROUPS = [
3265
3311
  ]
3266
3312
  }
3267
3313
  ];
3268
- var HINT_COUNT = GROUPS.reduce((n, g) => n + g.items.length, 0);
3314
+ GROUPS.reduce((n, g) => n + g.items.length, 0);
3315
+ GROUPS.map((g) => g.title);
3269
3316
  function shouldSuppress(flags) {
3270
3317
  if (flags["no-hints"] === true) return true;
3271
3318
  if (flags["hints"] === false) return true;
@@ -3273,22 +3320,41 @@ function shouldSuppress(flags) {
3273
3320
  if (env && env !== "0" && env.toLowerCase() !== "false") return true;
3274
3321
  return false;
3275
3322
  }
3276
- function printLaunchHints(renderer, flags) {
3323
+ var wrap = (n) => (n % GROUPS.length + GROUPS.length) % GROUPS.length;
3324
+ function pickGroupIndex(opts) {
3325
+ if (typeof opts.groupIndex === "number") return wrap(opts.groupIndex);
3326
+ if (opts.cursorFile) {
3327
+ try {
3328
+ let current = 0;
3329
+ try {
3330
+ const parsed = Number.parseInt(fs4.readFileSync(opts.cursorFile, "utf8").trim(), 10);
3331
+ if (Number.isFinite(parsed)) current = wrap(parsed);
3332
+ } catch {
3333
+ }
3334
+ fs4.mkdirSync(path24.dirname(opts.cursorFile), { recursive: true });
3335
+ fs4.writeFileSync(opts.cursorFile, String(wrap(current + 1)));
3336
+ return current;
3337
+ } catch {
3338
+ }
3339
+ }
3340
+ return Math.floor(Math.random() * GROUPS.length);
3341
+ }
3342
+ function printLaunchHints(renderer, flags, opts = {}) {
3277
3343
  if (shouldSuppress(flags)) return;
3344
+ const idx = pickGroupIndex(opts);
3345
+ const group = GROUPS[idx];
3346
+ if (!group) return;
3278
3347
  const lines = [];
3279
3348
  lines.push("");
3280
3349
  lines.push(
3281
- ` ${color.cyan("\u25C6")} ${color.bold(`WrongStack \u2014 ${HINT_COUNT} things you can do here`)}`
3350
+ ` ${color.cyan("\u25C6")} ${color.bold(group.title)} ${color.dim(`(${idx + 1}/${GROUPS.length} \xB7 more each launch)`)}`
3282
3351
  );
3283
- for (const group of GROUPS) {
3284
- lines.push(` ${color.dim("\u2500")} ${color.cyan(group.title)}`);
3285
- for (const item of group.items) {
3286
- lines.push(` ${color.bold(item.key)} ${color.dim("\u2014")} ${color.dim(item.blurb)}`);
3287
- }
3352
+ for (const item of group.items) {
3353
+ lines.push(` ${color.bold(item.key)} ${color.dim("\u2014")} ${color.dim(item.blurb)}`);
3288
3354
  }
3289
3355
  lines.push("");
3290
3356
  lines.push(
3291
- ` ${color.dim(`tip: hide this with ${color.bold("--no-hints")} or ${color.bold("WRONGSTACK_NO_HINTS=1")}`)}`
3357
+ ` ${color.dim(`${color.bold("/help")} lists everything \xB7 hide with ${color.bold("--no-hints")}`)}`
3292
3358
  );
3293
3359
  lines.push("");
3294
3360
  renderer.write(`${lines.join("\n")}
@@ -3307,10 +3373,10 @@ async function detectPackageManager(root, declared) {
3307
3373
  const name = declared.split("@")[0];
3308
3374
  if (name) return name;
3309
3375
  }
3310
- if (await pathExists(path23.join(root, "pnpm-lock.yaml"))) return "pnpm";
3311
- if (await pathExists(path23.join(root, "bun.lockb"))) return "bun";
3312
- if (await pathExists(path23.join(root, "bun.lock"))) return "bun";
3313
- if (await pathExists(path23.join(root, "yarn.lock"))) return "yarn";
3376
+ if (await pathExists(path24.join(root, "pnpm-lock.yaml"))) return "pnpm";
3377
+ if (await pathExists(path24.join(root, "bun.lockb"))) return "bun";
3378
+ if (await pathExists(path24.join(root, "bun.lock"))) return "bun";
3379
+ if (await pathExists(path24.join(root, "yarn.lock"))) return "yarn";
3314
3380
  return "npm";
3315
3381
  }
3316
3382
  function hasUsableScript(scripts, name) {
@@ -3331,7 +3397,7 @@ function parseMakeTargets(makefile) {
3331
3397
  async function detectProjectFacts(root) {
3332
3398
  const facts = { hints: [] };
3333
3399
  try {
3334
- const pkg = JSON.parse(await fsp2.readFile(path23.join(root, "package.json"), "utf8"));
3400
+ const pkg = JSON.parse(await fsp2.readFile(path24.join(root, "package.json"), "utf8"));
3335
3401
  const scripts = pkg.scripts ?? {};
3336
3402
  const pm = await detectPackageManager(root, pkg.packageManager);
3337
3403
  if (hasUsableScript(scripts, "build")) facts.build = `${pm} run build`;
@@ -3345,14 +3411,14 @@ async function detectProjectFacts(root) {
3345
3411
  } catch {
3346
3412
  }
3347
3413
  try {
3348
- if (!await pathExists(path23.join(root, "pyproject.toml"))) throw new Error("not python");
3414
+ if (!await pathExists(path24.join(root, "pyproject.toml"))) throw new Error("not python");
3349
3415
  facts.test ??= "pytest";
3350
3416
  facts.lint ??= "ruff check .";
3351
3417
  facts.hints.push("pyproject.toml");
3352
3418
  } catch {
3353
3419
  }
3354
3420
  try {
3355
- if (!await pathExists(path23.join(root, "go.mod"))) throw new Error("not go");
3421
+ if (!await pathExists(path24.join(root, "go.mod"))) throw new Error("not go");
3356
3422
  facts.build ??= "go build ./...";
3357
3423
  facts.test ??= "go test ./...";
3358
3424
  facts.run ??= "go run .";
@@ -3360,7 +3426,7 @@ async function detectProjectFacts(root) {
3360
3426
  } catch {
3361
3427
  }
3362
3428
  try {
3363
- if (!await pathExists(path23.join(root, "Cargo.toml"))) throw new Error("not rust");
3429
+ if (!await pathExists(path24.join(root, "Cargo.toml"))) throw new Error("not rust");
3364
3430
  facts.build ??= "cargo build";
3365
3431
  facts.test ??= "cargo test";
3366
3432
  facts.lint ??= "cargo clippy";
@@ -3369,7 +3435,7 @@ async function detectProjectFacts(root) {
3369
3435
  } catch {
3370
3436
  }
3371
3437
  try {
3372
- const makefile = await fsp2.readFile(path23.join(root, "Makefile"), "utf8");
3438
+ const makefile = await fsp2.readFile(path24.join(root, "Makefile"), "utf8");
3373
3439
  const targets = parseMakeTargets(makefile);
3374
3440
  facts.build ??= targets.has("build") ? "make build" : "make";
3375
3441
  if (targets.has("test")) facts.test ??= "make test";
@@ -4261,8 +4327,8 @@ function buildInitCommand(opts) {
4261
4327
  name: "init",
4262
4328
  description: "Create or update .wrongstack/AGENTS.md project context for the system prompt.",
4263
4329
  async run(_args, ctx) {
4264
- const dir = path23.join(ctx.projectRoot, ".wrongstack");
4265
- const file = path23.join(dir, "AGENTS.md");
4330
+ const dir = path24.join(ctx.projectRoot, ".wrongstack");
4331
+ const file = path24.join(dir, "AGENTS.md");
4266
4332
  const detected = await detectProjectFacts(ctx.projectRoot);
4267
4333
  const body = renderAgentsTemplate(detected);
4268
4334
  await fsp2.mkdir(dir, { recursive: true });
@@ -4453,18 +4519,18 @@ function stateBadge(state) {
4453
4519
  return color.dim(state);
4454
4520
  }
4455
4521
  }
4456
- async function readConfig(path24) {
4522
+ async function readConfig(path25) {
4457
4523
  try {
4458
- return JSON.parse(await fsp2.readFile(path24, "utf8"));
4524
+ return JSON.parse(await fsp2.readFile(path25, "utf8"));
4459
4525
  } catch {
4460
4526
  return {};
4461
4527
  }
4462
4528
  }
4463
- async function writeConfig(path24, cfg) {
4529
+ async function writeConfig(path25, cfg) {
4464
4530
  const raw = JSON.stringify(cfg, null, 2);
4465
- const tmp = path24 + ".tmp";
4531
+ const tmp = path25 + ".tmp";
4466
4532
  await fsp2.writeFile(tmp, raw, "utf8");
4467
- await fsp2.rename(tmp, path24);
4533
+ await fsp2.rename(tmp, path25);
4468
4534
  }
4469
4535
 
4470
4536
  // src/slash-commands/mcp.ts
@@ -4583,7 +4649,7 @@ function buildPlanCommand(opts) {
4583
4649
  name: "plan",
4584
4650
  description: "Strategic plan board: /plan [show|add <title>|start <id|#>|done <id|#>|remove <id|#>|promote <id|#> [subtask ...]|derive <id|#>|template [list|use <name>]|clear]",
4585
4651
  async run(args) {
4586
- const planPath = opts.planPath;
4652
+ const planPath = opts.paths?.projectPlan ?? opts.planPath;
4587
4653
  if (!planPath) return { message: "Plan storage is not configured for this session." };
4588
4654
  const ctx = opts.context;
4589
4655
  const sessionId = ctx?.session.id ?? "unknown";
@@ -5150,6 +5216,38 @@ ${color.dim("YOLO forced ON. Use /autonomy stop to end. Journal at /goal journal
5150
5216
  }
5151
5217
  };
5152
5218
  }
5219
+ function buildBtwCommand(opts) {
5220
+ return {
5221
+ name: "btw",
5222
+ description: 'Drop a "by the way" note for the running agent without interrupting it \u2014 delivered at the next step',
5223
+ argsHint: "<note>",
5224
+ help: [
5225
+ "/btw <note> Stash a note; the agent reads it at the start of its next",
5226
+ " iteration (between tool calls) without restarting.",
5227
+ "/btw Show how many notes are pending.",
5228
+ "",
5229
+ "Use `/steer` instead when you need to abort the current work immediately."
5230
+ ].join("\n"),
5231
+ async run(args) {
5232
+ const ctx = opts.context;
5233
+ if (!ctx) {
5234
+ return { message: "No active session \u2014 start a turn first, then use /btw to nudge it." };
5235
+ }
5236
+ const text = args.trim();
5237
+ if (!text) {
5238
+ const n = pendingBtwCount(ctx);
5239
+ return {
5240
+ message: n === 0 ? "No notes pending. Usage: /btw <note>" : `${n} note(s) pending \u2014 will reach the agent at its next step.`
5241
+ };
5242
+ }
5243
+ const pending = setBtwNote(ctx, text);
5244
+ return {
5245
+ message: `\u21AF Noted (${pending} pending) \u2014 the agent will fold this in at its next step:
5246
+ ${text}`
5247
+ };
5248
+ }
5249
+ };
5250
+ }
5153
5251
  var KNOWN_VERBS = /* @__PURE__ */ new Set([
5154
5252
  "",
5155
5253
  "show",
@@ -5180,7 +5278,7 @@ function buildGoalCommand(opts) {
5180
5278
  "Stage flow: decide \u2192 execute \u2192 reflect \u2192 sleep | paused | stopped",
5181
5279
  "Pausing stops after current iteration completes. Resume continues from next iteration.",
5182
5280
  "",
5183
- "Goals live in <projectRoot>/.wrongstack/goal.json and persist across sessions.",
5281
+ "Goals live in ~/.wrongstack/projects/<hash>/goal.json and persist across sessions.",
5184
5282
  "A goal is the prerequisite for /autonomy eternal \u2014 the engine consults it on",
5185
5283
  "every iteration to decide what to do next."
5186
5284
  ].join("\n"),
@@ -5189,7 +5287,7 @@ function buildGoalCommand(opts) {
5189
5287
  const [verbRaw, ...rest] = trimmed.split(/\s+/);
5190
5288
  const verb = (verbRaw ?? "").toLowerCase();
5191
5289
  const restJoined = rest.join(" ").trim();
5192
- const goalPath = goalFilePath(opts.projectRoot);
5290
+ const goalPath = opts.paths.projectGoal;
5193
5291
  const verbForDispatch = verb && !KNOWN_VERBS.has(verb) ? "set" : verb;
5194
5292
  const setText = verbForDispatch === "set" && !KNOWN_VERBS.has(verb) ? trimmed : restJoined;
5195
5293
  switch (verbForDispatch) {
@@ -5661,7 +5759,7 @@ var DEFAULTS = {
5661
5759
  cost: true
5662
5760
  };
5663
5761
  function resolveConfigPath() {
5664
- return process.env[CONFIG_ENV] ?? path23.join(process.env.HOME ?? "", ".wrongstack", "statusline.json");
5762
+ return process.env[CONFIG_ENV] ?? path24.join(process.env.HOME ?? "", ".wrongstack", "statusline.json");
5665
5763
  }
5666
5764
  async function loadStatuslineConfig() {
5667
5765
  const p = resolveConfigPath();
@@ -5675,7 +5773,7 @@ async function loadStatuslineConfig() {
5675
5773
  async function saveStatuslineConfig(cfg) {
5676
5774
  const p = resolveConfigPath();
5677
5775
  try {
5678
- await fsp2.mkdir(path23.dirname(p), { recursive: true });
5776
+ await fsp2.mkdir(path24.dirname(p), { recursive: true });
5679
5777
  await atomicWrite(p, JSON.stringify(cfg, null, 2));
5680
5778
  } catch (err) {
5681
5779
  throw new FsError({
@@ -6574,11 +6672,11 @@ When the error confidence is low (< 0.85) or the problem spans multiple files,
6574
6672
  };
6575
6673
  }
6576
6674
  function makeInstaller(opts, projectRoot, global) {
6577
- const globalRoot = path23.join(os6.homedir(), ".wrongstack");
6675
+ const globalRoot = path24.join(os6.homedir(), ".wrongstack");
6578
6676
  return new SkillInstaller({
6579
- manifestPath: path23.join(globalRoot, "installed-skills.json"),
6580
- projectSkillsDir: path23.join(projectRoot, ".wrongstack", "skills"),
6581
- globalSkillsDir: path23.join(globalRoot, "skills"),
6677
+ manifestPath: path24.join(globalRoot, "installed-skills.json"),
6678
+ projectSkillsDir: path24.join(projectRoot, ".wrongstack", "skills"),
6679
+ globalSkillsDir: path24.join(globalRoot, "skills"),
6582
6680
  projectHash: projectHash(projectRoot),
6583
6681
  skillLoader: opts.skillLoader
6584
6682
  });
@@ -6768,6 +6866,7 @@ function buildBuiltinSlashCommands(opts) {
6768
6866
  buildYoloCommand(opts),
6769
6867
  buildAutonomyCommand(opts),
6770
6868
  buildGoalCommand(opts),
6869
+ buildBtwCommand(opts),
6771
6870
  buildModeCommand(opts),
6772
6871
  buildExitCommand(opts),
6773
6872
  buildCommitCommand(),
@@ -6802,13 +6901,13 @@ var MANIFESTS = [
6802
6901
  ];
6803
6902
  async function detectProjectKind(projectRoot) {
6804
6903
  try {
6805
- await fsp2.access(path23.join(projectRoot, ".wrongstack", "AGENTS.md"));
6904
+ await fsp2.access(path24.join(projectRoot, ".wrongstack", "AGENTS.md"));
6806
6905
  return "initialized";
6807
6906
  } catch {
6808
6907
  }
6809
6908
  for (const m of MANIFESTS) {
6810
6909
  try {
6811
- await fsp2.access(path23.join(projectRoot, m));
6910
+ await fsp2.access(path24.join(projectRoot, m));
6812
6911
  return "project";
6813
6912
  } catch {
6814
6913
  }
@@ -6816,8 +6915,8 @@ async function detectProjectKind(projectRoot) {
6816
6915
  return "empty";
6817
6916
  }
6818
6917
  async function scaffoldAgentsMd(projectRoot) {
6819
- const dir = path23.join(projectRoot, ".wrongstack");
6820
- const file = path23.join(dir, "AGENTS.md");
6918
+ const dir = path24.join(projectRoot, ".wrongstack");
6919
+ const file = path24.join(dir, "AGENTS.md");
6821
6920
  const facts = await detectProjectFacts(projectRoot);
6822
6921
  const body = renderAgentsTemplate(facts);
6823
6922
  await fsp2.mkdir(dir, { recursive: true });
@@ -6830,7 +6929,7 @@ async function runProjectCheck(opts) {
6830
6929
  if (kind === "initialized") {
6831
6930
  renderer.write(
6832
6931
  `
6833
- ${color.green("\u2713")} Project initialized ${color.dim(`(${path23.join(projectRoot, ".wrongstack", "AGENTS.md")})`)}
6932
+ ${color.green("\u2713")} Project initialized ${color.dim(`(${path24.join(projectRoot, ".wrongstack", "AGENTS.md")})`)}
6834
6933
  `
6835
6934
  );
6836
6935
  return true;
@@ -6861,7 +6960,7 @@ async function runProjectCheck(opts) {
6861
6960
  }
6862
6961
  return true;
6863
6962
  }
6864
- const gitDir = path23.join(projectRoot, ".git");
6963
+ const gitDir = path24.join(projectRoot, ".git");
6865
6964
  let hasGit = false;
6866
6965
  try {
6867
6966
  await fsp2.access(gitDir);
@@ -7254,14 +7353,14 @@ function summarize(value, name) {
7254
7353
  if (typeof v === "object" && v !== null) {
7255
7354
  const o = v;
7256
7355
  if (name === "edit") {
7257
- const path24 = typeof o["path"] === "string" ? o["path"] : "";
7356
+ const path25 = typeof o["path"] === "string" ? o["path"] : "";
7258
7357
  const reps = typeof o["replacements"] === "number" ? o["replacements"] : 0;
7259
- return `${path24} ${reps} replacement${reps === 1 ? "" : "s"}`.trim();
7358
+ return `${path25} ${reps} replacement${reps === 1 ? "" : "s"}`.trim();
7260
7359
  }
7261
7360
  if (name === "write") {
7262
- const path24 = typeof o["path"] === "string" ? o["path"] : "";
7361
+ const path25 = typeof o["path"] === "string" ? o["path"] : "";
7263
7362
  const bytes = typeof o["bytes"] === "number" ? o["bytes"] : void 0;
7264
- return bytes !== void 0 ? `${path24} ${bytes}B` : path24;
7363
+ return bytes !== void 0 ? `${path25} ${bytes}B` : path25;
7265
7364
  }
7266
7365
  if (typeof o["count"] === "number") {
7267
7366
  return `${o["count"]} match${o["count"] === 1 ? "" : "es"}`;
@@ -8271,7 +8370,7 @@ var doctorCmd = async (_args, deps) => {
8271
8370
  }
8272
8371
  try {
8273
8372
  await fsp2.mkdir(deps.paths.projectSessions, { recursive: true });
8274
- const probe = path23.join(deps.paths.projectSessions, `.probe-${Date.now()}`);
8373
+ const probe = path24.join(deps.paths.projectSessions, `.probe-${Date.now()}`);
8275
8374
  await fsp2.writeFile(probe, "");
8276
8375
  await fsp2.unlink(probe);
8277
8376
  checks.push({ name: "sessions writable", status: "ok", detail: deps.paths.projectSessions });
@@ -8374,8 +8473,8 @@ var exportCmd = async (args, deps) => {
8374
8473
  return 1;
8375
8474
  }
8376
8475
  if (output) {
8377
- await fsp2.mkdir(path23.dirname(path23.resolve(deps.cwd, output)), { recursive: true });
8378
- await fsp2.writeFile(path23.resolve(deps.cwd, output), rendered, "utf8");
8476
+ await fsp2.mkdir(path24.dirname(path24.resolve(deps.cwd, output)), { recursive: true });
8477
+ await fsp2.writeFile(path24.resolve(deps.cwd, output), rendered, "utf8");
8379
8478
  deps.renderer.write(`Wrote ${rendered.length} bytes to ${output}
8380
8479
  `);
8381
8480
  } else {
@@ -8445,12 +8544,11 @@ var initCmd = async (_args, deps) => {
8445
8544
  await fsp2.mkdir(deps.paths.globalRoot, { recursive: true });
8446
8545
  const config = { version: 1, provider: providerId, model: modelId };
8447
8546
  if (apiKey) config.apiKey = apiKey;
8448
- const keyFile = path23.join(path23.dirname(deps.paths.globalConfig), ".key");
8449
- const vault = new DefaultSecretVault$1({ keyFile });
8547
+ const vault = new DefaultSecretVault$1({ keyFile: deps.paths.secretsKey });
8450
8548
  const encrypted = encryptConfigSecrets(config, vault);
8451
8549
  await atomicWrite(deps.paths.globalConfig, JSON.stringify(encrypted, null, 2));
8452
- await fsp2.mkdir(path23.join(deps.projectRoot, ".wrongstack"), { recursive: true });
8453
- const agentsFile = path23.join(deps.projectRoot, ".wrongstack", "AGENTS.md");
8550
+ await fsp2.mkdir(path24.join(deps.projectRoot, ".wrongstack"), { recursive: true });
8551
+ const agentsFile = path24.join(deps.projectRoot, ".wrongstack", "AGENTS.md");
8454
8552
  const projectFacts = await detectProjectFacts(deps.projectRoot);
8455
8553
  await atomicWrite(agentsFile, renderAgentsTemplate(projectFacts));
8456
8554
  deps.renderer.writeInfo(`Wrote ${deps.paths.globalConfig}`);
@@ -8756,7 +8854,7 @@ var usageCmd = async (_args, deps) => {
8756
8854
  return 0;
8757
8855
  };
8758
8856
  var projectsCmd = async (_args, deps) => {
8759
- const projectsRoot = path23.join(deps.paths.globalRoot, "projects");
8857
+ const projectsRoot = path24.join(deps.paths.globalRoot, "projects");
8760
8858
  try {
8761
8859
  const entries = await fsp2.readdir(projectsRoot);
8762
8860
  if (entries.length === 0) {
@@ -8766,7 +8864,7 @@ var projectsCmd = async (_args, deps) => {
8766
8864
  for (const hash of entries) {
8767
8865
  try {
8768
8866
  const meta = JSON.parse(
8769
- await fsp2.readFile(path23.join(projectsRoot, hash, "meta.json"), "utf8")
8867
+ await fsp2.readFile(path24.join(projectsRoot, hash, "meta.json"), "utf8")
8770
8868
  );
8771
8869
  deps.renderer.write(
8772
8870
  ` ${color.dim(hash)} ${color.dim(meta.lastSeen ?? "")} ${meta.root ?? "?"}
@@ -8896,14 +8994,12 @@ Cache age: ${isFinite(age) ? `${Math.round(age / 60)}m` : "never fetched"}. Run
8896
8994
  );
8897
8995
  return 0;
8898
8996
  };
8899
-
8900
- // src/subcommands/handlers/helpers.ts
8901
8997
  function redactKeys(obj) {
8902
8998
  if (!obj || typeof obj !== "object") return obj;
8903
8999
  if (Array.isArray(obj)) return obj.map(redactKeys);
8904
9000
  const out = {};
8905
9001
  for (const [k, v] of Object.entries(obj)) {
8906
- if (/api.?key|secret|token|pass/i.test(k) && typeof v === "string" && v.length > 0)
9002
+ if (isSecretField(k) && typeof v === "string" && v.length > 0)
8907
9003
  out[k] = "[REDACTED]";
8908
9004
  else out[k] = redactKeys(v);
8909
9005
  }
@@ -8927,7 +9023,7 @@ async function listFleetRuns(deps) {
8927
9023
  }
8928
9024
  const runs = [];
8929
9025
  for (const id of entries) {
8930
- const runDir = path23.join(deps.paths.projectSessions, id);
9026
+ const runDir = path24.join(deps.paths.projectSessions, id);
8931
9027
  let stat3;
8932
9028
  try {
8933
9029
  stat3 = await fsp2.stat(runDir);
@@ -8940,17 +9036,17 @@ async function listFleetRuns(deps) {
8940
9036
  let subagentCount = 0;
8941
9037
  let subagentsDir;
8942
9038
  try {
8943
- await fsp2.access(path23.join(runDir, "fleet.json"));
9039
+ await fsp2.access(path24.join(runDir, "fleet.json"));
8944
9040
  manifest = true;
8945
9041
  } catch {
8946
9042
  }
8947
9043
  try {
8948
- await fsp2.access(path23.join(runDir, "checkpoint.json"));
9044
+ await fsp2.access(path24.join(runDir, "checkpoint.json"));
8949
9045
  checkpoint = true;
8950
9046
  } catch {
8951
9047
  }
8952
9048
  try {
8953
- subagentsDir = path23.join(runDir, "subagents");
9049
+ subagentsDir = path24.join(runDir, "subagents");
8954
9050
  const files = await fsp2.readdir(subagentsDir);
8955
9051
  subagentCount = files.filter((f) => f.endsWith(".jsonl")).length;
8956
9052
  } catch {
@@ -8979,7 +9075,7 @@ async function listFleetRuns(deps) {
8979
9075
  return 0;
8980
9076
  }
8981
9077
  async function showFleetRun(runId, deps) {
8982
- const runDir = path23.join(deps.paths.projectSessions, runId);
9078
+ const runDir = path24.join(deps.paths.projectSessions, runId);
8983
9079
  let stat3;
8984
9080
  try {
8985
9081
  stat3 = await fsp2.stat(runDir);
@@ -8996,7 +9092,7 @@ async function showFleetRun(runId, deps) {
8996
9092
  deps.renderer.write(color.bold(`
8997
9093
  Fleet Run: ${runId}
8998
9094
  `) + "\n");
8999
- const manifestPath = path23.join(runDir, "fleet.json");
9095
+ const manifestPath = path24.join(runDir, "fleet.json");
9000
9096
  let manifestData = null;
9001
9097
  try {
9002
9098
  manifestData = await fsp2.readFile(manifestPath, "utf8");
@@ -9012,7 +9108,7 @@ Fleet Run: ${runId}
9012
9108
  deps.renderer.write(` ${color.dim("\u25CB")} fleet.json \u2014 not found
9013
9109
  `);
9014
9110
  }
9015
- const checkpointPath = path23.join(runDir, "checkpoint.json");
9111
+ const checkpointPath = path24.join(runDir, "checkpoint.json");
9016
9112
  let checkpointData = null;
9017
9113
  try {
9018
9114
  checkpointData = await fsp2.readFile(checkpointPath, "utf8");
@@ -9059,7 +9155,7 @@ Fleet Run: ${runId}
9059
9155
  } catch {
9060
9156
  }
9061
9157
  }
9062
- const subagentsDir = path23.join(runDir, "subagents");
9158
+ const subagentsDir = path24.join(runDir, "subagents");
9063
9159
  let subagentFiles = [];
9064
9160
  try {
9065
9161
  subagentFiles = await fsp2.readdir(subagentsDir);
@@ -9071,7 +9167,7 @@ Fleet Run: ${runId}
9071
9167
  Subagent transcripts (${subagentFiles.length}):
9072
9168
  `);
9073
9169
  for (const f of subagentFiles.sort()) {
9074
- const filePath = path23.join(subagentsDir, f);
9170
+ const filePath = path24.join(subagentsDir, f);
9075
9171
  let size;
9076
9172
  try {
9077
9173
  const s = await fsp2.stat(filePath);
@@ -9088,7 +9184,7 @@ Fleet Run: ${runId}
9088
9184
  ${color.dim("\u25CB")} No subagent transcripts
9089
9185
  `);
9090
9186
  }
9091
- const sharedDir = path23.join(runDir, "shared");
9187
+ const sharedDir = path24.join(runDir, "shared");
9092
9188
  try {
9093
9189
  const files = await fsp2.readdir(sharedDir);
9094
9190
  deps.renderer.write(`
@@ -9255,7 +9351,7 @@ function findSessionId(args) {
9255
9351
  var rewindCmd = async (args, deps) => {
9256
9352
  const flags = parseRewindFlags(args);
9257
9353
  const wpaths = resolveWstackPaths({ projectRoot: deps.projectRoot });
9258
- const sessionsDir = path23.join(wpaths.globalRoot, "sessions");
9354
+ const sessionsDir = path24.join(wpaths.globalRoot, "sessions");
9259
9355
  const rewind = new DefaultSessionRewinder(sessionsDir);
9260
9356
  let sessionId = findSessionId(args);
9261
9357
  if (!sessionId) {
@@ -9492,7 +9588,7 @@ function resolveBundledSkillsDir() {
9492
9588
  try {
9493
9589
  const req2 = createRequire(import.meta.url);
9494
9590
  const corePkg = req2.resolve("@wrongstack/core/package.json");
9495
- return path23.join(path23.dirname(corePkg), "skills");
9591
+ return path24.join(path24.dirname(corePkg), "skills");
9496
9592
  } catch {
9497
9593
  return void 0;
9498
9594
  }
@@ -9623,7 +9719,9 @@ async function boot(argv) {
9623
9719
  flags["no-tui"] = true;
9624
9720
  }
9625
9721
  if (choices.yolo !== config.yolo) config = patchConfig(config, { yolo: choices.yolo });
9626
- printLaunchHints(renderer, flags);
9722
+ printLaunchHints(renderer, flags, {
9723
+ cursorFile: path24.join(wpaths.cacheDir, "hint-cursor")
9724
+ });
9627
9725
  }
9628
9726
  return {
9629
9727
  config,
@@ -10653,7 +10751,7 @@ async function execute(deps) {
10653
10751
  supportsVision,
10654
10752
  attachments,
10655
10753
  effectiveMaxContext,
10656
- projectName: path23.basename(projectRoot) || void 0,
10754
+ projectName: path24.basename(projectRoot) || void 0,
10657
10755
  projectRoot,
10658
10756
  getAutonomy,
10659
10757
  onAutonomy,
@@ -10675,7 +10773,7 @@ async function execute(deps) {
10675
10773
  supportsVision,
10676
10774
  attachments,
10677
10775
  effectiveMaxContext,
10678
- projectName: path23.basename(projectRoot) || void 0,
10776
+ projectName: path24.basename(projectRoot) || void 0,
10679
10777
  getAutonomy,
10680
10778
  onAutonomy,
10681
10779
  getEternalEngine,
@@ -10777,7 +10875,7 @@ var MultiAgentHost = class {
10777
10875
  doneCondition: { type: "all_tasks_done" },
10778
10876
  maxConcurrent: this.opts.maxConcurrent ?? 4
10779
10877
  };
10780
- const defaultScratchpad = this.opts.sharedScratchpadPath || (this.opts.sessionsRoot && this.opts.directorRunId ? path23.join(this.opts.sessionsRoot, this.opts.directorRunId, "shared") : void 0);
10878
+ const defaultScratchpad = this.opts.sharedScratchpadPath || (this.opts.sessionsRoot && this.opts.directorRunId ? path24.join(this.opts.sessionsRoot, this.opts.directorRunId, "shared") : void 0);
10781
10879
  this.director = new Director({
10782
10880
  config: coordinatorConfig,
10783
10881
  manifestPath: this.opts.manifestPath,
@@ -11022,7 +11120,7 @@ var MultiAgentHost = class {
11022
11120
  model: opts?.model,
11023
11121
  tools: opts?.tools
11024
11122
  };
11025
- const transcriptPath = this.sessionFactory ? path23.join(this.sessionFactory.dir, `${subagentConfig.name}.jsonl`) : void 0;
11123
+ const transcriptPath = this.sessionFactory ? path24.join(this.sessionFactory.dir, `${subagentConfig.name}.jsonl`) : void 0;
11026
11124
  const { subagentId, taskId } = await this._spawnAndAssign(subagentConfig);
11027
11125
  this.fleetManager?.addPendingTask(taskId, subagentId, description);
11028
11126
  this.deps.events.emit("subagent.spawned", {
@@ -11165,16 +11263,16 @@ var MultiAgentHost = class {
11165
11263
  if (this.director) return this.director;
11166
11264
  this.opts.directorMode = true;
11167
11265
  if (this.opts.fleetRoot && !this.opts.manifestPath) {
11168
- this.opts.manifestPath = path23.join(this.opts.fleetRoot, "fleet.json");
11266
+ this.opts.manifestPath = path24.join(this.opts.fleetRoot, "fleet.json");
11169
11267
  }
11170
11268
  if (this.opts.fleetRoot && !this.opts.sharedScratchpadPath) {
11171
- this.opts.sharedScratchpadPath = path23.join(this.opts.fleetRoot, "shared");
11269
+ this.opts.sharedScratchpadPath = path24.join(this.opts.fleetRoot, "shared");
11172
11270
  }
11173
11271
  if (this.opts.fleetRoot && !this.opts.sessionsRoot) {
11174
- this.opts.sessionsRoot = path23.join(this.opts.fleetRoot, "subagents");
11272
+ this.opts.sessionsRoot = path24.join(this.opts.fleetRoot, "subagents");
11175
11273
  }
11176
11274
  if (this.opts.fleetRoot && !this.opts.stateCheckpointPath) {
11177
- this.opts.stateCheckpointPath = path23.join(this.opts.fleetRoot, "director-state.json");
11275
+ this.opts.stateCheckpointPath = path24.join(this.opts.fleetRoot, "director-state.json");
11178
11276
  }
11179
11277
  await this.ensureDirector();
11180
11278
  return this.director ?? null;
@@ -11340,11 +11438,11 @@ var SessionStats = class {
11340
11438
  if (e.name === "bash") this.bashCommands++;
11341
11439
  else if (e.name === "fetch") this.fetches++;
11342
11440
  if (!e.ok) return;
11343
- const path24 = typeof input?.path === "string" ? input.path : void 0;
11344
- if (e.name === "read" && path24) this.readPaths.add(path24);
11345
- else if (e.name === "edit" && path24) this.editedPaths.add(path24);
11346
- else if (e.name === "write" && path24) {
11347
- this.writtenPaths.add(path24);
11441
+ const path25 = typeof input?.path === "string" ? input.path : void 0;
11442
+ if (e.name === "read" && path25) this.readPaths.add(path25);
11443
+ else if (e.name === "edit" && path25) this.editedPaths.add(path25);
11444
+ else if (e.name === "write" && path25) {
11445
+ this.writtenPaths.add(path25);
11348
11446
  const content = typeof input?.content === "string" ? input.content : "";
11349
11447
  this.bytesWritten += Buffer.byteLength(content, "utf8");
11350
11448
  }
@@ -11632,7 +11730,7 @@ function setupMetrics(params) {
11632
11730
  const dumpMetrics = () => {
11633
11731
  if (!metricsSink) return;
11634
11732
  try {
11635
- const out = path23.join(wpaths.projectSessions, "metrics.json");
11733
+ const out = path24.join(wpaths.projectSessions, "metrics.json");
11636
11734
  const snap = metricsSink.snapshot();
11637
11735
  writeFileSync(out, JSON.stringify(snap, null, 2));
11638
11736
  } catch {
@@ -11823,12 +11921,12 @@ async function setupSession(params) {
11823
11921
  }
11824
11922
  const sessionRef = { current: session };
11825
11923
  await recoveryLock.write(session.id).catch(() => void 0);
11826
- const attachments = new DefaultAttachmentStore({ spoolDir: path23.join(wpaths.projectSessions, session.id, "attachments") });
11827
- const queueStore = new QueueStore({ dir: path23.join(wpaths.projectSessions, session.id) });
11924
+ const attachments = new DefaultAttachmentStore({ spoolDir: path24.join(wpaths.projectSessions, session.id, "attachments") });
11925
+ const queueStore = new QueueStore({ dir: path24.join(wpaths.projectSessions, session.id) });
11828
11926
  const ctxSignal = new AbortController().signal;
11829
11927
  const context = new Context({ systemPrompt, provider, session, signal: ctxSignal, tokenCounter, cwd, projectRoot, model: config.model });
11830
11928
  if (restoredMessages.length > 0) context.state.replaceMessages(restoredMessages);
11831
- const todosCheckpointPath = path23.join(wpaths.projectSessions, `${session.id}.todos.json`);
11929
+ const todosCheckpointPath = path24.join(wpaths.projectSessions, `${session.id}.todos.json`);
11832
11930
  if (resumeId) {
11833
11931
  try {
11834
11932
  const restoredTodos = await loadTodosCheckpoint(todosCheckpointPath);
@@ -11840,13 +11938,13 @@ async function setupSession(params) {
11840
11938
  }
11841
11939
  }
11842
11940
  const detachTodosCheckpoint = attachTodosCheckpoint(context.state, todosCheckpointPath, session.id);
11843
- const planPath = path23.join(wpaths.projectSessions, `${session.id}.plan.json`);
11941
+ const planPath = path24.join(wpaths.projectSessions, `${session.id}.plan.json`);
11844
11942
  context.state.setMeta("plan.path", planPath);
11845
11943
  let dirState;
11846
11944
  if (resumeId) {
11847
11945
  try {
11848
- const fleetRoot = path23.join(wpaths.projectSessions, session.id);
11849
- dirState = await loadDirectorState(path23.join(fleetRoot, "director-state.json"));
11946
+ const fleetRoot = path24.join(wpaths.projectSessions, session.id);
11947
+ dirState = await loadDirectorState(path24.join(fleetRoot, "director-state.json"));
11850
11948
  if (dirState) {
11851
11949
  const tCounts = {};
11852
11950
  for (const t of dirState.tasks) tCounts[t.status] = (tCounts[t.status] ?? 0) + 1;
@@ -11873,7 +11971,7 @@ function resolveBundledSkillsDir2() {
11873
11971
  try {
11874
11972
  const req2 = createRequire(import.meta.url);
11875
11973
  const corePkg = req2.resolve("@wrongstack/core/package.json");
11876
- return path23.join(path23.dirname(corePkg), "skills");
11974
+ return path24.join(path24.dirname(corePkg), "skills");
11877
11975
  } catch {
11878
11976
  return void 0;
11879
11977
  }
@@ -11963,7 +12061,7 @@ async function main(argv) {
11963
12061
  const skillLoader = container.resolve(TOKENS.SkillLoader);
11964
12062
  const sessionRef = {};
11965
12063
  const autonomyModeRef = { current: "off" };
11966
- const goalPathForPrompt = path23.join(projectRoot, ".wrongstack", "goal.json");
12064
+ const goalPathForPrompt = wpaths.projectGoal;
11967
12065
  container.bind(
11968
12066
  TOKENS.SystemPromptBuilder,
11969
12067
  () => new DefaultSystemPromptBuilder({
@@ -11973,7 +12071,7 @@ async function main(argv) {
11973
12071
  modeId,
11974
12072
  modePrompt,
11975
12073
  modelCapabilities,
11976
- planPath: () => sessionRef.current ? path23.join(wpaths.projectSessions, `${sessionRef.current.id}.plan.json`) : void 0,
12074
+ planPath: () => sessionRef.current ? path24.join(wpaths.projectSessions, `${sessionRef.current.id}.plan.json`) : void 0,
11977
12075
  contributors: [
11978
12076
  // Injects the ETERNAL AUTONOMY block when the user has activated
11979
12077
  // `/autonomy eternal`. Without this, the per-iteration directive
@@ -12172,12 +12270,12 @@ async function main(argv) {
12172
12270
  }
12173
12271
  }
12174
12272
  };
12175
- const fleetRoot = directorMode ? path23.join(wpaths.projectSessions, session.id) : void 0;
12176
- const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] : path23.join(fleetRoot, "fleet.json") : void 0;
12177
- const sharedScratchpadPath = directorMode ? path23.join(fleetRoot, "shared") : void 0;
12178
- const subagentSessionsRoot = directorMode ? path23.join(fleetRoot, "subagents") : void 0;
12179
- const stateCheckpointPath = directorMode ? path23.join(fleetRoot, "director-state.json") : void 0;
12180
- const fleetRootForPromotion = path23.join(wpaths.projectSessions, session.id);
12273
+ const fleetRoot = directorMode ? path24.join(wpaths.projectSessions, session.id) : void 0;
12274
+ const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] : path24.join(fleetRoot, "fleet.json") : void 0;
12275
+ const sharedScratchpadPath = directorMode ? path24.join(fleetRoot, "shared") : void 0;
12276
+ const subagentSessionsRoot = directorMode ? path24.join(fleetRoot, "subagents") : void 0;
12277
+ const stateCheckpointPath = directorMode ? path24.join(fleetRoot, "director-state.json") : void 0;
12278
+ const fleetRootForPromotion = path24.join(wpaths.projectSessions, session.id);
12181
12279
  const multiAgentHost = new MultiAgentHost(
12182
12280
  {
12183
12281
  container,
@@ -12269,6 +12367,7 @@ async function main(argv) {
12269
12367
  const slashCmds = buildBuiltinSlashCommands({
12270
12368
  registry: slashRegistry,
12271
12369
  toolRegistry,
12370
+ paths: wpaths,
12272
12371
  compactor: container.resolve(TOKENS.Compactor),
12273
12372
  sessionStore,
12274
12373
  skillLoader,
@@ -12486,7 +12585,7 @@ async function main(argv) {
12486
12585
  return director.spawn(cfg);
12487
12586
  },
12488
12587
  onFleetLog: async (subagentId, mode) => {
12489
- const subagentsRoot = path23.join(fleetRootForPromotion, "subagents");
12588
+ const subagentsRoot = path24.join(fleetRootForPromotion, "subagents");
12490
12589
  let runDirs;
12491
12590
  try {
12492
12591
  runDirs = await fsp2.readdir(subagentsRoot);
@@ -12495,7 +12594,7 @@ async function main(argv) {
12495
12594
  }
12496
12595
  const found = [];
12497
12596
  for (const runId of runDirs) {
12498
- const runDir = path23.join(subagentsRoot, runId);
12597
+ const runDir = path24.join(subagentsRoot, runId);
12499
12598
  let files;
12500
12599
  try {
12501
12600
  files = await fsp2.readdir(runDir);
@@ -12504,7 +12603,7 @@ async function main(argv) {
12504
12603
  }
12505
12604
  for (const f of files) {
12506
12605
  if (!f.endsWith(".jsonl")) continue;
12507
- const full = path23.join(runDir, f);
12606
+ const full = path24.join(runDir, f);
12508
12607
  try {
12509
12608
  const stat3 = await fsp2.stat(full);
12510
12609
  found.push({
@@ -12601,7 +12700,7 @@ async function main(argv) {
12601
12700
  }
12602
12701
  const dir = await multiAgentHost.ensureDirector();
12603
12702
  if (!dir) return "Director is not available.";
12604
- const dirStatePath = path23.join(fleetRootForPromotion, "director-state.json");
12703
+ const dirStatePath = path24.join(fleetRootForPromotion, "director-state.json");
12605
12704
  const prior = await loadDirectorState(dirStatePath);
12606
12705
  if (!prior) {
12607
12706
  return "No prior director-state.json found \u2014 nothing to retry.";
@@ -12672,9 +12771,9 @@ async function main(argv) {
12672
12771
  for (const tool of director2.tools(FLEET_ROSTER)) {
12673
12772
  toolRegistry.register(tool);
12674
12773
  }
12675
- const mp = path23.join(fleetRootForPromotion, "fleet.json");
12676
- const sp = path23.join(fleetRootForPromotion, "shared");
12677
- const ss = path23.join(fleetRootForPromotion, "subagents");
12774
+ const mp = path24.join(fleetRootForPromotion, "fleet.json");
12775
+ const sp = path24.join(fleetRootForPromotion, "shared");
12776
+ const ss = path24.join(fleetRootForPromotion, "subagents");
12678
12777
  const lines = [
12679
12778
  `${color.green("\u2713")} Promoted to director mode.`,
12680
12779
  ` Roster: ${Object.keys(FLEET_ROSTER).join(", ")}`,
@@ -12828,13 +12927,62 @@ Restart WrongStack to load or unload plugin code in this session.`;
12828
12927
  onDispatchClassify: makeProviderClassifier(
12829
12928
  context.provider,
12830
12929
  context.model
12831
- )
12930
+ ),
12931
+ onSddParallelRun: async (opts) => {
12932
+ const { SddParallelRun } = await import('@wrongstack/core');
12933
+ const sdd = await Promise.resolve().then(() => (init_sdd(), sdd_exports));
12934
+ const tracker = sdd.getTaskTracker();
12935
+ const builder = sdd.getActiveBuilder();
12936
+ if (!tracker || !builder) {
12937
+ return "No active SDD session with tasks. Use /sdd new to start one.";
12938
+ }
12939
+ const session2 = builder.getSession();
12940
+ if (session2.phase !== "executing" && session2.phase !== "task_review") {
12941
+ return `Cannot run parallel in phase "${session2.phase}". Use /sdd approve first.`;
12942
+ }
12943
+ const graphId = sdd.getTaskGraphId();
12944
+ const graphStore = new (await import('@wrongstack/core')).TaskGraphStore({
12945
+ baseDir: wpaths.projectTaskGraphs
12946
+ });
12947
+ const graph = graphId ? await graphStore.load(graphId) : null;
12948
+ if (!graph) {
12949
+ return "No task graph found for the current SDD session.";
12950
+ }
12951
+ const subagentFactory = multiAgentHost.makeSubagentFactory(config);
12952
+ const run = new SddParallelRun({
12953
+ tracker,
12954
+ graph,
12955
+ agent,
12956
+ projectRoot,
12957
+ parallelSlots: opts?.parallelSlots,
12958
+ subagentFactory,
12959
+ onProgress: (p) => {
12960
+ renderer.write(` \u2591 wave ${p.wave + 1} \xB7 ${p.completed}/${p.total} tasks \xB7 ${p.percent}% done
12961
+ `);
12962
+ }
12963
+ });
12964
+ globalThis.__sddParallelRun = run;
12965
+ const result = await run.run();
12966
+ delete globalThis.__sddParallelRun;
12967
+ const lines = [
12968
+ `SDD parallel run complete:`,
12969
+ ` ${result.totalWaves} waves \xB7 ${result.totalCompleted} done \xB7 ${result.totalFailed} failed`,
12970
+ ` ${(result.totalDurationMs / 1e3).toFixed(1)}s total`
12971
+ ];
12972
+ if (result.deadlocked) lines.push(color.red(" \u26A0 deadlock \u2014 tasks blocked by failed tasks."));
12973
+ if (result.stopRequested) lines.push(color.yellow(" \u26A1 stopped by user."));
12974
+ return lines.join("\n");
12975
+ },
12976
+ onSddParallelStop: () => {
12977
+ const run = globalThis.__sddParallelRun;
12978
+ run?.stop();
12979
+ }
12832
12980
  });
12833
12981
  for (const cmd of slashCmds) slashRegistry.register(cmd);
12834
12982
  const eternalFlag = typeof flags["eternal"] === "string" ? flags["eternal"].trim() : "";
12835
12983
  if (eternalFlag.length > 0) {
12836
- const { saveGoal: saveGoal2, emptyGoal: emptyGoal2, goalFilePath: goalFilePath4, loadGoal: loadGoal4 } = await import('@wrongstack/core');
12837
- const goalPath = goalFilePath4(projectRoot);
12984
+ const { saveGoal: saveGoal2, emptyGoal: emptyGoal2, goalFilePath: goalFilePath3, loadGoal: loadGoal4 } = await import('@wrongstack/core');
12985
+ const goalPath = goalFilePath3(projectRoot);
12838
12986
  const prior = await loadGoal4(goalPath);
12839
12987
  const next = prior ? { ...prior, goal: eternalFlag, setAt: (/* @__PURE__ */ new Date()).toISOString(), lastActivityAt: (/* @__PURE__ */ new Date()).toISOString() } : emptyGoal2(eternalFlag);
12840
12988
  await saveGoal2(goalPath, next);