@wrongstack/cli 0.8.4 → 0.8.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
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, AutoPhaseRunner, 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, PhaseStore, 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, WorktreeManager, PhaseOrchestrator, makeLLMClassifier, resolveWstackPaths, DefaultSecretVault, migratePlaintextSecrets, DefaultConfigLoader, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, atomicWrite, DefaultPluginAPI, makeAgentSubagentRunner, NULL_FLEET_BUS, buildChildEnv, 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
- import * as os7 from 'os';
9
- import os7__default from 'os';
8
+ import * as os6 from 'os';
9
+ import os6__default from 'os';
10
10
  import * as crypto2 from 'crypto';
11
11
  import { randomUUID } from 'crypto';
12
12
  import { DefaultSecretVault as DefaultSecretVault$1, encryptConfigSecrets, isSecretField, decryptConfigSecrets as decryptConfigSecrets$1 } from '@wrongstack/core/security';
@@ -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();
@@ -1399,7 +1399,7 @@ async function gatherProjectContext(projectRoot) {
1399
1399
  const parts = [];
1400
1400
  try {
1401
1401
  const pkgPath = path25.join(projectRoot, "package.json");
1402
- const pkgRaw = await fsp2.readFile(pkgPath, "utf8");
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")}`);
@@ -1415,13 +1415,13 @@ async function gatherProjectContext(projectRoot) {
1415
1415
  }
1416
1416
  try {
1417
1417
  const tsconfigPath = path25.join(projectRoot, "tsconfig.json");
1418
- await fsp2.access(tsconfigPath);
1418
+ await fsp3.access(tsconfigPath);
1419
1419
  parts.push("Language: TypeScript");
1420
1420
  } catch {
1421
1421
  }
1422
1422
  try {
1423
1423
  const srcDir = path25.join(projectRoot, "src");
1424
- const entries = await fsp2.readdir(srcDir, { withFileTypes: true });
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/")}`);
@@ -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;
@@ -1614,8 +1614,8 @@ async function readCache(homeFn = defaultHomeDir2) {
1614
1614
  async function writeCache(entry, homeFn = defaultHomeDir2) {
1615
1615
  try {
1616
1616
  const dir = path25.dirname(cachePath(homeFn));
1617
- await fsp2.mkdir(dir, { recursive: true });
1618
- await fsp2.writeFile(cachePath(homeFn), JSON.stringify(entry, null, 2), "utf8");
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
  }
@@ -1686,7 +1686,7 @@ async function getUpdateNotification(signal, homeFn) {
1686
1686
  var defaultHomeDir2, CACHE_TTL_MS;
1687
1687
  var init_update_check = __esm({
1688
1688
  "src/update-check.ts"() {
1689
- defaultHomeDir2 = () => os7.homedir();
1689
+ defaultHomeDir2 = () => os6.homedir();
1690
1690
  CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
1691
1691
  }
1692
1692
  });
@@ -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
  }
@@ -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(
@@ -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;
@@ -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(path25.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`;
@@ -2532,7 +2549,7 @@ async function detectProjectFacts(root) {
2532
2549
  } catch {
2533
2550
  }
2534
2551
  try {
2535
- const makefile = await fsp2.readFile(path25.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";
@@ -3428,8 +3445,8 @@ function buildInitCommand(opts) {
3428
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.`;
@@ -3618,7 +3635,7 @@ function stateBadge(state) {
3618
3635
  }
3619
3636
  async function readConfig(path26) {
3620
3637
  try {
3621
- return JSON.parse(await fsp2.readFile(path26, "utf8"));
3638
+ return JSON.parse(await fsp3.readFile(path26, "utf8"));
3622
3639
  } catch {
3623
3640
  return {};
3624
3641
  }
@@ -3626,8 +3643,8 @@ async function readConfig(path26) {
3626
3643
  async function writeConfig(path26, cfg) {
3627
3644
  const raw = JSON.stringify(cfg, null, 2);
3628
3645
  const tmp = path26 + ".tmp";
3629
- await fsp2.writeFile(tmp, raw, "utf8");
3630
- await fsp2.rename(tmp, path26);
3646
+ await fsp3.writeFile(tmp, raw, "utf8");
3647
+ await fsp3.rename(tmp, path26);
3631
3648
  }
3632
3649
 
3633
3650
  // src/slash-commands/mcp.ts
@@ -4899,7 +4916,7 @@ function resolveConfigPath() {
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(path25.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,7 +5824,7 @@ 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 = path25.join(os7.homedir(), ".wrongstack");
5827
+ const globalRoot = path25.join(os6.homedir(), ".wrongstack");
5811
5828
  return new SkillInstaller({
5812
5829
  manifestPath: path25.join(globalRoot, "installed-skills.json"),
5813
5830
  projectSkillsDir: path25.join(projectRoot, ".wrongstack", "skills"),
@@ -5967,229 +5984,207 @@ function buildSkillUninstallCommand(opts) {
5967
5984
  }
5968
5985
  };
5969
5986
  }
5970
- var currentRunner = null;
5971
- var currentGraph = null;
5972
- var DEFAULT_PHASES = [
5973
- {
5974
- name: "Discovery",
5975
- description: "Requirements gathering and analysis",
5976
- priority: "high",
5977
- estimateHours: 2,
5978
- parallelizable: false
5979
- },
5980
- {
5981
- name: "Design",
5982
- description: "Architecture and design decisions",
5983
- priority: "critical",
5984
- estimateHours: 4,
5985
- parallelizable: false
5986
- },
5987
- {
5988
- name: "Implementation",
5989
- description: "Core feature development",
5990
- priority: "critical",
5991
- estimateHours: 12,
5992
- parallelizable: false
5993
- },
5994
- {
5995
- name: "Testing",
5996
- description: "Unit, integration, and e2e tests",
5997
- priority: "high",
5998
- estimateHours: 6,
5999
- parallelizable: true
6000
- },
6001
- {
6002
- name: "Deployment",
6003
- description: "Deploy to production",
6004
- priority: "medium",
6005
- estimateHours: 2,
6006
- parallelizable: false
6007
- }
6008
- ];
6009
- function getStore() {
6010
- const baseDir = path25.join(os7.homedir(), ".wrongstack", "autophase");
6011
- return new PhaseStore({ baseDir });
5987
+ function getStore(opts) {
5988
+ return new PhaseStore({ baseDir: opts.paths.projectAutophase });
6012
5989
  }
6013
5990
  function formatProgress(p) {
6014
- const bars = "\u2588".repeat(Math.floor(p.percentComplete / 5)) + "\u2591".repeat(20 - Math.floor(p.percentComplete / 5));
5991
+ const filled = Math.floor(p.percentComplete / 5);
5992
+ const bars = "\u2588".repeat(filled) + "\u2591".repeat(20 - filled);
6015
5993
  return [
6016
5994
  `
6017
5995
  \u{1F4CA} Progress: ${bars} ${p.percentComplete}%`,
6018
5996
  ` \u{1F4CB} Phases: ${p.completed}/${p.totalPhases} done, ${p.running} running, ${p.pending} pending`,
6019
5997
  ` \u2705 Tasks: ${p.completedTasks}/${p.totalTasks} completed`,
6020
- ` \u23F1\uFE0F Est: ${p.estimatedHours.toFixed(1)}h | Actual: ${p.actualHours.toFixed(1)}h`
5998
+ ` \u23F1 Est: ${p.estimatedHours.toFixed(1)}h | Actual: ${p.actualHours.toFixed(1)}h`
6021
5999
  ].join("\n");
6022
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
+ };
6023
6010
  function formatPhaseList(graph) {
6024
6011
  const phases = Array.from(graph.phases.values());
6025
- const statusEmoji = {
6026
- pending: "\u23F3",
6027
- ready: "\u{1F51C}",
6028
- running: "\u{1F504}",
6029
- paused: "\u23F8\uFE0F",
6030
- completed: "\u2705",
6031
- failed: "\u274C",
6032
- skipped: "\u23ED\uFE0F"
6033
- };
6034
- return phases.map((p, i) => {
6035
- const emoji = statusEmoji[p.status] ?? "\u26AA";
6036
- const progress = p.taskGraph.nodes.size > 0 ? `${Array.from(p.taskGraph.nodes.values()).filter((t) => t.status === "completed").length}/${p.taskGraph.nodes.size}` : "0/0";
6037
- return ` ${i + 1}. ${emoji} ${p.name} (${p.status}) \u2014 ${progress} tasks`;
6038
- }).join("\n");
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");
6039
6022
  }
6040
- var autophaseCommand = {
6041
- name: "autophase",
6042
- description: "Otonom faz tabanl\u0131 i\u015F ak\u0131\u015F\u0131 \u2014 projeyi fazlara b\xF6l ve otonom \xE7al\u0131\u015Ft\u0131r",
6043
- argsHint: "[start|pause|resume|stop|status|list|load|save] [args...]",
6044
- async run(args, _ctx) {
6045
- const parts = args.trim().split(/\s+/).filter(Boolean);
6046
- const sub = parts[0] ?? "status";
6047
- const store = getStore();
6048
- switch (sub) {
6049
- case "start": {
6050
- const title = parts.slice(1).join(" ") || "Untitled Project";
6051
- const runLog = [];
6052
- const log = (line) => runLog.push(line);
6053
- log(`\u{1F680} AutoPhase ba\u015Flat\u0131l\u0131yor: ${title}`);
6054
- currentRunner = new AutoPhaseRunner({
6055
- title,
6056
- phases: DEFAULT_PHASES,
6057
- executeTask: async (task, phaseId) => {
6058
- log(` [${phaseId}] Executing: ${task.title}`);
6059
- await new Promise((r) => setTimeout(r, 500));
6060
- log(` [${phaseId}] \u2705 Completed: ${task.title}`);
6061
- },
6062
- onPhaseComplete: (phase) => {
6063
- log(`\u2705 Phase tamamland\u0131: ${phase.name} (${phase.actualDurationMs ? (phase.actualDurationMs / 6e4).toFixed(1) : 0}m)`);
6064
- },
6065
- onPhaseFail: (phase, error) => {
6066
- log(`\u274C Phase ba\u015Far\u0131s\u0131z: ${phase.name} \u2014 ${error.message}`);
6067
- },
6068
- onProgress: (progress) => {
6069
- if (progress.percentComplete % 10 === 0) {
6070
- log(formatProgress(progress));
6071
- }
6072
- },
6073
- onComplete: (graph) => {
6074
- log(`\u{1F389} T\xFCm fazlar tamamland\u0131! ${graph.title}`);
6075
- },
6076
- onFail: (_graph, phase, error) => {
6077
- log(`\u{1F4A5} AutoPhase durdu: ${phase.name} \u2014 ${error.message}`);
6078
- },
6079
- autonomous: true,
6080
- maxConcurrentPhases: 1,
6081
- maxConcurrentTasks: 2
6082
- });
6083
- currentGraph = await currentRunner.start();
6084
- await store.save(currentGraph);
6085
- return {
6086
- message: [
6087
- `AutoPhase ba\u015Flat\u0131ld\u0131: **${title}**`,
6088
- "",
6089
- formatPhaseList(currentGraph),
6090
- ...runLog.length > 0 ? ["", "---", ...runLog] : []
6091
- ].join("\n")
6092
- };
6093
- }
6094
- case "pause": {
6095
- if (!currentRunner) {
6096
- return { message: "\u274C Aktif AutoPhase yok. \xD6nce `/autophase start` \xE7al\u0131\u015Ft\u0131r\u0131n." };
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
+ };
6097
6080
  }
6098
- currentRunner.pause();
6099
- return { message: "\u23F8\uFE0F AutoPhase duraklat\u0131ld\u0131. Devam etmek i\xE7in `/autophase resume`" };
6100
- }
6101
- case "resume": {
6102
- if (!currentRunner) {
6103
- return { message: "\u274C Aktif AutoPhase yok. \xD6nce `/autophase start` \xE7al\u0131\u015Ft\u0131r\u0131n." };
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." };
6104
6085
  }
6105
- currentRunner.resume();
6106
- return { message: "\u25B6\uFE0F AutoPhase devam ediyor." };
6107
- }
6108
- case "stop": {
6109
- if (!currentRunner) {
6110
- return { message: "\u274C Aktif AutoPhase yok." };
6086
+ case "resume": {
6087
+ if (!opts.onAutoPhaseResume) return { message: "\u274C AutoPhase host not available." };
6088
+ opts.onAutoPhaseResume();
6089
+ return { message: "\u25B6 AutoPhase resuming." };
6111
6090
  }
6112
- currentRunner.stop();
6113
- currentRunner = null;
6114
- return { message: "\u{1F6D1} AutoPhase durduruldu." };
6115
- }
6116
- case "status": {
6117
- if (!currentRunner || !currentGraph) {
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();
6118
6104
  const graphs = await store.list();
6119
- if (graphs.length === 0) {
6120
- return { message: "Aktif AutoPhase yok. Ba\u015Flatmak i\xE7in `/autophase start [title]`" };
6121
- }
6122
- const list = graphs.slice(0, 5).map((g) => ` \u2022 ${g.title} (${g.status})`).join("\n");
6123
- return { message: `Kay\u0131tl\u0131 AutoPhase projeleri:
6124
- ${list}` };
6125
- }
6126
- const progress = currentRunner.getProgress();
6127
- const phaseList = formatPhaseList(currentGraph);
6128
- return {
6129
- message: [
6130
- `**${currentGraph.title}**`,
6131
- progress ? formatProgress(progress) : "",
6132
- "",
6133
- "**Fazlar:**",
6134
- phaseList,
6135
- "",
6136
- currentRunner.isPaused() ? "\u23F8\uFE0F Duraklat\u0131ld\u0131" : currentRunner.isRunning() ? "\u{1F504} \xC7al\u0131\u015F\u0131yor" : "\u23F9\uFE0F Durdu"
6137
- ].join("\n")
6138
- };
6139
- }
6140
- case "list": {
6141
- const graphs = await store.list();
6142
- if (graphs.length === 0) {
6143
- return { message: "Kay\u0131tl\u0131 AutoPhase projesi yok." };
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
+ };
6144
6113
  }
6145
- const list = graphs.map((g) => {
6146
- const statusEmoji = g.status === "completed" ? "\u2705" : g.status === "in_progress" ? "\u{1F504}" : "\u23F3";
6147
- return ` ${statusEmoji} ${g.title} (g\xFCncelleme: ${new Date(g.updatedAt).toLocaleDateString("tr-TR")})`;
6148
- }).join("\n");
6149
- return { message: `**Kay\u0131tl\u0131 Projeler:**
6150
- ${list}` };
6151
- }
6152
- case "load": {
6153
- const graphId = parts[1];
6154
- if (!graphId) {
6155
- return { message: "\u274C Graph ID gerekli. Kullan\u0131m: `/autophase load <id>`" };
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
+ };
6156
6123
  }
6157
- const graph = await store.load(graphId);
6158
- if (!graph) {
6159
- return { message: `\u274C Graph bulunamad\u0131: ${graphId}` };
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
+ };
6160
6138
  }
6161
- currentGraph = graph;
6162
- return {
6163
- message: `**${graph.title}** y\xFCklendi.
6139
+ }
6140
+ }
6141
+ };
6142
+ }
6164
6143
 
6165
- ${formatPhaseList(graph)}`
6166
- };
6144
+ // src/slash-commands/worktree.ts
6145
+ function buildWorktreeCommand(opts) {
6146
+ return {
6147
+ name: "worktree",
6148
+ aliases: ["wt"],
6149
+ description: "Inspect/manage git worktrees used for AutoPhase per-phase isolation.",
6150
+ argsHint: "[list | merge <branch> | prune | clean]",
6151
+ help: [
6152
+ "Usage: /worktree [subcommand]",
6153
+ "",
6154
+ " list List active worktrees (default).",
6155
+ " merge <branch> Squash-merge <branch> into the current branch.",
6156
+ " prune Remove stale worktree administrative entries.",
6157
+ " clean Remove all wstack-managed worktrees and branches.",
6158
+ "",
6159
+ "AutoPhase allocates one worktree per phase under .wrongstack/worktrees/",
6160
+ "so parallelizable phases run isolated, then merge back sequentially."
6161
+ ].join("\n"),
6162
+ async run(args) {
6163
+ if (!opts.onWorktree) {
6164
+ return { message: "\u26A0 No worktree manager active in this session." };
6167
6165
  }
6168
- case "save": {
6169
- if (!currentGraph) {
6170
- return { message: "\u274C Kaydedilecek aktif graph yok." };
6171
- }
6172
- await store.save(currentGraph);
6173
- return { message: `\u2705 **${currentGraph.title}** kaydedildi.` };
6166
+ const parts = args.trim().split(/\s+/).filter(Boolean);
6167
+ const sub = (parts[0] ?? "list").toLowerCase();
6168
+ switch (sub) {
6169
+ case "list":
6170
+ return { message: await opts.onWorktree("list") };
6171
+ case "merge": {
6172
+ const branch = parts[1];
6173
+ if (!branch) return { message: "Usage: /worktree merge <branch>" };
6174
+ return { message: await opts.onWorktree("merge", branch) };
6175
+ }
6176
+ case "prune":
6177
+ return { message: await opts.onWorktree("prune") };
6178
+ case "clean":
6179
+ return { message: await opts.onWorktree("clean") };
6180
+ default:
6181
+ return {
6182
+ message: `Unknown subcommand "${sub}". Valid: list, merge <branch>, prune, clean.`
6183
+ };
6174
6184
  }
6175
- default:
6176
- return {
6177
- message: [
6178
- "**AutoPhase Komutlar\u0131:**",
6179
- "",
6180
- "`/autophase start [title]` \u2014 Yeni proje ba\u015Flat",
6181
- "`/autophase pause` \u2014 Duraklat",
6182
- "`/autophase resume` \u2014 Devam et",
6183
- "`/autophase stop` \u2014 Durdur",
6184
- "`/autophase status` \u2014 Durum g\xF6ster",
6185
- "`/autophase list` \u2014 Kay\u0131tl\u0131 projeleri listele",
6186
- "`/autophase load <id>` \u2014 Projeyi y\xFCkle",
6187
- "`/autophase save` \u2014 Aktif projeyi kaydet"
6188
- ].join("\n")
6189
- };
6190
6185
  }
6191
- }
6192
- };
6186
+ };
6187
+ }
6193
6188
 
6194
6189
  // src/slash-commands/index.ts
6195
6190
  function buildBuiltinSlashCommands(opts) {
@@ -6232,7 +6227,8 @@ function buildBuiltinSlashCommands(opts) {
6232
6227
  buildPushCommand(),
6233
6228
  buildSecurityCommand(opts),
6234
6229
  buildFixCommand(opts),
6235
- autophaseCommand,
6230
+ buildAutoPhaseCommand(opts),
6231
+ buildWorktreeCommand(opts),
6236
6232
  buildStatuslineCommand({
6237
6233
  cwd: opts.cwd,
6238
6234
  hiddenItems: opts.statuslineHiddenItems ?? [],
@@ -6260,13 +6256,13 @@ var MANIFESTS = [
6260
6256
  ];
6261
6257
  async function detectProjectKind(projectRoot) {
6262
6258
  try {
6263
- await fsp2.access(path25.join(projectRoot, ".wrongstack", "AGENTS.md"));
6259
+ await fsp3.access(path25.join(projectRoot, ".wrongstack", "AGENTS.md"));
6264
6260
  return "initialized";
6265
6261
  } catch {
6266
6262
  }
6267
6263
  for (const m of MANIFESTS) {
6268
6264
  try {
6269
- await fsp2.access(path25.join(projectRoot, m));
6265
+ await fsp3.access(path25.join(projectRoot, m));
6270
6266
  return "project";
6271
6267
  } catch {
6272
6268
  }
@@ -6278,8 +6274,8 @@ async function scaffoldAgentsMd(projectRoot) {
6278
6274
  const file = path25.join(dir, "AGENTS.md");
6279
6275
  const facts = await detectProjectFacts(projectRoot);
6280
6276
  const body = renderAgentsTemplate(facts);
6281
- await fsp2.mkdir(dir, { recursive: true });
6282
- await fsp2.writeFile(file, body, "utf8");
6277
+ await fsp3.mkdir(dir, { recursive: true });
6278
+ await fsp3.writeFile(file, body, "utf8");
6283
6279
  return file;
6284
6280
  }
6285
6281
  async function runProjectCheck(opts) {
@@ -6322,7 +6318,7 @@ async function runProjectCheck(opts) {
6322
6318
  const gitDir = path25.join(projectRoot, ".git");
6323
6319
  let hasGit = false;
6324
6320
  try {
6325
- await fsp2.access(gitDir);
6321
+ await fsp3.access(gitDir);
6326
6322
  hasGit = true;
6327
6323
  } catch {
6328
6324
  }
@@ -6341,9 +6337,9 @@ async function runProjectCheck(opts) {
6341
6337
  }
6342
6338
  if (answer2 === "y" || answer2 === "yes") {
6343
6339
  try {
6344
- const { spawn: spawn3 } = await import('child_process');
6340
+ const { spawn: spawn4 } = await import('child_process');
6345
6341
  await new Promise((resolve4, reject) => {
6346
- const child = spawn3("git", ["init"], { cwd: projectRoot });
6342
+ const child = spawn4("git", ["init"], { cwd: projectRoot });
6347
6343
  child.on("error", reject);
6348
6344
  child.on("close", (code) => code === 0 ? resolve4() : reject(new Error(`git init failed with ${code}`)));
6349
6345
  });
@@ -6447,7 +6443,7 @@ async function bootConfig(flags) {
6447
6443
  const cwd = typeof flags["cwd"] === "string" ? path25.resolve(flags["cwd"]) : process.cwd();
6448
6444
  const pathResolver = new DefaultPathResolver(cwd);
6449
6445
  const projectRoot = pathResolver.projectRoot;
6450
- const userHome = os7.homedir();
6446
+ const userHome = os6.homedir();
6451
6447
  const wpaths = resolveWstackPaths({ projectRoot, userHome });
6452
6448
  await ensureProjectMeta(wpaths, projectRoot);
6453
6449
  const vault = new DefaultSecretVault({ keyFile: wpaths.secretsKey });
@@ -6495,13 +6491,13 @@ function flagsToConfigPatch(flags) {
6495
6491
  }
6496
6492
  async function ensureProjectMeta(paths, projectRoot) {
6497
6493
  try {
6498
- await fsp2.mkdir(paths.projectDir, { recursive: true });
6494
+ await fsp3.mkdir(paths.projectDir, { recursive: true });
6499
6495
  const meta = {
6500
6496
  hash: paths.projectHash,
6501
6497
  root: projectRoot,
6502
6498
  lastSeen: (/* @__PURE__ */ new Date()).toISOString()
6503
6499
  };
6504
- await fsp2.writeFile(paths.projectMeta, JSON.stringify(meta, null, 2));
6500
+ await fsp3.writeFile(paths.projectMeta, JSON.stringify(meta, null, 2));
6505
6501
  } catch {
6506
6502
  }
6507
6503
  }
@@ -6511,11 +6507,11 @@ var ReadlineInputReader = class {
6511
6507
  history = [];
6512
6508
  pending = false;
6513
6509
  constructor(opts = {}) {
6514
- this.historyFile = opts.historyFile ?? path25.join(os7.homedir(), ".wrongstack", "history");
6510
+ this.historyFile = opts.historyFile ?? path25.join(os6.homedir(), ".wrongstack", "history");
6515
6511
  }
6516
6512
  async loadHistory() {
6517
6513
  try {
6518
- const raw = await fsp2.readFile(this.historyFile, "utf8");
6514
+ const raw = await fsp3.readFile(this.historyFile, "utf8");
6519
6515
  this.history = raw.split("\n").filter(Boolean).slice(-1e3);
6520
6516
  } catch {
6521
6517
  this.history = [];
@@ -6523,8 +6519,8 @@ var ReadlineInputReader = class {
6523
6519
  }
6524
6520
  async saveHistory() {
6525
6521
  try {
6526
- await fsp2.mkdir(path25.dirname(this.historyFile), { recursive: true });
6527
- await fsp2.writeFile(this.historyFile, this.history.slice(-1e3).join("\n"));
6522
+ await fsp3.mkdir(path25.dirname(this.historyFile), { recursive: true });
6523
+ await fsp3.writeFile(this.historyFile, this.history.slice(-1e3).join("\n"));
6528
6524
  } catch {
6529
6525
  }
6530
6526
  }
@@ -6766,7 +6762,7 @@ async function safeDelete(filePath) {
6766
6762
  const filename = path25.basename(filePath);
6767
6763
  try {
6768
6764
  assertSafeToDelete(filename, dir);
6769
- await fsp2.unlink(filePath);
6765
+ await fsp3.unlink(filePath);
6770
6766
  } catch (err) {
6771
6767
  if (err instanceof Error && err.message.startsWith("Refusing")) {
6772
6768
  process.stderr.write(`[config-history] SAFETY: ${err.message}
@@ -6806,7 +6802,7 @@ function diffSummary(oldCfg, newCfg) {
6806
6802
  }
6807
6803
  return changes.length > 0 ? changes.slice(0, 5).join(", ") : "no changes";
6808
6804
  }
6809
- var defaultHomeDir = () => os7__default.homedir();
6805
+ var defaultHomeDir = () => os6__default.homedir();
6810
6806
  function historyDir(homeFn = defaultHomeDir) {
6811
6807
  return path25.join(homeFn(), ".wrongstack", "config.history", "entries");
6812
6808
  }
@@ -6824,7 +6820,7 @@ function entryId(ts) {
6824
6820
  }
6825
6821
  async function ensureHistoryDir(homeFn = defaultHomeDir) {
6826
6822
  try {
6827
- await fsp2.mkdir(historyDir(homeFn), { recursive: true });
6823
+ await fsp3.mkdir(historyDir(homeFn), { recursive: true });
6828
6824
  } catch (err) {
6829
6825
  throw new FsError({
6830
6826
  message: err instanceof Error ? err.message : String(err),
@@ -6836,7 +6832,7 @@ async function ensureHistoryDir(homeFn = defaultHomeDir) {
6836
6832
  }
6837
6833
  async function readIndex(homeFn = defaultHomeDir) {
6838
6834
  try {
6839
- const raw = await fsp2.readFile(historyIndexPath(homeFn), "utf8");
6835
+ const raw = await fsp3.readFile(historyIndexPath(homeFn), "utf8");
6840
6836
  return JSON.parse(raw);
6841
6837
  } catch {
6842
6838
  return { version: 1, entries: [] };
@@ -6861,7 +6857,7 @@ async function backupCurrent(homeFn = defaultHomeDir) {
6861
6857
  const ts = Date.now();
6862
6858
  let content;
6863
6859
  try {
6864
- content = await fsp2.readFile(cfg, "utf8");
6860
+ content = await fsp3.readFile(cfg, "utf8");
6865
6861
  } catch {
6866
6862
  }
6867
6863
  if (content !== void 0) {
@@ -6879,7 +6875,7 @@ async function backupCurrent(homeFn = defaultHomeDir) {
6879
6875
  }
6880
6876
  try {
6881
6877
  const dir = path25.join(homeFn(), ".wrongstack");
6882
- const files = await fsp2.readdir(dir);
6878
+ const files = await fsp3.readdir(dir);
6883
6879
  const baks = files.filter((f) => f.startsWith("config.json.") && f.endsWith(".bak")).sort().reverse();
6884
6880
  for (const f of baks.slice(10)) {
6885
6881
  await safeDelete(path25.join(dir, f));
@@ -6899,7 +6895,7 @@ async function appendHistory(oldCfg, newCfg, description, homeFn = defaultHomeDi
6899
6895
  diffSummary: diffSummary(oldCfg, newCfg)
6900
6896
  };
6901
6897
  try {
6902
- await fsp2.writeFile(
6898
+ await fsp3.writeFile(
6903
6899
  path25.join(historyDir(homeFn), `${id}.json`),
6904
6900
  JSON.stringify(entry, null, 2),
6905
6901
  "utf8"
@@ -6923,7 +6919,7 @@ async function listHistory(homeFn = defaultHomeDir) {
6923
6919
  }
6924
6920
  async function getHistoryEntry(id, homeFn = defaultHomeDir) {
6925
6921
  try {
6926
- const raw = await fsp2.readFile(path25.join(historyDir(homeFn), `${id}.json`), "utf8");
6922
+ const raw = await fsp3.readFile(path25.join(historyDir(homeFn), `${id}.json`), "utf8");
6927
6923
  return JSON.parse(raw);
6928
6924
  } catch {
6929
6925
  return null;
@@ -6935,7 +6931,7 @@ async function restoreFromHistory(id, homeFn = defaultHomeDir) {
6935
6931
  await backupCurrent(homeFn);
6936
6932
  let oldCfg = {};
6937
6933
  try {
6938
- const raw = await fsp2.readFile(configPath(homeFn), "utf8");
6934
+ const raw = await fsp3.readFile(configPath(homeFn), "utf8");
6939
6935
  oldCfg = JSON.parse(raw);
6940
6936
  } catch {
6941
6937
  }
@@ -6957,13 +6953,13 @@ async function restoreLast(homeFn = defaultHomeDir) {
6957
6953
  const cfg = configPath(homeFn);
6958
6954
  let oldCfg = {};
6959
6955
  try {
6960
- const raw = await fsp2.readFile(cfg, "utf8");
6956
+ const raw = await fsp3.readFile(cfg, "utf8");
6961
6957
  oldCfg = JSON.parse(raw);
6962
6958
  } catch {
6963
6959
  }
6964
6960
  let lastCfg = {};
6965
6961
  try {
6966
- const raw = await fsp2.readFile(last, "utf8");
6962
+ const raw = await fsp3.readFile(last, "utf8");
6967
6963
  lastCfg = JSON.parse(raw);
6968
6964
  } catch {
6969
6965
  return { ok: false, error: "No prior backup found" };
@@ -6994,7 +6990,7 @@ async function saveToGlobalConfig(configPath2, provider, model, homeFn = () => p
6994
6990
  existing.provider = provider;
6995
6991
  existing.model = model;
6996
6992
  await backupCurrent(homeFn);
6997
- await atomicWrite8(configPath2, JSON.stringify(existing, null, 2));
6993
+ await atomicWrite8(configPath2, JSON.stringify(existing, null, 2), { mode: 384 });
6998
6994
  try {
6999
6995
  await appendHistory(
7000
6996
  oldCfg,
@@ -8424,7 +8420,7 @@ async function readKeyInput(deps, intent) {
8424
8420
  async function loadProviders(deps) {
8425
8421
  let raw;
8426
8422
  try {
8427
- raw = await fsp2.readFile(deps.globalConfigPath, "utf8");
8423
+ raw = await fsp3.readFile(deps.globalConfigPath, "utf8");
8428
8424
  } catch (err) {
8429
8425
  if (err.code !== "ENOENT") {
8430
8426
  deps.renderer.writeWarning(
@@ -8449,7 +8445,7 @@ async function mutateProviders(deps, mutator) {
8449
8445
  let raw;
8450
8446
  let fileExists = true;
8451
8447
  try {
8452
- raw = await fsp2.readFile(deps.globalConfigPath, "utf8");
8448
+ raw = await fsp3.readFile(deps.globalConfigPath, "utf8");
8453
8449
  } catch (err) {
8454
8450
  if (err.code !== "ENOENT") {
8455
8451
  throw new Error(
@@ -8595,7 +8591,7 @@ var diagCmd = async (_args, deps) => {
8595
8591
  ` modelsCache: ${deps.paths.modelsCache}`,
8596
8592
  ` cacheAge: ${isFinite(age) ? `${Math.round(age / 60)}m` : "never"}`,
8597
8593
  ` node: ${process.version}`,
8598
- ` os: ${os7.platform()} ${os7.release()}`,
8594
+ ` os: ${os6.platform()} ${os6.release()}`,
8599
8595
  ` provider: ${cfg.provider ?? "<unset>"}`,
8600
8596
  ` model: ${cfg.model ?? "<unset>"}`,
8601
8597
  ` tools: ${deps.toolRegistry?.list().length ?? 0}`,
@@ -8663,7 +8659,7 @@ var doctorCmd = async (_args, deps) => {
8663
8659
  });
8664
8660
  }
8665
8661
  try {
8666
- await fsp2.access(deps.paths.secretsKey);
8662
+ await fsp3.access(deps.paths.secretsKey);
8667
8663
  checks.push({ name: "secret vault", status: "ok", detail: deps.paths.secretsKey });
8668
8664
  } catch {
8669
8665
  checks.push({
@@ -8673,10 +8669,10 @@ var doctorCmd = async (_args, deps) => {
8673
8669
  });
8674
8670
  }
8675
8671
  try {
8676
- await fsp2.mkdir(deps.paths.projectSessions, { recursive: true });
8672
+ await fsp3.mkdir(deps.paths.projectSessions, { recursive: true });
8677
8673
  const probe = path25.join(deps.paths.projectSessions, `.probe-${Date.now()}`);
8678
- await fsp2.writeFile(probe, "");
8679
- await fsp2.unlink(probe);
8674
+ await fsp3.writeFile(probe, "");
8675
+ await fsp3.unlink(probe);
8680
8676
  checks.push({ name: "sessions writable", status: "ok", detail: deps.paths.projectSessions });
8681
8677
  } catch (err) {
8682
8678
  checks.push({
@@ -8777,8 +8773,8 @@ var exportCmd = async (args, deps) => {
8777
8773
  return 1;
8778
8774
  }
8779
8775
  if (output) {
8780
- await fsp2.mkdir(path25.dirname(path25.resolve(deps.cwd, output)), { recursive: true });
8781
- await fsp2.writeFile(path25.resolve(deps.cwd, output), rendered, "utf8");
8776
+ await fsp3.mkdir(path25.dirname(path25.resolve(deps.cwd, output)), { recursive: true });
8777
+ await fsp3.writeFile(path25.resolve(deps.cwd, output), rendered, "utf8");
8782
8778
  deps.renderer.write(`Wrote ${rendered.length} bytes to ${output}
8783
8779
  `);
8784
8780
  } else {
@@ -8845,13 +8841,13 @@ var initCmd = async (_args, deps) => {
8845
8841
  } else {
8846
8842
  deps.renderer.writeInfo(`Found API key in env (${provider.envVars.join(" / ")}).`);
8847
8843
  }
8848
- await fsp2.mkdir(deps.paths.globalRoot, { recursive: true });
8844
+ await fsp3.mkdir(deps.paths.globalRoot, { recursive: true });
8849
8845
  const config = { version: 1, provider: providerId, model: modelId };
8850
8846
  if (apiKey) config.apiKey = apiKey;
8851
8847
  const vault = new DefaultSecretVault$1({ keyFile: deps.paths.secretsKey });
8852
8848
  const encrypted = encryptConfigSecrets(config, vault);
8853
- await atomicWrite(deps.paths.globalConfig, JSON.stringify(encrypted, null, 2));
8854
- await fsp2.mkdir(path25.join(deps.projectRoot, ".wrongstack"), { recursive: true });
8849
+ await atomicWrite(deps.paths.globalConfig, JSON.stringify(encrypted, null, 2), { mode: 384 });
8850
+ await fsp3.mkdir(path25.join(deps.projectRoot, ".wrongstack"), { recursive: true });
8855
8851
  const agentsFile = path25.join(deps.projectRoot, ".wrongstack", "AGENTS.md");
8856
8852
  const projectFacts = await detectProjectFacts(deps.projectRoot);
8857
8853
  await atomicWrite(agentsFile, renderAgentsTemplate(projectFacts));
@@ -8926,7 +8922,7 @@ async function addMcpServer(args, deps) {
8926
8922
  serverCfg.enabled = enable;
8927
8923
  let existing = {};
8928
8924
  try {
8929
- existing = JSON.parse(await fsp2.readFile(deps.paths.globalConfig, "utf8"));
8925
+ existing = JSON.parse(await fsp3.readFile(deps.paths.globalConfig, "utf8"));
8930
8926
  } catch {
8931
8927
  }
8932
8928
  const mcpServers = existing.mcpServers ?? {};
@@ -8935,7 +8931,7 @@ async function addMcpServer(args, deps) {
8935
8931
  `);
8936
8932
  mcpServers[name] = serverCfg;
8937
8933
  existing.mcpServers = mcpServers;
8938
- await atomicWrite(deps.paths.globalConfig, JSON.stringify(existing, null, 2));
8934
+ await atomicWrite(deps.paths.globalConfig, JSON.stringify(existing, null, 2), { mode: 384 });
8939
8935
  const verb = enable ? "Enabled" : "Added (disabled \u2014 set enabled:true to activate)";
8940
8936
  deps.renderer.writeInfo(
8941
8937
  `${verb} "${name}" (${serverCfg.transport}). Config written to ${deps.paths.globalConfig}.
@@ -8946,7 +8942,7 @@ async function addMcpServer(args, deps) {
8946
8942
  async function removeMcpServer(name, deps) {
8947
8943
  let existing = {};
8948
8944
  try {
8949
- existing = JSON.parse(await fsp2.readFile(deps.paths.globalConfig, "utf8"));
8945
+ existing = JSON.parse(await fsp3.readFile(deps.paths.globalConfig, "utf8"));
8950
8946
  } catch {
8951
8947
  deps.renderer.writeError("No config file found.\n");
8952
8948
  return 1;
@@ -8959,7 +8955,7 @@ async function removeMcpServer(name, deps) {
8959
8955
  }
8960
8956
  delete mcpServers[name];
8961
8957
  existing.mcpServers = mcpServers;
8962
- await atomicWrite(deps.paths.globalConfig, JSON.stringify(existing, null, 2));
8958
+ await atomicWrite(deps.paths.globalConfig, JSON.stringify(existing, null, 2), { mode: 384 });
8963
8959
  deps.renderer.writeInfo(`Removed "${name}" from config.
8964
8960
  `);
8965
8961
  return 0;
@@ -9067,7 +9063,7 @@ function renderConfiguredPlugins(config) {
9067
9063
  }
9068
9064
  async function readConfig2(file) {
9069
9065
  try {
9070
- return JSON.parse(await fsp2.readFile(file, "utf8"));
9066
+ return JSON.parse(await fsp3.readFile(file, "utf8"));
9071
9067
  } catch {
9072
9068
  return {};
9073
9069
  }
@@ -9097,7 +9093,7 @@ async function upsertPlugin(spec, opts, deps, verb) {
9097
9093
  };
9098
9094
  existing.plugins = plugins;
9099
9095
  existing.features = features;
9100
- await atomicWrite(deps.configPath, JSON.stringify(existing, null, 2));
9096
+ await atomicWrite(deps.configPath, JSON.stringify(existing, null, 2), { mode: 384 });
9101
9097
  return {
9102
9098
  code: 0,
9103
9099
  level: "info",
@@ -9114,7 +9110,7 @@ async function removePlugin(spec, deps) {
9114
9110
  return errorResult(`Plugin "${spec}" not in config.`);
9115
9111
  }
9116
9112
  existing.plugins = next;
9117
- await atomicWrite(deps.configPath, JSON.stringify(existing, null, 2));
9113
+ await atomicWrite(deps.configPath, JSON.stringify(existing, null, 2), { mode: 384 });
9118
9114
  return {
9119
9115
  code: 0,
9120
9116
  level: "info",
@@ -9160,7 +9156,7 @@ var usageCmd = async (_args, deps) => {
9160
9156
  var projectsCmd = async (_args, deps) => {
9161
9157
  const projectsRoot = path25.join(deps.paths.globalRoot, "projects");
9162
9158
  try {
9163
- const entries = await fsp2.readdir(projectsRoot);
9159
+ const entries = await fsp3.readdir(projectsRoot);
9164
9160
  if (entries.length === 0) {
9165
9161
  deps.renderer.write("No projects tracked.\n");
9166
9162
  return 0;
@@ -9168,7 +9164,7 @@ var projectsCmd = async (_args, deps) => {
9168
9164
  for (const hash of entries) {
9169
9165
  try {
9170
9166
  const meta = JSON.parse(
9171
- await fsp2.readFile(path25.join(projectsRoot, hash, "meta.json"), "utf8")
9167
+ await fsp3.readFile(path25.join(projectsRoot, hash, "meta.json"), "utf8")
9172
9168
  );
9173
9169
  deps.renderer.write(
9174
9170
  ` ${color.dim(hash)} ${color.dim(meta.lastSeen ?? "")} ${meta.root ?? "?"}
@@ -9319,7 +9315,7 @@ var sessionsFleetCmd = async (args, deps) => {
9319
9315
  async function listFleetRuns(deps) {
9320
9316
  let entries = [];
9321
9317
  try {
9322
- entries = await fsp2.readdir(deps.paths.projectSessions);
9318
+ entries = await fsp3.readdir(deps.paths.projectSessions);
9323
9319
  } catch {
9324
9320
  deps.renderer.writeError(`Cannot read projectSessions: ${deps.paths.projectSessions}
9325
9321
  `);
@@ -9330,7 +9326,7 @@ async function listFleetRuns(deps) {
9330
9326
  const runDir = path25.join(deps.paths.projectSessions, id);
9331
9327
  let stat3;
9332
9328
  try {
9333
- stat3 = await fsp2.stat(runDir);
9329
+ stat3 = await fsp3.stat(runDir);
9334
9330
  } catch {
9335
9331
  continue;
9336
9332
  }
@@ -9340,18 +9336,18 @@ async function listFleetRuns(deps) {
9340
9336
  let subagentCount = 0;
9341
9337
  let subagentsDir;
9342
9338
  try {
9343
- await fsp2.access(path25.join(runDir, "fleet.json"));
9339
+ await fsp3.access(path25.join(runDir, "fleet.json"));
9344
9340
  manifest = true;
9345
9341
  } catch {
9346
9342
  }
9347
9343
  try {
9348
- await fsp2.access(path25.join(runDir, "checkpoint.json"));
9344
+ await fsp3.access(path25.join(runDir, "checkpoint.json"));
9349
9345
  checkpoint = true;
9350
9346
  } catch {
9351
9347
  }
9352
9348
  try {
9353
9349
  subagentsDir = path25.join(runDir, "subagents");
9354
- const files = await fsp2.readdir(subagentsDir);
9350
+ const files = await fsp3.readdir(subagentsDir);
9355
9351
  subagentCount = files.filter((f) => f.endsWith(".jsonl")).length;
9356
9352
  } catch {
9357
9353
  }
@@ -9382,7 +9378,7 @@ async function showFleetRun(runId, deps) {
9382
9378
  const runDir = path25.join(deps.paths.projectSessions, runId);
9383
9379
  let stat3;
9384
9380
  try {
9385
- stat3 = await fsp2.stat(runDir);
9381
+ stat3 = await fsp3.stat(runDir);
9386
9382
  } catch {
9387
9383
  deps.renderer.writeError(`Fleet run not found: ${runId}
9388
9384
  `);
@@ -9399,7 +9395,7 @@ Fleet Run: ${runId}
9399
9395
  const manifestPath = path25.join(runDir, "fleet.json");
9400
9396
  let manifestData = null;
9401
9397
  try {
9402
- manifestData = await fsp2.readFile(manifestPath, "utf8");
9398
+ manifestData = await fsp3.readFile(manifestPath, "utf8");
9403
9399
  const manifest = JSON.parse(manifestData);
9404
9400
  const subagents = manifest.subagents ?? [];
9405
9401
  const tasks = manifest.tasks ?? [];
@@ -9415,12 +9411,12 @@ Fleet Run: ${runId}
9415
9411
  const checkpointPath = path25.join(runDir, "checkpoint.json");
9416
9412
  let checkpointData = null;
9417
9413
  try {
9418
- checkpointData = await fsp2.readFile(checkpointPath, "utf8");
9414
+ checkpointData = await fsp3.readFile(checkpointPath, "utf8");
9419
9415
  const snap = JSON.parse(checkpointData);
9420
9416
  const lockPath = `${checkpointPath}.lock`;
9421
9417
  let lockStatus = color.dim("\u25CB no lock");
9422
9418
  try {
9423
- const lockRaw = await fsp2.readFile(lockPath, "utf8");
9419
+ const lockRaw = await fsp3.readFile(lockPath, "utf8");
9424
9420
  const lock = JSON.parse(lockRaw);
9425
9421
  lockStatus = `${color.yellow("\u25B8")} lock held by pid ${lock.pid} on ${lock.hostname} (started ${lock.startedAt})`;
9426
9422
  } catch {
@@ -9462,7 +9458,7 @@ Fleet Run: ${runId}
9462
9458
  const subagentsDir = path25.join(runDir, "subagents");
9463
9459
  let subagentFiles = [];
9464
9460
  try {
9465
- subagentFiles = await fsp2.readdir(subagentsDir);
9461
+ subagentFiles = await fsp3.readdir(subagentsDir);
9466
9462
  subagentFiles = subagentFiles.filter((f) => f.endsWith(".jsonl"));
9467
9463
  } catch {
9468
9464
  }
@@ -9474,7 +9470,7 @@ Fleet Run: ${runId}
9474
9470
  const filePath = path25.join(subagentsDir, f);
9475
9471
  let size;
9476
9472
  try {
9477
- const s = await fsp2.stat(filePath);
9473
+ const s = await fsp3.stat(filePath);
9478
9474
  size = s.size;
9479
9475
  } catch {
9480
9476
  size = 0;
@@ -9490,7 +9486,7 @@ Fleet Run: ${runId}
9490
9486
  }
9491
9487
  const sharedDir = path25.join(runDir, "shared");
9492
9488
  try {
9493
- const files = await fsp2.readdir(sharedDir);
9489
+ const files = await fsp3.readdir(sharedDir);
9494
9490
  deps.renderer.write(`
9495
9491
  Shared scratchpad: ${files.length} file(s)
9496
9492
  `);
@@ -9787,7 +9783,7 @@ var skillsCmd = async (_args, deps) => {
9787
9783
  };
9788
9784
  var versionCmd = async (_args, deps) => {
9789
9785
  deps.renderer.write(
9790
- `WrongStack ${CLI_VERSION} (apiVersion ${API_VERSION}, node ${process.version}, ${os7.platform()})
9786
+ `WrongStack ${CLI_VERSION} (apiVersion ${API_VERSION}, node ${process.version}, ${os6.platform()})
9791
9787
  `
9792
9788
  );
9793
9789
  return 0;
@@ -10977,6 +10973,43 @@ async function execute(deps) {
10977
10973
  const banneredFamily = savedProviderCfg?.family ?? resolvedProvider?.family;
10978
10974
  const banneredKey = savedProviderCfg?.apiKey ?? config.apiKey ?? (resolvedProvider?.envVars ?? savedProviderCfg?.envVars ?? []).map((v) => process.env[v]).find((v) => !!v);
10979
10975
  const banneredKeyTail = banneredKey && banneredKey.length >= 3 ? banneredKey.slice(-3) : void 0;
10976
+ const autoPhaseHandlers = /* @__PURE__ */ new Map();
10977
+ const subscribeAutoPhase = (handler) => {
10978
+ const registrations = [];
10979
+ const autoPhaseEvents = [
10980
+ "phase.started",
10981
+ "phase.completed",
10982
+ "phase.failed",
10983
+ "phase.statusChange",
10984
+ "phase.taskCompleted",
10985
+ "phase.taskFailed",
10986
+ "phase.taskRetrying",
10987
+ "autonomous.tick",
10988
+ "graph.completed",
10989
+ "graph.failed",
10990
+ "agent.assigned",
10991
+ "agent.released",
10992
+ // Git-worktree isolation lifecycle → TUI worktree panel/monitor.
10993
+ "worktree.allocated",
10994
+ "worktree.committed",
10995
+ "worktree.merged",
10996
+ "worktree.conflict",
10997
+ "worktree.released",
10998
+ "worktree.failed"
10999
+ ];
11000
+ const onUntyped = events.on.bind(events);
11001
+ const offUntyped = events.off.bind(events);
11002
+ for (const ev of autoPhaseEvents) {
11003
+ const h = (p) => handler(ev, p);
11004
+ autoPhaseHandlers.set(ev, h);
11005
+ onUntyped(ev, h);
11006
+ registrations.push(() => offUntyped(ev, h));
11007
+ }
11008
+ return () => {
11009
+ for (const unregister of registrations) unregister();
11010
+ autoPhaseHandlers.clear();
11011
+ };
11012
+ };
10980
11013
  try {
10981
11014
  code = await runTui({
10982
11015
  agent,
@@ -10995,6 +11028,7 @@ async function execute(deps) {
10995
11028
  getEternalEngine,
10996
11029
  subscribeEternalIteration,
10997
11030
  subscribeEternalStage,
11031
+ subscribeAutoPhase,
10998
11032
  appVersion: CLI_VERSION,
10999
11033
  provider: config.provider,
11000
11034
  family: banneredFamily,
@@ -11279,8 +11313,9 @@ var MultiAgentHost = class {
11279
11313
  return async (subCfg) => {
11280
11314
  const events = new EventBus();
11281
11315
  const provider = await this.buildSubagentProvider(config, subCfg.provider);
11316
+ const subCwd = subCfg.cwd ?? this.deps.cwd;
11282
11317
  const baseSystem = await this.deps.systemPromptBuilder.build({
11283
- cwd: this.deps.cwd,
11318
+ cwd: subCwd,
11284
11319
  projectRoot: this.deps.projectRoot,
11285
11320
  tools: this.filterTools(subCfg.tools),
11286
11321
  model: subCfg.model ?? config.model,
@@ -11316,7 +11351,7 @@ var MultiAgentHost = class {
11316
11351
  session: subSession,
11317
11352
  signal: new AbortController().signal,
11318
11353
  tokenCounter: this.deps.tokenCounter,
11319
- cwd: this.deps.cwd,
11354
+ cwd: subCwd,
11320
11355
  projectRoot: this.deps.projectRoot,
11321
11356
  model: subCfg.model ?? config.model,
11322
11357
  tools: this.filterTools(subCfg.tools)
@@ -11884,6 +11919,218 @@ function samplePaths(set) {
11884
11919
  if (arr.length <= 2) return arr.join(", ");
11885
11920
  return `${arr[0]}, \u2026 (+${arr.length - 1} more)`;
11886
11921
  }
11922
+ var WORKTREE_PHASE_CONCURRENCY = 4;
11923
+ function gitText(args, cwd) {
11924
+ return new Promise((resolve4) => {
11925
+ let out = "";
11926
+ const child = spawn("git", args, { cwd, env: buildChildEnv(), stdio: ["ignore", "pipe", "pipe"] });
11927
+ child.stdout?.on("data", (c) => {
11928
+ out += c.toString();
11929
+ });
11930
+ child.stderr?.on("data", (c) => {
11931
+ out += c.toString();
11932
+ });
11933
+ child.on("error", () => resolve4({ code: 1, out }));
11934
+ child.on("close", (code) => resolve4({ code: code ?? 1, out: out.trim() }));
11935
+ });
11936
+ }
11937
+ async function isGitRepo2(cwd) {
11938
+ const { code, out } = await gitText(["rev-parse", "--is-inside-work-tree"], cwd);
11939
+ return code === 0 && out.trim() === "true";
11940
+ }
11941
+ function createAutoPhaseHost(deps) {
11942
+ const store = new PhaseStore({ baseDir: deps.storeDir });
11943
+ let active = null;
11944
+ const log = deps.log ?? (() => {
11945
+ });
11946
+ async function runOnce(prompt, label, signal, cwd) {
11947
+ const factory = deps.multiAgentHost.makeSubagentFactory(deps.getConfig());
11948
+ const built = await factory({ name: label, cwd });
11949
+ try {
11950
+ const result = await built.agent.run(prompt, { signal });
11951
+ if (result.status !== "done") {
11952
+ throw new Error(result.error?.message ?? `subagent ended with status "${result.status}"`);
11953
+ }
11954
+ return result.finalText ?? "";
11955
+ } finally {
11956
+ await built.dispose?.();
11957
+ }
11958
+ }
11959
+ function buildTaskPrompt(task, phaseName, goal) {
11960
+ return [
11961
+ `You are executing one task inside an autonomous, phase-based build.`,
11962
+ `Overall goal: ${goal}`,
11963
+ `Current phase: ${phaseName}`,
11964
+ "",
11965
+ `TASK: ${task.title}`,
11966
+ task.description ? `Details: ${task.description}` : "",
11967
+ `Type: ${task.type} \xB7 Priority: ${task.priority}`,
11968
+ "",
11969
+ `Do the work now using your tools (read, edit, write, bash, \u2026). Make the`,
11970
+ `change real \u2014 do not just describe it. When finished, end with a one-line`,
11971
+ `summary of what you changed. If the task is impossible or already done,`,
11972
+ `say so explicitly.`
11973
+ ].filter(Boolean).join("\n");
11974
+ }
11975
+ async function persist(graph) {
11976
+ try {
11977
+ await store.save(graph);
11978
+ } catch (err) {
11979
+ log(`\u26A0 AutoPhase save failed: ${err instanceof Error ? err.message : String(err)}`);
11980
+ }
11981
+ }
11982
+ return {
11983
+ async onAutoPhaseStart({ goal, projectContext }) {
11984
+ if (active?.orchestrator.isRunning()) {
11985
+ return { ok: false, error: "An AutoPhase run is already in progress. Use /autophase stop first." };
11986
+ }
11987
+ const abort = new AbortController();
11988
+ log(`\u{1F9E0} Planning phases for: ${goal}`);
11989
+ let phases;
11990
+ try {
11991
+ const planner = new AutoPhasePlanner({
11992
+ goal,
11993
+ projectContext,
11994
+ runOnce: (p) => runOnce(p, "autophase-planner", abort.signal)
11995
+ });
11996
+ const result = await planner.plan();
11997
+ if (result.parseFailed || result.phases.length === 0) {
11998
+ return { ok: false, error: "The planner did not produce a usable phase plan. Try a more specific goal." };
11999
+ }
12000
+ phases = result.phases;
12001
+ } catch (err) {
12002
+ return { ok: false, error: `Planning failed: ${err instanceof Error ? err.message : String(err)}` };
12003
+ }
12004
+ const todoCount = phases.reduce((n, p) => n + (p.taskTemplates?.length ?? 0), 0);
12005
+ log(`\u{1F4CB} Plan ready: ${phases.length} phases, ${todoCount} todos.`);
12006
+ const graph = await new PhaseGraphBuilder({
12007
+ title: goal,
12008
+ phases,
12009
+ autonomous: true
12010
+ }).build();
12011
+ await persist(graph);
12012
+ const worktreesEnabled = deps.worktrees !== false && process.env["WRONGSTACK_AUTOPHASE_WORKTREES"] !== "0";
12013
+ let worktrees;
12014
+ if (worktreesEnabled && await isGitRepo2(deps.projectRoot)) {
12015
+ worktrees = new WorktreeManager({ projectRoot: deps.projectRoot, events: deps.events });
12016
+ log(`\u{1F33F} Worktree isolation on \u2014 up to ${deps.maxConcurrentPhases ?? WORKTREE_PHASE_CONCURRENCY} phases run in parallel.`);
12017
+ }
12018
+ const orchestrator = new PhaseOrchestrator({
12019
+ graph,
12020
+ ctx: {
12021
+ executeTask: async (task, phaseId, env) => {
12022
+ const phase = graph.phases.get(phaseId);
12023
+ const phaseName = phase?.name ?? phaseId;
12024
+ return runOnce(
12025
+ buildTaskPrompt(task, phaseName, goal),
12026
+ `autophase-${phaseName}-${task.title}`.slice(0, 48),
12027
+ abort.signal,
12028
+ env?.cwd
12029
+ );
12030
+ },
12031
+ onPhaseComplete: (phase) => {
12032
+ log(`\u2705 Phase completed: ${phase.name}`);
12033
+ void persist(graph);
12034
+ },
12035
+ onPhaseFail: (phase, error) => {
12036
+ log(`\u274C Phase failed: ${phase.name} \u2014 ${error.message}`);
12037
+ void persist(graph);
12038
+ }
12039
+ },
12040
+ events: deps.events,
12041
+ worktrees,
12042
+ autonomous: true,
12043
+ // With isolation, parallelizable phases run concurrently; without it,
12044
+ // stay strictly sequential to protect the shared working tree.
12045
+ maxConcurrentPhases: worktrees ? deps.maxConcurrentPhases ?? WORKTREE_PHASE_CONCURRENCY : 1,
12046
+ // Sequential within a phase: each todo is a full-tool agent and todos in
12047
+ // a phase typically build on one another (they share the phase worktree).
12048
+ maxConcurrentTasks: 1
12049
+ });
12050
+ const onUntyped = deps.events.on;
12051
+ const offUntyped = deps.events.off;
12052
+ const onDone = () => {
12053
+ log(`\u{1F389} AutoPhase complete: ${graph.title}`);
12054
+ void persist(graph);
12055
+ };
12056
+ const onFailed = () => void persist(graph);
12057
+ onUntyped("graph.completed", onDone);
12058
+ onUntyped("graph.failed", onFailed);
12059
+ const unsubscribe = () => {
12060
+ offUntyped("graph.completed", onDone);
12061
+ offUntyped("graph.failed", onFailed);
12062
+ };
12063
+ active = { graph, orchestrator, abort, unsubscribe };
12064
+ void orchestrator.start().catch((err) => {
12065
+ log(`\u{1F4A5} AutoPhase aborted: ${err instanceof Error ? err.message : String(err)}`);
12066
+ });
12067
+ return { ok: true, graph };
12068
+ },
12069
+ onAutoPhasePause() {
12070
+ active?.orchestrator.pause();
12071
+ },
12072
+ onAutoPhaseResume() {
12073
+ active?.orchestrator.resume();
12074
+ },
12075
+ onAutoPhaseStop() {
12076
+ if (!active) return;
12077
+ active.abort.abort();
12078
+ active.orchestrator.stop();
12079
+ active.unsubscribe();
12080
+ void persist(active.graph);
12081
+ active = null;
12082
+ },
12083
+ getAutoPhaseRunner() {
12084
+ if (!active) return null;
12085
+ const a = active;
12086
+ return {
12087
+ graph: a.graph,
12088
+ getProgress: () => a.orchestrator.getProgress(),
12089
+ isRunning: () => a.orchestrator.isRunning()
12090
+ };
12091
+ },
12092
+ async onWorktree(action, target) {
12093
+ const root = deps.projectRoot;
12094
+ if (!await isGitRepo2(root)) return "\u26A0 Not a git repository \u2014 worktrees unavailable.";
12095
+ switch (action) {
12096
+ case "list": {
12097
+ const { out } = await gitText(["worktree", "list"], root);
12098
+ return out || "No worktrees.";
12099
+ }
12100
+ case "prune": {
12101
+ await gitText(["worktree", "prune"], root);
12102
+ const { out } = await gitText(["worktree", "list"], root);
12103
+ return `Pruned stale worktree entries.
12104
+ ${out}`;
12105
+ }
12106
+ case "merge": {
12107
+ if (!target) return "Usage: /worktree merge <branch>";
12108
+ if (target.startsWith("-")) return `Refusing unsafe branch name: ${target}`;
12109
+ const base = (await gitText(["rev-parse", "--abbrev-ref", "HEAD"], root)).out || "HEAD";
12110
+ await gitText(["merge", "--squash", target], root);
12111
+ const commit = await gitText(["commit", "-m", `merge ${target} (squash)`], root);
12112
+ if (commit.code !== 0 && !/nothing to commit/i.test(commit.out)) {
12113
+ await gitText(["reset", "--hard", "HEAD"], root);
12114
+ return `\u26A0 Merge of "${target}" into ${base} hit conflicts and was rolled back.
12115
+ ${commit.out}`;
12116
+ }
12117
+ return `\u2713 Merged "${target}" into ${base} (squash).`;
12118
+ }
12119
+ case "clean": {
12120
+ const list = (await gitText(["worktree", "list", "--porcelain"], root)).out;
12121
+ const dirs = list.split("\n").filter((l) => l.startsWith("worktree ")).map((l) => l.slice("worktree ".length)).filter((d) => d.includes(".wrongstack") && d.includes("worktrees"));
12122
+ for (const d of dirs) await gitText(["worktree", "remove", "--force", d], root);
12123
+ await gitText(["worktree", "prune"], root);
12124
+ const branches = (await gitText(["branch", "--list", "wstack/ap/*"], root)).out.split("\n").map((b) => b.replace(/^[*+]?\s*/, "").trim()).filter(Boolean);
12125
+ for (const b of branches) await gitText(["branch", "-D", b], root);
12126
+ return `\u{1F9F9} Removed ${dirs.length} worktree(s) and ${branches.length} branch(es).`;
12127
+ }
12128
+ default:
12129
+ return `Unknown worktree action: ${action}`;
12130
+ }
12131
+ }
12132
+ };
12133
+ }
11887
12134
  var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
11888
12135
  var FILLED2 = "\u2588";
11889
12136
  var EMPTY2 = "\u2591";
@@ -12051,7 +12298,7 @@ function setupMetrics(params) {
12051
12298
  name: "session-store",
12052
12299
  check: async () => {
12053
12300
  try {
12054
- await fsp2.access(wpaths.projectSessions);
12301
+ await fsp3.access(wpaths.projectSessions);
12055
12302
  return { status: "healthy" };
12056
12303
  } catch (e) {
12057
12304
  return { status: "unhealthy", detail: e instanceof Error ? e.message : "access denied" };
@@ -12707,6 +12954,15 @@ async function main(argv) {
12707
12954
  this.visible = visible;
12708
12955
  }
12709
12956
  };
12957
+ const autoPhaseHost = createAutoPhaseHost({
12958
+ multiAgentHost,
12959
+ getConfig: () => config,
12960
+ events,
12961
+ storeDir: wpaths.projectAutophase,
12962
+ projectRoot,
12963
+ log: (line) => renderer.write(`${line}
12964
+ `)
12965
+ });
12710
12966
  const slashCmds = buildBuiltinSlashCommands({
12711
12967
  registry: slashRegistry,
12712
12968
  toolRegistry,
@@ -12716,6 +12972,7 @@ async function main(argv) {
12716
12972
  skillLoader,
12717
12973
  tokenCounter,
12718
12974
  renderer,
12975
+ events,
12719
12976
  memoryStore,
12720
12977
  context,
12721
12978
  cwd,
@@ -12944,7 +13201,7 @@ async function main(argv) {
12944
13201
  const subagentsRoot = path25.join(fleetRootForPromotion, "subagents");
12945
13202
  let runDirs;
12946
13203
  try {
12947
- runDirs = await fsp2.readdir(subagentsRoot);
13204
+ runDirs = await fsp3.readdir(subagentsRoot);
12948
13205
  } catch {
12949
13206
  return "No fleet transcripts on disk \u2014 no subagents have been spawned for this session.";
12950
13207
  }
@@ -12953,7 +13210,7 @@ async function main(argv) {
12953
13210
  const runDir = path25.join(subagentsRoot, runId);
12954
13211
  let files;
12955
13212
  try {
12956
- files = await fsp2.readdir(runDir);
13213
+ files = await fsp3.readdir(runDir);
12957
13214
  } catch {
12958
13215
  continue;
12959
13216
  }
@@ -12961,7 +13218,7 @@ async function main(argv) {
12961
13218
  if (!f.endsWith(".jsonl")) continue;
12962
13219
  const full = path25.join(runDir, f);
12963
13220
  try {
12964
- const stat3 = await fsp2.stat(full);
13221
+ const stat3 = await fsp3.stat(full);
12965
13222
  found.push({
12966
13223
  runId,
12967
13224
  subagentId: f.replace(/\.jsonl$/, ""),
@@ -13000,7 +13257,7 @@ async function main(argv) {
13000
13257
  ].join("\n");
13001
13258
  }
13002
13259
  const t = matches[0];
13003
- const raw = await fsp2.readFile(t.file, "utf8");
13260
+ const raw = await fsp3.readFile(t.file, "utf8");
13004
13261
  if (mode === "raw") return raw;
13005
13262
  const lines = raw.split("\n").filter((l) => l.trim());
13006
13263
  const counts = {};
@@ -13226,10 +13483,10 @@ Restart WrongStack to load or unload plugin code in this session.`;
13226
13483
  void mcpRegistry.stopAll();
13227
13484
  },
13228
13485
  onBeforeExit: async () => {
13229
- const { spawn: spawn3 } = await import('child_process');
13486
+ const { spawn: spawn4 } = await import('child_process');
13230
13487
  const cwd2 = projectRoot;
13231
13488
  const statusResult = await new Promise((resolve4, reject) => {
13232
- const child = spawn3("git", ["status", "--porcelain"], { cwd: cwd2, stdio: ["ignore", "pipe", "pipe"] });
13489
+ const child = spawn4("git", ["status", "--porcelain"], { cwd: cwd2, stdio: ["ignore", "pipe", "pipe"] });
13233
13490
  let stdout = "";
13234
13491
  child.stdout?.on("data", (d) => {
13235
13492
  stdout += d;
@@ -13332,7 +13589,13 @@ Restart WrongStack to load or unload plugin code in this session.`;
13332
13589
  onSddParallelStop: () => {
13333
13590
  const run = globalThis.__sddParallelRun;
13334
13591
  run?.stop();
13335
- }
13592
+ },
13593
+ onAutoPhaseStart: autoPhaseHost.onAutoPhaseStart,
13594
+ onAutoPhasePause: autoPhaseHost.onAutoPhasePause,
13595
+ onAutoPhaseResume: autoPhaseHost.onAutoPhaseResume,
13596
+ onAutoPhaseStop: autoPhaseHost.onAutoPhaseStop,
13597
+ getAutoPhaseRunner: autoPhaseHost.getAutoPhaseRunner,
13598
+ onWorktree: autoPhaseHost.onWorktree
13336
13599
  });
13337
13600
  for (const cmd of slashCmds) slashRegistry.register(cmd);
13338
13601
  const eternalFlag = typeof flags["eternal"] === "string" ? flags["eternal"].trim() : "";