@stackmemoryai/stackmemory 1.2.4 → 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.
- package/README.md +10 -6
- package/dist/src/cli/claude-sm.js +33 -4
- package/dist/src/cli/codex-sm-danger.js +4 -1
- package/dist/src/cli/commands/skills.js +108 -1
- 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/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 +2 -2
- 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
|
@@ -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.6",
|
|
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"
|
|
@@ -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,253 +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 { logger } from "../core/monitoring/logger.js";
|
|
6
|
-
import { GraphitiClient } from "../integrations/graphiti/client.js";
|
|
7
|
-
import { DEFAULT_GRAPHITI_CONFIG } from "../integrations/graphiti/config.js";
|
|
8
|
-
class GraphitiHooks {
|
|
9
|
-
client;
|
|
10
|
-
config;
|
|
11
|
-
constructor(config = {}) {
|
|
12
|
-
this.config = { ...DEFAULT_GRAPHITI_CONFIG, ...config };
|
|
13
|
-
this.client = new GraphitiClient(this.config);
|
|
14
|
-
}
|
|
15
|
-
register(emitter) {
|
|
16
|
-
if (!this.config.enabled) {
|
|
17
|
-
logger.debug("Graphiti hooks disabled");
|
|
18
|
-
return;
|
|
19
|
-
}
|
|
20
|
-
emitter.registerHandler("session_start", this.onSessionStart.bind(this));
|
|
21
|
-
emitter.registerHandler("file_change", this.onFileChange.bind(this));
|
|
22
|
-
emitter.registerHandler("session_end", this.onSessionEnd.bind(this));
|
|
23
|
-
emitter.registerHandler("input_idle", this.onInputIdle.bind(this));
|
|
24
|
-
emitter.registerHandler("context_switch", this.onContextSwitch.bind(this));
|
|
25
|
-
emitter.registerHandler("prompt_submit", this.onPromptSubmit.bind(this));
|
|
26
|
-
emitter.registerHandler("tool_use", this.onToolUse.bind(this));
|
|
27
|
-
emitter.registerHandler(
|
|
28
|
-
"suggestion_ready",
|
|
29
|
-
this.onSuggestionReady.bind(this)
|
|
30
|
-
);
|
|
31
|
-
emitter.registerHandler("agent_start", this.onAgentStart.bind(this));
|
|
32
|
-
emitter.registerHandler("agent_complete", this.onAgentComplete.bind(this));
|
|
33
|
-
emitter.registerHandler("agent_error", this.onAgentError.bind(this));
|
|
34
|
-
logger.info("Graphiti hooks registered", {
|
|
35
|
-
endpoint: this.config.endpoint,
|
|
36
|
-
backend: this.config.backend,
|
|
37
|
-
maxHops: this.config.maxHops
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
async onSessionStart(event) {
|
|
41
|
-
try {
|
|
42
|
-
const status = await this.client.getStatus();
|
|
43
|
-
if (!status.connected) {
|
|
44
|
-
logger.warn("Graphiti not available - operating in degraded mode");
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
const episode = {
|
|
48
|
-
type: "session_start",
|
|
49
|
-
content: event.data || {},
|
|
50
|
-
timestamp: Date.now(),
|
|
51
|
-
source: "stackmemory",
|
|
52
|
-
metadata: { severity: "info" }
|
|
53
|
-
};
|
|
54
|
-
await this.client.upsertEpisode(episode);
|
|
55
|
-
} catch (error) {
|
|
56
|
-
logger.debug("Graphiti session_start failed", {
|
|
57
|
-
error: error instanceof Error ? error.message : String(error)
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
async onFileChange(event) {
|
|
62
|
-
const fileEvent = event;
|
|
63
|
-
try {
|
|
64
|
-
const episode = {
|
|
65
|
-
type: "file_change",
|
|
66
|
-
content: {
|
|
67
|
-
path: fileEvent.data.path,
|
|
68
|
-
changeType: fileEvent.data.changeType,
|
|
69
|
-
size: typeof fileEvent.data.content === "string" ? fileEvent.data.content.length : void 0
|
|
70
|
-
},
|
|
71
|
-
timestamp: Date.now(),
|
|
72
|
-
source: "stackmemory"
|
|
73
|
-
};
|
|
74
|
-
await this.client.upsertEpisode(episode);
|
|
75
|
-
} catch (error) {
|
|
76
|
-
logger.debug("Graphiti file_change episode failed", {
|
|
77
|
-
error: error instanceof Error ? error.message : String(error)
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
async onSessionEnd(event) {
|
|
82
|
-
try {
|
|
83
|
-
const episode = {
|
|
84
|
-
type: "session_end",
|
|
85
|
-
content: event.data || {},
|
|
86
|
-
timestamp: Date.now(),
|
|
87
|
-
source: "stackmemory"
|
|
88
|
-
};
|
|
89
|
-
await this.client.upsertEpisode(episode);
|
|
90
|
-
} catch (error) {
|
|
91
|
-
logger.debug("Graphiti session_end failed", {
|
|
92
|
-
error: error instanceof Error ? error.message : String(error)
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
async onInputIdle(event) {
|
|
97
|
-
const idle = event;
|
|
98
|
-
try {
|
|
99
|
-
const episode = {
|
|
100
|
-
type: "input_idle",
|
|
101
|
-
content: {
|
|
102
|
-
idleDuration: idle.data.idleDuration,
|
|
103
|
-
lastInput: idle.data.lastInput
|
|
104
|
-
},
|
|
105
|
-
timestamp: Date.now(),
|
|
106
|
-
source: "stackmemory"
|
|
107
|
-
};
|
|
108
|
-
await this.client.upsertEpisode(episode);
|
|
109
|
-
} catch (error) {
|
|
110
|
-
logger.debug("Graphiti input_idle episode failed", {
|
|
111
|
-
error: error instanceof Error ? error.message : String(error)
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
async onContextSwitch(event) {
|
|
116
|
-
const ctx = event;
|
|
117
|
-
try {
|
|
118
|
-
const episode = {
|
|
119
|
-
type: "context_switch",
|
|
120
|
-
content: {
|
|
121
|
-
fromBranch: ctx.data.fromBranch,
|
|
122
|
-
toBranch: ctx.data.toBranch,
|
|
123
|
-
fromProject: ctx.data.fromProject,
|
|
124
|
-
toProject: ctx.data.toProject
|
|
125
|
-
},
|
|
126
|
-
timestamp: Date.now(),
|
|
127
|
-
source: "stackmemory"
|
|
128
|
-
};
|
|
129
|
-
await this.client.upsertEpisode(episode);
|
|
130
|
-
} catch (error) {
|
|
131
|
-
logger.debug("Graphiti context_switch episode failed", {
|
|
132
|
-
error: error instanceof Error ? error.message : String(error)
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
async onPromptSubmit(event) {
|
|
137
|
-
try {
|
|
138
|
-
const episode = {
|
|
139
|
-
type: "prompt_submit",
|
|
140
|
-
content: event.data || {},
|
|
141
|
-
timestamp: Date.now(),
|
|
142
|
-
source: "stackmemory"
|
|
143
|
-
};
|
|
144
|
-
await this.client.upsertEpisode(episode);
|
|
145
|
-
} catch (error) {
|
|
146
|
-
logger.debug("Graphiti prompt_submit episode failed", {
|
|
147
|
-
error: error instanceof Error ? error.message : String(error)
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
async onToolUse(event) {
|
|
152
|
-
try {
|
|
153
|
-
const episode = {
|
|
154
|
-
type: "tool_use",
|
|
155
|
-
content: event.data || {},
|
|
156
|
-
timestamp: Date.now(),
|
|
157
|
-
source: "stackmemory"
|
|
158
|
-
};
|
|
159
|
-
await this.client.upsertEpisode(episode);
|
|
160
|
-
} catch (error) {
|
|
161
|
-
logger.debug("Graphiti tool_use episode failed", {
|
|
162
|
-
error: error instanceof Error ? error.message : String(error)
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
async onSuggestionReady(event) {
|
|
167
|
-
const suggestion = event;
|
|
168
|
-
try {
|
|
169
|
-
const episode = {
|
|
170
|
-
type: "suggestion_ready",
|
|
171
|
-
content: {
|
|
172
|
-
source: suggestion.data.source,
|
|
173
|
-
confidence: suggestion.data.confidence,
|
|
174
|
-
preview: suggestion.data.preview
|
|
175
|
-
},
|
|
176
|
-
timestamp: Date.now(),
|
|
177
|
-
source: "stackmemory"
|
|
178
|
-
};
|
|
179
|
-
await this.client.upsertEpisode(episode);
|
|
180
|
-
} catch (error) {
|
|
181
|
-
logger.debug("Graphiti suggestion_ready episode failed", {
|
|
182
|
-
error: error instanceof Error ? error.message : String(error)
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
async onAgentStart(event) {
|
|
187
|
-
const e = event;
|
|
188
|
-
try {
|
|
189
|
-
const episode = {
|
|
190
|
-
type: "agent_start",
|
|
191
|
-
content: { agentType: e.data.agentType, task: e.data.task },
|
|
192
|
-
timestamp: Date.now(),
|
|
193
|
-
source: "stackmemory"
|
|
194
|
-
};
|
|
195
|
-
await this.client.upsertEpisode(episode);
|
|
196
|
-
} catch (error) {
|
|
197
|
-
logger.debug("Graphiti agent_start episode failed", {
|
|
198
|
-
error: error instanceof Error ? error.message : String(error)
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
async onAgentComplete(event) {
|
|
203
|
-
const e = event;
|
|
204
|
-
try {
|
|
205
|
-
const episode = {
|
|
206
|
-
type: "agent_complete",
|
|
207
|
-
content: { ...e.data },
|
|
208
|
-
timestamp: Date.now(),
|
|
209
|
-
source: "stackmemory"
|
|
210
|
-
};
|
|
211
|
-
await this.client.upsertEpisode(episode);
|
|
212
|
-
} catch (error) {
|
|
213
|
-
logger.debug("Graphiti agent_complete episode failed", {
|
|
214
|
-
error: error instanceof Error ? error.message : String(error)
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
async onAgentError(event) {
|
|
219
|
-
const e = event;
|
|
220
|
-
try {
|
|
221
|
-
const episode = {
|
|
222
|
-
type: "agent_error",
|
|
223
|
-
content: { agentType: e.data.agentType, error: e.data.error },
|
|
224
|
-
timestamp: Date.now(),
|
|
225
|
-
source: "stackmemory"
|
|
226
|
-
};
|
|
227
|
-
await this.client.upsertEpisode(episode);
|
|
228
|
-
} catch (error) {
|
|
229
|
-
logger.debug("Graphiti agent_error episode failed", {
|
|
230
|
-
error: error instanceof Error ? error.message : String(error)
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
// Expose a simple temporal query helper for future MCP tooling
|
|
235
|
-
async buildTemporalContext(query = {}) {
|
|
236
|
-
const now = Date.now();
|
|
237
|
-
const q = {
|
|
238
|
-
query: query.query || void 0,
|
|
239
|
-
entityTypes: query.entityTypes || void 0,
|
|
240
|
-
relationTypes: query.relationTypes || void 0,
|
|
241
|
-
validFrom: query.validFrom ?? now - 1e3 * 60 * 60 * 24 * 30,
|
|
242
|
-
// 30d default
|
|
243
|
-
validTo: query.validTo ?? now,
|
|
244
|
-
maxHops: query.maxHops ?? this.config.maxHops,
|
|
245
|
-
k: query.k ?? 20,
|
|
246
|
-
rerank: query.rerank ?? true
|
|
247
|
-
};
|
|
248
|
-
return this.client.queryTemporal(q);
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
export {
|
|
252
|
-
GraphitiHooks
|
|
253
|
-
};
|