@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.
- package/README.md +17 -7
- package/dist/src/cli/claude-sm.js +33 -4
- package/dist/src/cli/codex-sm-danger.js +4 -1
- package/dist/src/cli/commands/ralph.js +3 -3
- package/dist/src/cli/commands/skills.js +108 -1
- package/dist/src/core/extensions/provider-adapter.js +5 -13
- package/dist/src/core/models/model-router.js +3 -5
- package/dist/src/hooks/daemon.js +0 -8
- package/dist/src/integrations/linear/webhook.js +0 -16
- package/dist/src/integrations/mcp/handlers/index.js +0 -8
- package/dist/src/integrations/mcp/handlers/trace-handlers.js +3 -0
- package/dist/src/integrations/mcp/server.js +0 -130
- package/dist/src/integrations/mcp/tool-definitions.js +9 -94
- package/dist/src/integrations/ralph/patterns/oracle-worker-pattern.js +6 -6
- package/dist/src/skills/claude-skills.js +37 -1
- package/dist/src/skills/theory-skill.js +191 -0
- package/dist/src/utils/hook-installer.js +8 -0
- package/package.json +21 -8
- package/templates/claude-hooks/theory-capture.js +100 -0
- package/dist/src/hooks/graphiti-hooks.js +0 -253
- package/dist/src/integrations/graphiti/client.js +0 -115
- package/dist/src/integrations/graphiti/config.js +0 -17
- package/dist/src/integrations/graphiti/linear-graphiti-bridge.js +0 -115
- package/dist/src/integrations/graphiti/types.js +0 -4
|
@@ -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
|
-
|
|
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
|
|
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-
|
|
324
|
-
costPerToken:
|
|
325
|
-
// $
|
|
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-
|
|
340
|
-
costPerToken:
|
|
341
|
-
// $0.
|
|
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
|
-
"description": "Project-scoped memory for AI coding tools. Durable context across sessions with MCP
|
|
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.
|
|
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
|
-
"
|
|
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
|
-
"
|
|
80
|
-
"
|
|
81
|
-
"
|
|
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();
|