@stackmemoryai/stackmemory 1.2.4 → 1.2.7

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.
@@ -14,7 +14,6 @@ class MCPToolDefinitions {
14
14
  ...this.getTraceTools(),
15
15
  ...this.getDiscoveryTools(),
16
16
  ...this.getEditTools(),
17
- ...this.getGraphitiTools(),
18
17
  ...this.getTeamTools(),
19
18
  ...this.getCordTools()
20
19
  ];
@@ -283,30 +282,6 @@ class MCPToolDefinitions {
283
282
  type: "object",
284
283
  properties: {}
285
284
  }
286
- },
287
- {
288
- name: "add_task_dependency",
289
- description: "Add dependency between tasks",
290
- inputSchema: {
291
- type: "object",
292
- properties: {
293
- task_id: {
294
- type: "string",
295
- description: "Task that depends on another"
296
- },
297
- depends_on: {
298
- type: "string",
299
- description: "Task that must be completed first"
300
- },
301
- dependency_type: {
302
- type: "string",
303
- enum: ["blocks", "related", "subtask"],
304
- default: "blocks",
305
- description: "Type of dependency"
306
- }
307
- },
308
- required: ["task_id", "depends_on"]
309
- }
310
285
  }
311
286
  ];
312
287
  }
@@ -417,7 +392,7 @@ class MCPToolDefinitions {
417
392
  return [
418
393
  {
419
394
  name: "get_traces",
420
- description: "Get execution traces with optional filtering",
395
+ description: "Get execution traces with optional filtering. Set analyze=true to run pattern analysis instead of listing traces.",
421
396
  inputSchema: {
422
397
  type: "object",
423
398
  properties: {
@@ -444,30 +419,21 @@ class MCPToolDefinitions {
444
419
  type: "boolean",
445
420
  default: false,
446
421
  description: "Include full trace context"
447
- }
448
- }
449
- }
450
- },
451
- {
452
- name: "analyze_traces",
453
- description: "Analyze trace patterns for insights",
454
- inputSchema: {
455
- type: "object",
456
- properties: {
422
+ },
423
+ analyze: {
424
+ type: "boolean",
425
+ default: false,
426
+ description: "Run analysis on traces instead of listing them"
427
+ },
457
428
  trace_id: {
458
429
  type: "string",
459
- description: "Specific trace to analyze"
430
+ description: "Specific trace to analyze (requires analyze=true)"
460
431
  },
461
432
  analysis_type: {
462
433
  type: "string",
463
434
  enum: ["performance", "patterns", "errors"],
464
435
  default: "performance",
465
- description: "Type of analysis to perform"
466
- },
467
- include_recommendations: {
468
- type: "boolean",
469
- default: true,
470
- description: "Include optimization recommendations"
436
+ description: "Type of analysis (requires analyze=true)"
471
437
  }
472
438
  }
473
439
  }
@@ -708,55 +674,6 @@ class MCPToolDefinitions {
708
674
  }
709
675
  ];
710
676
  }
711
- /**
712
- * Graphiti knowledge graph tools
713
- */
714
- getGraphitiTools() {
715
- return [
716
- {
717
- name: "graphiti_status",
718
- description: "Check Graphiti temporal knowledge graph connection status",
719
- inputSchema: {
720
- type: "object",
721
- properties: {}
722
- }
723
- },
724
- {
725
- name: "graphiti_query",
726
- description: "Query the Graphiti temporal knowledge graph for entities, relations, and episodes",
727
- inputSchema: {
728
- type: "object",
729
- properties: {
730
- query: {
731
- type: "string",
732
- description: "Semantic text query"
733
- },
734
- entityTypes: {
735
- type: "array",
736
- items: { type: "string" },
737
- description: 'Entity types to filter (e.g., ["Person", "File", "Issue"])'
738
- },
739
- validFrom: {
740
- type: "number",
741
- description: "Start of time window (epoch ms)"
742
- },
743
- validTo: {
744
- type: "number",
745
- description: "End of time window (epoch ms)"
746
- },
747
- maxHops: {
748
- type: "number",
749
- description: "Graph traversal depth (default 2)"
750
- },
751
- k: {
752
- type: "number",
753
- description: "Top-k results (default 20)"
754
- }
755
- }
756
- }
757
- }
758
- ];
759
- }
760
677
  /**
761
678
  * Multi-agent team collaboration tools
762
679
  */
