@stackmemoryai/stackmemory 1.10.5 → 1.12.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 (92) hide show
  1. package/README.md +104 -23
  2. package/dist/src/cli/claude-sm.js +266 -84
  3. package/dist/src/cli/codex-sm.js +185 -33
  4. package/dist/src/cli/commands/bench.js +209 -2
  5. package/dist/src/cli/commands/cache.js +126 -0
  6. package/dist/src/cli/commands/daemon.js +41 -0
  7. package/dist/src/cli/commands/handoff.js +40 -9
  8. package/dist/src/cli/commands/onboard.js +70 -3
  9. package/dist/src/cli/commands/optimize.js +117 -0
  10. package/dist/src/cli/commands/orchestrate.js +230 -5
  11. package/dist/src/cli/commands/orchestrator.js +312 -24
  12. package/dist/src/cli/commands/pack.js +322 -0
  13. package/dist/src/cli/commands/search.js +40 -1
  14. package/dist/src/cli/commands/setup.js +177 -7
  15. package/dist/src/cli/commands/skills.js +10 -1
  16. package/dist/src/cli/commands/state.js +265 -0
  17. package/dist/src/cli/gemini-sm.js +19 -29
  18. package/dist/src/cli/index.js +90 -29
  19. package/dist/src/cli/opencode-sm.js +38 -21
  20. package/dist/src/cli/utils/determinism-watcher.js +66 -0
  21. package/dist/src/cli/utils/real-cli-bin.js +44 -0
  22. package/dist/src/core/cache/content-cache.js +238 -0
  23. package/dist/src/core/cache/index.js +11 -0
  24. package/dist/src/core/cache/token-estimator.js +16 -0
  25. package/dist/src/core/context/frame-database.js +38 -30
  26. package/dist/src/core/cross-search/cross-project-search.js +269 -0
  27. package/dist/src/core/{merge → cross-search}/index.js +6 -4
  28. package/dist/src/core/database/sqlite-adapter.js +0 -83
  29. package/dist/src/core/extensions/provider-adapter.js +5 -0
  30. package/dist/src/core/models/model-router.js +22 -2
  31. package/dist/src/core/monitoring/logger.js +2 -1
  32. package/dist/src/core/optimization/trace-optimizer.js +413 -0
  33. package/dist/src/core/provenance/confidence-scorer.js +128 -0
  34. package/dist/src/core/provenance/index.js +40 -0
  35. package/dist/src/core/provenance/provenance-store.js +194 -0
  36. package/dist/src/core/provenance/types.js +82 -0
  37. package/dist/src/core/session/project-handoff.js +64 -0
  38. package/dist/src/core/session/session-manager.js +28 -0
  39. package/dist/src/core/shared-state/canonical-store.js +564 -0
  40. package/dist/src/core/skill-packs/index.js +18 -0
  41. package/dist/src/core/skill-packs/parser.js +42 -0
  42. package/dist/src/core/skill-packs/registry.js +224 -0
  43. package/dist/src/core/skill-packs/types.js +66 -0
  44. package/dist/src/core/trace/trace-event-store.js +282 -0
  45. package/dist/src/core/trace/trace-event.js +4 -0
  46. package/dist/src/daemon/daemon-config.js +7 -0
  47. package/dist/src/daemon/services/github-service.js +126 -0
  48. package/dist/src/daemon/unified-daemon.js +30 -0
  49. package/dist/src/features/sweep/pty-wrapper.js +13 -5
  50. package/dist/src/hooks/schemas.js +2 -0
  51. package/dist/src/integrations/claude-code/subagent-client.js +89 -0
  52. package/dist/src/integrations/github/pr-state.js +158 -0
  53. package/dist/src/integrations/linear/client.js +4 -1
  54. package/dist/src/integrations/mcp/handlers/cross-search-handlers.js +188 -0
  55. package/dist/src/integrations/mcp/handlers/index.js +40 -59
  56. package/dist/src/integrations/mcp/server.js +425 -311
  57. package/dist/src/integrations/mcp/tool-alias-registry.js +370 -0
  58. package/dist/src/integrations/mcp/tool-definitions.js +98 -229
  59. package/dist/src/integrations/ralph/context/stackmemory-context-loader.js +3 -40
  60. package/dist/src/integrations/ralph/learning/pattern-learner.js +1 -20
  61. package/dist/src/integrations/ralph/swarm/swarm-coordinator.js +0 -2
  62. package/dist/src/mcp/stackmemory-mcp-server.js +315 -0
  63. package/dist/src/orchestrators/multimodal/determinism.js +243 -0
  64. package/dist/src/orchestrators/multimodal/harness.js +147 -77
  65. package/dist/src/orchestrators/multimodal/providers.js +44 -3
  66. package/dist/src/utils/hook-installer.js +0 -8
  67. package/package.json +10 -1
  68. package/packs/coding/python-fastapi/instructions.md +60 -0
  69. package/packs/coding/python-fastapi/pack.yaml +28 -0
  70. package/packs/coding/typescript-react/instructions.md +47 -0
  71. package/packs/coding/typescript-react/pack.yaml +28 -0
  72. package/packs/core/commands/capture.md +32 -0
  73. package/packs/core/commands/learn.md +73 -0
  74. package/packs/core/commands/next.md +36 -0
  75. package/packs/core/commands/restart.md +58 -0
  76. package/packs/core/commands/restore.md +29 -0
  77. package/packs/core/commands/start.md +57 -0
  78. package/packs/core/commands/stop.md +65 -0
  79. package/packs/core/commands/summary.md +40 -0
  80. package/packs/core/manifest.json +24 -0
  81. package/packs/ops/decision-recovery/instructions.md +65 -0
  82. package/packs/ops/decision-recovery/pack.yaml +89 -0
  83. package/dist/src/cli/commands/team.js +0 -168
  84. package/dist/src/core/context/shared-context-layer.js +0 -620
  85. package/dist/src/core/context/stack-merge-resolver.js +0 -748
  86. package/dist/src/core/merge/conflict-detector.js +0 -430
  87. package/dist/src/core/merge/resolution-engine.js +0 -557
  88. package/dist/src/core/merge/stack-diff.js +0 -531
  89. package/dist/src/core/merge/unified-merge-resolver.js +0 -302
  90. package/dist/src/integrations/mcp/handlers/cord-handlers.js +0 -397
  91. package/dist/src/integrations/mcp/handlers/team-handlers.js +0 -211
  92. /package/dist/src/core/{merge → cache}/types.js +0 -0
