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/dist/index.js CHANGED
@@ -8,25 +8,26 @@ import {
8
8
  ingestFile,
9
9
  redact,
10
10
  searchWiki
11
- } from "./chunk-ITMR6THY.js";
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-YU7WBJU5.js";
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-4WCDAEPP.js";
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 createHash6 } from "crypto";
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 fileURLToPath4 } from "url";
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 createHash2 } from "crypto";
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 = createHash2("sha256").update(project).digest("hex").slice(0, 16);
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
- const row = db.prepare(
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({ limit: 100 });
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 = groupAtoms.slice(0, 5).map((a) => a.fact).join("; ");
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: summary.slice(0, 300),
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 existingPersonaRow = db.prepare(
1590
- "SELECT content FROM persona WHERE team_id = ? AND user_id = ? ORDER BY updated_at DESC LIMIT 1"
1591
- ).get(teamId, userId);
1592
- const existingContent = existingPersonaRow?.content ?? "";
1593
- const repeatedTags = Array.from(tagFreq.entries()).filter(([tag, count]) => count >= personaThreshold && !existingContent.includes(tag)).sort((a, b) => b[1] - a[1]).slice(0, 3);
1594
- if (repeatedTags.length > 0) {
1595
- const newTraits = repeatedTags.map(([tag, count]) => `${tag}: ${count} occurrences`);
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 createHash3 } from "crypto";
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 lines = ["[remem-mcp] Recent project memory:"];
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
- const personaRow = db.prepare(
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("SELECT summary FROM scenarios ORDER BY created_at DESC LIMIT 3").all();
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 = createHash3("sha256").update(content).digest("hex").slice(0, 16);
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(...globalRows.filter((r) => !seen.has(r.id)).slice(0, 2));
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 tagStr = tags.length > 0 ? ` [${tags.join(", ")}]` : "";
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("SELECT summary FROM scenarios ORDER BY created_at DESC LIMIT 3").all();
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
- const personaRow = db.prepare(
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-${createHash3("sha256").update(pattern.signature + pattern.file_path).digest("hex").slice(0, 12)}`;
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 = createHash3("sha256").update(content2).digest("hex").slice(0, 16);
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-${createHash3("sha256").update(decision.choice + sessionKey).digest("hex").slice(0, 12)}`;
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 = createHash3("sha256").update(decContent).digest("hex").slice(0, 16);
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 = createHash3("sha256").update(semanticContent).digest("hex").slice(0, 16);
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-${createHash3("sha256").update((p.sig ?? p.title) + (p.fpath ?? "")).digest("hex").slice(0, 12)}`;
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-${createHash3("sha256").update(d.choice + sessionKey).digest("hex").slice(0, 12)}`,
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-${createHash3("sha256").update(d.choice + sessionKey).digest("hex").slice(0, 12)}`,
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 createHash3("sha256").update(path).digest("hex").slice(0, 16);
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
- PRIMARY KEY (team_id, user_id)
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 = createHash3("sha256").update(content).digest("hex");
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: fileURLToPath5 } = await import("url");
7317
+ const { fileURLToPath: fileURLToPath6 } = await import("url");
7237
7318
  const sqliteVec5 = await import("sqlite-vec");
7238
- const { indexFile: indexFile2 } = await import("./engine-S47HHGAO.js");
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(fileURLToPath5(import.meta.url)), "storage", "schema.sql");
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: fileURLToPath5 } = await import("url");
7325
- const schemaPath = join9(dirname4(fileURLToPath5(import.meta.url)), "storage", "schema.sql");
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-${createHash3("sha256").update(sessionKey + Date.now()).digest("hex").slice(0, 12)}`;
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-${createHash3("sha256").update(sessionKey + "transcript" + Date.now()).digest("hex").slice(0, 12)}`;
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: fileURLToPath5 } = await import("url");
7423
- const schemaPath = join9(dirname4(fileURLToPath5(import.meta.url)), "storage", "schema.sql");
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 `npx --prefer-offline -y remem-mcp ${subcommand}`;
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
- console.log("Installing lifecycle hooks...\n");
8087
- let installed = 0;
8088
- if (installDevinHooks()) installed++;
8089
- if (installClaudeCodeHooks()) installed++;
8090
- if (installCodexHooks()) installed++;
8091
- if (installed === 0) {
8092
- console.log("\nNo supported agents found.");
8093
- console.log("Install Devin CLI, Claude Code, or Codex CLI first, then run this command again.");
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
- Hooks wired to ${installed} agent(s).`);
8098
- console.log("\nHooks installed:");
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
- console.log(` Claude Code: Hooks removed from ${claudePath}`);
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 === 0) {
8187
- console.log("No hooks found to remove.");
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 (JSON.stringify(servers["remem-mcp"]) === JSON.stringify(entry)) {
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
- console.log("\nRegistering MCP server...\n");
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
- count++;
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
- console.log(" Codex CLI: Already registered.");
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
- console.log(" Codex CLI: MCP server registered.");
8398
- count++;
8512
+ names.push("Codex CLI");
8399
8513
  }
8400
8514
  }
8401
- if (count === 0) {
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(__dirname, "..", "skills", "remem-mcp", "SKILL.md"),
8419
- join13(__dirname, "skills", "remem-mcp", "SKILL.md")
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
- var SKILL_TARGETS = [
8432
- {
8433
- name: "Devin CLI",
8434
- path: join13(homedir10(), ".config", "devin", "skills", "remem-mcp", "SKILL.md")
8435
- },
8436
- {
8437
- name: "Claude Code",
8438
- path: join13(homedir10(), ".claude", "skills", "remem-mcp", "SKILL.md")
8439
- },
8440
- {
8441
- name: "Codex CLI",
8442
- path: join13(homedir10(), ".codex", "skills", "remem-mcp", "SKILL.md")
8443
- },
8444
- {
8445
- name: "Generic (.agents)",
8446
- path: join13(homedir10(), ".agents", "skills", "remem-mcp", "SKILL.md")
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
- let installed = 0;
8452
- for (const target of SKILL_TARGETS) {
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
- installed++;
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 createHash4 } from "crypto";
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 createHash4("sha256").update(json).digest("hex");
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 createHash5 } from "crypto";
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 createHash5("sha256").update(cwd).digest("hex").slice(0, 16);
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: `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. To store cross-project knowledge (rules, conventions, learnings reusable across projects), pass session_key="global" or auto_global=true \u2014 these appear in every project's recall automatically.`,
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) that should appear in every project's recall."
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: "Set to true to auto-classify: generic rules/learnings go to global, project-specific content stays in project. Only applies when session_key is not explicitly set. Requires REMEM_GLOBAL_SESSION_KEY to be configured."
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 = createHash5("sha256").update(redactedContent).digest("hex");
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 = createHash5("sha256").update(newContent).digest("hex");
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 = createHash5("sha256").update(dedupPayload).digest("hex");
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 = createHash5("sha256").update(dedupPayload).digest("hex");
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(fileURLToPath4(import.meta.url));
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(` Captured ${captured} project basics.`);
15431
- } else {
15432
- const hasProjectFiles = existsSync16(join16(process.cwd(), "package.json")) || existsSync16(join16(process.cwd(), "Cargo.toml")) || existsSync16(join16(process.cwd(), "go.mod")) || existsSync16(join16(process.cwd(), "pyproject.toml"));
15433
- if (hasProjectFiles) {
15434
- console.log(" Project basics already captured (no changes since last setup).");
15435
- } else {
15436
- console.log(" No project files detected. Skipping bootstrap.");
15437
- }
15438
- }
15439
- console.log("\n\u2713 Done. Restart your agent.");
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 = createHash6("sha256").update(process.cwd()).digest("hex").slice(0, 16);
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-5PWQTRGZ.js");
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 = { storage: storage2, embedder: new LocalEmbedder(), sessionKey: "default" };
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(fileURLToPath4(import.meta.url)), "..", "package.json");
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 for Devin CLI
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 (Devin CLI only):
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) {