@stackmemoryai/stackmemory 1.10.4 → 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.
- package/README.md +104 -23
- package/dist/src/cli/claude-sm.js +266 -84
- package/dist/src/cli/codex-sm.js +185 -33
- package/dist/src/cli/commands/bench.js +209 -2
- package/dist/src/cli/commands/cache.js +126 -0
- package/dist/src/cli/commands/daemon.js +41 -0
- package/dist/src/cli/commands/handoff.js +40 -9
- package/dist/src/cli/commands/onboard.js +70 -3
- package/dist/src/cli/commands/optimize.js +117 -0
- package/dist/src/cli/commands/orchestrate.js +230 -5
- package/dist/src/cli/commands/orchestrator.js +312 -24
- package/dist/src/cli/commands/pack.js +322 -0
- package/dist/src/cli/commands/search.js +40 -1
- package/dist/src/cli/commands/setup.js +177 -7
- package/dist/src/cli/commands/skills.js +10 -1
- package/dist/src/cli/commands/state.js +265 -0
- package/dist/src/cli/commands/wiki.js +33 -0
- package/dist/src/cli/gemini-sm.js +19 -29
- package/dist/src/cli/index.js +90 -29
- package/dist/src/cli/opencode-sm.js +38 -21
- package/dist/src/cli/utils/determinism-watcher.js +66 -0
- package/dist/src/cli/utils/real-cli-bin.js +44 -0
- package/dist/src/core/cache/content-cache.js +238 -0
- package/dist/src/core/cache/index.js +11 -0
- package/dist/src/core/cache/token-estimator.js +16 -0
- package/dist/src/core/context/frame-database.js +38 -30
- package/dist/src/core/cross-search/cross-project-search.js +269 -0
- package/dist/src/core/{merge → cross-search}/index.js +6 -4
- package/dist/src/core/database/sqlite-adapter.js +0 -83
- package/dist/src/core/extensions/provider-adapter.js +5 -0
- package/dist/src/core/models/model-router.js +22 -2
- package/dist/src/core/monitoring/logger.js +2 -1
- package/dist/src/core/optimization/trace-optimizer.js +413 -0
- package/dist/src/core/provenance/confidence-scorer.js +128 -0
- package/dist/src/core/provenance/index.js +40 -0
- package/dist/src/core/provenance/provenance-store.js +194 -0
- package/dist/src/core/provenance/types.js +82 -0
- package/dist/src/core/session/project-handoff.js +64 -0
- package/dist/src/core/session/session-manager.js +28 -0
- package/dist/src/core/shared-state/canonical-store.js +564 -0
- package/dist/src/core/skill-packs/index.js +18 -0
- package/dist/src/core/skill-packs/parser.js +42 -0
- package/dist/src/core/skill-packs/registry.js +224 -0
- package/dist/src/core/skill-packs/types.js +66 -0
- package/dist/src/core/trace/trace-event-store.js +282 -0
- package/dist/src/core/trace/trace-event.js +4 -0
- package/dist/src/core/wiki/wiki-compiler.js +219 -0
- package/dist/src/daemon/daemon-config.js +7 -0
- package/dist/src/daemon/services/github-service.js +126 -0
- package/dist/src/daemon/unified-daemon.js +30 -0
- package/dist/src/features/sweep/pty-wrapper.js +13 -5
- package/dist/src/hooks/schemas.js +2 -0
- package/dist/src/integrations/claude-code/subagent-client.js +89 -0
- package/dist/src/integrations/github/pr-state.js +158 -0
- package/dist/src/integrations/linear/client.js +4 -1
- package/dist/src/integrations/mcp/handlers/cross-search-handlers.js +188 -0
- package/dist/src/integrations/mcp/handlers/index.js +40 -59
- package/dist/src/integrations/mcp/server.js +425 -311
- package/dist/src/integrations/mcp/tool-alias-registry.js +370 -0
- package/dist/src/integrations/mcp/tool-definitions.js +98 -229
- package/dist/src/integrations/ralph/context/stackmemory-context-loader.js +3 -40
- package/dist/src/integrations/ralph/learning/pattern-learner.js +1 -20
- package/dist/src/integrations/ralph/swarm/swarm-coordinator.js +0 -2
- package/dist/src/mcp/stackmemory-mcp-server.js +315 -0
- package/dist/src/orchestrators/multimodal/determinism.js +243 -0
- package/dist/src/orchestrators/multimodal/harness.js +147 -77
- package/dist/src/orchestrators/multimodal/providers.js +44 -3
- package/dist/src/utils/hook-installer.js +8 -8
- package/package.json +10 -1
- package/packs/coding/python-fastapi/instructions.md +60 -0
- package/packs/coding/python-fastapi/pack.yaml +28 -0
- package/packs/coding/typescript-react/instructions.md +47 -0
- package/packs/coding/typescript-react/pack.yaml +28 -0
- package/packs/core/commands/capture.md +32 -0
- package/packs/core/commands/learn.md +73 -0
- package/packs/core/commands/next.md +36 -0
- package/packs/core/commands/restart.md +58 -0
- package/packs/core/commands/restore.md +29 -0
- package/packs/core/commands/start.md +57 -0
- package/packs/core/commands/stop.md +65 -0
- package/packs/core/commands/summary.md +40 -0
- package/packs/core/manifest.json +24 -0
- package/packs/ops/decision-recovery/instructions.md +65 -0
- package/packs/ops/decision-recovery/pack.yaml +89 -0
- package/templates/claude-hooks/doc-ingest.js +76 -0
- package/dist/src/cli/commands/team.js +0 -168
- package/dist/src/core/context/shared-context-layer.js +0 -620
- package/dist/src/core/context/stack-merge-resolver.js +0 -748
- package/dist/src/core/merge/conflict-detector.js +0 -430
- package/dist/src/core/merge/resolution-engine.js +0 -557
- package/dist/src/core/merge/stack-diff.js +0 -531
- package/dist/src/core/merge/unified-merge-resolver.js +0 -302
- package/dist/src/integrations/mcp/handlers/cord-handlers.js +0 -397
- package/dist/src/integrations/mcp/handlers/team-handlers.js +0 -211
- /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
|
-
- **
|
|
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
|
|
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
|
-
|
|
354
|
+
LLM quality gate that evaluates run output (ported from predict-rlm's critic pattern):
|
|
278
355
|
|
|
279
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
|
403
|
+
const handoff = loadProjectHandoff(
|
|
351
404
|
process.cwd(),
|
|
352
|
-
|
|
353
|
-
"last-handoff.md"
|
|
405
|
+
this.isGitRepo() ? this.getCurrentBranch() : void 0
|
|
354
406
|
);
|
|
355
|
-
if (
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
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(
|
|
377
|
-
path.join(
|
|
435
|
+
path.join(runtimeDirname, "../../templates/claude-hooks"),
|
|
436
|
+
path.join(runtimeDirname, "../../../templates/claude-hooks"),
|
|
378
437
|
path.join(
|
|
379
|
-
|
|
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
|
|
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.
|
|
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.
|
|
856
|
-
|
|
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.
|
|
862
|
-
|
|
1042
|
+
process.on("SIGTERM", async () => {
|
|
1043
|
+
await this.finalizeSession("session_terminate", null, {
|
|
1044
|
+
signal: "SIGTERM"
|
|
863
1045
|
});
|
|
864
1046
|
claude.kill("SIGTERM");
|
|
865
1047
|
});
|