@@ -988,8 +905,6 @@ class MCPToolDefinitions {
988
905
  return this.getDiscoveryTools();
989
906
  case "edit":
990
907
  return this.getEditTools();
991
- case "graphiti":
992
- return this.getGraphitiTools();
993
908
  case "team":
994
909
  return this.getTeamTools();
995
910
  case "cord":
@@ -320,9 +320,9 @@ const defaultModelConfigs = {
320
320
  {
321
321
  tier: "oracle",
322
322
  provider: "claude",
323
- model: "claude-3-opus-20240229",
324
- costPerToken: 0.015,
325
- // $15/1M input tokens
323
+ model: "claude-sonnet-4-5-20250929",
324
+ costPerToken: 3e-3,
325
+ // $3/1M input tokens
326
326
  capabilities: [
327
327
  "strategic_planning",
328
328
  "complex_reasoning",
@@ -336,9 +336,9 @@ const defaultModelConfigs = {
336
336
  {
337
337
  tier: "worker",
338
338
  provider: "claude",
339
- model: "claude-3-5-haiku-20241022",
340
- costPerToken: 25e-5,
341
- // $0.25/1M input tokens
339
+ model: "claude-haiku-4-5-20251001",
340
+ costPerToken: 8e-4,
341
+ // $0.80/1M input tokens
342
342
  capabilities: [
343
343
  "code_implementation",
344
344
  "unit_testing",
@@ -889,6 +889,25 @@ class ClaudeSkillsManager {
889
889
  };
890
890
  }
891
891
  }
892
+ case "theory": {
893
+ const { TheorySkill } = await import("./theory-skill.js");
894
+ const theorySkill = new TheorySkill(this.context);
895
+ switch (args[0]) {
896
+ case "show":
897
+ return theorySkill.show();
898
+ case "update":
899
+ return theorySkill.update(args.slice(1).join(" "));
900
+ case "init":
901
+ return theorySkill.init(args.slice(1).join(" "));
902
+ case "status":
903
+ return theorySkill.status();
904
+ default:
905
+ return {
906
+ success: false,
907
+ message: "Usage: theory show|init|update|status"
908
+ };
909
+ }
910
+ }
892
911
  case "linear-run": {
893
912
  if (!this.linearTaskRunner) {
894
913
  return {
@@ -931,7 +950,8 @@ class ClaudeSkillsManager {
931
950
  "dashboard",
932
951
  "api",
933
952
  "spec",
934
- "agent"
953
+ "agent",
954
+ "theory"
935
955
  ];
936
956
  if (this.rlmOrchestrator) {
937
957
  skills.push("rlm", "lint");
@@ -1088,6 +1108,22 @@ Options:
1088
1108
  Each agent runs in a disposable /tmp workspace (git clone --depth=1).
1089
1109
  Patches: git apply .stackmemory/patches/<file>.patch
1090
1110
  Spec branches: cd /tmp/sm-spec-* && git log --oneline
1111
+ `;
1112
+ case "theory":
1113
+ return `
1114
+ /theory show
1115
+ /theory init "<problem statement>"
1116
+ /theory update "<full content>"
1117
+ /theory status
1118
+
1119
+ Maintain a living THEORY.MD at repo root \u2014 a narrative operating theory:
1120
+ show \u2014 Display current THEORY.MD content
1121
+ init \u2014 Create THEORY.MD with scaffold sections
1122
+ update \u2014 Overwrite THEORY.MD (validates length, warns on anti-patterns)
1123
+ status \u2014 Show metadata: exists, line count, sections, last modified
1124
+
1125
+ Sections: Problem, Operating Theory, Strategy, Key Discoveries, Open Questions
1126
+ Based on Theorist by @blader (MIT).
1091
1127
  `;
1092
1128
  case "linear-run":
1093
1129
  return `
@@ -0,0 +1,191 @@
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 * as fs from "fs";
6
+ import * as path from "path";
7
+ import { execSync } from "child_process";
8
+ import { logger } from "../core/monitoring/logger.js";
9
+ const THEORY_FILE = "THEORY.MD";
10
+ const MIN_CONTENT_LENGTH = 100;
11
+ const MAX_LINE_WARNING = 200;
12
+ const TEMPLATE_SECTIONS = [
13
+ "## Problem",
14
+ "## Operating Theory",
15
+ "## Strategy",
16
+ "## Key Discoveries",
17
+ "## Open Questions"
18
+ ];
19
+ function getGitRoot() {
20
+ try {
21
+ return execSync("git rev-parse --show-toplevel", {
22
+ encoding: "utf-8",
23
+ timeout: 5e3
24
+ }).trim();
25
+ } catch {
26
+ return void 0;
27
+ }
28
+ }
29
+ function generateTemplate(problemStatement) {
30
+ return `# THEORY.MD
31
+
32
+ ## Problem
33
+
34
+ ${problemStatement}
35
+
36
+ ## Operating Theory
37
+
38
+ _What is your current mental model of how this system works or should work?_
39
+
40
+ ## Strategy
41
+
42
+ _What approach are you taking and why?_
43
+
44
+ ## Key Discoveries
45
+
46
+ _What have you learned that changed your thinking?_
47
+
48
+ ## Open Questions
49
+
50
+ _What don't you know yet? What assumptions need validation?_
51
+ `;
52
+ }
53
+ class TheorySkill {
54
+ constructor(context) {
55
+ this.context = context;
56
+ this.rootDir = getGitRoot() || process.cwd();
57
+ }
58
+ rootDir;
59
+ get theoryPath() {
60
+ return path.join(this.rootDir, THEORY_FILE);
61
+ }
62
+ show() {
63
+ if (!fs.existsSync(this.theoryPath)) {
64
+ return {
65
+ success: false,
66
+ message: `No ${THEORY_FILE} found. Run \`theory init "<problem>"\` to create one.`
67
+ };
68
+ }
69
+ const content = fs.readFileSync(this.theoryPath, "utf-8");
70
+ return {
71
+ success: true,
72
+ message: content,
73
+ data: { path: this.theoryPath, length: content.length }
74
+ };
75
+ }
76
+ init(problemStatement) {
77
+ if (!problemStatement || problemStatement.trim().length === 0) {
78
+ return {
79
+ success: false,
80
+ message: "A problem statement is required to initialize THEORY.MD."
81
+ };
82
+ }
83
+ if (fs.existsSync(this.theoryPath)) {
84
+ return {
85
+ success: false,
86
+ message: `${THEORY_FILE} already exists at ${this.theoryPath}. Use \`theory update\` to modify it.`
87
+ };
88
+ }
89
+ const content = generateTemplate(problemStatement.trim());
90
+ fs.writeFileSync(this.theoryPath, content, "utf-8");
91
+ logger.info("Created THEORY.MD", { path: this.theoryPath });
92
+ return {
93
+ success: true,
94
+ message: `Created ${THEORY_FILE} at ${this.theoryPath}`,
95
+ data: { path: this.theoryPath, sections: TEMPLATE_SECTIONS }
96
+ };
97
+ }
98
+ update(content) {
99
+ if (!content || content.trim().length === 0) {
100
+ return { success: false, message: "Content is required for update." };
101
+ }
102
+ if (content.trim().length < MIN_CONTENT_LENGTH) {
103
+ return {
104
+ success: false,
105
+ message: `Content too short (${content.trim().length} chars). THEORY.MD should be at least ${MIN_CONTENT_LENGTH} characters to be meaningful.`
106
+ };
107
+ }
108
+ const warnings = [];
109
+ if (/\[[ x]\]/i.test(content)) {
110
+ warnings.push(
111
+ "Contains checkboxes \u2014 THEORY.MD is narrative, not a checklist."
112
+ );
113
+ }
114
+ if (/\d{4}-\d{2}-\d{2}/.test(content)) {
115
+ warnings.push(
116
+ "Contains dates \u2014 THEORY.MD captures current thinking, not a changelog."
117
+ );
118
+ }
119
+ const lineCount = content.split("\n").length;
120
+ if (lineCount > MAX_LINE_WARNING) {
121
+ warnings.push(
122
+ `${lineCount} lines is long. Consider distilling to keep THEORY.MD focused.`
123
+ );
124
+ }
125
+ fs.writeFileSync(this.theoryPath, content, "utf-8");
126
+ if (this.context.frameManager) {
127
+ try {
128
+ const frameId = this.context.frameManager.createFrame(
129
+ "write",
130
+ "theory-update",
131
+ { source: THEORY_FILE, length: content.length }
132
+ );
133
+ this.context.frameManager.addEvent(
134
+ "artifact",
135
+ {
136
+ type: "theory-update",
137
+ path: this.theoryPath,
138
+ length: content.length,
139
+ lineCount
140
+ },
141
+ frameId
142
+ );
143
+ this.context.frameManager.closeFrame(frameId, {
144
+ theory_updated: true
145
+ });
146
+ } catch (err) {
147
+ logger.warn("Failed to record theory update frame", { error: err });
148
+ }
149
+ }
150
+ logger.info("Updated THEORY.MD", {
151
+ path: this.theoryPath,
152
+ length: content.length
153
+ });
154
+ const message = warnings.length > 0 ? `Updated ${THEORY_FILE}. Warnings:
155
+ ${warnings.map((w) => ` - ${w}`).join("\n")}` : `Updated ${THEORY_FILE} (${lineCount} lines)`;
156
+ return {
157
+ success: true,
158
+ message,
159
+ data: { path: this.theoryPath, lineCount, warnings }
160
+ };
161
+ }
162
+ status() {
163
+ if (!fs.existsSync(this.theoryPath)) {
164
+ return {
165
+ success: true,
166
+ message: `No ${THEORY_FILE} found.`,
167
+ data: { exists: false }
168
+ };
169
+ }
170
+ const content = fs.readFileSync(this.theoryPath, "utf-8");
171
+ const lines = content.split("\n");
172
+ const stat = fs.statSync(this.theoryPath);
173
+ const sections = TEMPLATE_SECTIONS.filter((s) => content.includes(s));
174
+ return {
175
+ success: true,
176
+ message: `${THEORY_FILE}: ${lines.length} lines, ${sections.length}/${TEMPLATE_SECTIONS.length} sections`,
177
+ data: {
178
+ exists: true,
179
+ path: this.theoryPath,
180
+ lineCount: lines.length,
181
+ charCount: content.length,
182
+ sections,
183
+ totalSections: TEMPLATE_SECTIONS.length,
184
+ lastModified: stat.mtime.toISOString()
185
+ }
186
+ };
187
+ }
188
+ }
189
+ export {
190
+ TheorySkill
191
+ };
@@ -39,6 +39,14 @@ const CANONICAL_HOOKS = [
39
39
  timeout: 2,
40
40
  commandPrefix: "node",
41
41
  required: true
42
+ },
43
+ {
44
+ scriptName: "theory-capture.js",
45
+ eventType: "PostToolUse",
46
+ matcher: "Edit|Write|MultiEdit",
47
+ timeout: 2,
48
+ commandPrefix: "node",
49
+ required: false
42
50
  }
43
51
  ];
44
52
  const DEAD_HOOKS = ["sms-response-handler.js"];
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stackmemoryai/stackmemory",
3
- "version": "1.2.4",
4
- "description": "Project-scoped memory for AI coding tools. Durable context across sessions with MCP integration, frames, smart retrieval, Claude Code skills, and automatic hooks.",
3
+ "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.",
5
5
  "engines": {
6
6
  "node": ">=20.0.0",
7
7
  "npm": ">=10.0.0"
@@ -52,7 +52,7 @@
52
52
  "type": "git",
53
53
  "url": "https://github.com/stackmemoryai/stackmemory.git"
54
54
  },
55
- "homepage": "https://github.com/stackmemoryai/stackmemory",
55
+ "homepage": "https://stackmemoryai.github.io/stackmemory/",
56
56
  "bugs": {
57
57
  "url": "https://github.com/stackmemoryai/stackmemory/issues"
58
58
  },
@@ -63,22 +63,35 @@
63
63
  },
64
64
  "keywords": [
65
65
  "ai",
66
+ "ai-memory",
67
+ "ai-coding",
66
68
  "memory",
67
69
  "context",
70
+ "context-management",
68
71
  "llm",
69
72
  "mcp",
73
+ "mcp-server",
74
+ "mcp-tools",
75
+ "model-context-protocol",
70
76
  "claude",
71
77
  "claude-code",
72
- "coding",
78
+ "codex",
79
+ "opencode",
80
+ "coding-assistant",
73
81
  "persistence",
82
+ "session-persistence",
83
+ "fts5",
84
+ "full-text-search",
85
+ "sqlite",
74
86
  "skills",
75
87
  "hooks",
76
- "spec-generator",
77
88
  "linear",
89
+ "linear-integration",
78
90
  "task-runner",
79
- "prompt-optimization",
80
- "model-routing",
81
- "agent-orchestration"
91
+ "agent-orchestration",
92
+ "multi-agent",
93
+ "developer-tools",
94
+ "devtools"
82
95
  ],
83
96
  "scripts": {
84
97
  "start": "node dist/src/integrations/mcp/server.js",
@@ -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();