@stackmemoryai/stackmemory 1.2.2 → 1.2.6

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 (77) hide show
  1. package/README.md +10 -6
  2. package/dist/src/cli/claude-sm.js +33 -4
  3. package/dist/src/cli/codex-sm-danger.js +4 -1
  4. package/dist/src/cli/codex-sm.js +6 -8
  5. package/dist/src/cli/commands/config.js +0 -81
  6. package/dist/src/cli/commands/context-rehydrate.js +133 -47
  7. package/dist/src/cli/commands/db.js +35 -8
  8. package/dist/src/cli/commands/handoff.js +1 -1
  9. package/dist/src/cli/commands/linear.js +9 -0
  10. package/dist/src/cli/commands/ralph.js +2 -2
  11. package/dist/src/cli/commands/setup.js +2 -2
  12. package/dist/src/cli/commands/signup.js +3 -1
  13. package/dist/src/cli/commands/skills.js +108 -1
  14. package/dist/src/cli/commands/storage-tier.js +26 -8
  15. package/dist/src/cli/index.js +1 -57
  16. package/dist/src/core/config/feature-flags.js +0 -4
  17. package/dist/src/core/context/dual-stack-manager.js +10 -3
  18. package/dist/src/core/context/frame-database.js +32 -0
  19. package/dist/src/core/context/frame-handoff-manager.js +2 -2
  20. package/dist/src/core/context/{refactored-frame-manager.js → frame-manager.js} +3 -3
  21. package/dist/src/core/context/index.js +2 -2
  22. package/dist/src/core/database/sqlite-adapter.js +161 -1
  23. package/dist/src/core/digest/frame-digest-integration.js +1 -1
  24. package/dist/src/core/digest/index.js +1 -1
  25. package/dist/src/core/execution/parallel-executor.js +5 -1
  26. package/dist/src/core/projects/project-isolation.js +18 -4
  27. package/dist/src/core/security/index.js +2 -0
  28. package/dist/src/core/security/input-sanitizer.js +23 -0
  29. package/dist/src/core/utils/update-checker.js +10 -6
  30. package/dist/src/daemon/daemon-config.js +2 -1
  31. package/dist/src/daemon/services/auto-save-service.js +121 -0
  32. package/dist/src/daemon/services/maintenance-service.js +76 -1
  33. package/dist/src/features/sweep/prompt-builder.js +2 -2
  34. package/dist/src/hooks/daemon.js +0 -8
  35. package/dist/src/integrations/linear/config.js +3 -1
  36. package/dist/src/integrations/linear/sync.js +18 -5
  37. package/dist/src/integrations/linear/webhook.js +0 -16
  38. package/dist/src/integrations/mcp/handlers/code-execution-handlers.js +33 -7
  39. package/dist/src/integrations/mcp/handlers/cord-handlers.js +397 -0
  40. package/dist/src/integrations/mcp/handlers/index.js +55 -9
  41. package/dist/src/integrations/mcp/handlers/task-handlers.js +55 -12
  42. package/dist/src/integrations/mcp/handlers/team-handlers.js +211 -0
  43. package/dist/src/integrations/mcp/handlers/trace-handlers.js +28 -9
  44. package/dist/src/integrations/mcp/index.js +2 -2
  45. package/dist/src/integrations/mcp/refactored-server.js +31 -10
  46. package/dist/src/integrations/mcp/server.js +0 -130
  47. package/dist/src/integrations/mcp/tool-definitions.js +196 -67
  48. package/dist/src/integrations/ralph/context/context-budget-manager.js +10 -2
  49. package/dist/src/integrations/ralph/context/stackmemory-context-loader.js +54 -22
  50. package/dist/src/integrations/ralph/learning/pattern-learner.js +59 -24
  51. package/dist/src/integrations/ralph/orchestration/multi-loop-orchestrator.js +81 -35
  52. package/dist/src/integrations/ralph/patterns/compounding-engineering-pattern.js +12 -4
  53. package/dist/src/integrations/ralph/patterns/extended-coherence-sessions.js +32 -9
  54. package/dist/src/integrations/ralph/swarm/git-workflow-manager.js +25 -8
  55. package/dist/src/integrations/ralph/swarm/swarm-coordinator.js +17 -5
  56. package/dist/src/integrations/ralph/visualization/ralph-debugger.js +73 -22
  57. package/dist/src/skills/claude-skills.js +37 -103
  58. package/dist/src/skills/theory-skill.js +191 -0
  59. package/dist/src/utils/hook-installer.js +16 -0
  60. package/package.json +6 -6
  61. package/scripts/install-claude-hooks-auto.js +8 -0
  62. package/templates/claude-hooks/cord-trace.js +225 -0
  63. package/templates/claude-hooks/theory-capture.js +100 -0
  64. package/dist/src/core/config/storage-config.js +0 -114
  65. package/dist/src/core/storage/chromadb-adapter.js +0 -379
  66. package/dist/src/hooks/graphiti-hooks.js +0 -253
  67. package/dist/src/integrations/claude-code/enhanced-pre-clear-hooks.js +0 -458
  68. package/dist/src/integrations/graphiti/client.js +0 -115
  69. package/dist/src/integrations/graphiti/config.js +0 -17
  70. package/dist/src/integrations/graphiti/linear-graphiti-bridge.js +0 -115
  71. package/dist/src/integrations/graphiti/types.js +0 -4
  72. package/dist/src/integrations/ralph/coordination/enhanced-coordination.js +0 -409
  73. package/dist/src/skills/repo-ingestion-skill.js +0 -631
  74. package/templates/claude-hooks/chromadb-wrapper +0 -21
  75. /package/dist/src/core/context/{enhanced-rehydration.js → rehydration.js} +0 -0
  76. /package/dist/src/core/digest/{enhanced-hybrid-digest.js → hybrid-digest.js} +0 -0
  77. /package/dist/src/core/session/{enhanced-handoff.js → handoff.js} +0 -0
