gitmem-mcp 1.2.1 → 1.3.0
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 +16 -0
- package/bin/init-wizard.js +307 -404
- package/bin/uninstall.js +79 -80
- package/dist/schemas/analyze.d.ts +2 -2
- package/dist/server.js +7 -0
- package/dist/services/enforcement.js +12 -14
- package/dist/services/metrics.d.ts +1 -1
- package/dist/services/metrics.js +1 -0
- package/dist/services/session-state.d.ts +23 -2
- package/dist/services/session-state.js +46 -0
- package/dist/tools/definitions.d.ts +460 -0
- package/dist/tools/definitions.js +81 -2
- package/dist/tools/recall.d.ts +1 -0
- package/dist/tools/recall.js +12 -1
- package/dist/tools/reflect-scars.d.ts +20 -0
- package/dist/tools/reflect-scars.js +219 -0
- package/dist/tools/session-close.js +28 -3
- package/dist/types/index.d.ts +24 -0
- package/hooks/scripts/auto-retrieve-hook.sh +31 -0
- package/package.json +1 -1
- package/schema/starter-scars.json +75 -3
package/bin/uninstall.js
CHANGED
|
@@ -28,6 +28,35 @@ const deleteAll = args.includes("--all");
|
|
|
28
28
|
const clientIdx = args.indexOf("--client");
|
|
29
29
|
const clientFlag = clientIdx !== -1 ? args[clientIdx + 1]?.toLowerCase() : null;
|
|
30
30
|
|
|
31
|
+
// ── Colors (brand-matched to init wizard) ──
|
|
32
|
+
|
|
33
|
+
const _color =
|
|
34
|
+
!process.env.NO_COLOR &&
|
|
35
|
+
!process.env.GITMEM_NO_COLOR &&
|
|
36
|
+
process.stdout.isTTY;
|
|
37
|
+
|
|
38
|
+
const C = {
|
|
39
|
+
reset: _color ? "\x1b[0m" : "",
|
|
40
|
+
bold: _color ? "\x1b[1m" : "",
|
|
41
|
+
dim: _color ? "\x1b[2m" : "",
|
|
42
|
+
red: _color ? "\x1b[31m" : "",
|
|
43
|
+
green: _color ? "\x1b[32m" : "",
|
|
44
|
+
yellow: _color ? "\x1b[33m" : "",
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const RIPPLE = `${C.dim}(${C.reset}${C.red}(${C.reset}${C.bold}\u25cf${C.reset}${C.red})${C.reset}${C.dim})${C.reset}`;
|
|
48
|
+
const PRODUCT = `${RIPPLE} ${C.red}gitmem${C.reset}`;
|
|
49
|
+
const CHECK = `${C.bold}\u2714${C.reset}`;
|
|
50
|
+
const SKIP = `${C.dim}\u00b7${C.reset}`;
|
|
51
|
+
|
|
52
|
+
let actionsTaken = 0;
|
|
53
|
+
|
|
54
|
+
function log(icon, msg, extra) {
|
|
55
|
+
if (icon === CHECK) actionsTaken++;
|
|
56
|
+
const suffix = extra ? ` ${C.dim}${extra}${C.reset}` : "";
|
|
57
|
+
console.log(`${icon} ${msg}${suffix}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
31
60
|
// ── Client Configuration ──
|
|
32
61
|
|
|
33
62
|
const CLIENT_CONFIGS = {
|
|
@@ -125,13 +154,11 @@ function writeJson(path, data) {
|
|
|
125
154
|
}
|
|
126
155
|
|
|
127
156
|
function isGitmemHook(entry) {
|
|
128
|
-
// Claude Code format: entry.hooks is an array of {command: "..."}
|
|
129
157
|
if (entry.hooks && Array.isArray(entry.hooks)) {
|
|
130
158
|
return entry.hooks.some(
|
|
131
159
|
(h) => typeof h.command === "string" && h.command.includes("gitmem")
|
|
132
160
|
);
|
|
133
161
|
}
|
|
134
|
-
// Cursor format: entry itself has {command: "..."}
|
|
135
162
|
if (typeof entry.command === "string") {
|
|
136
163
|
return entry.command.includes("gitmem");
|
|
137
164
|
}
|
|
@@ -142,49 +169,47 @@ function isGitmemHook(entry) {
|
|
|
142
169
|
|
|
143
170
|
function stepInstructions() {
|
|
144
171
|
if (!existsSync(cc.instructionsFile)) {
|
|
145
|
-
|
|
172
|
+
log(SKIP, `No ${cc.instructionsName} found`);
|
|
146
173
|
return;
|
|
147
174
|
}
|
|
148
175
|
|
|
149
176
|
let content = readFileSync(cc.instructionsFile, "utf-8");
|
|
150
177
|
|
|
151
178
|
if (!content.includes(cc.startMarker)) {
|
|
152
|
-
|
|
179
|
+
log(SKIP, `No gitmem section in ${cc.instructionsName}`);
|
|
153
180
|
return;
|
|
154
181
|
}
|
|
155
182
|
|
|
156
183
|
const startIdx = content.indexOf(cc.startMarker);
|
|
157
184
|
const endIdx = content.indexOf(cc.endMarker);
|
|
158
185
|
if (startIdx === -1 || endIdx === -1) {
|
|
159
|
-
|
|
186
|
+
log(SKIP, `Malformed gitmem markers in ${cc.instructionsName}`);
|
|
160
187
|
return;
|
|
161
188
|
}
|
|
162
189
|
|
|
163
|
-
// Remove the block including markers and surrounding whitespace
|
|
164
190
|
const before = content.slice(0, startIdx).trimEnd();
|
|
165
191
|
const after = content.slice(endIdx + cc.endMarker.length).trimStart();
|
|
166
|
-
|
|
167
192
|
const result = before + (before && after ? "\n\n" : "") + after;
|
|
168
193
|
|
|
169
194
|
if (result.trim() === "") {
|
|
170
195
|
rmSync(cc.instructionsFile);
|
|
171
|
-
|
|
196
|
+
log(CHECK, `Removed ${cc.instructionsName}`, "(was gitmem-only)");
|
|
172
197
|
} else {
|
|
173
198
|
writeFileSync(cc.instructionsFile, result.trimEnd() + "\n");
|
|
174
|
-
|
|
199
|
+
log(CHECK, `Stripped gitmem section from ${cc.instructionsName}`, "(your content preserved)");
|
|
175
200
|
}
|
|
176
201
|
}
|
|
177
202
|
|
|
178
203
|
function stepMcpJson() {
|
|
179
204
|
const config = readJson(cc.mcpConfigPath);
|
|
180
205
|
if (!config?.mcpServers) {
|
|
181
|
-
|
|
206
|
+
log(SKIP, `No ${cc.mcpConfigName} found`);
|
|
182
207
|
return;
|
|
183
208
|
}
|
|
184
209
|
|
|
185
210
|
const had = !!config.mcpServers.gitmem || !!config.mcpServers["gitmem-mcp"];
|
|
186
211
|
if (!had) {
|
|
187
|
-
|
|
212
|
+
log(SKIP, `No gitmem in ${cc.mcpConfigName}`);
|
|
188
213
|
return;
|
|
189
214
|
}
|
|
190
215
|
|
|
@@ -193,9 +218,11 @@ function stepMcpJson() {
|
|
|
193
218
|
|
|
194
219
|
const remaining = Object.keys(config.mcpServers).length;
|
|
195
220
|
writeJson(cc.mcpConfigPath, config);
|
|
196
|
-
|
|
197
|
-
`
|
|
198
|
-
|
|
221
|
+
if (remaining > 0) {
|
|
222
|
+
log(CHECK, `Removed gitmem server`, `(${remaining} other server${remaining !== 1 ? "s" : ""} preserved)`);
|
|
223
|
+
} else {
|
|
224
|
+
log(CHECK, `Removed gitmem server from ${cc.mcpConfigName}`);
|
|
225
|
+
}
|
|
199
226
|
}
|
|
200
227
|
|
|
201
228
|
function stepHooks() {
|
|
@@ -208,7 +235,7 @@ function stepHooks() {
|
|
|
208
235
|
function stepHooksClaude() {
|
|
209
236
|
const settings = readJson(cc.settingsFile);
|
|
210
237
|
if (!settings?.hooks) {
|
|
211
|
-
|
|
238
|
+
log(SKIP, "No hooks in .claude/settings.json");
|
|
212
239
|
return;
|
|
213
240
|
}
|
|
214
241
|
|
|
@@ -232,7 +259,7 @@ function stepHooksClaude() {
|
|
|
232
259
|
}
|
|
233
260
|
|
|
234
261
|
if (removed === 0) {
|
|
235
|
-
|
|
262
|
+
log(SKIP, "No gitmem hooks found");
|
|
236
263
|
return;
|
|
237
264
|
}
|
|
238
265
|
|
|
@@ -243,18 +270,17 @@ function stepHooksClaude() {
|
|
|
243
270
|
}
|
|
244
271
|
|
|
245
272
|
writeJson(cc.settingsFile, settings);
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
);
|
|
273
|
+
if (preserved > 0) {
|
|
274
|
+
log(CHECK, "Removed automatic memory hooks", `(${preserved} other hook${preserved !== 1 ? "s" : ""} preserved)`);
|
|
275
|
+
} else {
|
|
276
|
+
log(CHECK, "Removed automatic memory hooks");
|
|
277
|
+
}
|
|
252
278
|
}
|
|
253
279
|
|
|
254
280
|
function stepHooksCursor() {
|
|
255
281
|
const config = readJson(cc.hooksFile);
|
|
256
282
|
if (!config?.hooks) {
|
|
257
|
-
|
|
283
|
+
log(SKIP, `No hooks in ${cc.hooksFileName}`);
|
|
258
284
|
return;
|
|
259
285
|
}
|
|
260
286
|
|
|
@@ -278,7 +304,7 @@ function stepHooksCursor() {
|
|
|
278
304
|
}
|
|
279
305
|
|
|
280
306
|
if (removed === 0) {
|
|
281
|
-
|
|
307
|
+
log(SKIP, "No gitmem hooks found");
|
|
282
308
|
return;
|
|
283
309
|
}
|
|
284
310
|
|
|
@@ -289,38 +315,36 @@ function stepHooksCursor() {
|
|
|
289
315
|
}
|
|
290
316
|
|
|
291
317
|
writeJson(cc.hooksFile, config);
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
);
|
|
318
|
+
if (preserved > 0) {
|
|
319
|
+
log(CHECK, "Removed automatic memory hooks", `(${preserved} other hook${preserved !== 1 ? "s" : ""} preserved)`);
|
|
320
|
+
} else {
|
|
321
|
+
log(CHECK, "Removed automatic memory hooks");
|
|
322
|
+
}
|
|
298
323
|
}
|
|
299
324
|
|
|
300
325
|
function stepPermissions() {
|
|
301
326
|
if (!cc.hasPermissions) {
|
|
302
|
-
|
|
327
|
+
log(SKIP, `Not needed for ${cc.name}`);
|
|
303
328
|
return;
|
|
304
329
|
}
|
|
305
330
|
|
|
306
331
|
const settings = readJson(cc.settingsFile);
|
|
307
332
|
const allow = settings?.permissions?.allow;
|
|
308
333
|
if (!Array.isArray(allow)) {
|
|
309
|
-
|
|
334
|
+
log(SKIP, "No permissions in .claude/settings.json");
|
|
310
335
|
return;
|
|
311
336
|
}
|
|
312
337
|
|
|
313
338
|
const pattern = "mcp__gitmem__*";
|
|
314
339
|
const idx = allow.indexOf(pattern);
|
|
315
340
|
if (idx === -1) {
|
|
316
|
-
|
|
341
|
+
log(SKIP, "No gitmem permissions found");
|
|
317
342
|
return;
|
|
318
343
|
}
|
|
319
344
|
|
|
320
345
|
allow.splice(idx, 1);
|
|
321
346
|
settings.permissions.allow = allow;
|
|
322
347
|
|
|
323
|
-
// Clean up empty permissions
|
|
324
348
|
if (allow.length === 0) {
|
|
325
349
|
delete settings.permissions.allow;
|
|
326
350
|
}
|
|
@@ -332,31 +356,26 @@ function stepPermissions() {
|
|
|
332
356
|
}
|
|
333
357
|
|
|
334
358
|
writeJson(cc.settingsFile, settings);
|
|
335
|
-
|
|
359
|
+
log(CHECK, "Removed tool permissions");
|
|
336
360
|
}
|
|
337
361
|
|
|
338
362
|
async function stepGitmemDir() {
|
|
339
363
|
if (!existsSync(gitmemDir)) {
|
|
340
|
-
|
|
364
|
+
log(SKIP, "No .gitmem/ directory");
|
|
341
365
|
return;
|
|
342
366
|
}
|
|
343
367
|
|
|
344
368
|
if (deleteAll) {
|
|
345
369
|
rmSync(gitmemDir, { recursive: true, force: true });
|
|
346
|
-
|
|
370
|
+
log(CHECK, "Deleted .gitmem/ directory");
|
|
347
371
|
return;
|
|
348
372
|
}
|
|
349
373
|
|
|
350
|
-
|
|
351
|
-
"
|
|
352
|
-
);
|
|
353
|
-
|
|
354
|
-
// Default to No for data deletion
|
|
355
|
-
if (await confirm("Delete .gitmem/?", false)) {
|
|
356
|
-
rmSync(gitmemDir, { recursive: true, force: true });
|
|
357
|
-
console.log(" Deleted .gitmem/ directory");
|
|
374
|
+
if (await confirm("Keep .gitmem/ memory data for future use?", true)) {
|
|
375
|
+
log(CHECK, ".gitmem/ preserved — your memories will be here if you reinstall");
|
|
358
376
|
} else {
|
|
359
|
-
|
|
377
|
+
rmSync(gitmemDir, { recursive: true, force: true });
|
|
378
|
+
log(CHECK, "Deleted .gitmem/ directory");
|
|
360
379
|
}
|
|
361
380
|
}
|
|
362
381
|
|
|
@@ -366,58 +385,38 @@ function stepGitignore() {
|
|
|
366
385
|
let content = readFileSync(gitignorePath, "utf-8");
|
|
367
386
|
if (!content.includes(".gitmem/")) return;
|
|
368
387
|
|
|
369
|
-
// Remove the .gitmem/ line
|
|
370
388
|
const lines = content.split("\n");
|
|
371
389
|
const filtered = lines.filter((line) => line.trim() !== ".gitmem/");
|
|
372
390
|
writeFileSync(gitignorePath, filtered.join("\n"));
|
|
391
|
+
log(CHECK, "Cleaned .gitignore");
|
|
373
392
|
}
|
|
374
393
|
|
|
375
394
|
// ── Main ──
|
|
376
395
|
|
|
377
396
|
async function main() {
|
|
397
|
+
const pkg = readJson(join(__dirname, "..", "package.json"));
|
|
398
|
+
const version = pkg?.version || "1.0.0";
|
|
399
|
+
|
|
378
400
|
console.log("");
|
|
379
|
-
console.log(
|
|
380
|
-
|
|
381
|
-
console.log(` (client: ${client} — via --client flag)`);
|
|
382
|
-
} else {
|
|
383
|
-
console.log(` (client: ${client} — auto-detected)`);
|
|
384
|
-
}
|
|
401
|
+
console.log(`${PRODUCT} \u2500\u2500 uninstall v${version}`);
|
|
402
|
+
console.log(`${C.dim}Removing gitmem from ${cc.name}${clientFlag ? "" : " (auto-detected)"}${C.reset}`);
|
|
385
403
|
console.log("");
|
|
386
404
|
|
|
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
405
|
stepInstructions();
|
|
392
|
-
console.log("");
|
|
393
|
-
step++;
|
|
394
|
-
|
|
395
|
-
console.log(` Step ${step}/${stepCount} — Remove gitmem from ${cc.mcpConfigName}`);
|
|
396
406
|
stepMcpJson();
|
|
397
|
-
console.log("");
|
|
398
|
-
step++;
|
|
399
|
-
|
|
400
|
-
const hooksTarget = cc.hooksInSettings ? ".claude/settings.json" : cc.hooksFileName;
|
|
401
|
-
console.log(` Step ${step}/${stepCount} — Remove gitmem hooks from ${hooksTarget}`);
|
|
402
407
|
stepHooks();
|
|
403
|
-
|
|
404
|
-
step++;
|
|
405
|
-
|
|
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
|
-
}
|
|
412
|
-
|
|
413
|
-
console.log(` Step ${step}/${stepCount} — Delete .gitmem/ directory?`);
|
|
408
|
+
stepPermissions();
|
|
414
409
|
await stepGitmemDir();
|
|
415
|
-
|
|
416
|
-
// Also clean .gitignore entry
|
|
417
410
|
stepGitignore();
|
|
418
411
|
|
|
419
412
|
console.log("");
|
|
420
|
-
|
|
413
|
+
if (actionsTaken > 0) {
|
|
414
|
+
console.log(`${C.dim}gitmem-mcp has been removed.${C.reset}`);
|
|
415
|
+
console.log(`${C.dim}Reinstall anytime: ${C.reset}${C.red}npx gitmem-mcp init${C.reset}`);
|
|
416
|
+
} else {
|
|
417
|
+
console.log(`${C.dim}gitmem-mcp is not installed in this project.${C.reset}`);
|
|
418
|
+
console.log(`${C.dim}Install: ${C.reset}${C.red}npx gitmem-mcp init${C.reset}`);
|
|
419
|
+
}
|
|
421
420
|
console.log("");
|
|
422
421
|
|
|
423
422
|
if (rl) rl.close();
|
|
@@ -18,12 +18,12 @@ export declare const AnalyzeParamsSchema: z.ZodObject<{
|
|
|
18
18
|
}, "strip", z.ZodTypeAny, {
|
|
19
19
|
project?: string | undefined;
|
|
20
20
|
agent?: string | undefined;
|
|
21
|
-
lens?: "
|
|
21
|
+
lens?: "reflections" | "summary" | "blindspots" | undefined;
|
|
22
22
|
days?: number | undefined;
|
|
23
23
|
}, {
|
|
24
24
|
project?: string | undefined;
|
|
25
25
|
agent?: string | undefined;
|
|
26
|
-
lens?: "
|
|
26
|
+
lens?: "reflections" | "summary" | "blindspots" | undefined;
|
|
27
27
|
days?: number | undefined;
|
|
28
28
|
}>;
|
|
29
29
|
export type AnalyzeParams = z.infer<typeof AnalyzeParamsSchema>;
|
package/dist/server.js
CHANGED
|
@@ -18,6 +18,7 @@ import { recordScarUsage } from "./tools/record-scar-usage.js";
|
|
|
18
18
|
import { recordScarUsageBatch } from "./tools/record-scar-usage-batch.js";
|
|
19
19
|
import { recall } from "./tools/recall.js";
|
|
20
20
|
import { confirmScars } from "./tools/confirm-scars.js";
|
|
21
|
+
import { reflectScars } from "./tools/reflect-scars.js";
|
|
21
22
|
import { saveTranscript } from "./tools/save-transcript.js";
|
|
22
23
|
import { getTranscript } from "./tools/get-transcript.js";
|
|
23
24
|
import { searchTranscripts } from "./tools/search-transcripts.js";
|
|
@@ -103,6 +104,11 @@ export function createServer() {
|
|
|
103
104
|
case "gm-confirm":
|
|
104
105
|
result = await confirmScars(toolArgs);
|
|
105
106
|
break;
|
|
107
|
+
case "reflect_scars":
|
|
108
|
+
case "gitmem-rf":
|
|
109
|
+
case "gm-reflect":
|
|
110
|
+
result = await reflectScars(toolArgs);
|
|
111
|
+
break;
|
|
106
112
|
case "session_start":
|
|
107
113
|
case "gitmem-ss":
|
|
108
114
|
case "gm-open":
|
|
@@ -213,6 +219,7 @@ export function createServer() {
|
|
|
213
219
|
const commands = [
|
|
214
220
|
{ alias: "gitmem-r", full: "recall", description: "Check scars before taking action" },
|
|
215
221
|
{ alias: "gitmem-cs", full: "confirm_scars", description: "Confirm recalled scars (APPLYING/N_A/REFUTED)" },
|
|
222
|
+
{ alias: "gitmem-rf", full: "reflect_scars", description: "End-of-session scar reflection (OBEYED/REFUTED)" },
|
|
216
223
|
{ alias: "gitmem-ss", full: "session_start", description: "Initialize session with context" },
|
|
217
224
|
{ alias: "gitmem-sr", full: "session_refresh", description: "Refresh context for active session" },
|
|
218
225
|
{ alias: "gitmem-sc", full: "session_close", description: "Close session with compliance validation" },
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* - Universal: works in ALL MCP clients, no IDE hooks needed
|
|
11
11
|
* - Lightweight: pure in-memory checks, no I/O
|
|
12
12
|
*/
|
|
13
|
-
import { getCurrentSession, hasUnconfirmedScars, getSurfacedScars } from "./session-state.js";
|
|
13
|
+
import { getCurrentSession, hasUnconfirmedScars, getSurfacedScars, isRecallCalled } from "./session-state.js";
|
|
14
14
|
/**
|
|
15
15
|
* Tools that require an active session to function properly.
|
|
16
16
|
* Read-only/administrative tools are excluded.
|
|
@@ -107,19 +107,17 @@ export function checkEnforcement(toolName) {
|
|
|
107
107
|
};
|
|
108
108
|
}
|
|
109
109
|
// Check 3: No recall before consequential action
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
};
|
|
122
|
-
}
|
|
110
|
+
// Uses recallCalled boolean to avoid false positives when recall returns 0 scars
|
|
111
|
+
if (CONSEQUENTIAL_TOOLS.has(toolName) && !isRecallCalled()) {
|
|
112
|
+
return {
|
|
113
|
+
warning: [
|
|
114
|
+
"--- gitmem enforcement ---",
|
|
115
|
+
"No recall() was run this session before this action.",
|
|
116
|
+
"Consider calling recall() first to check for relevant institutional memory.",
|
|
117
|
+
"Past mistakes and patterns may prevent repeating known issues.",
|
|
118
|
+
"---",
|
|
119
|
+
].join("\n"),
|
|
120
|
+
};
|
|
123
121
|
}
|
|
124
122
|
return { warning: null };
|
|
125
123
|
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
/**
|
|
8
8
|
* Tool names that can be tracked
|
|
9
9
|
*/
|
|
10
|
-
export type ToolName = "recall" | "search" | "log" | "session_start" | "session_refresh" | "session_close" | "create_learning" | "create_decision" | "record_scar_usage" | "record_scar_usage_batch" | "save_transcript" | "get_transcript" | "search_transcripts" | "analyze" | "graph_traverse" | "prepare_context" | "absorb_observations" | "list_threads" | "resolve_thread" | "create_thread" | "confirm_scars" | "cleanup_threads" | "health";
|
|
10
|
+
export type ToolName = "recall" | "search" | "log" | "session_start" | "session_refresh" | "session_close" | "create_learning" | "create_decision" | "record_scar_usage" | "record_scar_usage_batch" | "save_transcript" | "get_transcript" | "search_transcripts" | "analyze" | "graph_traverse" | "prepare_context" | "absorb_observations" | "list_threads" | "resolve_thread" | "create_thread" | "confirm_scars" | "reflect_scars" | "cleanup_threads" | "health";
|
|
11
11
|
/**
|
|
12
12
|
* Phase tags for context
|
|
13
13
|
*/
|
package/dist/services/metrics.js
CHANGED
|
@@ -33,6 +33,7 @@ export const PERFORMANCE_TARGETS = {
|
|
|
33
33
|
resolve_thread: 100, // In-memory mutation + file write
|
|
34
34
|
create_thread: 100, // In-memory mutation + file write
|
|
35
35
|
confirm_scars: 500, // In-memory validation + file write
|
|
36
|
+
reflect_scars: 500, // In-memory validation + file write
|
|
36
37
|
cleanup_threads: 2000, // Fetch all threads + lifecycle computation
|
|
37
38
|
health: 100, // In-memory read from EffectTracker
|
|
38
39
|
};
|
|
@@ -11,15 +11,17 @@
|
|
|
11
11
|
*
|
|
12
12
|
* This allows recall() to always assign variants even without explicit parameters.
|
|
13
13
|
*/
|
|
14
|
-
import type { SurfacedScar, ScarConfirmation, Observation, SessionChild, ThreadObject } from "../types/index.js";
|
|
14
|
+
import type { SurfacedScar, ScarConfirmation, ScarReflection, Observation, SessionChild, ThreadObject } from "../types/index.js";
|
|
15
15
|
interface SessionContext {
|
|
16
16
|
sessionId: string;
|
|
17
17
|
linearIssue?: string;
|
|
18
18
|
agent?: string;
|
|
19
19
|
project?: string;
|
|
20
20
|
startedAt: Date;
|
|
21
|
+
recallCalled: boolean;
|
|
21
22
|
surfacedScars: SurfacedScar[];
|
|
22
23
|
confirmations: ScarConfirmation[];
|
|
24
|
+
reflections: ScarReflection[];
|
|
23
25
|
observations: Observation[];
|
|
24
26
|
children: SessionChild[];
|
|
25
27
|
threads: ThreadObject[];
|
|
@@ -28,7 +30,7 @@ interface SessionContext {
|
|
|
28
30
|
* Set the current active session
|
|
29
31
|
* Called by session_start
|
|
30
32
|
*/
|
|
31
|
-
export declare function setCurrentSession(context: Omit<SessionContext, 'surfacedScars' | 'confirmations' | 'observations' | 'children' | 'threads'> & {
|
|
33
|
+
export declare function setCurrentSession(context: Omit<SessionContext, 'recallCalled' | 'surfacedScars' | 'confirmations' | 'reflections' | 'observations' | 'children' | 'threads'> & {
|
|
32
34
|
surfacedScars?: SurfacedScar[];
|
|
33
35
|
observations?: Observation[];
|
|
34
36
|
children?: SessionChild[];
|
|
@@ -53,6 +55,16 @@ export declare function getProject(): string | null;
|
|
|
53
55
|
* Check if currently working on a Linear issue
|
|
54
56
|
*/
|
|
55
57
|
export declare function hasActiveIssue(): boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Mark that recall() was called this session (independent of whether it returned scars).
|
|
60
|
+
* Called by recall tool before any early return.
|
|
61
|
+
*/
|
|
62
|
+
export declare function setRecallCalled(): void;
|
|
63
|
+
/**
|
|
64
|
+
* Check if recall() was called this session.
|
|
65
|
+
* Used by enforcement to avoid false positives when recall returns 0 scars.
|
|
66
|
+
*/
|
|
67
|
+
export declare function isRecallCalled(): boolean;
|
|
56
68
|
/**
|
|
57
69
|
* Add surfaced scars to tracking (deduplicates by scar_id)
|
|
58
70
|
* Called by session_start and recall when scars are surfaced.
|
|
@@ -71,6 +83,15 @@ export declare function addConfirmations(confirmations: ScarConfirmation[]): voi
|
|
|
71
83
|
* Get all scar confirmations for the current session.
|
|
72
84
|
*/
|
|
73
85
|
export declare function getConfirmations(): ScarConfirmation[];
|
|
86
|
+
/**
|
|
87
|
+
* Add end-of-session scar reflections (OBEYED/REFUTED) to the current session.
|
|
88
|
+
* Called by reflect_scars tool after validation.
|
|
89
|
+
*/
|
|
90
|
+
export declare function addReflections(reflections: ScarReflection[]): void;
|
|
91
|
+
/**
|
|
92
|
+
* Get all end-of-session scar reflections for the current session.
|
|
93
|
+
*/
|
|
94
|
+
export declare function getReflections(): ScarReflection[];
|
|
74
95
|
/**
|
|
75
96
|
* Check if there are recall-surfaced scars that haven't been confirmed.
|
|
76
97
|
* Only checks scars with source "recall" — session_start scars don't require confirmation.
|
|
@@ -20,8 +20,10 @@ let currentSession = null;
|
|
|
20
20
|
export function setCurrentSession(context) {
|
|
21
21
|
currentSession = {
|
|
22
22
|
...context,
|
|
23
|
+
recallCalled: false,
|
|
23
24
|
surfacedScars: context.surfacedScars || [],
|
|
24
25
|
confirmations: [],
|
|
26
|
+
reflections: [],
|
|
25
27
|
observations: context.observations || [],
|
|
26
28
|
children: context.children || [],
|
|
27
29
|
threads: context.threads || [],
|
|
@@ -58,6 +60,23 @@ export function getProject() {
|
|
|
58
60
|
export function hasActiveIssue() {
|
|
59
61
|
return !!(currentSession?.linearIssue);
|
|
60
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Mark that recall() was called this session (independent of whether it returned scars).
|
|
65
|
+
* Called by recall tool before any early return.
|
|
66
|
+
*/
|
|
67
|
+
export function setRecallCalled() {
|
|
68
|
+
if (currentSession) {
|
|
69
|
+
currentSession.recallCalled = true;
|
|
70
|
+
console.error("[session-state] recall() marked as called");
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Check if recall() was called this session.
|
|
75
|
+
* Used by enforcement to avoid false positives when recall returns 0 scars.
|
|
76
|
+
*/
|
|
77
|
+
export function isRecallCalled() {
|
|
78
|
+
return currentSession?.recallCalled ?? false;
|
|
79
|
+
}
|
|
61
80
|
/**
|
|
62
81
|
* Add surfaced scars to tracking (deduplicates by scar_id)
|
|
63
82
|
* Called by session_start and recall when scars are surfaced.
|
|
@@ -108,6 +127,33 @@ export function addConfirmations(confirmations) {
|
|
|
108
127
|
export function getConfirmations() {
|
|
109
128
|
return currentSession?.confirmations || [];
|
|
110
129
|
}
|
|
130
|
+
/**
|
|
131
|
+
* Add end-of-session scar reflections (OBEYED/REFUTED) to the current session.
|
|
132
|
+
* Called by reflect_scars tool after validation.
|
|
133
|
+
*/
|
|
134
|
+
export function addReflections(reflections) {
|
|
135
|
+
if (!currentSession) {
|
|
136
|
+
console.warn("[session-state] Cannot add reflections: no active session");
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
for (const ref of reflections) {
|
|
140
|
+
// Replace existing reflection for same scar_id (allow re-reflection)
|
|
141
|
+
const idx = currentSession.reflections.findIndex(r => r.scar_id === ref.scar_id);
|
|
142
|
+
if (idx >= 0) {
|
|
143
|
+
currentSession.reflections[idx] = ref;
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
currentSession.reflections.push(ref);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
console.error(`[session-state] Reflections tracked: ${currentSession.reflections.length} total`);
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Get all end-of-session scar reflections for the current session.
|
|
153
|
+
*/
|
|
154
|
+
export function getReflections() {
|
|
155
|
+
return currentSession?.reflections || [];
|
|
156
|
+
}
|
|
111
157
|
/**
|
|
112
158
|
* Check if there are recall-surfaced scars that haven't been confirmed.
|
|
113
159
|
* Only checks scars with source "recall" — session_start scars don't require confirmation.
|