@wrongstack/cli 0.8.2 → 0.8.5

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,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
- import * as path24 from 'path';
2
+ import * as path25 from 'path';
3
3
  import { join } from 'path';
4
- import * as fsp2 from 'fs/promises';
4
+ import * as fsp3 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, pendingBtwCount, setBtwNote, 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, PhaseStore, AutoPhasePlanner, PhaseGraphBuilder, PhaseOrchestrator, 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';
@@ -403,7 +403,7 @@ function buildSddCommand(opts) {
403
403
  if (!sessionState.getBuilder() && !forceFlag) {
404
404
  const sessionPath = opts.paths.projectSddSession;
405
405
  try {
406
- await fsp2.access(sessionPath);
406
+ await fsp3.access(sessionPath);
407
407
  const projectContext2 = await gatherProjectContext(opts.context?.projectRoot ?? process.cwd());
408
408
  const tempBuilder = new AISpecBuilder({
409
409
  store: specStore,
@@ -1054,16 +1054,16 @@ Start executing the tasks one by one.`
1054
1054
  const sessionPath = opts.paths.projectSddSession;
1055
1055
  let deletedFromDisk = false;
1056
1056
  try {
1057
- await fsp2.unlink(sessionPath);
1057
+ await fsp3.unlink(sessionPath);
1058
1058
  deletedFromDisk = true;
1059
1059
  } catch {
1060
1060
  }
1061
1061
  try {
1062
- await fsp2.rm(opts.paths.projectSpecs, { recursive: true, force: true });
1062
+ await fsp3.rm(opts.paths.projectSpecs, { recursive: true, force: true });
1063
1063
  } catch {
1064
1064
  }
1065
1065
  try {
1066
- await fsp2.rm(opts.paths.projectTaskGraphs, { recursive: true, force: true });
1066
+ await fsp3.rm(opts.paths.projectTaskGraphs, { recursive: true, force: true });
1067
1067
  } catch {
1068
1068
  }
1069
1069
  const cancelBuilder = sddState.getBuilder();
@@ -1398,8 +1398,8 @@ function sddHelp() {
1398
1398
  async function gatherProjectContext(projectRoot) {
1399
1399
  const parts = [];
1400
1400
  try {
1401
- const pkgPath = path24.join(projectRoot, "package.json");
1402
- const pkgRaw = await fsp2.readFile(pkgPath, "utf8");
1401
+ const pkgPath = path25.join(projectRoot, "package.json");
1402
+ const pkgRaw = await fsp3.readFile(pkgPath, "utf8");
1403
1403
  const pkg = JSON.parse(pkgRaw);
1404
1404
  parts.push(`Project: ${String(pkg.name ?? "unknown")}`);
1405
1405
  parts.push(`Description: ${String(pkg.description ?? "none")}`);
@@ -1414,14 +1414,14 @@ async function gatherProjectContext(projectRoot) {
1414
1414
  } catch {
1415
1415
  }
1416
1416
  try {
1417
- const tsconfigPath = path24.join(projectRoot, "tsconfig.json");
1418
- await fsp2.access(tsconfigPath);
1417
+ const tsconfigPath = path25.join(projectRoot, "tsconfig.json");
1418
+ await fsp3.access(tsconfigPath);
1419
1419
  parts.push("Language: TypeScript");
1420
1420
  } catch {
1421
1421
  }
1422
1422
  try {
1423
- const srcDir = path24.join(projectRoot, "src");
1424
- const entries = await fsp2.readdir(srcDir, { withFileTypes: true });
1423
+ const srcDir = path25.join(projectRoot, "src");
1424
+ const entries = await fsp3.readdir(srcDir, { withFileTypes: true });
1425
1425
  const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
1426
1426
  if (dirs.length > 0) {
1427
1427
  parts.push(`Source structure: src/${dirs.join(", src/")}`);
@@ -1576,7 +1576,7 @@ __export(update_check_exports, {
1576
1576
  getUpdateNotification: () => getUpdateNotification
1577
1577
  });
1578
1578
  function cachePath(homeFn = defaultHomeDir2) {
1579
- return path24.join(homeFn(), ".wrongstack", "update-cache.json");
1579
+ return path25.join(homeFn(), ".wrongstack", "update-cache.json");
1580
1580
  }
1581
1581
  function currentVersion() {
1582
1582
  const req2 = createRequire(import.meta.url);
@@ -1603,7 +1603,7 @@ function isNewer(a, b) {
1603
1603
  }
1604
1604
  async function readCache(homeFn = defaultHomeDir2) {
1605
1605
  try {
1606
- const raw = await fsp2.readFile(cachePath(homeFn), "utf8");
1606
+ const raw = await fsp3.readFile(cachePath(homeFn), "utf8");
1607
1607
  const entry = JSON.parse(raw);
1608
1608
  if (Date.now() - entry.timestamp > CACHE_TTL_MS) return null;
1609
1609
  return entry;
@@ -1613,9 +1613,9 @@ async function readCache(homeFn = defaultHomeDir2) {
1613
1613
  }
1614
1614
  async function writeCache(entry, homeFn = defaultHomeDir2) {
1615
1615
  try {
1616
- const dir = path24.dirname(cachePath(homeFn));
1617
- await fsp2.mkdir(dir, { recursive: true });
1618
- await fsp2.writeFile(cachePath(homeFn), JSON.stringify(entry, null, 2), "utf8");
1616
+ const dir = path25.dirname(cachePath(homeFn));
1617
+ await fsp3.mkdir(dir, { recursive: true });
1618
+ await fsp3.writeFile(cachePath(homeFn), JSON.stringify(entry, null, 2), "utf8");
1619
1619
  } catch {
1620
1620
  }
1621
1621
  }
@@ -1823,10 +1823,27 @@ async function runWebUI(opts) {
1823
1823
  });
1824
1824
  wss.on("connection", (ws, req2) => {
1825
1825
  const isLoopback = (hostname) => hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
1826
+ const tokenMatches = (provided) => {
1827
+ if (!provided) return false;
1828
+ const a = Buffer.from(provided);
1829
+ const b = Buffer.from(authToken);
1830
+ return a.length === b.length && crypto2.timingSafeEqual(a, b);
1831
+ };
1826
1832
  try {
1827
1833
  const url = new URL(req2.url ?? "/", `http://localhost:${port}`);
1828
1834
  const token = url.searchParams.get("token");
1829
- const tokenOk = token === authToken;
1835
+ const tokenOk = tokenMatches(token);
1836
+ const hostHeader = (req2.headers.host ?? "").trim();
1837
+ let hostOk = false;
1838
+ try {
1839
+ hostOk = !!hostHeader && isLoopback(new URL(`http://${hostHeader}`).hostname);
1840
+ } catch {
1841
+ hostOk = false;
1842
+ }
1843
+ if (!hostOk) {
1844
+ ws.close(4003, "Forbidden: non-loopback Host header");
1845
+ return;
1846
+ }
1830
1847
  const origin = req2.headers.origin;
1831
1848
  if (origin) {
1832
1849
  try {
@@ -2206,7 +2223,7 @@ async function runWebUI(opts) {
2206
2223
  if (!opts.globalConfigPath) return {};
2207
2224
  let raw;
2208
2225
  try {
2209
- raw = await fsp2.readFile(opts.globalConfigPath, "utf8");
2226
+ raw = await fsp3.readFile(opts.globalConfigPath, "utf8");
2210
2227
  } catch {
2211
2228
  return {};
2212
2229
  }
@@ -2217,7 +2234,7 @@ async function runWebUI(opts) {
2217
2234
  return {};
2218
2235
  }
2219
2236
  if (!parsed.providers) return {};
2220
- const keyFile = path24.join(path24.dirname(opts.globalConfigPath), ".key");
2237
+ const keyFile = path25.join(path25.dirname(opts.globalConfigPath), ".key");
2221
2238
  const vault = new DefaultSecretVault$1({ keyFile });
2222
2239
  return decryptConfigSecrets$1(parsed.providers, vault);
2223
2240
  }
@@ -2226,7 +2243,7 @@ async function runWebUI(opts) {
2226
2243
  let raw;
2227
2244
  let fileExists = true;
2228
2245
  try {
2229
- raw = await fsp2.readFile(opts.globalConfigPath, "utf8");
2246
+ raw = await fsp3.readFile(opts.globalConfigPath, "utf8");
2230
2247
  } catch (err) {
2231
2248
  if (err.code !== "ENOENT") {
2232
2249
  throw new Error(
@@ -2250,7 +2267,7 @@ async function runWebUI(opts) {
2250
2267
  parsed = {};
2251
2268
  }
2252
2269
  parsed.providers = providers;
2253
- const keyFile = path24.join(path24.dirname(opts.globalConfigPath), ".key");
2270
+ const keyFile = path25.join(path25.dirname(opts.globalConfigPath), ".key");
2254
2271
  const vault = new DefaultSecretVault$1({ keyFile });
2255
2272
  const encrypted = encryptConfigSecrets(parsed, vault);
2256
2273
  await atomicWrite(opts.globalConfigPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
@@ -2459,7 +2476,7 @@ function parseSpawnFlags(input) {
2459
2476
  }
2460
2477
  async function pathExists(file) {
2461
2478
  try {
2462
- await fsp2.access(file);
2479
+ await fsp3.access(file);
2463
2480
  return true;
2464
2481
  } catch {
2465
2482
  return false;
@@ -2470,10 +2487,10 @@ async function detectPackageManager(root, declared) {
2470
2487
  const name = declared.split("@")[0];
2471
2488
  if (name) return name;
2472
2489
  }
2473
- if (await pathExists(path24.join(root, "pnpm-lock.yaml"))) return "pnpm";
2474
- if (await pathExists(path24.join(root, "bun.lockb"))) return "bun";
2475
- if (await pathExists(path24.join(root, "bun.lock"))) return "bun";
2476
- if (await pathExists(path24.join(root, "yarn.lock"))) return "yarn";
2490
+ if (await pathExists(path25.join(root, "pnpm-lock.yaml"))) return "pnpm";
2491
+ if (await pathExists(path25.join(root, "bun.lockb"))) return "bun";
2492
+ if (await pathExists(path25.join(root, "bun.lock"))) return "bun";
2493
+ if (await pathExists(path25.join(root, "yarn.lock"))) return "yarn";
2477
2494
  return "npm";
2478
2495
  }
2479
2496
  function hasUsableScript(scripts, name) {
@@ -2494,7 +2511,7 @@ function parseMakeTargets(makefile) {
2494
2511
  async function detectProjectFacts(root) {
2495
2512
  const facts = { hints: [] };
2496
2513
  try {
2497
- const pkg = JSON.parse(await fsp2.readFile(path24.join(root, "package.json"), "utf8"));
2514
+ const pkg = JSON.parse(await fsp3.readFile(path25.join(root, "package.json"), "utf8"));
2498
2515
  const scripts = pkg.scripts ?? {};
2499
2516
  const pm = await detectPackageManager(root, pkg.packageManager);
2500
2517
  if (hasUsableScript(scripts, "build")) facts.build = `${pm} run build`;
@@ -2508,14 +2525,14 @@ async function detectProjectFacts(root) {
2508
2525
  } catch {
2509
2526
  }
2510
2527
  try {
2511
- if (!await pathExists(path24.join(root, "pyproject.toml"))) throw new Error("not python");
2528
+ if (!await pathExists(path25.join(root, "pyproject.toml"))) throw new Error("not python");
2512
2529
  facts.test ??= "pytest";
2513
2530
  facts.lint ??= "ruff check .";
2514
2531
  facts.hints.push("pyproject.toml");
2515
2532
  } catch {
2516
2533
  }
2517
2534
  try {
2518
- if (!await pathExists(path24.join(root, "go.mod"))) throw new Error("not go");
2535
+ if (!await pathExists(path25.join(root, "go.mod"))) throw new Error("not go");
2519
2536
  facts.build ??= "go build ./...";
2520
2537
  facts.test ??= "go test ./...";
2521
2538
  facts.run ??= "go run .";
@@ -2523,7 +2540,7 @@ async function detectProjectFacts(root) {
2523
2540
  } catch {
2524
2541
  }
2525
2542
  try {
2526
- if (!await pathExists(path24.join(root, "Cargo.toml"))) throw new Error("not rust");
2543
+ if (!await pathExists(path25.join(root, "Cargo.toml"))) throw new Error("not rust");
2527
2544
  facts.build ??= "cargo build";
2528
2545
  facts.test ??= "cargo test";
2529
2546
  facts.lint ??= "cargo clippy";
@@ -2532,7 +2549,7 @@ async function detectProjectFacts(root) {
2532
2549
  } catch {
2533
2550
  }
2534
2551
  try {
2535
- const makefile = await fsp2.readFile(path24.join(root, "Makefile"), "utf8");
2552
+ const makefile = await fsp3.readFile(path25.join(root, "Makefile"), "utf8");
2536
2553
  const targets = parseMakeTargets(makefile);
2537
2554
  facts.build ??= targets.has("build") ? "make build" : "make";
2538
2555
  if (targets.has("test")) facts.test ??= "make test";
@@ -3424,12 +3441,12 @@ function buildInitCommand(opts) {
3424
3441
  name: "init",
3425
3442
  description: "Create or update .wrongstack/AGENTS.md project context for the system prompt.",
3426
3443
  async run(_args, ctx) {
3427
- const dir = path24.join(ctx.projectRoot, ".wrongstack");
3428
- const file = path24.join(dir, "AGENTS.md");
3444
+ const dir = path25.join(ctx.projectRoot, ".wrongstack");
3445
+ const file = path25.join(dir, "AGENTS.md");
3429
3446
  const detected = await detectProjectFacts(ctx.projectRoot);
3430
3447
  const body = renderAgentsTemplate(detected);
3431
- await fsp2.mkdir(dir, { recursive: true });
3432
- await fsp2.writeFile(file, body, "utf8");
3448
+ await fsp3.mkdir(dir, { recursive: true });
3449
+ await fsp3.writeFile(file, body, "utf8");
3433
3450
  if (detected.hints.length > 0) {
3434
3451
  const msg2 = `Wrote ${file}
3435
3452
  Pre-filled: ${detected.hints.join(", ")}. Edit the file with project context and instructions the system prompt should carry.`;
@@ -3616,18 +3633,18 @@ function stateBadge(state) {
3616
3633
  return color.dim(state);
3617
3634
  }
3618
3635
  }
3619
- async function readConfig(path25) {
3636
+ async function readConfig(path26) {
3620
3637
  try {
3621
- return JSON.parse(await fsp2.readFile(path25, "utf8"));
3638
+ return JSON.parse(await fsp3.readFile(path26, "utf8"));
3622
3639
  } catch {
3623
3640
  return {};
3624
3641
  }
3625
3642
  }
3626
- async function writeConfig(path25, cfg) {
3643
+ async function writeConfig(path26, cfg) {
3627
3644
  const raw = JSON.stringify(cfg, null, 2);
3628
- const tmp = path25 + ".tmp";
3629
- await fsp2.writeFile(tmp, raw, "utf8");
3630
- await fsp2.rename(tmp, path25);
3645
+ const tmp = path26 + ".tmp";
3646
+ await fsp3.writeFile(tmp, raw, "utf8");
3647
+ await fsp3.rename(tmp, path26);
3631
3648
  }
3632
3649
 
3633
3650
  // src/slash-commands/mcp.ts
@@ -4894,12 +4911,12 @@ var DEFAULTS = {
4894
4911
  cost: true
4895
4912
  };
4896
4913
  function resolveConfigPath() {
4897
- return process.env[CONFIG_ENV] ?? path24.join(process.env.HOME ?? "", ".wrongstack", "statusline.json");
4914
+ return process.env[CONFIG_ENV] ?? path25.join(process.env.HOME ?? "", ".wrongstack", "statusline.json");
4898
4915
  }
4899
4916
  async function loadStatuslineConfig() {
4900
4917
  const p = resolveConfigPath();
4901
4918
  try {
4902
- const raw = await fsp2.readFile(p, "utf8");
4919
+ const raw = await fsp3.readFile(p, "utf8");
4903
4920
  return { ...DEFAULTS, ...JSON.parse(raw) };
4904
4921
  } catch {
4905
4922
  return { ...DEFAULTS };
@@ -4908,7 +4925,7 @@ async function loadStatuslineConfig() {
4908
4925
  async function saveStatuslineConfig(cfg) {
4909
4926
  const p = resolveConfigPath();
4910
4927
  try {
4911
- await fsp2.mkdir(path24.dirname(p), { recursive: true });
4928
+ await fsp3.mkdir(path25.dirname(p), { recursive: true });
4912
4929
  await atomicWrite(p, JSON.stringify(cfg, null, 2));
4913
4930
  } catch (err) {
4914
4931
  throw new FsError({
@@ -5807,11 +5824,11 @@ When the error confidence is low (< 0.85) or the problem spans multiple files,
5807
5824
  };
5808
5825
  }
5809
5826
  function makeInstaller(opts, projectRoot, global) {
5810
- const globalRoot = path24.join(os6.homedir(), ".wrongstack");
5827
+ const globalRoot = path25.join(os6.homedir(), ".wrongstack");
5811
5828
  return new SkillInstaller({
5812
- manifestPath: path24.join(globalRoot, "installed-skills.json"),
5813
- projectSkillsDir: path24.join(projectRoot, ".wrongstack", "skills"),
5814
- globalSkillsDir: path24.join(globalRoot, "skills"),
5829
+ manifestPath: path25.join(globalRoot, "installed-skills.json"),
5830
+ projectSkillsDir: path25.join(projectRoot, ".wrongstack", "skills"),
5831
+ globalSkillsDir: path25.join(globalRoot, "skills"),
5815
5832
  projectHash: projectHash(projectRoot),
5816
5833
  skillLoader: opts.skillLoader
5817
5834
  });
@@ -5967,6 +5984,162 @@ function buildSkillUninstallCommand(opts) {
5967
5984
  }
5968
5985
  };
5969
5986
  }
5987
+ function getStore(opts) {
5988
+ return new PhaseStore({ baseDir: opts.paths.projectAutophase });
5989
+ }
5990
+ function formatProgress(p) {
5991
+ const filled = Math.floor(p.percentComplete / 5);
5992
+ const bars = "\u2588".repeat(filled) + "\u2591".repeat(20 - filled);
5993
+ return [
5994
+ `
5995
+ \u{1F4CA} Progress: ${bars} ${p.percentComplete}%`,
5996
+ ` \u{1F4CB} Phases: ${p.completed}/${p.totalPhases} done, ${p.running} running, ${p.pending} pending`,
5997
+ ` \u2705 Tasks: ${p.completedTasks}/${p.totalTasks} completed`,
5998
+ ` \u23F1 Est: ${p.estimatedHours.toFixed(1)}h | Actual: ${p.actualHours.toFixed(1)}h`
5999
+ ].join("\n");
6000
+ }
6001
+ var STATUS_EMOJI = {
6002
+ pending: "\u23F3",
6003
+ ready: "\u{1F51C}",
6004
+ running: "\u{1F504}",
6005
+ paused: "\u23F8",
6006
+ completed: "\u2705",
6007
+ failed: "\u274C",
6008
+ skipped: "\u23ED"
6009
+ };
6010
+ function formatPhaseList(graph) {
6011
+ const phases = Array.from(graph.phases.values());
6012
+ return [
6013
+ "",
6014
+ "Phases:",
6015
+ ...phases.map((p) => {
6016
+ const total = p.taskGraph.nodes.size;
6017
+ const done = Array.from(p.taskGraph.nodes.values()).filter((t) => t.status === "completed").length;
6018
+ const tasks = total > 0 ? ` (${done}/${total} todos)` : "";
6019
+ return ` ${STATUS_EMOJI[p.status] ?? "?"} ${p.name}: ${p.status}${tasks}`;
6020
+ })
6021
+ ].join("\n");
6022
+ }
6023
+ async function gatherProjectContext2(projectRoot) {
6024
+ try {
6025
+ const raw = await fsp3.readFile(path25.join(projectRoot, "package.json"), "utf8");
6026
+ const pkg = JSON.parse(raw);
6027
+ const parts = [
6028
+ `Project: ${String(pkg.name ?? "unknown")}`,
6029
+ pkg.description ? `Description: ${String(pkg.description)}` : ""
6030
+ ].filter(Boolean);
6031
+ return parts.join("\n") || void 0;
6032
+ } catch {
6033
+ return void 0;
6034
+ }
6035
+ }
6036
+ function buildAutoPhaseCommand(opts) {
6037
+ return {
6038
+ name: "autophase",
6039
+ description: "Autonomous phase-based workflow \u2014 plans a project into phases of todos and builds it with the LLM.",
6040
+ help: [
6041
+ "Usage:",
6042
+ " /autophase Show current status",
6043
+ " /autophase start <goal> Plan + start an autonomous phase build",
6044
+ " /autophase pause Pause (in-flight tasks finish, no new ones start)",
6045
+ " /autophase resume Resume a paused run",
6046
+ " /autophase stop Stop and abort in-flight tasks",
6047
+ " /autophase save Persist current graph to disk",
6048
+ " /autophase load [title] Load a persisted graph (display only)",
6049
+ " /autophase list List saved projects",
6050
+ ""
6051
+ ].join("\n"),
6052
+ async run(args) {
6053
+ const parts = args.trim().split(/\s+/).filter(Boolean);
6054
+ const sub = parts[0] ?? "status";
6055
+ const store = getStore(opts);
6056
+ switch (sub) {
6057
+ case "start": {
6058
+ const goal = parts.slice(1).join(" ").trim();
6059
+ if (!goal) {
6060
+ return { message: "Usage: /autophase start <goal> \u2014 describe what to build." };
6061
+ }
6062
+ if (!opts.onAutoPhaseStart) {
6063
+ return { message: "\u274C AutoPhase is not available in this session (no LLM host wired)." };
6064
+ }
6065
+ const projectContext = await gatherProjectContext2(opts.projectRoot);
6066
+ const result = await opts.onAutoPhaseStart({ goal, projectContext });
6067
+ if (!result.ok) {
6068
+ return { message: `\u274C ${result.error}` };
6069
+ }
6070
+ return {
6071
+ message: [
6072
+ `\u{1F680} AutoPhase started: **${result.graph.title}**`,
6073
+ formatPhaseList(result.graph),
6074
+ "",
6075
+ "Building autonomously in the background \u2014 one subagent per todo.",
6076
+ "Use `/autophase` for status, `/autophase pause` to hold, `/autophase stop` to abort."
6077
+ ].join("\n"),
6078
+ metadata: { autoPhaseInit: { title: result.graph.title } }
6079
+ };
6080
+ }
6081
+ case "pause": {
6082
+ if (!opts.onAutoPhasePause) return { message: "\u274C AutoPhase host not available." };
6083
+ opts.onAutoPhasePause();
6084
+ return { message: "\u23F8\uFE0F AutoPhase paused \u2014 running tasks will finish; no new ones will start." };
6085
+ }
6086
+ case "resume": {
6087
+ if (!opts.onAutoPhaseResume) return { message: "\u274C AutoPhase host not available." };
6088
+ opts.onAutoPhaseResume();
6089
+ return { message: "\u25B6 AutoPhase resuming." };
6090
+ }
6091
+ case "stop": {
6092
+ if (!opts.onAutoPhaseStop) return { message: "\u274C AutoPhase host not available." };
6093
+ opts.onAutoPhaseStop();
6094
+ return { message: "\u23F9 AutoPhase stopped \u2014 in-flight tasks aborted, progress saved." };
6095
+ }
6096
+ case "save": {
6097
+ const view = opts.getAutoPhaseRunner?.();
6098
+ if (!view) return { message: "\u274C No active AutoPhase to save." };
6099
+ await store.save(view.graph);
6100
+ return { message: `\u{1F4BE} AutoPhase saved: ${view.graph.title}` };
6101
+ }
6102
+ case "load": {
6103
+ const title = parts.slice(1).join(" ").trim();
6104
+ const graphs = await store.list();
6105
+ if (graphs.length === 0) return { message: "\u274C No saved projects." };
6106
+ const entry = title ? graphs.find((g) => g.title.toLowerCase().includes(title.toLowerCase())) : graphs[0];
6107
+ if (!entry) return { message: `\u274C No saved project matching "${title}".` };
6108
+ const graph = await store.load(entry.id);
6109
+ if (!graph) return { message: `\u274C Could not load project "${entry.title}".` };
6110
+ return {
6111
+ message: [`\u{1F4C2} Loaded (display only): **${graph.title}**`, formatPhaseList(graph)].join("\n")
6112
+ };
6113
+ }
6114
+ case "list": {
6115
+ const graphs = await store.list();
6116
+ if (graphs.length === 0) return { message: "No saved projects." };
6117
+ return {
6118
+ message: [
6119
+ "Saved AutoPhase projects:",
6120
+ ...graphs.map((g) => ` \xB7 ${g.title} \u2014 ${g.status} (updated ${new Date(g.updatedAt).toLocaleString()})`)
6121
+ ].join("\n")
6122
+ };
6123
+ }
6124
+ case "default":
6125
+ case "status": {
6126
+ const view = opts.getAutoPhaseRunner?.();
6127
+ if (!view) {
6128
+ return { message: "No active AutoPhase. Run `/autophase start <goal>` to begin." };
6129
+ }
6130
+ const progress = view.getProgress();
6131
+ return {
6132
+ message: [
6133
+ `**${view.graph.title}** ${view.isRunning() ? "\u{1F504} running" : "\u23F8 idle"}`,
6134
+ formatPhaseList(view.graph),
6135
+ ...progress ? [formatProgress(progress)] : []
6136
+ ].join("\n")
6137
+ };
6138
+ }
6139
+ }
6140
+ }
6141
+ };
6142
+ }
5970
6143
 
5971
6144
  // src/slash-commands/index.ts
5972
6145
  function buildBuiltinSlashCommands(opts) {
@@ -6009,6 +6182,7 @@ function buildBuiltinSlashCommands(opts) {
6009
6182
  buildPushCommand(),
6010
6183
  buildSecurityCommand(opts),
6011
6184
  buildFixCommand(opts),
6185
+ buildAutoPhaseCommand(opts),
6012
6186
  buildStatuslineCommand({
6013
6187
  cwd: opts.cwd,
6014
6188
  hiddenItems: opts.statuslineHiddenItems ?? [],
@@ -6036,13 +6210,13 @@ var MANIFESTS = [
6036
6210
  ];
6037
6211
  async function detectProjectKind(projectRoot) {
6038
6212
  try {
6039
- await fsp2.access(path24.join(projectRoot, ".wrongstack", "AGENTS.md"));
6213
+ await fsp3.access(path25.join(projectRoot, ".wrongstack", "AGENTS.md"));
6040
6214
  return "initialized";
6041
6215
  } catch {
6042
6216
  }
6043
6217
  for (const m of MANIFESTS) {
6044
6218
  try {
6045
- await fsp2.access(path24.join(projectRoot, m));
6219
+ await fsp3.access(path25.join(projectRoot, m));
6046
6220
  return "project";
6047
6221
  } catch {
6048
6222
  }
@@ -6050,12 +6224,12 @@ async function detectProjectKind(projectRoot) {
6050
6224
  return "empty";
6051
6225
  }
6052
6226
  async function scaffoldAgentsMd(projectRoot) {
6053
- const dir = path24.join(projectRoot, ".wrongstack");
6054
- const file = path24.join(dir, "AGENTS.md");
6227
+ const dir = path25.join(projectRoot, ".wrongstack");
6228
+ const file = path25.join(dir, "AGENTS.md");
6055
6229
  const facts = await detectProjectFacts(projectRoot);
6056
6230
  const body = renderAgentsTemplate(facts);
6057
- await fsp2.mkdir(dir, { recursive: true });
6058
- await fsp2.writeFile(file, body, "utf8");
6231
+ await fsp3.mkdir(dir, { recursive: true });
6232
+ await fsp3.writeFile(file, body, "utf8");
6059
6233
  return file;
6060
6234
  }
6061
6235
  async function runProjectCheck(opts) {
@@ -6064,7 +6238,7 @@ async function runProjectCheck(opts) {
6064
6238
  if (kind === "initialized") {
6065
6239
  renderer.write(
6066
6240
  `
6067
- ${color.green("\u2713")} Project initialized ${color.dim(`(${path24.join(projectRoot, ".wrongstack", "AGENTS.md")})`)}
6241
+ ${color.green("\u2713")} Project initialized ${color.dim(`(${path25.join(projectRoot, ".wrongstack", "AGENTS.md")})`)}
6068
6242
  `
6069
6243
  );
6070
6244
  return true;
@@ -6095,10 +6269,10 @@ async function runProjectCheck(opts) {
6095
6269
  }
6096
6270
  return true;
6097
6271
  }
6098
- const gitDir = path24.join(projectRoot, ".git");
6272
+ const gitDir = path25.join(projectRoot, ".git");
6099
6273
  let hasGit = false;
6100
6274
  try {
6101
- await fsp2.access(gitDir);
6275
+ await fsp3.access(gitDir);
6102
6276
  hasGit = true;
6103
6277
  } catch {
6104
6278
  }
@@ -6220,7 +6394,7 @@ async function runLaunchPrompts(opts) {
6220
6394
  return { mode, yolo, director, autonomy };
6221
6395
  }
6222
6396
  async function bootConfig(flags) {
6223
- const cwd = typeof flags["cwd"] === "string" ? path24.resolve(flags["cwd"]) : process.cwd();
6397
+ const cwd = typeof flags["cwd"] === "string" ? path25.resolve(flags["cwd"]) : process.cwd();
6224
6398
  const pathResolver = new DefaultPathResolver(cwd);
6225
6399
  const projectRoot = pathResolver.projectRoot;
6226
6400
  const userHome = os6.homedir();
@@ -6271,13 +6445,13 @@ function flagsToConfigPatch(flags) {
6271
6445
  }
6272
6446
  async function ensureProjectMeta(paths, projectRoot) {
6273
6447
  try {
6274
- await fsp2.mkdir(paths.projectDir, { recursive: true });
6448
+ await fsp3.mkdir(paths.projectDir, { recursive: true });
6275
6449
  const meta = {
6276
6450
  hash: paths.projectHash,
6277
6451
  root: projectRoot,
6278
6452
  lastSeen: (/* @__PURE__ */ new Date()).toISOString()
6279
6453
  };
6280
- await fsp2.writeFile(paths.projectMeta, JSON.stringify(meta, null, 2));
6454
+ await fsp3.writeFile(paths.projectMeta, JSON.stringify(meta, null, 2));
6281
6455
  } catch {
6282
6456
  }
6283
6457
  }
@@ -6287,11 +6461,11 @@ var ReadlineInputReader = class {
6287
6461
  history = [];
6288
6462
  pending = false;
6289
6463
  constructor(opts = {}) {
6290
- this.historyFile = opts.historyFile ?? path24.join(os6.homedir(), ".wrongstack", "history");
6464
+ this.historyFile = opts.historyFile ?? path25.join(os6.homedir(), ".wrongstack", "history");
6291
6465
  }
6292
6466
  async loadHistory() {
6293
6467
  try {
6294
- const raw = await fsp2.readFile(this.historyFile, "utf8");
6468
+ const raw = await fsp3.readFile(this.historyFile, "utf8");
6295
6469
  this.history = raw.split("\n").filter(Boolean).slice(-1e3);
6296
6470
  } catch {
6297
6471
  this.history = [];
@@ -6299,8 +6473,8 @@ var ReadlineInputReader = class {
6299
6473
  }
6300
6474
  async saveHistory() {
6301
6475
  try {
6302
- await fsp2.mkdir(path24.dirname(this.historyFile), { recursive: true });
6303
- await fsp2.writeFile(this.historyFile, this.history.slice(-1e3).join("\n"));
6476
+ await fsp3.mkdir(path25.dirname(this.historyFile), { recursive: true });
6477
+ await fsp3.writeFile(this.historyFile, this.history.slice(-1e3).join("\n"));
6304
6478
  } catch {
6305
6479
  }
6306
6480
  }
@@ -6526,23 +6700,23 @@ function assertSafeToDelete(filename, parentDir) {
6526
6700
  if (PROTECTED_BASENAMES.has(filename)) {
6527
6701
  throw new Error(`Refusing to delete protected file: ${filename}`);
6528
6702
  }
6529
- if (filename !== path24.basename(filename)) {
6703
+ if (filename !== path25.basename(filename)) {
6530
6704
  throw new Error(`Refusing to delete path with traversal: ${filename}`);
6531
6705
  }
6532
6706
  if (!filename.startsWith("config.json.") || !filename.endsWith(".bak")) {
6533
6707
  throw new Error(`Refusing to delete unknown file: ${filename}`);
6534
6708
  }
6535
- const resolvedParent = path24.resolve(parentDir);
6709
+ const resolvedParent = path25.resolve(parentDir);
6536
6710
  if (!resolvedParent.endsWith(".wrongstack")) {
6537
6711
  throw new Error(`Unexpected parent directory for bak prune: ${resolvedParent}`);
6538
6712
  }
6539
6713
  }
6540
6714
  async function safeDelete(filePath) {
6541
- const dir = path24.dirname(filePath);
6542
- const filename = path24.basename(filePath);
6715
+ const dir = path25.dirname(filePath);
6716
+ const filename = path25.basename(filePath);
6543
6717
  try {
6544
6718
  assertSafeToDelete(filename, dir);
6545
- await fsp2.unlink(filePath);
6719
+ await fsp3.unlink(filePath);
6546
6720
  } catch (err) {
6547
6721
  if (err instanceof Error && err.message.startsWith("Refusing")) {
6548
6722
  process.stderr.write(`[config-history] SAFETY: ${err.message}
@@ -6584,23 +6758,23 @@ function diffSummary(oldCfg, newCfg) {
6584
6758
  }
6585
6759
  var defaultHomeDir = () => os6__default.homedir();
6586
6760
  function historyDir(homeFn = defaultHomeDir) {
6587
- return path24.join(homeFn(), ".wrongstack", "config.history", "entries");
6761
+ return path25.join(homeFn(), ".wrongstack", "config.history", "entries");
6588
6762
  }
6589
6763
  function historyIndexPath(homeFn = defaultHomeDir) {
6590
- return path24.join(homeFn(), ".wrongstack", "config.history", "index.json");
6764
+ return path25.join(homeFn(), ".wrongstack", "config.history", "index.json");
6591
6765
  }
6592
6766
  function configPath(homeFn = defaultHomeDir) {
6593
- return path24.join(homeFn(), ".wrongstack", "config.json");
6767
+ return path25.join(homeFn(), ".wrongstack", "config.json");
6594
6768
  }
6595
6769
  function backupLastPath(homeFn = defaultHomeDir) {
6596
- return path24.join(homeFn(), ".wrongstack", "config.json.last");
6770
+ return path25.join(homeFn(), ".wrongstack", "config.json.last");
6597
6771
  }
6598
6772
  function entryId(ts) {
6599
6773
  return ts.replace(/[:.]/g, "-").slice(0, 19);
6600
6774
  }
6601
6775
  async function ensureHistoryDir(homeFn = defaultHomeDir) {
6602
6776
  try {
6603
- await fsp2.mkdir(historyDir(homeFn), { recursive: true });
6777
+ await fsp3.mkdir(historyDir(homeFn), { recursive: true });
6604
6778
  } catch (err) {
6605
6779
  throw new FsError({
6606
6780
  message: err instanceof Error ? err.message : String(err),
@@ -6612,7 +6786,7 @@ async function ensureHistoryDir(homeFn = defaultHomeDir) {
6612
6786
  }
6613
6787
  async function readIndex(homeFn = defaultHomeDir) {
6614
6788
  try {
6615
- const raw = await fsp2.readFile(historyIndexPath(homeFn), "utf8");
6789
+ const raw = await fsp3.readFile(historyIndexPath(homeFn), "utf8");
6616
6790
  return JSON.parse(raw);
6617
6791
  } catch {
6618
6792
  return { version: 1, entries: [] };
@@ -6637,7 +6811,7 @@ async function backupCurrent(homeFn = defaultHomeDir) {
6637
6811
  const ts = Date.now();
6638
6812
  let content;
6639
6813
  try {
6640
- content = await fsp2.readFile(cfg, "utf8");
6814
+ content = await fsp3.readFile(cfg, "utf8");
6641
6815
  } catch {
6642
6816
  }
6643
6817
  if (content !== void 0) {
@@ -6648,17 +6822,17 @@ async function backupCurrent(homeFn = defaultHomeDir) {
6648
6822
  }
6649
6823
  if (content !== void 0) {
6650
6824
  try {
6651
- const bakPath = path24.join(homeFn(), ".wrongstack", `config.json.${ts}.bak`);
6825
+ const bakPath = path25.join(homeFn(), ".wrongstack", `config.json.${ts}.bak`);
6652
6826
  await atomicWrite(bakPath, content);
6653
6827
  } catch {
6654
6828
  }
6655
6829
  }
6656
6830
  try {
6657
- const dir = path24.join(homeFn(), ".wrongstack");
6658
- const files = await fsp2.readdir(dir);
6831
+ const dir = path25.join(homeFn(), ".wrongstack");
6832
+ const files = await fsp3.readdir(dir);
6659
6833
  const baks = files.filter((f) => f.startsWith("config.json.") && f.endsWith(".bak")).sort().reverse();
6660
6834
  for (const f of baks.slice(10)) {
6661
- await safeDelete(path24.join(dir, f));
6835
+ await safeDelete(path25.join(dir, f));
6662
6836
  }
6663
6837
  } catch {
6664
6838
  }
@@ -6675,8 +6849,8 @@ async function appendHistory(oldCfg, newCfg, description, homeFn = defaultHomeDi
6675
6849
  diffSummary: diffSummary(oldCfg, newCfg)
6676
6850
  };
6677
6851
  try {
6678
- await fsp2.writeFile(
6679
- path24.join(historyDir(homeFn), `${id}.json`),
6852
+ await fsp3.writeFile(
6853
+ path25.join(historyDir(homeFn), `${id}.json`),
6680
6854
  JSON.stringify(entry, null, 2),
6681
6855
  "utf8"
6682
6856
  );
@@ -6684,7 +6858,7 @@ async function appendHistory(oldCfg, newCfg, description, homeFn = defaultHomeDi
6684
6858
  throw new FsError({
6685
6859
  message: err instanceof Error ? err.message : String(err),
6686
6860
  code: ERROR_CODES.FS_WRITE_FAILED,
6687
- path: path24.join(historyDir(homeFn), `${id}.json`),
6861
+ path: path25.join(historyDir(homeFn), `${id}.json`),
6688
6862
  cause: err
6689
6863
  });
6690
6864
  }
@@ -6699,7 +6873,7 @@ async function listHistory(homeFn = defaultHomeDir) {
6699
6873
  }
6700
6874
  async function getHistoryEntry(id, homeFn = defaultHomeDir) {
6701
6875
  try {
6702
- const raw = await fsp2.readFile(path24.join(historyDir(homeFn), `${id}.json`), "utf8");
6876
+ const raw = await fsp3.readFile(path25.join(historyDir(homeFn), `${id}.json`), "utf8");
6703
6877
  return JSON.parse(raw);
6704
6878
  } catch {
6705
6879
  return null;
@@ -6711,7 +6885,7 @@ async function restoreFromHistory(id, homeFn = defaultHomeDir) {
6711
6885
  await backupCurrent(homeFn);
6712
6886
  let oldCfg = {};
6713
6887
  try {
6714
- const raw = await fsp2.readFile(configPath(homeFn), "utf8");
6888
+ const raw = await fsp3.readFile(configPath(homeFn), "utf8");
6715
6889
  oldCfg = JSON.parse(raw);
6716
6890
  } catch {
6717
6891
  }
@@ -6733,13 +6907,13 @@ async function restoreLast(homeFn = defaultHomeDir) {
6733
6907
  const cfg = configPath(homeFn);
6734
6908
  let oldCfg = {};
6735
6909
  try {
6736
- const raw = await fsp2.readFile(cfg, "utf8");
6910
+ const raw = await fsp3.readFile(cfg, "utf8");
6737
6911
  oldCfg = JSON.parse(raw);
6738
6912
  } catch {
6739
6913
  }
6740
6914
  let lastCfg = {};
6741
6915
  try {
6742
- const raw = await fsp2.readFile(last, "utf8");
6916
+ const raw = await fsp3.readFile(last, "utf8");
6743
6917
  lastCfg = JSON.parse(raw);
6744
6918
  } catch {
6745
6919
  return { ok: false, error: "No prior backup found" };
@@ -6770,7 +6944,7 @@ async function saveToGlobalConfig(configPath2, provider, model, homeFn = () => p
6770
6944
  existing.provider = provider;
6771
6945
  existing.model = model;
6772
6946
  await backupCurrent(homeFn);
6773
- await atomicWrite8(configPath2, JSON.stringify(existing, null, 2));
6947
+ await atomicWrite8(configPath2, JSON.stringify(existing, null, 2), { mode: 384 });
6774
6948
  try {
6775
6949
  await appendHistory(
6776
6950
  oldCfg,
@@ -7097,7 +7271,7 @@ function pickGroupIndex(opts) {
7097
7271
  if (Number.isFinite(parsed)) current = wrap(parsed);
7098
7272
  } catch {
7099
7273
  }
7100
- fs9.mkdirSync(path24.dirname(opts.cursorFile), { recursive: true });
7274
+ fs9.mkdirSync(path25.dirname(opts.cursorFile), { recursive: true });
7101
7275
  fs9.writeFileSync(opts.cursorFile, String(wrap(current + 1)));
7102
7276
  return current;
7103
7277
  } catch {
@@ -7433,14 +7607,14 @@ function summarize(value, name) {
7433
7607
  if (typeof v === "object" && v !== null) {
7434
7608
  const o = v;
7435
7609
  if (name === "edit") {
7436
- const path25 = typeof o["path"] === "string" ? o["path"] : "";
7610
+ const path26 = typeof o["path"] === "string" ? o["path"] : "";
7437
7611
  const reps = typeof o["replacements"] === "number" ? o["replacements"] : 0;
7438
- return `${path25} ${reps} replacement${reps === 1 ? "" : "s"}`.trim();
7612
+ return `${path26} ${reps} replacement${reps === 1 ? "" : "s"}`.trim();
7439
7613
  }
7440
7614
  if (name === "write") {
7441
- const path25 = typeof o["path"] === "string" ? o["path"] : "";
7615
+ const path26 = typeof o["path"] === "string" ? o["path"] : "";
7442
7616
  const bytes = typeof o["bytes"] === "number" ? o["bytes"] : void 0;
7443
- return bytes !== void 0 ? `${path25} ${bytes}B` : path25;
7617
+ return bytes !== void 0 ? `${path26} ${bytes}B` : path26;
7444
7618
  }
7445
7619
  if (typeof o["count"] === "number") {
7446
7620
  return `${o["count"]} match${o["count"] === 1 ? "" : "es"}`;
@@ -8200,7 +8374,7 @@ async function readKeyInput(deps, intent) {
8200
8374
  async function loadProviders(deps) {
8201
8375
  let raw;
8202
8376
  try {
8203
- raw = await fsp2.readFile(deps.globalConfigPath, "utf8");
8377
+ raw = await fsp3.readFile(deps.globalConfigPath, "utf8");
8204
8378
  } catch (err) {
8205
8379
  if (err.code !== "ENOENT") {
8206
8380
  deps.renderer.writeWarning(
@@ -8225,7 +8399,7 @@ async function mutateProviders(deps, mutator) {
8225
8399
  let raw;
8226
8400
  let fileExists = true;
8227
8401
  try {
8228
- raw = await fsp2.readFile(deps.globalConfigPath, "utf8");
8402
+ raw = await fsp3.readFile(deps.globalConfigPath, "utf8");
8229
8403
  } catch (err) {
8230
8404
  if (err.code !== "ENOENT") {
8231
8405
  throw new Error(
@@ -8439,7 +8613,7 @@ var doctorCmd = async (_args, deps) => {
8439
8613
  });
8440
8614
  }
8441
8615
  try {
8442
- await fsp2.access(deps.paths.secretsKey);
8616
+ await fsp3.access(deps.paths.secretsKey);
8443
8617
  checks.push({ name: "secret vault", status: "ok", detail: deps.paths.secretsKey });
8444
8618
  } catch {
8445
8619
  checks.push({
@@ -8449,10 +8623,10 @@ var doctorCmd = async (_args, deps) => {
8449
8623
  });
8450
8624
  }
8451
8625
  try {
8452
- await fsp2.mkdir(deps.paths.projectSessions, { recursive: true });
8453
- const probe = path24.join(deps.paths.projectSessions, `.probe-${Date.now()}`);
8454
- await fsp2.writeFile(probe, "");
8455
- await fsp2.unlink(probe);
8626
+ await fsp3.mkdir(deps.paths.projectSessions, { recursive: true });
8627
+ const probe = path25.join(deps.paths.projectSessions, `.probe-${Date.now()}`);
8628
+ await fsp3.writeFile(probe, "");
8629
+ await fsp3.unlink(probe);
8456
8630
  checks.push({ name: "sessions writable", status: "ok", detail: deps.paths.projectSessions });
8457
8631
  } catch (err) {
8458
8632
  checks.push({
@@ -8553,8 +8727,8 @@ var exportCmd = async (args, deps) => {
8553
8727
  return 1;
8554
8728
  }
8555
8729
  if (output) {
8556
- await fsp2.mkdir(path24.dirname(path24.resolve(deps.cwd, output)), { recursive: true });
8557
- await fsp2.writeFile(path24.resolve(deps.cwd, output), rendered, "utf8");
8730
+ await fsp3.mkdir(path25.dirname(path25.resolve(deps.cwd, output)), { recursive: true });
8731
+ await fsp3.writeFile(path25.resolve(deps.cwd, output), rendered, "utf8");
8558
8732
  deps.renderer.write(`Wrote ${rendered.length} bytes to ${output}
8559
8733
  `);
8560
8734
  } else {
@@ -8621,14 +8795,14 @@ var initCmd = async (_args, deps) => {
8621
8795
  } else {
8622
8796
  deps.renderer.writeInfo(`Found API key in env (${provider.envVars.join(" / ")}).`);
8623
8797
  }
8624
- await fsp2.mkdir(deps.paths.globalRoot, { recursive: true });
8798
+ await fsp3.mkdir(deps.paths.globalRoot, { recursive: true });
8625
8799
  const config = { version: 1, provider: providerId, model: modelId };
8626
8800
  if (apiKey) config.apiKey = apiKey;
8627
8801
  const vault = new DefaultSecretVault$1({ keyFile: deps.paths.secretsKey });
8628
8802
  const encrypted = encryptConfigSecrets(config, vault);
8629
- await atomicWrite(deps.paths.globalConfig, JSON.stringify(encrypted, null, 2));
8630
- await fsp2.mkdir(path24.join(deps.projectRoot, ".wrongstack"), { recursive: true });
8631
- const agentsFile = path24.join(deps.projectRoot, ".wrongstack", "AGENTS.md");
8803
+ await atomicWrite(deps.paths.globalConfig, JSON.stringify(encrypted, null, 2), { mode: 384 });
8804
+ await fsp3.mkdir(path25.join(deps.projectRoot, ".wrongstack"), { recursive: true });
8805
+ const agentsFile = path25.join(deps.projectRoot, ".wrongstack", "AGENTS.md");
8632
8806
  const projectFacts = await detectProjectFacts(deps.projectRoot);
8633
8807
  await atomicWrite(agentsFile, renderAgentsTemplate(projectFacts));
8634
8808
  deps.renderer.writeInfo(`Wrote ${deps.paths.globalConfig}`);
@@ -8702,7 +8876,7 @@ async function addMcpServer(args, deps) {
8702
8876
  serverCfg.enabled = enable;
8703
8877
  let existing = {};
8704
8878
  try {
8705
- existing = JSON.parse(await fsp2.readFile(deps.paths.globalConfig, "utf8"));
8879
+ existing = JSON.parse(await fsp3.readFile(deps.paths.globalConfig, "utf8"));
8706
8880
  } catch {
8707
8881
  }
8708
8882
  const mcpServers = existing.mcpServers ?? {};
@@ -8711,7 +8885,7 @@ async function addMcpServer(args, deps) {
8711
8885
  `);
8712
8886
  mcpServers[name] = serverCfg;
8713
8887
  existing.mcpServers = mcpServers;
8714
- await atomicWrite(deps.paths.globalConfig, JSON.stringify(existing, null, 2));
8888
+ await atomicWrite(deps.paths.globalConfig, JSON.stringify(existing, null, 2), { mode: 384 });
8715
8889
  const verb = enable ? "Enabled" : "Added (disabled \u2014 set enabled:true to activate)";
8716
8890
  deps.renderer.writeInfo(
8717
8891
  `${verb} "${name}" (${serverCfg.transport}). Config written to ${deps.paths.globalConfig}.
@@ -8722,7 +8896,7 @@ async function addMcpServer(args, deps) {
8722
8896
  async function removeMcpServer(name, deps) {
8723
8897
  let existing = {};
8724
8898
  try {
8725
- existing = JSON.parse(await fsp2.readFile(deps.paths.globalConfig, "utf8"));
8899
+ existing = JSON.parse(await fsp3.readFile(deps.paths.globalConfig, "utf8"));
8726
8900
  } catch {
8727
8901
  deps.renderer.writeError("No config file found.\n");
8728
8902
  return 1;
@@ -8735,7 +8909,7 @@ async function removeMcpServer(name, deps) {
8735
8909
  }
8736
8910
  delete mcpServers[name];
8737
8911
  existing.mcpServers = mcpServers;
8738
- await atomicWrite(deps.paths.globalConfig, JSON.stringify(existing, null, 2));
8912
+ await atomicWrite(deps.paths.globalConfig, JSON.stringify(existing, null, 2), { mode: 384 });
8739
8913
  deps.renderer.writeInfo(`Removed "${name}" from config.
8740
8914
  `);
8741
8915
  return 0;
@@ -8843,7 +9017,7 @@ function renderConfiguredPlugins(config) {
8843
9017
  }
8844
9018
  async function readConfig2(file) {
8845
9019
  try {
8846
- return JSON.parse(await fsp2.readFile(file, "utf8"));
9020
+ return JSON.parse(await fsp3.readFile(file, "utf8"));
8847
9021
  } catch {
8848
9022
  return {};
8849
9023
  }
@@ -8873,7 +9047,7 @@ async function upsertPlugin(spec, opts, deps, verb) {
8873
9047
  };
8874
9048
  existing.plugins = plugins;
8875
9049
  existing.features = features;
8876
- await atomicWrite(deps.configPath, JSON.stringify(existing, null, 2));
9050
+ await atomicWrite(deps.configPath, JSON.stringify(existing, null, 2), { mode: 384 });
8877
9051
  return {
8878
9052
  code: 0,
8879
9053
  level: "info",
@@ -8890,7 +9064,7 @@ async function removePlugin(spec, deps) {
8890
9064
  return errorResult(`Plugin "${spec}" not in config.`);
8891
9065
  }
8892
9066
  existing.plugins = next;
8893
- await atomicWrite(deps.configPath, JSON.stringify(existing, null, 2));
9067
+ await atomicWrite(deps.configPath, JSON.stringify(existing, null, 2), { mode: 384 });
8894
9068
  return {
8895
9069
  code: 0,
8896
9070
  level: "info",
@@ -8934,9 +9108,9 @@ var usageCmd = async (_args, deps) => {
8934
9108
  return 0;
8935
9109
  };
8936
9110
  var projectsCmd = async (_args, deps) => {
8937
- const projectsRoot = path24.join(deps.paths.globalRoot, "projects");
9111
+ const projectsRoot = path25.join(deps.paths.globalRoot, "projects");
8938
9112
  try {
8939
- const entries = await fsp2.readdir(projectsRoot);
9113
+ const entries = await fsp3.readdir(projectsRoot);
8940
9114
  if (entries.length === 0) {
8941
9115
  deps.renderer.write("No projects tracked.\n");
8942
9116
  return 0;
@@ -8944,7 +9118,7 @@ var projectsCmd = async (_args, deps) => {
8944
9118
  for (const hash of entries) {
8945
9119
  try {
8946
9120
  const meta = JSON.parse(
8947
- await fsp2.readFile(path24.join(projectsRoot, hash, "meta.json"), "utf8")
9121
+ await fsp3.readFile(path25.join(projectsRoot, hash, "meta.json"), "utf8")
8948
9122
  );
8949
9123
  deps.renderer.write(
8950
9124
  ` ${color.dim(hash)} ${color.dim(meta.lastSeen ?? "")} ${meta.root ?? "?"}
@@ -9095,7 +9269,7 @@ var sessionsFleetCmd = async (args, deps) => {
9095
9269
  async function listFleetRuns(deps) {
9096
9270
  let entries = [];
9097
9271
  try {
9098
- entries = await fsp2.readdir(deps.paths.projectSessions);
9272
+ entries = await fsp3.readdir(deps.paths.projectSessions);
9099
9273
  } catch {
9100
9274
  deps.renderer.writeError(`Cannot read projectSessions: ${deps.paths.projectSessions}
9101
9275
  `);
@@ -9103,10 +9277,10 @@ async function listFleetRuns(deps) {
9103
9277
  }
9104
9278
  const runs = [];
9105
9279
  for (const id of entries) {
9106
- const runDir = path24.join(deps.paths.projectSessions, id);
9280
+ const runDir = path25.join(deps.paths.projectSessions, id);
9107
9281
  let stat3;
9108
9282
  try {
9109
- stat3 = await fsp2.stat(runDir);
9283
+ stat3 = await fsp3.stat(runDir);
9110
9284
  } catch {
9111
9285
  continue;
9112
9286
  }
@@ -9116,18 +9290,18 @@ async function listFleetRuns(deps) {
9116
9290
  let subagentCount = 0;
9117
9291
  let subagentsDir;
9118
9292
  try {
9119
- await fsp2.access(path24.join(runDir, "fleet.json"));
9293
+ await fsp3.access(path25.join(runDir, "fleet.json"));
9120
9294
  manifest = true;
9121
9295
  } catch {
9122
9296
  }
9123
9297
  try {
9124
- await fsp2.access(path24.join(runDir, "checkpoint.json"));
9298
+ await fsp3.access(path25.join(runDir, "checkpoint.json"));
9125
9299
  checkpoint = true;
9126
9300
  } catch {
9127
9301
  }
9128
9302
  try {
9129
- subagentsDir = path24.join(runDir, "subagents");
9130
- const files = await fsp2.readdir(subagentsDir);
9303
+ subagentsDir = path25.join(runDir, "subagents");
9304
+ const files = await fsp3.readdir(subagentsDir);
9131
9305
  subagentCount = files.filter((f) => f.endsWith(".jsonl")).length;
9132
9306
  } catch {
9133
9307
  }
@@ -9155,10 +9329,10 @@ async function listFleetRuns(deps) {
9155
9329
  return 0;
9156
9330
  }
9157
9331
  async function showFleetRun(runId, deps) {
9158
- const runDir = path24.join(deps.paths.projectSessions, runId);
9332
+ const runDir = path25.join(deps.paths.projectSessions, runId);
9159
9333
  let stat3;
9160
9334
  try {
9161
- stat3 = await fsp2.stat(runDir);
9335
+ stat3 = await fsp3.stat(runDir);
9162
9336
  } catch {
9163
9337
  deps.renderer.writeError(`Fleet run not found: ${runId}
9164
9338
  `);
@@ -9172,10 +9346,10 @@ async function showFleetRun(runId, deps) {
9172
9346
  deps.renderer.write(color.bold(`
9173
9347
  Fleet Run: ${runId}
9174
9348
  `) + "\n");
9175
- const manifestPath = path24.join(runDir, "fleet.json");
9349
+ const manifestPath = path25.join(runDir, "fleet.json");
9176
9350
  let manifestData = null;
9177
9351
  try {
9178
- manifestData = await fsp2.readFile(manifestPath, "utf8");
9352
+ manifestData = await fsp3.readFile(manifestPath, "utf8");
9179
9353
  const manifest = JSON.parse(manifestData);
9180
9354
  const subagents = manifest.subagents ?? [];
9181
9355
  const tasks = manifest.tasks ?? [];
@@ -9188,15 +9362,15 @@ Fleet Run: ${runId}
9188
9362
  deps.renderer.write(` ${color.dim("\u25CB")} fleet.json \u2014 not found
9189
9363
  `);
9190
9364
  }
9191
- const checkpointPath = path24.join(runDir, "checkpoint.json");
9365
+ const checkpointPath = path25.join(runDir, "checkpoint.json");
9192
9366
  let checkpointData = null;
9193
9367
  try {
9194
- checkpointData = await fsp2.readFile(checkpointPath, "utf8");
9368
+ checkpointData = await fsp3.readFile(checkpointPath, "utf8");
9195
9369
  const snap = JSON.parse(checkpointData);
9196
9370
  const lockPath = `${checkpointPath}.lock`;
9197
9371
  let lockStatus = color.dim("\u25CB no lock");
9198
9372
  try {
9199
- const lockRaw = await fsp2.readFile(lockPath, "utf8");
9373
+ const lockRaw = await fsp3.readFile(lockPath, "utf8");
9200
9374
  const lock = JSON.parse(lockRaw);
9201
9375
  lockStatus = `${color.yellow("\u25B8")} lock held by pid ${lock.pid} on ${lock.hostname} (started ${lock.startedAt})`;
9202
9376
  } catch {
@@ -9235,10 +9409,10 @@ Fleet Run: ${runId}
9235
9409
  } catch {
9236
9410
  }
9237
9411
  }
9238
- const subagentsDir = path24.join(runDir, "subagents");
9412
+ const subagentsDir = path25.join(runDir, "subagents");
9239
9413
  let subagentFiles = [];
9240
9414
  try {
9241
- subagentFiles = await fsp2.readdir(subagentsDir);
9415
+ subagentFiles = await fsp3.readdir(subagentsDir);
9242
9416
  subagentFiles = subagentFiles.filter((f) => f.endsWith(".jsonl"));
9243
9417
  } catch {
9244
9418
  }
@@ -9247,10 +9421,10 @@ Fleet Run: ${runId}
9247
9421
  Subagent transcripts (${subagentFiles.length}):
9248
9422
  `);
9249
9423
  for (const f of subagentFiles.sort()) {
9250
- const filePath = path24.join(subagentsDir, f);
9424
+ const filePath = path25.join(subagentsDir, f);
9251
9425
  let size;
9252
9426
  try {
9253
- const s = await fsp2.stat(filePath);
9427
+ const s = await fsp3.stat(filePath);
9254
9428
  size = s.size;
9255
9429
  } catch {
9256
9430
  size = 0;
@@ -9264,9 +9438,9 @@ Fleet Run: ${runId}
9264
9438
  ${color.dim("\u25CB")} No subagent transcripts
9265
9439
  `);
9266
9440
  }
9267
- const sharedDir = path24.join(runDir, "shared");
9441
+ const sharedDir = path25.join(runDir, "shared");
9268
9442
  try {
9269
- const files = await fsp2.readdir(sharedDir);
9443
+ const files = await fsp3.readdir(sharedDir);
9270
9444
  deps.renderer.write(`
9271
9445
  Shared scratchpad: ${files.length} file(s)
9272
9446
  `);
@@ -9431,7 +9605,7 @@ function findSessionId(args) {
9431
9605
  var rewindCmd = async (args, deps) => {
9432
9606
  const flags = parseRewindFlags(args);
9433
9607
  const wpaths = resolveWstackPaths({ projectRoot: deps.projectRoot });
9434
- const sessionsDir = path24.join(wpaths.globalRoot, "sessions");
9608
+ const sessionsDir = path25.join(wpaths.globalRoot, "sessions");
9435
9609
  const rewind = new DefaultSessionRewinder(sessionsDir);
9436
9610
  let sessionId = findSessionId(args);
9437
9611
  if (!sessionId) {
@@ -9668,7 +9842,7 @@ function resolveBundledSkillsDir() {
9668
9842
  try {
9669
9843
  const req2 = createRequire(import.meta.url);
9670
9844
  const corePkg = req2.resolve("@wrongstack/core/package.json");
9671
- return path24.join(path24.dirname(corePkg), "skills");
9845
+ return path25.join(path25.dirname(corePkg), "skills");
9672
9846
  } catch {
9673
9847
  return void 0;
9674
9848
  }
@@ -9830,7 +10004,7 @@ async function boot(argv) {
9830
10004
  if (choices.director) flags["director"] = true;
9831
10005
  flags["autonomy"] = choices.autonomy;
9832
10006
  printLaunchHints(renderer, flags, {
9833
- cursorFile: path24.join(wpaths.cacheDir, "hint-cursor")
10007
+ cursorFile: path25.join(wpaths.cacheDir, "hint-cursor")
9834
10008
  });
9835
10009
  }
9836
10010
  return {
@@ -10753,6 +10927,36 @@ async function execute(deps) {
10753
10927
  const banneredFamily = savedProviderCfg?.family ?? resolvedProvider?.family;
10754
10928
  const banneredKey = savedProviderCfg?.apiKey ?? config.apiKey ?? (resolvedProvider?.envVars ?? savedProviderCfg?.envVars ?? []).map((v) => process.env[v]).find((v) => !!v);
10755
10929
  const banneredKeyTail = banneredKey && banneredKey.length >= 3 ? banneredKey.slice(-3) : void 0;
10930
+ const autoPhaseHandlers = /* @__PURE__ */ new Map();
10931
+ const subscribeAutoPhase = (handler) => {
10932
+ const registrations = [];
10933
+ const autoPhaseEvents = [
10934
+ "phase.started",
10935
+ "phase.completed",
10936
+ "phase.failed",
10937
+ "phase.statusChange",
10938
+ "phase.taskCompleted",
10939
+ "phase.taskFailed",
10940
+ "phase.taskRetrying",
10941
+ "autonomous.tick",
10942
+ "graph.completed",
10943
+ "graph.failed",
10944
+ "agent.assigned",
10945
+ "agent.released"
10946
+ ];
10947
+ const onUntyped = events.on.bind(events);
10948
+ const offUntyped = events.off.bind(events);
10949
+ for (const ev of autoPhaseEvents) {
10950
+ const h = (p) => handler(ev, p);
10951
+ autoPhaseHandlers.set(ev, h);
10952
+ onUntyped(ev, h);
10953
+ registrations.push(() => offUntyped(ev, h));
10954
+ }
10955
+ return () => {
10956
+ for (const unregister of registrations) unregister();
10957
+ autoPhaseHandlers.clear();
10958
+ };
10959
+ };
10756
10960
  try {
10757
10961
  code = await runTui({
10758
10962
  agent,
@@ -10771,6 +10975,7 @@ async function execute(deps) {
10771
10975
  getEternalEngine,
10772
10976
  subscribeEternalIteration,
10773
10977
  subscribeEternalStage,
10978
+ subscribeAutoPhase,
10774
10979
  appVersion: CLI_VERSION,
10775
10980
  provider: config.provider,
10776
10981
  family: banneredFamily,
@@ -10861,7 +11066,7 @@ async function execute(deps) {
10861
11066
  supportsVision,
10862
11067
  attachments,
10863
11068
  effectiveMaxContext,
10864
- projectName: path24.basename(projectRoot) || void 0,
11069
+ projectName: path25.basename(projectRoot) || void 0,
10865
11070
  projectRoot,
10866
11071
  getAutonomy,
10867
11072
  onAutonomy,
@@ -10885,7 +11090,7 @@ async function execute(deps) {
10885
11090
  supportsVision,
10886
11091
  attachments,
10887
11092
  effectiveMaxContext,
10888
- projectName: path24.basename(projectRoot) || void 0,
11093
+ projectName: path25.basename(projectRoot) || void 0,
10889
11094
  getAutonomy,
10890
11095
  onAutonomy,
10891
11096
  getEternalEngine,
@@ -10989,7 +11194,7 @@ var MultiAgentHost = class {
10989
11194
  doneCondition: { type: "all_tasks_done" },
10990
11195
  maxConcurrent: this.opts.maxConcurrent ?? 4
10991
11196
  };
10992
- const defaultScratchpad = this.opts.sharedScratchpadPath || (this.opts.sessionsRoot && this.opts.directorRunId ? path24.join(this.opts.sessionsRoot, this.opts.directorRunId, "shared") : void 0);
11197
+ const defaultScratchpad = this.opts.sharedScratchpadPath || (this.opts.sessionsRoot && this.opts.directorRunId ? path25.join(this.opts.sessionsRoot, this.opts.directorRunId, "shared") : void 0);
10993
11198
  this.director = new Director({
10994
11199
  config: coordinatorConfig,
10995
11200
  manifestPath: this.opts.manifestPath,
@@ -11234,7 +11439,7 @@ var MultiAgentHost = class {
11234
11439
  model: opts?.model,
11235
11440
  tools: opts?.tools
11236
11441
  };
11237
- const transcriptPath = this.sessionFactory ? path24.join(this.sessionFactory.dir, `${subagentConfig.name}.jsonl`) : void 0;
11442
+ const transcriptPath = this.sessionFactory ? path25.join(this.sessionFactory.dir, `${subagentConfig.name}.jsonl`) : void 0;
11238
11443
  const { subagentId, taskId } = await this._spawnAndAssign(subagentConfig);
11239
11444
  this.fleetManager?.addPendingTask(taskId, subagentId, description);
11240
11445
  this.deps.events.emit("subagent.spawned", {
@@ -11377,16 +11582,16 @@ var MultiAgentHost = class {
11377
11582
  if (this.director) return this.director;
11378
11583
  this.opts.directorMode = true;
11379
11584
  if (this.opts.fleetRoot && !this.opts.manifestPath) {
11380
- this.opts.manifestPath = path24.join(this.opts.fleetRoot, "fleet.json");
11585
+ this.opts.manifestPath = path25.join(this.opts.fleetRoot, "fleet.json");
11381
11586
  }
11382
11587
  if (this.opts.fleetRoot && !this.opts.sharedScratchpadPath) {
11383
- this.opts.sharedScratchpadPath = path24.join(this.opts.fleetRoot, "shared");
11588
+ this.opts.sharedScratchpadPath = path25.join(this.opts.fleetRoot, "shared");
11384
11589
  }
11385
11590
  if (this.opts.fleetRoot && !this.opts.sessionsRoot) {
11386
- this.opts.sessionsRoot = path24.join(this.opts.fleetRoot, "subagents");
11591
+ this.opts.sessionsRoot = path25.join(this.opts.fleetRoot, "subagents");
11387
11592
  }
11388
11593
  if (this.opts.fleetRoot && !this.opts.stateCheckpointPath) {
11389
- this.opts.stateCheckpointPath = path24.join(this.opts.fleetRoot, "director-state.json");
11594
+ this.opts.stateCheckpointPath = path25.join(this.opts.fleetRoot, "director-state.json");
11390
11595
  }
11391
11596
  await this.ensureDirector();
11392
11597
  return this.director ?? null;
@@ -11552,11 +11757,11 @@ var SessionStats = class {
11552
11757
  if (e.name === "bash") this.bashCommands++;
11553
11758
  else if (e.name === "fetch") this.fetches++;
11554
11759
  if (!e.ok) return;
11555
- const path25 = typeof input?.path === "string" ? input.path : void 0;
11556
- if (e.name === "read" && path25) this.readPaths.add(path25);
11557
- else if (e.name === "edit" && path25) this.editedPaths.add(path25);
11558
- else if (e.name === "write" && path25) {
11559
- this.writtenPaths.add(path25);
11760
+ const path26 = typeof input?.path === "string" ? input.path : void 0;
11761
+ if (e.name === "read" && path26) this.readPaths.add(path26);
11762
+ else if (e.name === "edit" && path26) this.editedPaths.add(path26);
11763
+ else if (e.name === "write" && path26) {
11764
+ this.writtenPaths.add(path26);
11560
11765
  const content = typeof input?.content === "string" ? input.content : "";
11561
11766
  this.bytesWritten += Buffer.byteLength(content, "utf8");
11562
11767
  }
@@ -11660,6 +11865,150 @@ function samplePaths(set) {
11660
11865
  if (arr.length <= 2) return arr.join(", ");
11661
11866
  return `${arr[0]}, \u2026 (+${arr.length - 1} more)`;
11662
11867
  }
11868
+ function createAutoPhaseHost(deps) {
11869
+ const store = new PhaseStore({ baseDir: deps.storeDir });
11870
+ let active = null;
11871
+ const log = deps.log ?? (() => {
11872
+ });
11873
+ async function runOnce(prompt, label, signal) {
11874
+ const factory = deps.multiAgentHost.makeSubagentFactory(deps.getConfig());
11875
+ const built = await factory({ name: label });
11876
+ try {
11877
+ const result = await built.agent.run(prompt, { signal });
11878
+ if (result.status !== "done") {
11879
+ throw new Error(result.error?.message ?? `subagent ended with status "${result.status}"`);
11880
+ }
11881
+ return result.finalText ?? "";
11882
+ } finally {
11883
+ await built.dispose?.();
11884
+ }
11885
+ }
11886
+ function buildTaskPrompt(task, phaseName, goal) {
11887
+ return [
11888
+ `You are executing one task inside an autonomous, phase-based build.`,
11889
+ `Overall goal: ${goal}`,
11890
+ `Current phase: ${phaseName}`,
11891
+ "",
11892
+ `TASK: ${task.title}`,
11893
+ task.description ? `Details: ${task.description}` : "",
11894
+ `Type: ${task.type} \xB7 Priority: ${task.priority}`,
11895
+ "",
11896
+ `Do the work now using your tools (read, edit, write, bash, \u2026). Make the`,
11897
+ `change real \u2014 do not just describe it. When finished, end with a one-line`,
11898
+ `summary of what you changed. If the task is impossible or already done,`,
11899
+ `say so explicitly.`
11900
+ ].filter(Boolean).join("\n");
11901
+ }
11902
+ async function persist(graph) {
11903
+ try {
11904
+ await store.save(graph);
11905
+ } catch (err) {
11906
+ log(`\u26A0 AutoPhase save failed: ${err instanceof Error ? err.message : String(err)}`);
11907
+ }
11908
+ }
11909
+ return {
11910
+ async onAutoPhaseStart({ goal, projectContext }) {
11911
+ if (active?.orchestrator.isRunning()) {
11912
+ return { ok: false, error: "An AutoPhase run is already in progress. Use /autophase stop first." };
11913
+ }
11914
+ const abort = new AbortController();
11915
+ log(`\u{1F9E0} Planning phases for: ${goal}`);
11916
+ let phases;
11917
+ try {
11918
+ const planner = new AutoPhasePlanner({
11919
+ goal,
11920
+ projectContext,
11921
+ runOnce: (p) => runOnce(p, "autophase-planner", abort.signal)
11922
+ });
11923
+ const result = await planner.plan();
11924
+ if (result.parseFailed || result.phases.length === 0) {
11925
+ return { ok: false, error: "The planner did not produce a usable phase plan. Try a more specific goal." };
11926
+ }
11927
+ phases = result.phases;
11928
+ } catch (err) {
11929
+ return { ok: false, error: `Planning failed: ${err instanceof Error ? err.message : String(err)}` };
11930
+ }
11931
+ const todoCount = phases.reduce((n, p) => n + (p.taskTemplates?.length ?? 0), 0);
11932
+ log(`\u{1F4CB} Plan ready: ${phases.length} phases, ${todoCount} todos.`);
11933
+ const graph = await new PhaseGraphBuilder({
11934
+ title: goal,
11935
+ phases,
11936
+ autonomous: true
11937
+ }).build();
11938
+ await persist(graph);
11939
+ const orchestrator = new PhaseOrchestrator({
11940
+ graph,
11941
+ ctx: {
11942
+ executeTask: async (task, phaseId) => {
11943
+ const phase = graph.phases.get(phaseId);
11944
+ const phaseName = phase?.name ?? phaseId;
11945
+ return runOnce(
11946
+ buildTaskPrompt(task, phaseName, goal),
11947
+ `autophase-${phaseName}-${task.title}`.slice(0, 48),
11948
+ abort.signal
11949
+ );
11950
+ },
11951
+ onPhaseComplete: (phase) => {
11952
+ log(`\u2705 Phase completed: ${phase.name}`);
11953
+ void persist(graph);
11954
+ },
11955
+ onPhaseFail: (phase, error) => {
11956
+ log(`\u274C Phase failed: ${phase.name} \u2014 ${error.message}`);
11957
+ void persist(graph);
11958
+ }
11959
+ },
11960
+ events: deps.events,
11961
+ autonomous: true,
11962
+ maxConcurrentPhases: 1,
11963
+ // Sequential within a phase: each todo is a full-tool agent editing the
11964
+ // shared working tree, and todos in a phase typically build on one
11965
+ // another. Running two at once risks concurrent writes / lost edits.
11966
+ maxConcurrentTasks: 1
11967
+ });
11968
+ const onUntyped = deps.events.on;
11969
+ const offUntyped = deps.events.off;
11970
+ const onDone = () => {
11971
+ log(`\u{1F389} AutoPhase complete: ${graph.title}`);
11972
+ void persist(graph);
11973
+ };
11974
+ const onFailed = () => void persist(graph);
11975
+ onUntyped("graph.completed", onDone);
11976
+ onUntyped("graph.failed", onFailed);
11977
+ const unsubscribe = () => {
11978
+ offUntyped("graph.completed", onDone);
11979
+ offUntyped("graph.failed", onFailed);
11980
+ };
11981
+ active = { graph, orchestrator, abort, unsubscribe };
11982
+ void orchestrator.start().catch((err) => {
11983
+ log(`\u{1F4A5} AutoPhase aborted: ${err instanceof Error ? err.message : String(err)}`);
11984
+ });
11985
+ return { ok: true, graph };
11986
+ },
11987
+ onAutoPhasePause() {
11988
+ active?.orchestrator.pause();
11989
+ },
11990
+ onAutoPhaseResume() {
11991
+ active?.orchestrator.resume();
11992
+ },
11993
+ onAutoPhaseStop() {
11994
+ if (!active) return;
11995
+ active.abort.abort();
11996
+ active.orchestrator.stop();
11997
+ active.unsubscribe();
11998
+ void persist(active.graph);
11999
+ active = null;
12000
+ },
12001
+ getAutoPhaseRunner() {
12002
+ if (!active) return null;
12003
+ const a = active;
12004
+ return {
12005
+ graph: a.graph,
12006
+ getProgress: () => a.orchestrator.getProgress(),
12007
+ isRunning: () => a.orchestrator.isRunning()
12008
+ };
12009
+ }
12010
+ };
12011
+ }
11663
12012
  var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
11664
12013
  var FILLED2 = "\u2588";
11665
12014
  var EMPTY2 = "\u2591";
@@ -11827,7 +12176,7 @@ function setupMetrics(params) {
11827
12176
  name: "session-store",
11828
12177
  check: async () => {
11829
12178
  try {
11830
- await fsp2.access(wpaths.projectSessions);
12179
+ await fsp3.access(wpaths.projectSessions);
11831
12180
  return { status: "healthy" };
11832
12181
  } catch (e) {
11833
12182
  return { status: "unhealthy", detail: e instanceof Error ? e.message : "access denied" };
@@ -11844,7 +12193,7 @@ function setupMetrics(params) {
11844
12193
  const dumpMetrics = () => {
11845
12194
  if (!metricsSink) return;
11846
12195
  try {
11847
- const out = path24.join(wpaths.projectSessions, "metrics.json");
12196
+ const out = path25.join(wpaths.projectSessions, "metrics.json");
11848
12197
  const snap = metricsSink.snapshot();
11849
12198
  writeFileSync(out, JSON.stringify(snap, null, 2));
11850
12199
  } catch {
@@ -12035,12 +12384,12 @@ async function setupSession(params) {
12035
12384
  }
12036
12385
  const sessionRef = { current: session };
12037
12386
  await recoveryLock.write(session.id).catch(() => void 0);
12038
- const attachments = new DefaultAttachmentStore({ spoolDir: path24.join(wpaths.projectSessions, session.id, "attachments") });
12039
- const queueStore = new QueueStore({ dir: path24.join(wpaths.projectSessions, session.id) });
12387
+ const attachments = new DefaultAttachmentStore({ spoolDir: path25.join(wpaths.projectSessions, session.id, "attachments") });
12388
+ const queueStore = new QueueStore({ dir: path25.join(wpaths.projectSessions, session.id) });
12040
12389
  const ctxSignal = new AbortController().signal;
12041
12390
  const context = new Context({ systemPrompt, provider, session, signal: ctxSignal, tokenCounter, cwd, projectRoot, model: config.model });
12042
12391
  if (restoredMessages.length > 0) context.state.replaceMessages(restoredMessages);
12043
- const todosCheckpointPath = path24.join(wpaths.projectSessions, `${session.id}.todos.json`);
12392
+ const todosCheckpointPath = path25.join(wpaths.projectSessions, `${session.id}.todos.json`);
12044
12393
  if (resumeId) {
12045
12394
  try {
12046
12395
  const restoredTodos = await loadTodosCheckpoint(todosCheckpointPath);
@@ -12052,13 +12401,13 @@ async function setupSession(params) {
12052
12401
  }
12053
12402
  }
12054
12403
  const detachTodosCheckpoint = attachTodosCheckpoint(context.state, todosCheckpointPath, session.id);
12055
- const planPath = path24.join(wpaths.projectSessions, `${session.id}.plan.json`);
12404
+ const planPath = path25.join(wpaths.projectSessions, `${session.id}.plan.json`);
12056
12405
  context.state.setMeta("plan.path", planPath);
12057
12406
  let dirState;
12058
12407
  if (resumeId) {
12059
12408
  try {
12060
- const fleetRoot = path24.join(wpaths.projectSessions, session.id);
12061
- dirState = await loadDirectorState(path24.join(fleetRoot, "director-state.json"));
12409
+ const fleetRoot = path25.join(wpaths.projectSessions, session.id);
12410
+ dirState = await loadDirectorState(path25.join(fleetRoot, "director-state.json"));
12062
12411
  if (dirState) {
12063
12412
  const tCounts = {};
12064
12413
  for (const t of dirState.tasks) tCounts[t.status] = (tCounts[t.status] ?? 0) + 1;
@@ -12085,7 +12434,7 @@ function resolveBundledSkillsDir2() {
12085
12434
  try {
12086
12435
  const req2 = createRequire(import.meta.url);
12087
12436
  const corePkg = req2.resolve("@wrongstack/core/package.json");
12088
- return path24.join(path24.dirname(corePkg), "skills");
12437
+ return path25.join(path25.dirname(corePkg), "skills");
12089
12438
  } catch {
12090
12439
  return void 0;
12091
12440
  }
@@ -12185,7 +12534,7 @@ async function main(argv) {
12185
12534
  modeId,
12186
12535
  modePrompt,
12187
12536
  modelCapabilities,
12188
- planPath: () => sessionRef.current ? path24.join(wpaths.projectSessions, `${sessionRef.current.id}.plan.json`) : void 0,
12537
+ planPath: () => sessionRef.current ? path25.join(wpaths.projectSessions, `${sessionRef.current.id}.plan.json`) : void 0,
12189
12538
  contributors: [
12190
12539
  // Injects the ETERNAL AUTONOMY block when the user has activated
12191
12540
  // `/autonomy eternal`. Without this, the per-iteration directive
@@ -12389,12 +12738,12 @@ async function main(argv) {
12389
12738
  }
12390
12739
  }
12391
12740
  };
12392
- const fleetRoot = directorMode ? path24.join(wpaths.projectSessions, session.id) : void 0;
12393
- const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] : path24.join(fleetRoot, "fleet.json") : void 0;
12394
- const sharedScratchpadPath = directorMode ? path24.join(fleetRoot, "shared") : void 0;
12395
- const subagentSessionsRoot = directorMode ? path24.join(fleetRoot, "subagents") : void 0;
12396
- const stateCheckpointPath = directorMode ? path24.join(fleetRoot, "director-state.json") : void 0;
12397
- const fleetRootForPromotion = path24.join(wpaths.projectSessions, session.id);
12741
+ const fleetRoot = directorMode ? path25.join(wpaths.projectSessions, session.id) : void 0;
12742
+ const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] : path25.join(fleetRoot, "fleet.json") : void 0;
12743
+ const sharedScratchpadPath = directorMode ? path25.join(fleetRoot, "shared") : void 0;
12744
+ const subagentSessionsRoot = directorMode ? path25.join(fleetRoot, "subagents") : void 0;
12745
+ const stateCheckpointPath = directorMode ? path25.join(fleetRoot, "director-state.json") : void 0;
12746
+ const fleetRootForPromotion = path25.join(wpaths.projectSessions, session.id);
12398
12747
  const multiAgentHost = new MultiAgentHost(
12399
12748
  {
12400
12749
  container,
@@ -12483,6 +12832,14 @@ async function main(argv) {
12483
12832
  this.visible = visible;
12484
12833
  }
12485
12834
  };
12835
+ const autoPhaseHost = createAutoPhaseHost({
12836
+ multiAgentHost,
12837
+ getConfig: () => config,
12838
+ events,
12839
+ storeDir: wpaths.projectAutophase,
12840
+ log: (line) => renderer.write(`${line}
12841
+ `)
12842
+ });
12486
12843
  const slashCmds = buildBuiltinSlashCommands({
12487
12844
  registry: slashRegistry,
12488
12845
  toolRegistry,
@@ -12492,6 +12849,7 @@ async function main(argv) {
12492
12849
  skillLoader,
12493
12850
  tokenCounter,
12494
12851
  renderer,
12852
+ events,
12495
12853
  memoryStore,
12496
12854
  context,
12497
12855
  cwd,
@@ -12717,27 +13075,27 @@ async function main(argv) {
12717
13075
  return director.spawn(cfg);
12718
13076
  },
12719
13077
  onFleetLog: async (subagentId, mode) => {
12720
- const subagentsRoot = path24.join(fleetRootForPromotion, "subagents");
13078
+ const subagentsRoot = path25.join(fleetRootForPromotion, "subagents");
12721
13079
  let runDirs;
12722
13080
  try {
12723
- runDirs = await fsp2.readdir(subagentsRoot);
13081
+ runDirs = await fsp3.readdir(subagentsRoot);
12724
13082
  } catch {
12725
13083
  return "No fleet transcripts on disk \u2014 no subagents have been spawned for this session.";
12726
13084
  }
12727
13085
  const found = [];
12728
13086
  for (const runId of runDirs) {
12729
- const runDir = path24.join(subagentsRoot, runId);
13087
+ const runDir = path25.join(subagentsRoot, runId);
12730
13088
  let files;
12731
13089
  try {
12732
- files = await fsp2.readdir(runDir);
13090
+ files = await fsp3.readdir(runDir);
12733
13091
  } catch {
12734
13092
  continue;
12735
13093
  }
12736
13094
  for (const f of files) {
12737
13095
  if (!f.endsWith(".jsonl")) continue;
12738
- const full = path24.join(runDir, f);
13096
+ const full = path25.join(runDir, f);
12739
13097
  try {
12740
- const stat3 = await fsp2.stat(full);
13098
+ const stat3 = await fsp3.stat(full);
12741
13099
  found.push({
12742
13100
  runId,
12743
13101
  subagentId: f.replace(/\.jsonl$/, ""),
@@ -12776,7 +13134,7 @@ async function main(argv) {
12776
13134
  ].join("\n");
12777
13135
  }
12778
13136
  const t = matches[0];
12779
- const raw = await fsp2.readFile(t.file, "utf8");
13137
+ const raw = await fsp3.readFile(t.file, "utf8");
12780
13138
  if (mode === "raw") return raw;
12781
13139
  const lines = raw.split("\n").filter((l) => l.trim());
12782
13140
  const counts = {};
@@ -12832,7 +13190,7 @@ async function main(argv) {
12832
13190
  }
12833
13191
  const dir = await multiAgentHost.ensureDirector();
12834
13192
  if (!dir) return "Director is not available.";
12835
- const dirStatePath = path24.join(fleetRootForPromotion, "director-state.json");
13193
+ const dirStatePath = path25.join(fleetRootForPromotion, "director-state.json");
12836
13194
  const prior = await loadDirectorState(dirStatePath);
12837
13195
  if (!prior) {
12838
13196
  return "No prior director-state.json found \u2014 nothing to retry.";
@@ -12903,9 +13261,9 @@ async function main(argv) {
12903
13261
  for (const tool of director2.tools(FLEET_ROSTER)) {
12904
13262
  toolRegistry.register(tool);
12905
13263
  }
12906
- const mp = path24.join(fleetRootForPromotion, "fleet.json");
12907
- const sp = path24.join(fleetRootForPromotion, "shared");
12908
- const ss = path24.join(fleetRootForPromotion, "subagents");
13264
+ const mp = path25.join(fleetRootForPromotion, "fleet.json");
13265
+ const sp = path25.join(fleetRootForPromotion, "shared");
13266
+ const ss = path25.join(fleetRootForPromotion, "subagents");
12909
13267
  const lines = [
12910
13268
  `${color.green("\u2713")} Promoted to director mode.`,
12911
13269
  ` Roster: ${Object.keys(FLEET_ROSTER).join(", ")}`,
@@ -13108,7 +13466,12 @@ Restart WrongStack to load or unload plugin code in this session.`;
13108
13466
  onSddParallelStop: () => {
13109
13467
  const run = globalThis.__sddParallelRun;
13110
13468
  run?.stop();
13111
- }
13469
+ },
13470
+ onAutoPhaseStart: autoPhaseHost.onAutoPhaseStart,
13471
+ onAutoPhasePause: autoPhaseHost.onAutoPhasePause,
13472
+ onAutoPhaseResume: autoPhaseHost.onAutoPhaseResume,
13473
+ onAutoPhaseStop: autoPhaseHost.onAutoPhaseStop,
13474
+ getAutoPhaseRunner: autoPhaseHost.getAutoPhaseRunner
13112
13475
  });
13113
13476
  for (const cmd of slashCmds) slashRegistry.register(cmd);
13114
13477
  const eternalFlag = typeof flags["eternal"] === "string" ? flags["eternal"].trim() : "";