Skip to content

Commit c5f6ab0

Browse files
author
StackMemory Bot (CLI)
committed
feat(mcp): wire cord/team tools, add digest feature, clean up dead code
- Wire 8 cord + team tools into server.ts dispatch (5 cord, 3 team) - Add sm_digest MCP tool and CLI command (today/yesterday/week) - Sync tool-definitions.ts with 8 new categories (28 tools added) - Remove 4 dead browser debug stubs, 1 dead plan_and_code tool - Gate planning tools behind ANTHROPIC_API_KEY, diffmem behind DIFFMEM_ENDPOINT - Archive 7 unused hook templates - Update tool count to 55 across README, site, package.json
1 parent 9fbfdce commit c5f6ab0

File tree

20 files changed

+1416
-308
lines changed

20 files changed

+1416
-308
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ Lossless, project-scoped memory for AI coding tools. **[Website](https://stackme
1414
StackMemory is a **production-ready memory runtime** for AI coding tools that preserves full project context across sessions:
1515

1616
- **Zero-config setup**`stackmemory init` just works
17-
- **32 MCP tools** for Claude Code integration (context, tasks, Linear, traces, discovery, cord, team)
17+
- **55 MCP tools** for Claude Code integration (context, tasks, Linear, traces, discovery, cord, team)
1818
- **FTS5 full-text search** with BM25 scoring and hybrid retrieval
1919
- **Full Linear integration** with bidirectional sync and OAuth/API key support
2020
- **Context persistence** that survives `/clear` operations
@@ -57,7 +57,7 @@ Tools forget decisions and constraints between sessions. StackMemory makes conte
5757

5858
## Features
5959

60-
- **MCP tools** for Claude Code: 36 tools across context, tasks, Linear, traces, discovery, cord, and team
60+
- **MCP tools** for Claude Code: 56 tools across context, tasks, Linear, traces, planning, discovery, cord, team, and more
6161
- **FTS5 search**: full-text search with BM25 scoring, hybrid retrieval, and smart thresholds
6262
- **Skills**: `/spec` (iterative spec generation), `/linear-run` (task execution via RLM)
6363
- **Hooks**: automatic context save, task tracking, Linear sync, PROMPT_PLAN updates, cord tracing
@@ -356,7 +356,7 @@ See [docs/cli.md](https://github.com/stackmemoryai/stackmemory/blob/main/docs/cl
356356
## Documentation
357357

358358
- [Getting Started](./docs/GETTING_STARTED.md) — Quick start guide (5 minutes)
359-
- [MCP Tools Reference](https://stackmemoryai.github.io/stackmemory/tools.html) — All 32 MCP tools
359+
- [MCP Tools Reference](https://stackmemoryai.github.io/stackmemory/tools.html) — All 55 MCP tools
360360
- [CLI Reference](./docs/cli.md) — Full command reference
361361
- [Setup Guide](./docs/SETUP.md) — Advanced setup options
362362
- [Development Guide](./docs/DEVELOPMENT.md) — Contributing and development

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@stackmemoryai/stackmemory",
33
"version": "1.2.7",
4-
"description": "Project-scoped memory for AI coding tools. Durable context across sessions with 32 MCP tools, FTS5 search, Claude/Codex/OpenCode wrappers, Linear sync, automatic hooks, and log analysis.",
4+
"description": "Project-scoped memory for AI coding tools. Durable context across sessions with 55 MCP tools, FTS5 search, Claude/Codex/OpenCode wrappers, Linear sync, automatic hooks, and log analysis.",
55
"engines": {
66
"node": ">=20.0.0",
77
"npm": ">=10.0.0"

site/demo.svg

Lines changed: 1 addition & 1 deletion
Loading

site/index.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@
99
<!-- Open Graph -->
1010
<meta property="og:type" content="website">
1111
<meta property="og:title" content="StackMemory — Lossless memory for AI coding tools">
12-
<meta property="og:description" content="Project-scoped memory runtime that preserves full context across sessions. 32 MCP tools, FTS5 search, Linear sync, Claude/Codex/OpenCode wrappers.">
12+
<meta property="og:description" content="Project-scoped memory runtime that preserves full context across sessions. 55 MCP tools, FTS5 search, Linear sync, Claude/Codex/OpenCode wrappers.">
1313
<meta property="og:url" content="https://stackmemoryai.github.io/stackmemory/">
1414
<meta property="og:image" content="https://stackmemoryai.github.io/stackmemory/og-image.png">
1515
<meta property="og:site_name" content="StackMemory">
1616

1717
<!-- Twitter Card -->
1818
<meta name="twitter:card" content="summary_large_image">
1919
<meta name="twitter:title" content="StackMemory — Lossless memory for AI coding tools">
20-
<meta name="twitter:description" content="Project-scoped memory runtime that preserves full context across sessions. 32 MCP tools, FTS5 search, Linear sync.">
20+
<meta name="twitter:description" content="Project-scoped memory runtime that preserves full context across sessions. 55 MCP tools, FTS5 search, Linear sync.">
2121
<meta name="twitter:image" content="https://stackmemoryai.github.io/stackmemory/og-image.png">
2222

2323
<link rel="icon" href="favicon.svg" type="image/svg+xml">

site/og-image.svg

Lines changed: 1 addition & 1 deletion
Loading

site/tools.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44
<meta charset="UTF-8">
55
<meta name="viewport" content="width=device-width, initial-scale=1.0">
66
<title>MCP Tools Reference — StackMemory</title>
7-
<meta name="description" content="Complete reference for StackMemory's 32 MCP tools: context management, task tracking, Linear integration, discovery, traces, team collaboration, and cord orchestration.">
7+
<meta name="description" content="Complete reference for StackMemory's 55 MCP tools: context management, task tracking, Linear integration, discovery, traces, team collaboration, and cord orchestration.">
88
<link rel="icon" href="favicon.svg" type="image/svg+xml">
99
<meta property="og:title" content="StackMemory MCP Tools Reference">
10-
<meta property="og:description" content="32 MCP tools for AI coding context management.">
10+
<meta property="og:description" content="55 MCP tools for AI coding context management.">
1111
<meta property="og:url" content="https://stackmemoryai.github.io/stackmemory/tools.html">
1212
<script src="https://cdn.tailwindcss.com"></script>
1313
<script>

src/cli/commands/digest.ts

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/**
2+
* Digest Command for StackMemory CLI
3+
* Generates chronological activity summaries (today/yesterday/week)
4+
*/
5+
6+
import { Command } from 'commander';
7+
import Database from 'better-sqlite3';
8+
import { join } from 'path';
9+
import { existsSync, writeFileSync, mkdirSync } from 'fs';
10+
import { execSync } from 'child_process';
11+
import {
12+
generateChronologicalDigest,
13+
type DigestPeriod,
14+
} from '../../core/digest/chronological-digest.js';
15+
16+
function findProjectRoot(): string {
17+
let dir = process.cwd();
18+
while (dir !== '/') {
19+
if (existsSync(join(dir, '.git'))) return dir;
20+
dir = join(dir, '..');
21+
}
22+
return process.cwd();
23+
}
24+
25+
function getProjectId(projectRoot: string): string {
26+
let identifier: string;
27+
try {
28+
identifier = execSync('git config --get remote.origin.url', {
29+
cwd: projectRoot,
30+
stdio: 'pipe',
31+
timeout: 5000,
32+
})
33+
.toString()
34+
.trim();
35+
} catch {
36+
identifier = projectRoot;
37+
}
38+
const cleaned = identifier
39+
.replace(/\.git$/, '')
40+
.replace(/[^a-zA-Z0-9-]/g, '-')
41+
.toLowerCase();
42+
return cleaned.substring(cleaned.length - 50) || 'unknown';
43+
}
44+
45+
export function createDigestCommands(): Command {
46+
const digest = new Command('digest')
47+
.description('Generate chronological activity digest')
48+
.argument('<period>', 'Time period: today, yesterday, or week')
49+
.option('-o, --output <path>', 'Custom output path')
50+
.action((period: string, options: { output?: string }) => {
51+
const validPeriods: DigestPeriod[] = ['today', 'yesterday', 'week'];
52+
if (!validPeriods.includes(period as DigestPeriod)) {
53+
console.error(
54+
`Invalid period "${period}". Use: ${validPeriods.join(', ')}`
55+
);
56+
process.exit(1);
57+
}
58+
59+
const projectRoot = findProjectRoot();
60+
const dbPath = join(projectRoot, '.stackmemory', 'context.db');
61+
62+
if (!existsSync(dbPath)) {
63+
console.error(
64+
'No StackMemory database found. Run stackmemory in a project first.'
65+
);
66+
process.exit(1);
67+
}
68+
69+
const db = new Database(dbPath, { readonly: true });
70+
const projectId = getProjectId(projectRoot);
71+
72+
try {
73+
const markdown = generateChronologicalDigest(
74+
db,
75+
period as DigestPeriod,
76+
projectId
77+
);
78+
79+
const smDir = join(projectRoot, '.stackmemory');
80+
if (!existsSync(smDir)) mkdirSync(smDir, { recursive: true });
81+
82+
const outputPath = options.output || join(smDir, `${period}.md`);
83+
writeFileSync(outputPath, markdown);
84+
console.log(`Digest written to ${outputPath}`);
85+
} finally {
86+
db.close();
87+
}
88+
});
89+
90+
return digest;
91+
}

src/cli/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ import { createPingCommand } from './commands/ping.js';
6161
import { createAuditCommand } from './commands/audit.js';
6262
import { createStatsCommand } from './commands/stats.js';
6363
import { createBenchCommand } from './commands/bench.js';
64+
import { createDigestCommands } from './commands/digest.js';
6465
import chalk from 'chalk';
6566
import * as fs from 'fs';
6667
import * as path from 'path';
@@ -682,6 +683,7 @@ program.addCommand(createModelCommand());
682683
program.addCommand(createAuditCommand());
683684
program.addCommand(createStatsCommand());
684685
program.addCommand(createBenchCommand());
686+
program.addCommand(createDigestCommands());
685687

686688
// Register setup and diagnostic commands
687689
registerSetupCommands(program);
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
/**
2+
* Chronological Digest Generator
3+
* Produces compact markdown summaries of activity for today/yesterday/week periods.
4+
*/
5+
6+
import type Database from 'better-sqlite3';
7+
8+
export type DigestPeriod = 'today' | 'yesterday' | 'week';
9+
10+
interface FrameRow {
11+
frame_id: string;
12+
name: string;
13+
type: string;
14+
state: string;
15+
created_at: number;
16+
closed_at: number | null;
17+
inputs: string;
18+
outputs: string;
19+
}
20+
21+
interface AnchorRow {
22+
anchor_id: string;
23+
frame_id: string;
24+
type: string;
25+
text: string;
26+
priority: number;
27+
created_at: number;
28+
}
29+
30+
interface EventRow {
31+
event_id: string;
32+
frame_id: string;
33+
event_type: string;
34+
payload: string;
35+
ts: number;
36+
}
37+
38+
function getTimeRange(period: DigestPeriod): {
39+
start: number;
40+
end: number;
41+
label: string;
42+
} {
43+
const now = new Date();
44+
const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());
45+
46+
switch (period) {
47+
case 'today': {
48+
return {
49+
start: Math.floor(todayStart.getTime() / 1000),
50+
end: Math.floor(now.getTime() / 1000),
51+
label: `Today — ${todayStart.toISOString().slice(0, 10)}`,
52+
};
53+
}
54+
case 'yesterday': {
55+
const yesterdayStart = new Date(todayStart);
56+
yesterdayStart.setDate(yesterdayStart.getDate() - 1);
57+
return {
58+
start: Math.floor(yesterdayStart.getTime() / 1000),
59+
end: Math.floor(todayStart.getTime() / 1000),
60+
label: `Yesterday — ${yesterdayStart.toISOString().slice(0, 10)}`,
61+
};
62+
}
63+
case 'week': {
64+
const weekStart = new Date(todayStart);
65+
weekStart.setDate(weekStart.getDate() - 7);
66+
return {
67+
start: Math.floor(weekStart.getTime() / 1000),
68+
end: Math.floor(now.getTime() / 1000),
69+
label: `Week — ${weekStart.toISOString().slice(0, 10)} to ${todayStart.toISOString().slice(0, 10)}`,
70+
};
71+
}
72+
}
73+
}
74+
75+
function formatDate(epoch: number): string {
76+
return new Date(epoch * 1000).toISOString().slice(0, 10);
77+
}
78+
79+
export function generateChronologicalDigest(
80+
db: Database.Database,
81+
period: DigestPeriod,
82+
projectId: string
83+
): string {
84+
const { start, end, label } = getTimeRange(period);
85+
86+
// Query frames in the time window
87+
const frames = db
88+
.prepare(
89+
`SELECT frame_id, name, type, state, created_at, closed_at, inputs, outputs
90+
FROM frames
91+
WHERE project_id = ? AND created_at >= ? AND created_at < ?
92+
ORDER BY created_at ASC`
93+
)
94+
.all(projectId, start, end) as FrameRow[];
95+
96+
if (frames.length === 0) {
97+
return `# ${label}\n\nNo activity recorded.\n`;
98+
}
99+
100+
// Query anchors for these frames
101+
const frameIds = frames.map((f) => f.frame_id);
102+
const placeholders = frameIds.map(() => '?').join(',');
103+
const anchors = db
104+
.prepare(
105+
`SELECT anchor_id, frame_id, type, text, priority, created_at
106+
FROM anchors
107+
WHERE frame_id IN (${placeholders})
108+
ORDER BY priority DESC, created_at ASC`
109+
)
110+
.all(...frameIds) as AnchorRow[];
111+
112+
// Query events for file counts (tool_call events with file_path)
113+
const events = db
114+
.prepare(
115+
`SELECT event_id, frame_id, event_type, payload, ts
116+
FROM events
117+
WHERE frame_id IN (${placeholders}) AND event_type IN ('tool_call', 'decision')
118+
ORDER BY ts ASC`
119+
)
120+
.all(...frameIds) as EventRow[];
121+
122+
// Group anchors and events by frame
123+
const anchorsByFrame = new Map<string, AnchorRow[]>();
124+
for (const a of anchors) {
125+
const list = anchorsByFrame.get(a.frame_id) || [];
126+
list.push(a);
127+
anchorsByFrame.set(a.frame_id, list);
128+
}
129+
130+
const eventsByFrame = new Map<string, EventRow[]>();
131+
for (const e of events) {
132+
const list = eventsByFrame.get(e.frame_id) || [];
133+
list.push(e);
134+
eventsByFrame.set(e.frame_id, list);
135+
}
136+
137+
// Group frames by date for week view
138+
const framesByDate = new Map<string, FrameRow[]>();
139+
for (const f of frames) {
140+
const date = formatDate(f.created_at);
141+
const list = framesByDate.get(date) || [];
142+
list.push(f);
143+
framesByDate.set(date, list);
144+
}
145+
146+
const lines: string[] = [`# ${label}\n`];
147+
148+
const renderFrame = (f: FrameRow) => {
149+
lines.push(`## ${f.name} (${f.type}, ${f.state})`);
150+
151+
const frameAnchors = anchorsByFrame.get(f.frame_id) || [];
152+
const frameEvents = eventsByFrame.get(f.frame_id) || [];
153+
154+
// Key decisions and constraints
155+
for (const a of frameAnchors.slice(0, 8)) {
156+
lines.push(`- ${a.type}: ${a.text}`);
157+
}
158+
159+
// Count files from tool_call events
160+
const files = new Set<string>();
161+
for (const e of frameEvents) {
162+
try {
163+
const payload = JSON.parse(e.payload);
164+
if (payload.arguments?.file_path)
165+
files.add(payload.arguments.file_path);
166+
if (payload.arguments?.path) files.add(payload.arguments.path);
167+
} catch {
168+
// ignore parse errors
169+
}
170+
}
171+
172+
if (files.size > 0) {
173+
lines.push(`- ${files.size} files touched`);
174+
}
175+
176+
lines.push('');
177+
};
178+
179+
if (period === 'week') {
180+
// Week: group by date
181+
for (const [date, dateFrames] of framesByDate) {
182+
lines.push(`### ${date}\n`);
183+
for (const f of dateFrames) {
184+
renderFrame(f);
185+
}
186+
}
187+
} else {
188+
// Today/yesterday: flat list
189+
for (const f of frames) {
190+
renderFrame(f);
191+
}
192+
}
193+
194+
// Summary stats
195+
const completed = frames.filter((f) => f.state === 'completed').length;
196+
const active = frames.filter((f) => f.state === 'active').length;
197+
lines.push('---');
198+
lines.push(
199+
`*${frames.length} frames total: ${completed} completed, ${active} active*`
200+
);
201+
lines.push(`*Generated: ${new Date().toISOString()}*\n`);
202+
203+
return lines.join('\n');
204+
}

0 commit comments

Comments
 (0)