@stackmemoryai/stackmemory 1.2.0 → 1.2.2

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 (47) hide show
  1. package/dist/src/cli/claude-sm.js +65 -0
  2. package/dist/src/cli/commands/skills.js +123 -1
  3. package/dist/src/hooks/graphiti-hooks.js +149 -0
  4. package/dist/src/hooks/session-summary.js +30 -0
  5. package/dist/src/integrations/graphiti/linear-graphiti-bridge.js +115 -0
  6. package/dist/src/integrations/greptile/client.js +101 -0
  7. package/dist/src/integrations/greptile/config.js +14 -0
  8. package/dist/src/integrations/greptile/index.js +11 -0
  9. package/dist/src/integrations/greptile/types.js +4 -0
  10. package/dist/src/integrations/linear/webhook.js +16 -0
  11. package/dist/src/integrations/mcp/handlers/greptile-handlers.js +456 -0
  12. package/dist/src/integrations/mcp/server.js +136 -0
  13. package/dist/src/integrations/mcp/tool-definitions.js +53 -1
  14. package/dist/src/skills/claude-skills.js +46 -1
  15. package/dist/src/skills/parallel-agent-skill.js +514 -0
  16. package/dist/src/utils/hook-installer.js +155 -0
  17. package/package.json +2 -2
  18. package/scripts/gepa/.before-optimize.md +140 -0
  19. package/scripts/gepa/config.json +7 -1
  20. package/scripts/gepa/evals/fixtures/api-endpoint.ts +31 -0
  21. package/scripts/gepa/evals/fixtures/brittle-integration.ts +38 -0
  22. package/scripts/gepa/evals/fixtures/fts5-triggers.sql +23 -0
  23. package/scripts/gepa/evals/fixtures/leaky-service.ts +70 -0
  24. package/scripts/gepa/evals/fixtures/mcp-dispatch-stub.ts +39 -0
  25. package/scripts/gepa/evals/fixtures/pr-diff.txt +24 -0
  26. package/scripts/gepa/evals/fixtures/unsafe-webhook.ts +34 -0
  27. package/scripts/gepa/evals/fixtures/unwrapped-db-op.ts +42 -0
  28. package/scripts/gepa/evals/stackmemory-tasks.jsonl +8 -0
  29. package/scripts/gepa/generations/gen-000/baseline.md +48 -0
  30. package/scripts/gepa/generations/gen-001/baseline.md +172 -0
  31. package/scripts/gepa/generations/gen-001/variant-a.md +176 -0
  32. package/scripts/gepa/generations/gen-001/variant-b.md +266 -0
  33. package/scripts/gepa/generations/gen-001/variant-c.md +142 -0
  34. package/scripts/gepa/generations/gen-001/variant-d.md +172 -0
  35. package/scripts/gepa/hooks/reflect.js +44 -5
  36. package/scripts/gepa/optimize.js +281 -39
  37. package/scripts/gepa/results/eval-1-baseline.json +218 -0
  38. package/scripts/gepa/results/eval-1-variant-a.json +218 -0
  39. package/scripts/gepa/results/eval-1-variant-b.json +218 -0
  40. package/scripts/gepa/results/eval-1-variant-c.json +198 -0
  41. package/scripts/gepa/results/eval-1-variant-d.json +198 -0
  42. package/scripts/gepa/state.json +44 -5
  43. package/scripts/install-claude-hooks-auto.js +176 -44
  44. package/templates/claude-hooks/auto-checkpoint.js +174 -0
  45. package/templates/claude-hooks/chime-on-stop.sh +22 -0
  46. package/templates/claude-hooks/session-rescue.sh +15 -0
  47. package/templates/claude-hooks/stop-checkpoint.js +120 -0
