@stackmemoryai/stackmemory 1.2.6 → 1.2.8
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 +9 -3
- package/dist/src/cli/commands/digest.js +73 -0
- package/dist/src/cli/commands/ralph.js +3 -3
- package/dist/src/cli/index.js +2 -0
- package/dist/src/core/digest/chronological-digest.js +143 -0
- package/dist/src/core/digest/index.js +1 -0
- package/dist/src/core/extensions/provider-adapter.js +5 -13
- package/dist/src/core/models/model-router.js +3 -5
- package/dist/src/integrations/mcp/server.js +473 -247
- package/dist/src/integrations/mcp/tool-definitions.js +567 -50
- package/dist/src/integrations/ralph/patterns/oracle-worker-pattern.js +6 -6
- package/package.json +21 -8
- package/templates/claude-hooks/session-rescue.sh +3 -0
- /package/templates/claude-hooks/{auto-background-hook.js → archive/auto-background-hook.js} +0 -0
- /package/templates/claude-hooks/{hook-config.json → archive/hook-config.json} +0 -0
- /package/templates/claude-hooks/{hooks.json → archive/hooks.json} +0 -0
- /package/templates/claude-hooks/{on-compact-detected → archive/on-compact-detected} +0 -0
- /package/templates/claude-hooks/{on-exit → archive/on-exit} +0 -0
- /package/templates/claude-hooks/{post-edit-sweep.js → archive/post-edit-sweep.js} +0 -0
- /package/templates/claude-hooks/{pre-tool-use → archive/pre-tool-use} +0 -0
package/README.md
CHANGED
|
@@ -5,12 +5,16 @@
|
|
|
5
5
|
[](https://codecov.io/gh/stackmemoryai/stackmemory)
|
|
6
6
|
[](https://www.npmjs.com/package/@stackmemoryai/stackmemory)
|
|
7
7
|
|
|
8
|
-
Lossless, project-scoped memory for AI coding tools.
|
|
8
|
+
Lossless, project-scoped memory for AI coding tools. **[Website](https://stackmemoryai.github.io/stackmemory/)** | **[MCP Tools](https://stackmemoryai.github.io/stackmemory/tools.html)** | **[Getting Started](./docs/GETTING_STARTED.md)**
|
|
9
|
+
|
|
10
|
+
<p align="center">
|
|
11
|
+
<img src="site/demo.svg" alt="StackMemory setup demo" width="560">
|
|
12
|
+
</p>
|
|
9
13
|
|
|
10
14
|
StackMemory is a **production-ready memory runtime** for AI coding tools that preserves full project context across sessions:
|
|
11
15
|
|
|
12
16
|
- **Zero-config setup** — `stackmemory init` just works
|
|
13
|
-
- **
|
|
17
|
+
- **55 MCP tools** for Claude Code integration (context, tasks, Linear, traces, discovery, cord, team)
|
|
14
18
|
- **FTS5 full-text search** with BM25 scoring and hybrid retrieval
|
|
15
19
|
- **Full Linear integration** with bidirectional sync and OAuth/API key support
|
|
16
20
|
- **Context persistence** that survives `/clear` operations
|
|
@@ -53,7 +57,7 @@ Tools forget decisions and constraints between sessions. StackMemory makes conte
|
|
|
53
57
|
|
|
54
58
|
## Features
|
|
55
59
|
|
|
56
|
-
- **MCP tools** for Claude Code:
|
|
60
|
+
- **MCP tools** for Claude Code: 56 tools across context, tasks, Linear, traces, planning, discovery, cord, team, and more
|
|
57
61
|
- **FTS5 search**: full-text search with BM25 scoring, hybrid retrieval, and smart thresholds
|
|
58
62
|
- **Skills**: `/spec` (iterative spec generation), `/linear-run` (task execution via RLM)
|
|
59
63
|
- **Hooks**: automatic context save, task tracking, Linear sync, PROMPT_PLAN updates, cord tracing
|
|
@@ -351,6 +355,8 @@ See [docs/cli.md](https://github.com/stackmemoryai/stackmemory/blob/main/docs/cl
|
|
|
351
355
|
|
|
352
356
|
## Documentation
|
|
353
357
|
|
|
358
|
+
- [Getting Started](./docs/GETTING_STARTED.md) — Quick start guide (5 minutes)
|
|
359
|
+
- [MCP Tools Reference](https://stackmemoryai.github.io/stackmemory/tools.html) — All 55 MCP tools
|
|
354
360
|
- [CLI Reference](./docs/cli.md) — Full command reference
|
|
355
361
|
- [Setup Guide](./docs/SETUP.md) — Advanced setup options
|
|
356
362
|
- [Development Guide](./docs/DEVELOPMENT.md) — Contributing and development
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { fileURLToPath as __fileURLToPath } from 'url';
|
|
2
|
+
import { dirname as __pathDirname } from 'path';
|
|
3
|
+
const __filename = __fileURLToPath(import.meta.url);
|
|
4
|
+
const __dirname = __pathDirname(__filename);
|
|
5
|
+
import { Command } from "commander";
|
|
6
|
+
import Database from "better-sqlite3";
|
|
7
|
+
import { join } from "path";
|
|
8
|
+
import { existsSync, writeFileSync, mkdirSync } from "fs";
|
|
9
|
+
import { execSync } from "child_process";
|
|
10
|
+
import {
|
|
11
|
+
generateChronologicalDigest
|
|
12
|
+
} from "../../core/digest/chronological-digest.js";
|
|
13
|
+
function findProjectRoot() {
|
|
14
|
+
let dir = process.cwd();
|
|
15
|
+
while (dir !== "/") {
|
|
16
|
+
if (existsSync(join(dir, ".git"))) return dir;
|
|
17
|
+
dir = join(dir, "..");
|
|
18
|
+
}
|
|
19
|
+
return process.cwd();
|
|
20
|
+
}
|
|
21
|
+
function getProjectId(projectRoot) {
|
|
22
|
+
let identifier;
|
|
23
|
+
try {
|
|
24
|
+
identifier = execSync("git config --get remote.origin.url", {
|
|
25
|
+
cwd: projectRoot,
|
|
26
|
+
stdio: "pipe",
|
|
27
|
+
timeout: 5e3
|
|
28
|
+
}).toString().trim();
|
|
29
|
+
} catch {
|
|
30
|
+
identifier = projectRoot;
|
|
31
|
+
}
|
|
32
|
+
const cleaned = identifier.replace(/\.git$/, "").replace(/[^a-zA-Z0-9-]/g, "-").toLowerCase();
|
|
33
|
+
return cleaned.substring(cleaned.length - 50) || "unknown";
|
|
34
|
+
}
|
|
35
|
+
function createDigestCommands() {
|
|
36
|
+
const digest = new Command("digest").description("Generate chronological activity digest").argument("<period>", "Time period: today, yesterday, or week").option("-o, --output <path>", "Custom output path").action((period, options) => {
|
|
37
|
+
const validPeriods = ["today", "yesterday", "week"];
|
|
38
|
+
if (!validPeriods.includes(period)) {
|
|
39
|
+
console.error(
|
|
40
|
+
`Invalid period "${period}". Use: ${validPeriods.join(", ")}`
|
|
41
|
+
);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
const projectRoot = findProjectRoot();
|
|
45
|
+
const dbPath = join(projectRoot, ".stackmemory", "context.db");
|
|
46
|
+
if (!existsSync(dbPath)) {
|
|
47
|
+
console.error(
|
|
48
|
+
"No StackMemory database found. Run stackmemory in a project first."
|
|
49
|
+
);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
const db = new Database(dbPath, { readonly: true });
|
|
53
|
+
const projectId = getProjectId(projectRoot);
|
|
54
|
+
try {
|
|
55
|
+
const markdown = generateChronologicalDigest(
|
|
56
|
+
db,
|
|
57
|
+
period,
|
|
58
|
+
projectId
|
|
59
|
+
);
|
|
60
|
+
const smDir = join(projectRoot, ".stackmemory");
|
|
61
|
+
if (!existsSync(smDir)) mkdirSync(smDir, { recursive: true });
|
|
62
|
+
const outputPath = options.output || join(smDir, `${period}.md`);
|
|
63
|
+
writeFileSync(outputPath, markdown);
|
|
64
|
+
console.log(`Digest written to ${outputPath}`);
|
|
65
|
+
} finally {
|
|
66
|
+
db.close();
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
return digest;
|
|
70
|
+
}
|
|
71
|
+
export {
|
|
72
|
+
createDigestCommands
|
|
73
|
+
};
|
|
@@ -317,12 +317,12 @@ ${contextResponse.context}`;
|
|
|
317
317
|
"Launch Oracle/Worker pattern swarm for cost-effective execution"
|
|
318
318
|
).argument("<project>", "Project description for Oracle planning").option(
|
|
319
319
|
"--oracle <model>",
|
|
320
|
-
"Oracle model (default: claude-
|
|
321
|
-
"claude-
|
|
320
|
+
"Oracle model (default: claude-sonnet-4-5)",
|
|
321
|
+
"claude-sonnet-4-5-20250929"
|
|
322
322
|
).option(
|
|
323
323
|
"--workers <models>",
|
|
324
324
|
"Comma-separated worker models",
|
|
325
|
-
"claude-
|
|
325
|
+
"claude-haiku-4-5-20251001"
|
|
326
326
|
).option("--budget <amount>", "Cost budget in USD", "10.0").option("--max-workers <count>", "Maximum worker agents", "5").option("--hints <hints>", "Comma-separated planning hints").action(async (project, options) => {
|
|
327
327
|
return trace.command(
|
|
328
328
|
"ralph-oracle-worker",
|
package/dist/src/cli/index.js
CHANGED
|
@@ -54,6 +54,7 @@ import { createPingCommand } from "./commands/ping.js";
|
|
|
54
54
|
import { createAuditCommand } from "./commands/audit.js";
|
|
55
55
|
import { createStatsCommand } from "./commands/stats.js";
|
|
56
56
|
import { createBenchCommand } from "./commands/bench.js";
|
|
57
|
+
import { createDigestCommands } from "./commands/digest.js";
|
|
57
58
|
import chalk from "chalk";
|
|
58
59
|
import * as fs from "fs";
|
|
59
60
|
import * as path from "path";
|
|
@@ -501,6 +502,7 @@ program.addCommand(createModelCommand());
|
|
|
501
502
|
program.addCommand(createAuditCommand());
|
|
502
503
|
program.addCommand(createStatsCommand());
|
|
503
504
|
program.addCommand(createBenchCommand());
|
|
505
|
+
program.addCommand(createDigestCommands());
|
|
504
506
|
registerSetupCommands(program);
|
|
505
507
|
program.command("mm-spike").description(
|
|
506
508
|
"Run multi-agent planning/implementation spike (planner/implementer/critic)"
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { fileURLToPath as __fileURLToPath } from 'url';
|
|
2
|
+
import { dirname as __pathDirname } from 'path';
|
|
3
|
+
const __filename = __fileURLToPath(import.meta.url);
|
|
4
|
+
const __dirname = __pathDirname(__filename);
|
|
5
|
+
function getTimeRange(period) {
|
|
6
|
+
const now = /* @__PURE__ */ new Date();
|
|
7
|
+
const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
8
|
+
switch (period) {
|
|
9
|
+
case "today": {
|
|
10
|
+
return {
|
|
11
|
+
start: Math.floor(todayStart.getTime() / 1e3),
|
|
12
|
+
end: Math.floor(now.getTime() / 1e3),
|
|
13
|
+
label: `Today \u2014 ${todayStart.toISOString().slice(0, 10)}`
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
case "yesterday": {
|
|
17
|
+
const yesterdayStart = new Date(todayStart);
|
|
18
|
+
yesterdayStart.setDate(yesterdayStart.getDate() - 1);
|
|
19
|
+
return {
|
|
20
|
+
start: Math.floor(yesterdayStart.getTime() / 1e3),
|
|
21
|
+
end: Math.floor(todayStart.getTime() / 1e3),
|
|
22
|
+
label: `Yesterday \u2014 ${yesterdayStart.toISOString().slice(0, 10)}`
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
case "week": {
|
|
26
|
+
const weekStart = new Date(todayStart);
|
|
27
|
+
weekStart.setDate(weekStart.getDate() - 7);
|
|
28
|
+
return {
|
|
29
|
+
start: Math.floor(weekStart.getTime() / 1e3),
|
|
30
|
+
end: Math.floor(now.getTime() / 1e3),
|
|
31
|
+
label: `Week \u2014 ${weekStart.toISOString().slice(0, 10)} to ${todayStart.toISOString().slice(0, 10)}`
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function formatDate(epoch) {
|
|
37
|
+
return new Date(epoch * 1e3).toISOString().slice(0, 10);
|
|
38
|
+
}
|
|
39
|
+
function generateChronologicalDigest(db, period, projectId) {
|
|
40
|
+
const { start, end, label } = getTimeRange(period);
|
|
41
|
+
let frames = db.prepare(
|
|
42
|
+
`SELECT frame_id, name, type, state, created_at, closed_at, inputs, outputs
|
|
43
|
+
FROM frames
|
|
44
|
+
WHERE project_id = ? AND created_at >= ? AND created_at < ?
|
|
45
|
+
ORDER BY created_at ASC`
|
|
46
|
+
).all(projectId, start, end);
|
|
47
|
+
if (frames.length === 0 && projectId !== "default") {
|
|
48
|
+
frames = db.prepare(
|
|
49
|
+
`SELECT frame_id, name, type, state, created_at, closed_at, inputs, outputs
|
|
50
|
+
FROM frames
|
|
51
|
+
WHERE project_id = 'default' AND created_at >= ? AND created_at < ?
|
|
52
|
+
ORDER BY created_at ASC`
|
|
53
|
+
).all(start, end);
|
|
54
|
+
}
|
|
55
|
+
if (frames.length === 0) {
|
|
56
|
+
return `# ${label}
|
|
57
|
+
|
|
58
|
+
No activity recorded.
|
|
59
|
+
`;
|
|
60
|
+
}
|
|
61
|
+
const frameIds = frames.map((f) => f.frame_id);
|
|
62
|
+
const placeholders = frameIds.map(() => "?").join(",");
|
|
63
|
+
const anchors = db.prepare(
|
|
64
|
+
`SELECT anchor_id, frame_id, type, text, priority, created_at
|
|
65
|
+
FROM anchors
|
|
66
|
+
WHERE frame_id IN (${placeholders})
|
|
67
|
+
ORDER BY priority DESC, created_at ASC`
|
|
68
|
+
).all(...frameIds);
|
|
69
|
+
const events = db.prepare(
|
|
70
|
+
`SELECT event_id, frame_id, event_type, payload, ts
|
|
71
|
+
FROM events
|
|
72
|
+
WHERE frame_id IN (${placeholders}) AND event_type IN ('tool_call', 'decision')
|
|
73
|
+
ORDER BY ts ASC`
|
|
74
|
+
).all(...frameIds);
|
|
75
|
+
const anchorsByFrame = /* @__PURE__ */ new Map();
|
|
76
|
+
for (const a of anchors) {
|
|
77
|
+
const list = anchorsByFrame.get(a.frame_id) || [];
|
|
78
|
+
list.push(a);
|
|
79
|
+
anchorsByFrame.set(a.frame_id, list);
|
|
80
|
+
}
|
|
81
|
+
const eventsByFrame = /* @__PURE__ */ new Map();
|
|
82
|
+
for (const e of events) {
|
|
83
|
+
const list = eventsByFrame.get(e.frame_id) || [];
|
|
84
|
+
list.push(e);
|
|
85
|
+
eventsByFrame.set(e.frame_id, list);
|
|
86
|
+
}
|
|
87
|
+
const framesByDate = /* @__PURE__ */ new Map();
|
|
88
|
+
for (const f of frames) {
|
|
89
|
+
const date = formatDate(f.created_at);
|
|
90
|
+
const list = framesByDate.get(date) || [];
|
|
91
|
+
list.push(f);
|
|
92
|
+
framesByDate.set(date, list);
|
|
93
|
+
}
|
|
94
|
+
const lines = [`# ${label}
|
|
95
|
+
`];
|
|
96
|
+
const renderFrame = (f) => {
|
|
97
|
+
lines.push(`## ${f.name} (${f.type}, ${f.state})`);
|
|
98
|
+
const frameAnchors = anchorsByFrame.get(f.frame_id) || [];
|
|
99
|
+
const frameEvents = eventsByFrame.get(f.frame_id) || [];
|
|
100
|
+
for (const a of frameAnchors.slice(0, 8)) {
|
|
101
|
+
lines.push(`- ${a.type}: ${a.text}`);
|
|
102
|
+
}
|
|
103
|
+
const files = /* @__PURE__ */ new Set();
|
|
104
|
+
for (const e of frameEvents) {
|
|
105
|
+
try {
|
|
106
|
+
const payload = JSON.parse(e.payload);
|
|
107
|
+
if (payload.arguments?.file_path)
|
|
108
|
+
files.add(payload.arguments.file_path);
|
|
109
|
+
if (payload.arguments?.path) files.add(payload.arguments.path);
|
|
110
|
+
} catch {
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (files.size > 0) {
|
|
114
|
+
lines.push(`- ${files.size} files touched`);
|
|
115
|
+
}
|
|
116
|
+
lines.push("");
|
|
117
|
+
};
|
|
118
|
+
if (period === "week") {
|
|
119
|
+
for (const [date, dateFrames] of framesByDate) {
|
|
120
|
+
lines.push(`### ${date}
|
|
121
|
+
`);
|
|
122
|
+
for (const f of dateFrames) {
|
|
123
|
+
renderFrame(f);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
} else {
|
|
127
|
+
for (const f of frames) {
|
|
128
|
+
renderFrame(f);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
const completed = frames.filter((f) => f.state === "completed").length;
|
|
132
|
+
const active = frames.filter((f) => f.state === "active").length;
|
|
133
|
+
lines.push("---");
|
|
134
|
+
lines.push(
|
|
135
|
+
`*${frames.length} frames total: ${completed} completed, ${active} active*`
|
|
136
|
+
);
|
|
137
|
+
lines.push(`*Generated: ${(/* @__PURE__ */ new Date()).toISOString()}*
|
|
138
|
+
`);
|
|
139
|
+
return lines.join("\n");
|
|
140
|
+
}
|
|
141
|
+
export {
|
|
142
|
+
generateChronologicalDigest
|
|
143
|
+
};
|
|
@@ -113,10 +113,11 @@ class ClaudeAdapter {
|
|
|
113
113
|
}
|
|
114
114
|
async listModels() {
|
|
115
115
|
return [
|
|
116
|
-
"claude-opus-4-
|
|
116
|
+
"claude-opus-4-6",
|
|
117
|
+
"claude-sonnet-4-5-20250929",
|
|
118
|
+
"claude-haiku-4-5-20251001",
|
|
117
119
|
"claude-sonnet-4-20250514",
|
|
118
|
-
"claude-3-5-haiku-20241022"
|
|
119
|
-
"claude-3-opus-20240229"
|
|
120
|
+
"claude-3-5-haiku-20241022"
|
|
120
121
|
];
|
|
121
122
|
}
|
|
122
123
|
buildRequestBody(messages, options) {
|
|
@@ -360,16 +361,7 @@ class GPTAdapter {
|
|
|
360
361
|
}
|
|
361
362
|
}
|
|
362
363
|
async listModels() {
|
|
363
|
-
return [
|
|
364
|
-
"gpt-4o",
|
|
365
|
-
"gpt-4o-mini",
|
|
366
|
-
"gpt-4-turbo",
|
|
367
|
-
"gpt-4",
|
|
368
|
-
"gpt-3.5-turbo",
|
|
369
|
-
"o1",
|
|
370
|
-
"o1-mini",
|
|
371
|
-
"o1-preview"
|
|
372
|
-
];
|
|
364
|
+
return ["gpt-4o", "gpt-4o-mini", "o3-mini", "o4-mini"];
|
|
373
365
|
}
|
|
374
366
|
}
|
|
375
367
|
function createProvider(id, config) {
|
|
@@ -22,16 +22,14 @@ const MODEL_TOKEN_LIMITS = {
|
|
|
22
22
|
"claude-sonnet-4-5-20250929": 2e5,
|
|
23
23
|
"claude-haiku-4-5-20251001": 2e5,
|
|
24
24
|
"claude-sonnet-4-20250514": 2e5,
|
|
25
|
-
// Claude 3.x
|
|
25
|
+
// Claude 3.x (legacy, still functional)
|
|
26
26
|
"claude-3-5-sonnet-20241022": 2e5,
|
|
27
27
|
"claude-3-5-haiku-20241022": 2e5,
|
|
28
|
-
"claude-3-opus-20240229": 2e5,
|
|
29
28
|
// OpenAI
|
|
30
29
|
"gpt-4o": 128e3,
|
|
31
|
-
"gpt-
|
|
32
|
-
"gpt-4": 8192,
|
|
33
|
-
o1: 2e5,
|
|
30
|
+
"gpt-4o-mini": 128e3,
|
|
34
31
|
"o3-mini": 2e5,
|
|
32
|
+
"o4-mini": 2e5,
|
|
35
33
|
// Qwen
|
|
36
34
|
"qwen3-max-2025-01-23": 128e3,
|
|
37
35
|
// Cerebras
|