@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
@@ -9,10 +9,21 @@ import { spawn, execSync, execFileSync } from "child_process";
9
9
  import * as fs from "fs";
10
10
  import * as path from "path";
11
11
  import * as os from "os";
12
+ import { fileURLToPath } from "url";
12
13
  import { program } from "commander";
13
14
  import { v4 as uuidv4 } from "uuid";
14
15
  import chalk from "chalk";
15
16
  import { initializeTracing, trace } from "../core/trace/index.js";
17
+ import { resolveRealCliBin } from "./utils/real-cli-bin.js";
18
+ import {
19
+ startDeterminismWatcher,
20
+ stopDeterminismWatcher
21
+ } from "./utils/determinism-watcher.js";
22
+ import {
23
+ canonicalStateStore,
24
+ projectIdFromIdentifier
25
+ } from "../core/shared-state/canonical-store.js";
26
+ import { loadProjectHandoff } from "../core/session/project-handoff.js";
16
27
  import {
17
28
  getModelRouter,
18
29
  loadModelRouterConfig
@@ -43,6 +54,7 @@ import {
43
54
  writeSettingsAtomic,
44
55
  getSettingsPath
45
56
  } from "../utils/hook-installer.js";
57
+ const runtimeDirname = path.dirname(fileURLToPath(import.meta.url));
46
58
  const DEFAULT_SM_CONFIG = {
47
59
  defaultWorktree: false,
48
60
  defaultSandbox: false,
@@ -81,6 +93,11 @@ class ClaudeSM {
81
93
  worktreeScriptPath;
82
94
  claudeConfigDir;
83
95
  smConfig;
96
+ sessionId;
97
+ ownsSession;
98
+ sessionEnded;
99
+ determinismWatcher;
100
+ skippedHandoffReason;
84
101
  constructor() {
85
102
  this.smConfig = loadSMConfig();
86
103
  this.config = {
@@ -100,10 +117,15 @@ class ClaudeSM {
100
117
  };
101
118
  this.stackmemoryPath = this.findStackMemory();
102
119
  this.worktreeScriptPath = path.join(
103
- __dirname,
120
+ runtimeDirname,
104
121
  "../../scripts/claude-worktree-manager.sh"
105
122
  );
106
123
  this.claudeConfigDir = path.join(os.homedir(), ".claude");
124
+ this.sessionId = process.env["STACKMEMORY_SESSION"] || uuidv4();
125
+ this.ownsSession = !process.env["STACKMEMORY_SESSION"];
126
+ this.sessionEnded = false;
127
+ this.determinismWatcher = null;
128
+ this.skippedHandoffReason = null;
107
129
  if (!fs.existsSync(this.claudeConfigDir)) {
108
130
  fs.mkdirSync(this.claudeConfigDir, { recursive: true });
109
131
  }
@@ -172,6 +194,20 @@ class ClaudeSM {
172
194
  return "main";
173
195
  }
174
196
  }
197
+ getProjectId() {
198
+ const root = this.getRepoRoot() || process.cwd();
199
+ try {
200
+ const remote = execSync("git config --get remote.origin.url", {
201
+ cwd: root,
202
+ encoding: "utf8"
203
+ }).trim();
204
+ if (remote) {
205
+ return projectIdFromIdentifier(remote);
206
+ }
207
+ } catch {
208
+ }
209
+ return projectIdFromIdentifier(root);
210
+ }
175
211
  hasUncommittedChanges() {
176
212
  try {
177
213
  const status = execSync("git status --porcelain", { encoding: "utf8" });
@@ -181,17 +217,16 @@ class ClaudeSM {
181
217
  }
182
218
  }
183
219
  resolveClaudeBin() {
184
- if (this.config.claudeBin && this.config.claudeBin.trim()) {
185
- return this.config.claudeBin.trim();
186
- }
187
- const envBin = process.env["CLAUDE_BIN"];
188
- if (envBin && envBin.trim()) return envBin.trim();
189
- try {
190
- execSync("which claude", { stdio: "ignore" });
191
- return "claude";
192
- } catch {
193
- }
194
- return null;
220
+ return resolveRealCliBin({
221
+ explicitBin: this.config.claudeBin,
222
+ envBin: process.env["CLAUDE_BIN"],
223
+ preferredPaths: [
224
+ path.join(os.homedir(), ".local", "bin", "claude"),
225
+ "/usr/local/bin/claude",
226
+ "/opt/homebrew/bin/claude"
227
+ ],
228
+ pathCommands: ["claude"]
229
+ });
195
230
  }
196
231
  gepaProcesses = [];
197
232
  startGEPAWatcher() {
@@ -206,9 +241,9 @@ class ClaudeSM {
206
241
  }
207
242
  const gepaPaths = [
208
243
  // From dist/src/cli -> scripts/gepa (3 levels up)
209
- path.join(__dirname, "../../../scripts/gepa/hooks/auto-optimize.js"),
244
+ path.join(runtimeDirname, "../../../scripts/gepa/hooks/auto-optimize.js"),
210
245
  // From src/cli -> scripts/gepa (2 levels up, for dev mode)
211
- path.join(__dirname, "../../scripts/gepa/hooks/auto-optimize.js"),
246
+ path.join(runtimeDirname, "../../scripts/gepa/hooks/auto-optimize.js"),
212
247
  // Global install location
213
248
  path.join(
214
249
  os.homedir(),
@@ -220,7 +255,7 @@ class ClaudeSM {
220
255
  ),
221
256
  // npm global install
222
257
  path.join(
223
- __dirname,
258
+ runtimeDirname,
224
259
  "..",
225
260
  "..",
226
261
  "scripts",
@@ -260,6 +295,24 @@ class ClaudeSM {
260
295
  }
261
296
  this.gepaProcesses = [];
262
297
  }
298
+ startDeterminismWatcher() {
299
+ this.determinismWatcher = startDeterminismWatcher({
300
+ stackmemoryBin: this.stackmemoryPath,
301
+ cwd: process.cwd(),
302
+ task: this.config.task,
303
+ instanceId: this.config.instanceId,
304
+ sessionId: this.sessionId,
305
+ tool: "claude"
306
+ });
307
+ if (this.determinismWatcher) {
308
+ const modeLabel = this.determinismWatcher.mode === "targeted" ? "targeted" : "repo-root fallback";
309
+ console.log(chalk.gray(` Determinism: ${modeLabel}`));
310
+ }
311
+ }
312
+ stopDeterminismWatcher() {
313
+ stopDeterminismWatcher(this.determinismWatcher);
314
+ this.determinismWatcher = null;
315
+ }
263
316
  setupWorktree() {
264
317
  if (!this.config.useWorktree || !this.isGitRepo()) {
265
318
  return null;
@@ -347,16 +400,22 @@ class ClaudeSM {
347
400
  getHandoffContent() {
348
401
  if (!this.config.contextEnabled) return null;
349
402
  try {
350
- const handoffPath = path.join(
403
+ const handoff = loadProjectHandoff(
351
404
  process.cwd(),
352
- ".stackmemory",
353
- "last-handoff.md"
405
+ this.isGitRepo() ? this.getCurrentBranch() : void 0
354
406
  );
355
- if (fs.existsSync(handoffPath)) {
356
- const content = fs.readFileSync(handoffPath, "utf8").trim();
357
- if (content.length > 0) {
358
- return content.length > 8e3 ? content.substring(0, 8e3) + "\n\n[...truncated]" : content;
359
- }
407
+ if (!handoff) {
408
+ this.skippedHandoffReason = null;
409
+ return null;
410
+ }
411
+ if (!handoff.compatible) {
412
+ this.skippedHandoffReason = handoff.mismatchReason || "stale handoff";
413
+ return null;
414
+ }
415
+ this.skippedHandoffReason = null;
416
+ const content = handoff.content.trim();
417
+ if (content.length > 0) {
418
+ return content.length > 8e3 ? content.substring(0, 8e3) + "\n\n[...truncated]" : content;
360
419
  }
361
420
  } catch {
362
421
  }
@@ -373,10 +432,10 @@ class ClaudeSM {
373
432
  fs.mkdirSync(hooksDir, { recursive: true });
374
433
  }
375
434
  const candidateDirs = [
376
- path.join(__dirname, "../../templates/claude-hooks"),
377
- path.join(__dirname, "../../../templates/claude-hooks"),
435
+ path.join(runtimeDirname, "../../templates/claude-hooks"),
436
+ path.join(runtimeDirname, "../../../templates/claude-hooks"),
378
437
  path.join(
379
- __dirname,
438
+ runtimeDirname,
380
439
  "..",
381
440
  "..",
382
441
  "..",
@@ -468,6 +527,149 @@ class ClaudeSM {
468
527
  console.log(chalk.gray(`
469
528
  Session ended (exit ${exitCode ?? 0})`));
470
529
  }
530
+ async publishSessionStart() {
531
+ const projectPath = process.cwd();
532
+ const projectId = this.getProjectId();
533
+ const branch = this.isGitRepo() ? this.getCurrentBranch() : void 0;
534
+ await canonicalStateStore.upsertSession({
535
+ sessionId: this.sessionId,
536
+ tool: "claude",
537
+ projectId,
538
+ projectPath,
539
+ branch,
540
+ instanceId: this.config.instanceId,
541
+ metadata: {
542
+ task: this.config.task,
543
+ sandbox: this.config.useSandbox,
544
+ chrome: this.config.useChrome
545
+ }
546
+ });
547
+ await canonicalStateStore.upsertInstance({
548
+ instanceId: this.config.instanceId,
549
+ tool: "claude",
550
+ sessionId: this.sessionId,
551
+ projectId,
552
+ projectPath,
553
+ branch,
554
+ worktreePath: this.config.worktreePath,
555
+ pid: process.pid,
556
+ status: "active",
557
+ metadata: {
558
+ task: this.config.task,
559
+ sandbox: this.config.useSandbox,
560
+ chrome: this.config.useChrome
561
+ }
562
+ });
563
+ await canonicalStateStore.appendEvent({
564
+ type: "session_start",
565
+ tool: "claude",
566
+ sessionId: this.sessionId,
567
+ instanceId: this.config.instanceId,
568
+ projectId,
569
+ projectPath,
570
+ branch,
571
+ payload: {
572
+ task: this.config.task,
573
+ worktreePath: this.config.worktreePath,
574
+ sandbox: this.config.useSandbox,
575
+ chrome: this.config.useChrome
576
+ }
577
+ });
578
+ const claimResult = await canonicalStateStore.claimPaths({
579
+ tool: "claude",
580
+ sessionId: this.sessionId,
581
+ instanceId: this.config.instanceId,
582
+ projectId,
583
+ projectPath,
584
+ branch,
585
+ paths: [],
586
+ metadata: {
587
+ task: this.config.task,
588
+ scope: "branch"
589
+ }
590
+ });
591
+ if (claimResult.conflicts.length > 0) {
592
+ console.log(chalk.yellow("\u26A0\uFE0F Shared state conflict detected"));
593
+ for (const conflict of claimResult.conflicts.slice(0, 3)) {
594
+ console.log(
595
+ chalk.gray(
596
+ ` Claim ${conflict.claimId.slice(0, 8)} already owns ${conflict.branch || "overlapping work"}`
597
+ )
598
+ );
599
+ }
600
+ }
601
+ }
602
+ async publishSessionEnd(eventType, payload = {}) {
603
+ if (this.sessionEnded) {
604
+ return;
605
+ }
606
+ this.sessionEnded = true;
607
+ const projectPath = process.cwd();
608
+ const projectId = this.getProjectId();
609
+ const branch = this.isGitRepo() ? this.getCurrentBranch() : void 0;
610
+ await canonicalStateStore.appendEvent({
611
+ type: eventType,
612
+ tool: "claude",
613
+ sessionId: this.sessionId,
614
+ instanceId: this.config.instanceId,
615
+ projectId,
616
+ projectPath,
617
+ branch,
618
+ payload
619
+ });
620
+ await canonicalStateStore.releaseClaims({
621
+ instanceId: this.config.instanceId,
622
+ reason: eventType
623
+ });
624
+ await canonicalStateStore.endInstance(this.config.instanceId);
625
+ if (this.ownsSession) {
626
+ await canonicalStateStore.endSession(this.sessionId);
627
+ }
628
+ }
629
+ async finalizeSession(eventType, exitCode, payload = {}) {
630
+ this.stopGEPAWatcher();
631
+ this.stopDeterminismWatcher();
632
+ this.saveContext(
633
+ eventType === "session_end" ? "Claude session ended" : eventType === "session_interrupt" ? "Claude session interrupted" : "Claude session terminated",
634
+ {
635
+ action: eventType,
636
+ exitCode,
637
+ ...payload
638
+ }
639
+ );
640
+ await this.publishSessionEnd(eventType, {
641
+ exitCode,
642
+ ...payload
643
+ });
644
+ if (eventType === "session_end" && process.env["LINEAR_API_KEY"]) {
645
+ try {
646
+ execSync("stackmemory linear sync", {
647
+ stdio: "ignore",
648
+ timeout: 1e4
649
+ });
650
+ } catch {
651
+ }
652
+ }
653
+ if (this.config.tracingEnabled) {
654
+ const summary = trace.getExecutionSummary();
655
+ console.log();
656
+ console.log(chalk.gray("\u2500".repeat(42)));
657
+ console.log(chalk.blue("Debug Trace Summary:"));
658
+ console.log(chalk.gray(summary));
659
+ }
660
+ if (eventType === "session_end" && this.config.notifyOnDone) {
661
+ this.notifyDone(exitCode);
662
+ }
663
+ if (this.config.worktreePath) {
664
+ console.log();
665
+ console.log(chalk.gray("\u2500".repeat(42)));
666
+ console.log(chalk.blue("Session ended in worktree:"));
667
+ console.log(chalk.gray(` ${this.config.worktreePath}`));
668
+ console.log();
669
+ console.log(chalk.gray("To remove worktree: gd_claude"));
670
+ console.log(chalk.gray("To merge to main: cwm"));
671
+ }
672
+ }
471
673
  async run(args) {
472
674
  const claudeArgs = [];
473
675
  let i = 0;
@@ -642,10 +844,25 @@ Session ended (exit ${exitCode ?? 0})`));
642
844
  this.loadContext();
643
845
  this.ensureHooks();
644
846
  process.env["CLAUDE_INSTANCE_ID"] = this.config.instanceId;
847
+ process.env["STACKMEMORY_SESSION"] = this.sessionId;
645
848
  if (this.config.worktreePath) {
646
849
  process.env["CLAUDE_WORKTREE_PATH"] = this.config.worktreePath;
647
850
  }
851
+ const claudeBin = this.resolveClaudeBin();
852
+ if (!claudeBin) {
853
+ console.error(chalk.red("\u274C Claude CLI not found."));
854
+ console.log(
855
+ chalk.gray(
856
+ " Install Claude CLI or set an override:\n export CLAUDE_BIN=/path/to/claude\n claude-sm --help\n\n Ensure PATH includes npm global bin (npm bin -g)."
857
+ )
858
+ );
859
+ process.exit(1);
860
+ return;
861
+ }
862
+ await this.publishSessionStart();
863
+ this.startDeterminismWatcher();
648
864
  console.log(chalk.gray(`\u{1F916} Instance ID: ${this.config.instanceId}`));
865
+ console.log(chalk.gray(`\u{1F9E0} Session ID: ${this.sessionId.slice(0, 8)}`));
649
866
  console.log(chalk.gray(`\u{1F4C1} Working in: ${process.cwd()}`));
650
867
  if (this.config.useSandbox) {
651
868
  console.log(chalk.yellow("\u{1F512} Sandbox mode enabled"));
@@ -712,6 +929,10 @@ Session ended (exit ${exitCode ?? 0})`));
712
929
  if (handoffContent) {
713
930
  initialInput = handoffContent;
714
931
  console.log(chalk.gray(" Handoff context ready"));
932
+ } else if (this.skippedHandoffReason) {
933
+ console.log(
934
+ chalk.gray(` Handoff skipped: ${this.skippedHandoffReason}`)
935
+ );
715
936
  }
716
937
  const theoryContent = this.getTheoryContent();
717
938
  if (theoryContent) {
@@ -723,21 +944,25 @@ ${theoryContent}`;
723
944
  }
724
945
  console.log();
725
946
  if (this.config.useSweep) {
726
- const claudeBin2 = this.resolveClaudeBin();
727
- if (!claudeBin2) {
728
- console.error(chalk.red("Claude CLI not found."));
729
- process.exit(1);
730
- return;
731
- }
732
947
  console.log(
733
948
  chalk.cyan("[Sweep] Launching Claude with prediction bar...")
734
949
  );
735
950
  console.log(chalk.gray("\u2500".repeat(42)));
736
951
  try {
737
952
  await launchWrapper({
738
- claudeBin: claudeBin2,
953
+ claudeBin,
739
954
  claudeArgs,
740
- initialInput: initialInput || void 0
955
+ initialInput: initialInput || void 0,
956
+ onExit: async (exitCode) => {
957
+ await this.finalizeSession("session_end", exitCode);
958
+ },
959
+ onSignal: async (signal) => {
960
+ await this.finalizeSession(
961
+ signal === "SIGINT" ? "session_interrupt" : "session_terminate",
962
+ null,
963
+ { signal }
964
+ );
965
+ }
741
966
  });
742
967
  return;
743
968
  } catch (error) {
@@ -756,17 +981,6 @@ ${theoryContent}`;
756
981
  }
757
982
  console.log(chalk.gray("Starting Claude..."));
758
983
  console.log(chalk.gray("\u2500".repeat(42)));
759
- const claudeBin = this.resolveClaudeBin();
760
- if (!claudeBin) {
761
- console.error(chalk.red("\u274C Claude CLI not found."));
762
- console.log(
763
- chalk.gray(
764
- " Install Claude CLI or set an override:\n export CLAUDE_BIN=/path/to/claude\n claude-sm --help\n\n Ensure PATH includes npm global bin (npm bin -g)."
765
- )
766
- );
767
- process.exit(1);
768
- return;
769
- }
770
984
  const fallbackMonitor = new FallbackMonitor({
771
985
  enabled: true,
772
986
  maxRestarts: 2,
@@ -807,7 +1021,6 @@ ${theoryContent}`;
807
1021
  process.exit(1);
808
1022
  });
809
1023
  claude.on("exit", async (code) => {
810
- this.stopGEPAWatcher();
811
1024
  const status = fallbackMonitor.getStatus();
812
1025
  if (status.inFallback) {
813
1026
  console.log(
@@ -817,49 +1030,18 @@ Session completed on fallback provider: ${status.currentProvider}`
817
1030
  )
818
1031
  );
819
1032
  }
820
- this.saveContext("Claude session ended", {
821
- action: "session_end",
822
- exitCode: code
823
- });
824
- if (process.env["LINEAR_API_KEY"]) {
825
- try {
826
- execSync("stackmemory linear sync", {
827
- stdio: "ignore",
828
- timeout: 1e4
829
- });
830
- } catch {
831
- }
832
- }
833
- if (this.config.tracingEnabled) {
834
- const summary = trace.getExecutionSummary();
835
- console.log();
836
- console.log(chalk.gray("\u2500".repeat(42)));
837
- console.log(chalk.blue("Debug Trace Summary:"));
838
- console.log(chalk.gray(summary));
839
- }
840
- if (this.config.notifyOnDone) {
841
- this.notifyDone(code);
842
- }
843
- if (this.config.worktreePath) {
844
- console.log();
845
- console.log(chalk.gray("\u2500".repeat(42)));
846
- console.log(chalk.blue("Session ended in worktree:"));
847
- console.log(chalk.gray(` ${this.config.worktreePath}`));
848
- console.log();
849
- console.log(chalk.gray("To remove worktree: gd_claude"));
850
- console.log(chalk.gray("To merge to main: cwm"));
851
- }
1033
+ await this.finalizeSession("session_end", code);
852
1034
  process.exit(code || 0);
853
1035
  });
854
- process.on("SIGINT", () => {
855
- this.saveContext("Claude session interrupted", {
856
- action: "session_interrupt"
1036
+ process.on("SIGINT", async () => {
1037
+ await this.finalizeSession("session_interrupt", null, {
1038
+ signal: "SIGINT"
857
1039
  });
858
1040
  claude.kill("SIGINT");
859
1041
  });
860
- process.on("SIGTERM", () => {
861
- this.saveContext("Claude session terminated", {
862
- action: "session_terminate"
1042
+ process.on("SIGTERM", async () => {
1043
+ await this.finalizeSession("session_terminate", null, {
1044
+ signal: "SIGTERM"
863
1045
  });
864
1046
  claude.kill("SIGTERM");
865
1047
  });