package/README.md CHANGED
@@ -14,7 +14,7 @@ Lossless, project-scoped memory for AI coding tools. **[Website](https://stackme
14
14
  StackMemory is a **production-ready memory runtime** for AI coding tools that preserves full project context across sessions:
15
15
 
16
16
  - **Zero-config setup** — `stackmemory init` just works
17
- - **56 MCP tools** for Claude Code integration (context, tasks, Linear, traces, discovery, cord, team, planning, providers, and more)
17
+ - **73 MCP tools** for Claude Code integration (context, tasks, Linear, traces, discovery, cord, team, planning, providers, and more)
18
18
  - **FTS5 full-text search** with BM25 scoring and hybrid retrieval
19
19
  - **Full Linear integration** with bidirectional sync and OAuth/API key support
20
20
  - **Context persistence** that survives `/clear` operations
@@ -97,6 +97,16 @@ stackmemory doctor
97
97
 
98
98
  Restart Claude Code and StackMemory MCP tools will be available.
99
99
 
100
+ ### Also available as `croissant-ai`
101
+
102
+ [croissant.ai](https://croissant.ai) wraps StackMemory as a business-facing CLI for executive intelligence. Install via `npx croissant-ai` — all unknown commands forward to StackMemory automatically.
103
+
104
+ ```bash
105
+ npx croissant-ai ask "What's our MRR?" # croissant.ai query API
106
+ npx croissant-ai memory capture # → stackmemory capture
107
+ npx croissant-ai init # → stackmemory init (fallback)
108
+ ```
109
+
100
110
  ### Wrapper Scripts
101
111
 
102
112
  StackMemory ships wrapper scripts that launch your coding tool with StackMemory context pre-loaded:
@@ -132,6 +142,44 @@ Runs as an MCP server. Editors (e.g., Claude Code) call StackMemory on each inte
132
142
 
133
143
  ---
134
144
 
145
+ ## Command Packs
146
+
147
+ StackMemory ships installable command packs for Claude Code — curated slash commands that integrate session lifecycle management directly into your workflow.
148
+
149
+ ### Core Pack
150
+
151
+ The **core** pack provides session lifecycle commands that use StackMemory for context persistence:
152
+
153
+ | Command | Description |
154
+ |---------|-------------|
155
+ | `/start` | Boot a session — load memory, restore handoff, show git state, suggest next work |
156
+ | `/stop` | End a session — summarize, capture handoff, review learnings |
157
+ | `/restart` | Chain `/stop` → `/clear` → `/start` in one command |
158
+ | `/next` | Suggest what to work on based on context |
159
+
160
+ Internal dependencies (installed automatically): `/summary`, `/capture`, `/learn`, `/restore`
161
+
162
+ ```bash
163
+ # Install core commands
164
+ stackmemory setup-commands
165
+
166
+ # List available packs
167
+ stackmemory setup-commands --list
168
+
169
+ # Preview what would be installed
170
+ stackmemory setup-commands --dry-run
171
+
172
+ # Reinstall (overwrite existing)
173
+ stackmemory setup-commands --force
174
+
175
+ # Remove installed commands
176
+ stackmemory setup-commands --uninstall
177
+ ```
178
+
179
+ Commands are symlinked to `~/.claude/commands/` and available immediately in Claude Code.
180
+
181
+ ---
182
+
135
183
  ## Skills System
136
184
 
137
185
  StackMemory ships Claude Code skills that integrate directly into your workflow. Skills are invoked via `/skill-name` in Claude Code or `stackmemory skills <name>` from the CLI.
@@ -272,34 +320,54 @@ claude-sm
272
320
 
273
321
  ---
274
322
 
275
- ## RLM (Recursive Language Model) Orchestration
323
+ ## RLM-Aware Harness
324
+
325
+ StackMemory includes an RLM (Recursive Language Model) system inspired by [predict-rlm](https://github.com/Trampoline-AI/predict-rlm) — three layers that make agents self-harnessing:
326
+
327
+ ### Layer 1: Context-as-REPL
328
+
329
+ Agents access tenant data programmatically via tool functions instead of prose handoffs:
330
+
331
+ - `search_knowledge(query, topK)` — vector search over tenant data
332
+ - `read_context(query)` — knowledge graph entity/relationship queries
333
+ - `list_runs(threadId)` — prior run history with status summaries
334
+ - `get_run_events(runId)` — event stream replay for a specific run
335
+ - `query_interrupts(threadId)` — pending approvals and blocks
336
+ - `llm_summarize(prompt)` — sub-LLM (Haiku) for quick synthesis
337
+
338
+ ### Layer 2: Typed Dispatch
339
+
340
+ Signature-based sub-agent spawning with validated I/O contracts:
341
+
342
+ | Signature | Persona | Inputs | Outputs |
343
+ |-----------|---------|--------|---------|
344
+ | `ImplementFeature` | eng | spec, context_files | branch, changed_files, test_passed |
345
+ | `FixBug` | eng | description, error_log | branch, changed_files, root_cause |
346
+ | `ReviewPR` | eng | pr_url, focus_areas | verdict, comments, risk_level |
347
+ | `Investigate` | data | question, data_sources | findings, evidence, confidence |
348
+ | `Summarize` | data | content, format | summary, key_points |
349
+
350
+ Parent runs dispatch typed sub-runs via `dispatchSubRun()` and collect validated results via `collectSubRun()` with timeout handling.
351
+
352
+ ### Layer 3: Critic + Self-Decomposition
276
353
 
277
- StackMemory includes an RLM system that handles complex tasks through recursive decomposition and parallel execution using Claude Code's Task tool.
354
+ LLM quality gate that evaluates run output (ported from predict-rlm's critic pattern):
278
355
 
279
- ### Key Features
356
+ - **Score >= 80**: Pass — run ships as-is
357
+ - **Score 50-79**: Revise — creates a new run with critic feedback appended
358
+ - **Score < 50**: Decompose — dispatches typed sub-runs to break down the task
280
359
 
281
- - **Recursive Task Decomposition**: Breaks complex tasks into manageable subtasks
282
- - **Parallel Subagent Execution**: Run multiple specialized agents concurrently
283
- - **8 Specialized Agent Types**: Planning, Code, Testing, Linting, Review, Improve, Context, Publish
284
- - **Multi-Stage Review**: Iterative improvement cycles with quality scoring (0-1 scale)
285
- - **Automatic Test Generation**: Unit, integration, and E2E test creation
360
+ Loop control: max 3 revisions, max depth 2 for decomposition.
286
361
 
287
- ### Usage
362
+ ### Legacy RLM Orchestrator
363
+
364
+ The original RLM system remains available for direct use:
288
365
 
289
366
  ```bash
290
- # Basic usage
291
367
  stackmemory skills rlm "Your complex task description"
292
-
293
- # With options
294
- stackmemory skills rlm "Refactor authentication system" \
295
- --max-parallel 8 \
296
- --review-stages 5 \
297
- --quality-threshold 0.9 \
298
- --test-mode all
368
+ stackmemory skills rlm "Refactor auth" --max-parallel 8 --quality-threshold 0.9
299
369
  ```
300
370
 
301
- ### Configuration Options
302
-
303
371
  | Option | Description | Default |
304
372
  |--------|-------------|---------|
305
373
  | `--max-parallel` | Maximum concurrent subagents | 5 |
@@ -307,7 +375,6 @@ stackmemory skills rlm "Refactor authentication system" \
307
375
  | `--review-stages` | Number of review iterations | 3 |
308
376
  | `--quality-threshold` | Target quality score (0-1) | 0.85 |
309
377
  | `--test-mode` | Test generation mode (unit/integration/e2e/all) | all |
310
- | `--verbose` | Show all recursive operations | false |
311
378
 
312
379
  **Note**: RLM requires Claude Code Max plan for unlimited subagent execution.
313
380
 
@@ -417,18 +484,32 @@ Options: `--until`, `--until-not`, `--until-empty`, `--until-non-empty`, `--unti
417
484
  ## Documentation
418
485
 
419
486
  - [Getting Started](./docs/GETTING_STARTED.md) — Quick start guide (5 minutes)
420
- - [MCP Tools Reference](https://stackmemoryai.github.io/stackmemory/tools.html) — All 56 MCP tools
487
+ - [MCP Tools Reference](https://stackmemoryai.github.io/stackmemory/tools.html) — All 73 MCP tools
421
488
  - [CLI Reference](./docs/cli.md) — Full command reference
422
489
  - [Setup Guide](./docs/SETUP.md) — Advanced setup options
423
490
  - [Development Guide](./docs/DEVELOPMENT.md) — Contributing and development
424
491
  - [Architecture](./docs/architecture.md) — System design
425
492
  - [API Reference](./docs/API_REFERENCE.md) — API documentation
426
- - [Vision](./vision.md) — Product vision and principles
493
+ - [Vision](./docs/vision.md) — Product vision and principles
427
494
  - [Status](./docs/status.md) — Current project status
428
495
  - [Roadmap](./docs/roadmap.md) — Future plans
429
496
 
430
497
  ---
431
498
 
499
+ ## Substrate: The Three-Layer Vision
500
+
501
+ StackMemory is the foundation of a three-layer architecture for AI-native development:
502
+
503
+ | Layer | Product | Status | Purpose |
504
+ |-------|---------|--------|---------|
505
+ | **L1** | StackMemory | Shipped (v1.10.6) | Context persistence — lossless memory across sessions |
506
+ | **L2** | Substrate Desktop | Shipping | Agent orchestration — multi-agent coordination with worktree isolation |
507
+ | **L3** | Substrate Cloud | Design | Multiplayer development — shared context across teams and agents |
508
+
509
+ Grounded in distributed systems theory: FLP impossibility, Byzantine→Crash conversion via test hooks, Chandra-Toueg failure detectors for agent liveness. See [COORDINATION_THEORY.md](./docs/architecture/COORDINATION_THEORY.md).
510
+
511
+ ---
512
+
432
513
  ## License
433
514
 
434
515
  Licensed under the [Business Source License 1.1](./LICENSE). You can use, modify, and self-host StackMemory freely. The one restriction: you may not offer it as a competing hosted service. The license converts to MIT after 4 years per release.
@@ -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
  });