remem-mcp 0.7.3 → 0.7.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 +20 -59
- package/dist/{chunk-ITMR6THY.js → chunk-2RKMX6EE.js} +5 -4
- package/dist/chunk-2RKMX6EE.js.map +1 -0
- package/dist/{chunk-4WCDAEPP.js → chunk-L7YZBBIB.js} +306 -32
- package/dist/chunk-L7YZBBIB.js.map +1 -0
- package/dist/{chunk-YU7WBJU5.js → chunk-MPEN2X43.js} +106 -2
- package/dist/{chunk-YU7WBJU5.js.map → chunk-MPEN2X43.js.map} +1 -1
- package/dist/{engine-S47HHGAO.js → engine-LAY25SCA.js} +4 -2
- package/dist/index.js +440 -261
- package/dist/index.js.map +1 -1
- package/dist/sdk.js +3 -3
- package/dist/{server-5PWQTRGZ.js → server-MYSF7R5Y.js} +4 -3
- package/dist/server-MYSF7R5Y.js.map +1 -0
- package/dist/storage/schema.sql +15 -5
- package/package.json +1 -1
- package/skills/remem-mcp/SKILL.md +3 -1
- package/dist/chunk-4WCDAEPP.js.map +0 -1
- package/dist/chunk-ITMR6THY.js.map +0 -1
- package/dist/server-5PWQTRGZ.js.map +0 -1
- /package/dist/{engine-S47HHGAO.js.map → engine-LAY25SCA.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -8,25 +8,26 @@ import {
|
|
|
8
8
|
ingestFile,
|
|
9
9
|
redact,
|
|
10
10
|
searchWiki
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-2RKMX6EE.js";
|
|
12
12
|
import {
|
|
13
13
|
engine_exports,
|
|
14
14
|
findCallees,
|
|
15
15
|
findCallers,
|
|
16
|
+
findEntryPoints,
|
|
16
17
|
impactAnalysis,
|
|
17
18
|
indexDirectory,
|
|
18
19
|
indexFile,
|
|
19
20
|
init_engine,
|
|
20
21
|
listSymbols,
|
|
21
22
|
searchSymbols
|
|
22
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-MPEN2X43.js";
|
|
23
24
|
import {
|
|
24
25
|
LocalEmbedder,
|
|
25
26
|
SQLiteBackend,
|
|
26
27
|
init_local,
|
|
27
28
|
local_exports,
|
|
28
29
|
stripQueryProperNouns
|
|
29
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-L7YZBBIB.js";
|
|
30
31
|
import {
|
|
31
32
|
generateId,
|
|
32
33
|
init_ulid
|
|
@@ -39,11 +40,11 @@ import {
|
|
|
39
40
|
|
|
40
41
|
// src/index.ts
|
|
41
42
|
init_esm_shims();
|
|
42
|
-
import { createHash as
|
|
43
|
+
import { createHash as createHash9 } from "crypto";
|
|
43
44
|
import { existsSync as existsSync16, readdirSync as readdirSync2, readFileSync as readFileSync13, statSync as statSync3 } from "fs";
|
|
44
45
|
import { homedir as homedir13 } from "os";
|
|
45
46
|
import { dirname as dirname10, join as join16 } from "path";
|
|
46
|
-
import { fileURLToPath as
|
|
47
|
+
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
47
48
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
48
49
|
import Database13 from "better-sqlite3";
|
|
49
50
|
import * as sqliteVec4 from "sqlite-vec";
|
|
@@ -274,7 +275,12 @@ async function atomsCommand(dbPath, flags) {
|
|
|
274
275
|
|
|
275
276
|
// src/cli/consolidate.ts
|
|
276
277
|
init_esm_shims();
|
|
278
|
+
import { createHash as createHash2 } from "crypto";
|
|
277
279
|
init_ulid();
|
|
280
|
+
function cliSessionKey() {
|
|
281
|
+
if (process.env.REMEM_SESSION_KEY) return process.env.REMEM_SESSION_KEY;
|
|
282
|
+
return createHash2("sha256").update(process.cwd()).digest("hex").slice(0, 16);
|
|
283
|
+
}
|
|
278
284
|
async function consolidateCommand(dbPath, flags) {
|
|
279
285
|
const storage = new SQLiteBackend(dbPath);
|
|
280
286
|
try {
|
|
@@ -321,7 +327,8 @@ async function consolidateCommand(dbPath, flags) {
|
|
|
321
327
|
atomIds: groupAtoms.map((a) => a.id),
|
|
322
328
|
summary: summary2.slice(0, 300),
|
|
323
329
|
personaTags: [topic],
|
|
324
|
-
createdAt: Date.now()
|
|
330
|
+
createdAt: Date.now(),
|
|
331
|
+
sessionKey: cliSessionKey()
|
|
325
332
|
});
|
|
326
333
|
console.log(` [${topic}] ${groupAtoms.length} atoms \u2192 ${id2}`);
|
|
327
334
|
count++;
|
|
@@ -348,7 +355,8 @@ Created ${count} scenario(s).`);
|
|
|
348
355
|
atomIds,
|
|
349
356
|
summary,
|
|
350
357
|
personaTags: tags,
|
|
351
|
-
createdAt: Date.now()
|
|
358
|
+
createdAt: Date.now(),
|
|
359
|
+
sessionKey: cliSessionKey()
|
|
352
360
|
});
|
|
353
361
|
console.log(`Consolidated ${atomIds.length} atoms into scenario ${id}.`);
|
|
354
362
|
} finally {
|
|
@@ -360,7 +368,7 @@ Created ${count} scenario(s).`);
|
|
|
360
368
|
init_esm_shims();
|
|
361
369
|
init_engine();
|
|
362
370
|
import { execSync, spawn } from "child_process";
|
|
363
|
-
import { createHash as
|
|
371
|
+
import { createHash as createHash3 } from "crypto";
|
|
364
372
|
import { existsSync as existsSync3, mkdirSync as mkdirSync3, mkdtempSync, readFileSync as readFileSync2, rmSync, writeFileSync } from "fs";
|
|
365
373
|
import { homedir, tmpdir } from "os";
|
|
366
374
|
import { dirname as dirname3, join as join3 } from "path";
|
|
@@ -585,7 +593,7 @@ async function demo() {
|
|
|
585
593
|
}
|
|
586
594
|
db.close();
|
|
587
595
|
const project = join3(tmpDir, "project");
|
|
588
|
-
const sessionKey =
|
|
596
|
+
const sessionKey = createHash3("sha256").update(project).digest("hex").slice(0, 16);
|
|
589
597
|
line(` ${C.gray}Setting up test project...${C.reset}`);
|
|
590
598
|
createRealProject(project, true);
|
|
591
599
|
await sleep(500);
|
|
@@ -1254,6 +1262,7 @@ async function knowledgeCommand(dbPath, flags) {
|
|
|
1254
1262
|
|
|
1255
1263
|
// src/cli/persona.ts
|
|
1256
1264
|
init_esm_shims();
|
|
1265
|
+
import { createHash as createHash4 } from "crypto";
|
|
1257
1266
|
import Database3 from "better-sqlite3";
|
|
1258
1267
|
async function personaCommand(dbPath, flags) {
|
|
1259
1268
|
const storage = new SQLiteBackend(dbPath);
|
|
@@ -1261,13 +1270,14 @@ async function personaCommand(dbPath, flags) {
|
|
|
1261
1270
|
const teamId = flags["team-id"] ?? "default";
|
|
1262
1271
|
const agentId = flags["agent-id"] ?? "unknown";
|
|
1263
1272
|
const userId = flags["user-id"] ?? "default";
|
|
1273
|
+
const sessionKey = flags["session-key"] ?? (process.env.REMEM_SESSION_KEY ? process.env.REMEM_SESSION_KEY : createHash4("sha256").update(process.cwd()).digest("hex").slice(0, 16));
|
|
1264
1274
|
if (flags.clear !== void 0) {
|
|
1265
|
-
await storage.writePersona(teamId, agentId, userId, "");
|
|
1275
|
+
await storage.writePersona(teamId, agentId, userId, "", sessionKey);
|
|
1266
1276
|
console.log("Persona cleared.");
|
|
1267
1277
|
return;
|
|
1268
1278
|
}
|
|
1269
1279
|
if (flags.set) {
|
|
1270
|
-
const existing = await storage.readPersona(teamId, agentId, userId);
|
|
1280
|
+
const existing = await storage.readPersona(teamId, agentId, userId, sessionKey);
|
|
1271
1281
|
const entry = flags.set;
|
|
1272
1282
|
const [trait] = entry.split(":");
|
|
1273
1283
|
let content;
|
|
@@ -1287,15 +1297,20 @@ async function personaCommand(dbPath, flags) {
|
|
|
1287
1297
|
} else {
|
|
1288
1298
|
content = entry;
|
|
1289
1299
|
}
|
|
1290
|
-
await storage.writePersona(teamId, agentId, userId, content);
|
|
1300
|
+
await storage.writePersona(teamId, agentId, userId, content, sessionKey);
|
|
1291
1301
|
console.log(`Persona updated: ${entry}`);
|
|
1292
1302
|
return;
|
|
1293
1303
|
}
|
|
1294
1304
|
const db = new Database3(dbPath, { readonly: true });
|
|
1295
1305
|
try {
|
|
1296
|
-
|
|
1297
|
-
"SELECT content FROM persona WHERE team_id = ? AND user_id = ? ORDER BY updated_at DESC LIMIT 1"
|
|
1298
|
-
).get(teamId, userId);
|
|
1306
|
+
let row = db.prepare(
|
|
1307
|
+
"SELECT content FROM persona WHERE team_id = ? AND user_id = ? AND session_key = ? ORDER BY updated_at DESC LIMIT 1"
|
|
1308
|
+
).get(teamId, userId, sessionKey);
|
|
1309
|
+
if (!row) {
|
|
1310
|
+
row = db.prepare(
|
|
1311
|
+
"SELECT content FROM persona WHERE team_id = ? AND user_id = ? AND (session_key IS NULL OR session_key = '') ORDER BY updated_at DESC LIMIT 1"
|
|
1312
|
+
).get(teamId, userId);
|
|
1313
|
+
}
|
|
1299
1314
|
if (!row || !row.content) {
|
|
1300
1315
|
console.log('No persona set. Use --set "trait: value" to add one.');
|
|
1301
1316
|
return;
|
|
@@ -1492,6 +1507,7 @@ Memory: ${total.c} captures across ${sessions.c} project(s)`);
|
|
|
1492
1507
|
// src/cli/worker.ts
|
|
1493
1508
|
init_esm_shims();
|
|
1494
1509
|
init_local();
|
|
1510
|
+
import { createHash as createHash5 } from "crypto";
|
|
1495
1511
|
|
|
1496
1512
|
// src/pipeline/worker.ts
|
|
1497
1513
|
init_esm_shims();
|
|
@@ -1546,7 +1562,7 @@ async function runPipelineWorker(opts, embedder) {
|
|
|
1546
1562
|
result.errors.push(`capture ${cap.id}: ${err}`);
|
|
1547
1563
|
}
|
|
1548
1564
|
}
|
|
1549
|
-
const atoms = await storage.listAtoms({ limit: 200 });
|
|
1565
|
+
const atoms = await storage.listAtoms({ limit: 200, sessionKey: opts.sessionKey });
|
|
1550
1566
|
const groups = /* @__PURE__ */ new Map();
|
|
1551
1567
|
for (const a of atoms) {
|
|
1552
1568
|
const words = a.fact.toLowerCase().split(/[^a-z0-9]+/).filter((w) => w.length > 3);
|
|
@@ -1554,27 +1570,32 @@ async function runPipelineWorker(opts, embedder) {
|
|
|
1554
1570
|
if (!groups.has(key)) groups.set(key, []);
|
|
1555
1571
|
groups.get(key).push(a);
|
|
1556
1572
|
}
|
|
1557
|
-
const existingScenarios = await storage.listScenarios({
|
|
1573
|
+
const existingScenarios = await storage.listScenarios({
|
|
1574
|
+
limit: 100,
|
|
1575
|
+
sessionKey: opts.sessionKey
|
|
1576
|
+
});
|
|
1558
1577
|
const existingTopics = new Set(existingScenarios.flatMap((s) => s.personaTags ?? []));
|
|
1559
1578
|
for (const [topic, groupAtoms] of groups) {
|
|
1560
1579
|
if (groupAtoms.length < consolidateThreshold) continue;
|
|
1561
1580
|
if (existingTopics.has(topic)) continue;
|
|
1562
|
-
const summary =
|
|
1581
|
+
const summary = buildScenarioSummary(topic, groupAtoms);
|
|
1563
1582
|
const id = generateId();
|
|
1564
1583
|
await storage.putScenario({
|
|
1565
1584
|
id,
|
|
1566
1585
|
atomIds: groupAtoms.map((a) => a.id),
|
|
1567
|
-
summary
|
|
1586
|
+
summary,
|
|
1568
1587
|
personaTags: [topic],
|
|
1569
|
-
createdAt: Date.now()
|
|
1588
|
+
createdAt: Date.now(),
|
|
1589
|
+
sessionKey: opts.sessionKey
|
|
1570
1590
|
});
|
|
1571
1591
|
result.scenariosCreated++;
|
|
1572
1592
|
}
|
|
1573
1593
|
const tagCounts = db.prepare(
|
|
1574
1594
|
`SELECT tags FROM captures
|
|
1575
1595
|
WHERE (team_id = ? OR team_id IS NULL OR team_id = 'default')
|
|
1596
|
+
AND session_key = ?
|
|
1576
1597
|
ORDER BY created_at DESC LIMIT 100`
|
|
1577
|
-
).all(teamId);
|
|
1598
|
+
).all(teamId, opts.sessionKey);
|
|
1578
1599
|
const tagFreq = /* @__PURE__ */ new Map();
|
|
1579
1600
|
for (const row of tagCounts) {
|
|
1580
1601
|
if (!row.tags) continue;
|
|
@@ -1586,16 +1607,13 @@ async function runPipelineWorker(opts, embedder) {
|
|
|
1586
1607
|
} catch {
|
|
1587
1608
|
}
|
|
1588
1609
|
}
|
|
1589
|
-
const
|
|
1590
|
-
|
|
1591
|
-
).
|
|
1592
|
-
const
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
const updatedContent = existingContent ? `${existingContent}
|
|
1597
|
-
${newTraits.join("\n")}` : newTraits.join("\n");
|
|
1598
|
-
await storage.writePersona(teamId, "default", userId, updatedContent);
|
|
1610
|
+
const existingPersona = await storage.readPersona(teamId, "default", userId, opts.sessionKey);
|
|
1611
|
+
const existingContent = existingPersona?.content ?? "";
|
|
1612
|
+
const manualTraits = existingContent.split("\n").filter((line2) => line2.trim() && !line2.trim().endsWith("occurrences"));
|
|
1613
|
+
const topTags = Array.from(tagFreq.entries()).filter(([, count]) => count >= personaThreshold).sort((a, b) => b[1] - a[1]).slice(0, 10).map(([tag, count]) => `${tag}: ${count} occurrences`);
|
|
1614
|
+
if (topTags.length > 0) {
|
|
1615
|
+
const updatedContent = [...manualTraits, ...topTags].join("\n");
|
|
1616
|
+
await storage.writePersona(teamId, "default", userId, updatedContent, opts.sessionKey);
|
|
1599
1617
|
result.personaUpdated = true;
|
|
1600
1618
|
}
|
|
1601
1619
|
} finally {
|
|
@@ -1603,16 +1621,31 @@ ${newTraits.join("\n")}` : newTraits.join("\n");
|
|
|
1603
1621
|
}
|
|
1604
1622
|
return result;
|
|
1605
1623
|
}
|
|
1624
|
+
function buildScenarioSummary(topic, atoms) {
|
|
1625
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1626
|
+
const facts = [];
|
|
1627
|
+
for (const a of atoms) {
|
|
1628
|
+
const normalized = a.fact.replace(/^Error:\s*/i, "").toLowerCase().slice(0, 60);
|
|
1629
|
+
if (seen.has(normalized)) continue;
|
|
1630
|
+
seen.add(normalized);
|
|
1631
|
+
facts.push(a.fact.slice(0, 80));
|
|
1632
|
+
if (facts.length >= 5) break;
|
|
1633
|
+
}
|
|
1634
|
+
const body = facts.join("; ");
|
|
1635
|
+
return `${topic}: ${body}`.slice(0, 300);
|
|
1636
|
+
}
|
|
1606
1637
|
|
|
1607
1638
|
// src/cli/worker.ts
|
|
1608
1639
|
async function workerCommand(dbPath, flags) {
|
|
1609
1640
|
const embedder = new LocalEmbedder();
|
|
1641
|
+
const sessionKey = flags["session-key"] ?? (process.env.REMEM_SESSION_KEY ? process.env.REMEM_SESSION_KEY : createHash5("sha256").update(process.cwd()).digest("hex").slice(0, 16));
|
|
1610
1642
|
const result = await runPipelineWorker(
|
|
1611
1643
|
{
|
|
1612
1644
|
dbPath,
|
|
1613
1645
|
batchSize: flags["batch-size"] ? Number(flags["batch-size"]) : 50,
|
|
1614
1646
|
teamId: flags["team-id"],
|
|
1615
|
-
userId: flags["user-id"]
|
|
1647
|
+
userId: flags["user-id"],
|
|
1648
|
+
sessionKey
|
|
1616
1649
|
},
|
|
1617
1650
|
embedder
|
|
1618
1651
|
);
|
|
@@ -4295,7 +4328,7 @@ function exportMarkdown(dbPath, outputPath, filters) {
|
|
|
4295
4328
|
// src/hook-handlers.ts
|
|
4296
4329
|
init_esm_shims();
|
|
4297
4330
|
import { execSync as execSync2, spawn as spawn2 } from "child_process";
|
|
4298
|
-
import { createHash as
|
|
4331
|
+
import { createHash as createHash6 } from "crypto";
|
|
4299
4332
|
import { appendFileSync as appendFileSync2, existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
4300
4333
|
import { homedir as homedir7, tmpdir as tmpdir2 } from "os";
|
|
4301
4334
|
import { dirname as dirname4, join as join9 } from "path";
|
|
@@ -4633,9 +4666,9 @@ function hookRecall(dbPath) {
|
|
|
4633
4666
|
const globalKey = process.env.REMEM_GLOBAL_SESSION_KEY;
|
|
4634
4667
|
if (globalKey) {
|
|
4635
4668
|
const globalErrors = db.prepare(`${errorSql} AND session_key = ? ORDER BY created_at DESC LIMIT 3`).all(globalKey);
|
|
4636
|
-
rows.push(...globalErrors);
|
|
4669
|
+
rows.push(...globalErrors.map((row) => ({ ...row, scope: "global" })));
|
|
4637
4670
|
const globalOthers = db.prepare(`${otherSql} AND session_key = ? ORDER BY created_at DESC LIMIT 3`).all(globalKey);
|
|
4638
|
-
rows.push(...globalOthers);
|
|
4671
|
+
rows.push(...globalOthers.map((row) => ({ ...row, scope: "global" })));
|
|
4639
4672
|
}
|
|
4640
4673
|
if (sessionKey) {
|
|
4641
4674
|
const sessionErrors = db.prepare(`${errorSql} AND session_key = ? ORDER BY created_at DESC LIMIT 5`).all(sessionKey);
|
|
@@ -4656,7 +4689,23 @@ function hookRecall(dbPath) {
|
|
|
4656
4689
|
process.stdout.write(JSON.stringify({}));
|
|
4657
4690
|
return;
|
|
4658
4691
|
}
|
|
4659
|
-
const
|
|
4692
|
+
const globalRows = rows.filter((row) => row.scope === "global");
|
|
4693
|
+
const projectRows = rows.filter((row) => row.scope !== "global");
|
|
4694
|
+
const lines = [];
|
|
4695
|
+
const appendMemoryRows = (header, scopedRows) => {
|
|
4696
|
+
if (scopedRows.length === 0) return;
|
|
4697
|
+
if (lines.length > 0) lines.push("");
|
|
4698
|
+
lines.push(header);
|
|
4699
|
+
for (const row of scopedRows) {
|
|
4700
|
+
const date = new Date(row.created_at).toISOString().split("T")[0];
|
|
4701
|
+
const tags = safeParseTags(row.tags);
|
|
4702
|
+
const tagStr = tags.length > 0 ? ` [${tags.join(", ")}]` : "";
|
|
4703
|
+
const content = row.content.length > 200 ? `${row.content.slice(0, 200)}...` : row.content;
|
|
4704
|
+
lines.push(`- (${row.type}${tagStr}) ${date}: ${content}`);
|
|
4705
|
+
}
|
|
4706
|
+
};
|
|
4707
|
+
appendMemoryRows("[remem-mcp] Global memory (read automatically):", globalRows);
|
|
4708
|
+
appendMemoryRows("[remem-mcp] Project memory:", projectRows);
|
|
4660
4709
|
for (const row of rows) {
|
|
4661
4710
|
const date = new Date(row.created_at).toISOString().split("T")[0];
|
|
4662
4711
|
const tags = safeParseTags(row.tags);
|
|
@@ -4665,9 +4714,14 @@ function hookRecall(dbPath) {
|
|
|
4665
4714
|
lines.push(`- (${row.type}${tagStr}) ${date}: ${content}`);
|
|
4666
4715
|
}
|
|
4667
4716
|
try {
|
|
4668
|
-
|
|
4669
|
-
"SELECT content FROM persona WHERE team_id = ? AND user_id = ? ORDER BY updated_at DESC LIMIT 1"
|
|
4670
|
-
).get("default", "default");
|
|
4717
|
+
let personaRow = db.prepare(
|
|
4718
|
+
"SELECT content FROM persona WHERE team_id = ? AND user_id = ? AND session_key = ? ORDER BY updated_at DESC LIMIT 1"
|
|
4719
|
+
).get("default", "default", sessionKey);
|
|
4720
|
+
if (!personaRow) {
|
|
4721
|
+
personaRow = db.prepare(
|
|
4722
|
+
"SELECT content FROM persona WHERE team_id = ? AND user_id = ? AND (session_key IS NULL OR session_key = '') ORDER BY updated_at DESC LIMIT 1"
|
|
4723
|
+
).get("default", "default");
|
|
4724
|
+
}
|
|
4671
4725
|
if (personaRow && personaRow.content) {
|
|
4672
4726
|
lines.push("");
|
|
4673
4727
|
lines.push(`## Persona (L3)`);
|
|
@@ -4676,7 +4730,9 @@ function hookRecall(dbPath) {
|
|
|
4676
4730
|
} catch {
|
|
4677
4731
|
}
|
|
4678
4732
|
try {
|
|
4679
|
-
const scenarios = db.prepare(
|
|
4733
|
+
const scenarios = db.prepare(
|
|
4734
|
+
"SELECT summary FROM scenarios WHERE session_key = ? ORDER BY created_at DESC LIMIT 3"
|
|
4735
|
+
).all(sessionKey);
|
|
4680
4736
|
if (scenarios.length > 0) {
|
|
4681
4737
|
lines.push("");
|
|
4682
4738
|
lines.push(`## Scenarios (L2)`);
|
|
@@ -4699,6 +4755,9 @@ function hookRecall(dbPath) {
|
|
|
4699
4755
|
lines.push(
|
|
4700
4756
|
"After completing non-trivial work, call capture() to save a 1-3 sentence summary."
|
|
4701
4757
|
);
|
|
4758
|
+
lines.push(
|
|
4759
|
+
'Captures are project-local by default. Save to global only when the user explicitly asks (e.g. "remember this globally").'
|
|
4760
|
+
);
|
|
4702
4761
|
const context = lines.join("\n");
|
|
4703
4762
|
logToFile(`SessionStart: loaded ${rows.length} capture(s)
|
|
4704
4763
|
${context}`);
|
|
@@ -4823,7 +4882,7 @@ function hookUserPromptSubmit(dbPath) {
|
|
|
4823
4882
|
if (updateFact) {
|
|
4824
4883
|
try {
|
|
4825
4884
|
const content = updateFact.type === "decision" ? `Decision: ${updateFact.fact}` : updateFact.fact;
|
|
4826
|
-
const contentHash =
|
|
4885
|
+
const contentHash = createHash6("sha256").update(content).digest("hex").slice(0, 16);
|
|
4827
4886
|
const existing = db.prepare("SELECT id FROM captures WHERE content_hash = ? AND deleted_at IS NULL").get(contentHash);
|
|
4828
4887
|
if (existing) {
|
|
4829
4888
|
logToFile(`UserPromptSubmit: duplicate fact (hash match), skipping capture`);
|
|
@@ -4886,7 +4945,9 @@ function hookUserPromptSubmit(dbPath) {
|
|
|
4886
4945
|
try {
|
|
4887
4946
|
const globalRows = db.prepare(sql).all(ftsQuery, globalKey);
|
|
4888
4947
|
const seen = new Set(rows.map((r) => r.id));
|
|
4889
|
-
rows.push(
|
|
4948
|
+
rows.push(
|
|
4949
|
+
...globalRows.filter((r) => !seen.has(r.id)).slice(0, 2).map((r) => ({ ...r, scope: "global" }))
|
|
4950
|
+
);
|
|
4890
4951
|
} catch {
|
|
4891
4952
|
}
|
|
4892
4953
|
}
|
|
@@ -4895,7 +4956,8 @@ function hookUserPromptSubmit(dbPath) {
|
|
|
4895
4956
|
for (const row of rows.slice(0, 5)) {
|
|
4896
4957
|
const date = new Date(row.created_at).toISOString().split("T")[0];
|
|
4897
4958
|
const tags = safeParseTags(row.tags);
|
|
4898
|
-
const
|
|
4959
|
+
const globalTag = row.scope === "global" ? " [global]" : "";
|
|
4960
|
+
const tagStr = tags.length > 0 ? ` [${tags.join(", ")}]` : globalTag;
|
|
4899
4961
|
const content = row.content.length > 200 ? `${row.content.slice(0, 200)}...` : row.content;
|
|
4900
4962
|
lines.push(`- (${row.type}${tagStr}) ${date}: ${content}`);
|
|
4901
4963
|
}
|
|
@@ -4903,6 +4965,9 @@ function hookUserPromptSubmit(dbPath) {
|
|
|
4903
4965
|
lines.push(
|
|
4904
4966
|
"This is BM25-only (fast, shallow). MUST call recall() for hybrid search (BM25 + vector) with more results and filters."
|
|
4905
4967
|
);
|
|
4968
|
+
lines.push(
|
|
4969
|
+
"Global entries are read-only context. Save to global only when the user explicitly asks."
|
|
4970
|
+
);
|
|
4906
4971
|
recallContext = lines.join("\n");
|
|
4907
4972
|
logToFile(
|
|
4908
4973
|
`UserPromptSubmit: injected ${rows.length} memorie(s) for prompt: ${prompt2.slice(0, 80)}`
|
|
@@ -4912,7 +4977,9 @@ function hookUserPromptSubmit(dbPath) {
|
|
|
4912
4977
|
}
|
|
4913
4978
|
}
|
|
4914
4979
|
try {
|
|
4915
|
-
const scenarios = db.prepare(
|
|
4980
|
+
const scenarios = db.prepare(
|
|
4981
|
+
"SELECT summary FROM scenarios WHERE session_key = ? ORDER BY created_at DESC LIMIT 3"
|
|
4982
|
+
).all(sessionKey);
|
|
4916
4983
|
if (scenarios.length > 0) {
|
|
4917
4984
|
const scenarioLines = scenarios.map((s) => `- ${s.summary}`);
|
|
4918
4985
|
if (recallContext) {
|
|
@@ -4928,9 +4995,14 @@ ${scenarioLines.join("\n")}`;
|
|
|
4928
4995
|
} catch {
|
|
4929
4996
|
}
|
|
4930
4997
|
try {
|
|
4931
|
-
|
|
4932
|
-
"SELECT content FROM persona WHERE team_id = ? AND user_id = ? ORDER BY updated_at DESC LIMIT 1"
|
|
4933
|
-
).get("default", "default");
|
|
4998
|
+
let personaRow = db.prepare(
|
|
4999
|
+
"SELECT content FROM persona WHERE team_id = ? AND user_id = ? AND session_key = ? ORDER BY updated_at DESC LIMIT 1"
|
|
5000
|
+
).get("default", "default", sessionKey);
|
|
5001
|
+
if (!personaRow) {
|
|
5002
|
+
personaRow = db.prepare(
|
|
5003
|
+
"SELECT content FROM persona WHERE team_id = ? AND user_id = ? AND (session_key IS NULL OR session_key = '') ORDER BY updated_at DESC LIMIT 1"
|
|
5004
|
+
).get("default", "default");
|
|
5005
|
+
}
|
|
4934
5006
|
if (personaRow && personaRow.content) {
|
|
4935
5007
|
if (recallContext) {
|
|
4936
5008
|
recallContext += `
|
|
@@ -5154,9 +5226,9 @@ function hookPostToolUse(dbPath) {
|
|
|
5154
5226
|
const sessionKey2 = hashPath(writeEditCwd);
|
|
5155
5227
|
const pattern = detectPattern(toolName, toolInput);
|
|
5156
5228
|
if (pattern) {
|
|
5157
|
-
const id2 = `pat-${
|
|
5229
|
+
const id2 = `pat-${createHash6("sha256").update(pattern.signature + pattern.file_path).digest("hex").slice(0, 12)}`;
|
|
5158
5230
|
const content2 = `Pattern: ${pattern.title} in ${pattern.file_path}`;
|
|
5159
|
-
const hash =
|
|
5231
|
+
const hash = createHash6("sha256").update(content2).digest("hex").slice(0, 16);
|
|
5160
5232
|
const patternConflict = detectPatternConflict(db2, sessionKey2, {
|
|
5161
5233
|
pattern_type: pattern.pattern_type,
|
|
5162
5234
|
language: pattern.language,
|
|
@@ -5428,7 +5500,7 @@ function hookPostToolUse(dbPath) {
|
|
|
5428
5500
|
const decision = detectDecision(command, stdout);
|
|
5429
5501
|
if (decision) {
|
|
5430
5502
|
try {
|
|
5431
|
-
const decId = `dec-${
|
|
5503
|
+
const decId = `dec-${createHash6("sha256").update(decision.choice + sessionKey).digest("hex").slice(0, 12)}`;
|
|
5432
5504
|
const existingDec = db.prepare("SELECT id, metadata FROM captures WHERE id = ?").get(decId);
|
|
5433
5505
|
const conflict = detectDecisionConflict(
|
|
5434
5506
|
db,
|
|
@@ -5476,7 +5548,7 @@ function hookPostToolUse(dbPath) {
|
|
|
5476
5548
|
);
|
|
5477
5549
|
} else {
|
|
5478
5550
|
const decContent = `Decision: ${decision.title}`;
|
|
5479
|
-
const decHash =
|
|
5551
|
+
const decHash = createHash6("sha256").update(decContent).digest("hex").slice(0, 16);
|
|
5480
5552
|
db.prepare(
|
|
5481
5553
|
"INSERT INTO captures (id, session_key, agent_id, type, content, content_hash, tags, created_at, metadata) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
|
5482
5554
|
).run(
|
|
@@ -5527,7 +5599,7 @@ Error (${errorType}): ${truncatedError}`;
|
|
|
5527
5599
|
const normalizedError = truncatedError.replace(/line \d+/g, "line N").replace(/col \d+/g, "col N").replace(/\b\d+\b/g, "N").replace(/'[^']+'/g, "'X'").replace(/"[^"]+"/g, '"X"').replace(/src\/[^\s:]+/g, "src/PATH").replace(/\.\/[^\s:]+/g, "./PATH").replace(/\/[^\s:]+\/[^\s:]+/g, "/PATH");
|
|
5528
5600
|
const semanticContent = `Command failed: ${command}
|
|
5529
5601
|
Error (${errorType}): ${normalizedError}`;
|
|
5530
|
-
const contentHash =
|
|
5602
|
+
const contentHash = createHash6("sha256").update(semanticContent).digest("hex").slice(0, 16);
|
|
5531
5603
|
const id = generateId2();
|
|
5532
5604
|
const now = Date.now();
|
|
5533
5605
|
const recent = db.prepare("SELECT id FROM captures WHERE content_hash = ? AND created_at > ? LIMIT 1").get(contentHash, now - 36e5);
|
|
@@ -5923,7 +5995,7 @@ function hookPreToolUse(dbPath) {
|
|
|
5923
5995
|
lines2.push("");
|
|
5924
5996
|
lines2.push("Follow these patterns for consistency.");
|
|
5925
5997
|
for (const p of patterns) {
|
|
5926
|
-
const patId = `pat-${
|
|
5998
|
+
const patId = `pat-${createHash6("sha256").update((p.sig ?? p.title) + (p.fpath ?? "")).digest("hex").slice(0, 12)}`;
|
|
5927
5999
|
logInjectionDrift(sessionKey2, "pattern", patId, `edit ${filePath}`);
|
|
5928
6000
|
}
|
|
5929
6001
|
const output3 = {
|
|
@@ -6136,7 +6208,7 @@ function hookPreToolUse(dbPath) {
|
|
|
6136
6208
|
logInjectionDrift(
|
|
6137
6209
|
sessionKey,
|
|
6138
6210
|
"decision",
|
|
6139
|
-
`dec-${
|
|
6211
|
+
`dec-${createHash6("sha256").update(d.choice + sessionKey).digest("hex").slice(0, 12)}`,
|
|
6140
6212
|
command
|
|
6141
6213
|
);
|
|
6142
6214
|
}
|
|
@@ -6298,7 +6370,7 @@ function hookPreToolUse(dbPath) {
|
|
|
6298
6370
|
logInjectionDrift(
|
|
6299
6371
|
sessionKey,
|
|
6300
6372
|
"decision",
|
|
6301
|
-
`dec-${
|
|
6373
|
+
`dec-${createHash6("sha256").update(d.choice + sessionKey).digest("hex").slice(0, 12)}`,
|
|
6302
6374
|
command
|
|
6303
6375
|
);
|
|
6304
6376
|
}
|
|
@@ -6884,7 +6956,7 @@ function extractFilePaths(content) {
|
|
|
6884
6956
|
return paths;
|
|
6885
6957
|
}
|
|
6886
6958
|
function hashPath(path) {
|
|
6887
|
-
return
|
|
6959
|
+
return createHash6("sha256").update(path).digest("hex").slice(0, 16);
|
|
6888
6960
|
}
|
|
6889
6961
|
function ensureSchema2(db) {
|
|
6890
6962
|
db.exec(`CREATE TABLE IF NOT EXISTS captures (
|
|
@@ -6943,10 +7015,12 @@ function ensureSchema2(db) {
|
|
|
6943
7015
|
)`);
|
|
6944
7016
|
db.exec(`CREATE TABLE IF NOT EXISTS persona (
|
|
6945
7017
|
team_id TEXT NOT NULL,
|
|
7018
|
+
agent_id TEXT NOT NULL,
|
|
6946
7019
|
user_id TEXT NOT NULL,
|
|
6947
7020
|
content TEXT NOT NULL,
|
|
6948
7021
|
updated_at INTEGER NOT NULL,
|
|
6949
|
-
|
|
7022
|
+
session_key TEXT,
|
|
7023
|
+
PRIMARY KEY (team_id, agent_id, user_id, session_key)
|
|
6950
7024
|
)`);
|
|
6951
7025
|
db.exec(`CREATE TABLE IF NOT EXISTS skills (
|
|
6952
7026
|
id TEXT PRIMARY KEY,
|
|
@@ -6978,14 +7052,21 @@ function ensureSchema2(db) {
|
|
|
6978
7052
|
id TEXT PRIMARY KEY,
|
|
6979
7053
|
atom_ids TEXT NOT NULL,
|
|
6980
7054
|
summary TEXT,
|
|
7055
|
+
persona_tags TEXT,
|
|
6981
7056
|
created_at INTEGER NOT NULL,
|
|
6982
|
-
team_id TEXT
|
|
7057
|
+
team_id TEXT,
|
|
7058
|
+
agent_id TEXT,
|
|
7059
|
+
user_id TEXT,
|
|
7060
|
+
session_key TEXT
|
|
6983
7061
|
)`);
|
|
6984
7062
|
db.exec(`CREATE TABLE IF NOT EXISTS schema_version (version INTEGER NOT NULL)`);
|
|
6985
7063
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_captures_session ON captures(session_key)`);
|
|
6986
7064
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_captures_type ON captures(type)`);
|
|
6987
7065
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_errors_session ON error_patterns(session_key)`);
|
|
6988
7066
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_atoms_capture ON atoms(capture_id)`);
|
|
7067
|
+
db.exec(
|
|
7068
|
+
`CREATE INDEX IF NOT EXISTS idx_scenarios_session ON scenarios(session_key, created_at DESC)`
|
|
7069
|
+
);
|
|
6989
7070
|
}
|
|
6990
7071
|
function defaultTranscriptDir() {
|
|
6991
7072
|
return process.env.DEVIN_TRANSCRIPTS_DIR ?? join9(homedir7(), ".local", "share", "devin", "cli", "transcripts");
|
|
@@ -7080,7 +7161,7 @@ async function captureSessionTranscript(dbPath, sessionId, transcriptPath, cwd)
|
|
|
7080
7161
|
const content = `Session: ${sid}
|
|
7081
7162
|
Task: ${taskText}
|
|
7082
7163
|
Outcome: ${outcomeText}`;
|
|
7083
|
-
const contentHash =
|
|
7164
|
+
const contentHash = createHash6("sha256").update(content).digest("hex");
|
|
7084
7165
|
const db = new Database8(dbPath);
|
|
7085
7166
|
ensureSchema2(db);
|
|
7086
7167
|
try {
|
|
@@ -7233,15 +7314,15 @@ async function hookPostCommit(dbPath) {
|
|
|
7233
7314
|
}
|
|
7234
7315
|
const { readFileSync: readFileSync14 } = await import("fs");
|
|
7235
7316
|
const { join: join17, dirname: dirname11 } = await import("path");
|
|
7236
|
-
const { fileURLToPath:
|
|
7317
|
+
const { fileURLToPath: fileURLToPath6 } = await import("url");
|
|
7237
7318
|
const sqliteVec5 = await import("sqlite-vec");
|
|
7238
|
-
const { indexFile: indexFile2 } = await import("./engine-
|
|
7319
|
+
const { indexFile: indexFile2 } = await import("./engine-LAY25SCA.js");
|
|
7239
7320
|
const db = new Database8(dbPath);
|
|
7240
7321
|
ensureSchema2(db);
|
|
7241
7322
|
try {
|
|
7242
7323
|
db.pragma("journal_mode = WAL");
|
|
7243
7324
|
sqliteVec5.load(db);
|
|
7244
|
-
const schemaPath = join17(dirname11(
|
|
7325
|
+
const schemaPath = join17(dirname11(fileURLToPath6(import.meta.url)), "storage", "schema.sql");
|
|
7245
7326
|
try {
|
|
7246
7327
|
const schema = readFileSync14(schemaPath, "utf-8");
|
|
7247
7328
|
db.exec(schema);
|
|
@@ -7321,14 +7402,14 @@ function hookPreCompact(dbPath) {
|
|
|
7321
7402
|
} catch {
|
|
7322
7403
|
}
|
|
7323
7404
|
try {
|
|
7324
|
-
const { fileURLToPath:
|
|
7325
|
-
const schemaPath = join9(dirname4(
|
|
7405
|
+
const { fileURLToPath: fileURLToPath6 } = await import("url");
|
|
7406
|
+
const schemaPath = join9(dirname4(fileURLToPath6(import.meta.url)), "storage", "schema.sql");
|
|
7326
7407
|
if (existsSync8(schemaPath)) {
|
|
7327
7408
|
db.exec(readFileSync6(schemaPath, "utf-8"));
|
|
7328
7409
|
}
|
|
7329
7410
|
} catch {
|
|
7330
7411
|
}
|
|
7331
|
-
const checkpointId = `ckpt-${
|
|
7412
|
+
const checkpointId = `ckpt-${createHash6("sha256").update(sessionKey + Date.now()).digest("hex").slice(0, 12)}`;
|
|
7332
7413
|
const summary = `Context compaction triggered (${trigger}). Session checkpoint saved. After compaction, recall recent memory to recover decisions made, approaches tried, and what was verified working.`;
|
|
7333
7414
|
db.prepare(
|
|
7334
7415
|
`INSERT INTO captures (id, type, content, tags, metadata, session_key, agent_id, created_at)
|
|
@@ -7351,7 +7432,7 @@ function hookPreCompact(dbPath) {
|
|
|
7351
7432
|
try {
|
|
7352
7433
|
const transcript = readFileSync6(input.transcript_path, "utf-8");
|
|
7353
7434
|
const recent = transcript.slice(-2e3);
|
|
7354
|
-
const transcriptId = `txcpt-${
|
|
7435
|
+
const transcriptId = `txcpt-${createHash6("sha256").update(sessionKey + "transcript" + Date.now()).digest("hex").slice(0, 12)}`;
|
|
7355
7436
|
db.prepare(
|
|
7356
7437
|
`INSERT INTO captures (id, type, content, tags, metadata, session_key, agent_id, created_at)
|
|
7357
7438
|
VALUES (?, 'task', ?, ?, ?, ?, ?, ?)`
|
|
@@ -7419,8 +7500,8 @@ function hookPostCompaction(dbPath) {
|
|
|
7419
7500
|
const input = raw.trim() ? JSON.parse(raw) : {};
|
|
7420
7501
|
const sessionKey = hashPath(input.cwd ?? process.cwd());
|
|
7421
7502
|
const globalKey = process.env.REMEM_GLOBAL_SESSION_KEY ?? null;
|
|
7422
|
-
const { fileURLToPath:
|
|
7423
|
-
const schemaPath = join9(dirname4(
|
|
7503
|
+
const { fileURLToPath: fileURLToPath6 } = await import("url");
|
|
7504
|
+
const schemaPath = join9(dirname4(fileURLToPath6(import.meta.url)), "storage", "schema.sql");
|
|
7424
7505
|
const db = new Database8(dbPath, { readonly: true });
|
|
7425
7506
|
const cutoff = Date.now() - 24 * 60 * 60 * 1e3;
|
|
7426
7507
|
const sessionKeys = globalKey ? [sessionKey, globalKey] : [sessionKey];
|
|
@@ -7774,14 +7855,16 @@ import { existsSync as existsSync9, mkdirSync as mkdirSync6, readFileSync as rea
|
|
|
7774
7855
|
import { homedir as homedir8 } from "os";
|
|
7775
7856
|
import { dirname as dirname5, join as join10 } from "path";
|
|
7776
7857
|
function hookCommand(subcommand) {
|
|
7858
|
+
const globalKey = process.env.REMEM_GLOBAL_SESSION_KEY;
|
|
7859
|
+
const globalEnvPrefix = globalKey ? `env REMEM_GLOBAL_SESSION_KEY='${globalKey.replaceAll("'", `'\\''`)}' ` : "";
|
|
7777
7860
|
try {
|
|
7778
7861
|
const binPath = execFileSync2("which", ["remem-mcp"], { encoding: "utf-8" }).trim();
|
|
7779
7862
|
if (binPath && existsSync9(binPath)) {
|
|
7780
|
-
return `${binPath} ${subcommand}`;
|
|
7863
|
+
return `${globalEnvPrefix}${binPath} ${subcommand}`;
|
|
7781
7864
|
}
|
|
7782
7865
|
} catch {
|
|
7783
7866
|
}
|
|
7784
|
-
return
|
|
7867
|
+
return `${globalEnvPrefix}npx --prefer-offline -y remem-mcp ${subcommand}`;
|
|
7785
7868
|
}
|
|
7786
7869
|
var CODEX_HOOK_SUBCOMMANDS = [
|
|
7787
7870
|
"hook-recall",
|
|
@@ -7842,18 +7925,6 @@ var HOOKS_CONFIG = {
|
|
|
7842
7925
|
]
|
|
7843
7926
|
}
|
|
7844
7927
|
],
|
|
7845
|
-
PreToolUse: [
|
|
7846
|
-
{
|
|
7847
|
-
matcher: "Bash|exec",
|
|
7848
|
-
hooks: [
|
|
7849
|
-
{
|
|
7850
|
-
type: "command",
|
|
7851
|
-
command: hookCommand("hook-pre-tool-use"),
|
|
7852
|
-
timeout: 5
|
|
7853
|
-
}
|
|
7854
|
-
]
|
|
7855
|
-
}
|
|
7856
|
-
],
|
|
7857
7928
|
PostToolUse: [
|
|
7858
7929
|
{
|
|
7859
7930
|
matcher: "Bash|exec",
|
|
@@ -7881,10 +7952,6 @@ var HOOKS_CONFIG = {
|
|
|
7881
7952
|
]
|
|
7882
7953
|
}
|
|
7883
7954
|
],
|
|
7884
|
-
// Note: PostToolUseFailure is not supported by Devin CLI.
|
|
7885
|
-
// Devin supports: PreToolUse, PostToolUse, UserPromptSubmit, Stop,
|
|
7886
|
-
// PostCompaction, SessionStart, SessionEnd, PermissionRequest.
|
|
7887
|
-
// Error capture is handled via PostToolUse with exit code 2.
|
|
7888
7955
|
UserPromptSubmit: [
|
|
7889
7956
|
{
|
|
7890
7957
|
hooks: [
|
|
@@ -7896,17 +7963,6 @@ var HOOKS_CONFIG = {
|
|
|
7896
7963
|
]
|
|
7897
7964
|
}
|
|
7898
7965
|
],
|
|
7899
|
-
Stop: [
|
|
7900
|
-
{
|
|
7901
|
-
hooks: [
|
|
7902
|
-
{
|
|
7903
|
-
type: "command",
|
|
7904
|
-
command: hookCommand("hook-stop"),
|
|
7905
|
-
timeout: 10
|
|
7906
|
-
}
|
|
7907
|
-
]
|
|
7908
|
-
}
|
|
7909
|
-
],
|
|
7910
7966
|
SessionEnd: [
|
|
7911
7967
|
{
|
|
7912
7968
|
hooks: [
|
|
@@ -7958,7 +8014,6 @@ function installDevinHooks() {
|
|
|
7958
8014
|
const config = readJsonConfig(configPath);
|
|
7959
8015
|
const updated = mergeHooks(config, HOOKS_CONFIG);
|
|
7960
8016
|
writeJsonConfig(configPath, updated);
|
|
7961
|
-
console.log(` Devin CLI: Hooks wired into ${configPath}`);
|
|
7962
8017
|
return true;
|
|
7963
8018
|
}
|
|
7964
8019
|
function installClaudeCodeHooks() {
|
|
@@ -7970,17 +8025,6 @@ function installClaudeCodeHooks() {
|
|
|
7970
8025
|
const { PostCompaction: _omit, ...baseWithoutPostCompaction } = HOOKS_CONFIG;
|
|
7971
8026
|
const claudeHooksConfig = {
|
|
7972
8027
|
...baseWithoutPostCompaction,
|
|
7973
|
-
PreCompact: [
|
|
7974
|
-
{
|
|
7975
|
-
hooks: [
|
|
7976
|
-
{
|
|
7977
|
-
type: "command",
|
|
7978
|
-
command: hookCommand("hook-pre-compact"),
|
|
7979
|
-
timeout: 10
|
|
7980
|
-
}
|
|
7981
|
-
]
|
|
7982
|
-
}
|
|
7983
|
-
],
|
|
7984
8028
|
PostCompact: [
|
|
7985
8029
|
{
|
|
7986
8030
|
hooks: [
|
|
@@ -7996,7 +8040,6 @@ function installClaudeCodeHooks() {
|
|
|
7996
8040
|
const config = readJsonConfig(settingsPath);
|
|
7997
8041
|
const updated = mergeHooks(config, claudeHooksConfig);
|
|
7998
8042
|
writeJsonConfig(settingsPath, updated);
|
|
7999
|
-
console.log(` Claude Code: Hooks wired into ${settingsPath}`);
|
|
8000
8043
|
return true;
|
|
8001
8044
|
}
|
|
8002
8045
|
function installCodexHooks() {
|
|
@@ -8006,7 +8049,6 @@ function installCodexHooks() {
|
|
|
8006
8049
|
}
|
|
8007
8050
|
let content = readFileSync7(configPath, "utf-8");
|
|
8008
8051
|
if (hasCodexRememHooks(content)) {
|
|
8009
|
-
console.log(` Codex CLI: Hooks already installed in ${configPath}`);
|
|
8010
8052
|
return true;
|
|
8011
8053
|
}
|
|
8012
8054
|
const hooksToml = `
|
|
@@ -8020,16 +8062,6 @@ command = "${hookCommand("hook-recall")}"
|
|
|
8020
8062
|
timeout = 10
|
|
8021
8063
|
# <<< remem-mcp SessionStart <<<
|
|
8022
8064
|
|
|
8023
|
-
# >>> remem-mcp PreToolUse >>>
|
|
8024
|
-
[[hooks.PreToolUse]]
|
|
8025
|
-
matcher = "Bash|exec"
|
|
8026
|
-
|
|
8027
|
-
[[hooks.PreToolUse.hooks]]
|
|
8028
|
-
type = "command"
|
|
8029
|
-
command = "${hookCommand("hook-pre-tool-use")}"
|
|
8030
|
-
timeout = 5
|
|
8031
|
-
# <<< remem-mcp PreToolUse <<<
|
|
8032
|
-
|
|
8033
8065
|
# >>> remem-mcp PostToolUse >>>
|
|
8034
8066
|
[[hooks.PostToolUse]]
|
|
8035
8067
|
matcher = "Bash|exec"
|
|
@@ -8058,15 +8090,6 @@ command = "${hookCommand("hook-user-prompt")}"
|
|
|
8058
8090
|
timeout = 5
|
|
8059
8091
|
# <<< remem-mcp UserPromptSubmit <<<
|
|
8060
8092
|
|
|
8061
|
-
# >>> remem-mcp Stop >>>
|
|
8062
|
-
[[hooks.Stop]]
|
|
8063
|
-
|
|
8064
|
-
[[hooks.Stop.hooks]]
|
|
8065
|
-
type = "command"
|
|
8066
|
-
command = "${hookCommand("hook-stop")}"
|
|
8067
|
-
timeout = 5
|
|
8068
|
-
# <<< remem-mcp Stop <<<
|
|
8069
|
-
|
|
8070
8093
|
# >>> remem-mcp SessionEnd >>>
|
|
8071
8094
|
[[hooks.SessionEnd]]
|
|
8072
8095
|
|
|
@@ -8078,38 +8101,73 @@ timeout = 10
|
|
|
8078
8101
|
`;
|
|
8079
8102
|
content = content.trimEnd() + "\n" + hooksToml;
|
|
8080
8103
|
writeFileSync6(configPath, content, "utf-8");
|
|
8081
|
-
console.log(` Codex CLI: Hooks wired into ${configPath}`);
|
|
8082
|
-
console.log(` Note: Set sandbox_mode = "danger-full-access" for MCP tools to work.`);
|
|
8083
8104
|
return true;
|
|
8084
8105
|
}
|
|
8106
|
+
function installZcodeHooks() {
|
|
8107
|
+
const configPath = join10(homedir8(), ".zcode", "cli", "config.json");
|
|
8108
|
+
if (!existsSync9(join10(homedir8(), ".zcode"))) {
|
|
8109
|
+
return false;
|
|
8110
|
+
}
|
|
8111
|
+
const config = readJsonConfig(configPath);
|
|
8112
|
+
const { PostCompaction: _omit, SessionEnd: _omit2, ...baseConfig } = HOOKS_CONFIG;
|
|
8113
|
+
const zcodeHooksConfig = {
|
|
8114
|
+
...baseConfig,
|
|
8115
|
+
Stop: [
|
|
8116
|
+
{
|
|
8117
|
+
hooks: [
|
|
8118
|
+
{
|
|
8119
|
+
type: "command",
|
|
8120
|
+
command: hookCommand("hook-stop"),
|
|
8121
|
+
timeout: 10
|
|
8122
|
+
}
|
|
8123
|
+
]
|
|
8124
|
+
}
|
|
8125
|
+
]
|
|
8126
|
+
};
|
|
8127
|
+
const updated = mergeZcodeHooks(config, zcodeHooksConfig);
|
|
8128
|
+
writeJsonConfig(configPath, updated);
|
|
8129
|
+
return true;
|
|
8130
|
+
}
|
|
8131
|
+
function mergeZcodeHooks(config, hooks) {
|
|
8132
|
+
const existing = config.hooks ?? {};
|
|
8133
|
+
const existingEvents = existing.events ?? {};
|
|
8134
|
+
const merged = { ...existingEvents };
|
|
8135
|
+
for (const [event, newEntries] of Object.entries(hooks)) {
|
|
8136
|
+
const existingEntries = existingEvents[event] ?? [];
|
|
8137
|
+
const filtered = existingEntries.filter((entry) => {
|
|
8138
|
+
const hooks2 = entry?.hooks;
|
|
8139
|
+
if (!hooks2) return true;
|
|
8140
|
+
return !hooks2.some((h) => h?.command?.includes("remem-mcp"));
|
|
8141
|
+
});
|
|
8142
|
+
merged[event] = [...filtered, ...newEntries];
|
|
8143
|
+
}
|
|
8144
|
+
return {
|
|
8145
|
+
...config,
|
|
8146
|
+
hooks: {
|
|
8147
|
+
...existing,
|
|
8148
|
+
enabled: true,
|
|
8149
|
+
// configuration-file hooks are disabled by default in ZCode
|
|
8150
|
+
events: merged
|
|
8151
|
+
}
|
|
8152
|
+
};
|
|
8153
|
+
}
|
|
8085
8154
|
async function installHooks() {
|
|
8086
|
-
|
|
8087
|
-
|
|
8088
|
-
if (installDevinHooks())
|
|
8089
|
-
if (installClaudeCodeHooks())
|
|
8090
|
-
if (
|
|
8091
|
-
if (
|
|
8092
|
-
|
|
8093
|
-
console.log("
|
|
8155
|
+
await uninstallHooks(true);
|
|
8156
|
+
const names = [];
|
|
8157
|
+
if (installDevinHooks()) names.push("Devin CLI");
|
|
8158
|
+
if (installClaudeCodeHooks()) names.push("Claude Code");
|
|
8159
|
+
if (installZcodeHooks()) names.push("ZCode");
|
|
8160
|
+
if (installCodexHooks()) names.push("Codex CLI");
|
|
8161
|
+
if (names.length === 0) {
|
|
8162
|
+
console.log("No supported agents found.");
|
|
8163
|
+
console.log(
|
|
8164
|
+
"Install ZCode, Devin CLI, Claude Code, or Codex CLI first, then run this command again."
|
|
8165
|
+
);
|
|
8094
8166
|
return;
|
|
8095
8167
|
}
|
|
8096
|
-
console.log(`
|
|
8097
|
-
|
|
8098
|
-
|
|
8099
|
-
console.log(" SessionStart \u2192 auto-recall recent memory into agent context");
|
|
8100
|
-
console.log(" UserPromptSubmit \u2192 heuristic recall: inject memory matching user prompt");
|
|
8101
|
-
console.log(" PreToolUse \u2192 inject past errors before lint/build/test commands");
|
|
8102
|
-
console.log(" PostToolUse \u2192 auto-capture failed commands as error memories");
|
|
8103
|
-
console.log(" PreCompact \u2192 save checkpoint before compaction (Claude Code only)");
|
|
8104
|
-
console.log(" PostCompact \u2192 re-inject memory after compaction (Claude Code)");
|
|
8105
|
-
console.log(" PostCompaction \u2192 re-inject memory after compaction (Devin CLI, Codex CLI)");
|
|
8106
|
-
console.log(" Stop \u2192 auto-capture session transcript + remind to save");
|
|
8107
|
-
console.log(" SessionEnd \u2192 silently capture session summary to memory DB");
|
|
8108
|
-
console.log("\nRestart your agent for hooks to take effect.");
|
|
8109
|
-
console.log("\nTo verify: run /hooks in your agent.");
|
|
8110
|
-
}
|
|
8111
|
-
async function uninstallHooks() {
|
|
8112
|
-
console.log("Removing lifecycle hooks...\n");
|
|
8168
|
+
console.log(`Hooks: ${names.join(", ")}`);
|
|
8169
|
+
}
|
|
8170
|
+
async function uninstallHooks(calledFromInstall = false) {
|
|
8113
8171
|
let removed = 0;
|
|
8114
8172
|
const rememEvents = [
|
|
8115
8173
|
"SessionStart",
|
|
@@ -8143,7 +8201,6 @@ async function uninstallHooks() {
|
|
|
8143
8201
|
delete config.hooks;
|
|
8144
8202
|
}
|
|
8145
8203
|
writeJsonConfig(devinPath, config);
|
|
8146
|
-
console.log(` Devin CLI: Hooks removed from ${devinPath}`);
|
|
8147
8204
|
removed++;
|
|
8148
8205
|
}
|
|
8149
8206
|
}
|
|
@@ -8168,7 +8225,41 @@ async function uninstallHooks() {
|
|
|
8168
8225
|
delete config.hooks;
|
|
8169
8226
|
}
|
|
8170
8227
|
writeJsonConfig(claudePath, config);
|
|
8171
|
-
|
|
8228
|
+
removed++;
|
|
8229
|
+
}
|
|
8230
|
+
}
|
|
8231
|
+
const zcodePath = join10(homedir8(), ".zcode", "cli", "config.json");
|
|
8232
|
+
if (existsSync9(zcodePath)) {
|
|
8233
|
+
const config = readJsonConfig(zcodePath);
|
|
8234
|
+
const hooks = config.hooks ?? {};
|
|
8235
|
+
const events = hooks.events ?? {};
|
|
8236
|
+
let changed = false;
|
|
8237
|
+
for (const ev of rememEvents) {
|
|
8238
|
+
if (events[ev]) {
|
|
8239
|
+
events[ev] = events[ev].filter((entry) => {
|
|
8240
|
+
const entryHooks = entry?.hooks;
|
|
8241
|
+
if (!entryHooks) return true;
|
|
8242
|
+
return !entryHooks.some((h) => h?.command?.includes("remem-mcp"));
|
|
8243
|
+
});
|
|
8244
|
+
if (Array.isArray(events[ev]) && events[ev].length === 0) {
|
|
8245
|
+
delete events[ev];
|
|
8246
|
+
}
|
|
8247
|
+
changed = true;
|
|
8248
|
+
}
|
|
8249
|
+
}
|
|
8250
|
+
if (changed) {
|
|
8251
|
+
if (Object.keys(events).length === 0) {
|
|
8252
|
+
delete hooks.events;
|
|
8253
|
+
delete hooks.enabled;
|
|
8254
|
+
} else {
|
|
8255
|
+
hooks.events = events;
|
|
8256
|
+
}
|
|
8257
|
+
if (Object.keys(hooks).length === 0) {
|
|
8258
|
+
delete config.hooks;
|
|
8259
|
+
} else {
|
|
8260
|
+
config.hooks = hooks;
|
|
8261
|
+
}
|
|
8262
|
+
writeJsonConfig(zcodePath, config);
|
|
8172
8263
|
removed++;
|
|
8173
8264
|
}
|
|
8174
8265
|
}
|
|
@@ -8179,15 +8270,11 @@ async function uninstallHooks() {
|
|
|
8179
8270
|
if (cleaned !== content) {
|
|
8180
8271
|
content = cleaned;
|
|
8181
8272
|
writeFileSync6(codexPath, content.trimEnd() + "\n", "utf-8");
|
|
8182
|
-
console.log(` Codex CLI: Hooks removed from ${codexPath}`);
|
|
8183
8273
|
removed++;
|
|
8184
8274
|
}
|
|
8185
8275
|
}
|
|
8186
|
-
if (removed
|
|
8187
|
-
console.log(
|
|
8188
|
-
} else {
|
|
8189
|
-
console.log(`
|
|
8190
|
-
Hooks removed from ${removed} agent(s).`);
|
|
8276
|
+
if (removed > 0 && !calledFromInstall) {
|
|
8277
|
+
console.log(`Hooks removed from ${removed} agent(s).`);
|
|
8191
8278
|
}
|
|
8192
8279
|
}
|
|
8193
8280
|
|
|
@@ -8330,20 +8417,46 @@ var JSON_TARGETS = [
|
|
|
8330
8417
|
key: "mcpServers"
|
|
8331
8418
|
}
|
|
8332
8419
|
];
|
|
8420
|
+
function registerZcodeServer() {
|
|
8421
|
+
const configPath = join12(homedir9(), ".zcode", "cli", "config.json");
|
|
8422
|
+
const config = readJsonConfig2(configPath);
|
|
8423
|
+
const mcp = config.mcp ?? {};
|
|
8424
|
+
const servers = mcp.servers ?? {};
|
|
8425
|
+
if (servers["remem-mcp"]) {
|
|
8426
|
+
return true;
|
|
8427
|
+
}
|
|
8428
|
+
servers["remem-mcp"] = MCP_SERVER_ENTRY;
|
|
8429
|
+
mcp.servers = servers;
|
|
8430
|
+
config.mcp = mcp;
|
|
8431
|
+
writeJsonConfig2(configPath, config);
|
|
8432
|
+
return true;
|
|
8433
|
+
}
|
|
8434
|
+
function readJsonConfig2(path) {
|
|
8435
|
+
if (!existsSync11(path)) return {};
|
|
8436
|
+
try {
|
|
8437
|
+
return JSON.parse(readFileSync9(path, "utf-8"));
|
|
8438
|
+
} catch {
|
|
8439
|
+
return {};
|
|
8440
|
+
}
|
|
8441
|
+
}
|
|
8442
|
+
function writeJsonConfig2(path, data) {
|
|
8443
|
+
const dir = dirname7(path);
|
|
8444
|
+
if (!existsSync11(dir)) mkdirSync7(dir, { recursive: true });
|
|
8445
|
+
writeFileSync7(path, `${JSON.stringify(data, null, 2)}
|
|
8446
|
+
`, "utf-8");
|
|
8447
|
+
}
|
|
8333
8448
|
function registerJsonServer(target) {
|
|
8334
8449
|
let config = {};
|
|
8335
8450
|
if (existsSync11(target.path)) {
|
|
8336
8451
|
try {
|
|
8337
8452
|
config = JSON.parse(readFileSync9(target.path, "utf-8"));
|
|
8338
8453
|
} catch {
|
|
8339
|
-
console.log(` ${target.name}: Config file unreadable, skipping.`);
|
|
8340
8454
|
return false;
|
|
8341
8455
|
}
|
|
8342
8456
|
}
|
|
8343
8457
|
const servers = config[target.key] || {};
|
|
8344
8458
|
const entry = target.useGlobal ? MCP_SERVER_ENTRY_WITH_GLOBAL : MCP_SERVER_ENTRY;
|
|
8345
|
-
if (
|
|
8346
|
-
console.log(` ${target.name}: Already registered.`);
|
|
8459
|
+
if (servers["remem-mcp"]) {
|
|
8347
8460
|
return true;
|
|
8348
8461
|
}
|
|
8349
8462
|
servers["remem-mcp"] = entry;
|
|
@@ -8353,12 +8466,10 @@ function registerJsonServer(target) {
|
|
|
8353
8466
|
mkdirSync7(dir, { recursive: true });
|
|
8354
8467
|
}
|
|
8355
8468
|
writeFileSync7(target.path, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
8356
|
-
console.log(` ${target.name}: MCP server registered.`);
|
|
8357
8469
|
return true;
|
|
8358
8470
|
}
|
|
8359
8471
|
async function installMcpServer() {
|
|
8360
|
-
|
|
8361
|
-
let count = 0;
|
|
8472
|
+
const names = [];
|
|
8362
8473
|
for (const target of JSON_TARGETS) {
|
|
8363
8474
|
const agentDir = dirname7(target.path);
|
|
8364
8475
|
if (!existsSync11(agentDir)) {
|
|
@@ -8375,15 +8486,19 @@ async function installMcpServer() {
|
|
|
8375
8486
|
}
|
|
8376
8487
|
}
|
|
8377
8488
|
if (registerJsonServer(target)) {
|
|
8378
|
-
|
|
8489
|
+
names.push(target.name);
|
|
8490
|
+
}
|
|
8491
|
+
}
|
|
8492
|
+
if (existsSync11(join12(homedir9(), ".zcode"))) {
|
|
8493
|
+
if (registerZcodeServer()) {
|
|
8494
|
+
names.push("ZCode");
|
|
8379
8495
|
}
|
|
8380
8496
|
}
|
|
8381
8497
|
const codexConfig = join12(homedir9(), ".codex", "config.toml");
|
|
8382
8498
|
if (existsSync11(codexConfig)) {
|
|
8383
8499
|
const content = readFileSync9(codexConfig, "utf-8");
|
|
8384
8500
|
if (content.includes("[mcp_servers.remem-mcp]")) {
|
|
8385
|
-
|
|
8386
|
-
count++;
|
|
8501
|
+
names.push("Codex CLI");
|
|
8387
8502
|
} else {
|
|
8388
8503
|
const tomlEntry = `
|
|
8389
8504
|
[mcp_servers.remem-mcp]
|
|
@@ -8394,17 +8509,10 @@ args = ["-y", "remem-mcp"]
|
|
|
8394
8509
|
REMEM_GLOBAL_SESSION_KEY = "global"
|
|
8395
8510
|
`;
|
|
8396
8511
|
writeFileSync7(codexConfig, content + tomlEntry, "utf-8");
|
|
8397
|
-
|
|
8398
|
-
count++;
|
|
8512
|
+
names.push("Codex CLI");
|
|
8399
8513
|
}
|
|
8400
8514
|
}
|
|
8401
|
-
|
|
8402
|
-
console.log(" No agent config files found. MCP server will need manual setup.");
|
|
8403
|
-
console.log(" See README for manual config instructions.");
|
|
8404
|
-
} else {
|
|
8405
|
-
console.log(`
|
|
8406
|
-
MCP server registered in ${count} agent config(s).`);
|
|
8407
|
-
}
|
|
8515
|
+
console.log(`MCP server: ${names.join(", ")}`);
|
|
8408
8516
|
}
|
|
8409
8517
|
|
|
8410
8518
|
// src/install-skill.ts
|
|
@@ -8412,11 +8520,16 @@ init_esm_shims();
|
|
|
8412
8520
|
import { existsSync as existsSync12, mkdirSync as mkdirSync8, readFileSync as readFileSync10, writeFileSync as writeFileSync8 } from "fs";
|
|
8413
8521
|
import { homedir as homedir10 } from "os";
|
|
8414
8522
|
import { dirname as dirname8, join as join13 } from "path";
|
|
8523
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
8524
|
+
function moduleDir() {
|
|
8525
|
+
return typeof __dirname !== "undefined" ? __dirname : dirname8(fileURLToPath4(import.meta.url));
|
|
8526
|
+
}
|
|
8415
8527
|
function loadSkillContent() {
|
|
8528
|
+
const here = moduleDir();
|
|
8416
8529
|
const candidates = [
|
|
8417
8530
|
join13(process.cwd(), "skills", "remem-mcp", "SKILL.md"),
|
|
8418
|
-
join13(
|
|
8419
|
-
join13(
|
|
8531
|
+
join13(here, "..", "skills", "remem-mcp", "SKILL.md"),
|
|
8532
|
+
join13(here, "skills", "remem-mcp", "SKILL.md")
|
|
8420
8533
|
];
|
|
8421
8534
|
for (const path of candidates) {
|
|
8422
8535
|
try {
|
|
@@ -8428,48 +8541,42 @@ function loadSkillContent() {
|
|
|
8428
8541
|
"Could not find skills/remem-mcp/SKILL.md. Make sure the package includes the skills directory."
|
|
8429
8542
|
);
|
|
8430
8543
|
}
|
|
8431
|
-
|
|
8432
|
-
|
|
8433
|
-
|
|
8434
|
-
|
|
8435
|
-
|
|
8436
|
-
|
|
8437
|
-
|
|
8438
|
-
|
|
8439
|
-
|
|
8440
|
-
|
|
8441
|
-
|
|
8442
|
-
|
|
8443
|
-
|
|
8444
|
-
|
|
8445
|
-
|
|
8446
|
-
|
|
8447
|
-
|
|
8448
|
-
|
|
8544
|
+
function skillTargets() {
|
|
8545
|
+
return [
|
|
8546
|
+
{
|
|
8547
|
+
name: "Devin CLI",
|
|
8548
|
+
path: join13(homedir10(), ".config", "devin", "skills", "remem-mcp", "SKILL.md")
|
|
8549
|
+
},
|
|
8550
|
+
{
|
|
8551
|
+
name: "Claude Code",
|
|
8552
|
+
path: join13(homedir10(), ".claude", "skills", "remem-mcp", "SKILL.md")
|
|
8553
|
+
},
|
|
8554
|
+
{
|
|
8555
|
+
name: "Codex CLI",
|
|
8556
|
+
path: join13(homedir10(), ".codex", "skills", "remem-mcp", "SKILL.md")
|
|
8557
|
+
},
|
|
8558
|
+
{
|
|
8559
|
+
name: "ZCode",
|
|
8560
|
+
path: join13(homedir10(), ".zcode", "skills", "remem-mcp", "SKILL.md")
|
|
8561
|
+
},
|
|
8562
|
+
{
|
|
8563
|
+
name: "Generic (.agents)",
|
|
8564
|
+
path: join13(homedir10(), ".agents", "skills", "remem-mcp", "SKILL.md")
|
|
8565
|
+
}
|
|
8566
|
+
];
|
|
8567
|
+
}
|
|
8449
8568
|
async function installSkill() {
|
|
8450
8569
|
const skillContent = loadSkillContent();
|
|
8451
|
-
|
|
8452
|
-
for (const target of
|
|
8570
|
+
const names = [];
|
|
8571
|
+
for (const target of skillTargets()) {
|
|
8453
8572
|
const dir = dirname8(target.path);
|
|
8454
8573
|
if (!existsSync12(dir)) {
|
|
8455
8574
|
mkdirSync8(dir, { recursive: true });
|
|
8456
8575
|
}
|
|
8457
|
-
if (existsSync12(target.path)) {
|
|
8458
|
-
console.log(` ${target.name}: Already installed. Updated.`);
|
|
8459
|
-
} else {
|
|
8460
|
-
console.log(` ${target.name}: Installed.`);
|
|
8461
|
-
}
|
|
8462
8576
|
writeFileSync8(target.path, skillContent, "utf-8");
|
|
8463
|
-
|
|
8577
|
+
names.push(target.name);
|
|
8464
8578
|
}
|
|
8465
|
-
console.log(`
|
|
8466
|
-
Skill installed to ${installed} location(s).`);
|
|
8467
|
-
console.log("Restart your agent to load the skill.");
|
|
8468
|
-
console.log("\nThe skill teaches your agent to:");
|
|
8469
|
-
console.log(" - Recall past context before answering");
|
|
8470
|
-
console.log(" - Capture decisions, learnings, and fixes after completing work");
|
|
8471
|
-
console.log(" - Search with filters when recall is too broad");
|
|
8472
|
-
console.log(" - Forget only on explicit user request");
|
|
8579
|
+
console.log(`Skill: ${names.join(", ")}`);
|
|
8473
8580
|
}
|
|
8474
8581
|
|
|
8475
8582
|
// src/pipeline/mermaid.ts
|
|
@@ -8694,7 +8801,7 @@ function extractDescription(content) {
|
|
|
8694
8801
|
|
|
8695
8802
|
// src/security/audit.ts
|
|
8696
8803
|
init_esm_shims();
|
|
8697
|
-
import { createHash as
|
|
8804
|
+
import { createHash as createHash7 } from "crypto";
|
|
8698
8805
|
import { appendFileSync as appendFileSync3, existsSync as existsSync13, mkdirSync as mkdirSync9 } from "fs";
|
|
8699
8806
|
import { dirname as dirname9 } from "path";
|
|
8700
8807
|
var AuditLogger = class {
|
|
@@ -8726,7 +8833,7 @@ var AuditLogger = class {
|
|
|
8726
8833
|
/** Hash the arguments for the audit log. The log does not store raw arguments. */
|
|
8727
8834
|
static hashArgs(args) {
|
|
8728
8835
|
const json = JSON.stringify(args);
|
|
8729
|
-
return
|
|
8836
|
+
return createHash7("sha256").update(json).digest("hex");
|
|
8730
8837
|
}
|
|
8731
8838
|
};
|
|
8732
8839
|
|
|
@@ -8734,7 +8841,7 @@ var AuditLogger = class {
|
|
|
8734
8841
|
init_esm_shims();
|
|
8735
8842
|
init_engine();
|
|
8736
8843
|
import { execSync as execSync3 } from "child_process";
|
|
8737
|
-
import { createHash as
|
|
8844
|
+
import { createHash as createHash8 } from "crypto";
|
|
8738
8845
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
8739
8846
|
import {
|
|
8740
8847
|
CallToolRequestSchema,
|
|
@@ -8801,7 +8908,7 @@ var indexingStatus = /* @__PURE__ */ new Map();
|
|
|
8801
8908
|
function defaultSessionKey() {
|
|
8802
8909
|
if (process.env.REMEM_SESSION_KEY) return process.env.REMEM_SESSION_KEY;
|
|
8803
8910
|
const cwd = process.cwd();
|
|
8804
|
-
return
|
|
8911
|
+
return createHash8("sha256").update(cwd).digest("hex").slice(0, 16);
|
|
8805
8912
|
}
|
|
8806
8913
|
function globalSessionKey() {
|
|
8807
8914
|
return process.env.REMEM_GLOBAL_SESSION_KEY ?? null;
|
|
@@ -8909,7 +9016,7 @@ var TOOLS = [
|
|
|
8909
9016
|
},
|
|
8910
9017
|
{
|
|
8911
9018
|
name: "capture",
|
|
8912
|
-
description:
|
|
9019
|
+
description: 'Store a decision, a learning, or a task outcome to memory. Call this tool after you complete a non-trivial task, make a decision, or fix a bug with a known root cause. You can capture a single text string, or a list of role-based conversation messages. Captures are project-local by default. Write to global memory ONLY when the user explicitly asks to remember something across projects/globally; then pass session_key="global".',
|
|
8913
9020
|
inputSchema: {
|
|
8914
9021
|
type: "object",
|
|
8915
9022
|
properties: {
|
|
@@ -8944,7 +9051,7 @@ var TOOLS = [
|
|
|
8944
9051
|
tags: { type: "array", items: { type: "string" }, description: "Optional tags." },
|
|
8945
9052
|
session_key: {
|
|
8946
9053
|
type: "string",
|
|
8947
|
-
description: "The session key. The default is hash(cwd). Use 'global' to store cross-project knowledge (rules, conventions, learnings)
|
|
9054
|
+
description: "The session key. The default is hash(cwd). Use 'global' to store cross-project knowledge (rules, conventions, learnings), but ONLY when the user explicitly asks to save it globally."
|
|
8948
9055
|
},
|
|
8949
9056
|
metadata: { type: "object", description: "Optional metadata." },
|
|
8950
9057
|
verified: {
|
|
@@ -8968,7 +9075,7 @@ var TOOLS = [
|
|
|
8968
9075
|
auto_global: {
|
|
8969
9076
|
type: "boolean",
|
|
8970
9077
|
default: false,
|
|
8971
|
-
description: "
|
|
9078
|
+
description: "Compatibility option for explicit global routing. Prefer session_key='global' and use this only when the user explicitly asks for cross-project classification. Requires REMEM_GLOBAL_SESSION_KEY."
|
|
8972
9079
|
},
|
|
8973
9080
|
atoms: {
|
|
8974
9081
|
type: "array",
|
|
@@ -9350,6 +9457,10 @@ var TOOLS = [
|
|
|
9350
9457
|
items: { type: "string" },
|
|
9351
9458
|
description: "Optional tags for categorization (e.g., ['database', 'migration'])."
|
|
9352
9459
|
},
|
|
9460
|
+
session_key: {
|
|
9461
|
+
type: "string",
|
|
9462
|
+
description: "The session key. Defaults to hash(cwd) so the scenario is scoped to the current project."
|
|
9463
|
+
},
|
|
9353
9464
|
...TENANT_PARAMS
|
|
9354
9465
|
},
|
|
9355
9466
|
required: ["atom_ids", "summary"]
|
|
@@ -9568,6 +9679,27 @@ var TOOLS = [
|
|
|
9568
9679
|
}
|
|
9569
9680
|
}
|
|
9570
9681
|
},
|
|
9682
|
+
{
|
|
9683
|
+
name: "codegraph_entry_points",
|
|
9684
|
+
description: "Find entry points in the codebase \u2014 main functions, HTTP handlers, CLI commands, event handlers. An entry point is called by external infrastructure (HTTP server, CLI framework, event loop) rather than by other code. Inspired by codegraph-ai's find_entry_points.",
|
|
9685
|
+
inputSchema: {
|
|
9686
|
+
type: "object",
|
|
9687
|
+
properties: {
|
|
9688
|
+
repo_path: {
|
|
9689
|
+
type: "string",
|
|
9690
|
+
description: "The repository root path (absolute)."
|
|
9691
|
+
},
|
|
9692
|
+
team_id: { type: "string", description: "The team ID for isolation." },
|
|
9693
|
+
limit: {
|
|
9694
|
+
type: "integer",
|
|
9695
|
+
default: 100,
|
|
9696
|
+
maximum: 500,
|
|
9697
|
+
description: "Maximum number of entry points to return."
|
|
9698
|
+
}
|
|
9699
|
+
},
|
|
9700
|
+
required: ["repo_path"]
|
|
9701
|
+
}
|
|
9702
|
+
},
|
|
9571
9703
|
// ─── Wiki tools ───
|
|
9572
9704
|
{
|
|
9573
9705
|
name: "wiki_ingest",
|
|
@@ -10115,6 +10247,8 @@ function createServer(opts) {
|
|
|
10115
10247
|
return handleCodegraphDetectChanges(args, opts);
|
|
10116
10248
|
case "codegraph_stats":
|
|
10117
10249
|
return handleCodegraphStats(args, opts);
|
|
10250
|
+
case "codegraph_entry_points":
|
|
10251
|
+
return handleCodegraphEntryPoints(args, opts);
|
|
10118
10252
|
case "wiki_ingest":
|
|
10119
10253
|
return handleWikiIngest(args, opts);
|
|
10120
10254
|
case "wiki_search":
|
|
@@ -10313,6 +10447,7 @@ ${wikiLines.join("\n")}`;
|
|
|
10313
10447
|
teamId,
|
|
10314
10448
|
agentId,
|
|
10315
10449
|
userId,
|
|
10450
|
+
sessionKey,
|
|
10316
10451
|
limit: 3
|
|
10317
10452
|
});
|
|
10318
10453
|
if (scenarios.length > 0) {
|
|
@@ -10419,7 +10554,7 @@ async function handleCapture(args, opts) {
|
|
|
10419
10554
|
};
|
|
10420
10555
|
}
|
|
10421
10556
|
const { text: redactedContent, redacted: wasRedacted } = opts.redactSecrets ? redact(content) : { text: content, redacted: false };
|
|
10422
|
-
const contentHash =
|
|
10557
|
+
const contentHash = createHash8("sha256").update(redactedContent).digest("hex");
|
|
10423
10558
|
if (!overrideRejection) {
|
|
10424
10559
|
const rejected = await opts.storage.findRejectedByContentHash(contentHash, sessionKey, agentId);
|
|
10425
10560
|
if (rejected.length > 0) {
|
|
@@ -11193,7 +11328,7 @@ async function handleUpdate(args, opts) {
|
|
|
11193
11328
|
const newTags = args.tags ? JSON.stringify(args.tags) : row.tags;
|
|
11194
11329
|
const newType = args.type ?? row.type;
|
|
11195
11330
|
const newTrust = args.verified ? "verified" : row.trust_state;
|
|
11196
|
-
const contentHash =
|
|
11331
|
+
const contentHash = createHash8("sha256").update(newContent).digest("hex");
|
|
11197
11332
|
db.prepare(
|
|
11198
11333
|
"UPDATE captures SET content = ?, tags = ?, type = ?, trust_state = ?, content_hash = ? WHERE id = ?"
|
|
11199
11334
|
).run(newContent, newTags, newType, newTrust, contentHash, id);
|
|
@@ -11948,7 +12083,7 @@ async function handleHandoff(args, opts) {
|
|
|
11948
12083
|
};
|
|
11949
12084
|
}
|
|
11950
12085
|
const dedupPayload = JSON.stringify({ task, status: status2, progress, decisions, files, nextSteps });
|
|
11951
|
-
const contentHash =
|
|
12086
|
+
const contentHash = createHash8("sha256").update(dedupPayload).digest("hex");
|
|
11952
12087
|
let existing = [];
|
|
11953
12088
|
try {
|
|
11954
12089
|
existing = await opts.storage.findByContentHash(contentHash, sessionKey);
|
|
@@ -12063,7 +12198,7 @@ async function handleAdr(args, opts) {
|
|
|
12063
12198
|
};
|
|
12064
12199
|
}
|
|
12065
12200
|
const dedupPayload = JSON.stringify({ title, context, decision, alternatives, consequences });
|
|
12066
|
-
const contentHash =
|
|
12201
|
+
const contentHash = createHash8("sha256").update(dedupPayload).digest("hex");
|
|
12067
12202
|
let existing = [];
|
|
12068
12203
|
try {
|
|
12069
12204
|
existing = await opts.storage.findByContentHash(contentHash, sessionKey);
|
|
@@ -12218,6 +12353,7 @@ async function handleScenarioConsolidate(args, opts) {
|
|
|
12218
12353
|
const personaTags = args.persona_tags;
|
|
12219
12354
|
const { teamId, userId, taskId } = extractTenant(args);
|
|
12220
12355
|
const agentId = args.agent_id ?? detectAgentId();
|
|
12356
|
+
const sessionKey = args.session_key ?? defaultSessionKey();
|
|
12221
12357
|
if (!atomIds || atomIds.length === 0) {
|
|
12222
12358
|
return { content: [{ type: "text", text: "Error: atom_ids is required." }], isError: true };
|
|
12223
12359
|
}
|
|
@@ -12242,7 +12378,8 @@ async function handleScenarioConsolidate(args, opts) {
|
|
|
12242
12378
|
createdAt: Date.now(),
|
|
12243
12379
|
teamId,
|
|
12244
12380
|
agentId,
|
|
12245
|
-
userId
|
|
12381
|
+
userId,
|
|
12382
|
+
sessionKey
|
|
12246
12383
|
};
|
|
12247
12384
|
try {
|
|
12248
12385
|
await opts.storage.putScenario(scenario);
|
|
@@ -12270,6 +12407,7 @@ async function handlePersonaUpdate(args, opts) {
|
|
|
12270
12407
|
const value = args.value;
|
|
12271
12408
|
const { teamId, userId } = extractTenant(args);
|
|
12272
12409
|
const agentId = args.agent_id ?? detectAgentId();
|
|
12410
|
+
const sessionKey = args.session_key ?? defaultSessionKey();
|
|
12273
12411
|
if (!trait || !value) {
|
|
12274
12412
|
return {
|
|
12275
12413
|
content: [{ type: "text", text: "Error: trait and value are required." }],
|
|
@@ -12278,7 +12416,7 @@ async function handlePersonaUpdate(args, opts) {
|
|
|
12278
12416
|
}
|
|
12279
12417
|
const tid = teamId ?? "default";
|
|
12280
12418
|
const uid = userId ?? "default";
|
|
12281
|
-
const existing = await opts.storage.readPersona(tid, agentId, uid);
|
|
12419
|
+
const existing = await opts.storage.readPersona(tid, agentId, uid, sessionKey);
|
|
12282
12420
|
let content;
|
|
12283
12421
|
if (existing) {
|
|
12284
12422
|
const lines = existing.content.split("\n").filter((l) => l.trim());
|
|
@@ -12294,7 +12432,7 @@ async function handlePersonaUpdate(args, opts) {
|
|
|
12294
12432
|
content = `${trait}: ${value}`;
|
|
12295
12433
|
}
|
|
12296
12434
|
try {
|
|
12297
|
-
await opts.storage.writePersona(tid, agentId, uid, content);
|
|
12435
|
+
await opts.storage.writePersona(tid, agentId, uid, content, sessionKey);
|
|
12298
12436
|
} catch (err) {
|
|
12299
12437
|
return { content: [{ type: "text", text: `Error: ${err}` }], isError: true };
|
|
12300
12438
|
}
|
|
@@ -12726,6 +12864,31 @@ function handleCodegraphStats(args, opts) {
|
|
|
12726
12864
|
content: [{ type: "text", text: lines.join("\n") }]
|
|
12727
12865
|
};
|
|
12728
12866
|
}
|
|
12867
|
+
function handleCodegraphEntryPoints(args, opts) {
|
|
12868
|
+
const repoPath = args.repo_path;
|
|
12869
|
+
const teamId = args.team_id ?? void 0;
|
|
12870
|
+
const limit = args.limit ?? 100;
|
|
12871
|
+
if (!repoPath) {
|
|
12872
|
+
return { content: [{ type: "text", text: "Error: repo_path is required." }], isError: true };
|
|
12873
|
+
}
|
|
12874
|
+
const db = getDb(opts);
|
|
12875
|
+
const entryPoints = findEntryPoints(db, { repoPath, teamId, limit });
|
|
12876
|
+
if (entryPoints.length === 0) {
|
|
12877
|
+
return { content: [{ type: "text", text: "No entry points found. Run codegraph_index first." }] };
|
|
12878
|
+
}
|
|
12879
|
+
const lines = entryPoints.map(
|
|
12880
|
+
(s) => `${s.id} ${s.kind.padEnd(10)} L${s.lineStart}-${s.lineEnd} ${s.name} at ${s.filePath}`
|
|
12881
|
+
);
|
|
12882
|
+
return {
|
|
12883
|
+
content: [
|
|
12884
|
+
{
|
|
12885
|
+
type: "text",
|
|
12886
|
+
text: `Found ${entryPoints.length} entry point(s) in ${repoPath}:
|
|
12887
|
+
${lines.join("\n")}`
|
|
12888
|
+
}
|
|
12889
|
+
]
|
|
12890
|
+
};
|
|
12891
|
+
}
|
|
12729
12892
|
async function handleWikiIngest(args, opts) {
|
|
12730
12893
|
const path = args.path;
|
|
12731
12894
|
const repoPath = args.repo_path ?? path;
|
|
@@ -15012,7 +15175,7 @@ function openDbWithSchema(dbPath) {
|
|
|
15012
15175
|
sqliteVec4.load(db);
|
|
15013
15176
|
const hasSymbols = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='symbols'").get();
|
|
15014
15177
|
if (!hasSymbols) {
|
|
15015
|
-
const distDir = dirname10(
|
|
15178
|
+
const distDir = dirname10(fileURLToPath5(import.meta.url));
|
|
15016
15179
|
const candidates = [
|
|
15017
15180
|
join16(distDir, "storage", "schema.sql"),
|
|
15018
15181
|
join16(distDir, "schema.sql"),
|
|
@@ -15365,14 +15528,26 @@ After that, the agent follows the rules automatically.`);
|
|
|
15365
15528
|
await installHooks();
|
|
15366
15529
|
return;
|
|
15367
15530
|
}
|
|
15531
|
+
if (arg === "guide") {
|
|
15532
|
+
console.log(`
|
|
15533
|
+
remem-mcp quick guide
|
|
15534
|
+
|
|
15535
|
+
Step 1 Restart your agent (quit and reopen)
|
|
15536
|
+
Step 2 Ask: "what do you remember?"
|
|
15537
|
+
Step 3 If agent recalls past context = memory working
|
|
15538
|
+
|
|
15539
|
+
In your agent, you can say:
|
|
15540
|
+
"index the code in src" \u2192 CodeGraph indexes symbols
|
|
15541
|
+
"find who calls function X" \u2192 caller analysis
|
|
15542
|
+
"what do you remember?" \u2192 recall past context
|
|
15543
|
+
`);
|
|
15544
|
+
return;
|
|
15545
|
+
}
|
|
15368
15546
|
if (arg === "setup") {
|
|
15369
15547
|
console.log("remem-mcp setup\n");
|
|
15370
15548
|
await installMcpServer();
|
|
15371
|
-
console.log("");
|
|
15372
15549
|
await installHooks();
|
|
15373
|
-
console.log("");
|
|
15374
15550
|
await installSkill();
|
|
15375
|
-
console.log("\nCapturing project basics...");
|
|
15376
15551
|
const { Memory: Memory2 } = await import("./sdk.js");
|
|
15377
15552
|
const mem = new Memory2();
|
|
15378
15553
|
let captured = 0;
|
|
@@ -15427,19 +15602,16 @@ After that, the agent follows the rules automatically.`);
|
|
|
15427
15602
|
if (id) captured++;
|
|
15428
15603
|
}
|
|
15429
15604
|
if (captured > 0) {
|
|
15430
|
-
console.log(`
|
|
15431
|
-
}
|
|
15432
|
-
|
|
15433
|
-
|
|
15434
|
-
|
|
15435
|
-
|
|
15436
|
-
|
|
15437
|
-
|
|
15438
|
-
|
|
15439
|
-
console.log("\
|
|
15440
|
-
console.log(" Hooks auto-recall on start, auto-capture errors, auto-save on exit.");
|
|
15441
|
-
console.log(" Skill teaches agent to use codegraph_search instead of grep.");
|
|
15442
|
-
console.log(" `npx remem-mcp status` to see memory.");
|
|
15605
|
+
console.log(`Project basics: ${captured} captured`);
|
|
15606
|
+
}
|
|
15607
|
+
console.log("\n\u2713 Setup complete.\n");
|
|
15608
|
+
console.log(" Step 1 Restart your agent (quit and reopen)");
|
|
15609
|
+
console.log(' Step 2 Ask: "what do you remember?"');
|
|
15610
|
+
console.log(" Step 3 If agent recalls past context = memory working\n");
|
|
15611
|
+
console.log(" In your agent, you can say:");
|
|
15612
|
+
console.log(' "index the code in src" \u2192 CodeGraph indexes symbols');
|
|
15613
|
+
console.log(' "find who calls function X" \u2192 caller analysis');
|
|
15614
|
+
console.log(' "what do you remember?" \u2192 recall past context');
|
|
15443
15615
|
return;
|
|
15444
15616
|
}
|
|
15445
15617
|
if (arg === "uninstall-hooks") {
|
|
@@ -15498,7 +15670,7 @@ ${rows.length} capture(s).`);
|
|
|
15498
15670
|
FROM captures WHERE deleted_at IS NULL
|
|
15499
15671
|
GROUP BY session_key ORDER BY cnt DESC`
|
|
15500
15672
|
).all();
|
|
15501
|
-
const cwdHash =
|
|
15673
|
+
const cwdHash = createHash9("sha256").update(process.cwd()).digest("hex").slice(0, 16);
|
|
15502
15674
|
const globalKey = process.env.REMEM_GLOBAL_SESSION_KEY ?? "global";
|
|
15503
15675
|
if (rows.length === 0) {
|
|
15504
15676
|
console.log("No sessions found.");
|
|
@@ -15822,7 +15994,7 @@ ${sorted.length} unique tag(s).`);
|
|
|
15822
15994
|
return;
|
|
15823
15995
|
}
|
|
15824
15996
|
if (arg === "proxy") {
|
|
15825
|
-
const { loadProxyConfig, startProxyServer } = await import("./server-
|
|
15997
|
+
const { loadProxyConfig, startProxyServer } = await import("./server-MYSF7R5Y.js");
|
|
15826
15998
|
const proxyConfig = loadProxyConfig();
|
|
15827
15999
|
if (!proxyConfig.dbPath) {
|
|
15828
16000
|
proxyConfig.dbPath = defaultDbPath3();
|
|
@@ -15852,7 +16024,11 @@ ${sorted.length} unique tag(s).`);
|
|
|
15852
16024
|
teamId: row.team_id ?? "default",
|
|
15853
16025
|
agentId: row.agent_id ?? "default"
|
|
15854
16026
|
};
|
|
15855
|
-
const ctx = {
|
|
16027
|
+
const ctx = {
|
|
16028
|
+
storage: storage2,
|
|
16029
|
+
embedder: new LocalEmbedder(),
|
|
16030
|
+
sessionKey: "default"
|
|
16031
|
+
};
|
|
15856
16032
|
const result = await pipeline2.process(input, ctx);
|
|
15857
16033
|
if (Object.keys(result).length > 0) extracted++;
|
|
15858
16034
|
}
|
|
@@ -16096,7 +16272,7 @@ ${sorted.length} unique tag(s).`);
|
|
|
16096
16272
|
}
|
|
16097
16273
|
if (arg === "version" || arg === "--version" || arg === "-v") {
|
|
16098
16274
|
try {
|
|
16099
|
-
const pkgPath = join16(dirname10(
|
|
16275
|
+
const pkgPath = join16(dirname10(fileURLToPath5(import.meta.url)), "..", "package.json");
|
|
16100
16276
|
const pkg = JSON.parse(readFileSync13(pkgPath, "utf-8"));
|
|
16101
16277
|
console.log(`remem-mcp v${pkg.version}`);
|
|
16102
16278
|
} catch {
|
|
@@ -16110,6 +16286,7 @@ ${sorted.length} unique tag(s).`);
|
|
|
16110
16286
|
|
|
16111
16287
|
Getting started:
|
|
16112
16288
|
remem-mcp setup One-command install (MCP + hooks + skill)
|
|
16289
|
+
remem-mcp guide Quick start guide (what to do after setup)
|
|
16113
16290
|
remem-mcp demo Error learning loop demo (15s)
|
|
16114
16291
|
remem-mcp demo-codegraph Live CodeGraph demo on facebook/react
|
|
16115
16292
|
remem-mcp status One dashboard: health + all 3 loops + recent
|
|
@@ -16127,6 +16304,7 @@ Daily use:
|
|
|
16127
16304
|
`);
|
|
16128
16305
|
if (!showAll) {
|
|
16129
16306
|
console.log(`The server runs as a stdio process. Add it to your MCP client:
|
|
16307
|
+
ZCode: ~/.zcode/cli/config.json (mcp.servers)
|
|
16130
16308
|
Claude Code: ~/.claude.json
|
|
16131
16309
|
Cursor: ~/.cursor/mcp.json
|
|
16132
16310
|
Devin CLI: devin mcp add remem-mcp -- npx -y remem-mcp
|
|
@@ -16140,7 +16318,7 @@ Setup & maintenance:
|
|
|
16140
16318
|
remem-mcp Start the MCP server (stdio)
|
|
16141
16319
|
remem-mcp setup Install MCP + hooks + skill (one command)
|
|
16142
16320
|
remem-mcp init Show how to activate memory in your agent session
|
|
16143
|
-
remem-mcp install-skill Install the agent skill
|
|
16321
|
+
remem-mcp install-skill Install the agent skill (ZCode, Claude Code, Devin, Codex)
|
|
16144
16322
|
remem-mcp install-hooks Install lifecycle hooks (SessionStart, Stop, SessionEnd)
|
|
16145
16323
|
remem-mcp uninstall-hooks Remove lifecycle hooks
|
|
16146
16324
|
remem-mcp hook-post-commit Auto-index changed files (git post-commit hook)
|
|
@@ -16237,27 +16415,28 @@ Common flags for L1-L3 and knowledge/skills commands:
|
|
|
16237
16415
|
--type <type> Filter by type (for knowledge: wiki, code-graph)
|
|
16238
16416
|
|
|
16239
16417
|
The server runs as a stdio process. Add it to your MCP client configuration:
|
|
16418
|
+
ZCode: ~/.zcode/cli/config.json (mcp.servers)
|
|
16240
16419
|
Claude Code: ~/.claude.json
|
|
16241
16420
|
Cursor: ~/.cursor/mcp.json
|
|
16242
16421
|
Devin CLI: devin mcp add remem-mcp -- npx -y remem-mcp
|
|
16243
16422
|
|
|
16244
|
-
To install the skill
|
|
16423
|
+
To install the skill:
|
|
16245
16424
|
npx remem-mcp install-skill
|
|
16246
16425
|
`);
|
|
16247
16426
|
return;
|
|
16248
16427
|
}
|
|
16249
16428
|
const config = loadConfig();
|
|
16250
|
-
try {
|
|
16251
|
-
importArtifact(config.dbPath, process.cwd());
|
|
16252
|
-
} catch (err) {
|
|
16253
|
-
console.error(`[remem-mcp] Auto-import failed: ${err}`);
|
|
16254
|
-
}
|
|
16255
16429
|
if (config.storage !== "sqlite") {
|
|
16256
16430
|
console.error(
|
|
16257
16431
|
`[remem-mcp] Storage backend "${config.storage}" is not implemented yet. Using sqlite.`
|
|
16258
16432
|
);
|
|
16259
16433
|
}
|
|
16260
16434
|
const storage = new SQLiteBackend(config.dbPath);
|
|
16435
|
+
try {
|
|
16436
|
+
importArtifact(config.dbPath, process.cwd());
|
|
16437
|
+
} catch (err) {
|
|
16438
|
+
console.error(`[remem-mcp] Auto-import failed: ${err}`);
|
|
16439
|
+
}
|
|
16261
16440
|
const embedder = new LocalEmbedder();
|
|
16262
16441
|
let pipeline;
|
|
16263
16442
|
if (config.pipeline === "atom" && config.llm) {
|