@stackmemoryai/stackmemory 1.10.5 → 1.14.0

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.
Files changed (225) hide show
  1. package/LICENSE +131 -64
  2. package/README.md +107 -24
  3. package/bin/claude-sm +16 -1
  4. package/bin/claude-smd +16 -1
  5. package/bin/codex-smd +16 -1
  6. package/bin/gemini-sm +16 -1
  7. package/bin/hermes-sm +21 -0
  8. package/bin/hermes-smd +21 -0
  9. package/bin/opencode-sm +16 -1
  10. package/dist/src/cli/claude-sm.js +266 -84
  11. package/dist/src/cli/codex-sm.js +225 -33
  12. package/dist/src/cli/commands/bench.js +209 -2
  13. package/dist/src/cli/commands/brain.js +206 -0
  14. package/dist/src/cli/commands/cache.js +126 -0
  15. package/dist/src/cli/commands/company-os.js +184 -0
  16. package/dist/src/cli/commands/context.js +5 -0
  17. package/dist/src/cli/commands/daemon.js +41 -0
  18. package/dist/src/cli/commands/handoff.js +40 -9
  19. package/dist/src/cli/commands/onboard.js +70 -3
  20. package/dist/src/cli/commands/operator.js +127 -0
  21. package/dist/src/cli/commands/optimize.js +117 -0
  22. package/dist/src/cli/commands/orchestrate.js +232 -5
  23. package/dist/src/cli/commands/orchestrator.js +315 -26
  24. package/dist/src/cli/commands/pack.js +322 -0
  25. package/dist/src/cli/commands/patterns.js +254 -0
  26. package/dist/src/cli/commands/portal.js +161 -0
  27. package/dist/src/cli/commands/scaffold.js +92 -0
  28. package/dist/src/cli/commands/search.js +40 -1
  29. package/dist/src/cli/commands/setup.js +178 -11
  30. package/dist/src/cli/commands/skills.js +10 -1
  31. package/dist/src/cli/commands/state.js +265 -0
  32. package/dist/src/cli/commands/sync.js +253 -0
  33. package/dist/src/cli/commands/tasks.js +130 -1
  34. package/dist/src/cli/commands/vision.js +221 -0
  35. package/dist/src/cli/gemini-sm.js +19 -29
  36. package/dist/src/cli/hermes-sm.js +224 -0
  37. package/dist/src/cli/index.js +105 -39
  38. package/dist/src/cli/opencode-sm.js +38 -21
  39. package/dist/src/cli/utils/determinism-watcher.js +66 -0
  40. package/dist/src/cli/utils/real-cli-bin.js +116 -0
  41. package/dist/src/core/brain/brain-store.js +187 -0
  42. package/dist/src/core/brain/brain-sync.js +193 -0
  43. package/dist/src/core/brain/index.js +78 -0
  44. package/dist/src/core/brain/types.js +10 -0
  45. package/dist/src/core/cache/content-cache.js +238 -0
  46. package/dist/src/{integrations/diffmem → core/cache}/index.js +5 -5
  47. package/dist/src/core/cache/token-estimator.js +39 -0
  48. package/dist/src/core/config/feature-flags.js +2 -6
  49. package/dist/src/core/context/frame-database.js +79 -27
  50. package/dist/src/core/context/recursive-context-manager.js +1 -1
  51. package/dist/src/core/context/rehydration.js +2 -1
  52. package/dist/src/core/cross-search/cross-project-search.js +269 -0
  53. package/dist/src/core/cross-search/index.js +10 -0
  54. package/dist/src/core/database/sqlite-adapter.js +14 -84
  55. package/dist/src/core/extensions/provider-adapter.js +5 -0
  56. package/dist/src/core/models/model-router.js +54 -2
  57. package/dist/src/core/models/provider-pricing.js +58 -4
  58. package/dist/src/core/monitoring/logger.js +2 -1
  59. package/dist/src/core/optimization/trace-optimizer.js +413 -0
  60. package/dist/src/core/patterns/index.js +22 -0
  61. package/dist/src/core/patterns/pattern-applier.js +39 -0
  62. package/dist/src/core/patterns/pattern-observer.js +157 -0
  63. package/dist/src/core/patterns/pattern-store.js +259 -0
  64. package/dist/src/core/patterns/types.js +19 -0
  65. package/dist/src/core/provenance/confidence-scorer.js +128 -0
  66. package/dist/src/core/provenance/index.js +40 -0
  67. package/dist/src/core/provenance/provenance-store.js +194 -0
  68. package/dist/src/core/provenance/types.js +82 -0
  69. package/dist/src/core/retrieval/llm-context-retrieval.js +5 -4
  70. package/dist/src/core/retrieval/unified-context-assembler.js +11 -66
  71. package/dist/src/core/session/project-handoff.js +64 -0
  72. package/dist/src/core/session/session-manager.js +28 -0
  73. package/dist/src/core/shared-state/canonical-store.js +564 -0
  74. package/dist/src/core/skill-packs/index.js +18 -0
  75. package/dist/src/core/skill-packs/parser.js +42 -0
  76. package/dist/src/core/skill-packs/registry.js +224 -0
  77. package/dist/src/core/skill-packs/types.js +79 -0
  78. package/dist/src/core/storage/cloud-sync-manager.js +116 -0
  79. package/dist/src/core/storage/cloud-sync.js +574 -0
  80. package/dist/src/core/storage/two-tier-storage.js +5 -1
  81. package/dist/src/core/tasks/master-tasks-template.js +43 -0
  82. package/dist/src/core/tasks/md-task-parser.js +138 -0
  83. package/dist/src/core/trace/trace-event-store.js +282 -0
  84. package/dist/src/core/vision/index.js +27 -0
  85. package/dist/src/core/vision/signals.js +79 -0
  86. package/dist/src/core/vision/types.js +22 -0
  87. package/dist/src/core/vision/vision-file.js +146 -0
  88. package/dist/src/core/vision/vision-loop.js +220 -0
  89. package/dist/src/core/wiki/wiki-compiler.js +103 -1
  90. package/dist/src/daemon/daemon-config.js +52 -0
  91. package/dist/src/daemon/services/desire-path-service.js +566 -0
  92. package/dist/src/daemon/services/github-service.js +126 -0
  93. package/dist/src/daemon/services/research-stream-service.js +320 -0
  94. package/dist/src/daemon/services/telemetry-service.js +192 -0
  95. package/dist/src/daemon/unified-daemon.js +58 -1
  96. package/dist/src/features/browser/cli-browser-agent.js +417 -0
  97. package/dist/src/features/browser/stagehand-workflows.js +578 -0
  98. package/dist/src/features/operator/adapter-factory.js +62 -0
  99. package/dist/src/features/operator/browser-adapter.js +109 -0
  100. package/dist/src/features/operator/desktop-adapter.js +125 -0
  101. package/dist/src/features/operator/index.js +39 -0
  102. package/dist/src/features/operator/llm-decision.js +137 -0
  103. package/dist/src/features/operator/operator-logger.js +92 -0
  104. package/dist/src/features/operator/overnight-runner.js +327 -0
  105. package/dist/src/features/operator/screen-adapter.js +91 -0
  106. package/dist/src/features/operator/session-manager.js +127 -0
  107. package/dist/src/features/operator/state-machine.js +227 -0
  108. package/dist/src/features/operator/task-queue.js +81 -0
  109. package/dist/src/features/portal/index.js +26 -0
  110. package/dist/src/features/portal/server.js +240 -0
  111. package/dist/src/features/portal/types.js +14 -0
  112. package/dist/src/features/portal/ui.js +195 -0
  113. package/dist/src/features/sweep/pty-wrapper.js +13 -5
  114. package/dist/src/features/tasks/task-aware-context.js +2 -1
  115. package/dist/src/features/tui/simple-monitor.js +0 -23
  116. package/dist/src/features/tui/swarm-monitor.js +8 -66
  117. package/dist/src/features/web/client/hooks/use-socket.js +12 -0
  118. package/dist/src/{core/merge/index.js → features/web/client/lib/utils.js} +8 -4
  119. package/dist/src/features/web/client/next-env.d.js +4 -0
  120. package/dist/src/features/web/client/stores/session-store.js +12 -0
  121. package/dist/src/features/web/server/gcp-billing.js +76 -0
  122. package/dist/src/features/web/server/index.js +10 -0
  123. package/dist/src/features/web/server/spend-calculator.js +228 -0
  124. package/dist/src/hooks/schemas.js +6 -1
  125. package/dist/src/integrations/anthropic/client.js +3 -2
  126. package/dist/src/integrations/claude-code/agent-bridge.js +0 -3
  127. package/dist/src/integrations/claude-code/subagent-client.js +307 -11
  128. package/dist/src/integrations/claude-code/task-coordinator.js +2 -1
  129. package/dist/src/integrations/github/pr-state.js +158 -0
  130. package/dist/src/integrations/linear/client.js +4 -1
  131. package/dist/src/integrations/linear/webhook-retry.js +196 -0
  132. package/dist/src/integrations/linear/webhook-server.js +18 -22
  133. package/dist/src/integrations/mcp/handlers/cloud-sync-handlers.js +101 -0
  134. package/dist/src/integrations/mcp/handlers/index.js +40 -84
  135. package/dist/src/integrations/mcp/server.js +542 -641
  136. package/dist/src/integrations/mcp/tool-alias-registry.js +297 -0
  137. package/dist/src/integrations/mcp/tool-definitions.js +152 -682
  138. package/dist/src/mcp/stackmemory-mcp-server.js +571 -231
  139. package/dist/src/orchestrators/multimodal/determinism.js +244 -0
  140. package/dist/src/orchestrators/multimodal/harness.js +149 -78
  141. package/dist/src/orchestrators/multimodal/providers.js +44 -3
  142. package/dist/src/skills/recursive-agent-orchestrator.js +2 -4
  143. package/dist/src/utils/hook-installer.js +0 -8
  144. package/dist/src/utils/process-cleanup.js +1 -7
  145. package/docs/README.md +42 -0
  146. package/docs/guides/README_INSTALL.md +208 -0
  147. package/package.json +27 -9
  148. package/packs/coding/python-fastapi/instructions.md +60 -0
  149. package/packs/coding/python-fastapi/pack.yaml +28 -0
  150. package/packs/coding/typescript-react/instructions.md +47 -0
  151. package/packs/coding/typescript-react/pack.yaml +28 -0
  152. package/packs/core/commands/capture.md +32 -0
  153. package/packs/core/commands/learn.md +73 -0
  154. package/packs/core/commands/next.md +36 -0
  155. package/packs/core/commands/restart.md +58 -0
  156. package/packs/core/commands/restore.md +29 -0
  157. package/packs/core/commands/start.md +57 -0
  158. package/packs/core/commands/stop.md +65 -0
  159. package/packs/core/commands/summary.md +40 -0
  160. package/packs/core/manifest.json +24 -0
  161. package/packs/ops/decision-recovery/instructions.md +65 -0
  162. package/packs/ops/decision-recovery/pack.yaml +89 -0
  163. package/scripts/claude-code-wrapper.sh +11 -0
  164. package/scripts/claude-sm-setup.sh +12 -1
  165. package/scripts/codex-wrapper.sh +11 -0
  166. package/scripts/git-hooks/branch-context-manager.sh +11 -0
  167. package/scripts/git-hooks/post-checkout-stackmemory.sh +11 -0
  168. package/scripts/git-hooks/post-commit-stackmemory.sh +11 -0
  169. package/scripts/git-hooks/pre-commit-stackmemory.sh +11 -0
  170. package/scripts/hooks/cleanup-shell.sh +12 -1
  171. package/scripts/hooks/task-complete.sh +12 -1
  172. package/scripts/install-code-execution-hooks.sh +12 -1
  173. package/scripts/install-sweep-hook.sh +12 -0
  174. package/scripts/install.sh +11 -0
  175. package/scripts/opencode-wrapper.sh +11 -0
  176. package/scripts/portal/cloud-init.yaml +69 -0
  177. package/scripts/portal/setup.sh +69 -0
  178. package/scripts/portal/stackmemory-portal.service +34 -0
  179. package/scripts/setup-claude-integration.sh +12 -1
  180. package/scripts/smoke-init-db.sh +23 -0
  181. package/scripts/stackmemory-daemon.sh +11 -0
  182. package/scripts/verify-dist.cjs +11 -4
  183. package/dist/src/cli/commands/ralph.js +0 -1053
  184. package/dist/src/cli/commands/team.js +0 -168
  185. package/dist/src/core/context/shared-context-layer.js +0 -620
  186. package/dist/src/core/context/stack-merge-resolver.js +0 -748
  187. package/dist/src/core/merge/conflict-detector.js +0 -430
  188. package/dist/src/core/merge/resolution-engine.js +0 -557
  189. package/dist/src/core/merge/stack-diff.js +0 -531
  190. package/dist/src/core/merge/unified-merge-resolver.js +0 -302
  191. package/dist/src/hooks/diffmem-hooks.js +0 -376
  192. package/dist/src/integrations/diffmem/client.js +0 -208
  193. package/dist/src/integrations/diffmem/config.js +0 -14
  194. package/dist/src/integrations/greptile/client.js +0 -101
  195. package/dist/src/integrations/greptile/config.js +0 -14
  196. package/dist/src/integrations/greptile/index.js +0 -11
  197. package/dist/src/integrations/mcp/handlers/cord-handlers.js +0 -397
  198. package/dist/src/integrations/mcp/handlers/diffmem-handlers.js +0 -455
  199. package/dist/src/integrations/mcp/handlers/greptile-handlers.js +0 -456
  200. package/dist/src/integrations/mcp/handlers/provider-handlers.js +0 -227
  201. package/dist/src/integrations/mcp/handlers/team-handlers.js +0 -211
  202. package/dist/src/integrations/ralph/bridge/ralph-stackmemory-bridge.js +0 -863
  203. package/dist/src/integrations/ralph/context/context-budget-manager.js +0 -308
  204. package/dist/src/integrations/ralph/context/stackmemory-context-loader.js +0 -391
  205. package/dist/src/integrations/ralph/index.js +0 -17
  206. package/dist/src/integrations/ralph/learning/pattern-learner.js +0 -435
  207. package/dist/src/integrations/ralph/lifecycle/iteration-lifecycle.js +0 -448
  208. package/dist/src/integrations/ralph/loopmax.js +0 -488
  209. package/dist/src/integrations/ralph/monitoring/swarm-dashboard.js +0 -293
  210. package/dist/src/integrations/ralph/monitoring/swarm-registry.js +0 -107
  211. package/dist/src/integrations/ralph/orchestration/multi-loop-orchestrator.js +0 -508
  212. package/dist/src/integrations/ralph/patterns/compounding-engineering-pattern.js +0 -407
  213. package/dist/src/integrations/ralph/patterns/extended-coherence-sessions.js +0 -495
  214. package/dist/src/integrations/ralph/patterns/oracle-worker-pattern.js +0 -387
  215. package/dist/src/integrations/ralph/performance/performance-optimizer.js +0 -357
  216. package/dist/src/integrations/ralph/recovery/crash-recovery.js +0 -461
  217. package/dist/src/integrations/ralph/state/state-reconciler.js +0 -420
  218. package/dist/src/integrations/ralph/swarm/git-workflow-manager.js +0 -444
  219. package/dist/src/integrations/ralph/swarm/swarm-coordinator.js +0 -1007
  220. package/dist/src/integrations/ralph/visualization/ralph-debugger.js +0 -635
  221. package/scripts/ralph-loop-implementation.js +0 -404
  222. /package/dist/src/core/{merge → cache}/types.js +0 -0
  223. /package/dist/src/{integrations/diffmem/types.js → core/storage/cloud-sync-types.js} +0 -0
  224. /package/dist/src/{integrations/greptile/types.js → core/trace/trace-event.js} +0 -0
  225. /package/dist/src/{integrations/ralph → features/operator}/types.js +0 -0
