@wrongstack/cli 0.8.5 → 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
@@ -3,7 +3,7 @@ import * as path25 from 'path';
3
3
  import { join } from 'path';
4
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, 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';
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
8
  import * as os6 from 'os';
9
9
  import os6__default from 'os';
@@ -6141,6 +6141,51 @@ function buildAutoPhaseCommand(opts) {
6141
6141
  };
6142
6142
  }
6143
6143
 
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." };
6165
+ }
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
+ };
6184
+ }
6185
+ }
6186
+ };
6187
+ }
6188
+
6144
6189
  // src/slash-commands/index.ts
6145
6190
  function buildBuiltinSlashCommands(opts) {
6146
6191
  return [
@@ -6183,6 +6228,7 @@ function buildBuiltinSlashCommands(opts) {
6183
6228
  buildSecurityCommand(opts),
6184
6229
  buildFixCommand(opts),
6185
6230
  buildAutoPhaseCommand(opts),
6231
+ buildWorktreeCommand(opts),
6186
6232
  buildStatuslineCommand({
6187
6233
  cwd: opts.cwd,
6188
6234
  hiddenItems: opts.statuslineHiddenItems ?? [],
@@ -6291,9 +6337,9 @@ async function runProjectCheck(opts) {
6291
6337
  }
6292
6338
  if (answer2 === "y" || answer2 === "yes") {
6293
6339
  try {
6294
- const { spawn: spawn3 } = await import('child_process');
6340
+ const { spawn: spawn4 } = await import('child_process');
6295
6341
  await new Promise((resolve4, reject) => {
6296
- const child = spawn3("git", ["init"], { cwd: projectRoot });
6342
+ const child = spawn4("git", ["init"], { cwd: projectRoot });
6297
6343
  child.on("error", reject);
6298
6344
  child.on("close", (code) => code === 0 ? resolve4() : reject(new Error(`git init failed with ${code}`)));
6299
6345
  });
@@ -10942,7 +10988,14 @@ async function execute(deps) {
10942
10988
  "graph.completed",
10943
10989
  "graph.failed",
10944
10990
  "agent.assigned",
10945
- "agent.released"
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"
10946
10999
  ];
10947
11000
  const onUntyped = events.on.bind(events);
10948
11001
  const offUntyped = events.off.bind(events);
@@ -11260,8 +11313,9 @@ var MultiAgentHost = class {
11260
11313
  return async (subCfg) => {
11261
11314
  const events = new EventBus();
11262
11315
  const provider = await this.buildSubagentProvider(config, subCfg.provider);
11316
+ const subCwd = subCfg.cwd ?? this.deps.cwd;
11263
11317
  const baseSystem = await this.deps.systemPromptBuilder.build({
11264
- cwd: this.deps.cwd,
11318
+ cwd: subCwd,
11265
11319
  projectRoot: this.deps.projectRoot,
11266
11320
  tools: this.filterTools(subCfg.tools),
11267
11321
  model: subCfg.model ?? config.model,
@@ -11297,7 +11351,7 @@ var MultiAgentHost = class {
11297
11351
  session: subSession,
11298
11352
  signal: new AbortController().signal,
11299
11353
  tokenCounter: this.deps.tokenCounter,
11300
- cwd: this.deps.cwd,
11354
+ cwd: subCwd,
11301
11355
  projectRoot: this.deps.projectRoot,
11302
11356
  model: subCfg.model ?? config.model,
11303
11357
  tools: this.filterTools(subCfg.tools)
@@ -11865,14 +11919,33 @@ function samplePaths(set) {
11865
11919
  if (arr.length <= 2) return arr.join(", ");
11866
11920
  return `${arr[0]}, \u2026 (+${arr.length - 1} more)`;
11867
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
+ }
11868
11941
  function createAutoPhaseHost(deps) {
11869
11942
  const store = new PhaseStore({ baseDir: deps.storeDir });
11870
11943
  let active = null;
11871
11944
  const log = deps.log ?? (() => {
11872
11945
  });
11873
- async function runOnce(prompt, label, signal) {
11946
+ async function runOnce(prompt, label, signal, cwd) {
11874
11947
  const factory = deps.multiAgentHost.makeSubagentFactory(deps.getConfig());
11875
- const built = await factory({ name: label });
11948
+ const built = await factory({ name: label, cwd });
11876
11949
  try {
11877
11950
  const result = await built.agent.run(prompt, { signal });
11878
11951
  if (result.status !== "done") {
@@ -11936,16 +12009,23 @@ function createAutoPhaseHost(deps) {
11936
12009
  autonomous: true
11937
12010
  }).build();
11938
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
+ }
11939
12018
  const orchestrator = new PhaseOrchestrator({
11940
12019
  graph,
11941
12020
  ctx: {
11942
- executeTask: async (task, phaseId) => {
12021
+ executeTask: async (task, phaseId, env) => {
11943
12022
  const phase = graph.phases.get(phaseId);
11944
12023
  const phaseName = phase?.name ?? phaseId;
11945
12024
  return runOnce(
11946
12025
  buildTaskPrompt(task, phaseName, goal),
11947
12026
  `autophase-${phaseName}-${task.title}`.slice(0, 48),
11948
- abort.signal
12027
+ abort.signal,
12028
+ env?.cwd
11949
12029
  );
11950
12030
  },
11951
12031
  onPhaseComplete: (phase) => {
@@ -11958,11 +12038,13 @@ function createAutoPhaseHost(deps) {
11958
12038
  }
11959
12039
  },
11960
12040
  events: deps.events,
12041
+ worktrees,
11961
12042
  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.
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).
11966
12048
  maxConcurrentTasks: 1
11967
12049
  });
11968
12050
  const onUntyped = deps.events.on;
@@ -12006,6 +12088,46 @@ function createAutoPhaseHost(deps) {
12006
12088
  getProgress: () => a.orchestrator.getProgress(),
12007
12089
  isRunning: () => a.orchestrator.isRunning()
12008
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
+ }
12009
12131
  }
12010
12132
  };
12011
12133
  }
@@ -12837,6 +12959,7 @@ async function main(argv) {
12837
12959
  getConfig: () => config,
12838
12960
  events,
12839
12961
  storeDir: wpaths.projectAutophase,
12962
+ projectRoot,
12840
12963
  log: (line) => renderer.write(`${line}
12841
12964
  `)
12842
12965
  });
@@ -13360,10 +13483,10 @@ Restart WrongStack to load or unload plugin code in this session.`;
13360
13483
  void mcpRegistry.stopAll();
13361
13484
  },
13362
13485
  onBeforeExit: async () => {
13363
- const { spawn: spawn3 } = await import('child_process');
13486
+ const { spawn: spawn4 } = await import('child_process');
13364
13487
  const cwd2 = projectRoot;
13365
13488
  const statusResult = await new Promise((resolve4, reject) => {
13366
- 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"] });
13367
13490
  let stdout = "";
13368
13491
  child.stdout?.on("data", (d) => {
13369
13492
  stdout += d;
@@ -13471,7 +13594,8 @@ Restart WrongStack to load or unload plugin code in this session.`;
13471
13594
  onAutoPhasePause: autoPhaseHost.onAutoPhasePause,
13472
13595
  onAutoPhaseResume: autoPhaseHost.onAutoPhaseResume,
13473
13596
  onAutoPhaseStop: autoPhaseHost.onAutoPhaseStop,
13474
- getAutoPhaseRunner: autoPhaseHost.getAutoPhaseRunner
13597
+ getAutoPhaseRunner: autoPhaseHost.getAutoPhaseRunner,
13598
+ onWorktree: autoPhaseHost.onWorktree
13475
13599
  });
13476
13600
  for (const cmd of slashCmds) slashRegistry.register(cmd);
13477
13601
  const eternalFlag = typeof flags["eternal"] === "string" ? flags["eternal"].trim() : "";