memorix 1.9.4 → 1.9.5

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.
Files changed (60) hide show
  1. package/CHANGELOG.md +33 -1
  2. package/README.md +6 -5
  3. package/README.zh-CN.md +6 -5
  4. package/dist/cli/index.js +890 -325
  5. package/dist/index.d.ts +3 -1
  6. package/dist/index.js +251 -104
  7. package/dist/maintenance-runner.js +144 -113
  8. package/dist/media-audio-runner.js +57 -26
  9. package/dist/media-video-runner.js +57 -26
  10. package/dist/memcode-runtime/CHANGELOG.md +33 -1
  11. package/dist/sdk.d.ts +1 -0
  12. package/dist/sdk.js +1012 -842
  13. package/dist/types.d.ts +1 -1
  14. package/dist/vector-backfill-runner.js +57 -26
  15. package/docs/AGENT_OPERATOR_PLAYBOOK.md +47 -3
  16. package/docs/API_REFERENCE.md +3 -1
  17. package/docs/CONFIGURATION.md +15 -1
  18. package/docs/DOCKER.md +23 -3
  19. package/docs/INTEGRATIONS.md +1 -1
  20. package/docs/KNOWN_ISSUES_AND_ROADMAP.md +26 -1
  21. package/docs/MULTI-DEVICE-SYNC-RESEARCH.md +4 -0
  22. package/docs/SETUP.md +55 -0
  23. package/package.json +2 -2
  24. package/plugins/antigravity/memorix/plugin.json +1 -1
  25. package/plugins/claude/memorix/.claude-plugin/plugin.json +1 -1
  26. package/plugins/codebuddy/memorix/.codebuddy-plugin/plugin.json +1 -1
  27. package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
  28. package/plugins/copilot/memorix/plugin.json +1 -1
  29. package/plugins/gemini/memorix/gemini-extension.json +1 -1
  30. package/plugins/hermes/memorix/plugin.yaml +1 -1
  31. package/plugins/omp/memorix/package.json +1 -1
  32. package/plugins/openclaw/memorix/.codex-plugin/plugin.json +1 -1
  33. package/plugins/pi/memorix/package.json +1 -1
  34. package/server.json +2 -2
  35. package/src/cli/command-guide.ts +2 -2
  36. package/src/cli/commands/agent-integrations.ts +57 -0
  37. package/src/cli/commands/background.ts +11 -2
  38. package/src/cli/commands/serve-http.ts +193 -42
  39. package/src/cli/commands/setup.ts +77 -10
  40. package/src/cli/commands/sync-store.ts +47 -12
  41. package/src/cli/commands/sync.ts +26 -0
  42. package/src/dashboard/server.ts +10 -12
  43. package/src/integrations/registry.ts +1 -1
  44. package/src/memory/graph.ts +10 -8
  45. package/src/memory/session.ts +1 -1
  46. package/src/sdk.ts +24 -0
  47. package/src/server/mcp-binding-store.ts +14 -3
  48. package/src/server.ts +2 -2
  49. package/src/store/file-lock.ts +28 -3
  50. package/src/store/graph-store.ts +178 -46
  51. package/src/store/persistence.ts +14 -9
  52. package/src/store/sqlite-db.ts +6 -3
  53. package/src/store/sqlite-store.ts +32 -27
  54. package/src/sync/engine.ts +9 -7
  55. package/src/sync/namespace.ts +23 -0
  56. package/src/sync/policy.ts +19 -4
  57. package/src/sync/store-port.ts +43 -13
  58. package/src/sync/types.ts +2 -0
  59. package/src/types.ts +1 -1
  60. package/src/workspace/mcp-adapters/grok.ts +32 -0
package/dist/cli/index.js CHANGED
@@ -3232,7 +3232,7 @@ ${import_picocolors.default.gray(m3)} ${s4}
3232
3232
 
3233
3233
  // src/cli/version.ts
3234
3234
  function getCliVersion() {
3235
- return true ? "1.9.4" : pkg.version;
3235
+ return true ? "1.9.5" : pkg.version;
3236
3236
  }
3237
3237
  var init_version = __esm({
3238
3238
  "src/cli/version.ts"() {
@@ -9473,6 +9473,14 @@ END;
9473
9473
  });
9474
9474
 
9475
9475
  // src/store/sqlite-db.ts
9476
+ var sqlite_db_exports = {};
9477
+ __export(sqlite_db_exports, {
9478
+ closeAllDatabases: () => closeAllDatabases,
9479
+ closeDatabase: () => closeDatabase,
9480
+ getDatabase: () => getDatabase,
9481
+ isSqliteAvailable: () => isSqliteAvailable,
9482
+ loadBetterSqlite3: () => loadBetterSqlite3
9483
+ });
9476
9484
  import "module";
9477
9485
  import path3 from "path";
9478
9486
  import fs from "fs";
@@ -9589,6 +9597,17 @@ function getDatabase(dataDir) {
9589
9597
  throw error40;
9590
9598
  }
9591
9599
  }
