@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
@@ -3,6 +3,7 @@ import { dirname as __pathDirname } from 'path';
3
3
  const __filename = __fileURLToPath(import.meta.url);
4
4
  const __dirname = __pathDirname(__filename);
5
5
  import { spawn, execSync } from "child_process";
6
+ import { estimateTokens } from "../../core/cache/token-estimator.js";
6
7
  import {
7
8
  appendFileSync,
8
9
  existsSync,
@@ -19,6 +20,7 @@ import { createReadStream } from "fs";
19
20
  import { createInterface } from "readline";
20
21
  import { fileURLToPath } from "url";
21
22
  import { Transform } from "stream";
23
+ import { createHash } from "crypto";
22
24
  import { logger } from "../../core/monitoring/logger.js";
23
25
  import { isProcessAlive } from "../../utils/process-cleanup.js";
24
26
  import {
@@ -34,6 +36,59 @@ import {
34
36
  TraceCollector,
35
37
  stringifyEventTruncated
36
38
  } from "./conductor-traces.js";
39
+ const PROMPT_PHASES = [
40
+ "system",
41
+ "understand",
42
+ "implement",
43
+ "validate",
44
+ "deliver"
45
+ ];
46
+ function buildPromptFromPhases(variables) {
47
+ const promptsDir = join(homedir(), ".stackmemory", "conductor", "prompts");
48
+ const systemPath = join(promptsDir, "system.md");
49
+ if (!existsSync(systemPath)) return null;
50
+ const versions = {};
51
+ const parts = [];
52
+ for (const phase of PROMPT_PHASES) {
53
+ const phasePath = join(promptsDir, `${phase}.md`);
54
+ if (!existsSync(phasePath)) continue;
55
+ let content = readFileSync(phasePath, "utf-8");
56
+ for (const [key, value] of Object.entries(variables)) {
57
+ content = content.replace(new RegExp(`\\{\\{${key}\\}\\}`, "g"), value);
58
+ }
59
+ parts.push(content);
60
+ const hash = createHash("sha256").update(readFileSync(phasePath, "utf-8")).digest("hex").slice(0, 8);
61
+ versions[phase] = hash;
62
+ }
63
+ if (parts.length === 0) return null;
64
+ const dspyPath = join(
65
+ homedir(),
66
+ ".stackmemory",
67
+ "dspy",
68
+ "optimized_state.json"
69
+ );
70
+ if (existsSync(dspyPath)) {
71
+ try {
72
+ const state = JSON.parse(readFileSync(dspyPath, "utf-8"));
73
+ for (const phase of PROMPT_PHASES) {
74
+ const sig = state[phase];
75
+ if (sig?.fewShotExamples?.length) {
76
+ const examples = sig.fewShotExamples.slice(0, 3).map(
77
+ (ex) => `<example>
78
+ Input: ${JSON.stringify(ex.input)}
79
+ Output: ${JSON.stringify(ex.output)}
80
+ </example>`
81
+ ).join("\n");
82
+ parts.push(`
83
+ ## Optimized Examples (${phase}):
84
+ ${examples}`);
85
+ }
86
+ }
87
+ } catch {
88
+ }
89
+ }
90
+ return { prompt: parts.join("\n\n"), versions };
91
+ }
37
92
  function getOutcomesLogPath() {
38
93
  return join(homedir(), ".stackmemory", "conductor", "outcomes.jsonl");
39
94
  }
@@ -44,11 +99,6 @@ function logAgentOutcome(entry) {
44
99
  }
45
100
  function createPullRequest(opts) {
46
101
  try {
47
- execSync(`git push -u origin "${opts.branch}"`, {
48
- cwd: opts.workspacePath,
49
- stdio: "pipe",
50
- timeout: 6e4
51
- });
52
102
  const prTitle = `feat(conductor): ${opts.issueId} \u2014 ${opts.title}`;
53
103
  const prBody = [
54
104
  "## Summary",
@@ -60,6 +110,33 @@ function createPullRequest(opts) {
60
110
  "",
61
111
  "_This PR was auto-created by StackMemory Conductor._"
62
112
  ].join("\n");
113
+ if (opts.useGitButler) {
114
+ execSync(`but push --branch "${opts.branch}"`, {
115
+ cwd: opts.workspacePath,
116
+ stdio: "pipe",
117
+ timeout: 6e4
118
+ });
119
+ const result2 = execSync(
120
+ `but pr create --branch "${opts.branch}" --title "${prTitle.replace(/"/g, '\\"')}" --body "${prBody.replace(/"/g, '\\"')}"`,
121
+ {
122
+ cwd: opts.workspacePath,
123
+ encoding: "utf-8",
124
+ stdio: ["pipe", "pipe", "pipe"],
125
+ timeout: 3e4
126
+ }
127
+ );
128
+ const prUrl2 = result2.trim();
129
+ logger.info("Created PR via GitButler", {
130
+ issueId: opts.issueId,
131
+ prUrl: prUrl2
132
+ });
133
+ return prUrl2;
134
+ }
135
+ execSync(`git push -u origin "${opts.branch}"`, {
136
+ cwd: opts.workspacePath,
137
+ stdio: "pipe",
138
+ timeout: 6e4
139
+ });
63
140
  const result = execSync(
64
141
  `gh pr create --base "${opts.baseBranch}" --head "${opts.branch}" --title "${prTitle.replace(/"/g, '\\"')}" --body "${prBody.replace(/"/g, '\\"')}"`,
65
142
  {
@@ -158,8 +235,74 @@ function getRetryStrategy(issue, outcomes) {
158
235
  );
159
236
  }
160
237
  }
238
+ const promptsDir = join(homedir(), ".stackmemory", "conductor", "prompts");
239
+ if (lastFailure?.phase && existsSync(join(promptsDir, "system.md"))) {
240
+ const phaseAssertions = getPhaseAssertions(
241
+ lastFailure.phase,
242
+ lastFailure.errorTail || ""
243
+ );
244
+ adjustments.push(...phaseAssertions);
245
+ }
161
246
  return { shouldRetry: true, adjustments };
162
247
  }
248
+ function getPhaseAssertions(phase, error) {
249
+ const assertions = [];
250
+ switch (phase) {
251
+ case "reading":
252
+ case "planning":
253
+ assertions.push(
254
+ "ASSERTION: Re-read the issue description completely before planning.",
255
+ "ASSERTION: List ALL files you plan to modify before starting implementation."
256
+ );
257
+ break;
258
+ case "implementing":
259
+ if (/scope|unrelated|refactor/i.test(error)) {
260
+ assertions.push(
261
+ "ASSERTION: Only modify files directly required by the issue. Do NOT refactor surrounding code."
262
+ );
263
+ }
264
+ if (/import|module|ESM/i.test(error)) {
265
+ assertions.push(
266
+ "ASSERTION: Every relative import MUST end with .js extension. Check ALL new imports."
267
+ );
268
+ }
269
+ assertions.push(
270
+ "ASSERTION: After implementing, review your diff \u2014 if any change is not required by the issue, revert it."
271
+ );
272
+ break;
273
+ case "testing":
274
+ case "linting":
275
+ case "building":
276
+ if (/lint|eslint/i.test(error)) {
277
+ assertions.push(
278
+ "ASSERTION: Run `npm run lint` IMMEDIATELY. Fix every error. Do NOT proceed until lint passes.",
279
+ "ASSERTION: Common lint fixes \u2014 catch {} not catch (_err) {}, remove unused imports, add .js to relative imports."
280
+ );
281
+ }
282
+ if (/test|vitest|jest|FAIL/i.test(error)) {
283
+ assertions.push(
284
+ "ASSERTION: Read the FULL test error output. Identify which assertion fails and why.",
285
+ "ASSERTION: If vi.clearAllMocks() is in beforeEach, re-set any mockReturnValue calls after it."
286
+ );
287
+ }
288
+ if (/build|tsc|type/i.test(error)) {
289
+ assertions.push(
290
+ "ASSERTION: Run `npm run build` and fix ALL TypeScript errors before committing."
291
+ );
292
+ }
293
+ assertions.push(
294
+ "ASSERTION: Do NOT use --no-verify to bypass pre-commit hooks. Fix the underlying issue."
295
+ );
296
+ break;
297
+ case "committing":
298
+ assertions.push(
299
+ "ASSERTION: Commit message must follow format: type(scope): description",
300
+ "ASSERTION: If pre-commit hook fails, fix the issue and create a NEW commit \u2014 do NOT amend."
301
+ );
302
+ break;
303
+ }
304
+ return assertions;
305
+ }
163
306
  function findPackageRoot() {
164
307
  const currentFile = fileURLToPath(import.meta.url);
165
308
  let dir = dirname(currentFile);
@@ -340,6 +483,8 @@ class Conductor {
340
483
  stateCache = /* @__PURE__ */ new Map();
341
484
  activeStatesLower;
342
485
  terminalStatesLower;
486
+ /** Whether to use GitButler virtual branches instead of git worktrees */
487
+ useGitButler = false;
343
488
  /** Global rate limit backoff state */
344
489
  rateLimit = {
345
490
  inBackoff: false,
@@ -397,21 +542,79 @@ class Conductor {
397
542
  );
398
543
  }
399
544
  }
400
- if (!existsSync(this.config.workspaceRoot)) {
545
+ const wsMode = this.config.workspaceMode || "auto";
546
+ const laneMode = Boolean(this.config.laneBranch);
547
+ if (laneMode && wsMode === "gitbutler") {
548
+ throw new Error(
549
+ "--lane is only supported with git worktrees. Use --workspace-mode worktree or omit --workspace-mode."
550
+ );
551
+ }
552
+ if (!laneMode && (wsMode === "gitbutler" || wsMode === "auto")) {
553
+ try {
554
+ const butVersion = execSync("but --version", {
555
+ cwd: this.config.repoRoot,
556
+ encoding: "utf-8",
557
+ stdio: ["pipe", "pipe", "pipe"],
558
+ timeout: 5e3
559
+ }).trim();
560
+ const gbDir = join(this.config.repoRoot, ".git", "gitbutler");
561
+ if (wsMode === "gitbutler" || existsSync(gbDir)) {
562
+ this.useGitButler = true;
563
+ logger.info("Using GitButler virtual branches", {
564
+ version: butVersion
565
+ });
566
+ console.log(`[conductor] GitButler mode (${butVersion})`);
567
+ }
568
+ } catch {
569
+ if (wsMode === "gitbutler") {
570
+ throw new Error(
571
+ "GitButler CLI (but) not found. Install: brew install --cask gitbutler"
572
+ );
573
+ }
574
+ }
575
+ } else if (laneMode) {
576
+ logger.info("Lane mode enabled; using git worktrees", {
577
+ laneBranch: this.config.laneBranch
578
+ });
579
+ }
580
+ if (!this.useGitButler && !existsSync(this.config.workspaceRoot)) {
401
581
  mkdirSync(this.config.workspaceRoot, { recursive: true });
402
582
  }
403
583
  this.client = await this.createLinearClient();
404
- if (!this.config.teamId && this.client) {
584
+ if (this.client) {
405
585
  try {
406
- const team = await this.client.getTeam();
407
- this.config.teamId = team.id;
408
- logger.info("Auto-detected Linear team", {
409
- id: team.id,
410
- name: team.name,
411
- key: team.key
412
- });
586
+ if (this.config.teamId) {
587
+ const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-/i.test(this.config.teamId);
588
+ if (!isUuid) {
589
+ const teams = await this.client.getTeams();
590
+ const needle = this.config.teamId.toLowerCase();
591
+ const match = teams.find(
592
+ (t) => t.name.toLowerCase() === needle || t.key.toLowerCase() === needle
593
+ );
594
+ if (match) {
595
+ logger.info("Resolved team name to ID", {
596
+ name: this.config.teamId,
597
+ id: match.id,
598
+ key: match.key
599
+ });
600
+ this.config.teamId = match.id;
601
+ } else {
602
+ logger.warn("Team not found by name/key, will try as ID", {
603
+ team: this.config.teamId
604
+ });
605
+ }
606
+ }
607
+ } else {
608
+ const team = await this.client.getTeam();
609
+ this.config.teamId = team.id;
610
+ logger.info("Auto-detected Linear team", {
611
+ id: team.id,
612
+ name: team.name,
613
+ key: team.key
614
+ });
615
+ }
413
616
  } catch (err) {
414
- logger.warn("Failed to auto-detect team", {
617
+ logger.warn("Failed to resolve team", {
415
618
  error: err.message
416
619
  });
417
620
  }
@@ -433,7 +636,9 @@ class Conductor {
433
636
  try {
434
637
  await this.poll();
435
638
  } catch (err) {
436
- logger.error("Initial poll failed", { error: err.message });
639
+ const e = err;
640
+ logger.error("Initial poll failed", { error: e.message, stack: e.stack });
641
+ console.error(`[conductor] Initial poll failed: ${e.message}`);
437
642
  }
438
643
  await this.schedulePoll();
439
644
  }
@@ -691,6 +896,7 @@ class Conductor {
691
896
  const allCandidates = [];
692
897
  const issues = await this.client.getIssues({
693
898
  teamId: this.config.teamId,
899
+ stateType: "unstarted",
694
900
  limit: 50
695
901
  });
696
902
  for (const issue of issues) {
@@ -824,7 +1030,8 @@ class Conductor {
824
1030
  title: issue.title,
825
1031
  filesModified: run.filesModified,
826
1032
  toolCalls: run.toolCalls,
827
- workspacePath: run.workspacePath
1033
+ workspacePath: run.workspacePath,
1034
+ useGitButler: this.useGitButler
828
1035
  });
829
1036
  if (url) {
830
1037
  prUrl = url;
@@ -843,6 +1050,7 @@ class Conductor {
843
1050
  durationMs: Date.now() - run.startedAt,
844
1051
  hasCommits: true,
845
1052
  labels: issue.labels.map((l) => l.name),
1053
+ promptVersions: this.lastPromptVersions,
846
1054
  prUrl
847
1055
  });
848
1056
  await this.runHook(
@@ -878,6 +1086,7 @@ class Conductor {
878
1086
  durationMs: Date.now() - run.startedAt,
879
1087
  hasCommits: false,
880
1088
  labels: issue.labels.map((l) => l.name),
1089
+ promptVersions: this.lastPromptVersions,
881
1090
  errorTail: run.error?.slice(-500)
882
1091
  });
883
1092
  if (this.handleRateLimitError(run.error, issue.identifier)) {
@@ -1102,6 +1311,45 @@ class Conductor {
1102
1311
  // ── Workspace Management ──
1103
1312
  async createWorkspace(issue) {
1104
1313
  const wsKey = this.sanitizeIdentifier(issue.identifier);
1314
+ if (this.useGitButler) {
1315
+ return this.createGitButlerBranch(issue, wsKey);
1316
+ }
1317
+ return this.createWorktree(issue, wsKey);
1318
+ }
1319
+ createGitButlerBranch(issue, wsKey) {
1320
+ if (this.config.laneBranch) {
1321
+ throw new Error(
1322
+ "Lane mode requires git worktrees; GitButler virtual branches are not supported."
1323
+ );
1324
+ }
1325
+ const branchName = `conductor/${wsKey}`;
1326
+ try {
1327
+ execSync("but pull", {
1328
+ cwd: this.config.repoRoot,
1329
+ stdio: "pipe",
1330
+ timeout: 3e4
1331
+ });
1332
+ } catch {
1333
+ }
1334
+ try {
1335
+ execSync(`but branch new "${branchName}"`, {
1336
+ cwd: this.config.repoRoot,
1337
+ stdio: "pipe",
1338
+ timeout: 1e4
1339
+ });
1340
+ logger.info("Created GitButler virtual branch", {
1341
+ identifier: issue.identifier,
1342
+ branch: branchName
1343
+ });
1344
+ } catch {
1345
+ logger.info("GitButler branch may already exist, reusing", {
1346
+ identifier: issue.identifier,
1347
+ branch: branchName
1348
+ });
1349
+ }
1350
+ return this.config.repoRoot;
1351
+ }
1352
+ createWorktree(issue, wsKey) {
1105
1353
  const wsPath = join(this.config.workspaceRoot, wsKey);
1106
1354
  if (existsSync(wsPath)) {
1107
1355
  logger.info("Reusing existing workspace", {
@@ -1110,7 +1358,9 @@ class Conductor {
1110
1358
  });
1111
1359
  return wsPath;
1112
1360
  }
1113
- const branchName = `conductor/${wsKey}`;
1361
+ const lane = this.config.laneBranch;
1362
+ const branchName = lane ? `worktree-agent-${wsKey}` : `conductor/${wsKey}`;
1363
+ const startPoint = lane ? lane : `origin/${this.config.baseBranch}`;
1114
1364
  try {
1115
1365
  execSync("git fetch origin", {
1116
1366
  cwd: this.config.repoRoot,
@@ -1118,7 +1368,7 @@ class Conductor {
1118
1368
  timeout: 3e4
1119
1369
  });
1120
1370
  execSync(
1121
- `git worktree add "${wsPath}" -b "${branchName}" "origin/${this.config.baseBranch}"`,
1371
+ `git worktree add "${wsPath}" -b "${branchName}" "${startPoint}"`,
1122
1372
  {
1123
1373
  cwd: this.config.repoRoot,
1124
1374
  stdio: "pipe",
@@ -1128,7 +1378,8 @@ class Conductor {
1128
1378
  logger.info("Created workspace", {
1129
1379
  identifier: issue.identifier,
1130
1380
  path: wsPath,
1131
- branch: branchName
1381
+ branch: branchName,
1382
+ lane: lane || null
1132
1383
  });
1133
1384
  } catch (err) {
1134
1385
  try {
@@ -1147,6 +1398,25 @@ class Conductor {
1147
1398
  }
1148
1399
  async removeWorkspace(issue) {
1149
1400
  const wsKey = this.sanitizeIdentifier(issue.identifier);
1401
+ const branchName = `conductor/${wsKey}`;
1402
+ if (this.useGitButler) {
1403
+ await this.runHook("before-remove", this.config.repoRoot, issue).catch(
1404
+ () => {
1405
+ }
1406
+ );
1407
+ try {
1408
+ execSync(`but unapply "${branchName}"`, {
1409
+ cwd: this.config.repoRoot,
1410
+ stdio: "pipe",
1411
+ timeout: 1e4
1412
+ });
1413
+ } catch {
1414
+ logger.debug("GitButler branch already unapplied", {
1415
+ identifier: issue.identifier
1416
+ });
1417
+ }
1418
+ return;
1419
+ }
1150
1420
  const wsPath = join(this.config.workspaceRoot, wsKey);
1151
1421
  if (!existsSync(wsPath)) return;
1152
1422
  await this.runHook("before-remove", wsPath, issue).catch(() => {
@@ -1333,7 +1603,7 @@ class Conductor {
1333
1603
  }
1334
1604
  if (block.type === "text" && block.text) {
1335
1605
  const text = block.text;
1336
- run.tokensUsed += Math.ceil(text.length / 4);
1606
+ run.tokensUsed += estimateTokens(text);
1337
1607
  turnTextParts.push(text);
1338
1608
  }
1339
1609
  }
@@ -1469,7 +1739,7 @@ class Conductor {
1469
1739
  }
1470
1740
  }
1471
1741
  if (msg.method === "item/text" && params?.text) {
1472
- run.tokensUsed += Math.ceil(params.text.length / 4);
1742
+ run.tokensUsed += estimateTokens(params.text);
1473
1743
  }
1474
1744
  if (run.toolCalls % 5 === 0 || phase) {
1475
1745
  this.writeAgentStatus(issue.identifier, run);
@@ -1562,10 +1832,12 @@ class Conductor {
1562
1832
  }
1563
1833
  }
1564
1834
  }
1835
+ /** Last prompt version hashes — set by buildPrompt, read by outcome logging */
1836
+ lastPromptVersions = {};
1565
1837
  /**
1566
- * Build the agent prompt. If a custom template exists at
1567
- * ~/.stackmemory/conductor/prompt-template.md, use it with variable
1568
- * substitution. Otherwise fall back to the default template.
1838
+ * Build the agent prompt. Tries decomposed phase files first
1839
+ * (~/.stackmemory/conductor/prompts/*.md), then typed templates,
1840
+ * then custom prompt-template.md, then default.
1569
1841
  *
1570
1842
  * Template variables: {{ISSUE_ID}}, {{TITLE}}, {{DESCRIPTION}},
1571
1843
  * {{LABELS}}, {{PRIORITY}}, {{ATTEMPT}}, {{PRIOR_CONTEXT}}
@@ -1593,6 +1865,21 @@ class Conductor {
1593
1865
  );
1594
1866
  }
1595
1867
  const priorContext = contextParts.join("\n");
1868
+ const variables = {
1869
+ ISSUE_ID: issue.identifier,
1870
+ TITLE: issue.title,
1871
+ DESCRIPTION: issue.description || "",
1872
+ LABELS: labels,
1873
+ PRIORITY: priority,
1874
+ SCOPE: issue.identifier.toLowerCase().replace(/-\d+$/, ""),
1875
+ ATTEMPT: String(attempt),
1876
+ PRIOR_CONTEXT: priorContext
1877
+ };
1878
+ const phaseResult = buildPromptFromPhases(variables);
1879
+ if (phaseResult) {
1880
+ this.lastPromptVersions = phaseResult.versions;
1881
+ return phaseResult.prompt;
1882
+ }
1596
1883
  const templateDir = join(
1597
1884
  __dirname,
1598
1885
  "..",
@@ -1850,7 +2137,8 @@ class Conductor {
1850
2137
  title: run.issue.title,
1851
2138
  filesModified: run.filesModified,
1852
2139
  toolCalls: run.toolCalls,
1853
- workspacePath: wsPath
2140
+ workspacePath: wsPath,
2141
+ useGitButler: this.useGitButler
1854
2142
  });
1855
2143
  if (url) {
1856
2144
  prUrl = url;
@@ -1870,6 +2158,7 @@ class Conductor {
1870
2158
  durationMs,
1871
2159
  hasCommits,
1872
2160
  labels: run.issue.labels.map((l) => l.name),
2161
+ promptVersions: this.lastPromptVersions,
1873
2162
  errorTail,
1874
2163
  prUrl
1875
2164
  });