@@ -0,0 +1,174 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Auto-Checkpoint Hook (PostToolUse)
5
+ *
6
+ * Counts tool calls per project session. Every 25 calls, writes a lightweight
7
+ * checkpoint to ~/.stackmemory/checkpoints/{project-hash}/latest.json.
8
+ * Must complete in <50ms -- pure file I/O only.
9
+ *
10
+ * State is keyed per-session (process.ppid) to avoid race conditions
11
+ * when multiple Claude instances run concurrently.
12
+ */
13
+
14
+ const fs = require('fs');
15
+ const path = require('path');
16
+ const crypto = require('crypto');
17
+
18
+ const CHECKPOINT_INTERVAL = 25;
19
+ const SAVE_INTERVAL = 5;
20
+ const MAX_RECENT_TOOLS = 20;
21
+ const MAX_FILES_TRACKED = 50;
22
+ const TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
23
+
24
+ const HOME = process.env.HOME || '/tmp';
25
+ const SESSION_ID = process.env.CLAUDE_INSTANCE_ID || String(process.ppid);
26
+ const STATE_FILE = path.join(
27
+ HOME,
28
+ '.stackmemory',
29
+ `checkpoint-state-${SESSION_ID}.json`
30
+ );
31
+
32
+ function projectHash(cwd) {
33
+ return crypto.createHash('md5').update(cwd).digest('hex').slice(0, 12);
34
+ }
35
+
36
+ function ensureDir(dir) {
37
+ if (!fs.existsSync(dir)) {
38
+ fs.mkdirSync(dir, { recursive: true });
39
+ }
40
+ }
41
+
42
+ function safeWriteFile(filePath, content) {
43
+ const tmp = filePath + '.tmp';
44
+ fs.writeFileSync(tmp, content);
45
+ fs.renameSync(tmp, filePath);
46
+ }
47
+
48
+ function loadState() {
49
+ try {
50
+ if (fs.existsSync(STATE_FILE)) {
51
+ const state = JSON.parse(fs.readFileSync(STATE_FILE, 'utf-8'));
52
+ const cutoff = Date.now() - TTL_MS;
53
+ for (const [cwd, proj] of Object.entries(state.projects)) {
54
+ if (
55
+ proj.sessionStart &&
56
+ new Date(proj.sessionStart).getTime() < cutoff
57
+ ) {
58
+ delete state.projects[cwd];
59
+ }
60
+ }
61
+ return state;
62
+ }
63
+ } catch {
64
+ // Corrupted state, start fresh
65
+ }
66
+ return { projects: {} };
67
+ }
68
+
69
+ function saveState(state) {
70
+ try {
71
+ ensureDir(path.dirname(STATE_FILE));
72
+ safeWriteFile(STATE_FILE, JSON.stringify(state));
73
+ } catch {
74
+ // Best-effort
75
+ }
76
+ }
77
+
78
+ function getProjectState(state, cwd) {
79
+ if (!state.projects[cwd]) {
80
+ state.projects[cwd] = {
81
+ toolCount: 0,
82
+ checkpointCount: 0,
83
+ lastCheckpoint: null,
84
+ sessionStart: new Date().toISOString(),
85
+ filesModified: [],
86
+ recentTools: [],
87
+ };
88
+ }
89
+ return state.projects[cwd];
90
+ }
91
+
92
+ function writeCheckpoint(proj, cwd) {
93
+ const hash = projectHash(cwd);
94
+ const cpDir = path.join(HOME, '.stackmemory', 'checkpoints', hash);
95
+ ensureDir(cpDir);
96
+
97
+ const checkpoint = {
98
+ toolCount: proj.toolCount,
99
+ checkpointNumber: proj.checkpointCount,
100
+ timestamp: new Date().toISOString(),
101
+ filesModified: proj.filesModified.slice(-MAX_FILES_TRACKED),
102
+ recentTools: proj.recentTools.slice(-MAX_RECENT_TOOLS),
103
+ cwd,
104
+ };
105
+
106
+ safeWriteFile(
107
+ path.join(cpDir, 'latest.json'),
108
+ JSON.stringify(checkpoint, null, 2)
109
+ );
110
+ }
111
+
112
+ function trackFile(proj, toolInput) {
113
+ const filePath = toolInput?.file_path;
114
+ if (filePath && !proj.filesModified.includes(filePath)) {
115
+ proj.filesModified.push(filePath);
116
+ if (proj.filesModified.length > MAX_FILES_TRACKED) {
117
+ proj.filesModified = proj.filesModified.slice(-MAX_FILES_TRACKED);
118
+ }
119
+ }
120
+ }
121
+
122
+ async function readInput() {
123
+ let input = '';
124
+ for await (const chunk of process.stdin) {
125
+ input += chunk;
126
+ }
127
+ return JSON.parse(input);
128
+ }
129
+
130
+ async function main() {
131
+ try {
132
+ const input = await readInput();
133
+ const { tool_name, tool_input } = input;
134
+
135
+ const cwd = process.cwd();
136
+ const state = loadState();
137
+ const proj = getProjectState(state, cwd);
138
+
139
+ // Increment counter
140
+ proj.toolCount++;
141
+
142
+ // Track tool name
143
+ proj.recentTools.push(tool_name);
144
+ if (proj.recentTools.length > MAX_RECENT_TOOLS) {
145
+ proj.recentTools = proj.recentTools.slice(-MAX_RECENT_TOOLS);
146
+ }
147
+
148
+ // Track modified files from Edit/Write
149
+ if (
150
+ tool_name === 'Edit' ||
151
+ tool_name === 'Write' ||
152
+ tool_name === 'MultiEdit'
153
+ ) {
154
+ trackFile(proj, tool_input);
155
+ }
156
+
157
+ // Checkpoint every N calls
158
+ const isCheckpoint = proj.toolCount % CHECKPOINT_INTERVAL === 0;
159
+ if (isCheckpoint) {
160
+ proj.checkpointCount++;
161
+ proj.lastCheckpoint = new Date().toISOString();
162
+ writeCheckpoint(proj, cwd);
163
+ }
164
+
165
+ // Only persist state every SAVE_INTERVAL calls or on checkpoint boundaries
166
+ if (isCheckpoint || proj.toolCount % SAVE_INTERVAL === 0) {
167
+ saveState(state);
168
+ }
169
+ } catch {
170
+ // Silent fail -- never block the agent
171
+ }
172
+ }
173
+
174
+ main();
@@ -0,0 +1,22 @@
1
+ #!/bin/bash
2
+ # Chime once when Claude Code needs user input
3
+ # Uses macOS system sound
4
+
5
+ # Prevent multiple chimes by checking if we recently played
6
+ CHIME_LOCK="/tmp/claude-chime-lock"
7
+ CHIME_COOLDOWN=2 # seconds
8
+
9
+ if [ -f "$CHIME_LOCK" ]; then
10
+ LAST_CHIME=$(cat "$CHIME_LOCK")
11
+ NOW=$(date +%s)
12
+ DIFF=$((NOW - LAST_CHIME))
13
+ if [ "$DIFF" -lt "$CHIME_COOLDOWN" ]; then
14
+ exit 0 # Skip chime if too recent
15
+ fi
16
+ fi
17
+
18
+ # Record chime time
19
+ date +%s > "$CHIME_LOCK"
20
+
21
+ # Play system sound (Glass is a nice subtle chime)
22
+ afplay /System/Library/Sounds/Glass.aiff &
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env bash
2
+ # session-rescue.sh — Auto-capture stackmemory handoff on session close
3
+ # Runs as a Stop hook. Silent on failure. Skips non-stackmemory projects.
4
+
5
+ set -euo pipefail
6
+
7
+ # Only run in stackmemory-initialized projects
8
+ [ -d ".stackmemory" ] || exit 0
9
+
10
+ # Require stackmemory CLI
11
+ command -v stackmemory >/dev/null 2>&1 || exit 0
12
+
13
+ # Capture without committing, compact format, 10s timeout
14
+ timeout 10 stackmemory capture --no-commit --format compact \
15
+ -m "auto-rescue on session close" >/dev/null 2>&1 || true
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Stop-Checkpoint Hook
5
+ *
6
+ * Fires on session stop. Reads checkpoint state for current project.
7
+ * If 3+ checkpoints accumulated, outputs a "context heavy" message.
8
+ * Writes a final checkpoint with session stop metadata.
9
+ *
10
+ * State is keyed per-session (process.ppid) to avoid race conditions
11
+ * when multiple Claude instances run concurrently.
12
+ */
13
+
14
+ const fs = require('fs');
15
+ const path = require('path');
16
+ const crypto = require('crypto');
17
+
18
+ const HOME = process.env.HOME || '/tmp';
19
+ const SESSION_ID = process.env.CLAUDE_INSTANCE_ID || String(process.ppid);
20
+ const STATE_FILE = path.join(
21
+ HOME,
22
+ '.stackmemory',
23
+ `checkpoint-state-${SESSION_ID}.json`
24
+ );
25
+ const MAX_FILES_TRACKED = 50;
26
+ const MAX_RECENT_TOOLS = 20;
27
+
28
+ function projectHash(cwd) {
29
+ return crypto.createHash('md5').update(cwd).digest('hex').slice(0, 12);
30
+ }
31
+
32
+ function ensureDir(dir) {
33
+ if (!fs.existsSync(dir)) {
34
+ fs.mkdirSync(dir, { recursive: true });
35
+ }
36
+ }
37
+
38
+ function safeWriteFile(filePath, content) {
39
+ const tmp = filePath + '.tmp';
40
+ fs.writeFileSync(tmp, content);
41
+ fs.renameSync(tmp, filePath);
42
+ }
43
+
44
+ function loadState() {
45
+ try {
46
+ if (fs.existsSync(STATE_FILE)) {
47
+ const state = JSON.parse(fs.readFileSync(STATE_FILE, 'utf-8'));
48
+ const cutoff = Date.now() - 24 * 60 * 60 * 1000;
49
+ for (const [cwd, proj] of Object.entries(state.projects)) {
50
+ if (
51
+ proj.sessionStart &&
52
+ new Date(proj.sessionStart).getTime() < cutoff
53
+ ) {
54
+ delete state.projects[cwd];
55
+ }
56
+ }
57
+ return state;
58
+ }
59
+ } catch {
60
+ // Corrupted state, start fresh
61
+ }
62
+ return { projects: {} };
63
+ }
64
+
65
+ function main() {
66
+ try {
67
+ const cwd = process.cwd();
68
+ const state = loadState();
69
+ const proj = state.projects?.[cwd];
70
+
71
+ if (!proj) return;
72
+
73
+ const { toolCount, checkpointCount, sessionStart, filesModified } = proj;
74
+
75
+ // Write final checkpoint
76
+ const hash = projectHash(cwd);
77
+ const cpDir = path.join(HOME, '.stackmemory', 'checkpoints', hash);
78
+ ensureDir(cpDir);
79
+
80
+ // NaN duration guard
81
+ const startMs = sessionStart ? new Date(sessionStart).getTime() : 0;
82
+ const duration =
83
+ startMs > 0 ? Math.round((Date.now() - startMs) / 1000) : 0;
84
+
85
+ const finalCheckpoint = {
86
+ reason: 'session_stop',
87
+ toolCount,
88
+ checkpointNumber: checkpointCount,
89
+ totalFiles: filesModified?.length || 0,
90
+ duration,
91
+ timestamp: new Date().toISOString(),
92
+ filesModified: (filesModified || []).slice(-MAX_FILES_TRACKED),
93
+ recentTools: (proj.recentTools || []).slice(-MAX_RECENT_TOOLS),
94
+ cwd,
95
+ };
96
+
97
+ safeWriteFile(
98
+ path.join(cpDir, 'latest.json'),
99
+ JSON.stringify(finalCheckpoint, null, 2)
100
+ );
101
+
102
+ // Output context-heavy message if 3+ checkpoints
103
+ if (checkpointCount >= 3) {
104
+ console.error(
105
+ `\nContext heavy (${checkpointCount} checkpoints, ${toolCount} tool calls). State saved. Consider /clear to reload from checkpoint.\n`
106
+ );
107
+ }
108
+
109
+ // Clean up session state file since session is ending
110
+ try {
111
+ fs.unlinkSync(STATE_FILE);
112
+ } catch {
113
+ // Best-effort cleanup
114
+ }
115
+ } catch {
116
+ // Silent fail -- never block the agent
117
+ }
118
+ }
119
+
120
+ main();