@@ -0,0 +1,225 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Cord Trace Hook (PostToolUse)
5
+ *
6
+ * Fires on every cord_* MCP tool call, logging metrics to a JSONL trace file
7
+ * and accumulating session metrics in a per-session state file.
8
+ *
9
+ * Trace dir: ~/.stackmemory/cord-traces/
10
+ * cord-trace-{date}.jsonl — append-only trace log
11
+ * cord-state-{session_id}.json — running session metrics
12
+ *
13
+ * Must complete in <50ms — pure file I/O only.
14
+ */
15
+
16
+ const fs = require('fs');
17
+ const path = require('path');
18
+
19
+ const HOME = process.env.HOME || '/tmp';
20
+ const TRACE_DIR = path.join(HOME, '.stackmemory', 'cord-traces');
21
+ const CORD_TOOLS = new Set([
22
+ 'cord_spawn',
23
+ 'cord_fork',
24
+ 'cord_complete',
25
+ 'cord_ask',
26
+ 'cord_tree',
27
+ ]);
28
+
29
+ function ensureDir(dir) {
30
+ if (!fs.existsSync(dir)) {
31
+ fs.mkdirSync(dir, { recursive: true });
32
+ }
33
+ }
34
+
35
+ function safeWriteFile(filePath, content) {
36
+ const tmp = filePath + '.tmp';
37
+ fs.writeFileSync(tmp, content);
38
+ fs.renameSync(tmp, filePath);
39
+ }
40
+
41
+ /**
42
+ * Extract the cord tool name from the full MCP tool name.
43
+ * e.g. "mcp__stackmemory-refactored__cord_spawn" -> "cord_spawn"
44
+ */
45
+ function extractCordTool(toolName) {
46
+ if (!toolName) return null;
47
+ for (const t of CORD_TOOLS) {
48
+ if (toolName.endsWith(t)) return t;
49
+ }
50
+ return null;
51
+ }
52
+
53
+ function loadState(sessionId) {
54
+ const stateFile = path.join(TRACE_DIR, `cord-state-${sessionId}.json`);
55
+ try {
56
+ if (fs.existsSync(stateFile)) {
57
+ return JSON.parse(fs.readFileSync(stateFile, 'utf-8'));
58
+ }
59
+ } catch {
60
+ // Corrupted state, start fresh
61
+ }
62
+ return {
63
+ session_id: sessionId,
64
+ started: new Date().toISOString(),
65
+ tool_counts: {},
66
+ task_ids: [],
67
+ context_modes: {},
68
+ unblocked_total: 0,
69
+ max_depth: 0,
70
+ completion_rate: 0,
71
+ ask_count: 0,
72
+ completed_count: 0,
73
+ spawn_fork_count: 0,
74
+ };
75
+ }
76
+
77
+ function saveState(sessionId, state) {
78
+ const stateFile = path.join(TRACE_DIR, `cord-state-${sessionId}.json`);
79
+ safeWriteFile(stateFile, JSON.stringify(state, null, 2));
80
+ }
81
+
82
+ function appendTrace(entry) {
83
+ const date = new Date().toISOString().slice(0, 10);
84
+ const traceFile = path.join(TRACE_DIR, `cord-trace-${date}.jsonl`);
85
+ fs.appendFileSync(traceFile, JSON.stringify(entry) + '\n');
86
+ }
87
+
88
+ /**
89
+ * Safely extract metadata from tool_response.
90
+ * MCP tool responses come as { content: [{ type, text }], metadata: {...} }.
91
+ * The hook receives tool_response which may be the raw MCP result or a string.
92
+ */
93
+ function extractMetadata(toolResponse) {
94
+ if (!toolResponse) return {};
95
+ if (typeof toolResponse === 'string') {
96
+ try {
97
+ const parsed = JSON.parse(toolResponse);
98
+ return parsed.metadata || parsed;
99
+ } catch {
100
+ return {};
101
+ }
102
+ }
103
+ if (toolResponse.metadata) return toolResponse.metadata;
104
+ return toolResponse;
105
+ }
106
+
107
+ function updateState(state, cordTool, toolInput, toolResponse) {
108
+ // Increment tool count
109
+ state.tool_counts[cordTool] = (state.tool_counts[cordTool] || 0) + 1;
110
+
111
+ const meta = extractMetadata(toolResponse);
112
+
113
+ switch (cordTool) {
114
+ case 'cord_spawn':
115
+ case 'cord_fork': {
116
+ state.spawn_fork_count++;
117
+
118
+ // Track context mode
119
+ const mode = meta.context_mode || cordTool.replace('cord_', '');
120
+ state.context_modes[mode] = (state.context_modes[mode] || 0) + 1;
121
+
122
+ // Track task ID
123
+ if (meta.task_id && !state.task_ids.includes(meta.task_id)) {
124
+ state.task_ids.push(meta.task_id);
125
+ }
126
+
127
+ // Track max depth
128
+ const depth = meta.depth ?? 0;
129
+ if (depth > state.max_depth) {
130
+ state.max_depth = depth;
131
+ }
132
+ break;
133
+ }
134
+
135
+ case 'cord_complete': {
136
+ state.completed_count++;
137
+
138
+ // Count unblocked tasks
139
+ const unblocked = meta.unblocked;
140
+ if (Array.isArray(unblocked)) {
141
+ state.unblocked_total += unblocked.length;
142
+ }
143
+
144
+ // Update completion rate
145
+ if (state.spawn_fork_count > 0) {
146
+ state.completion_rate = +(
147
+ state.completed_count / state.spawn_fork_count
148
+ ).toFixed(3);
149
+ }
150
+ break;
151
+ }
152
+
153
+ case 'cord_ask': {
154
+ state.ask_count++;
155
+
156
+ // Track task ID
157
+ if (meta.task_id && !state.task_ids.includes(meta.task_id)) {
158
+ state.task_ids.push(meta.task_id);
159
+ }
160
+
161
+ // Track context mode
162
+ state.context_modes['ask'] = (state.context_modes['ask'] || 0) + 1;
163
+ break;
164
+ }
165
+
166
+ case 'cord_tree':
167
+ // Read-only — no state change
168
+ break;
169
+ }
170
+ }
171
+
172
+ function buildMetricsSummary(state) {
173
+ return {
174
+ task_count: state.task_ids.length,
175
+ active: state.spawn_fork_count - state.completed_count,
176
+ completed: state.completed_count,
177
+ asks: state.ask_count,
178
+ };
179
+ }
180
+
181
+ async function readInput() {
182
+ let input = '';
183
+ for await (const chunk of process.stdin) {
184
+ input += chunk;
185
+ }
186
+ return JSON.parse(input);
187
+ }
188
+
189
+ async function main() {
190
+ try {
191
+ const input = await readInput();
192
+ const { tool_name, tool_input, tool_response, session_id } = input;
193
+
194
+ const cordTool = extractCordTool(tool_name);
195
+ if (!cordTool) return; // Not a cord tool, shouldn't happen given matcher
196
+
197
+ const sid =
198
+ session_id || process.env.CLAUDE_INSTANCE_ID || String(process.ppid);
199
+
200
+ ensureDir(TRACE_DIR);
201
+
202
+ // Load and update state
203
+ const state = loadState(sid);
204
+ updateState(state, cordTool, tool_input, tool_response);
205
+
206
+ // Build trace entry
207
+ const entry = {
208
+ ts: new Date().toISOString(),
209
+ session_id: sid,
210
+ cwd: input.cwd || process.cwd(),
211
+ tool: cordTool,
212
+ input: tool_input || {},
213
+ response: extractMetadata(tool_response),
214
+ metrics: buildMetricsSummary(state),
215
+ };
216
+
217
+ // Append trace + save state
218
+ appendTrace(entry);
219
+ saveState(sid, state);
220
+ } catch {
221
+ // Silent fail — never block the agent
222
+ }
223
+ }
224
+
225
+ main();
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Theory Capture Hook (PostToolUse)
5
+ *
6
+ * Detects writes to THEORY.MD and:
7
+ * 1. Caches metadata to .stackmemory/theory-cache.json
8
+ * 2. Fire-and-forget: records a StackMemory frame via CLI
9
+ *
10
+ * Must complete in <50ms -- file I/O only, CLI exec is detached.
11
+ */
12
+
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+ const crypto = require('crypto');
16
+ const { spawn } = require('child_process');
17
+
18
+ const WRITE_TOOLS = ['Edit', 'Write', 'MultiEdit'];
19
+
20
+ function ensureDir(dir) {
21
+ if (!fs.existsSync(dir)) {
22
+ fs.mkdirSync(dir, { recursive: true });
23
+ }
24
+ }
25
+
26
+ function safeWriteFile(filePath, content) {
27
+ const tmp = filePath + '.tmp';
28
+ fs.writeFileSync(tmp, content);
29
+ fs.renameSync(tmp, filePath);
30
+ }
31
+
32
+ function isTheoryPath(filePath) {
33
+ if (!filePath) return false;
34
+ const lower = filePath.toLowerCase();
35
+ return (
36
+ lower.endsWith('/theory.md') ||
37
+ lower.endsWith('\\theory.md') ||
38
+ lower === 'theory.md'
39
+ );
40
+ }
41
+
42
+ async function readInput() {
43
+ let input = '';
44
+ for await (const chunk of process.stdin) {
45
+ input += chunk;
46
+ }
47
+ return JSON.parse(input);
48
+ }
49
+
50
+ async function main() {
51
+ try {
52
+ const input = await readInput();
53
+ const { tool_name, tool_input } = input;
54
+
55
+ // Only care about file-write tools
56
+ if (!WRITE_TOOLS.includes(tool_name)) return;
57
+
58
+ const fp = tool_input?.file_path || '';
59
+ if (!isTheoryPath(fp)) return;
60
+
61
+ // Read current content
62
+ let content;
63
+ try {
64
+ content = fs.readFileSync(fp, 'utf-8');
65
+ } catch {
66
+ return; // File doesn't exist or unreadable
67
+ }
68
+
69
+ // Cache metadata for next session start
70
+ const cacheDir = path.join(process.cwd(), '.stackmemory');
71
+ ensureDir(cacheDir);
72
+
73
+ const lineCount = content.split('\n').length;
74
+ safeWriteFile(
75
+ path.join(cacheDir, 'theory-cache.json'),
76
+ JSON.stringify({
77
+ path: fp,
78
+ timestamp: new Date().toISOString(),
79
+ hash: crypto.createHash('md5').update(content).digest('hex'),
80
+ lineCount,
81
+ })
82
+ );
83
+
84
+ // Fire-and-forget: record as frame via CLI
85
+ const child = spawn(
86
+ 'stackmemory',
87
+ ['context', 'add', 'artifact', `THEORY.MD updated (${lineCount} lines)`],
88
+ {
89
+ cwd: process.cwd(),
90
+ stdio: 'ignore',
91
+ detached: true,
92
+ }
93
+ );
94
+ child.unref();
95
+ } catch {
96
+ // Silent fail -- never block the agent
97
+ }
98
+ }
99
+
100
+ main();
@@ -1,114 +0,0 @@
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 { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
6
- import { join } from "path";
7
- import { homedir } from "os";
8
- const DEFAULT_STORAGE_CONFIG = {
9
- mode: "sqlite",
10
- chromadb: {
11
- enabled: false
12
- }
13
- };
14
- const STACKMEMORY_DIR = join(homedir(), ".stackmemory");
15
- const CONFIG_FILE = join(STACKMEMORY_DIR, "storage-config.json");
16
- function loadStorageConfig() {
17
- try {
18
- if (existsSync(CONFIG_FILE)) {
19
- const content = readFileSync(CONFIG_FILE, "utf-8");
20
- const config = JSON.parse(content);
21
- return {
22
- mode: config.mode || DEFAULT_STORAGE_CONFIG.mode,
23
- chromadb: {
24
- ...DEFAULT_STORAGE_CONFIG.chromadb,
25
- ...config.chromadb
26
- }
27
- };
28
- }
29
- } catch (error) {
30
- console.warn("Failed to load storage config, using defaults:", error);
31
- }
32
- return DEFAULT_STORAGE_CONFIG;
33
- }
34
- function saveStorageConfig(config) {
35
- try {
36
- if (!existsSync(STACKMEMORY_DIR)) {
37
- mkdirSync(STACKMEMORY_DIR, { recursive: true });
38
- }
39
- writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), "utf-8");
40
- } catch (error) {
41
- console.error("Failed to save storage config:", error);
42
- throw error;
43
- }
44
- }
45
- function isChromaDBEnabled() {
46
- const config = loadStorageConfig();
47
- if (!config.chromadb.enabled) {
48
- return false;
49
- }
50
- const apiKey = config.chromadb.apiKey || process.env["CHROMADB_API_KEY"];
51
- if (!apiKey) {
52
- return false;
53
- }
54
- return true;
55
- }
56
- function getStorageMode() {
57
- const config = loadStorageConfig();
58
- if (config.mode === "hybrid" && !isChromaDBEnabled()) {
59
- return "sqlite";
60
- }
61
- return config.mode;
62
- }
63
- function getChromaDBConfig() {
64
- if (!isChromaDBEnabled()) {
65
- return null;
66
- }
67
- const config = loadStorageConfig();
68
- const apiKey = config.chromadb.apiKey || process.env["CHROMADB_API_KEY"];
69
- const apiUrl = config.chromadb.apiUrl || process.env["CHROMADB_API_URL"] || "https://api.trychroma.com";
70
- return {
71
- enabled: true,
72
- apiKey,
73
- apiUrl,
74
- tenant: config.chromadb.tenant || process.env["CHROMADB_TENANT"] || "default_tenant",
75
- database: config.chromadb.database || process.env["CHROMADB_DATABASE"] || "default_database"
76
- };
77
- }
78
- function enableChromaDB(chromaConfig) {
79
- const config = loadStorageConfig();
80
- config.mode = "hybrid";
81
- config.chromadb = {
82
- enabled: true,
83
- apiKey: chromaConfig.apiKey,
84
- apiUrl: chromaConfig.apiUrl || "https://api.trychroma.com",
85
- tenant: chromaConfig.tenant || "default_tenant",
86
- database: chromaConfig.database || "default_database"
87
- };
88
- saveStorageConfig(config);
89
- }
90
- function disableChromaDB() {
91
- const config = loadStorageConfig();
92
- config.mode = "sqlite";
93
- config.chromadb = {
94
- enabled: false
95
- };
96
- saveStorageConfig(config);
97
- }
98
- function getStorageModeDescription() {
99
- const mode = getStorageMode();
100
- if (mode === "hybrid") {
101
- return "Hybrid (SQLite + ChromaDB for semantic search and cloud backup)";
102
- }
103
- return "SQLite (local storage only, fast, no external dependencies)";
104
- }
105
- export {
106
- disableChromaDB,
107
- enableChromaDB,
108
- getChromaDBConfig,
109
- getStorageMode,
110
- getStorageModeDescription,
111
- isChromaDBEnabled,
112
- loadStorageConfig,
113
- saveStorageConfig
114
- };