@@ -18,6 +18,7 @@ import Database from "better-sqlite3";
18
18
  import { logger } from "../../core/monitoring/logger.js";
19
19
  import { isProcessAlive } from "../../utils/process-cleanup.js";
20
20
  import { Conductor } from "./orchestrator.js";
21
+ import { createVisionCommand } from "./vision.js";
21
22
  import {
22
23
  getAgentStatusDir,
23
24
  getOutcomesLogPath
@@ -189,13 +190,13 @@ function progressBar(pct, width) {
189
190
  return `${col}${"\u2501".repeat(filled)}${c.d}${"\u254C".repeat(empty)}${c.r}`;
190
191
  }
191
192
  function scanAgentStatuses() {
192
- const agentsDir2 = join(homedir(), ".stackmemory", "conductor", "agents");
193
- if (!existsSync(agentsDir2)) return [];
194
- const entries = readdirSync(agentsDir2, { withFileTypes: true });
193
+ const agentsDir = join(homedir(), ".stackmemory", "conductor", "agents");
194
+ if (!existsSync(agentsDir)) return [];
195
+ const entries = readdirSync(agentsDir, { withFileTypes: true });
195
196
  const statuses = [];
196
197
  for (const entry of entries) {
197
198
  if (!entry.isDirectory()) continue;
198
- const statusPath = join(agentsDir2, entry.name, "status.json");
199
+ const statusPath = join(agentsDir, entry.name, "status.json");
199
200
  if (!existsSync(statusPath)) continue;
200
201
  try {
201
202
  const data = JSON.parse(readFileSync(statusPath, "utf-8"));
@@ -668,6 +669,7 @@ function createConductorCommands() {
668
669
  }
669
670
  cmd.help();
670
671
  });
672
+ cmd.addCommand(createVisionCommand());
671
673
  cmd.command("capture").description("Capture workspace context after an agent run").requiredOption("--issue <id>", "Issue identifier (e.g., STA-476)").option("--workspace <path>", "Workspace directory", process.cwd()).option("--attempt <n>", "Attempt number", "1").action(async (options) => {
672
674
  const workspace = options.workspace;
673
675
  const issueId = options.issue;
@@ -953,6 +955,7 @@ function createConductorCommands() {
953
955
  console.log("");
954
956
  });
955
957
  cmd.command("finalize").description("Clean up completed/dead agents that conductor missed").option("--dry-run", "Show what would be done without doing it", false).action(async (options) => {
958
+ const agentsDir = join(homedir(), ".stackmemory", "conductor", "agents");
956
959
  const statuses = scanAgentStatuses();
957
960
  const needsFinalize = statuses.map((s) => ({ ...s, ...enrichStatus(s) })).filter((s) => {
958
961
  return !s.alive || s.elapsed > STALE_FINALIZE_THRESHOLD_MS;
@@ -1038,6 +1041,221 @@ function createConductorCommands() {
1038
1041
  );
1039
1042
  }
1040
1043
  });
1044
+ const isMerged = (repo, branch, lane) => {
1045
+ try {
1046
+ execSync(`git merge-base --is-ancestor "${branch}" "${lane}"`, {
1047
+ cwd: repo,
1048
+ stdio: "pipe",
1049
+ timeout: 5e3
1050
+ });
1051
+ return true;
1052
+ } catch (err) {
1053
+ const status = err.status;
1054
+ if (status !== 1) throw err;
1055
+ }
1056
+ try {
1057
+ const out = execSync(`git cherry "${lane}" "${branch}"`, {
1058
+ cwd: repo,
1059
+ encoding: "utf-8",
1060
+ timeout: 1e4
1061
+ }).trim();
1062
+ if (!out) return true;
1063
+ const lines = out.split("\n").filter(Boolean);
1064
+ return lines.every((l) => l.startsWith("-"));
1065
+ } catch {
1066
+ return false;
1067
+ }
1068
+ };
1069
+ const bucketLaneBranches = (repo, lane) => {
1070
+ let all = [];
1071
+ try {
1072
+ all = execSync(`git branch --list 'worktree-agent-*'`, {
1073
+ cwd: repo,
1074
+ encoding: "utf-8",
1075
+ timeout: 1e4
1076
+ }).split("\n").map((s) => s.trim().replace(/^[*+]\s*/, "")).filter(Boolean);
1077
+ } catch {
1078
+ }
1079
+ const merged = [];
1080
+ const unmerged = [];
1081
+ for (const b of all) {
1082
+ (isMerged(repo, b, lane) ? merged : unmerged).push(b);
1083
+ }
1084
+ return { merged, unmerged };
1085
+ };
1086
+ const getWorktreeCleanliness = (repo, wtPath) => {
1087
+ try {
1088
+ const out = execSync(`git -C "${wtPath}" status --short`, {
1089
+ cwd: repo,
1090
+ encoding: "utf-8",
1091
+ timeout: 5e3
1092
+ });
1093
+ return out.trim().length > 0 ? "dirty" : "clean";
1094
+ } catch {
1095
+ return "unknown";
1096
+ }
1097
+ };
1098
+ const laneCmd = cmd.command("lane").description(
1099
+ "Inspect or clean up worktree-agent-* branches against a lane"
1100
+ );
1101
+ laneCmd.command("status").description(
1102
+ "List worktree-agent-* branches and their merge state vs the lane"
1103
+ ).option(
1104
+ "--lane <branch>",
1105
+ "Lane branch to compare against (default: current branch)"
1106
+ ).option("--repo <path>", "Git repo root", process.cwd()).action((options) => {
1107
+ const repo = options.repo;
1108
+ const lane = options.lane || execSync("git branch --show-current", {
1109
+ cwd: repo,
1110
+ encoding: "utf-8"
1111
+ }).trim();
1112
+ if (!lane) {
1113
+ console.error(
1114
+ `${c.red}Could not resolve lane branch.${c.r} Pass --lane.`
1115
+ );
1116
+ process.exit(1);
1117
+ }
1118
+ const { merged, unmerged } = bucketLaneBranches(repo, lane);
1119
+ console.log(`
1120
+ ${c.b}Lane:${c.r} ${c.cyan}${lane}${c.r}
1121
+ `);
1122
+ if (merged.length) {
1123
+ console.log(` ${c.green}merged (${merged.length}):${c.r}`);
1124
+ for (const b of merged) console.log(` ${c.gray}\u2713${c.r} ${b}`);
1125
+ }
1126
+ if (unmerged.length) {
1127
+ console.log(`
1128
+ ${c.orange}unmerged (${unmerged.length}):${c.r}`);
1129
+ for (const b of unmerged) console.log(` ${c.orange}\u2022${c.r} ${b}`);
1130
+ }
1131
+ if (!merged.length && !unmerged.length) {
1132
+ console.log(` ${c.gray}No worktree-agent-* branches found.${c.r}`);
1133
+ }
1134
+ });
1135
+ laneCmd.command("cleanup").description(
1136
+ "Remove worktree-agent-* branches and worktrees already merged into the lane"
1137
+ ).option(
1138
+ "--lane <branch>",
1139
+ "Lane branch to compare against (default: current branch)"
1140
+ ).option("--repo <path>", "Git repo root", process.cwd()).option("--dry-run", "Show what would be removed without doing it", false).option(
1141
+ "--force",
1142
+ "Remove worktrees even if they have uncommitted changes",
1143
+ false
1144
+ ).action((options) => {
1145
+ const repo = options.repo;
1146
+ const lane = options.lane || execSync("git branch --show-current", {
1147
+ cwd: repo,
1148
+ encoding: "utf-8"
1149
+ }).trim();
1150
+ if (!lane) {
1151
+ console.error(
1152
+ `${c.red}Could not resolve lane branch.${c.r} Pass --lane.`
1153
+ );
1154
+ process.exit(1);
1155
+ }
1156
+ const { merged } = bucketLaneBranches(repo, lane);
1157
+ if (merged.length === 0) {
1158
+ console.log(
1159
+ `${c.green}Nothing to clean up for lane ${c.cyan}${lane}${c.r}.`
1160
+ );
1161
+ return;
1162
+ }
1163
+ const worktreeMap = /* @__PURE__ */ new Map();
1164
+ try {
1165
+ const wt = execSync("git worktree list --porcelain", {
1166
+ cwd: repo,
1167
+ encoding: "utf-8",
1168
+ timeout: 1e4
1169
+ });
1170
+ let currentPath = "";
1171
+ for (const line of wt.split("\n")) {
1172
+ if (line.startsWith("worktree ")) {
1173
+ currentPath = line.slice("worktree ".length).trim();
1174
+ } else if (line.startsWith("branch ")) {
1175
+ const ref = line.slice("branch ".length).trim();
1176
+ const b = ref.replace(/^refs\/heads\//, "");
1177
+ if (currentPath) worktreeMap.set(b, currentPath);
1178
+ }
1179
+ }
1180
+ } catch {
1181
+ }
1182
+ console.log(
1183
+ `
1184
+ ${c.b}Lane:${c.r} ${c.cyan}${lane}${c.r} ${c.d}(${merged.length} merged branches)${c.r}
1185
+ `
1186
+ );
1187
+ let skippedDirty = 0;
1188
+ let skippedUnknown = 0;
1189
+ for (const branch of merged) {
1190
+ const wtPath = worktreeMap.get(branch);
1191
+ const cleanliness = wtPath ? getWorktreeCleanliness(repo, wtPath) : "unknown";
1192
+ const dirty = cleanliness === "dirty";
1193
+ if (dirty && !options.force) {
1194
+ skippedDirty++;
1195
+ console.log(
1196
+ ` ${c.orange}\u26A0${c.r} ${c.orange}dirty${c.r} ${wtPath} ${c.d}(skipping ${branch} \u2014 pass --force to remove)${c.r}`
1197
+ );
1198
+ continue;
1199
+ }
1200
+ if (wtPath && cleanliness === "unknown" && !options.force) {
1201
+ skippedUnknown++;
1202
+ console.log(
1203
+ ` ${c.orange}\u26A0${c.r} ${c.orange}unknown${c.r} ${wtPath} ${c.d}(skipping ${branch} \u2014 could not verify clean state; pass --force to remove)${c.r}`
1204
+ );
1205
+ continue;
1206
+ }
1207
+ const dirtyTag = dirty ? ` ${c.orange}[dirty]${c.r}` : "";
1208
+ const unknownTag = cleanliness === "unknown" ? ` ${c.orange}[unknown]${c.r}` : "";
1209
+ const action = wtPath ? `${c.gray}worktree remove${c.r} ${wtPath}${dirtyTag}${unknownTag} + ${c.gray}branch -D${c.r} ${branch}` : `${c.gray}branch -D${c.r} ${branch}`;
1210
+ console.log(` ${c.green}\u2713${c.r} ${action}`);
1211
+ if (options.dryRun) continue;
1212
+ if (wtPath) {
1213
+ try {
1214
+ execSync(`git worktree remove "${wtPath}" --force`, {
1215
+ cwd: repo,
1216
+ stdio: "pipe",
1217
+ timeout: 15e3
1218
+ });
1219
+ } catch (err) {
1220
+ console.log(
1221
+ ` ${c.red}worktree remove failed:${c.r} ${err.message}`
1222
+ );
1223
+ }
1224
+ }
1225
+ try {
1226
+ execSync(`git branch -D "${branch}"`, {
1227
+ cwd: repo,
1228
+ stdio: "pipe",
1229
+ timeout: 5e3
1230
+ });
1231
+ } catch (err) {
1232
+ console.log(
1233
+ ` ${c.red}branch -D failed:${c.r} ${err.message}`
1234
+ );
1235
+ }
1236
+ }
1237
+ if (!options.dryRun) {
1238
+ try {
1239
+ execSync("git worktree prune", {
1240
+ cwd: repo,
1241
+ stdio: "pipe",
1242
+ timeout: 1e4
1243
+ });
1244
+ } catch {
1245
+ }
1246
+ const skipNote = skippedDirty ? ` ${c.orange}(${skippedDirty} dirty skipped \u2014 pass --force)${c.r}` : "";
1247
+ const unknownNote = skippedUnknown ? ` ${c.orange}(${skippedUnknown} unknown skipped \u2014 pass --force)${c.r}` : "";
1248
+ console.log(`
1249
+ ${c.green}Done.${c.r}${skipNote}${unknownNote}`);
1250
+ } else {
1251
+ const skipNote = skippedDirty ? ` ${c.orange}(${skippedDirty} dirty would be skipped \u2014 pass --force to include)${c.r}` : "";
1252
+ const unknownNote = skippedUnknown ? ` ${c.orange}(${skippedUnknown} unknown would be skipped \u2014 pass --force to include)${c.r}` : "";
1253
+ console.log(
1254
+ `
1255
+ ${c.d}Dry run \u2014 no changes made. Remove --dry-run to execute.${c.r}${skipNote}${unknownNote}`
1256
+ );
1257
+ }
1258
+ });
1041
1259
  cmd.command("logs").description("Tail agent output log").argument("<issue-id>", "Issue identifier (e.g., STA-499)").option("-f, --follow", "Follow the log (tail -f)", false).option("-n, --lines <n>", "Number of lines to show", "50").action(async (issueId, options) => {
1042
1260
  const logPath = join(getAgentStatusDir(issueId), "output.log");
1043
1261
  if (!existsSync(logPath)) {
@@ -1521,6 +1739,13 @@ function createConductorCommands() {
1521
1739
  ).option(
1522
1740
  "--no-pr",
1523
1741
  "Disable automatic GitHub PR creation after agent success"
1742
+ ).option(
1743
+ "--workspace-mode <mode>",
1744
+ 'Workspace mode: "auto" (detect GitButler), "gitbutler", or "worktree"',
1745
+ "auto"
1746
+ ).option(
1747
+ "--lane <branch>",
1748
+ "Optional lane branch. When set, conductor uses git worktrees rooted at the lane as worktree-agent-<id> and suppresses PRs (lane is human-curated)."
1524
1749
  ).action(async (options) => {
1525
1750
  ensureDefaultPromptTemplate();
1526
1751
  const conductor = new Conductor({
@@ -1537,7 +1762,9 @@ function createConductorCommands() {
1537
1762
  turnTimeoutMs: parseInt(options.turnTimeout, 10),
1538
1763
  agentMode: options.mode === "adapter" ? "adapter" : "cli",
1539
1764
  model: options.model,
1540
- autoPR: options.pr
1765
+ autoPR: options.lane ? false : options.pr,
1766
+ workspaceMode: options.workspaceMode,
1767
+ laneBranch: options.lane
1541
1768
  });
1542
1769
  await conductor.start();
1543
1770
  });