gitmem-mcp 1.0.15 → 1.1.1
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/CHANGELOG.md +25 -0
- package/README.md +2 -2
- package/bin/gitmem.js +264 -123
- package/bin/init-wizard.js +332 -93
- package/bin/uninstall.js +187 -45
- package/cursorrules.template +92 -0
- package/dist/commands/check.js +8 -1
- package/dist/schemas/absorb-observations.js +3 -3
- package/dist/schemas/create-decision.js +8 -8
- package/dist/schemas/create-learning.js +13 -13
- package/dist/schemas/prepare-context.js +2 -2
- package/dist/schemas/thread.js +10 -10
- package/dist/server.js +1 -1
- package/dist/services/behavioral-decay.js +3 -1
- package/dist/services/embedding.js +2 -0
- package/dist/services/local-file-storage.js +3 -1
- package/dist/services/thread-manager.js +5 -2
- package/dist/tools/confirm-scars.js +6 -3
- package/dist/tools/recall.js +3 -1
- package/hooks/scripts/recall-check.sh +51 -52
- package/package.json +2 -2
package/bin/uninstall.js
CHANGED
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
* GitMem Uninstall
|
|
5
5
|
*
|
|
6
6
|
* Cleanly reverses everything `npx gitmem-mcp init` did.
|
|
7
|
-
*
|
|
7
|
+
* Supports Claude Code and Cursor IDE.
|
|
8
|
+
*
|
|
9
|
+
* Usage: npx gitmem-mcp uninstall [--yes] [--all] [--client <claude|cursor>]
|
|
8
10
|
*/
|
|
9
11
|
|
|
10
12
|
import {
|
|
@@ -23,16 +25,81 @@ const cwd = process.cwd();
|
|
|
23
25
|
const args = process.argv.slice(2);
|
|
24
26
|
const autoYes = args.includes("--yes") || args.includes("-y");
|
|
25
27
|
const deleteAll = args.includes("--all");
|
|
28
|
+
const clientIdx = args.indexOf("--client");
|
|
29
|
+
const clientFlag = clientIdx !== -1 ? args[clientIdx + 1]?.toLowerCase() : null;
|
|
30
|
+
|
|
31
|
+
// ── Client Configuration ──
|
|
32
|
+
|
|
33
|
+
const CLIENT_CONFIGS = {
|
|
34
|
+
claude: {
|
|
35
|
+
name: "Claude Code",
|
|
36
|
+
mcpConfigPath: join(cwd, ".mcp.json"),
|
|
37
|
+
mcpConfigName: ".mcp.json",
|
|
38
|
+
instructionsFile: join(cwd, "CLAUDE.md"),
|
|
39
|
+
instructionsName: "CLAUDE.md",
|
|
40
|
+
startMarker: "<!-- gitmem:start -->",
|
|
41
|
+
endMarker: "<!-- gitmem:end -->",
|
|
42
|
+
configDir: join(cwd, ".claude"),
|
|
43
|
+
settingsFile: join(cwd, ".claude", "settings.json"),
|
|
44
|
+
hasPermissions: true,
|
|
45
|
+
hooksInSettings: true,
|
|
46
|
+
},
|
|
47
|
+
cursor: {
|
|
48
|
+
name: "Cursor",
|
|
49
|
+
mcpConfigPath: join(cwd, ".cursor", "mcp.json"),
|
|
50
|
+
mcpConfigName: ".cursor/mcp.json",
|
|
51
|
+
instructionsFile: join(cwd, ".cursorrules"),
|
|
52
|
+
instructionsName: ".cursorrules",
|
|
53
|
+
startMarker: "# --- gitmem:start ---",
|
|
54
|
+
endMarker: "# --- gitmem:end ---",
|
|
55
|
+
configDir: join(cwd, ".cursor"),
|
|
56
|
+
settingsFile: null,
|
|
57
|
+
hasPermissions: false,
|
|
58
|
+
hooksInSettings: false,
|
|
59
|
+
hooksFile: join(cwd, ".cursor", "hooks.json"),
|
|
60
|
+
hooksFileName: ".cursor/hooks.json",
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// ── Client Detection ──
|
|
65
|
+
|
|
66
|
+
function detectClient() {
|
|
67
|
+
if (clientFlag) {
|
|
68
|
+
if (clientFlag !== "claude" && clientFlag !== "cursor") {
|
|
69
|
+
console.error(` Error: Unknown client "${clientFlag}". Use --client claude or --client cursor.`);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
return clientFlag;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const hasCursorDir = existsSync(join(cwd, ".cursor"));
|
|
76
|
+
const hasClaudeDir = existsSync(join(cwd, ".claude"));
|
|
77
|
+
const hasMcpJson = existsSync(join(cwd, ".mcp.json"));
|
|
78
|
+
const hasClaudeMd = existsSync(join(cwd, "CLAUDE.md"));
|
|
79
|
+
const hasCursorRules = existsSync(join(cwd, ".cursorrules"));
|
|
80
|
+
const hasCursorMcp = existsSync(join(cwd, ".cursor", "mcp.json"));
|
|
81
|
+
|
|
82
|
+
if (hasCursorDir && !hasClaudeDir && !hasMcpJson && !hasClaudeMd) return "cursor";
|
|
83
|
+
if (hasCursorRules && !hasClaudeMd) return "cursor";
|
|
84
|
+
if (hasCursorMcp && !hasMcpJson) return "cursor";
|
|
85
|
+
|
|
86
|
+
if (hasClaudeDir && !hasCursorDir) return "claude";
|
|
87
|
+
if (hasMcpJson && !hasCursorMcp) return "claude";
|
|
88
|
+
if (hasClaudeMd && !hasCursorRules) return "claude";
|
|
89
|
+
|
|
90
|
+
return "claude";
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const client = detectClient();
|
|
94
|
+
const cc = CLIENT_CONFIGS[client];
|
|
26
95
|
|
|
27
96
|
const gitmemDir = join(cwd, ".gitmem");
|
|
28
|
-
const mcpJsonPath = join(cwd, ".mcp.json");
|
|
29
|
-
const claudeMdPath = join(cwd, "CLAUDE.md");
|
|
30
|
-
const claudeDir = join(cwd, ".claude");
|
|
31
|
-
const settingsPath = join(claudeDir, "settings.json");
|
|
32
97
|
const gitignorePath = join(cwd, ".gitignore");
|
|
33
98
|
|
|
34
99
|
let rl;
|
|
35
100
|
|
|
101
|
+
// ── Helpers ──
|
|
102
|
+
|
|
36
103
|
async function confirm(message, defaultYes = true) {
|
|
37
104
|
if (autoYes) return defaultYes;
|
|
38
105
|
if (!rl) {
|
|
@@ -58,62 +125,66 @@ function writeJson(path, data) {
|
|
|
58
125
|
}
|
|
59
126
|
|
|
60
127
|
function isGitmemHook(entry) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
128
|
+
// Claude Code format: entry.hooks is an array of {command: "..."}
|
|
129
|
+
if (entry.hooks && Array.isArray(entry.hooks)) {
|
|
130
|
+
return entry.hooks.some(
|
|
131
|
+
(h) => typeof h.command === "string" && h.command.includes("gitmem")
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
// Cursor format: entry itself has {command: "..."}
|
|
135
|
+
if (typeof entry.command === "string") {
|
|
136
|
+
return entry.command.includes("gitmem");
|
|
137
|
+
}
|
|
138
|
+
return false;
|
|
65
139
|
}
|
|
66
140
|
|
|
67
141
|
// ── Steps ──
|
|
68
142
|
|
|
69
|
-
function
|
|
70
|
-
if (!existsSync(
|
|
71
|
-
console.log(
|
|
143
|
+
function stepInstructions() {
|
|
144
|
+
if (!existsSync(cc.instructionsFile)) {
|
|
145
|
+
console.log(` No ${cc.instructionsName} found. Skipping.`);
|
|
72
146
|
return;
|
|
73
147
|
}
|
|
74
148
|
|
|
75
|
-
let content = readFileSync(
|
|
76
|
-
const startMarker = "<!-- gitmem:start -->";
|
|
77
|
-
const endMarker = "<!-- gitmem:end -->";
|
|
149
|
+
let content = readFileSync(cc.instructionsFile, "utf-8");
|
|
78
150
|
|
|
79
|
-
if (!content.includes(startMarker)) {
|
|
80
|
-
console.log(
|
|
151
|
+
if (!content.includes(cc.startMarker)) {
|
|
152
|
+
console.log(` No gitmem section in ${cc.instructionsName}. Skipping.`);
|
|
81
153
|
return;
|
|
82
154
|
}
|
|
83
155
|
|
|
84
|
-
const startIdx = content.indexOf(startMarker);
|
|
85
|
-
const endIdx = content.indexOf(endMarker);
|
|
156
|
+
const startIdx = content.indexOf(cc.startMarker);
|
|
157
|
+
const endIdx = content.indexOf(cc.endMarker);
|
|
86
158
|
if (startIdx === -1 || endIdx === -1) {
|
|
87
|
-
console.log(
|
|
159
|
+
console.log(` Malformed gitmem markers in ${cc.instructionsName}. Skipping.`);
|
|
88
160
|
return;
|
|
89
161
|
}
|
|
90
162
|
|
|
91
163
|
// Remove the block including markers and surrounding whitespace
|
|
92
164
|
const before = content.slice(0, startIdx).trimEnd();
|
|
93
|
-
const after = content.slice(endIdx + endMarker.length).trimStart();
|
|
165
|
+
const after = content.slice(endIdx + cc.endMarker.length).trimStart();
|
|
94
166
|
|
|
95
167
|
const result = before + (before && after ? "\n\n" : "") + after;
|
|
96
168
|
|
|
97
169
|
if (result.trim() === "") {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
console.log(" Removed CLAUDE.md (was gitmem-only)");
|
|
170
|
+
rmSync(cc.instructionsFile);
|
|
171
|
+
console.log(` Removed ${cc.instructionsName} (was gitmem-only)`);
|
|
101
172
|
} else {
|
|
102
|
-
writeFileSync(
|
|
103
|
-
console.log(
|
|
173
|
+
writeFileSync(cc.instructionsFile, result.trimEnd() + "\n");
|
|
174
|
+
console.log(` Stripped gitmem section from ${cc.instructionsName}`);
|
|
104
175
|
}
|
|
105
176
|
}
|
|
106
177
|
|
|
107
178
|
function stepMcpJson() {
|
|
108
|
-
const config = readJson(
|
|
179
|
+
const config = readJson(cc.mcpConfigPath);
|
|
109
180
|
if (!config?.mcpServers) {
|
|
110
|
-
console.log(
|
|
181
|
+
console.log(` No ${cc.mcpConfigName} found. Skipping.`);
|
|
111
182
|
return;
|
|
112
183
|
}
|
|
113
184
|
|
|
114
185
|
const had = !!config.mcpServers.gitmem || !!config.mcpServers["gitmem-mcp"];
|
|
115
186
|
if (!had) {
|
|
116
|
-
console.log(
|
|
187
|
+
console.log(` No gitmem in ${cc.mcpConfigName}. Skipping.`);
|
|
117
188
|
return;
|
|
118
189
|
}
|
|
119
190
|
|
|
@@ -121,14 +192,21 @@ function stepMcpJson() {
|
|
|
121
192
|
delete config.mcpServers["gitmem-mcp"];
|
|
122
193
|
|
|
123
194
|
const remaining = Object.keys(config.mcpServers).length;
|
|
124
|
-
writeJson(
|
|
195
|
+
writeJson(cc.mcpConfigPath, config);
|
|
125
196
|
console.log(
|
|
126
197
|
` Removed gitmem server (${remaining} other server${remaining !== 1 ? "s" : ""} preserved)`
|
|
127
198
|
);
|
|
128
199
|
}
|
|
129
200
|
|
|
130
201
|
function stepHooks() {
|
|
131
|
-
|
|
202
|
+
if (cc.hooksInSettings) {
|
|
203
|
+
return stepHooksClaude();
|
|
204
|
+
}
|
|
205
|
+
return stepHooksCursor();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function stepHooksClaude() {
|
|
209
|
+
const settings = readJson(cc.settingsFile);
|
|
132
210
|
if (!settings?.hooks) {
|
|
133
211
|
console.log(" No hooks in .claude/settings.json. Skipping.");
|
|
134
212
|
return;
|
|
@@ -164,7 +242,53 @@ function stepHooks() {
|
|
|
164
242
|
delete settings.hooks;
|
|
165
243
|
}
|
|
166
244
|
|
|
167
|
-
writeJson(
|
|
245
|
+
writeJson(cc.settingsFile, settings);
|
|
246
|
+
console.log(
|
|
247
|
+
` Removed gitmem hooks` +
|
|
248
|
+
(preserved > 0
|
|
249
|
+
? ` (${preserved} other hook${preserved !== 1 ? "s" : ""} preserved)`
|
|
250
|
+
: "")
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function stepHooksCursor() {
|
|
255
|
+
const config = readJson(cc.hooksFile);
|
|
256
|
+
if (!config?.hooks) {
|
|
257
|
+
console.log(` No hooks in ${cc.hooksFileName}. Skipping.`);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
let removed = 0;
|
|
262
|
+
let preserved = 0;
|
|
263
|
+
const cleaned = {};
|
|
264
|
+
|
|
265
|
+
for (const [eventType, entries] of Object.entries(config.hooks)) {
|
|
266
|
+
if (!Array.isArray(entries)) continue;
|
|
267
|
+
const nonGitmem = entries.filter((e) => {
|
|
268
|
+
if (isGitmemHook(e)) {
|
|
269
|
+
removed++;
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
preserved++;
|
|
273
|
+
return true;
|
|
274
|
+
});
|
|
275
|
+
if (nonGitmem.length > 0) {
|
|
276
|
+
cleaned[eventType] = nonGitmem;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (removed === 0) {
|
|
281
|
+
console.log(" No gitmem hooks found. Skipping.");
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (Object.keys(cleaned).length > 0) {
|
|
286
|
+
config.hooks = cleaned;
|
|
287
|
+
} else {
|
|
288
|
+
delete config.hooks;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
writeJson(cc.hooksFile, config);
|
|
168
292
|
console.log(
|
|
169
293
|
` Removed gitmem hooks` +
|
|
170
294
|
(preserved > 0
|
|
@@ -174,7 +298,12 @@ function stepHooks() {
|
|
|
174
298
|
}
|
|
175
299
|
|
|
176
300
|
function stepPermissions() {
|
|
177
|
-
|
|
301
|
+
if (!cc.hasPermissions) {
|
|
302
|
+
console.log(` Not needed for ${cc.name}. Skipping.`);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const settings = readJson(cc.settingsFile);
|
|
178
307
|
const allow = settings?.permissions?.allow;
|
|
179
308
|
if (!Array.isArray(allow)) {
|
|
180
309
|
console.log(" No permissions in .claude/settings.json. Skipping.");
|
|
@@ -202,7 +331,7 @@ function stepPermissions() {
|
|
|
202
331
|
delete settings.permissions;
|
|
203
332
|
}
|
|
204
333
|
|
|
205
|
-
writeJson(
|
|
334
|
+
writeJson(cc.settingsFile, settings);
|
|
206
335
|
console.log(" Removed mcp__gitmem__* from permissions.allow");
|
|
207
336
|
}
|
|
208
337
|
|
|
@@ -247,28 +376,41 @@ function stepGitignore() {
|
|
|
247
376
|
|
|
248
377
|
async function main() {
|
|
249
378
|
console.log("");
|
|
250
|
-
console.log(
|
|
379
|
+
console.log(` gitmem — Uninstall (${cc.name})`);
|
|
380
|
+
if (clientFlag) {
|
|
381
|
+
console.log(` (client: ${client} — via --client flag)`);
|
|
382
|
+
} else {
|
|
383
|
+
console.log(` (client: ${client} — auto-detected)`);
|
|
384
|
+
}
|
|
251
385
|
console.log("");
|
|
252
386
|
|
|
253
|
-
|
|
254
|
-
|
|
387
|
+
const stepCount = cc.hasPermissions ? 5 : 4;
|
|
388
|
+
let step = 1;
|
|
389
|
+
|
|
390
|
+
console.log(` Step ${step}/${stepCount} — Remove gitmem section from ${cc.instructionsName}`);
|
|
391
|
+
stepInstructions();
|
|
255
392
|
console.log("");
|
|
393
|
+
step++;
|
|
256
394
|
|
|
257
|
-
console.log(
|
|
395
|
+
console.log(` Step ${step}/${stepCount} — Remove gitmem from ${cc.mcpConfigName}`);
|
|
258
396
|
stepMcpJson();
|
|
259
397
|
console.log("");
|
|
398
|
+
step++;
|
|
260
399
|
|
|
261
|
-
|
|
400
|
+
const hooksTarget = cc.hooksInSettings ? ".claude/settings.json" : cc.hooksFileName;
|
|
401
|
+
console.log(` Step ${step}/${stepCount} — Remove gitmem hooks from ${hooksTarget}`);
|
|
262
402
|
stepHooks();
|
|
263
403
|
console.log("");
|
|
404
|
+
step++;
|
|
264
405
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
406
|
+
if (cc.hasPermissions) {
|
|
407
|
+
console.log(` Step ${step}/${stepCount} — Remove gitmem permissions from .claude/settings.json`);
|
|
408
|
+
stepPermissions();
|
|
409
|
+
console.log("");
|
|
410
|
+
step++;
|
|
411
|
+
}
|
|
270
412
|
|
|
271
|
-
console.log(
|
|
413
|
+
console.log(` Step ${step}/${stepCount} — Delete .gitmem/ directory?`);
|
|
272
414
|
await stepGitmemDir();
|
|
273
415
|
|
|
274
416
|
// Also clean .gitignore entry
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# --- gitmem:start ---
|
|
2
|
+
# GitMem — Persistent Memory
|
|
3
|
+
|
|
4
|
+
You have a persistent memory layer called gitmem. Every session starts from
|
|
5
|
+
zero — but gitmem carries forward lessons, mistakes, wins, decisions, and
|
|
6
|
+
open threads across sessions and projects. Search it. Test it. You might be
|
|
7
|
+
amazed at what's already there. If convinced, make it your own.
|
|
8
|
+
|
|
9
|
+
## Red Lines
|
|
10
|
+
|
|
11
|
+
These constraints are enforced by hooks. Attempting to bypass them will be blocked.
|
|
12
|
+
|
|
13
|
+
### Credential Protection
|
|
14
|
+
|
|
15
|
+
Credential exposure in conversation history is permanent and irreversible.
|
|
16
|
+
|
|
17
|
+
1. **NEVER read credential files in full.** Files like `mcp-config.json`, `.env`, `.credentials.json`, `.netrc`, `.npmrc`, SSH keys, or `.pem`/`.key` files must not be read or dumped.
|
|
18
|
+
2. **NEVER print environment variable values that contain secrets.** Commands like `env | grep KEY`, `echo $API_KEY`, or `printenv TOKEN` expose credentials in output.
|
|
19
|
+
3. **NEVER display API keys, tokens, or secrets in conversation output.**
|
|
20
|
+
|
|
21
|
+
Safe alternatives: `env | grep -c VARNAME` (count only), `[ -n "$VARNAME" ] && echo "set"` (existence check), `grep -c '"key"' config.json` (structure check).
|
|
22
|
+
|
|
23
|
+
### Recall Before Consequential Actions
|
|
24
|
+
|
|
25
|
+
1. **NEVER parallelize `recall()` with actions that expose, modify, or transmit sensitive data.** Recall must complete first.
|
|
26
|
+
2. **Confirm scars before acting.** Each recalled scar requires APPLYING (past-tense evidence), N_A (explanation), or REFUTED (risk acknowledgment).
|
|
27
|
+
3. **Parallel recall is only safe with benign reads** — source code, docs, non-sensitive config.
|
|
28
|
+
|
|
29
|
+
## Tools
|
|
30
|
+
|
|
31
|
+
| Tool | When to use |
|
|
32
|
+
|------|-------------|
|
|
33
|
+
| `recall` | Before any task — surfaces relevant warnings from past experience |
|
|
34
|
+
| `confirm_scars` | After recall — acknowledge each scar as APPLYING, N_A, or REFUTED |
|
|
35
|
+
| `search` | Explore institutional knowledge by topic |
|
|
36
|
+
| `log` | Browse recent learnings chronologically |
|
|
37
|
+
| `session_start` | Beginning of session — loads last session context and open threads |
|
|
38
|
+
| `session_close` | End of session — persists what you learned |
|
|
39
|
+
| `create_learning` | Capture a mistake (scar), success (win), or pattern |
|
|
40
|
+
| `create_decision` | Log an architectural or operational decision with rationale |
|
|
41
|
+
| `list_threads` | See unresolved work carrying over between sessions |
|
|
42
|
+
| `create_thread` | Track something that needs follow-up in a future session |
|
|
43
|
+
| `help` | Show all available commands |
|
|
44
|
+
|
|
45
|
+
## Session end
|
|
46
|
+
|
|
47
|
+
On "closing", "done for now", or "wrapping up":
|
|
48
|
+
|
|
49
|
+
1. **Answer these reflection questions** and display to the human:
|
|
50
|
+
- What broke that you didn't expect?
|
|
51
|
+
- What took longer than it should have?
|
|
52
|
+
- What would you do differently next time?
|
|
53
|
+
- What pattern or approach worked well?
|
|
54
|
+
- What assumption was wrong?
|
|
55
|
+
- Which scars did you apply?
|
|
56
|
+
- What should be captured as institutional memory?
|
|
57
|
+
|
|
58
|
+
2. **Ask the human**: "Any corrections or additions?" Wait for their response.
|
|
59
|
+
|
|
60
|
+
3. **Write payload** to `.gitmem/closing-payload.json`:
|
|
61
|
+
```json
|
|
62
|
+
{
|
|
63
|
+
"closing_reflection": {
|
|
64
|
+
"what_broke": "...",
|
|
65
|
+
"what_took_longer": "...",
|
|
66
|
+
"do_differently": "...",
|
|
67
|
+
"what_worked": "...",
|
|
68
|
+
"wrong_assumption": "...",
|
|
69
|
+
"scars_applied": ["scar title 1", "scar title 2"],
|
|
70
|
+
"institutional_memory_items": "...",
|
|
71
|
+
"collaborative_dynamic": "Q8: How human preferred to work",
|
|
72
|
+
"rapport_notes": "Q9: What collaborative dynamic worked"
|
|
73
|
+
},
|
|
74
|
+
"task_completion": {
|
|
75
|
+
"questions_displayed_at": "ISO timestamp",
|
|
76
|
+
"reflection_completed_at": "ISO timestamp",
|
|
77
|
+
"human_asked_at": "ISO timestamp",
|
|
78
|
+
"human_response_at": "ISO timestamp",
|
|
79
|
+
"human_response": "human's correction text or 'Looks good'"
|
|
80
|
+
},
|
|
81
|
+
"human_corrections": "",
|
|
82
|
+
"scars_to_record": [],
|
|
83
|
+
"learnings_created": [],
|
|
84
|
+
"open_threads": [],
|
|
85
|
+
"decisions": []
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
4. **Call `session_close`** with `session_id` and `close_type: "standard"`
|
|
90
|
+
|
|
91
|
+
For short exploratory sessions (< 30 min, no real work), use `close_type: "quick"` — no questions needed.
|
|
92
|
+
# --- gitmem:end ---
|
package/dist/commands/check.js
CHANGED
|
@@ -119,6 +119,7 @@ async function runQuickCheck() {
|
|
|
119
119
|
apikey: supabaseKey,
|
|
120
120
|
Authorization: `Bearer ${supabaseKey}`,
|
|
121
121
|
},
|
|
122
|
+
signal: AbortSignal.timeout(5_000),
|
|
122
123
|
});
|
|
123
124
|
const durationMs = Date.now() - startTime;
|
|
124
125
|
if (response.ok) {
|
|
@@ -158,6 +159,7 @@ async function runQuickCheck() {
|
|
|
158
159
|
headers: {
|
|
159
160
|
Authorization: `Bearer ${openaiKey}`,
|
|
160
161
|
},
|
|
162
|
+
signal: AbortSignal.timeout(5_000),
|
|
161
163
|
});
|
|
162
164
|
const durationMs = Date.now() - startTime;
|
|
163
165
|
if (response.ok) {
|
|
@@ -189,6 +191,7 @@ async function runQuickCheck() {
|
|
|
189
191
|
headers: {
|
|
190
192
|
Authorization: `Bearer ${openrouterKey}`,
|
|
191
193
|
},
|
|
194
|
+
signal: AbortSignal.timeout(5_000),
|
|
192
195
|
});
|
|
193
196
|
const durationMs = Date.now() - startTime;
|
|
194
197
|
if (response.ok) {
|
|
@@ -216,7 +219,9 @@ async function runQuickCheck() {
|
|
|
216
219
|
else if (embeddingProvider === "ollama") {
|
|
217
220
|
try {
|
|
218
221
|
const startTime = Date.now();
|
|
219
|
-
const response = await fetch(`${ollamaUrl}/api/tags
|
|
222
|
+
const response = await fetch(`${ollamaUrl}/api/tags`, {
|
|
223
|
+
signal: AbortSignal.timeout(5_000),
|
|
224
|
+
});
|
|
220
225
|
const durationMs = Date.now() - startTime;
|
|
221
226
|
if (response.ok) {
|
|
222
227
|
health.embedding = {
|
|
@@ -327,6 +332,7 @@ async function runQuickCheck() {
|
|
|
327
332
|
Authorization: `Bearer ${supabaseKey}`,
|
|
328
333
|
Prefer: "count=exact",
|
|
329
334
|
},
|
|
335
|
+
signal: AbortSignal.timeout(5_000),
|
|
330
336
|
});
|
|
331
337
|
const count = response.headers.get("content-range");
|
|
332
338
|
if (count) {
|
|
@@ -403,6 +409,7 @@ async function runFullCheck() {
|
|
|
403
409
|
apikey: supabaseKey,
|
|
404
410
|
Authorization: `Bearer ${supabaseKey}`,
|
|
405
411
|
},
|
|
412
|
+
signal: AbortSignal.timeout(5_000),
|
|
406
413
|
});
|
|
407
414
|
}, 5);
|
|
408
415
|
}
|
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
export const ObservationSeveritySchema = z.enum(["info", "warning", "scar_candidate"]);
|
|
6
6
|
export const ObservationSchema = z.object({
|
|
7
|
-
source: z.string().min(1, "source is required — who made this observation?"),
|
|
8
|
-
text: z.string().min(1, "text is required — what was observed?"),
|
|
7
|
+
source: z.string().min(1, "source is required — who made this observation?").max(500),
|
|
8
|
+
text: z.string().min(1, "text is required — what was observed?").max(5000),
|
|
9
9
|
severity: ObservationSeveritySchema,
|
|
10
|
-
context: z.string().optional(),
|
|
10
|
+
context: z.string().max(1000).optional(),
|
|
11
11
|
});
|
|
12
12
|
export const AbsorbObservationsParamsSchema = z.object({
|
|
13
13
|
task_id: z.string().optional(),
|
|
@@ -7,14 +7,14 @@ import { ProjectSchema } from "./common.js";
|
|
|
7
7
|
* Create decision parameters schema
|
|
8
8
|
*/
|
|
9
9
|
export const CreateDecisionParamsSchema = z.object({
|
|
10
|
-
title: z.string().min(1, "title is required"),
|
|
11
|
-
decision: z.string().min(1, "decision text is required"),
|
|
12
|
-
rationale: z.string().min(1, "rationale is required"),
|
|
13
|
-
alternatives_considered: z.array(z.string()).optional(),
|
|
14
|
-
personas_involved: z.array(z.string()).optional(),
|
|
15
|
-
docs_affected: z.array(z.string()).optional(),
|
|
16
|
-
linear_issue: z.string().optional(),
|
|
17
|
-
session_id: z.string().optional(),
|
|
10
|
+
title: z.string().min(1, "title is required").max(500),
|
|
11
|
+
decision: z.string().min(1, "decision text is required").max(2000),
|
|
12
|
+
rationale: z.string().min(1, "rationale is required").max(2000),
|
|
13
|
+
alternatives_considered: z.array(z.string().max(1000)).optional(),
|
|
14
|
+
personas_involved: z.array(z.string().max(200)).optional(),
|
|
15
|
+
docs_affected: z.array(z.string().max(500)).optional(),
|
|
16
|
+
linear_issue: z.string().max(100).optional(),
|
|
17
|
+
session_id: z.string().max(100).optional(),
|
|
18
18
|
project: ProjectSchema.optional(),
|
|
19
19
|
});
|
|
20
20
|
/**
|
|
@@ -13,22 +13,22 @@ import { LearningTypeSchema, ScarSeveritySchema, ProjectSchema } from "./common.
|
|
|
13
13
|
export const CreateLearningParamsSchema = z
|
|
14
14
|
.object({
|
|
15
15
|
learning_type: LearningTypeSchema,
|
|
16
|
-
title: z.string().min(1, "title is required"),
|
|
17
|
-
description: z.string().min(1, "description is required"),
|
|
16
|
+
title: z.string().min(1, "title is required").max(1000),
|
|
17
|
+
description: z.string().min(1, "description is required").max(5000),
|
|
18
18
|
severity: ScarSeveritySchema.optional(),
|
|
19
|
-
scar_type: z.string().optional(),
|
|
20
|
-
counter_arguments: z.array(z.string()).optional(),
|
|
21
|
-
problem_context: z.string().optional(),
|
|
22
|
-
solution_approach: z.string().optional(),
|
|
23
|
-
applies_when: z.array(z.string()).optional(),
|
|
24
|
-
domain: z.array(z.string()).optional(),
|
|
25
|
-
keywords: z.array(z.string()).optional(),
|
|
26
|
-
source_linear_issue: z.string().optional(),
|
|
19
|
+
scar_type: z.string().max(100).optional(),
|
|
20
|
+
counter_arguments: z.array(z.string().max(2000)).optional(),
|
|
21
|
+
problem_context: z.string().max(2000).optional(),
|
|
22
|
+
solution_approach: z.string().max(2000).optional(),
|
|
23
|
+
applies_when: z.array(z.string().max(500)).optional(),
|
|
24
|
+
domain: z.array(z.string().max(100)).optional(),
|
|
25
|
+
keywords: z.array(z.string().max(100)).optional(),
|
|
26
|
+
source_linear_issue: z.string().max(100).optional(),
|
|
27
27
|
project: ProjectSchema.optional(),
|
|
28
28
|
// LLM-cooperative enforcement fields
|
|
29
|
-
why_this_matters: z.string().optional(),
|
|
30
|
-
action_protocol: z.array(z.string()).optional(),
|
|
31
|
-
self_check_criteria: z.array(z.string()).optional(),
|
|
29
|
+
why_this_matters: z.string().max(2000).optional(),
|
|
30
|
+
action_protocol: z.array(z.string().max(1000)).optional(),
|
|
31
|
+
self_check_criteria: z.array(z.string().max(1000)).optional(),
|
|
32
32
|
})
|
|
33
33
|
.superRefine((data, ctx) => {
|
|
34
34
|
// Scars require severity
|
|
@@ -11,10 +11,10 @@ export const PrepareContextFormatSchema = z.enum(["full", "compact", "gate"]);
|
|
|
11
11
|
* PrepareContext parameters schema
|
|
12
12
|
*/
|
|
13
13
|
export const PrepareContextParamsSchema = z.object({
|
|
14
|
-
plan: z.string().min(1, "plan is required - describe what the team is about to do"),
|
|
14
|
+
plan: z.string().min(1, "plan is required - describe what the team is about to do").max(500),
|
|
15
15
|
format: PrepareContextFormatSchema,
|
|
16
16
|
max_tokens: PositiveIntSchema.optional(),
|
|
17
|
-
agent_role: z.string().optional(),
|
|
17
|
+
agent_role: z.string().max(100).optional(),
|
|
18
18
|
project: ProjectSchema.optional(),
|
|
19
19
|
});
|
|
20
20
|
/**
|
package/dist/schemas/thread.js
CHANGED
|
@@ -11,14 +11,14 @@ export const ThreadStatusSchema = z.enum(["open", "resolved"]);
|
|
|
11
11
|
* Thread object schema (structured thread with lifecycle)
|
|
12
12
|
*/
|
|
13
13
|
export const ThreadObjectSchema = z.object({
|
|
14
|
-
id: z.string(),
|
|
15
|
-
text: z.string(),
|
|
14
|
+
id: z.string().max(100),
|
|
15
|
+
text: z.string().max(3000),
|
|
16
16
|
status: ThreadStatusSchema,
|
|
17
|
-
created_at: z.string(),
|
|
18
|
-
resolved_at: z.string().optional(),
|
|
19
|
-
source_session: z.string().optional(),
|
|
20
|
-
resolved_by_session: z.string().optional(),
|
|
21
|
-
resolution_note: z.string().optional(),
|
|
17
|
+
created_at: z.string().max(100),
|
|
18
|
+
resolved_at: z.string().max(100).optional(),
|
|
19
|
+
source_session: z.string().max(100).optional(),
|
|
20
|
+
resolved_by_session: z.string().max(100).optional(),
|
|
21
|
+
resolution_note: z.string().max(1000).optional(),
|
|
22
22
|
});
|
|
23
23
|
/**
|
|
24
24
|
* list_threads parameters
|
|
@@ -32,8 +32,8 @@ export const ListThreadsParamsSchema = z.object({
|
|
|
32
32
|
* resolve_thread parameters
|
|
33
33
|
*/
|
|
34
34
|
export const ResolveThreadParamsSchema = z.object({
|
|
35
|
-
thread_id: z.string().optional(),
|
|
36
|
-
text_match: z.string().optional(),
|
|
37
|
-
resolution_note: z.string().optional(),
|
|
35
|
+
thread_id: z.string().max(100).optional(),
|
|
36
|
+
text_match: z.string().max(500).optional(),
|
|
37
|
+
resolution_note: z.string().max(1000).optional(),
|
|
38
38
|
});
|
|
39
39
|
//# sourceMappingURL=thread.js.map
|
package/dist/server.js
CHANGED
|
@@ -330,7 +330,7 @@ export function createServer() {
|
|
|
330
330
|
.replace(/\b\d{5}\b/g, "[code]") // redact PG error codes
|
|
331
331
|
.replace(/at\s+\S+\s+\(.+\)/g, "") // strip stack frames
|
|
332
332
|
.slice(0, 200); // cap length
|
|
333
|
-
console.error(`[server] Tool error:`,
|
|
333
|
+
console.error(`[server] Tool error:`, safeMessage);
|
|
334
334
|
return {
|
|
335
335
|
content: [
|
|
336
336
|
{
|
|
@@ -38,10 +38,11 @@ export async function refreshBehavioralScores() {
|
|
|
38
38
|
"Content-Profile": "public",
|
|
39
39
|
},
|
|
40
40
|
body: "{}",
|
|
41
|
+
signal: AbortSignal.timeout(10_000),
|
|
41
42
|
});
|
|
42
43
|
if (!response.ok) {
|
|
43
44
|
const text = await response.text();
|
|
44
|
-
console.error(`[behavioral-decay] RPC failed: ${response.status} - ${text.slice(0, 200)}`);
|
|
45
|
+
console.error(`[behavioral-decay] RPC failed: ${response.status} - ${text.replace(/[\x00-\x1f\x7f]/g, "").slice(0, 200)}`);
|
|
45
46
|
return null;
|
|
46
47
|
}
|
|
47
48
|
const result = await response.json();
|
|
@@ -87,6 +88,7 @@ export async function fetchDismissalCounts(scarIds) {
|
|
|
87
88
|
"Content-Type": "application/json",
|
|
88
89
|
"Accept-Profile": "public",
|
|
89
90
|
},
|
|
91
|
+
signal: AbortSignal.timeout(10_000),
|
|
90
92
|
});
|
|
91
93
|
if (!response.ok) {
|
|
92
94
|
return result;
|
|
@@ -156,6 +156,7 @@ async function embedOpenAI(text, config) {
|
|
|
156
156
|
model: config.model,
|
|
157
157
|
input: text,
|
|
158
158
|
}),
|
|
159
|
+
signal: AbortSignal.timeout(30_000),
|
|
159
160
|
});
|
|
160
161
|
if (!response.ok) {
|
|
161
162
|
const errorText = await response.text();
|
|
@@ -180,6 +181,7 @@ async function embedOllama(text, config) {
|
|
|
180
181
|
model: config.model,
|
|
181
182
|
input: text,
|
|
182
183
|
}),
|
|
184
|
+
signal: AbortSignal.timeout(30_000),
|
|
183
185
|
});
|
|
184
186
|
if (!response.ok) {
|
|
185
187
|
const errorText = await response.text();
|
|
@@ -175,9 +175,11 @@ export class LocalFileStorage {
|
|
|
175
175
|
const existing = this.readCollection("learnings");
|
|
176
176
|
const existingIds = new Set(existing.map((e) => e.id));
|
|
177
177
|
let loaded = 0;
|
|
178
|
+
const now = new Date().toISOString();
|
|
178
179
|
for (const scar of scars) {
|
|
179
180
|
if (!existingIds.has(scar.id)) {
|
|
180
|
-
|
|
181
|
+
// Stamp created_at to install time so starter scars don't show stale ages
|
|
182
|
+
existing.push({ ...scar, created_at: now });
|
|
181
183
|
loaded++;
|
|
182
184
|
}
|
|
183
185
|
}
|