9600
+ function closeDatabase(dataDir) {
9601
+ const normalized = path3.resolve(dataDir);
9602
+ const db2 = _dbCache.get(normalized);
9603
+ if (db2) {
9604
+ try {
9605
+ db2.close();
9606
+ } catch {
9607
+ }
9608
+ _dbCache.delete(normalized);
9609
+ }
9610
+ }
9592
9611
  function closeAllDatabases() {
9593
9612
  for (const [key, db2] of _dbCache) {
9594
9613
  try {
@@ -9598,6 +9617,14 @@ function closeAllDatabases() {
9598
9617
  _dbCache.delete(key);
9599
9618
  }
9600
9619
  }
9620
+ function isSqliteAvailable() {
9621
+ try {
9622
+ loadBetterSqlite3();
9623
+ return true;
9624
+ } catch {
9625
+ return false;
9626
+ }
9627
+ }
9601
9628
  var BetterSqlite3, CREATE_OBSERVATIONS_TABLE, CREATE_MINI_SKILLS_TABLE, CREATE_SESSIONS_TABLE, CREATE_META_TABLE, CREATE_TEAM_AGENTS_TABLE, CREATE_TEAM_MESSAGES_TABLE, CREATE_TEAM_TASKS_TABLE, CREATE_TEAM_TASK_DEPS_TABLE, CREATE_TEAM_LOCKS_TABLE, CREATE_TEAM_ROLES_TABLE, CREATE_CHAT_TRANSCRIPT_TABLE, CREATE_GRAPH_ENTITIES_TABLE, CREATE_GRAPH_RELATIONS_TABLE, CREATE_CODE_FILES_TABLE, CREATE_CODE_SYMBOLS_TABLE, CREATE_CODE_EDGES_TABLE, CREATE_OBSERVATION_CODE_REFS_TABLE, CREATE_SCHEMA_MIGRATIONS_TABLE, CREATE_CODE_STATE_SNAPSHOTS_TABLE, CREATE_CODE_STATE_SNAPSHOT_FILES_TABLE, CREATE_KNOWLEDGE_CLAIMS_TABLE, CREATE_KNOWLEDGE_CLAIM_EVIDENCE_TABLE, CREATE_KNOWLEDGE_CLAIM_EVENTS_TABLE, CREATE_KNOWLEDGE_WORKSPACES_TABLE, CREATE_KNOWLEDGE_PAGES_TABLE, CREATE_KNOWLEDGE_PAGE_CLAIMS_TABLE, CREATE_KNOWLEDGE_PAGE_LINKS_TABLE, CREATE_KNOWLEDGE_PROPOSALS_TABLE, CREATE_KNOWLEDGE_WORKFLOWS_TABLE, CREATE_KNOWLEDGE_WORKFLOW_RUNS_TABLE, CREATE_LONG_TERM_MEMORIES_TABLE, CREATE_LONG_TERM_MEMORY_EVIDENCE_TABLE, CREATE_LONG_TERM_MEMORY_EVENTS_TABLE, CREATE_MEMORY_OUTCOME_SIGNALS_TABLE, CREATE_MEDIA_ASSETS_TABLE, CREATE_MEDIA_ASSET_LINKS_TABLE, CREATE_MEDIA_DERIVATIONS_TABLE, CREATE_MEDIA_EMBEDDING_PROFILES_TABLE, CREATE_MEDIA_EMBEDDINGS_TABLE, CREATE_MEDIA_JOBS_TABLE, CREATE_MAINTENANCE_JOBS_TABLE, CREATE_MAINTENANCE_TARGETS_TABLE, CREATE_COMPACTION_CHECKPOINTS_TABLE, CREATE_EVIDENCE_CARDS_TABLE, CREATE_EVIDENCE_CARD_EVENTS_TABLE, CREATE_MEMORY_FEEDBACK_STATES_TABLE, CREATE_MEMORY_FEEDBACK_EVENTS_TABLE, CREATE_MCP_BINDINGS_TABLE, CREATE_INDEXES, SCHEMA_MIGRATIONS, _dbCache;
9602
9629
  var init_sqlite_db = __esm({
9603
9630
  "src/store/sqlite-db.ts"() {
@@ -9770,18 +9797,21 @@ CREATE TABLE IF NOT EXISTS chat_transcript (
9770
9797
  `;
9771
9798
  CREATE_GRAPH_ENTITIES_TABLE = `
9772
9799
  CREATE TABLE IF NOT EXISTS graph_entities (
9773
- name TEXT PRIMARY KEY,
9800
+ projectId TEXT NOT NULL,
9801
+ name TEXT NOT NULL,
9774
9802
  entityType TEXT NOT NULL DEFAULT '',
9775
- observations TEXT NOT NULL DEFAULT '[]'
9803
+ observations TEXT NOT NULL DEFAULT '[]',
9804
+ PRIMARY KEY (projectId, name)
9776
9805
  );
9777
9806
  `;
9778
9807
  CREATE_GRAPH_RELATIONS_TABLE = `
9779
9808
  CREATE TABLE IF NOT EXISTS graph_relations (
9780
9809
  id INTEGER PRIMARY KEY AUTOINCREMENT,
9810
+ projectId TEXT NOT NULL,
9781
9811
  from_entity TEXT NOT NULL,
9782
9812
  to_entity TEXT NOT NULL,
9783
9813
  relationType TEXT NOT NULL DEFAULT '',
9784
- UNIQUE(from_entity, to_entity, relationType)
9814
+ UNIQUE(projectId, from_entity, to_entity, relationType)
9785
9815
  );
9786
9816
  `;
9787
9817
  CREATE_CODE_FILES_TABLE = `
@@ -10649,6 +10679,21 @@ var init_sqlite_reliability = __esm({
10649
10679
  import { promises as fs2 } from "fs";
10650
10680
  import { randomUUID } from "crypto";
10651
10681
  import path4 from "path";
10682
+ function canReclaimStaleLock(raw) {
10683
+ try {
10684
+ const parsed = JSON.parse(raw);
10685
+ const pid = Number(parsed.pid);
10686
+ if (!Number.isSafeInteger(pid) || pid < 1) return true;
10687
+ try {
10688
+ process.kill(pid, 0);
10689
+ return false;
10690
+ } catch (error40) {
10691
+ return error40.code !== "EPERM";
10692
+ }
10693
+ } catch {
10694
+ return true;
10695
+ }
10696
+ }
10652
10697
  async function acquireLock(lockPath) {
10653
10698
  for (let i4 = 0; i4 < MAX_RETRIES; i4++) {
10654
10699
  try {
@@ -10662,9 +10707,12 @@ async function acquireLock(lockPath) {
10662
10707
  try {
10663
10708
  const stat6 = await fs2.stat(lockPath);
10664
10709
  if (Date.now() - stat6.mtimeMs > LOCK_STALE_MS) {
10665
- await fs2.unlink(lockPath).catch(() => {
10666
- });
10667
- continue;
10710
+ const raw2 = await fs2.readFile(lockPath, "utf8").catch(() => "");
10711
+ if (canReclaimStaleLock(raw2)) {
10712
+ await fs2.unlink(lockPath).catch(() => {
10713
+ });
10714
+ continue;
10715
+ }
10668
10716
  }
10669
10717
  } catch {
10670
10718
  continue;
@@ -10675,6 +10723,10 @@ async function acquireLock(lockPath) {
10675
10723
  }
10676
10724
  }
10677
10725
  }
10726
+ const raw = await fs2.readFile(lockPath, "utf8").catch(() => "");
10727
+ if (!canReclaimStaleLock(raw)) {
10728
+ throw new Error(`Failed to acquire lock: ${lockPath} (owner is still running)`);
10729
+ }
10678
10730
  await fs2.unlink(lockPath).catch(() => {
10679
10731
  });
10680
10732
  try {
@@ -10913,6 +10965,11 @@ async function listProjectDirs(baseDir) {
10913
10965
  }
10914
10966
  }
10915
10967
  async function migrateSubdirsToFlat(baseDir) {
10968
+ const base = baseDir ?? resolveDefaultDataDir();
10969
+ await fs4.mkdir(base, { recursive: true });
10970
+ return withFileLock(base, () => migrateSubdirsToFlatUnlocked(base));
10971
+ }
10972
+ async function migrateSubdirsToFlatUnlocked(baseDir) {
10916
10973
  const base = baseDir ?? resolveDefaultDataDir();
10917
10974
  await fs4.mkdir(base, { recursive: true });
10918
10975
  let entries;
@@ -10969,7 +11026,7 @@ async function migrateSubdirsToFlat(baseDir) {
10969
11026
  for (const { obs } of subdirData) {
10970
11027
  for (const o4 of obs) {
10971
11028
  const isDuplicate = allObs.some(
10972
- (existing) => existing.title === o4.title && existing.createdAt === o4.createdAt
11029
+ (existing) => existing.title === o4.title && existing.createdAt === o4.createdAt && existing.projectId === o4.projectId
10973
11030
  );
10974
11031
  if (!isDuplicate) {
10975
11032
  allObs.push(o4);
@@ -11002,18 +11059,14 @@ async function migrateSubdirsToFlat(baseDir) {
11002
11059
  mergedRelations.push(rel);
11003
11060
  }
11004
11061
  }
11005
- await fs4.writeFile(path6.join(base, "observations.json"), JSON.stringify(allObs, null, 2), "utf-8");
11006
- await fs4.writeFile(
11007
- path6.join(base, "counter.json"),
11008
- JSON.stringify({ nextId: allObs.length + 1 }),
11009
- "utf-8"
11010
- );
11062
+ await atomicWriteFile(path6.join(base, "observations.json"), JSON.stringify(allObs, null, 2));
11063
+ await atomicWriteFile(path6.join(base, "counter.json"), JSON.stringify({ nextId: allObs.length + 1 }));
11011
11064
  const graphLines = [
11012
11065
  ...[...entityMap.values()].map((e5) => JSON.stringify({ type: "entity", name: e5.name, entityType: e5.entityType, observations: e5.observations })),
11013
11066
  ...mergedRelations.map((r6) => JSON.stringify({ type: "relation", from: r6.from, to: r6.to, relationType: r6.relationType }))
11014
11067
  ];
11015
11068
  if (graphLines.length > 0) {
11016
- await fs4.writeFile(path6.join(base, "graph.jsonl"), graphLines.join("\n"), "utf-8");
11069
+ await atomicWriteFile(path6.join(base, "graph.jsonl"), graphLines.join("\n"));
11017
11070
  }
11018
11071
  let allSessions = [];
11019
11072
  try {
@@ -11031,7 +11084,7 @@ async function migrateSubdirsToFlat(baseDir) {
11031
11084
  }
11032
11085
  }
11033
11086
  if (allSessions.length > 0) {
11034
- await fs4.writeFile(path6.join(base, "sessions.json"), JSON.stringify(allSessions, null, 2), "utf-8");
11087
+ await atomicWriteFile(path6.join(base, "sessions.json"), JSON.stringify(allSessions, null, 2));
11035
11088
  }
11036
11089
  const backupDir = path6.join(base, ".migrated-subdirs");
11037
11090
  await fs4.mkdir(backupDir, { recursive: true });
@@ -11067,6 +11120,7 @@ var init_persistence = __esm({
11067
11120
  "src/store/persistence.ts"() {
11068
11121
  "use strict";
11069
11122
  init_esm_shims();
11123
+ init_file_lock();
11070
11124
  init_persistence_json();
11071
11125
  }
11072
11126
  });
@@ -25338,6 +25392,7 @@ var init_sqlite_store = __esm({
25338
25392
  init_sqlite_db();
25339
25393
  init_sqlite_reliability();
25340
25394
  init_sqlite_fts();
25395
+ init_file_lock();
25341
25396
  SqliteBackend = class {
25342
25397
  db = null;
25343
25398
  dataDir = "";
@@ -25501,38 +25556,43 @@ var init_sqlite_store = __esm({
25501
25556
  }
25502
25557
  // ── Migration ────────────────────────────────────────────────────
25503
25558
  async migrateFromJsonIfNeeded() {
25504
- const count3 = this.db.prepare(`SELECT COUNT(*) AS cnt FROM observations`).get();
25505
- if (count3.cnt > 0) return;
25506
25559
  const jsonPath = path9.join(this.dataDir, "observations.json");
25507
25560
  if (!fs6.existsSync(jsonPath)) return;
25508
- try {
25561
+ await withFileLock(this.dataDir, async () => {
25562
+ const count3 = this.db.prepare(`SELECT COUNT(*) AS cnt FROM observations`).get();
25563
+ if (count3.cnt > 0) return;
25509
25564
  const raw = fs6.readFileSync(jsonPath, "utf-8");
25510
- const observations2 = JSON.parse(raw);
25565
+ const parsed = JSON.parse(raw);
25566
+ if (!Array.isArray(parsed)) {
25567
+ throw new Error("[memorix] observations.json migration refused: expected a JSON array");
25568
+ }
25569
+ const observations2 = parsed;
25511
25570
  if (observations2.length === 0) return;
25571
+ if (observations2.some((obs) => !obs || !Number.isSafeInteger(obs.id) || obs.id < 1 || typeof obs.projectId !== "string")) {
25572
+ throw new Error("[memorix] observations.json migration refused: invalid observation record");
25573
+ }
25512
25574
  console.error(`[memorix] Migrating ${observations2.length} observations from JSON to SQLite...`);
25513
25575
  const insertMany = this.db.transaction((obsList) => {
25514
25576
  for (const obs of obsList) {
25515
25577
  this.stmtInsert.run(obsToRow(obs));
25516
25578
  }
25517
- });
25518
- insertMany(observations2);
25519
- const counterPath = path9.join(this.dataDir, "counter.json");
25520
- if (fs6.existsSync(counterPath)) {
25521
- try {
25579
+ const counterPath = path9.join(this.dataDir, "counter.json");
25580
+ let counterNextId;
25581
+ if (fs6.existsSync(counterPath)) {
25522
25582
  const counterData = JSON.parse(fs6.readFileSync(counterPath, "utf-8"));
25523
- const nextId2 = counterData.nextId ?? counterData.next_id ?? Math.max(...observations2.map((o4) => o4.id)) + 1;
25524
- this.rawSaveIdCounter(nextId2);
25525
- } catch {
25526
- this.rawSaveIdCounter(Math.max(...observations2.map((o4) => o4.id)) + 1);
25583
+ const candidate2 = counterData.nextId ?? counterData.next_id;
25584
+ if (candidate2 !== void 0 && (!Number.isSafeInteger(candidate2) || Number(candidate2) < 1)) {
25585
+ throw new Error("[memorix] counter.json migration refused: nextId must be a positive integer");
25586
+ }
25587
+ counterNextId = candidate2 === void 0 ? void 0 : Number(candidate2);
25527
25588
  }
25528
- } else {
25529
- this.rawSaveIdCounter(Math.max(...observations2.map((o4) => o4.id)) + 1);
25530
- }
25531
- this.bumpGeneration();
25589
+ const maxId = Math.max(...obsList.map((obs) => obs.id));
25590
+ this.rawSaveIdCounter(Math.max(counterNextId ?? 1, maxId + 1));
25591
+ this.bumpGeneration();
25592
+ });
25593
+ insertMany(observations2);
25532
25594
  console.error(`[memorix] Migration complete. ${observations2.length} observations now in SQLite.`);
25533
- } catch (err) {
25534
- console.error(`[memorix] JSON\u2192SQLite migration failed (non-fatal, data preserved in JSON): ${err}`);
25535
- }
25595
+ });
25536
25596
  }
25537
25597
  // ── Public read ──────────────────────────────────────────────────
25538
25598
  async loadAll() {
@@ -86686,12 +86746,12 @@ var require_jwa = __commonJS({
86686
86746
  };
86687
86747
  }
86688
86748
  var bufferEqual;
86689
- var timingSafeEqual2 = "timingSafeEqual" in crypto2 ? function timingSafeEqual3(a4, b4) {
86749
+ var timingSafeEqual3 = "timingSafeEqual" in crypto2 ? function timingSafeEqual4(a4, b4) {
86690
86750
  if (a4.byteLength !== b4.byteLength) {
86691
86751
  return false;
86692
86752
  }
86693
86753
  return crypto2.timingSafeEqual(a4, b4);
86694
- } : function timingSafeEqual3(a4, b4) {
86754
+ } : function timingSafeEqual4(a4, b4) {
86695
86755
  if (!bufferEqual) {
86696
86756
  bufferEqual = require_buffer_equal_constant_time();
86697
86757
  }
@@ -86700,7 +86760,7 @@ var require_jwa = __commonJS({
86700
86760
  function createHmacVerifier(bits) {
86701
86761
  return function verify2(thing, signature, secret) {
86702
86762
  var computedSig = createHmacSigner(bits)(thing, secret);
86703
- return timingSafeEqual2(Buffer5.from(signature), Buffer5.from(computedSig));
86763
+ return timingSafeEqual3(Buffer5.from(signature), Buffer5.from(computedSig));
86704
86764
  };
86705
86765
  }
86706
86766
  function createKeySigner(bits) {
@@ -194277,6 +194337,32 @@ var init_codebuddy = __esm({
194277
194337
  }
194278
194338
  });
194279
194339
 
194340
+ // src/workspace/mcp-adapters/grok.ts
194341
+ import { homedir as homedir25 } from "os";
194342
+ import { join as join22 } from "path";
194343
+ var GrokMCPAdapter;
194344
+ var init_grok = __esm({
194345
+ "src/workspace/mcp-adapters/grok.ts"() {
194346
+ "use strict";
194347
+ init_esm_shims();
194348
+ init_codex();
194349
+ GrokMCPAdapter = class {
194350
+ source = "grok";
194351
+ toml = new CodexMCPAdapter();
194352
+ parse(content) {
194353
+ return this.toml.parse(content);
194354
+ }
194355
+ generate(servers) {
194356
+ return this.toml.generate(servers);
194357
+ }
194358
+ getConfigPath(_projectRoot) {
194359
+ const grokHome = process.env.GROK_HOME?.trim() || join22(homedir25(), ".grok");
194360
+ return join22(grokHome, "config.toml");
194361
+ }
194362
+ };
194363
+ }
194364
+ });
194365
+
194280
194366
  // src/integrations/registry.ts
194281
194367
  function getSetupIntegrationRows() {
194282
194368
  return MEMORIX_INTEGRATIONS.filter((item) => item.status === "ready" && item.agent !== "memcode" && item.agent !== "any-mcp").map((item) => ({
@@ -194334,7 +194420,7 @@ var init_registry2 = __esm({
194334
194420
  status: "ready",
194335
194421
  install: "memorix setup --agent grok --global",
194336
194422
  surfaces: ["hooks", "AGENTS.md"],
194337
- note: "Writes ~/.grok/hooks/memorix.json and leaves Grok MCP host-owned; setup does not write config.toml."
194423
+ note: "Writes ~/.grok/hooks/memorix.json and leaves Grok MCP host-owned by default. Pass --mcp http to write [mcp_servers.memorix] in Grok config.toml."
194338
194424
  },
194339
194425
  {
194340
194426
  agent: "cursor",
@@ -194739,9 +194825,9 @@ __export(audit_exports, {
194739
194825
  });
194740
194826
  import * as fs18 from "fs/promises";
194741
194827
  import * as path30 from "path";
194742
- import { homedir as homedir25 } from "os";
194828
+ import { homedir as homedir26 } from "os";
194743
194829
  function getAuditFilePath() {
194744
- return process.env.MEMORIX_AUDIT_FILE || path30.join(homedir25(), ".memorix", "audit.json");
194830
+ return process.env.MEMORIX_AUDIT_FILE || path30.join(homedir26(), ".memorix", "audit.json");
194745
194831
  }
194746
194832
  async function loadAudit() {
194747
194833
  try {
@@ -196195,6 +196281,7 @@ var init_installers = __esm({
196195
196281
  // src/cli/commands/setup.ts
196196
196282
  var setup_exports = {};
196197
196283
  __export(setup_exports, {
196284
+ MEMORIX_HTTP_MCP_URL: () => MEMORIX_HTTP_MCP_URL,
196198
196285
  buildMemorixServer: () => buildMemorixServer,
196199
196286
  buildSetupPlan: () => buildSetupPlan,
196200
196287
  default: () => setup_default,
@@ -196214,19 +196301,20 @@ __export(setup_exports, {
196214
196301
  migrateLegacyCodexIntegration: () => migrateLegacyCodexIntegration,
196215
196302
  parseCodexPluginState: () => parseCodexPluginState,
196216
196303
  removeLegacyCodexMemorixMcpConfig: () => removeLegacyCodexMemorixMcpConfig,
196304
+ rewriteBundledMemorixMcpToHttp: () => rewriteBundledMemorixMcpToHttp,
196217
196305
  tryInstallCodexPlugin: () => tryInstallCodexPlugin
196218
196306
  });
196219
196307
  import { spawnSync } from "child_process";
196220
196308
  import { existsSync as existsSync17 } from "fs";
196221
196309
  import { copyFile as copyFile2, mkdir as mkdir6, readFile as readFile6, readdir as readdir3, rm as rm2, writeFile as writeFile7 } from "fs/promises";
196222
- import { homedir as homedir27 } from "os";
196310
+ import { homedir as homedir28 } from "os";
196223
196311
  import path32 from "path";
196224
196312
  import { fileURLToPath as fileURLToPath7 } from "url";
196225
196313
  function getSetupAgentTargets() {
196226
196314
  return [...SUPPORTED_SETUP_AGENTS];
196227
196315
  }
196228
196316
  function buildSetupPlan(options) {
196229
- const mcp = options.agent === "grok" ? "none" : options.mcp ?? "stdio";
196317
+ const mcp = options.agent === "grok" ? options.mcp === "http" ? "http" : "none" : options.mcp ?? "stdio";
196230
196318
  const actions = [];
196231
196319
  const isPackageOwnedAgent = options.agent !== "all" && PACKAGE_OWNED_INTEGRATION_AGENTS.has(options.agent);
196232
196320
  const includeHooks = options.hooks ?? true;
@@ -196382,7 +196470,8 @@ function getMcpAdapter(agent) {
196382
196470
  opencode: new OpenCodeMCPAdapter(),
196383
196471
  trae: new TraeMCPAdapter(),
196384
196472
  dsh: new DshMCPAdapter(),
196385
- workbuddy: new WorkbuddyMCPAdapter()
196473
+ workbuddy: new WorkbuddyMCPAdapter(),
196474
+ grok: new GrokMCPAdapter()
196386
196475
  };
196387
196476
  return adapters[agent];
196388
196477
  }
@@ -196392,7 +196481,7 @@ function buildMemorixServer(mcp) {
196392
196481
  name: "memorix",
196393
196482
  command: "",
196394
196483
  args: [],
196395
- url: "http://localhost:3211/mcp"
196484
+ url: MEMORIX_HTTP_MCP_URL
196396
196485
  };
196397
196486
  }
196398
196487
  return {
@@ -196403,6 +196492,36 @@ function buildMemorixServer(mcp) {
196403
196492
  args: ["serve", "--mode", "lite"]
196404
196493
  };
196405
196494
  }
196495
+ function toHttpMemorixServer(existing) {
196496
+ const next = { url: MEMORIX_HTTP_MCP_URL };
196497
+ if (existing.alwaysLoad === true) next.alwaysLoad = true;
196498
+ if (typeof existing.type === "string") next.type = "http";
196499
+ return next;
196500
+ }
196501
+ async function rewriteBundledMemorixMcpToHttp(root) {
196502
+ const candidates = [".mcp.json", "mcp_config.json", "gemini-extension.json"].map((name) => path32.join(root, name));
196503
+ const rewritten = [];
196504
+ for (const filePath of candidates) {
196505
+ if (!existsSync17(filePath)) continue;
196506
+ let doc;
196507
+ try {
196508
+ doc = JSON.parse(await readFile6(filePath, "utf-8"));
196509
+ } catch {
196510
+ continue;
196511
+ }
196512
+ if (!doc || typeof doc !== "object" || Array.isArray(doc)) continue;
196513
+ const record2 = doc;
196514
+ const servers = record2.mcpServers;
196515
+ if (!servers || typeof servers !== "object" || Array.isArray(servers)) continue;
196516
+ const serverMap = servers;
196517
+ const existing = serverMap.memorix;
196518
+ if (!existing || typeof existing !== "object" || Array.isArray(existing)) continue;
196519
+ serverMap.memorix = toHttpMemorixServer(existing);
196520
+ await writeFile7(filePath, JSON.stringify(record2, null, 2) + "\n", "utf-8");
196521
+ rewritten.push(filePath);
196522
+ }
196523
+ return rewritten;
196524
+ }
196406
196525
  function mergeJsonMcpConfig(existingContent, generatedContent) {
196407
196526
  let generated;
196408
196527
  try {
@@ -196504,7 +196623,7 @@ function isOwnedLegacyCodexMcp(content) {
196504
196623
  const server = new CodexMCPAdapter().parse(content).find((entry) => entry.name === "memorix");
196505
196624
  return Boolean(server && isLegacyCodexMemorixNodeServer(server));
196506
196625
  }
196507
- async function removeLegacyCodexMemorixMcpConfig(configPath = path32.join(homedir27(), ".codex", "config.toml")) {
196626
+ async function removeLegacyCodexMemorixMcpConfig(configPath = path32.join(homedir28(), ".codex", "config.toml")) {
196508
196627
  let existingContent;
196509
196628
  try {
196510
196629
  existingContent = await readFile6(configPath, "utf-8");
@@ -196561,7 +196680,7 @@ async function removeOwnedLegacyCodexPluginFolder(homeDir) {
196561
196680
  }
196562
196681
  }
196563
196682
  async function migrateLegacyCodexIntegration(options = {}) {
196564
- const homeDir = options.homeDir ?? homedir27();
196683
+ const homeDir = options.homeDir ?? homedir28();
196565
196684
  const projectRoot = options.projectRoot ?? process.cwd();
196566
196685
  const configPath = path32.join(homeDir, ".codex", "config.toml");
196567
196686
  let mcp = "missing";
@@ -196671,7 +196790,7 @@ async function installMcpConfig(options) {
196671
196790
  existingContent = null;
196672
196791
  }
196673
196792
  const generated = adapter.generate([server]);
196674
- const content = options.agent === "codex" ? mergeTomlMcpConfig(existingContent, generated) : options.agent === "hermes" ? mergeYamlMcpConfig(existingContent, generated) : options.agent === "dsh" ? mergeDshPatchConfig(existingContent, generated) : mergeJsonMcpConfig(existingContent, generated);
196793
+ const content = options.agent === "codex" || options.agent === "grok" ? mergeTomlMcpConfig(existingContent, generated) : options.agent === "hermes" ? mergeYamlMcpConfig(existingContent, generated) : options.agent === "dsh" ? mergeDshPatchConfig(existingContent, generated) : mergeJsonMcpConfig(existingContent, generated);
196675
196794
  await mkdir6(path32.dirname(configPath), { recursive: true });
196676
196795
  await writeFile7(configPath, content, "utf-8");
196677
196796
  return { agent: options.agent, configPath, transport: options.mcp };
@@ -196762,7 +196881,7 @@ async function stripCodeBuddyHookCapture(pluginPath) {
196762
196881
  await writeFile7(manifestPath, JSON.stringify(manifest, null, 2) + "\n", "utf-8");
196763
196882
  }
196764
196883
  async function installCodeBuddyPluginPackage(options = {}) {
196765
- const home = options.homeDir ?? homedir27();
196884
+ const home = options.homeDir ?? homedir28();
196766
196885
  const packageRoot = findPackageRoot();
196767
196886
  const source = path32.join(packageRoot, "plugins", "codebuddy", "memorix");
196768
196887
  if (!existsSync17(source)) throw new Error(`CodeBuddy plugin template not found: ${source}`);
@@ -196782,7 +196901,7 @@ async function installCodeBuddyPluginPackage(options = {}) {
196782
196901
  };
196783
196902
  }
196784
196903
  async function installPluginPackage(options) {
196785
- const home = options.homeDir ?? homedir27();
196904
+ const home = options.homeDir ?? homedir28();
196786
196905
  const packageRoot = findPackageRoot();
196787
196906
  const source = path32.join(packageRoot, "plugins", options.agent, "memorix");
196788
196907
  const includeHooks = options.includeHooks ?? true;
@@ -196831,7 +196950,7 @@ async function installPluginPackage(options) {
196831
196950
  };
196832
196951
  }
196833
196952
  async function installGeminiExtensionPackage(options = {}) {
196834
- const home = options.homeDir ?? homedir27();
196953
+ const home = options.homeDir ?? homedir28();
196835
196954
  const packageRoot = findPackageRoot();
196836
196955
  const source = path32.join(packageRoot, "plugins", "gemini", "memorix");
196837
196956
  if (!existsSync17(source)) {
@@ -196851,7 +196970,7 @@ async function installAntigravityPluginPackage(options = {}) {
196851
196970
  if (!existsSync17(source)) {
196852
196971
  throw new Error(`Antigravity plugin template not found: ${source}`);
196853
196972
  }
196854
- const pluginPath = options.global === false ? path32.join(options.projectRoot ?? process.cwd(), ".agents", "plugins", "memorix") : path32.join(options.homeDir ?? homedir27(), ".gemini", "config", "plugins", "memorix");
196973
+ const pluginPath = options.global === false ? path32.join(options.projectRoot ?? process.cwd(), ".agents", "plugins", "memorix") : path32.join(options.homeDir ?? homedir28(), ".gemini", "config", "plugins", "memorix");
196855
196974
  await copyDir(source, pluginPath);
196856
196975
  if (options.includeHooks === false) {
196857
196976
  await rm2(path32.join(pluginPath, "hooks.json"), { force: true });
@@ -196869,7 +196988,7 @@ async function installPiPackage(options = {}) {
196869
196988
  if (!existsSync17(source)) {
196870
196989
  throw new Error(`Pi package template not found: ${source}`);
196871
196990
  }
196872
- const defaultRoot = options.global ? path32.join(options.homeDir ?? homedir27(), ".pi", "agent", "packages") : path32.join(process.cwd(), ".pi", "packages");
196991
+ const defaultRoot = options.global ? path32.join(options.homeDir ?? homedir28(), ".pi", "agent", "packages") : path32.join(process.cwd(), ".pi", "packages");
196873
196992
  const packagePath = options.packageDir ?? path32.join(defaultRoot, "memorix");
196874
196993
  await copyDir(source, packagePath);
196875
196994
  await writeOfficialSkills(path32.join(packagePath, "skills"));
@@ -196881,7 +197000,7 @@ async function installPiPackage(options = {}) {
196881
197000
  };
196882
197001
  }
196883
197002
  async function installOpenClawBundlePackage(options = {}) {
196884
- const home = options.homeDir ?? homedir27();
197003
+ const home = options.homeDir ?? homedir28();
196885
197004
  const packageRoot = findPackageRoot();
196886
197005
  const source = path32.join(packageRoot, "plugins", "openclaw", "memorix");
196887
197006
  if (!existsSync17(source)) {
@@ -196930,7 +197049,7 @@ async function installOmpPackage(options = {}) {
196930
197049
  if (!existsSync17(source)) {
196931
197050
  throw new Error(`Oh-my-Pi package template not found: ${source}`);
196932
197051
  }
196933
- const defaultRoot = options.global ? path32.join(options.homeDir ?? homedir27(), ".omp", "agent", "packages") : path32.join(process.cwd(), ".omp", "packages");
197052
+ const defaultRoot = options.global ? path32.join(options.homeDir ?? homedir28(), ".omp", "agent", "packages") : path32.join(process.cwd(), ".omp", "packages");
196934
197053
  const packagePath = options.packageDir ?? path32.join(defaultRoot, "memorix");
196935
197054
  await copyDir(source, packagePath);
196936
197055
  await writeOfficialSkills(path32.join(packagePath, "skills"));
@@ -197204,7 +197323,7 @@ ${result.stdout || ""}`.toLowerCase();
197204
197323
  }
197205
197324
  async function installAgentSetup(agent, plan, global2) {
197206
197325
  const { installAgentGuidance: installAgentGuidance2, installHooks: installHooks2 } = await Promise.resolve().then(() => (init_installers(), installers_exports));
197207
- const targetRoot = global2 ? homedir27() : process.cwd();
197326
+ const targetRoot = global2 ? homedir28() : process.cwd();
197208
197327
  const hasPluginPackage = plan.actions.includes("plugin-package") && PLUGIN_PACKAGE_AGENTS.has(agent);
197209
197328
  const hasExtensionPackage = plan.actions.includes("extension-package") && agent === "gemini-cli";
197210
197329
  const hasPiPackage = plan.actions.includes("pi-package") && agent === "pi";
@@ -197213,7 +197332,8 @@ async function installAgentSetup(agent, plan, global2) {
197213
197332
  const hasHermesPlugin = plan.actions.includes("hermes-plugin") && agent === "hermes";
197214
197333
  const hasOmpPackage = plan.actions.includes("omp-package") && agent === "omp";
197215
197334
  const hasCodeBuddyPlugin = plan.actions.includes("codebuddy-plugin") && agent === "codebuddy";
197216
- const wantsMcpConfig = plan.mcp !== "none" && agent !== "pi" && agent !== "grok" && !(agent === "codex" && !global2);
197335
+ const wantsMcpConfig = plan.mcp !== "none" && agent !== "pi" && !(agent === "codex" && !global2) && !(agent === "grok" && plan.mcp !== "http");
197336
+ const bundledMcpRoots = [];
197217
197337
  if (PLUGIN_PACKAGE_AGENTS.has(agent) && plan.includePlugin && !global2) {
197218
197338
  v3.info(`${agent}: project setup leaves user-level plugins untouched. Run \`memorix setup --agent ${agent} --global\` to install its plugin, skills, and lifecycle hooks.`);
197219
197339
  }
@@ -197222,6 +197342,7 @@ async function installAgentSetup(agent, plan, global2) {
197222
197342
  agent,
197223
197343
  includeHooks: plan.includeHooks
197224
197344
  });
197345
+ bundledMcpRoots.push(result.pluginPath);
197225
197346
  v3.success(`${agent}: plugin package -> ${result.pluginPath}`);
197226
197347
  if (result.marketplacePath) {
197227
197348
  v3.info(`${agent}: marketplace -> ${result.marketplacePath}`);
@@ -197252,6 +197373,7 @@ async function installAgentSetup(agent, plan, global2) {
197252
197373
  }
197253
197374
  if (hasCodeBuddyPlugin) {
197254
197375
  const result = await installCodeBuddyPluginPackage({ includeHooks: plan.includeHooks });
197376
+ bundledMcpRoots.push(result.pluginPath);
197255
197377
  v3.success(`${agent}: plugin -> ${result.pluginPath}`);
197256
197378
  v3.info(`${agent}: marketplace -> ${result.marketplacePath}`);
197257
197379
  const install = tryInstallCodeBuddyPlugin(result.marketplaceRoot);
@@ -197262,6 +197384,7 @@ async function installAgentSetup(agent, plan, global2) {
197262
197384
  }
197263
197385
  if (hasExtensionPackage) {
197264
197386
  const result = await installGeminiExtensionPackage();
197387
+ bundledMcpRoots.push(result.extensionPath);
197265
197388
  v3.success(`${agent}: extension package -> ${result.extensionPath}`);
197266
197389
  v3.info(result.installHint);
197267
197390
  }
@@ -197275,6 +197398,7 @@ async function installAgentSetup(agent, plan, global2) {
197275
197398
  }
197276
197399
  if (hasOpenClawBundle) {
197277
197400
  const result = await installOpenClawBundlePackage({ includeHooks: plan.includeHooks });
197401
+ bundledMcpRoots.push(result.bundlePath);
197278
197402
  v3.success(`${agent}: compatible bundle -> ${result.bundlePath}`);
197279
197403
  if (plan.includeHooks) {
197280
197404
  const install = tryInstallOpenClawBundle(result.bundlePath);
@@ -197287,6 +197411,7 @@ async function installAgentSetup(agent, plan, global2) {
197287
197411
  }
197288
197412
  if (hasAntigravityPlugin) {
197289
197413
  const result = await installAntigravityPluginPackage({ global: global2, projectRoot: targetRoot, includeHooks: plan.includeHooks });
197414
+ bundledMcpRoots.push(result.pluginPath);
197290
197415
  v3.success(`${agent}: plugin -> ${result.pluginPath}`);
197291
197416
  if (!plan.includeHooks) {
197292
197417
  v3.info(`${agent}: hooks.json skipped because --noHooks was selected`);
@@ -197317,9 +197442,17 @@ async function installAgentSetup(agent, plan, global2) {
197317
197442
  v3.info(result.installHint);
197318
197443
  }
197319
197444
  if (wantsMcpConfig) {
197320
- if ((hasPluginPackage || hasCodeBuddyPlugin || hasExtensionPackage || hasOpenClawBundle || hasAntigravityPlugin) && plan.mcp === "stdio") {
197445
+ const mcpOwnedByPackage = hasPluginPackage || hasCodeBuddyPlugin || hasExtensionPackage || hasOpenClawBundle || hasAntigravityPlugin;
197446
+ if (mcpOwnedByPackage && plan.mcp === "stdio") {
197321
197447
  const packageLabel = hasExtensionPackage ? "extension package" : hasCodeBuddyPlugin ? "CodeBuddy plugin" : hasOpenClawBundle ? "OpenClaw bundle" : hasAntigravityPlugin ? "Antigravity plugin" : "plugin package";
197322
197448
  v3.info(`${agent}: stdio MCP is bundled in the ${packageLabel}`);
197449
+ } else if (mcpOwnedByPackage && plan.mcp === "http") {
197450
+ for (const root of bundledMcpRoots) {
197451
+ const rewritten = await rewriteBundledMemorixMcpToHttp(root);
197452
+ for (const filePath of rewritten) {
197453
+ v3.success(`${agent}: plugin MCP -> HTTP ${filePath}`);
197454
+ }
197455
+ }
197323
197456
  } else {
197324
197457
  const mcp = plan.mcp === "http" ? "http" : "stdio";
197325
197458
  const result = await installMcpConfig({ agent, projectRoot: targetRoot, global: global2, mcp });
@@ -197373,7 +197506,11 @@ async function installAgentSetup(agent, plan, global2) {
197373
197506
  if (agent === "pi") {
197374
197507
  v3.info("pi: Pi has no MCP config lane in the current CLI; use the installed package extension and skill.");
197375
197508
  } else if (agent === "grok") {
197376
- v3.info("grok: lifecycle hooks write ~/.grok/hooks/memorix.json. MCP stays host-owned (do not write config.toml).");
197509
+ if (plan.mcp === "http") {
197510
+ v3.info("grok: wrote [mcp_servers.memorix] HTTP URL in Grok config.toml. Start the control plane with `memorix background start`.");
197511
+ } else {
197512
+ v3.info("grok: lifecycle hooks write ~/.grok/hooks/memorix.json. MCP stays host-owned unless you pass `--mcp http`.");
197513
+ }
197377
197514
  if (!global2) v3.info("grok: project hooks require `/hooks-trust` or launching Grok with `--trust`.");
197378
197515
  } else if (plan.mcp === "stdio") {
197379
197516
  v3.info(`${agent}: MCP server command is \`memorix serve\``);
@@ -197400,7 +197537,7 @@ function printSetupIntegrationRows() {
197400
197537
  ].join("\n");
197401
197538
  ye2(table, "Memorix integrations");
197402
197539
  }
197403
- var import_yaml5, PLUGIN_PACKAGE_AGENTS, EXTENSION_PACKAGE_AGENTS, PI_PACKAGE_AGENTS, OPENCLAW_BUNDLE_AGENTS, ANTIGRAVITY_PLUGIN_AGENTS, HERMES_PLUGIN_AGENTS, OMP_PACKAGE_AGENTS, CODEBUDDY_PLUGIN_AGENTS, PACKAGE_OWNED_INTEGRATION_AGENTS, SUPPORTED_SETUP_AGENTS, getSetupIntegrationRows2, setup_default;
197540
+ var import_yaml5, PLUGIN_PACKAGE_AGENTS, EXTENSION_PACKAGE_AGENTS, PI_PACKAGE_AGENTS, OPENCLAW_BUNDLE_AGENTS, ANTIGRAVITY_PLUGIN_AGENTS, HERMES_PLUGIN_AGENTS, OMP_PACKAGE_AGENTS, CODEBUDDY_PLUGIN_AGENTS, PACKAGE_OWNED_INTEGRATION_AGENTS, SUPPORTED_SETUP_AGENTS, getSetupIntegrationRows2, MEMORIX_HTTP_MCP_URL, setup_default;
197404
197541
  var init_setup = __esm({
197405
197542
  "src/cli/commands/setup.ts"() {
197406
197543
  "use strict";
@@ -197424,6 +197561,7 @@ var init_setup = __esm({
197424
197561
  init_dsh();
197425
197562
  init_workbuddy();
197426
197563
  init_codebuddy();
197564
+ init_grok();
197427
197565
  init_registry2();
197428
197566
  init_official_skills();
197429
197567
  init_version();
@@ -197457,6 +197595,7 @@ var init_setup = __esm({
197457
197595
  "grok"
197458
197596
  ];
197459
197597
  getSetupIntegrationRows2 = getSetupIntegrationRows;
197598
+ MEMORIX_HTTP_MCP_URL = "http://localhost:3211/mcp";
197460
197599
  setup_default = defineCommand({
197461
197600
  meta: {
197462
197601
  name: "setup",
@@ -197532,7 +197671,7 @@ var init_setup = __esm({
197532
197671
  { value: "codex", label: "Codex", hint: "plugin package + marketplace + MCP" },
197533
197672
  { value: "copilot", label: "GitHub Copilot CLI", hint: "plugin package + hooks + MCP" },
197534
197673
  { value: "cursor", label: "Cursor", hint: "MCP + rules + skills" },
197535
- { value: "grok", label: "Grok Build", hint: "hooks file; MCP stays host-owned" },
197674
+ { value: "grok", label: "Grok Build", hint: "hooks file; MCP host-owned unless --mcp http" },
197536
197675
  { value: "gemini-cli", label: "Gemini CLI", hint: "extension + MCP" },
197537
197676
  { value: "openclaw", label: "OpenClaw", hint: "bundle + hook pack + skills + MCP" },
197538
197677
  { value: "hermes", label: "Hermes Agent", hint: "plugin + hooks + commands + skills + MCP" },
@@ -197598,7 +197737,7 @@ var config_path_exports = {};
197598
197737
  __export(config_path_exports, {
197599
197738
  default: () => config_path_default
197600
197739
  });
197601
- import { homedir as homedir28 } from "os";
197740
+ import { homedir as homedir29 } from "os";
197602
197741
  var config_path_default;
197603
197742
  var init_config_path = __esm({
197604
197743
  "src/cli/commands/config-path.ts"() {
@@ -197616,7 +197755,7 @@ var init_config_path = __esm({
197616
197755
  run: async () => {
197617
197756
  const project = detectProject(process.cwd());
197618
197757
  ye2([
197619
- `Global: ${getGlobalConfigTomlPath(homedir28())}`,
197758
+ `Global: ${getGlobalConfigTomlPath(homedir29())}`,
197620
197759
  `Project: ${project ? getProjectConfigTomlPath(project.rootPath) : "not inside a git project"}`
197621
197760
  ].join("\n"), "Memorix config");
197622
197761
  }
@@ -197692,7 +197831,7 @@ __export(config_migrate_exports, {
197692
197831
  serializeResolvedConfigToToml: () => serializeResolvedConfigToToml
197693
197832
  });
197694
197833
  import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
197695
- import { homedir as homedir29 } from "os";
197834
+ import { homedir as homedir30 } from "os";
197696
197835
  import { dirname as dirname4 } from "path";
197697
197836
  function serializeResolvedConfigToToml(config2, options = {}) {
197698
197837
  const includeSecrets = options.includeSecrets === true;
@@ -197791,7 +197930,7 @@ var init_config_migrate = __esm({
197791
197930
  v3.error("Not inside a git project. Use `memorix config migrate --global` for global config.");
197792
197931
  return;
197793
197932
  }
197794
- const target = isGlobal ? getGlobalConfigTomlPath(homedir29()) : getProjectConfigTomlPath(project.rootPath);
197933
+ const target = isGlobal ? getGlobalConfigTomlPath(homedir30()) : getProjectConfigTomlPath(project.rootPath);
197795
197934
  const resolved = getResolvedConfig({ projectRoot: project?.rootPath ?? null });
197796
197935
  const content = serializeResolvedConfigToToml(resolved, { includeSecrets: isGlobal });
197797
197936
  mkdirSync4(dirname4(target), { recursive: true });
@@ -197803,8 +197942,8 @@ var init_config_migrate = __esm({
197803
197942
  });
197804
197943
 
197805
197944
  // src/cli/commands/integrate-shared.ts
197806
- import { homedir as homedir30 } from "os";
197807
- function getIntegrationTargetRoot(useGlobalDefaults, cwd, homeDir = homedir30()) {
197945
+ import { homedir as homedir31 } from "os";
197946
+ function getIntegrationTargetRoot(useGlobalDefaults, cwd, homeDir = homedir31()) {
197808
197947
  return useGlobalDefaults ? homeDir : cwd;
197809
197948
  }
197810
197949
  function getIntegrationScopeLabel(useGlobalDefaults) {
@@ -199066,7 +199205,7 @@ __export(external_provider_exports, {
199066
199205
  });
199067
199206
  import { spawn as spawn5 } from "child_process";
199068
199207
  import { existsSync as existsSync19, readFileSync as readFileSync12 } from "fs";
199069
- import { delimiter, dirname as dirname5, extname, isAbsolute as isAbsolute4, join as join24, relative as relative4, resolve as resolve6, sep as sep2 } from "path";
199208
+ import { delimiter, dirname as dirname5, extname, isAbsolute as isAbsolute4, join as join25, relative as relative4, resolve as resolve6, sep as sep2 } from "path";
199070
199209
  function boundedTimeout(value) {
199071
199210
  if (!Number.isFinite(value)) return DEFAULT_EXTERNAL_CODEGRAPH_TIMEOUT_MS;
199072
199211
  return Math.max(100, Math.min(Math.floor(value), MAX_EXTERNAL_CODEGRAPH_TIMEOUT_MS));
@@ -199095,10 +199234,10 @@ function resolveCommandCandidates(command) {
199095
199234
  }
199096
199235
  const names = extname(requested) ? [requested] : process.platform === "win32" ? [requested + ".exe", requested + ".cmd", requested + ".js", requested] : [requested];
199097
199236
  const directories = [
199098
- ...process.platform === "win32" && process.env.LOCALAPPDATA ? [join24(process.env.LOCALAPPDATA, "codegraph", "current", "bin")] : [],
199237
+ ...process.platform === "win32" && process.env.LOCALAPPDATA ? [join25(process.env.LOCALAPPDATA, "codegraph", "current", "bin")] : [],
199099
199238
  ...(process.env.PATH ?? "").split(delimiter).filter(Boolean)
199100
199239
  ];
199101
- return [...new Set(directories.flatMap((directory) => names.map((name) => join24(directory, name))))];
199240
+ return [...new Set(directories.flatMap((directory) => names.map((name) => join25(directory, name))))];
199102
199241
  }
199103
199242
  function resolveLaunch(command) {
199104
199243
  for (const candidate2 of resolveCommandCandidates(command)) {
@@ -199106,8 +199245,8 @@ function resolveLaunch(command) {
199106
199245
  const extension3 = extname(candidate2).toLowerCase();
199107
199246
  if (extension3 === ".cmd") {
199108
199247
  const bundleRoot = resolve6(dirname5(candidate2), "..");
199109
- const node = join24(bundleRoot, "node.exe");
199110
- const entry = join24(bundleRoot, "lib", "dist", "bin", "codegraph.js");
199248
+ const node = join25(bundleRoot, "node.exe");
199249
+ const entry = join25(bundleRoot, "lib", "dist", "bin", "codegraph.js");
199111
199250
  if (existsSync19(node) && existsSync19(entry)) {
199112
199251
  return { executable: node, prefixArgs: ["--liftoff-only", entry] };
199113
199252
  }
@@ -199265,7 +199404,7 @@ async function inspectExternalCodeGraph(input) {
199265
199404
  const health2 = { state: "disabled", reason: "External CodeGraph is disabled by configuration." };
199266
199405
  return { health: health2, quality: providerQuality({ mode, health: health2 }) };
199267
199406
  }
199268
- if (!existsSync19(join24(input.projectRoot, ".codegraph"))) {
199407
+ if (!existsSync19(join25(input.projectRoot, ".codegraph"))) {
199269
199408
  const health2 = { state: "not-detected", reason: "No local .codegraph index is present for this project." };
199270
199409
  return { health: health2, quality: providerQuality({ mode, health: health2 }) };
199271
199410
  }
@@ -199300,7 +199439,7 @@ async function runExternalCodeGraphLifecycle(input) {
199300
199439
  quality: providerQuality({ mode, health })
199301
199440
  };
199302
199441
  }
199303
- const indexPath = join24(input.projectRoot, ".codegraph");
199442
+ const indexPath = join25(input.projectRoot, ".codegraph");
199304
199443
  if (input.action === "sync" && !existsSync19(indexPath)) {
199305
199444
  const health = { state: "not-detected", reason: "No local .codegraph index is present for this project." };
199306
199445
  return {
@@ -200126,6 +200265,7 @@ var init_project_context = __esm({
200126
200265
  // src/store/graph-store.ts
200127
200266
  var graph_store_exports = {};
200128
200267
  __export(graph_store_exports, {
200268
+ DEFAULT_GRAPH_PROJECT_ID: () => DEFAULT_GRAPH_PROJECT_ID,
200129
200269
  GraphSqliteStore: () => GraphSqliteStore,
200130
200270
  getGraphStore: () => getGraphStore,
200131
200271
  initGraphStore: () => initGraphStore,
@@ -200155,37 +200295,106 @@ function entityToRow(entity) {
200155
200295
  observations: JSON.stringify(entity.observations ?? [])
200156
200296
  };
200157
200297
  }
200158
- async function initGraphStore(dataDir) {
200159
- if (_graphStore && _graphDataDir === dataDir) return _graphStore;
200160
- _graphStore = new GraphSqliteStore();
200161
- await _graphStore.init(dataDir);
200162
- _graphDataDir = dataDir;
200163
- return _graphStore;
200298
+ function ensureScopedGraphSchema(db2) {
200299
+ const entityColumns = db2.prepare("PRAGMA table_info(graph_entities)").all();
200300
+ if (!entityColumns.some((column) => column.name === "projectId")) {
200301
+ const migrate = db2.transaction(() => {
200302
+ db2.exec("ALTER TABLE graph_entities RENAME TO graph_entities_legacy_unscoped");
200303
+ db2.exec(CREATE_SCOPED_GRAPH_ENTITIES);
200304
+ db2.exec(`INSERT INTO graph_entities (projectId, name, entityType, observations)
200305
+ SELECT '${LEGACY_GRAPH_PROJECT_ID}', name, entityType, observations
200306
+ FROM graph_entities_legacy_unscoped`);
200307
+ db2.exec("DROP TABLE graph_entities_legacy_unscoped");
200308
+ });
200309
+ migrate();
200310
+ } else {
200311
+ db2.exec(CREATE_SCOPED_GRAPH_ENTITIES);
200312
+ }
200313
+ const relationColumns = db2.prepare("PRAGMA table_info(graph_relations)").all();
200314
+ if (!relationColumns.some((column) => column.name === "projectId")) {
200315
+ const migrate = db2.transaction(() => {
200316
+ db2.exec("ALTER TABLE graph_relations RENAME TO graph_relations_legacy_unscoped");
200317
+ db2.exec(CREATE_SCOPED_GRAPH_RELATIONS);
200318
+ db2.exec(`INSERT INTO graph_relations (projectId, from_entity, to_entity, relationType)
200319
+ SELECT '${LEGACY_GRAPH_PROJECT_ID}', from_entity, to_entity, relationType
200320
+ FROM graph_relations_legacy_unscoped`);
200321
+ db2.exec("DROP TABLE graph_relations_legacy_unscoped");
200322
+ });
200323
+ migrate();
200324
+ } else {
200325
+ db2.exec(CREATE_SCOPED_GRAPH_RELATIONS);
200326
+ }
200327
+ }
200328
+ function graphStoreKey(dataDir, projectId) {
200329
+ return `${path34.resolve(dataDir)}\0${projectId}`;
200164
200330
  }
200165
- function getGraphStore() {
200166
- if (!_graphStore) throw new Error("[memorix] GraphStore not initialized \u2014 call initGraphStore() first");
200167
- return _graphStore;
200331
+ async function initGraphStore(dataDir, projectId = DEFAULT_GRAPH_PROJECT_ID) {
200332
+ const key = graphStoreKey(dataDir, projectId);
200333
+ const existing = _graphStores.get(key);
200334
+ if (existing) {
200335
+ _graphStores.delete(key);
200336
+ _graphStores.set(key, existing);
200337
+ _activeGraphKey = key;
200338
+ return existing;
200339
+ }
200340
+ const store2 = new GraphSqliteStore();
200341
+ await store2.init(dataDir, projectId);
200342
+ _graphStores.set(key, store2);
200343
+ while (_graphStores.size > MAX_GRAPH_STORE_CACHE) {
200344
+ const oldest = _graphStores.keys().next().value;
200345
+ if (!oldest) break;
200346
+ _graphStores.delete(oldest);
200347
+ }
200348
+ _activeGraphKey = key;
200349
+ return store2;
200350
+ }
200351
+ function getGraphStore(dataDir, projectId) {
200352
+ const key = dataDir && projectId ? graphStoreKey(dataDir, projectId) : _activeGraphKey;
200353
+ const store2 = key ? _graphStores.get(key) : void 0;
200354
+ if (!store2) throw new Error("[memorix] GraphStore not initialized \u2014 call initGraphStore() first");
200355
+ return store2;
200168
200356
  }
200169
200357
  function resetGraphStore() {
200170
- if (_graphStore) {
200358
+ for (const store2 of _graphStores.values()) {
200171
200359
  try {
200172
- _graphStore.close();
200360
+ store2.close();
200173
200361
  } catch {
200174
200362
  }
200175
200363
  }
200176
- _graphStore = null;
200177
- _graphDataDir = null;
200364
+ _graphStores.clear();
200365
+ _activeGraphKey = null;
200178
200366
  }
200179
- var GraphSqliteStore, _graphStore, _graphDataDir;
200367
+ var DEFAULT_GRAPH_PROJECT_ID, LEGACY_GRAPH_PROJECT_ID, MAX_GRAPH_STORE_CACHE, CREATE_SCOPED_GRAPH_ENTITIES, CREATE_SCOPED_GRAPH_RELATIONS, GraphSqliteStore, _graphStores, _activeGraphKey;
200180
200368
  var init_graph_store = __esm({
200181
200369
  "src/store/graph-store.ts"() {
200182
200370
  "use strict";
200183
200371
  init_esm_shims();
200184
200372
  init_sqlite_db();
200185
200373
  init_persistence();
200374
+ DEFAULT_GRAPH_PROJECT_ID = "__default__";
200375
+ LEGACY_GRAPH_PROJECT_ID = "__legacy__";
200376
+ MAX_GRAPH_STORE_CACHE = 32;
200377
+ CREATE_SCOPED_GRAPH_ENTITIES = `
200378
+ CREATE TABLE IF NOT EXISTS graph_entities (
200379
+ projectId TEXT NOT NULL,
200380
+ name TEXT NOT NULL,
200381
+ entityType TEXT NOT NULL DEFAULT '',
200382
+ observations TEXT NOT NULL DEFAULT '[]',
200383
+ PRIMARY KEY (projectId, name)
200384
+ );`;
200385
+ CREATE_SCOPED_GRAPH_RELATIONS = `
200386
+ CREATE TABLE IF NOT EXISTS graph_relations (
200387
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
200388
+ projectId TEXT NOT NULL,
200389
+ from_entity TEXT NOT NULL,
200390
+ to_entity TEXT NOT NULL,
200391
+ relationType TEXT NOT NULL DEFAULT '',
200392
+ UNIQUE(projectId, from_entity, to_entity, relationType)
200393
+ );`;
200186
200394
  GraphSqliteStore = class {
200187
200395
  db = null;
200188
200396
  dataDir = "";
200397
+ projectId = DEFAULT_GRAPH_PROJECT_ID;
200189
200398
  stmtInsertEntity = null;
200190
200399
  stmtUpdateEntityObs = null;
200191
200400
  stmtDeleteEntity = null;
@@ -200195,45 +200404,89 @@ var init_graph_store = __esm({
200195
200404
  stmtDeleteRelation = null;
200196
200405
  stmtDeleteRelationsByEntity = null;
200197
200406
  stmtSelectAllRelations = null;
200198
- async init(dataDir) {
200407
+ async init(dataDir, projectId = DEFAULT_GRAPH_PROJECT_ID) {
200199
200408
  this.dataDir = dataDir;
200409
+ this.projectId = projectId;
200200
200410
  this.db = getDatabase(dataDir);
200411
+ ensureScopedGraphSchema(this.db);
200201
200412
  this.stmtInsertEntity = this.db.prepare(
200202
- `INSERT OR REPLACE INTO graph_entities (name, entityType, observations) VALUES (@name, @entityType, @observations)`
200413
+ `INSERT OR REPLACE INTO graph_entities (projectId, name, entityType, observations) VALUES (@projectId, @name, @entityType, @observations)`
200203
200414
  );
200204
200415
  this.stmtUpdateEntityObs = this.db.prepare(
200205
- `UPDATE graph_entities SET observations = @observations WHERE name = @name`
200416
+ `UPDATE graph_entities SET observations = @observations WHERE projectId = @projectId AND name = @name`
200206
200417
  );
200207
- this.stmtDeleteEntity = this.db.prepare(`DELETE FROM graph_entities WHERE name = ?`);
200208
- this.stmtSelectAllEntities = this.db.prepare(`SELECT * FROM graph_entities`);
200209
- this.stmtSelectEntityByName = this.db.prepare(`SELECT * FROM graph_entities WHERE name = ?`);
200418
+ this.stmtDeleteEntity = this.db.prepare(`DELETE FROM graph_entities WHERE projectId = ? AND name = ?`);
200419
+ this.stmtSelectAllEntities = this.db.prepare(`SELECT * FROM graph_entities WHERE projectId = ? ORDER BY name`);
200420
+ this.stmtSelectEntityByName = this.db.prepare(`SELECT * FROM graph_entities WHERE projectId = ? AND name = ?`);
200210
200421
  this.stmtInsertRelation = this.db.prepare(
200211
- `INSERT OR IGNORE INTO graph_relations (from_entity, to_entity, relationType) VALUES (@from, @to, @relationType)`
200422
+ `INSERT OR IGNORE INTO graph_relations (projectId, from_entity, to_entity, relationType) VALUES (@projectId, @from, @to, @relationType)`
200212
200423
  );
200213
200424
  this.stmtDeleteRelation = this.db.prepare(
200214
- `DELETE FROM graph_relations WHERE from_entity = ? AND to_entity = ? AND relationType = ?`
200425
+ `DELETE FROM graph_relations WHERE projectId = ? AND from_entity = ? AND to_entity = ? AND relationType = ?`
200215
200426
  );
200216
200427
  this.stmtDeleteRelationsByEntity = this.db.prepare(
200217
- `DELETE FROM graph_relations WHERE from_entity = ? OR to_entity = ?`
200428
+ `DELETE FROM graph_relations WHERE projectId = ? AND (from_entity = ? OR to_entity = ?)`
200218
200429
  );
200219
- this.stmtSelectAllRelations = this.db.prepare(`SELECT * FROM graph_relations`);
200430
+ this.stmtSelectAllRelations = this.db.prepare(`SELECT * FROM graph_relations WHERE projectId = ? ORDER BY id`);
200220
200431
  await this.migrateFromJsonlIfNeeded();
200221
200432
  }
200222
200433
  async migrateFromJsonlIfNeeded() {
200223
- const count3 = this.db.prepare(`SELECT COUNT(*) AS cnt FROM graph_entities`).get();
200434
+ const count3 = this.db.prepare(`SELECT COUNT(*) AS cnt FROM graph_entities WHERE projectId = ?`).get(this.projectId);
200435
+ const totalCount = this.db.prepare(`SELECT COUNT(*) AS cnt FROM graph_entities`).get();
200224
200436
  if (count3.cnt > 0) return;
200437
+ if (totalCount.cnt > 0) return;
200225
200438
  const jsonlPath = path34.join(this.dataDir, "graph.jsonl");
200226
200439
  if (!fs20.existsSync(jsonlPath)) return;
200227
200440
  try {
200228
200441
  const data = await loadGraphJsonl(this.dataDir);
200229
200442
  if (data.entities.length === 0 && data.relations.length === 0) return;
200230
200443
  console.error(`[memorix] Migrating graph from JSONL to SQLite (${data.entities.length} entities, ${data.relations.length} relations)...`);
200444
+ const observations2 = this.db.prepare("SELECT id, projectId FROM observations").all();
200445
+ const projectByObservationId = new Map(observations2.map((row) => [row.id, row.projectId]));
200446
+ const projectsByEntity = /* @__PURE__ */ new Map();
200447
+ for (const entity of data.entities) {
200448
+ const projects = /* @__PURE__ */ new Set();
200449
+ for (const content of entity.observations ?? []) {
200450
+ const match2 = /^\[#(\d+)\]/.exec(content);
200451
+ const projectId = match2 ? projectByObservationId.get(Number(match2[1])) : void 0;
200452
+ if (projectId) projects.add(projectId);
200453
+ }
200454
+ projectsByEntity.set(entity.name, projects);
200455
+ }
200456
+ let changed = true;
200457
+ while (changed) {
200458
+ changed = false;
200459
+ for (const relation of data.relations) {
200460
+ const from = projectsByEntity.get(relation.from) ?? /* @__PURE__ */ new Set();
200461
+ const to2 = projectsByEntity.get(relation.to) ?? /* @__PURE__ */ new Set();
200462
+ if (from.size > 0 && to2.size === 0) {
200463
+ projectsByEntity.set(relation.to, new Set(from));
200464
+ changed = true;
200465
+ } else if (to2.size > 0 && from.size === 0) {
200466
+ projectsByEntity.set(relation.from, new Set(to2));
200467
+ changed = true;
200468
+ }
200469
+ }
200470
+ }
200471
+ const entityRows = data.entities.flatMap((entity) => {
200472
+ const projects = projectsByEntity.get(entity.name) ?? /* @__PURE__ */ new Set();
200473
+ const owners = projects.size > 0 ? [...projects] : [this.projectId];
200474
+ return owners.map((projectId) => ({ ...entityToRow(entity), projectId }));
200475
+ });
200476
+ const projectSet = new Set(entityRows.map((row) => String(row.projectId)));
200477
+ const relationRows = data.relations.flatMap((relation) => {
200478
+ const from = projectsByEntity.get(relation.from) ?? /* @__PURE__ */ new Set();
200479
+ const to2 = projectsByEntity.get(relation.to) ?? /* @__PURE__ */ new Set();
200480
+ const shared = [...from].filter((projectId) => to2.has(projectId));
200481
+ const owners = shared.length > 0 ? shared : from.size === 0 && to2.size === 0 ? [this.projectId] : [];
200482
+ return owners.filter((projectId) => projectSet.has(projectId)).map((projectId) => ({ projectId, from: relation.from, to: relation.to, relationType: relation.relationType }));
200483
+ });
200231
200484
  const insertAll = this.db.transaction(() => {
200232
- for (const entity of data.entities) {
200233
- this.stmtInsertEntity.run(entityToRow(entity));
200485
+ for (const entity of entityRows) {
200486
+ this.stmtInsertEntity.run(entity);
200234
200487
  }
200235
- for (const rel of data.relations) {
200236
- this.stmtInsertRelation.run({ from: rel.from, to: rel.to, relationType: rel.relationType });
200488
+ for (const relation of relationRows) {
200489
+ this.stmtInsertRelation.run(relation);
200237
200490
  }
200238
200491
  });
200239
200492
  insertAll();
@@ -200243,10 +200496,10 @@ var init_graph_store = __esm({
200243
200496
  }
200244
200497
  }
200245
200498
  loadEntities() {
200246
- return this.stmtSelectAllEntities.all().map(rowToEntity);
200499
+ return this.stmtSelectAllEntities.all(this.projectId).map(rowToEntity);
200247
200500
  }
200248
200501
  loadRelations() {
200249
- return this.stmtSelectAllRelations.all().map((row) => ({
200502
+ return this.stmtSelectAllRelations.all(this.projectId).map((row) => ({
200250
200503
  from: row.from_entity,
200251
200504
  to: row.to_entity,
200252
200505
  relationType: row.relationType
@@ -200255,7 +200508,7 @@ var init_graph_store = __esm({
200255
200508
  insertEntities(entities) {
200256
200509
  const insertAll = this.db.transaction(() => {
200257
200510
  for (const entity of entities) {
200258
- this.stmtInsertEntity.run(entityToRow(entity));
200511
+ this.stmtInsertEntity.run({ ...entityToRow(entity), projectId: this.projectId });
200259
200512
  }
200260
200513
  });
200261
200514
  insertAll();
@@ -200263,7 +200516,7 @@ var init_graph_store = __esm({
200263
200516
  insertRelations(relations) {
200264
200517
  const insertAll = this.db.transaction(() => {
200265
200518
  for (const rel of relations) {
200266
- this.stmtInsertRelation.run({ from: rel.from, to: rel.to, relationType: rel.relationType });
200519
+ this.stmtInsertRelation.run({ projectId: this.projectId, from: rel.from, to: rel.to, relationType: rel.relationType });
200267
200520
  }
200268
200521
  });
200269
200522
  insertAll();
@@ -200271,8 +200524,8 @@ var init_graph_store = __esm({
200271
200524
  deleteEntities(names) {
200272
200525
  const deleteAll = this.db.transaction(() => {
200273
200526
  for (const name of names) {
200274
- this.stmtDeleteRelationsByEntity.run(name, name);
200275
- this.stmtDeleteEntity.run(name);
200527
+ this.stmtDeleteRelationsByEntity.run(this.projectId, name, name);
200528
+ this.stmtDeleteEntity.run(this.projectId, name);
200276
200529
  }
200277
200530
  });
200278
200531
  deleteAll();
@@ -200280,7 +200533,7 @@ var init_graph_store = __esm({
200280
200533
  deleteRelations(relations) {
200281
200534
  const deleteAll = this.db.transaction(() => {
200282
200535
  for (const rel of relations) {
200283
- this.stmtDeleteRelation.run(rel.from, rel.to, rel.relationType);
200536
+ this.stmtDeleteRelation.run(this.projectId, rel.from, rel.to, rel.relationType);
200284
200537
  }
200285
200538
  });
200286
200539
  deleteAll();
@@ -200288,13 +200541,13 @@ var init_graph_store = __esm({
200288
200541
  addObservations(updates) {
200289
200542
  const updateAll = this.db.transaction(() => {
200290
200543
  for (const u5 of updates) {
200291
- const row = this.stmtSelectEntityByName.get(u5.entityName);
200544
+ const row = this.stmtSelectEntityByName.get(this.projectId, u5.entityName);
200292
200545
  if (!row) continue;
200293
200546
  const existing = safeJsonParse3(row.observations, []);
200294
200547
  const newObs = u5.contents.filter((c6) => !existing.includes(c6));
200295
200548
  if (newObs.length > 0) {
200296
200549
  existing.push(...newObs);
200297
- this.stmtUpdateEntityObs.run({ name: u5.entityName, observations: JSON.stringify(existing) });
200550
+ this.stmtUpdateEntityObs.run({ projectId: this.projectId, name: u5.entityName, observations: JSON.stringify(existing) });
200298
200551
  }
200299
200552
  }
200300
200553
  });
@@ -200303,11 +200556,11 @@ var init_graph_store = __esm({
200303
200556
  deleteObservations(deletions) {
200304
200557
  const deleteAll = this.db.transaction(() => {
200305
200558
  for (const d4 of deletions) {
200306
- const row = this.stmtSelectEntityByName.get(d4.entityName);
200559
+ const row = this.stmtSelectEntityByName.get(this.projectId, d4.entityName);
200307
200560
  if (!row) continue;
200308
200561
  const existing = safeJsonParse3(row.observations, []);
200309
200562
  const filtered = existing.filter((o4) => !d4.observations.includes(o4));
200310
- this.stmtUpdateEntityObs.run({ name: d4.entityName, observations: JSON.stringify(filtered) });
200563
+ this.stmtUpdateEntityObs.run({ projectId: this.projectId, name: d4.entityName, observations: JSON.stringify(filtered) });
200311
200564
  }
200312
200565
  });
200313
200566
  deleteAll();
@@ -200318,13 +200571,13 @@ var init_graph_store = __esm({
200318
200571
  */
200319
200572
  replaceAll(entities, relations) {
200320
200573
  const replaceAll = this.db.transaction(() => {
200321
- this.db.prepare("DELETE FROM graph_relations").run();
200322
- this.db.prepare("DELETE FROM graph_entities").run();
200574
+ this.db.prepare("DELETE FROM graph_relations WHERE projectId = ?").run(this.projectId);
200575
+ this.db.prepare("DELETE FROM graph_entities WHERE projectId = ?").run(this.projectId);
200323
200576
  for (const entity of entities) {
200324
- this.stmtInsertEntity.run(entityToRow(entity));
200577
+ this.stmtInsertEntity.run({ ...entityToRow(entity), projectId: this.projectId });
200325
200578
  }
200326
200579
  for (const rel of relations) {
200327
- this.stmtInsertRelation.run({ from: rel.from, to: rel.to, relationType: rel.relationType });
200580
+ this.stmtInsertRelation.run({ projectId: this.projectId, from: rel.from, to: rel.to, relationType: rel.relationType });
200328
200581
  }
200329
200582
  });
200330
200583
  replaceAll();
@@ -200332,8 +200585,8 @@ var init_graph_store = __esm({
200332
200585
  close() {
200333
200586
  }
200334
200587
  };
200335
- _graphStore = null;
200336
- _graphDataDir = null;
200588
+ _graphStores = /* @__PURE__ */ new Map();
200589
+ _activeGraphKey = null;
200337
200590
  }
200338
200591
  });
200339
200592
 
@@ -200348,11 +200601,14 @@ var init_graph = __esm({
200348
200601
  entities = [];
200349
200602
  relations = [];
200350
200603
  projectDir;
200604
+ projectId;
200605
+ store = null;
200351
200606
  initialized = false;
200352
200607
  /** Index: lowercase entity name → Entity for O(1) lookups */
200353
200608
  entityIndex = /* @__PURE__ */ new Map();
200354
- constructor(projectDir2) {
200609
+ constructor(projectDir2, projectId = "__default__") {
200355
200610
  this.projectDir = projectDir2;
200611
+ this.projectId = projectId;
200356
200612
  }
200357
200613
  /** Rebuild the entity name index */
200358
200614
  rebuildIndex() {
@@ -200364,10 +200620,9 @@ var init_graph = __esm({
200364
200620
  /** Load graph from SQLite on first access */
200365
200621
  async init() {
200366
200622
  if (this.initialized) return;
200367
- await initGraphStore(this.projectDir);
200368
- const store2 = getGraphStore();
200369
- this.entities = store2.loadEntities();
200370
- this.relations = store2.loadRelations();
200623
+ this.store = await initGraphStore(this.projectDir, this.projectId);
200624
+ this.entities = this.store.loadEntities();
200625
+ this.relations = this.store.loadRelations();
200371
200626
  this.rebuildIndex();
200372
200627
  this.initialized = true;
200373
200628
  }
@@ -200381,8 +200636,8 @@ var init_graph = __esm({
200381
200636
  }
200382
200637
  /** Persist current state to SQLite */
200383
200638
  async save() {
200384
- const store2 = getGraphStore();
200385
- store2.replaceAll(this.entities, this.relations);
200639
+ if (!this.store) throw new Error("[memorix] KnowledgeGraphManager not initialized");
200640
+ this.store.replaceAll(this.entities, this.relations);
200386
200641
  }
200387
200642
  /** Create new entities (skip duplicates by name) */
200388
200643
  async createEntities(entities) {
@@ -200758,7 +201013,7 @@ async function getSessionContext(projectDir2, projectId, limit2 = 3, reader2) {
200758
201013
  let graphNeighbors = [];
200759
201014
  if (!reader2 && activeEntities.length > 0) {
200760
201015
  try {
200761
- const graphMgr = new KnowledgeGraphManager(projectDir2);
201016
+ const graphMgr = new KnowledgeGraphManager(projectDir2, projectId);
200762
201017
  await graphMgr.init();
200763
201018
  const { relations } = await graphMgr.readGraph();
200764
201019
  const activeSet = new Set(activeEntities.map((n3) => n3.toLowerCase()));
@@ -205266,8 +205521,8 @@ __export(engine_exports2, {
205266
205521
  SkillsEngine: () => SkillsEngine
205267
205522
  });
205268
205523
  import { existsSync as existsSync21, readFileSync as readFileSync13, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, readdirSync as readdirSync3 } from "fs";
205269
- import { join as join25 } from "path";
205270
- import { homedir as homedir31 } from "os";
205524
+ import { join as join26 } from "path";
205525
+ import { homedir as homedir32 } from "os";
205271
205526
  var SKILLS_DIRS, SKILL_WORTHY_TYPES, MIN_OBS_FOR_SKILL, MIN_SCORE_FOR_SKILL, LOW_SIGNAL_TITLE_PATTERNS, LOW_SIGNAL_ENTITY_PATTERNS, COMMAND_TRACE_PATTERNS2, SkillsEngine;
205272
205527
  var init_engine4 = __esm({
205273
205528
  "src/skills/engine.ts"() {
@@ -205338,12 +205593,12 @@ var init_engine4 = __esm({
205338
205593
  listSkills() {
205339
205594
  const skills = [];
205340
205595
  const seen = /* @__PURE__ */ new Set();
205341
- const home = homedir31();
205596
+ const home = homedir32();
205342
205597
  for (const [agent, dirs] of Object.entries(SKILLS_DIRS)) {
205343
205598
  for (const dir of dirs) {
205344
- const paths = [join25(this.projectRoot, dir)];
205599
+ const paths = [join26(this.projectRoot, dir)];
205345
205600
  if (!this.skipGlobal) {
205346
- paths.push(join25(home, dir));
205601
+ paths.push(join26(home, dir));
205347
205602
  }
205348
205603
  for (const skillsRoot of paths) {
205349
205604
  if (!existsSync21(skillsRoot)) continue;
@@ -205353,7 +205608,7 @@ var init_engine4 = __esm({
205353
205608
  if (!entry.isDirectory()) continue;
205354
205609
  const name = entry.name;
205355
205610
  if (seen.has(name)) continue;
205356
- const skillMd = join25(skillsRoot, name, "SKILL.md");
205611
+ const skillMd = join26(skillsRoot, name, "SKILL.md");
205357
205612
  if (!existsSync21(skillMd)) continue;
205358
205613
  try {
205359
205614
  const content = readFileSync13(skillMd, "utf-8");
@@ -205361,7 +205616,7 @@ var init_engine4 = __esm({
205361
205616
  skills.push({
205362
205617
  name,
205363
205618
  description,
205364
- sourcePath: join25(skillsRoot, name),
205619
+ sourcePath: join26(skillsRoot, name),
205365
205620
  sourceAgent: agent,
205366
205621
  content,
205367
205622
  generated: false
@@ -205404,11 +205659,11 @@ var init_engine4 = __esm({
205404
205659
  writeSkill(skill2, target) {
205405
205660
  const dirs = SKILLS_DIRS[target];
205406
205661
  if (!dirs || dirs.length === 0) return null;
205407
- const targetDir = join25(this.projectRoot, dirs[0], skill2.name);
205662
+ const targetDir = join26(this.projectRoot, dirs[0], skill2.name);
205408
205663
  try {
205409
205664
  mkdirSync5(targetDir, { recursive: true });
205410
- writeFileSync5(join25(targetDir, "SKILL.md"), skill2.content, "utf-8");
205411
- return join25(dirs[0], skill2.name, "SKILL.md");
205665
+ writeFileSync5(join26(targetDir, "SKILL.md"), skill2.content, "utf-8");
205666
+ return join26(dirs[0], skill2.name, "SKILL.md");
205412
205667
  } catch {
205413
205668
  return null;
205414
205669
  }
@@ -206577,7 +206832,7 @@ var init_task_graph = __esm({
206577
206832
  // src/orchestrate/context-collector.ts
206578
206833
  import { execFileSync as execFileSync2 } from "child_process";
206579
206834
  import { readFileSync as readFileSync14, existsSync as existsSync22 } from "fs";
206580
- import { join as join26 } from "path";
206835
+ import { join as join27 } from "path";
206581
206836
  function normalizeEntryLimit(value, fallback) {
206582
206837
  if (!Number.isSafeInteger(value) || value < 1) return fallback;
206583
206838
  return Math.min(value, 500);
@@ -206652,7 +206907,7 @@ function collectFileTree(projectDir2, maxEntries) {
206652
206907
  }
206653
206908
  function collectDependencies(projectDir2) {
206654
206909
  try {
206655
- const pkgPath = join26(projectDir2, "package.json");
206910
+ const pkgPath = join27(projectDir2, "package.json");
206656
206911
  if (!existsSync22(pkgPath)) return "";
206657
206912
  const pkg = JSON.parse(readFileSync14(pkgPath, "utf-8"));
206658
206913
  const parts = [];
@@ -207989,7 +208244,7 @@ var init_tool_profile = __esm({
207989
208244
  function getMcpServerInfo() {
207990
208245
  return {
207991
208246
  name: "memorix",
207992
- version: true ? "1.9.4" : "1.0.1"
208247
+ version: true ? "1.9.5" : "1.0.1"
207993
208248
  };
207994
208249
  }
207995
208250
  function createMcpDiscoverResult(capabilities, serverInfo) {
@@ -221795,8 +222050,8 @@ __export(engine_exports3, {
221795
222050
  WorkspaceSyncEngine: () => WorkspaceSyncEngine
221796
222051
  });
221797
222052
  import { readFileSync as readFileSync16, readdirSync as readdirSync4, existsSync as existsSync25, cpSync, mkdirSync as mkdirSync7 } from "fs";
221798
- import { join as join28 } from "path";
221799
- import { homedir as homedir32 } from "os";
222053
+ import { join as join29 } from "path";
222054
+ import { homedir as homedir33 } from "os";
221800
222055
  var WorkspaceSyncEngine;
221801
222056
  var init_engine5 = __esm({
221802
222057
  "src/workspace/engine.ts"() {
@@ -221868,9 +222123,9 @@ var init_engine5 = __esm({
221868
222123
  const globalPath = adapter.getConfigPath();
221869
222124
  const pathsToCheck = [configPath, globalPath];
221870
222125
  if (target === "antigravity") {
221871
- pathsToCheck.push(join28(this.projectRoot, ".gemini", "settings.json"));
221872
- pathsToCheck.push(join28(homedir32(), ".gemini", "settings.json"));
221873
- pathsToCheck.push(join28(homedir32(), ".gemini", "antigravity", "mcp_config.json"));
222126
+ pathsToCheck.push(join29(this.projectRoot, ".gemini", "settings.json"));
222127
+ pathsToCheck.push(join29(homedir33(), ".gemini", "settings.json"));
222128
+ pathsToCheck.push(join29(homedir33(), ".gemini", "antigravity", "mcp_config.json"));
221874
222129
  }
221875
222130
  const merged = /* @__PURE__ */ new Map();
221876
222131
  for (const path50 of pathsToCheck) {
@@ -221987,7 +222242,7 @@ var init_engine5 = __esm({
221987
222242
  getTargetSkillsDir(target) {
221988
222243
  const dirs = _WorkspaceSyncEngine.SKILLS_DIRS[target];
221989
222244
  if (!dirs || dirs.length === 0) return null;
221990
- return join28(this.projectRoot, dirs[0]);
222245
+ return join29(this.projectRoot, dirs[0]);
221991
222246
  }
221992
222247
  /**
221993
222248
  * Scan all agent skills directories and collect unique skills.
@@ -221996,12 +222251,12 @@ var init_engine5 = __esm({
221996
222251
  const skills = [];
221997
222252
  const conflicts = [];
221998
222253
  const seen = /* @__PURE__ */ new Map();
221999
- const home = homedir32();
222254
+ const home = homedir33();
222000
222255
  for (const [agent, dirs] of Object.entries(_WorkspaceSyncEngine.SKILLS_DIRS)) {
222001
222256
  for (const dir of dirs) {
222002
222257
  const paths = [
222003
- join28(this.projectRoot, dir),
222004
- join28(home, dir)
222258
+ join29(this.projectRoot, dir),
222259
+ join29(home, dir)
222005
222260
  ];
222006
222261
  for (const skillsRoot of paths) {
222007
222262
  if (!existsSync25(skillsRoot)) continue;
@@ -222009,7 +222264,7 @@ var init_engine5 = __esm({
222009
222264
  const entries = readdirSync4(skillsRoot, { withFileTypes: true });
222010
222265
  for (const entry of entries) {
222011
222266
  if (!entry.isDirectory()) continue;
222012
- const skillMd = join28(skillsRoot, entry.name, "SKILL.md");
222267
+ const skillMd = join29(skillsRoot, entry.name, "SKILL.md");
222013
222268
  if (!existsSync25(skillMd)) continue;
222014
222269
  let description = "";
222015
222270
  try {
@@ -222021,7 +222276,7 @@ var init_engine5 = __esm({
222021
222276
  const newEntry = {
222022
222277
  name: entry.name,
222023
222278
  description,
222024
- sourcePath: join28(skillsRoot, entry.name),
222279
+ sourcePath: join29(skillsRoot, entry.name),
222025
222280
  sourceAgent: agent
222026
222281
  };
222027
222282
  const existing = seen.get(entry.name);
@@ -222058,7 +222313,7 @@ var init_engine5 = __esm({
222058
222313
  }
222059
222314
  for (const skill2 of skills) {
222060
222315
  if (skill2.sourceAgent === target) continue;
222061
- const dest = join28(targetDir, skill2.name);
222316
+ const dest = join29(targetDir, skill2.name);
222062
222317
  if (existsSync25(dest)) {
222063
222318
  skipped.push(`${skill2.name} (already exists in ${target})`);
222064
222319
  continue;
@@ -222074,13 +222329,13 @@ var init_engine5 = __esm({
222074
222329
  }
222075
222330
  scanWorkflows() {
222076
222331
  const workflows = [];
222077
- const wfDir = join28(this.projectRoot, ".windsurf", "workflows");
222332
+ const wfDir = join29(this.projectRoot, ".windsurf", "workflows");
222078
222333
  if (!existsSync25(wfDir)) return workflows;
222079
222334
  try {
222080
222335
  const files = readdirSync4(wfDir).filter((f7) => f7.endsWith(".md"));
222081
222336
  for (const file2 of files) {
222082
222337
  try {
222083
- const content = readFileSync16(join28(wfDir, file2), "utf-8");
222338
+ const content = readFileSync16(join29(wfDir, file2), "utf-8");
222084
222339
  workflows.push(this.workflowSyncer.parseWindsurfWorkflow(file2, content));
222085
222340
  } catch {
222086
222341
  }
@@ -222355,7 +222610,7 @@ __export(behavior_exports, {
222355
222610
  resetBehaviorConfigCache: () => resetBehaviorConfigCache
222356
222611
  });
222357
222612
  import { existsSync as existsSync26, readFileSync as readFileSync17 } from "fs";
222358
- import { homedir as homedir33 } from "os";
222613
+ import { homedir as homedir34 } from "os";
222359
222614
  function isSessionInject(value) {
222360
222615
  return value === "full" || value === "minimal" || value === "silent";
222361
222616
  }
@@ -222373,7 +222628,7 @@ function legacyBehavior(homeDir) {
222373
222628
  }
222374
222629
  }
222375
222630
  function getBehaviorConfig(options = {}) {
222376
- const homeDir = options.homeDir ?? homedir33();
222631
+ const homeDir = options.homeDir ?? homedir34();
222377
222632
  const projectRoot = options.projectRoot === void 0 ? detectProject()?.rootPath ?? null : options.projectRoot;
222378
222633
  const cacheKey2 = `${homeDir}\0${projectRoot ?? ""}`;
222379
222634
  const cached2 = cache4.get(cacheKey2);
@@ -223611,8 +223866,7 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
223611
223866
  break;
223612
223867
  }
223613
223868
  case "/graph": {
223614
- await initGraphStore(effectiveDataDir);
223615
- const gStore = getGraphStore();
223869
+ const gStore = await initGraphStore(effectiveDataDir, effectiveProjectId);
223616
223870
  const graph = { entities: gStore.loadEntities(), relations: gStore.loadRelations() };
223617
223871
  const graphObs = filterDashboardObservations(
223618
223872
  await getObservationStore().loadByProject(effectiveProjectId, { status: "active" }),
@@ -223656,8 +223910,8 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
223656
223910
  break;
223657
223911
  }
223658
223912
  case "/stats": {
223659
- await initGraphStore(effectiveDataDir);
223660
- const graph = { entities: getGraphStore().loadEntities(), relations: getGraphStore().loadRelations() };
223913
+ const gStore = await initGraphStore(effectiveDataDir, effectiveProjectId);
223914
+ const graph = { entities: gStore.loadEntities(), relations: gStore.loadRelations() };
223661
223915
  const observations2 = filterDashboardObservations(
223662
223916
  await getObservationStore().loadByProject(effectiveProjectId, { status: "active" }),
223663
223917
  effectiveProjectId
@@ -223850,15 +224104,15 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
223850
224104
  case "/knowledge-graph": {
223851
224105
  const { generateKnowledgeGraph: generateKnowledgeGraph2 } = await Promise.resolve().then(() => (init_knowledge_graph(), knowledge_graph_exports));
223852
224106
  const { initMiniSkillStore: initMiniSkillStore2, getMiniSkillStore: getMiniSkillStore2 } = await Promise.resolve().then(() => (init_mini_skill_store(), mini_skill_store_exports));
223853
- const { initGraphStore: initGraphStore2, getGraphStore: getGraphStore2 } = await Promise.resolve().then(() => (init_graph_store(), graph_store_exports));
224107
+ const { initGraphStore: initGraphStore2 } = await Promise.resolve().then(() => (init_graph_store(), graph_store_exports));
223854
224108
  await initMiniSkillStore2(effectiveDataDir);
223855
- await initGraphStore2(effectiveDataDir);
224109
+ const gStore = await initGraphStore2(effectiveDataDir, effectiveProjectId);
223856
224110
  const allObs = filterDashboardObservations(
223857
224111
  await getObservationStore().loadByProject(effectiveProjectId, { status: "active" }),
223858
224112
  effectiveProjectId
223859
224113
  );
223860
224114
  const skills = await getMiniSkillStore2().loadByProject(effectiveProjectId);
223861
- const fullGraph = { entities: getGraphStore2().loadEntities(), relations: getGraphStore2().loadRelations() };
224115
+ const fullGraph = { entities: gStore.loadEntities(), relations: gStore.loadRelations() };
223862
224116
  const scoped = scopeKnowledgeGraphToProject(fullGraph, allObs);
223863
224117
  const graph = generateKnowledgeGraph2({
223864
224118
  projectId: effectiveProjectId,
@@ -223873,7 +224127,7 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
223873
224127
  case "/config": {
223874
224128
  const os10 = await import("os");
223875
224129
  const { existsSync: existsSync36 } = await import("fs");
223876
- const { join: join35 } = await import("path");
224130
+ const { join: join36 } = await import("path");
223877
224131
  const configProjectRoot = effectiveProjectRoot;
223878
224132
  const resolved = getResolvedConfig({ projectRoot: configProjectRoot });
223879
224133
  const files = {
@@ -223888,13 +224142,13 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
223888
224142
  try {
223889
224143
  const home = os10.homedir();
223890
224144
  const paths = {
223891
- "project memorix.yml": configProjectRoot ? join35(configProjectRoot, "memorix.yml") : null,
223892
- "user memorix.yml": join35(home, ".memorix", "memorix.yml"),
223893
- "project memorix.toml": configProjectRoot ? join35(configProjectRoot, "memorix.toml") : null,
223894
- "user config.toml": join35(home, ".memorix", "config.toml"),
223895
- "project .env": configProjectRoot ? join35(configProjectRoot, ".env") : null,
223896
- "user .env": join35(home, ".memorix", ".env"),
223897
- "legacy config.json": join35(home, ".memorix", "config.json")
224145
+ "project memorix.yml": configProjectRoot ? join36(configProjectRoot, "memorix.yml") : null,
224146
+ "user memorix.yml": join36(home, ".memorix", "memorix.yml"),
224147
+ "project memorix.toml": configProjectRoot ? join36(configProjectRoot, "memorix.toml") : null,
224148
+ "user config.toml": join36(home, ".memorix", "config.toml"),
224149
+ "project .env": configProjectRoot ? join36(configProjectRoot, ".env") : null,
224150
+ "user .env": join36(home, ".memorix", ".env"),
224151
+ "legacy config.json": join36(home, ".memorix", "config.json")
223898
224152
  };
223899
224153
  for (const [key, fpath] of Object.entries(paths)) {
223900
224154
  if (fpath === null) {
@@ -224055,8 +224309,7 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
224055
224309
  } else {
224056
224310
  await obsStore.remove(obsId);
224057
224311
  try {
224058
- await initGraphStore(effectiveDataDir);
224059
- const gStore = getGraphStore();
224312
+ const gStore = await initGraphStore(effectiveDataDir, effectiveProjectId);
224060
224313
  const prefix = `[#${obsId}] `;
224061
224314
  const deletions = [];
224062
224315
  for (const entity of gStore.loadEntities()) {
@@ -224071,8 +224324,8 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
224071
224324
  break;
224072
224325
  }
224073
224326
  if (apiPath === "/export") {
224074
- await initGraphStore(effectiveDataDir);
224075
- const fullGraph = { entities: getGraphStore().loadEntities(), relations: getGraphStore().loadRelations() };
224327
+ const gStore = await initGraphStore(effectiveDataDir, effectiveProjectId);
224328
+ const fullGraph = { entities: gStore.loadEntities(), relations: gStore.loadRelations() };
224076
224329
  const observations2 = filterDashboardObservations(
224077
224330
  await getObservationStore().loadByProject(effectiveProjectId, { status: "active" }),
224078
224331
  effectiveProjectId
@@ -264596,7 +264849,7 @@ async function createMemorixServer(cwd, existingServer, sharedTeam, options = {}
264596
264849
  const store2 = getObservationStore();
264597
264850
  console.error(`[memorix] ObservationStore backend: ${store2.getBackendName()}, generation: ${store2.getGeneration()}`);
264598
264851
  }
264599
- let graphManager = new KnowledgeGraphManager(projectDir2);
264852
+ let graphManager = new KnowledgeGraphManager(projectDir2, project.id);
264600
264853
  if (!deferProjectRuntimeInit) {
264601
264854
  await graphManager.init();
264602
264855
  await initObservations(projectDir2);
@@ -264608,7 +264861,7 @@ async function createMemorixServer(cwd, existingServer, sharedTeam, options = {}
264608
264861
  await initObservationStore(projectDir2);
264609
264862
  await initMiniSkillStore(projectDir2);
264610
264863
  await initSessionStore(projectDir2);
264611
- graphManager = new KnowledgeGraphManager(projectDir2);
264864
+ graphManager = new KnowledgeGraphManager(projectDir2, project.id);
264612
264865
  await graphManager.init();
264613
264866
  await initObservations(projectDir2);
264614
264867
  const indexed = await prepareSearchIndex();
@@ -266820,10 +267073,10 @@ ${Math.max(0, managedIds.size - result.resolved.length)} visible writable observ
266820
267073
  );
266821
267074
  let enableKG = isToolInProfile("create_entities", toolProfile);
266822
267075
  try {
266823
- const { homedir: homedir37 } = await import("os");
266824
- const { join: join35 } = await import("path");
267076
+ const { homedir: homedir38 } = await import("os");
267077
+ const { join: join36 } = await import("path");
266825
267078
  const { readFile: readFile11 } = await import("fs/promises");
266826
- const raw = await readFile11(join35(homedir37(), ".memorix", "settings.json"), "utf-8");
267079
+ const raw = await readFile11(join36(homedir38(), ".memorix", "settings.json"), "utf-8");
266827
267080
  const s4 = JSON.parse(raw);
266828
267081
  if (s4.knowledgeGraph === true) enableKG = true;
266829
267082
  } catch {
@@ -269398,8 +269651,8 @@ __export(update_checker_exports, {
269398
269651
  });
269399
269652
  import { execFile as execFile3 } from "child_process";
269400
269653
  import { readFile as readFile7, writeFile as writeFile8, mkdir as mkdir7 } from "fs/promises";
269401
- import { join as join29 } from "path";
269402
- import { homedir as homedir34 } from "os";
269654
+ import { join as join30 } from "path";
269655
+ import { homedir as homedir35 } from "os";
269403
269656
  function getCurrentVersion() {
269404
269657
  try {
269405
269658
  return getCliVersion();
@@ -269578,8 +269831,8 @@ var init_update_checker = __esm({
269578
269831
  MIN_AUTO_UPDATE_TIMEOUT_MS = 5e3;
269579
269832
  MAX_AUTO_UPDATE_TIMEOUT_MS = 30 * 60 * 1e3;
269580
269833
  REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
269581
- CACHE_DIR2 = join29(homedir34(), ".memorix");
269582
- CACHE_FILE2 = join29(CACHE_DIR2, "update-check.json");
269834
+ CACHE_DIR2 = join30(homedir35(), ".memorix");
269835
+ CACHE_FILE2 = join30(CACHE_DIR2, "update-check.json");
269583
269836
  _testing = {
269584
269837
  CACHE_FILE: CACHE_FILE2,
269585
269838
  CHECK_INTERVAL_MS,
@@ -269637,7 +269890,7 @@ var init_serve = __esm({
269637
269890
  const { serveStdio, StdioServerTransport } = await import("@modelcontextprotocol/server/stdio");
269638
269891
  const { createModernMcpBridge: createModernMcpBridge2 } = await Promise.resolve().then(() => (init_modern_mcp_bridge(), modern_mcp_bridge_exports));
269639
269892
  const { detectProject: detectProject2, findGitInSubdirs: findGitInSubdirs2, isSystemDirectory: isSystemDirectory2 } = await Promise.resolve().then(() => (init_detector(), detector_exports));
269640
- const { homedir: homedir37 } = await import("os");
269893
+ const { homedir: homedir38 } = await import("os");
269641
269894
  const { writeLastProjectRoot: writeLastProjectRoot2 } = await Promise.resolve().then(() => (init_launch_root(), launch_root_exports));
269642
269895
  const { resolveServeProject: resolveServeProject2 } = await Promise.resolve().then(() => (init_serve_shared(), serve_shared_exports));
269643
269896
  let stdioHandle;
@@ -269666,7 +269919,7 @@ var init_serve = __esm({
269666
269919
  try {
269667
269920
  safeCwd = process.cwd();
269668
269921
  } catch {
269669
- safeCwd = homedir37();
269922
+ safeCwd = homedir38();
269670
269923
  }
269671
269924
  const resolution = resolveServeProject2(
269672
269925
  {
@@ -269674,7 +269927,7 @@ var init_serve = __esm({
269674
269927
  envProjectRoot: process.env.MEMORIX_PROJECT_ROOT,
269675
269928
  initCwd: process.env.INIT_CWD,
269676
269929
  processCwd: safeCwd,
269677
- homeDir: homedir37()
269930
+ homeDir: homedir38()
269678
269931
  },
269679
269932
  { detectProject: detectProject2, findGitInSubdirs: findGitInSubdirs2, isSystemDirectory: isSystemDirectory2 }
269680
269933
  );
@@ -271895,6 +272148,7 @@ var init_streamableHttp = __esm({
271895
272148
  // src/server/mcp-binding-store.ts
271896
272149
  var mcp_binding_store_exports = {};
271897
272150
  __export(mcp_binding_store_exports, {
272151
+ DEFAULT_MCP_BINDING_TTL_MS: () => DEFAULT_MCP_BINDING_TTL_MS,
271898
272152
  McpBindingStore: () => McpBindingStore
271899
272153
  });
271900
272154
  import { randomUUID as randomUUID19 } from "crypto";
@@ -271909,12 +272163,13 @@ function rowToBinding(row) {
271909
272163
  ...row.expires_at ? { expiresAt: String(row.expires_at) } : {}
271910
272164
  };
271911
272165
  }
271912
- var McpBindingStore;
272166
+ var DEFAULT_MCP_BINDING_TTL_MS, McpBindingStore;
271913
272167
  var init_mcp_binding_store = __esm({
271914
272168
  "src/server/mcp-binding-store.ts"() {
271915
272169
  "use strict";
271916
272170
  init_esm_shims();
271917
272171
  init_sqlite_db();
272172
+ DEFAULT_MCP_BINDING_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
271918
272173
  McpBindingStore = class {
271919
272174
  db = null;
271920
272175
  async init(dataDir) {
@@ -271926,6 +272181,7 @@ var init_mcp_binding_store = __esm({
271926
272181
  }
271927
272182
  create(input) {
271928
272183
  const now3 = /* @__PURE__ */ new Date();
272184
+ const ttlMs = input.ttlMs ?? DEFAULT_MCP_BINDING_TTL_MS;
271929
272185
  const binding = {
271930
272186
  handleId: `mxh_${randomUUID19().replace(/-/g, "")}`,
271931
272187
  projectId: input.projectId,
@@ -271933,7 +272189,7 @@ var init_mcp_binding_store = __esm({
271933
272189
  dataDir: input.dataDir,
271934
272190
  createdAt: now3.toISOString(),
271935
272191
  lastUsedAt: now3.toISOString(),
271936
- ...input.ttlMs && input.ttlMs > 0 ? { expiresAt: new Date(now3.getTime() + input.ttlMs).toISOString() } : {}
272192
+ ...ttlMs > 0 ? { expiresAt: new Date(now3.getTime() + ttlMs).toISOString() } : {}
271937
272193
  };
271938
272194
  this.requireDb().prepare(`
271939
272195
  INSERT INTO mcp_bindings (handle_id, project_id, project_root, data_dir, created_at, last_used_at, expires_at)
@@ -271955,8 +272211,15 @@ var init_mcp_binding_store = __esm({
271955
272211
  const binding = this.get(handleId);
271956
272212
  if (!binding) return void 0;
271957
272213
  const lastUsedAt = (/* @__PURE__ */ new Date()).toISOString();
271958
- this.requireDb().prepare(`UPDATE mcp_bindings SET last_used_at = ? WHERE handle_id = ?`).run(lastUsedAt, handleId);
271959
- return { ...binding, lastUsedAt };
272214
+ const expiresAt = new Date(Date.now() + DEFAULT_MCP_BINDING_TTL_MS).toISOString();
272215
+ this.requireDb().prepare(`UPDATE mcp_bindings SET last_used_at = ?, expires_at = ? WHERE handle_id = ?`).run(lastUsedAt, expiresAt, handleId);
272216
+ return { ...binding, lastUsedAt, expiresAt };
272217
+ }
272218
+ cleanupExpired(now3 = Date.now()) {
272219
+ const result = this.requireDb().prepare(
272220
+ `DELETE FROM mcp_bindings WHERE expires_at IS NOT NULL AND expires_at <= ?`
272221
+ ).run(new Date(now3).toISOString());
272222
+ return Number(result.changes ?? 0);
271960
272223
  }
271961
272224
  /** Reuse the most recent valid extension handle for a verified project root. */
271962
272225
  findByProjectRoot(projectRoot) {
@@ -272046,16 +272309,25 @@ var init_control_plane_maintenance = __esm({
272046
272309
  // src/cli/commands/serve-http.ts
272047
272310
  var serve_http_exports = {};
272048
272311
  __export(serve_http_exports, {
272312
+ DEFAULT_HTTP_BODY_LIMIT_BYTES: () => DEFAULT_HTTP_BODY_LIMIT_BYTES,
272049
272313
  DEFAULT_SESSION_TIMEOUT_MS: () => DEFAULT_SESSION_TIMEOUT_MS,
272050
272314
  EXPIRED_SESSION_TTL_MS: () => EXPIRED_SESSION_TTL_MS,
272315
+ HttpPayloadTooLargeError: () => HttpPayloadTooLargeError,
272316
+ MAX_HTTP_BODY_LIMIT_BYTES: () => MAX_HTTP_BODY_LIMIT_BYTES,
272051
272317
  MCP_SDK_COMPAT_PROTOCOL_VERSION: () => MCP_SDK_COMPAT_PROTOCOL_VERSION,
272318
+ MIN_HTTP_BODY_LIMIT_BYTES: () => MIN_HTTP_BODY_LIMIT_BYTES,
272052
272319
  _testing: () => _testing2,
272053
272320
  default: () => serve_http_default,
272321
+ isLoopbackHost: () => isLoopbackHost,
272054
272322
  isServerDiscoverRequest: () => isServerDiscoverRequest,
272323
+ parseHttpBodyLimit: () => parseHttpBodyLimit,
272324
+ parseJsonBody: () => parseJsonBody,
272055
272325
  parseSessionTimeoutMs: () => parseSessionTimeoutMs,
272056
- resolveServeHttpDefaults: () => resolveServeHttpDefaults
272326
+ resolveServeHttpDefaults: () => resolveServeHttpDefaults,
272327
+ validateHttpBindSecurity: () => validateHttpBindSecurity
272057
272328
  });
272058
272329
  import { getHeapStatistics } from "v8";
272330
+ import { timingSafeEqual as timingSafeEqual2 } from "crypto";
272059
272331
  function isServerDiscoverRequest(body) {
272060
272332
  return Boolean(body && typeof body === "object" && !Array.isArray(body) && body.method === "server/discover");
272061
272333
  }
@@ -272066,6 +272338,78 @@ function parseSessionTimeoutMs(raw) {
272066
272338
  if (!Number.isFinite(parsed) || parsed < 0) return DEFAULT_SESSION_TIMEOUT_MS;
272067
272339
  return Math.floor(parsed);
272068
272340
  }
272341
+ function parseHttpBodyLimit(raw) {
272342
+ const value = raw?.trim();
272343
+ if (!value) return DEFAULT_HTTP_BODY_LIMIT_BYTES;
272344
+ const parsed = Number(value);
272345
+ if (!Number.isSafeInteger(parsed) || parsed < MIN_HTTP_BODY_LIMIT_BYTES) {
272346
+ return DEFAULT_HTTP_BODY_LIMIT_BYTES;
272347
+ }
272348
+ return Math.min(parsed, MAX_HTTP_BODY_LIMIT_BYTES);
272349
+ }
272350
+ function isLoopbackHost(host) {
272351
+ const normalized = host.trim().toLowerCase();
272352
+ return normalized === "localhost" || normalized === "127.0.0.1" || normalized === "::1" || normalized === "[::1]";
272353
+ }
272354
+ function validateHttpBindSecurity(host, env3 = process.env) {
272355
+ const hasToken = Boolean(env3.MEMORIX_HTTP_AUTH_TOKEN?.trim());
272356
+ const explicitlyUnauthenticated = env3.MEMORIX_HTTP_ALLOW_UNAUTHENTICATED_BIND === "1";
272357
+ if (!isLoopbackHost(host) && !hasToken && !explicitlyUnauthenticated) {
272358
+ throw new Error(
272359
+ `[memorix] Refusing unauthenticated non-loopback HTTP bind on ${host}. Set MEMORIX_HTTP_AUTH_TOKEN or explicitly set MEMORIX_HTTP_ALLOW_UNAUTHENTICATED_BIND=1.`
272360
+ );
272361
+ }
272362
+ return { requiresAuth: hasToken, explicitlyUnauthenticated };
272363
+ }
272364
+ function isAuthorizedHttpRequest(req, token) {
272365
+ const header = req.headers.authorization;
272366
+ if (!header) return false;
272367
+ const match2 = /^Bearer\s+(.+)$/iu.exec(header);
272368
+ if (!match2) return false;
272369
+ const provided = Buffer.from(match2[1], "utf8");
272370
+ const expected = Buffer.from(token, "utf8");
272371
+ return provided.length === expected.length && timingSafeEqual2(provided, expected);
272372
+ }
272373
+ function parseJsonBody(req, limitBytes) {
272374
+ return new Promise((resolve9, reject) => {
272375
+ const contentLength = Number(req.headers["content-length"]);
272376
+ if (Number.isSafeInteger(contentLength) && contentLength > limitBytes) {
272377
+ req.resume();
272378
+ reject(new HttpPayloadTooLargeError(limitBytes));
272379
+ return;
272380
+ }
272381
+ const chunks = [];
272382
+ let size = 0;
272383
+ let settled = false;
272384
+ const fail2 = (error40) => {
272385
+ if (settled) return;
272386
+ settled = true;
272387
+ reject(error40);
272388
+ };
272389
+ req.on("data", (chunk) => {
272390
+ if (settled) return;
272391
+ size += chunk.length;
272392
+ if (size > limitBytes) {
272393
+ req.removeAllListeners("data");
272394
+ req.resume();
272395
+ fail2(new HttpPayloadTooLargeError(limitBytes));
272396
+ return;
272397
+ }
272398
+ chunks.push(chunk);
272399
+ });
272400
+ req.on("end", () => {
272401
+ if (settled) return;
272402
+ settled = true;
272403
+ try {
272404
+ const body = Buffer.concat(chunks).toString("utf-8");
272405
+ resolve9(body ? JSON.parse(body) : void 0);
272406
+ } catch (error40) {
272407
+ reject(error40);
272408
+ }
272409
+ });
272410
+ req.on("error", fail2);
272411
+ });
272412
+ }
272069
272413
  function resolveServeHttpDefaults(argsPort, configured) {
272070
272414
  const configuredPort = typeof configured.port === "number" && Number.isFinite(configured.port) ? configured.port : void 0;
272071
272415
  const port = parseTcpPortOrReport(argsPort, configuredPort ?? 3211);
@@ -272075,7 +272419,7 @@ function resolveServeHttpDefaults(argsPort, configured) {
272075
272419
  const transportNotice = configured.transport === "stdio" ? '[memorix] config server.transport = "stdio"; serve-http continues on HTTP (the explicit command wins).' : void 0;
272076
272420
  return { port, transportNotice };
272077
272421
  }
272078
- var DEFAULT_SESSION_TIMEOUT_MS, EXPIRED_SESSION_TTL_MS, MCP_SDK_COMPAT_PROTOCOL_VERSION, serve_http_default, _testing2;
272422
+ var DEFAULT_SESSION_TIMEOUT_MS, EXPIRED_SESSION_TTL_MS, DEFAULT_HTTP_BODY_LIMIT_BYTES, MAX_HTTP_BODY_LIMIT_BYTES, MIN_HTTP_BODY_LIMIT_BYTES, MCP_SDK_COMPAT_PROTOCOL_VERSION, HttpPayloadTooLargeError, serve_http_default, _testing2;
272079
272423
  var init_serve_http = __esm({
272080
272424
  "src/cli/commands/serve-http.ts"() {
272081
272425
  "use strict";
@@ -272091,7 +272435,17 @@ var init_serve_http = __esm({
272091
272435
  init_mcp_root_path();
272092
272436
  DEFAULT_SESSION_TIMEOUT_MS = 12 * 60 * 60 * 1e3;
272093
272437
  EXPIRED_SESSION_TTL_MS = 10 * 60 * 1e3;
272438
+ DEFAULT_HTTP_BODY_LIMIT_BYTES = 10 * 1024 * 1024;
272439
+ MAX_HTTP_BODY_LIMIT_BYTES = 64 * 1024 * 1024;
272440
+ MIN_HTTP_BODY_LIMIT_BYTES = 64 * 1024;
272094
272441
  MCP_SDK_COMPAT_PROTOCOL_VERSION = "2025-11-25";
272442
+ HttpPayloadTooLargeError = class extends Error {
272443
+ statusCode = 413;
272444
+ constructor(limitBytes) {
272445
+ super(`Request body exceeds the ${limitBytes} byte limit.`);
272446
+ this.name = "HttpPayloadTooLargeError";
272447
+ }
272448
+ };
272095
272449
  serve_http_default = defineCommand({
272096
272450
  meta: {
272097
272451
  name: "serve-http",
@@ -272128,7 +272482,7 @@ var init_serve_http = __esm({
272128
272482
  const { createProjectBindingController: createProjectBindingController2 } = await Promise.resolve().then(() => (init_request_context(), request_context_exports));
272129
272483
  const { findGitInSubdirs: findGitInSubdirs2 } = await Promise.resolve().then(() => (init_detector(), detector_exports));
272130
272484
  const { writeFileSync: writeFileSync13, mkdirSync: mkdirSync13 } = await import("fs");
272131
- const { homedir: homedir37 } = await import("os");
272485
+ const { homedir: homedir38 } = await import("os");
272132
272486
  const { McpBindingStore: McpBindingStore2 } = await Promise.resolve().then(() => (init_mcp_binding_store(), mcp_binding_store_exports));
272133
272487
  const { MemoryFeedbackStore: MemoryFeedbackStore2 } = await Promise.resolve().then(() => (init_feedback(), feedback_exports));
272134
272488
  const { createModernMcpBridge: createModernMcpBridge2 } = await Promise.resolve().then(() => (init_modern_mcp_bridge(), modern_mcp_bridge_exports));
@@ -272144,12 +272498,20 @@ var init_serve_http = __esm({
272144
272498
  if (resolved.transportNotice) console.error(resolved.transportNotice);
272145
272499
  const port = resolved.port;
272146
272500
  const host = args.host || "127.0.0.1";
272501
+ const bindSecurity = validateHttpBindSecurity(host);
272502
+ const httpAuthToken = process.env.MEMORIX_HTTP_AUTH_TOKEN?.trim() || "";
272503
+ if (bindSecurity.explicitlyUnauthenticated && !isLoopbackHost(host)) {
272504
+ console.error(`[memorix] WARNING: HTTP control plane is unauthenticated on ${host}; keep it behind a private network or authenticated proxy.`);
272505
+ }
272506
+ if (bindSecurity.requiresAuth) {
272507
+ console.error("[memorix] HTTP control plane authentication is enabled (Bearer token).");
272508
+ }
272147
272509
  const toolProfile = resolveToolProfile({ explicit: args.mode, envValue: process.env.MEMORIX_MODE, fallback: "team" });
272148
272510
  let safeCwd;
272149
272511
  try {
272150
272512
  safeCwd = process.cwd();
272151
272513
  } catch {
272152
- safeCwd = homedir37();
272514
+ safeCwd = homedir38();
272153
272515
  }
272154
272516
  const { resolveServeProject: resolveServeProject2 } = await Promise.resolve().then(() => (init_serve_shared(), serve_shared_exports));
272155
272517
  const { writeLastProjectRoot: writeLastProjectRoot2 } = await Promise.resolve().then(() => (init_launch_root(), launch_root_exports));
@@ -272160,7 +272522,7 @@ var init_serve_http = __esm({
272160
272522
  cwdArg: args.cwd,
272161
272523
  envProjectRoot: process.env.MEMORIX_PROJECT_ROOT,
272162
272524
  processCwd: safeCwd,
272163
- homeDir: homedir37()
272525
+ homeDir: homedir38()
272164
272526
  },
272165
272527
  { detectProject: detectorMod.detectProject, findGitInSubdirs: detectorMod.findGitInSubdirs, isSystemDirectory: detectorMod.isSystemDirectory }
272166
272528
  );
@@ -272174,6 +272536,7 @@ var init_serve_http = __esm({
272174
272536
  console.error(`[memorix] HTTP transport starting on ${host}:${port}`);
272175
272537
  console.error(`[memorix] Project root: ${projectRoot}`);
272176
272538
  const sessionTimeoutMs = parseSessionTimeoutMs(process.env.MEMORIX_SESSION_TIMEOUT_MS);
272539
+ const httpBodyLimitBytes = parseHttpBodyLimit(process.env.MEMORIX_HTTP_MAX_BODY_BYTES);
272177
272540
  console.error(
272178
272541
  sessionTimeoutMs === 0 ? "[memorix] HTTP session idle timeout: disabled" : `[memorix] HTTP session idle timeout: ${Math.round(sessionTimeoutMs / 6e4)}min (${sessionTimeoutMs}ms)`
272179
272542
  );
@@ -272244,21 +272607,6 @@ var init_serve_http = __esm({
272244
272607
  res.writeHead(404, { "Content-Type": "text/plain" });
272245
272608
  res.end(message);
272246
272609
  }
272247
- function parseBody(req) {
272248
- return new Promise((resolve9, reject) => {
272249
- const chunks = [];
272250
- req.on("data", (chunk) => chunks.push(chunk));
272251
- req.on("end", () => {
272252
- try {
272253
- const body = Buffer.concat(chunks).toString("utf-8");
272254
- resolve9(body ? JSON.parse(body) : void 0);
272255
- } catch (err) {
272256
- reject(err);
272257
- }
272258
- });
272259
- req.on("error", reject);
272260
- });
272261
- }
272262
272610
  function patchMcpResponse(res) {
272263
272611
  const origWriteHead = res.writeHead;
272264
272612
  const origWrite = res.write;
@@ -272348,7 +272696,7 @@ var init_serve_http = __esm({
272348
272696
  res.setHeader("Vary", "Origin");
272349
272697
  }
272350
272698
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
272351
- res.setHeader("Access-Control-Allow-Headers", "Content-Type, Accept, Mcp-Session-Id, Mcp-Project-Handle, Mcp-Project-Root, Mcp-Stateless, Last-Event-Id");
272699
+ res.setHeader("Access-Control-Allow-Headers", "Authorization, Content-Type, Accept, Mcp-Session-Id, Mcp-Project-Handle, Mcp-Project-Root, Mcp-Stateless, Last-Event-Id");
272352
272700
  res.setHeader("Access-Control-Expose-Headers", "Mcp-Session-Id, Mcp-Project-Handle, Mcp-Protocol-Version, Mcp-Method, Mcp-Name");
272353
272701
  res.setHeader("Mcp-Protocol-Version", MCP_SDK_COMPAT_PROTOCOL_VERSION);
272354
272702
  res.setHeader("Mcp-Method", req.method || "UNKNOWN");
@@ -272356,7 +272704,7 @@ var init_serve_http = __esm({
272356
272704
  }
272357
272705
  async function handlePost(req, res) {
272358
272706
  const sessionId = req.headers["mcp-session-id"];
272359
- const body = await parseBody(req);
272707
+ const body = await parseJsonBody(req, httpBodyLimitBytes);
272360
272708
  const params = body && typeof body === "object" && !Array.isArray(body) ? body.params : void 0;
272361
272709
  const claimlessDiscovery = !sessionId && isServerDiscoverRequest(body) && !req.headers["mcp-protocol-version"] && !req.headers["mcp-method"] && req.headers["mcp-stateless"] !== "true" && (params === void 0 || typeof params === "object" && params !== null && !Array.isArray(params) && !Object.prototype.hasOwnProperty.call(params, "_meta"));
272362
272710
  if (claimlessDiscovery) {
@@ -272660,7 +273008,7 @@ var init_serve_http = __esm({
272660
273008
  sendJson2({ error: "Maintenance actions require POST." }, 405);
272661
273009
  return;
272662
273010
  }
272663
- const bodyValue = await parseBody(req);
273011
+ const bodyValue = await parseJsonBody(req, Math.min(httpBodyLimitBytes, 1024 * 1024));
272664
273012
  const body = bodyValue && typeof bodyValue === "object" && !Array.isArray(bodyValue) ? bodyValue : {};
272665
273013
  const { projectId: maintenanceProjectId, dataDir: maintenanceDataDir } = await resolveRequestProject(url2);
272666
273014
  const maintenanceStore = await getDashboardObservationStore(maintenanceDataDir);
@@ -272795,9 +273143,9 @@ var init_serve_http = __esm({
272795
273143
  }
272796
273144
  if (apiPath === "/stats") {
272797
273145
  const { projectId: statsProjectId, dataDir: statsDataDir } = await resolveRequestProject(url2);
272798
- const { initGraphStore: initGraphStore2, getGraphStore: getGraphStore2 } = await Promise.resolve().then(() => (init_graph_store(), graph_store_exports));
272799
- await initGraphStore2(statsDataDir);
272800
- const graph = { entities: getGraphStore2().loadEntities(), relations: getGraphStore2().loadRelations() };
273146
+ const { initGraphStore: initGraphStore2 } = await Promise.resolve().then(() => (init_graph_store(), graph_store_exports));
273147
+ const gStore = await initGraphStore2(statsDataDir, statsProjectId);
273148
+ const graph = { entities: gStore.loadEntities(), relations: gStore.loadRelations() };
272801
273149
  const observations2 = await loadDashboardProjectObservations(statsDataDir, statsProjectId, "active");
272802
273150
  const feedback = await loadProjectFeedback(statsDataDir, statsProjectId);
272803
273151
  const statsStore = await getDashboardObservationStore(statsDataDir);
@@ -272959,9 +273307,9 @@ var init_serve_http = __esm({
272959
273307
  }
272960
273308
  if (apiPath === "/graph") {
272961
273309
  const { projectId: graphProjectId, dataDir: graphDataDir } = await resolveRequestProject(url2);
272962
- const { initGraphStore: initGraphStore2, getGraphStore: getGraphStore2 } = await Promise.resolve().then(() => (init_graph_store(), graph_store_exports));
272963
- await initGraphStore2(graphDataDir);
272964
- const fullGraph = { entities: getGraphStore2().loadEntities(), relations: getGraphStore2().loadRelations() };
273310
+ const { initGraphStore: initGraphStore2 } = await Promise.resolve().then(() => (init_graph_store(), graph_store_exports));
273311
+ const gStore = await initGraphStore2(graphDataDir, graphProjectId);
273312
+ const fullGraph = { entities: gStore.loadEntities(), relations: gStore.loadRelations() };
272965
273313
  const allObs = await loadDashboardProjectObservations(graphDataDir, graphProjectId, "active");
272966
273314
  const scoped = scopeKnowledgeGraphToProject(fullGraph, allObs);
272967
273315
  sendJson2({ entities: scoped.entities, relations: scoped.relations });
@@ -273034,12 +273382,12 @@ var init_serve_http = __esm({
273034
273382
  const { projectId: kgProjectId, dataDir: kgDataDir } = await resolveRequestProject(url2);
273035
273383
  const { generateKnowledgeGraph: generateKnowledgeGraph2 } = await Promise.resolve().then(() => (init_knowledge_graph(), knowledge_graph_exports));
273036
273384
  const { initMiniSkillStore: initMiniSkillStore2, getMiniSkillStore: getMiniSkillStore2 } = await Promise.resolve().then(() => (init_mini_skill_store(), mini_skill_store_exports));
273037
- const { initGraphStore: initGraphStore2, getGraphStore: getGraphStore2 } = await Promise.resolve().then(() => (init_graph_store(), graph_store_exports));
273385
+ const { initGraphStore: initGraphStore2 } = await Promise.resolve().then(() => (init_graph_store(), graph_store_exports));
273038
273386
  await initMiniSkillStore2(kgDataDir);
273039
- await initGraphStore2(kgDataDir);
273387
+ const gStore = await initGraphStore2(kgDataDir, kgProjectId);
273040
273388
  const allObs = await loadDashboardProjectObservations(kgDataDir, kgProjectId, "active");
273041
273389
  const skills = await getMiniSkillStore2().loadByProject(kgProjectId);
273042
- const fullGraph = { entities: getGraphStore2().loadEntities(), relations: getGraphStore2().loadRelations() };
273390
+ const fullGraph = { entities: gStore.loadEntities(), relations: gStore.loadRelations() };
273043
273391
  const scoped = scopeKnowledgeGraphToProject(fullGraph, allObs);
273044
273392
  const graph = generateKnowledgeGraph2({
273045
273393
  projectId: kgProjectId,
@@ -273055,7 +273403,7 @@ var init_serve_http = __esm({
273055
273403
  const { projectId: configProjectId } = await resolveRequestProject(url2);
273056
273404
  const os10 = await import("os");
273057
273405
  const { existsSync: existsSync36 } = await import("fs");
273058
- const { join: join35 } = await import("path");
273406
+ const { join: join36 } = await import("path");
273059
273407
  const { loadYamlConfig: loadYamlConfig2 } = await Promise.resolve().then(() => (init_yaml_loader(), yaml_loader_exports));
273060
273408
  const { loadFileConfig: loadFileConfig2, loadDotenv: loadDotenv2, getLoadedEnvFiles: getLoadedEnvFiles2 } = await Promise.resolve().then(() => (init_config(), config_exports));
273061
273409
  const isStartupProject = configProjectId === defaultProject.id;
@@ -273069,11 +273417,11 @@ var init_serve_http = __esm({
273069
273417
  const home = os10.homedir();
273070
273418
  if (effectiveRoot) {
273071
273419
  const paths = {
273072
- "project memorix.yml": join35(effectiveRoot, "memorix.yml"),
273073
- "user memorix.yml": join35(home, ".memorix", "memorix.yml"),
273074
- "project .env": join35(effectiveRoot, ".env"),
273075
- "user .env": join35(home, ".memorix", ".env"),
273076
- "legacy config.json": join35(home, ".memorix", "config.json")
273420
+ "project memorix.yml": join36(effectiveRoot, "memorix.yml"),
273421
+ "user memorix.yml": join36(home, ".memorix", "memorix.yml"),
273422
+ "project .env": join36(effectiveRoot, ".env"),
273423
+ "user .env": join36(home, ".memorix", ".env"),
273424
+ "legacy config.json": join36(home, ".memorix", "config.json")
273077
273425
  };
273078
273426
  for (const [key, fpath] of Object.entries(paths)) {
273079
273427
  files[key] = { exists: existsSync36(fpath), path: fpath };
@@ -273082,9 +273430,9 @@ var init_serve_http = __esm({
273082
273430
  files["project memorix.yml"] = { exists: false, path: "unavailable", unavailable: true };
273083
273431
  files["project .env"] = { exists: false, path: "unavailable", unavailable: true };
273084
273432
  const userPaths = {
273085
- "user memorix.yml": join35(home, ".memorix", "memorix.yml"),
273086
- "user .env": join35(home, ".memorix", ".env"),
273087
- "legacy config.json": join35(home, ".memorix", "config.json")
273433
+ "user memorix.yml": join36(home, ".memorix", "memorix.yml"),
273434
+ "user .env": join36(home, ".memorix", ".env"),
273435
+ "legacy config.json": join36(home, ".memorix", "config.json")
273088
273436
  };
273089
273437
  for (const [key, fpath] of Object.entries(userPaths)) {
273090
273438
  files[key] = { exists: existsSync36(fpath), path: fpath };
@@ -273300,9 +273648,8 @@ var init_serve_http = __esm({
273300
273648
  } else {
273301
273649
  await store2.remove(obsId);
273302
273650
  try {
273303
- const { initGraphStore: initGraphStore2, getGraphStore: getGraphStore2 } = await Promise.resolve().then(() => (init_graph_store(), graph_store_exports));
273304
- await initGraphStore2(delDataDir);
273305
- const gStore = getGraphStore2();
273651
+ const { initGraphStore: initGraphStore2 } = await Promise.resolve().then(() => (init_graph_store(), graph_store_exports));
273652
+ const gStore = await initGraphStore2(delDataDir, delProjectId);
273306
273653
  const prefix = `[#${obsId}] `;
273307
273654
  const deletions = [];
273308
273655
  for (const entity of gStore.loadEntities()) {
@@ -273318,9 +273665,9 @@ var init_serve_http = __esm({
273318
273665
  }
273319
273666
  if (apiPath === "/export") {
273320
273667
  const { projectId: expProjectId, projectName: expProjectName, dataDir: expDataDir } = await resolveRequestProject(url2);
273321
- const { initGraphStore: initGraphStore2, getGraphStore: getGraphStore2 } = await Promise.resolve().then(() => (init_graph_store(), graph_store_exports));
273322
- await initGraphStore2(expDataDir);
273323
- const fullGraph = { entities: getGraphStore2().loadEntities(), relations: getGraphStore2().loadRelations() };
273668
+ const { initGraphStore: initGraphStore2 } = await Promise.resolve().then(() => (init_graph_store(), graph_store_exports));
273669
+ const gStore = await initGraphStore2(expDataDir, expProjectId);
273670
+ const fullGraph = { entities: gStore.loadEntities(), relations: gStore.loadRelations() };
273324
273671
  const observations2 = await loadDashboardProjectObservations(expDataDir, expProjectId, "active");
273325
273672
  const expStore = await getDashboardObservationStore(expDataDir);
273326
273673
  const nextId2 = await expStore.loadIdCounter();
@@ -273358,7 +273705,8 @@ var init_serve_http = __esm({
273358
273705
  }, 503);
273359
273706
  return;
273360
273707
  }
273361
- sendJson2({ error: err instanceof Error ? err.message : "Unknown error" }, 500);
273708
+ const status = err instanceof HttpPayloadTooLargeError ? err.statusCode : 500;
273709
+ sendJson2({ error: err instanceof Error ? err.message : "Unknown error" }, status);
273362
273710
  }
273363
273711
  }
273364
273712
  let httpReadyAt = null;
@@ -273370,6 +273718,14 @@ var init_serve_http = __esm({
273370
273718
  return;
273371
273719
  }
273372
273720
  const url2 = new URL(req.url || "/", `http://localhost:${port}`);
273721
+ if (bindSecurity.requiresAuth && url2.pathname !== "/health" && !isAuthorizedHttpRequest(req, httpAuthToken)) {
273722
+ res.writeHead(401, {
273723
+ "Content-Type": "application/json",
273724
+ "WWW-Authenticate": "Bearer"
273725
+ });
273726
+ res.end(JSON.stringify({ error: "Authentication required" }));
273727
+ return;
273728
+ }
273373
273729
  if (url2.pathname === "/health") {
273374
273730
  const { getEmbeddingRuntimeHealth: getEmbeddingRuntimeHealth2 } = await Promise.resolve().then(() => (init_provider(), provider_exports));
273375
273731
  const memory = process.memoryUsage();
@@ -273440,10 +273796,15 @@ var init_serve_http = __esm({
273440
273796
  } catch (err) {
273441
273797
  console.error("[memorix] HTTP handler error:", err);
273442
273798
  if (!res.headersSent) {
273443
- res.writeHead(500, { "Content-Type": "application/json" });
273799
+ const status = err instanceof HttpPayloadTooLargeError ? err.statusCode : 500;
273800
+ const message = err instanceof Error ? err.message : "Internal server error";
273801
+ res.writeHead(status, { "Content-Type": "application/json" });
273444
273802
  res.end(JSON.stringify({
273445
273803
  jsonrpc: "2.0",
273446
- error: { code: -32603, message: "Internal server error" },
273804
+ error: {
273805
+ code: status === 413 ? -32013 : -32603,
273806
+ message: status === 413 ? message : "Internal server error"
273807
+ },
273447
273808
  id: null
273448
273809
  }));
273449
273810
  }
@@ -273458,31 +273819,62 @@ var init_serve_http = __esm({
273458
273819
  });
273459
273820
  httpServer.keepAliveTimeout = 6e4;
273460
273821
  httpServer.headersTimeout = 65e3;
273461
- httpServer.listen(port, host, () => {
273462
- httpReadyAt = (/* @__PURE__ */ new Date()).toISOString();
273463
- try {
273464
- const memorixDir = (process.env.HOME || process.env.USERPROFILE || "").replace(/\\/g, "/") + "/.memorix";
273465
- mkdirSync13(memorixDir, { recursive: true });
273466
- writeFileSync13(memorixDir + "/background.ready", JSON.stringify({ pid: process.pid, port, readyAt: Date.now() }));
273467
- } catch {
273822
+ const listenPromise = new Promise((resolve9, reject) => {
273823
+ const onError = (error40) => {
273824
+ if (typeof httpServer.off === "function") httpServer.off("listening", onListening);
273825
+ reject(error40);
273826
+ };
273827
+ const onListening = () => {
273828
+ if (typeof httpServer.off === "function") httpServer.off("error", onError);
273829
+ httpReadyAt = (/* @__PURE__ */ new Date()).toISOString();
273830
+ try {
273831
+ const memorixDir = (process.env.HOME || process.env.USERPROFILE || "").replace(/\\/g, "/") + "/.memorix";
273832
+ mkdirSync13(memorixDir, { recursive: true });
273833
+ writeFileSync13(memorixDir + "/background.ready", JSON.stringify({ pid: process.pid, port, readyAt: Date.now() }));
273834
+ } catch {
273835
+ }
273836
+ console.error(`[memorix] \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`);
273837
+ console.error(`[memorix] Mode: Control Plane (HTTP MCP + multi-session live dashboard)`);
273838
+ console.error(`[memorix] MCP Server listening on http://${host}:${port}/mcp`);
273839
+ console.error(`[memorix] Dashboard: http://${host}:${port}/`);
273840
+ console.error(`[memorix] Coordination: http://${host}:${port}/api/team`);
273841
+ console.error(`[memorix] Port: ${port}`);
273842
+ console.error(`[memorix] \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`);
273843
+ console.error(`[memorix] Sessions at startup: ${sessions.size} (live count available at /api/team)`);
273844
+ console.error(`[memorix] Agents can connect via: { "transport": "http", "url": "http://localhost:${port}/mcp" }`);
273845
+ Promise.resolve().then(() => (init_update_checker(), update_checker_exports)).then((m6) => m6.checkForUpdates()).catch(() => {
273846
+ });
273847
+ resolve9();
273848
+ };
273849
+ if (typeof httpServer.once === "function") {
273850
+ httpServer.once("error", onError);
273851
+ httpServer.once("listening", onListening);
273852
+ httpServer.listen(port, host);
273853
+ } else {
273854
+ httpServer.listen(port, host, onListening);
273468
273855
  }
273469
- console.error(`[memorix] \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`);
273470
- console.error(`[memorix] Mode: Control Plane (HTTP MCP + multi-session live dashboard)`);
273471
- console.error(`[memorix] MCP Server listening on http://${host}:${port}/mcp`);
273472
- console.error(`[memorix] Dashboard: http://${host}:${port}/`);
273473
- console.error(`[memorix] Coordination: http://${host}:${port}/api/team`);
273474
- console.error(`[memorix] Port: ${port}`);
273475
- console.error(`[memorix] \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`);
273476
- console.error(`[memorix] Sessions at startup: ${sessions.size} (live count available at /api/team)`);
273477
- console.error(`[memorix] Agents can connect via: { "transport": "http", "url": "http://localhost:${port}/mcp" }`);
273478
- Promise.resolve().then(() => (init_update_checker(), update_checker_exports)).then((m6) => m6.checkForUpdates()).catch(() => {
273479
- });
273480
273856
  });
273857
+ try {
273858
+ await listenPromise;
273859
+ } catch (error40) {
273860
+ console.error(`[memorix] HTTP server failed to listen on ${host}:${port}: ${error40 instanceof Error ? error40.message : String(error40)}`);
273861
+ for (const store2 of dashboardObservationStores.values()) {
273862
+ try {
273863
+ store2.close();
273864
+ } catch {
273865
+ }
273866
+ }
273867
+ dashboardObservationStores.clear();
273868
+ const { closeAllDatabases: closeAllDatabases2 } = await Promise.resolve().then(() => (init_sqlite_db(), sqlite_db_exports));
273869
+ closeAllDatabases2();
273870
+ throw error40;
273871
+ }
273481
273872
  const SESSION_TIMEOUT_MS = sessionTimeoutMs;
273482
273873
  const gcInterval = setInterval(() => {
273483
273874
  maybeProbeSummary();
273484
273875
  const now3 = Date.now();
273485
273876
  forgetExpiredSessions(now3);
273877
+ statelessBindingStore.cleanupExpired(now3);
273486
273878
  if (SESSION_TIMEOUT_MS === 0) return;
273487
273879
  for (const [sid, state] of sessions) {
273488
273880
  const lastActive = sessionLastActivity.get(sid) ?? 0;
@@ -273497,7 +273889,10 @@ var init_serve_http = __esm({
273497
273889
  }
273498
273890
  }, 6e4);
273499
273891
  gcInterval.unref();
273892
+ let shutdownStarted = false;
273500
273893
  const shutdown = async () => {
273894
+ if (shutdownStarted) return;
273895
+ shutdownStarted = true;
273501
273896
  if (suppressedProbeCount > 0) {
273502
273897
  console.error(`[memorix] ${suppressedProbeCount} probe connection(s) suppressed during this session`);
273503
273898
  }
@@ -273531,7 +273926,17 @@ var init_serve_http = __esm({
273531
273926
  console.error(`[memorix] Error closing session ${sid}:`, err);
273532
273927
  }
273533
273928
  }
273534
- httpServer.close();
273929
+ clearInterval(gcInterval);
273930
+ clearInterval(heartbeatInterval);
273931
+ await new Promise((resolve9) => {
273932
+ if (!httpServer.listening) {
273933
+ resolve9();
273934
+ return;
273935
+ }
273936
+ httpServer.close(() => resolve9());
273937
+ });
273938
+ const { closeAllDatabases: closeAllDatabases2 } = await Promise.resolve().then(() => (init_sqlite_db(), sqlite_db_exports));
273939
+ closeAllDatabases2();
273535
273940
  process.exit(0);
273536
273941
  };
273537
273942
  process.on("SIGINT", shutdown);
@@ -273570,7 +273975,13 @@ var init_serve_http = __esm({
273570
273975
  });
273571
273976
  _testing2 = {
273572
273977
  DEFAULT_SESSION_TIMEOUT_MS,
273978
+ DEFAULT_HTTP_BODY_LIMIT_BYTES,
273979
+ MAX_HTTP_BODY_LIMIT_BYTES,
273980
+ MIN_HTTP_BODY_LIMIT_BYTES,
273573
273981
  parseSessionTimeoutMs,
273982
+ parseHttpBodyLimit,
273983
+ isLoopbackHost,
273984
+ validateHttpBindSecurity,
273574
273985
  parseTcpPortOrReport
273575
273986
  };
273576
273987
  }
@@ -274021,8 +274432,10 @@ var init_journal = __esm({
274021
274432
  });
274022
274433
 
274023
274434
  // src/sync/policy.ts
274024
- function syncEligibility(observation, projectId) {
274025
- if (observation.projectId !== projectId) return { eligible: false, reason: "other-project" };
274435
+ function syncEligibility(observation, scopeId, scope = "project") {
274436
+ if (scope === "project" && observation.projectId !== scopeId) {
274437
+ return { eligible: false, reason: "other-project" };
274438
+ }
274026
274439
  if ((observation.visibility ?? "project") !== "project") return { eligible: false, reason: "non-project-visibility" };
274027
274440
  if ((observation.sharedWithAgentIds?.length ?? 0) > 0) {
274028
274441
  return { eligible: false, reason: "agent-targeted" };
@@ -274033,11 +274446,11 @@ function syncEligibility(observation, projectId) {
274033
274446
  if (observation.valueCategory === "ephemeral") return { eligible: false, reason: "ephemeral-value" };
274034
274447
  return { eligible: true };
274035
274448
  }
274036
- function eligibleObservations(observations2, projectId) {
274449
+ function eligibleObservations(observations2, scopeId, scope = "project") {
274037
274450
  const eligible = [];
274038
274451
  let excluded = 0;
274039
274452
  for (const observation of observations2) {
274040
- if (syncEligibility(observation, projectId).eligible) eligible.push(observation);
274453
+ if (syncEligibility(observation, scopeId, scope).eligible) eligible.push(observation);
274041
274454
  else excluded++;
274042
274455
  }
274043
274456
  return { eligible, excluded };
@@ -274054,13 +274467,26 @@ import { createHash as createHash22 } from "crypto";
274054
274467
  function syncNamespace(projectId) {
274055
274468
  return `project-${createHash22("sha256").update(projectId).digest("hex").slice(0, 32)}`;
274056
274469
  }
274470
+ function userSyncNamespace(env3 = process.env) {
274471
+ const override = env3.MEMORIX_SYNC_USER_NAMESPACE?.trim();
274472
+ if (!override) {
274473
+ throw new Error("[memorix] MEMORIX_SYNC_USER_NAMESPACE is required for --scope user; use one stable per-user namespace across devices");
274474
+ }
274475
+ if (!isSafeSyncDeviceId(override)) {
274476
+ throw new Error("[memorix] MEMORIX_SYNC_USER_NAMESPACE must match [A-Za-z0-9][A-Za-z0-9._-]{0,127}");
274477
+ }
274478
+ return `${USER_SYNC_NAMESPACE}${override}`;
274479
+ }
274057
274480
  function isSafeSyncDeviceId(value) {
274058
274481
  return /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(value);
274059
274482
  }
274483
+ var USER_SYNC_SCOPE_ID, USER_SYNC_NAMESPACE;
274060
274484
  var init_namespace = __esm({
274061
274485
  "src/sync/namespace.ts"() {
274062
274486
  "use strict";
274063
274487
  init_esm_shims();
274488
+ USER_SYNC_SCOPE_ID = "__user__";
274489
+ USER_SYNC_NAMESPACE = "user-";
274064
274490
  }
274065
274491
  });
274066
274492
 
@@ -274071,6 +274497,7 @@ async function runSync(store2, remote, opts) {
274071
274497
  remote: remote.kind,
274072
274498
  namespace: remote.namespace,
274073
274499
  projectId: store2.projectId(),
274500
+ scope: store2.scope(),
274074
274501
  deviceId: opts.deviceId,
274075
274502
  pushed: 0,
274076
274503
  pulledBatches: 0,
@@ -274098,7 +274525,7 @@ async function runSync(store2, remote, opts) {
274098
274525
  report.pending = pending.length;
274099
274526
  if (!opts.dryRun) {
274100
274527
  for (const pendingBatch of pending) {
274101
- validateBatches([pendingBatch.batch], store2.projectId(), remote.namespace);
274528
+ validateBatches([pendingBatch.batch], store2.projectId(), remote.namespace, store2.scope());
274102
274529
  if (pendingBatch.batch.deviceId !== opts.deviceId) {
274103
274530
  throw new Error("[memorix] sync outbox rejected: batch belongs to another device");
274104
274531
  }
@@ -274110,7 +274537,7 @@ async function runSync(store2, remote, opts) {
274110
274537
  report.pending = 0;
274111
274538
  }
274112
274539
  const current = await store2.loadAll();
274113
- const filtered = eligibleObservations(current, store2.projectId());
274540
+ const filtered = eligibleObservations(current, store2.projectId(), store2.scope());
274114
274541
  report.eligible = filtered.eligible.length;
274115
274542
  report.excluded = filtered.excluded;
274116
274543
  const state = await store2.loadState();
@@ -274133,7 +274560,7 @@ async function runSync(store2, remote, opts) {
274133
274560
  producedAt: nowIso(),
274134
274561
  entries: chunk.map((c6) => c6.entry)
274135
274562
  };
274136
- if (!opts.dryRun) validateBatches([batch], store2.projectId(), remote.namespace);
274563
+ if (!opts.dryRun) validateBatches([batch], store2.projectId(), remote.namespace, store2.scope());
274137
274564
  report.pushed += chunk.length;
274138
274565
  if (!opts.dryRun) {
274139
274566
  const states = chunk.map((c6) => ({ ...c6.nextState, shippedSeq: sequence }));
@@ -274154,7 +274581,7 @@ async function runSync(store2, remote, opts) {
274154
274581
  while (hasMore) {
274155
274582
  const page = await remote.pull(pullSince, PULL_PAGE_SIZE, pageToken);
274156
274583
  report.pulledBatches += page.batches.length;
274157
- validateBatches(page.batches, store2.projectId(), remote.namespace);
274584
+ validateBatches(page.batches, store2.projectId(), remote.namespace, store2.scope());
274158
274585
  for (const batch of page.batches) {
274159
274586
  if (batch.deviceId === opts.deviceId) {
274160
274587
  cursor.applied[batch.deviceId] = Math.max(cursor.applied[batch.deviceId] ?? 0, batch.sequence);
@@ -274224,7 +274651,7 @@ async function runSync(store2, remote, opts) {
274224
274651
  await remote.close();
274225
274652
  }
274226
274653
  }
274227
- function validateBatches(batches, projectId, namespace) {
274654
+ function validateBatches(batches, projectId, namespace, scope = "project") {
274228
274655
  for (const batch of batches) {
274229
274656
  if (batch.formatVersion !== 3 || batch.projectId !== projectId || batch.namespace !== namespace) {
274230
274657
  throw new Error("[memorix] sync batch rejected: wrong format, project, or remote namespace");
@@ -274236,7 +274663,7 @@ function validateBatches(batches, projectId, namespace) {
274236
274663
  if (!entry.syncKey || entry.kind !== "upsert" && entry.kind !== "tombstone" || !entry.version || !Number.isSafeInteger(entry.version.revision) || entry.version.revision < 1 || !entry.version.writer || typeof entry.contentHash !== "string" || entry.kind === "tombstone" && (entry.row !== void 0 || entry.contentHash !== "") || entry.kind === "upsert" && (!/^[a-f0-9]{64}$/.test(entry.contentHash) || entry.row === void 0)) {
274237
274664
  throw new Error("[memorix] sync batch rejected: malformed change entry");
274238
274665
  }
274239
- if (entry.kind === "upsert" && (!Number.isSafeInteger(entry.row.id) || entry.row.id < 1 || !eligibleObservations([entry.row], projectId).eligible.length)) {
274666
+ if (entry.kind === "upsert" && (!Number.isSafeInteger(entry.row.id) || entry.row.id < 1 || !eligibleObservations([entry.row], projectId, scope).eligible.length)) {
274240
274667
  throw new Error("[memorix] sync batch rejected: ineligible observation payload");
274241
274668
  }
274242
274669
  const incomingHash = entry.row ? contentHash(entry.row) : "";
@@ -274308,9 +274735,11 @@ var init_engine6 = __esm({
274308
274735
  // src/sync/store-port.ts
274309
274736
  import os7 from "os";
274310
274737
  import { randomUUID as randomUUID20 } from "crypto";
274311
- function createSqliteSyncStore(dataDir, store2, projectId) {
274738
+ function createSqliteSyncStore(dataDir, store2, projectId, options = {}) {
274312
274739
  const db2 = getDatabase(dataDir);
274313
- ensureSyncSchema(db2, projectId);
274740
+ const scope = options.scope ?? "project";
274741
+ const scopeId = scope === "user" ? USER_SYNC_SCOPE_ID : projectId;
274742
+ ensureSyncSchema(db2, scope === "user" ? LEGACY_PROJECT_ID : projectId);
274314
274743
  const getMeta = db2.prepare("SELECT value FROM sync_meta WHERE key = ?");
274315
274744
  const setMeta = db2.prepare("INSERT OR REPLACE INTO sync_meta (key, value) VALUES (?, ?)");
274316
274745
  const selState = db2.prepare("SELECT projectId, syncKey, obsId, revision, writer, kind, contentHash, shippedSeq FROM sync_row_state WHERE projectId = ?");
@@ -274350,7 +274779,7 @@ function createSqliteSyncStore(dataDir, store2, projectId) {
274350
274779
  SET deviceId = ?, batchJson = ?
274351
274780
  WHERE projectId = ? AND namespace = ? AND deviceId = ? AND sequence = ?`
274352
274781
  );
274353
- const namespace = syncNamespace(projectId);
274782
+ const namespace = scope === "user" ? userSyncNamespace() : syncNamespace(projectId);
274354
274783
  const deviceFingerprint = process.env.MEMORIX_SYNC_DEVICE_FINGERPRINT?.trim() || `${process.platform}|${os7.hostname()}|${os7.homedir()}`;
274355
274784
  function ensureDevice() {
274356
274785
  const row = getMeta.get(DEVICE_KEY);
@@ -274378,7 +274807,8 @@ function createSqliteSyncStore(dataDir, store2, projectId) {
274378
274807
  return `cursor:${namespace}`;
274379
274808
  }
274380
274809
  return {
274381
- projectId: () => projectId,
274810
+ projectId: () => scopeId,
274811
+ scope: () => scope,
274382
274812
  namespace: () => namespace,
274383
274813
  deviceId: ensureDevice,
274384
274814
  assertCanPublish,
@@ -274418,14 +274848,14 @@ function createSqliteSyncStore(dataDir, store2, projectId) {
274418
274848
  setMeta.run(cursorKey(), JSON.stringify(cursor));
274419
274849
  },
274420
274850
  async loadPendingBatches() {
274421
- return selPending.all(projectId, namespace, ensureDevice()).map((row) => ({
274851
+ return selPending.all(scopeId, namespace, ensureDevice()).map((row) => ({
274422
274852
  batch: JSON.parse(row.batchJson),
274423
274853
  states: JSON.parse(row.statesJson)
274424
274854
  }));
274425
274855
  },
274426
274856
  async enqueueBatch(pending) {
274427
274857
  insertPending.run({
274428
- projectId,
274858
+ projectId: scopeId,
274429
274859
  namespace,
274430
274860
  deviceId: pending.batch.deviceId,
274431
274861
  sequence: pending.batch.sequence,
@@ -274435,13 +274865,13 @@ function createSqliteSyncStore(dataDir, store2, projectId) {
274435
274865
  });
274436
274866
  },
274437
274867
  async markBatchShipped(sequence) {
274438
- deletePending.run(projectId, namespace, ensureDevice(), sequence);
274868
+ deletePending.run(scopeId, namespace, ensureDevice(), sequence);
274439
274869
  },
274440
274870
  async loadAll() {
274441
- return store2.loadByProject(projectId);
274871
+ return scope === "user" ? store2.loadAll() : store2.loadByProject(projectId);
274442
274872
  },
274443
274873
  async loadState() {
274444
- const rows = selState.all(projectId);
274874
+ const rows = selState.all(scopeId);
274445
274875
  const map2 = /* @__PURE__ */ new Map();
274446
274876
  for (const r6 of rows) map2.set(r6.syncKey, r6);
274447
274877
  return map2;
@@ -274455,8 +274885,10 @@ function createSqliteSyncStore(dataDir, store2, projectId) {
274455
274885
  async allocateId() {
274456
274886
  return store2.atomic(async (tx) => {
274457
274887
  const nextId2 = await tx.loadIdCounter();
274458
- await tx.saveIdCounter(nextId2 + 1);
274459
- return nextId2;
274888
+ const maxId = Number(db2.prepare("SELECT COALESCE(MAX(id), 0) AS maxId FROM observations").get()?.maxId ?? 0);
274889
+ const allocated = Math.max(nextId2, maxId + 1);
274890
+ await tx.saveIdCounter(allocated + 1);
274891
+ return allocated;
274460
274892
  });
274461
274893
  },
274462
274894
  async applyInsert(row, state) {
@@ -274486,7 +274918,11 @@ function createSqliteSyncStore(dataDir, store2, projectId) {
274486
274918
  async applyRemove(id, state) {
274487
274919
  await store2.atomic(async (tx) => {
274488
274920
  const existing = await tx.getById(id);
274489
- if (existing && existing.projectId !== state.projectId) {
274921
+ const expectedProjectId = scope === "user" ? sourceProjectIdFromSyncKey(state.syncKey) : state.projectId;
274922
+ if (!expectedProjectId) {
274923
+ throw new Error("[memorix] sync refused to remove an observation without a project-scoped sync key");
274924
+ }
274925
+ if (existing && existing.projectId !== expectedProjectId) {
274490
274926
  throw new Error("[memorix] sync refused to remove an observation from another project");
274491
274927
  }
274492
274928
  await tx.remove(id);
@@ -274515,6 +274951,15 @@ function createSqliteSyncStore(dataDir, store2, projectId) {
274515
274951
  }
274516
274952
  };
274517
274953
  }
274954
+ function sourceProjectIdFromSyncKey(syncKey) {
274955
+ const match2 = /^p:([^:]+):/.exec(syncKey);
274956
+ if (!match2) return void 0;
274957
+ try {
274958
+ return decodeURIComponent(match2[1]);
274959
+ } catch {
274960
+ return void 0;
274961
+ }
274962
+ }
274518
274963
  function ensureSyncSchema(db2, projectId) {
274519
274964
  db2.exec(CREATE_SYNC_TABLES);
274520
274965
  const columns = db2.prepare("PRAGMA table_info(sync_row_state)").all();
@@ -275425,9 +275870,30 @@ var sync_store_exports = {};
275425
275870
  __export(sync_store_exports, {
275426
275871
  default: () => sync_store_default
275427
275872
  });
275873
+ function parseScope(raw) {
275874
+ const value = String(raw || "project").toLowerCase();
275875
+ if (value === "project" || value === "user") return value;
275876
+ throw new Error("--scope must be one of: project, user");
275877
+ }
275878
+ async function openSyncStore(scope) {
275879
+ if (scope === "user") {
275880
+ const dataDir2 = await getProjectDataDir(USER_SYNC_SCOPE_ID);
275881
+ await initObservationStore(dataDir2);
275882
+ return {
275883
+ projectId: USER_SYNC_SCOPE_ID,
275884
+ syncStore: createSqliteSyncStore(dataDir2, getObservationStore(), USER_SYNC_SCOPE_ID, { scope: "user" })
275885
+ };
275886
+ }
275887
+ const { project, dataDir } = await getCliProjectContext();
275888
+ await initObservationStore(dataDir);
275889
+ return {
275890
+ projectId: project.id,
275891
+ syncStore: createSqliteSyncStore(dataDir, getObservationStore(), project.id)
275892
+ };
275893
+ }
275428
275894
  function renderReport(report) {
275429
275895
  const lines = [
275430
- `Store sync (${report.remote}) \u2014 device ${report.deviceId}${report.dryRun ? " [dry-run]" : ""}`,
275896
+ `Store sync (${report.remote}, scope ${report.scope}) \u2014 device ${report.deviceId}${report.dryRun ? " [dry-run]" : ""}`,
275431
275897
  `- Pushed changes: ${report.pushed}`,
275432
275898
  `- Pulled batches: ${report.pulledBatches}`,
275433
275899
  `- Applied upserts: ${report.applied}`,
@@ -275453,9 +275919,11 @@ var init_sync_store = __esm({
275453
275919
  init_dist2();
275454
275920
  init_operator_shared();
275455
275921
  init_obs_store();
275922
+ init_persistence();
275456
275923
  init_engine6();
275457
275924
  init_store_port();
275458
275925
  init_remote_factory();
275926
+ init_namespace();
275459
275927
  sync_store_default = defineCommand({
275460
275928
  meta: {
275461
275929
  name: "sync-store",
@@ -275467,23 +275935,34 @@ var init_sync_store = __esm({
275467
275935
  "push-only": { type: "boolean", description: "Only push local changes", default: false },
275468
275936
  "pull-only": { type: "boolean", description: "Only pull remote changes", default: false },
275469
275937
  through: { type: "string", description: "Compaction cutoff: device=sequence,device=sequence" },
275470
- yes: { type: "boolean", description: "Confirm destructive remote compaction", default: false }
275938
+ yes: { type: "boolean", description: "Confirm destructive remote compaction", default: false },
275939
+ scope: {
275940
+ type: "string",
275941
+ description: "project (default: current Git project) or user (all local projects)",
275942
+ default: "project"
275943
+ }
275471
275944
  },
275472
275945
  run: async ({ args }) => {
275473
275946
  const asJson = Boolean(args.json);
275474
275947
  const action = args._?.[0] || "status";
275948
+ let scope;
275949
+ try {
275950
+ scope = parseScope(args.scope);
275951
+ } catch (err) {
275952
+ emitError(err.message, asJson);
275953
+ process.exitCode = 2;
275954
+ return;
275955
+ }
275475
275956
  if (action === "device") {
275476
- const { project: project2, dataDir: dataDir2 } = await getCliProjectContext();
275477
- await initObservationStore(dataDir2);
275478
- const syncStore2 = createSqliteSyncStore(dataDir2, getObservationStore(), project2.id);
275479
275957
  const subaction = args._?.[1] || "";
275480
275958
  if (subaction !== "rotate") {
275481
275959
  emitError('expected "memorix sync store device rotate"', asJson);
275482
275960
  process.exitCode = 2;
275483
275961
  return;
275484
275962
  }
275963
+ const { projectId: projectId2, syncStore: syncStore2 } = await openSyncStore("user");
275485
275964
  const deviceId = syncStore2.rotateDevice();
275486
- emitResult({ project: project2.id, deviceId, rotated: true }, `Sync device identity rotated: ${deviceId}`, asJson);
275965
+ emitResult({ project: projectId2, scope: "user", deviceId, rotated: true }, `Sync device identity rotated: ${deviceId}`, asJson);
275487
275966
  return;
275488
275967
  }
275489
275968
  if (action !== "push" && action !== "pull" && action !== "status" && action !== "compact") {
@@ -275507,10 +275986,7 @@ var init_sync_store = __esm({
275507
275986
  );
275508
275987
  return;
275509
275988
  }
275510
- const { project, dataDir } = await getCliProjectContext();
275511
- await initObservationStore(dataDir);
275512
- const obsStore = getObservationStore();
275513
- const syncStore = createSqliteSyncStore(dataDir, obsStore, project.id);
275989
+ const { projectId, syncStore } = await openSyncStore(scope);
275514
275990
  if (action === "compact") {
275515
275991
  if (!args.yes && !args.dry) {
275516
275992
  emitError("remote compaction deletes relay events; repeat with --yes after specifying --through device=sequence", asJson);
@@ -275545,7 +276021,7 @@ var init_sync_store = __esm({
275545
276021
  try {
275546
276022
  await remote2.init({ create: false });
275547
276023
  const result = await remote2.compact(through, { dryRun: Boolean(args.dry) });
275548
- emitResult({ project: project.id, through, ...result, dryRun: Boolean(args.dry) }, `Compaction candidates: ${result.candidates}; deleted: ${result.deleted}`, asJson);
276024
+ emitResult({ project: projectId, scope, through, ...result, dryRun: Boolean(args.dry) }, `Compaction candidates: ${result.candidates}; deleted: ${result.deleted}`, asJson);
275549
276025
  } catch (err) {
275550
276026
  emitError(err.message, asJson);
275551
276027
  process.exitCode = 1;
@@ -275570,7 +276046,7 @@ var init_sync_store = __esm({
275570
276046
  push: true,
275571
276047
  pull: true
275572
276048
  });
275573
- emitResult({ project: project.id, ...report }, renderReport(report), asJson);
276049
+ emitResult({ project: projectId, ...report }, renderReport(report), asJson);
275574
276050
  } catch (err) {
275575
276051
  emitError(err.message, asJson);
275576
276052
  process.exitCode = 1;
@@ -275594,7 +276070,7 @@ var init_sync_store = __esm({
275594
276070
  push: doPush,
275595
276071
  pull: doPull
275596
276072
  });
275597
- emitResult({ project: project.id, ...report }, renderReport(report), asJson);
276073
+ emitResult({ project: projectId, ...report }, renderReport(report), asJson);
275598
276074
  } catch (err) {
275599
276075
  emitError(err.message, asJson);
275600
276076
  process.exitCode = 1;
@@ -275656,6 +276132,32 @@ var init_sync = __esm({
275656
276132
  type: "boolean",
275657
276133
  description: "Emit machine-readable JSON output",
275658
276134
  default: false
276135
+ },
276136
+ scope: {
276137
+ type: "string",
276138
+ description: "Store sync scope: project (default) or user",
276139
+ required: false,
276140
+ default: "project"
276141
+ },
276142
+ "push-only": {
276143
+ type: "boolean",
276144
+ description: "Store sync: only push local changes",
276145
+ required: false
276146
+ },
276147
+ "pull-only": {
276148
+ type: "boolean",
276149
+ description: "Store sync: only pull remote changes",
276150
+ required: false
276151
+ },
276152
+ through: {
276153
+ type: "string",
276154
+ description: "Store sync compaction cutoff: device=sequence[,device=sequence]",
276155
+ required: false
276156
+ },
276157
+ yes: {
276158
+ type: "boolean",
276159
+ description: "Store sync: confirm destructive remote compaction",
276160
+ required: false
275659
276161
  }
275660
276162
  },
275661
276163
  run: async ({ args }) => {
@@ -280273,8 +280775,20 @@ async function doStartUnlocked(port) {
280273
280775
  if (healthy) {
280274
280776
  process.stderr.write("[OK] Control plane is running and healthy.\n");
280275
280777
  } else {
280276
- process.stderr.write("[WARN] Health check timed out \u2014 service may still be initializing.\n");
280277
- process.stderr.write(" Check later: memorix background status\n");
280778
+ process.stderr.write("[ERROR] Health check timed out; refusing to leave an unverified background state.\n");
280779
+ if (isProcessRunning(pid)) {
280780
+ killProcess(pid);
280781
+ for (let i4 = 0; i4 < 10 && isProcessRunning(pid); i4++) {
280782
+ await new Promise((r6) => setTimeout(r6, 250));
280783
+ }
280784
+ }
280785
+ clearState();
280786
+ try {
280787
+ fs26.unlinkSync(getMemorixDir() + "/background.ready");
280788
+ } catch {
280789
+ }
280790
+ process.exitCode = 1;
280791
+ return;
280278
280792
  }
280279
280793
  const footer = [
280280
280794
  "",
@@ -280662,7 +281176,7 @@ import { spawnSync as spawnSync2 } from "child_process";
280662
281176
  import { existsSync as existsSync31, realpathSync as realpathSync2 } from "fs";
280663
281177
  import { mkdir as mkdir9, readFile as readFile10, writeFile as writeFile10 } from "fs/promises";
280664
281178
  import { basename as basename5, dirname as dirname8, resolve as resolve7 } from "path";
280665
- import { homedir as homedir35 } from "os";
281179
+ import { homedir as homedir36 } from "os";
280666
281180
  function requestedAgents(agent) {
280667
281181
  const all = getSetupAgentTargets();
280668
281182
  if (!agent || agent === "all") return all;
@@ -280740,16 +281254,16 @@ function asRecordArray(value) {
280740
281254
  return Array.isArray(value) ? value.map(asRecord3).filter((entry) => entry !== null) : [];
280741
281255
  }
280742
281256
  function codexPluginPath() {
280743
- return `${homedir35()}/plugins/${CODEX_PLUGIN_NAME}`;
281257
+ return `${homedir36()}/plugins/${CODEX_PLUGIN_NAME}`;
280744
281258
  }
280745
281259
  function codexPluginMcpPath() {
280746
281260
  return `${codexPluginPath()}/.mcp.json`;
280747
281261
  }
280748
281262
  function codexMarketplacePath() {
280749
- return `${homedir35()}/.agents/plugins/marketplace.json`;
281263
+ return `${homedir36()}/.agents/plugins/marketplace.json`;
280750
281264
  }
280751
281265
  function codexConfigPath() {
280752
- return `${homedir35()}/.codex/config.toml`;
281266
+ return `${homedir36()}/.codex/config.toml`;
280753
281267
  }
280754
281268
  function normalizeMarketplacePath(value) {
280755
281269
  return value.replace(/\\/g, "/").replace(/^\.\//, "");
@@ -281064,7 +281578,7 @@ function canonicalProjectPath(projectRoot) {
281064
281578
  return canonical.replace(/\\/g, "/");
281065
281579
  }
281066
281580
  function getClaudeLocalConfigPath() {
281067
- return `${homedir35()}/.claude.json`;
281581
+ return `${homedir36()}/.claude.json`;
281068
281582
  }
281069
281583
  function coerceMcpServer(name, value) {
281070
281584
  const entry = asRecord3(value);
@@ -281226,7 +281740,58 @@ async function inspectCodexPluginMcp() {
281226
281740
  };
281227
281741
  }
281228
281742
  }
281743
+ async function inspectOptionalGrokMcp(scope) {
281744
+ if (scope === "project" || scope === "local") {
281745
+ return { status: "skipped", issues: ["mcp-host-owned"], checks: [] };
281746
+ }
281747
+ const adapter = getMcpAdapter("grok");
281748
+ const configPath = adapter.getConfigPath();
281749
+ if (!existsSync31(configPath)) {
281750
+ return {
281751
+ status: "skipped",
281752
+ issues: ["mcp-host-owned"],
281753
+ checks: [{
281754
+ scope: "global",
281755
+ path: configPath,
281756
+ exists: false,
281757
+ status: "skipped",
281758
+ issues: ["mcp-host-owned"]
281759
+ }]
281760
+ };
281761
+ }
281762
+ const content = await readFile10(configPath, "utf-8");
281763
+ const server = adapter.parse(content).find((entry) => entry.name === "memorix");
281764
+ if (!server) {
281765
+ return {
281766
+ status: "skipped",
281767
+ issues: ["mcp-host-owned"],
281768
+ checks: [{
281769
+ scope: "global",
281770
+ path: configPath,
281771
+ exists: true,
281772
+ status: "skipped",
281773
+ issues: ["mcp-host-owned"]
281774
+ }]
281775
+ };
281776
+ }
281777
+ const issues = [];
281778
+ if (!server.url && looksLikeStaleMemorixCommand(server)) issues.push("stale-command-path");
281779
+ if (!server.url && !isRecommendedStdioServer(server)) issues.push("nonstandard-mcp-command");
281780
+ return {
281781
+ status: issues.length > 0 ? "repairable" : "ok",
281782
+ issues,
281783
+ checks: [{
281784
+ scope: "global",
281785
+ path: configPath,
281786
+ exists: true,
281787
+ status: issues.length > 0 ? "repairable" : "ok",
281788
+ issues,
281789
+ server: sanitizeServer(server)
281790
+ }]
281791
+ };
281792
+ }
281229
281793
  async function inspectMcp(agent, projectRoot, scope) {
281794
+ if (agent === "grok") return inspectOptionalGrokMcp(scope);
281230
281795
  if (!isMcpConfigAgent(agent)) {
281231
281796
  return { status: "skipped", issues: ["mcp-managed-by-package"], checks: [] };
281232
281797
  }
@@ -281323,7 +281888,7 @@ async function inspectGuidance(agent, projectRoot, scope) {
281323
281888
  }
281324
281889
  const checks = [];
281325
281890
  for (const targetScope of requestedGuidanceScopes(scope)) {
281326
- const root = targetScope === "project" ? projectRoot : homedir35();
281891
+ const root = targetScope === "project" ? projectRoot : homedir36();
281327
281892
  const rulesPath = getAgentRulesPath(agent, root, targetScope === "global");
281328
281893
  if (!existsSync31(rulesPath)) {
281329
281894
  checks.push({
@@ -281604,8 +282169,8 @@ var init_doctor = __esm({
281604
282169
  return;
281605
282170
  }
281606
282171
  const { existsSync: existsSync36, readFileSync: readFileSync25 } = await import("fs");
281607
- const { join: join35, basename: basename7 } = await import("path");
281608
- const { homedir: homedir37 } = await import("os");
282172
+ const { join: join36, basename: basename7 } = await import("path");
282173
+ const { homedir: homedir38 } = await import("os");
281609
282174
  const { execSync: execSync5 } = await import("child_process");
281610
282175
  const report = {};
281611
282176
  const issues = [];
@@ -281667,8 +282232,8 @@ var init_doctor = __esm({
281667
282232
  }
281668
282233
  lines.push("");
281669
282234
  lines.push("\u250C\u2500 Runtime Mode \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
281670
- const memorixDir = join35(homedir37(), ".memorix");
281671
- const bgStatePath = join35(memorixDir, "background.json");
282235
+ const memorixDir = join36(homedir38(), ".memorix");
282236
+ const bgStatePath = join36(memorixDir, "background.json");
281672
282237
  let bgRunning = false;
281673
282238
  let bgPort = 0;
281674
282239
  let bgPid = 0;
@@ -281788,7 +282353,7 @@ var init_doctor = __esm({
281788
282353
  }
281789
282354
  }
281790
282355
  try {
281791
- const cachePath = join35(memorixDir, "embedding-cache.json");
282356
+ const cachePath = join36(memorixDir, "embedding-cache.json");
281792
282357
  if (existsSync36(cachePath)) {
281793
282358
  const cache5 = JSON.parse(readFileSync25(cachePath, "utf-8"));
281794
282359
  const cacheCount = Object.keys(cache5).length;
@@ -282042,7 +282607,7 @@ var init_doctor = __esm({
282042
282607
  lines.push("\u250C\u2500 Conflict Check \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
282043
282608
  let conflictsFound = false;
282044
282609
  if (dataDir) {
282045
- const lockFile = join35(dataDir, ".memorix.lock");
282610
+ const lockFile = join36(dataDir, ".memorix.lock");
282046
282611
  if (existsSync36(lockFile)) {
282047
282612
  try {
282048
282613
  const lockData = JSON.parse(readFileSync25(lockFile, "utf-8"));
@@ -284254,7 +284819,7 @@ var init_pipeline_trace = __esm({
284254
284819
  // src/orchestrate/worktree.ts
284255
284820
  import { execFileSync as execFileSync5 } from "child_process";
284256
284821
  import { existsSync as existsSync33, rmSync as rmSync2 } from "fs";
284257
- import { join as join33, basename as basename6 } from "path";
284822
+ import { join as join34, basename as basename6 } from "path";
284258
284823
  function runGit4(cwd, args, timeout = 5e3) {
284259
284824
  return String(execFileSync5("git", args, {
284260
284825
  cwd,
@@ -284293,7 +284858,7 @@ function isManagedWorktreePath(worktreePath, worktreeBase) {
284293
284858
  function createWorktree(projectDir2, taskId, pipelineId) {
284294
284859
  const shortId2 = taskId.slice(0, 8);
284295
284860
  const shortPipeline = pipelineId.slice(0, 8);
284296
- const worktreeBase = join33(projectDir2, WORKTREE_DIR, `task-${shortId2}`);
284861
+ const worktreeBase = join34(projectDir2, WORKTREE_DIR, `task-${shortId2}`);
284297
284862
  const branchBase = `pipeline/${shortPipeline}/task-${shortId2}`;
284298
284863
  for (let attempt = 1; attempt <= 100; attempt++) {
284299
284864
  const suffix = attempt === 1 ? "" : `-${attempt}`;
@@ -284383,7 +284948,7 @@ function listWorktrees(projectDir2) {
284383
284948
  }
284384
284949
  }
284385
284950
  if (current) worktrees.push(current);
284386
- const worktreeBase = join33(projectDir2, WORKTREE_DIR);
284951
+ const worktreeBase = join34(projectDir2, WORKTREE_DIR);
284387
284952
  return worktrees.filter((w4) => isManagedWorktreePath(w4.path, worktreeBase));
284388
284953
  } catch {
284389
284954
  return [];
@@ -284805,16 +285370,16 @@ var init_cost_tracker = __esm({
284805
285370
 
284806
285371
  // src/orchestrate/evidence.ts
284807
285372
  import { mkdirSync as mkdirSync11, writeFileSync as writeFileSync11 } from "fs";
284808
- import { join as join34 } from "path";
285373
+ import { join as join35 } from "path";
284809
285374
  function writeTaskEvidence(projectDir2, pipelineId, evidence) {
284810
285375
  try {
284811
- const dir = join34(projectDir2, ".pipeline", pipelineId, "evidence", evidence.taskId.slice(0, 12));
285376
+ const dir = join35(projectDir2, ".pipeline", pipelineId, "evidence", evidence.taskId.slice(0, 12));
284812
285377
  mkdirSync11(dir, { recursive: true });
284813
285378
  if (evidence.prompt) {
284814
- safeWrite(join34(dir, "prompt.md"), evidence.prompt);
285379
+ safeWrite(join35(dir, "prompt.md"), evidence.prompt);
284815
285380
  }
284816
285381
  if (evidence.tailOutput) {
284817
- safeWrite(join34(dir, "output.txt"), evidence.tailOutput);
285382
+ safeWrite(join35(dir, "output.txt"), evidence.tailOutput);
284818
285383
  }
284819
285384
  if (evidence.gateResults) {
284820
285385
  for (const g4 of evidence.gateResults) {
@@ -284823,7 +285388,7 @@ Status: ${g4.passed ? "PASS" : "FAIL"}
284823
285388
  Duration: ${(g4.durationMs / 1e3).toFixed(1)}s
284824
285389
 
284825
285390
  ${g4.output}`;
284826
- safeWrite(join34(dir, `${g4.gate}.txt`), content);
285391
+ safeWrite(join35(dir, `${g4.gate}.txt`), content);
284827
285392
  }
284828
285393
  }
284829
285394
  const resultJson = {
@@ -284837,7 +285402,7 @@ ${g4.output}`;
284837
285402
  tokenUsage: evidence.tokenUsage,
284838
285403
  costUSD: evidence.costUSD
284839
285404
  };
284840
- safeWrite(join34(dir, "result.json"), JSON.stringify(resultJson, null, 2));
285405
+ safeWrite(join35(dir, "result.json"), JSON.stringify(resultJson, null, 2));
284841
285406
  return dir;
284842
285407
  } catch {
284843
285408
  return null;
@@ -284845,7 +285410,7 @@ ${g4.output}`;
284845
285410
  }
284846
285411
  function writePipelineSummary(projectDir2, summary) {
284847
285412
  try {
284848
- const dir = join34(projectDir2, ".pipeline", summary.pipelineId);
285413
+ const dir = join35(projectDir2, ".pipeline", summary.pipelineId);
284849
285414
  mkdirSync11(dir, { recursive: true });
284850
285415
  const elapsed = (summary.elapsedMs / 1e3).toFixed(0);
284851
285416
  const lines = [
@@ -284866,8 +285431,8 @@ function writePipelineSummary(projectDir2, summary) {
284866
285431
  lines.push(`- [${status}] ${t4.taskDescription.slice(0, 80)} \u2014 ${t4.agentName}, ${dur}s${fixes}`);
284867
285432
  }
284868
285433
  const content = lines.join("\n") + "\n";
284869
- safeWrite(join34(dir, "summary.md"), content);
284870
- return join34(dir, "summary.md");
285434
+ safeWrite(join35(dir, "summary.md"), content);
285435
+ return join35(dir, "summary.md");
284871
285436
  } catch {
284872
285437
  return null;
284873
285438
  }
@@ -291246,11 +291811,11 @@ var GUIDES = {
291246
291811
  "memorix sync rules --action status",
291247
291812
  "memorix sync workspace --action scan",
291248
291813
  "memorix sync store status --json",
291249
- "memorix sync store push|pull [--dry] [--json]",
291814
+ "memorix sync store push|pull [--dry] [--json] [--scope project|user]",
291250
291815
  "memorix sync store compact --dry --through device=sequence",
291251
291816
  "memorix sync store device rotate"
291252
291817
  ],
291253
- notes: ["Store sync is opt-in, project-scoped, and never copies the live SQLite database."]
291818
+ notes: ["Store sync is opt-in and never copies the live SQLite database. Default scope is the current Git project; `--scope user` relays every local project."]
291254
291819
  },
291255
291820
  ingest: {
291256
291821
  summary: "Ingest Git and image evidence into project memory.",