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/index.js CHANGED
@@ -707,18 +707,21 @@ CREATE TABLE IF NOT EXISTS chat_transcript (
707
707
  `;
708
708
  CREATE_GRAPH_ENTITIES_TABLE = `
709
709
  CREATE TABLE IF NOT EXISTS graph_entities (
710
- name TEXT PRIMARY KEY,
710
+ projectId TEXT NOT NULL,
711
+ name TEXT NOT NULL,
711
712
  entityType TEXT NOT NULL DEFAULT '',
712
- observations TEXT NOT NULL DEFAULT '[]'
713
+ observations TEXT NOT NULL DEFAULT '[]',
714
+ PRIMARY KEY (projectId, name)
713
715
  );
714
716
  `;
715
717
  CREATE_GRAPH_RELATIONS_TABLE = `
716
718
  CREATE TABLE IF NOT EXISTS graph_relations (
717
719
  id INTEGER PRIMARY KEY AUTOINCREMENT,
720
+ projectId TEXT NOT NULL,
718
721
  from_entity TEXT NOT NULL,
719
722
  to_entity TEXT NOT NULL,
720
723
  relationType TEXT NOT NULL DEFAULT '',
721
- UNIQUE(from_entity, to_entity, relationType)
724
+ UNIQUE(projectId, from_entity, to_entity, relationType)
722
725
  );
723
726
  `;
724
727
  CREATE_CODE_FILES_TABLE = `
@@ -1518,6 +1521,21 @@ CREATE UNIQUE INDEX IF NOT EXISTS idx_maintenance_jobs_active_dedupe
1518
1521
  import { promises as fs2 } from "fs";
1519
1522
  import { randomUUID } from "crypto";
1520
1523
  import path4 from "path";
1524
+ function canReclaimStaleLock(raw) {
1525
+ try {
1526
+ const parsed = JSON.parse(raw);
1527
+ const pid = Number(parsed.pid);
1528
+ if (!Number.isSafeInteger(pid) || pid < 1) return true;
1529
+ try {
1530
+ process.kill(pid, 0);
1531
+ return false;
1532
+ } catch (error) {
1533
+ return error.code !== "EPERM";
1534
+ }
1535
+ } catch {
1536
+ return true;
1537
+ }
1538
+ }
1521
1539
  async function acquireLock(lockPath) {
1522
1540
  for (let i2 = 0; i2 < MAX_RETRIES; i2++) {
1523
1541
  try {
@@ -1531,9 +1549,12 @@ async function acquireLock(lockPath) {
1531
1549
  try {
1532
1550
  const stat5 = await fs2.stat(lockPath);
1533
1551
  if (Date.now() - stat5.mtimeMs > LOCK_STALE_MS) {
1534
- await fs2.unlink(lockPath).catch(() => {
1535
- });
1536
- continue;
1552
+ const raw2 = await fs2.readFile(lockPath, "utf8").catch(() => "");
1553
+ if (canReclaimStaleLock(raw2)) {
1554
+ await fs2.unlink(lockPath).catch(() => {
1555
+ });
1556
+ continue;
1557
+ }
1537
1558
  }
1538
1559
  } catch {
1539
1560
  continue;
@@ -1544,6 +1565,10 @@ async function acquireLock(lockPath) {
1544
1565
  }
1545
1566
  }
1546
1567
  }
1568
+ const raw = await fs2.readFile(lockPath, "utf8").catch(() => "");
1569
+ if (!canReclaimStaleLock(raw)) {
1570
+ throw new Error(`Failed to acquire lock: ${lockPath} (owner is still running)`);
1571
+ }
1547
1572
  await fs2.unlink(lockPath).catch(() => {
1548
1573
  });
1549
1574
  try {
@@ -1782,6 +1807,11 @@ async function listProjectDirs(baseDir) {
1782
1807
  }
1783
1808
  }
1784
1809
  async function migrateSubdirsToFlat(baseDir) {
1810
+ const base = baseDir ?? resolveDefaultDataDir();
1811
+ await fs4.mkdir(base, { recursive: true });
1812
+ return withFileLock(base, () => migrateSubdirsToFlatUnlocked(base));
1813
+ }
1814
+ async function migrateSubdirsToFlatUnlocked(baseDir) {
1785
1815
  const base = baseDir ?? resolveDefaultDataDir();
1786
1816
  await fs4.mkdir(base, { recursive: true });
1787
1817
  let entries;
@@ -1838,7 +1868,7 @@ async function migrateSubdirsToFlat(baseDir) {
1838
1868
  for (const { obs } of subdirData) {
1839
1869
  for (const o of obs) {
1840
1870
  const isDuplicate = allObs.some(
1841
- (existing) => existing.title === o.title && existing.createdAt === o.createdAt
1871
+ (existing) => existing.title === o.title && existing.createdAt === o.createdAt && existing.projectId === o.projectId
1842
1872
  );
1843
1873
  if (!isDuplicate) {
1844
1874
  allObs.push(o);
@@ -1871,18 +1901,14 @@ async function migrateSubdirsToFlat(baseDir) {
1871
1901
  mergedRelations.push(rel);
1872
1902
  }
1873
1903
  }
1874
- await fs4.writeFile(path6.join(base, "observations.json"), JSON.stringify(allObs, null, 2), "utf-8");
1875
- await fs4.writeFile(
1876
- path6.join(base, "counter.json"),
1877
- JSON.stringify({ nextId: allObs.length + 1 }),
1878
- "utf-8"
1879
- );
1904
+ await atomicWriteFile(path6.join(base, "observations.json"), JSON.stringify(allObs, null, 2));
1905
+ await atomicWriteFile(path6.join(base, "counter.json"), JSON.stringify({ nextId: allObs.length + 1 }));
1880
1906
  const graphLines = [
1881
1907
  ...[...entityMap.values()].map((e2) => JSON.stringify({ type: "entity", name: e2.name, entityType: e2.entityType, observations: e2.observations })),
1882
1908
  ...mergedRelations.map((r2) => JSON.stringify({ type: "relation", from: r2.from, to: r2.to, relationType: r2.relationType }))
1883
1909
  ];
1884
1910
  if (graphLines.length > 0) {
1885
- await fs4.writeFile(path6.join(base, "graph.jsonl"), graphLines.join("\n"), "utf-8");
1911
+ await atomicWriteFile(path6.join(base, "graph.jsonl"), graphLines.join("\n"));
1886
1912
  }
1887
1913
  let allSessions = [];
1888
1914
  try {
@@ -1900,7 +1926,7 @@ async function migrateSubdirsToFlat(baseDir) {
1900
1926
  }
1901
1927
  }
1902
1928
  if (allSessions.length > 0) {
1903
- await fs4.writeFile(path6.join(base, "sessions.json"), JSON.stringify(allSessions, null, 2), "utf-8");
1929
+ await atomicWriteFile(path6.join(base, "sessions.json"), JSON.stringify(allSessions, null, 2));
1904
1930
  }
1905
1931
  const backupDir = path6.join(base, ".migrated-subdirs");
1906
1932
  await fs4.mkdir(backupDir, { recursive: true });
@@ -1936,6 +1962,7 @@ var init_persistence = __esm({
1936
1962
  "src/store/persistence.ts"() {
1937
1963
  "use strict";
1938
1964
  init_esm_shims();
1965
+ init_file_lock();
1939
1966
  init_persistence_json();
1940
1967
  }
1941
1968
  });
@@ -1943,6 +1970,7 @@ var init_persistence = __esm({
1943
1970
  // src/store/graph-store.ts
1944
1971
  var graph_store_exports = {};
1945
1972
  __export(graph_store_exports, {
1973
+ DEFAULT_GRAPH_PROJECT_ID: () => DEFAULT_GRAPH_PROJECT_ID,
1946
1974
  GraphSqliteStore: () => GraphSqliteStore,
1947
1975
  getGraphStore: () => getGraphStore,
1948
1976
  initGraphStore: () => initGraphStore,
@@ -1972,37 +2000,106 @@ function entityToRow(entity) {
1972
2000
  observations: JSON.stringify(entity.observations ?? [])
1973
2001
  };
1974
2002
  }
1975
- async function initGraphStore(dataDir) {
1976
- if (_graphStore && _graphDataDir === dataDir) return _graphStore;
1977
- _graphStore = new GraphSqliteStore();
1978
- await _graphStore.init(dataDir);
1979
- _graphDataDir = dataDir;
1980
- return _graphStore;
2003
+ function ensureScopedGraphSchema(db2) {
2004
+ const entityColumns = db2.prepare("PRAGMA table_info(graph_entities)").all();
2005
+ if (!entityColumns.some((column) => column.name === "projectId")) {
2006
+ const migrate = db2.transaction(() => {
2007
+ db2.exec("ALTER TABLE graph_entities RENAME TO graph_entities_legacy_unscoped");
2008
+ db2.exec(CREATE_SCOPED_GRAPH_ENTITIES);
2009
+ db2.exec(`INSERT INTO graph_entities (projectId, name, entityType, observations)
2010
+ SELECT '${LEGACY_GRAPH_PROJECT_ID}', name, entityType, observations
2011
+ FROM graph_entities_legacy_unscoped`);
2012
+ db2.exec("DROP TABLE graph_entities_legacy_unscoped");
2013
+ });
2014
+ migrate();
2015
+ } else {
2016
+ db2.exec(CREATE_SCOPED_GRAPH_ENTITIES);
2017
+ }
2018
+ const relationColumns = db2.prepare("PRAGMA table_info(graph_relations)").all();
2019
+ if (!relationColumns.some((column) => column.name === "projectId")) {
2020
+ const migrate = db2.transaction(() => {
2021
+ db2.exec("ALTER TABLE graph_relations RENAME TO graph_relations_legacy_unscoped");
2022
+ db2.exec(CREATE_SCOPED_GRAPH_RELATIONS);
2023
+ db2.exec(`INSERT INTO graph_relations (projectId, from_entity, to_entity, relationType)
2024
+ SELECT '${LEGACY_GRAPH_PROJECT_ID}', from_entity, to_entity, relationType
2025
+ FROM graph_relations_legacy_unscoped`);
2026
+ db2.exec("DROP TABLE graph_relations_legacy_unscoped");
2027
+ });
2028
+ migrate();
2029
+ } else {
2030
+ db2.exec(CREATE_SCOPED_GRAPH_RELATIONS);
2031
+ }
2032
+ }
2033
+ function graphStoreKey(dataDir, projectId) {
2034
+ return `${path7.resolve(dataDir)}\0${projectId}`;
1981
2035
  }
1982
- function getGraphStore() {
1983
- if (!_graphStore) throw new Error("[memorix] GraphStore not initialized \u2014 call initGraphStore() first");
1984
- return _graphStore;
2036
+ async function initGraphStore(dataDir, projectId = DEFAULT_GRAPH_PROJECT_ID) {
2037
+ const key = graphStoreKey(dataDir, projectId);
2038
+ const existing = _graphStores.get(key);
2039
+ if (existing) {
2040
+ _graphStores.delete(key);
2041
+ _graphStores.set(key, existing);
2042
+ _activeGraphKey = key;
2043
+ return existing;
2044
+ }
2045
+ const store = new GraphSqliteStore();
2046
+ await store.init(dataDir, projectId);
2047
+ _graphStores.set(key, store);
2048
+ while (_graphStores.size > MAX_GRAPH_STORE_CACHE) {
2049
+ const oldest = _graphStores.keys().next().value;
2050
+ if (!oldest) break;
2051
+ _graphStores.delete(oldest);
2052
+ }
2053
+ _activeGraphKey = key;
2054
+ return store;
2055
+ }
2056
+ function getGraphStore(dataDir, projectId) {
2057
+ const key = dataDir && projectId ? graphStoreKey(dataDir, projectId) : _activeGraphKey;
2058
+ const store = key ? _graphStores.get(key) : void 0;
2059
+ if (!store) throw new Error("[memorix] GraphStore not initialized \u2014 call initGraphStore() first");
2060
+ return store;
1985
2061
  }
1986
2062
  function resetGraphStore() {
1987
- if (_graphStore) {
2063
+ for (const store of _graphStores.values()) {
1988
2064
  try {
1989
- _graphStore.close();
2065
+ store.close();
1990
2066
  } catch {
1991
2067
  }
1992
2068
  }
1993
- _graphStore = null;
1994
- _graphDataDir = null;
2069
+ _graphStores.clear();
2070
+ _activeGraphKey = null;
1995
2071
  }
1996
- var GraphSqliteStore, _graphStore, _graphDataDir;
2072
+ var DEFAULT_GRAPH_PROJECT_ID, LEGACY_GRAPH_PROJECT_ID, MAX_GRAPH_STORE_CACHE, CREATE_SCOPED_GRAPH_ENTITIES, CREATE_SCOPED_GRAPH_RELATIONS, GraphSqliteStore, _graphStores, _activeGraphKey;
1997
2073
  var init_graph_store = __esm({
1998
2074
  "src/store/graph-store.ts"() {
1999
2075
  "use strict";
2000
2076
  init_esm_shims();
2001
2077
  init_sqlite_db();
2002
2078
  init_persistence();
2079
+ DEFAULT_GRAPH_PROJECT_ID = "__default__";
2080
+ LEGACY_GRAPH_PROJECT_ID = "__legacy__";
2081
+ MAX_GRAPH_STORE_CACHE = 32;
2082
+ CREATE_SCOPED_GRAPH_ENTITIES = `
2083
+ CREATE TABLE IF NOT EXISTS graph_entities (
2084
+ projectId TEXT NOT NULL,
2085
+ name TEXT NOT NULL,
2086
+ entityType TEXT NOT NULL DEFAULT '',
2087
+ observations TEXT NOT NULL DEFAULT '[]',
2088
+ PRIMARY KEY (projectId, name)
2089
+ );`;
2090
+ CREATE_SCOPED_GRAPH_RELATIONS = `
2091
+ CREATE TABLE IF NOT EXISTS graph_relations (
2092
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
2093
+ projectId TEXT NOT NULL,
2094
+ from_entity TEXT NOT NULL,
2095
+ to_entity TEXT NOT NULL,
2096
+ relationType TEXT NOT NULL DEFAULT '',
2097
+ UNIQUE(projectId, from_entity, to_entity, relationType)
2098
+ );`;
2003
2099
  GraphSqliteStore = class {
2004
2100
  db = null;
2005
2101
  dataDir = "";
2102
+ projectId = DEFAULT_GRAPH_PROJECT_ID;
2006
2103
  stmtInsertEntity = null;
2007
2104
  stmtUpdateEntityObs = null;
2008
2105
  stmtDeleteEntity = null;
@@ -2012,45 +2109,89 @@ var init_graph_store = __esm({
2012
2109
  stmtDeleteRelation = null;
2013
2110
  stmtDeleteRelationsByEntity = null;
2014
2111
  stmtSelectAllRelations = null;
2015
- async init(dataDir) {
2112
+ async init(dataDir, projectId = DEFAULT_GRAPH_PROJECT_ID) {
2016
2113
  this.dataDir = dataDir;
2114
+ this.projectId = projectId;
2017
2115
  this.db = getDatabase(dataDir);
2116
+ ensureScopedGraphSchema(this.db);
2018
2117
  this.stmtInsertEntity = this.db.prepare(
2019
- `INSERT OR REPLACE INTO graph_entities (name, entityType, observations) VALUES (@name, @entityType, @observations)`
2118
+ `INSERT OR REPLACE INTO graph_entities (projectId, name, entityType, observations) VALUES (@projectId, @name, @entityType, @observations)`
2020
2119
  );
2021
2120
  this.stmtUpdateEntityObs = this.db.prepare(
2022
- `UPDATE graph_entities SET observations = @observations WHERE name = @name`
2121
+ `UPDATE graph_entities SET observations = @observations WHERE projectId = @projectId AND name = @name`
2023
2122
  );
2024
- this.stmtDeleteEntity = this.db.prepare(`DELETE FROM graph_entities WHERE name = ?`);
2025
- this.stmtSelectAllEntities = this.db.prepare(`SELECT * FROM graph_entities`);
2026
- this.stmtSelectEntityByName = this.db.prepare(`SELECT * FROM graph_entities WHERE name = ?`);
2123
+ this.stmtDeleteEntity = this.db.prepare(`DELETE FROM graph_entities WHERE projectId = ? AND name = ?`);
2124
+ this.stmtSelectAllEntities = this.db.prepare(`SELECT * FROM graph_entities WHERE projectId = ? ORDER BY name`);
2125
+ this.stmtSelectEntityByName = this.db.prepare(`SELECT * FROM graph_entities WHERE projectId = ? AND name = ?`);
2027
2126
  this.stmtInsertRelation = this.db.prepare(
2028
- `INSERT OR IGNORE INTO graph_relations (from_entity, to_entity, relationType) VALUES (@from, @to, @relationType)`
2127
+ `INSERT OR IGNORE INTO graph_relations (projectId, from_entity, to_entity, relationType) VALUES (@projectId, @from, @to, @relationType)`
2029
2128
  );
2030
2129
  this.stmtDeleteRelation = this.db.prepare(
2031
- `DELETE FROM graph_relations WHERE from_entity = ? AND to_entity = ? AND relationType = ?`
2130
+ `DELETE FROM graph_relations WHERE projectId = ? AND from_entity = ? AND to_entity = ? AND relationType = ?`
2032
2131
  );
2033
2132
  this.stmtDeleteRelationsByEntity = this.db.prepare(
2034
- `DELETE FROM graph_relations WHERE from_entity = ? OR to_entity = ?`
2133
+ `DELETE FROM graph_relations WHERE projectId = ? AND (from_entity = ? OR to_entity = ?)`
2035
2134
  );
2036
- this.stmtSelectAllRelations = this.db.prepare(`SELECT * FROM graph_relations`);
2135
+ this.stmtSelectAllRelations = this.db.prepare(`SELECT * FROM graph_relations WHERE projectId = ? ORDER BY id`);
2037
2136
  await this.migrateFromJsonlIfNeeded();
2038
2137
  }
2039
2138
  async migrateFromJsonlIfNeeded() {
2040
- const count2 = this.db.prepare(`SELECT COUNT(*) AS cnt FROM graph_entities`).get();
2139
+ const count2 = this.db.prepare(`SELECT COUNT(*) AS cnt FROM graph_entities WHERE projectId = ?`).get(this.projectId);
2140
+ const totalCount = this.db.prepare(`SELECT COUNT(*) AS cnt FROM graph_entities`).get();
2041
2141
  if (count2.cnt > 0) return;
2142
+ if (totalCount.cnt > 0) return;
2042
2143
  const jsonlPath = path7.join(this.dataDir, "graph.jsonl");
2043
2144
  if (!fs5.existsSync(jsonlPath)) return;
2044
2145
  try {
2045
2146
  const data = await loadGraphJsonl(this.dataDir);
2046
2147
  if (data.entities.length === 0 && data.relations.length === 0) return;
2047
2148
  console.error(`[memorix] Migrating graph from JSONL to SQLite (${data.entities.length} entities, ${data.relations.length} relations)...`);
2149
+ const observations2 = this.db.prepare("SELECT id, projectId FROM observations").all();
2150
+ const projectByObservationId = new Map(observations2.map((row) => [row.id, row.projectId]));
2151
+ const projectsByEntity = /* @__PURE__ */ new Map();
2152
+ for (const entity of data.entities) {
2153
+ const projects = /* @__PURE__ */ new Set();
2154
+ for (const content of entity.observations ?? []) {
2155
+ const match2 = /^\[#(\d+)\]/.exec(content);
2156
+ const projectId = match2 ? projectByObservationId.get(Number(match2[1])) : void 0;
2157
+ if (projectId) projects.add(projectId);
2158
+ }
2159
+ projectsByEntity.set(entity.name, projects);
2160
+ }
2161
+ let changed = true;
2162
+ while (changed) {
2163
+ changed = false;
2164
+ for (const relation of data.relations) {
2165
+ const from = projectsByEntity.get(relation.from) ?? /* @__PURE__ */ new Set();
2166
+ const to = projectsByEntity.get(relation.to) ?? /* @__PURE__ */ new Set();
2167
+ if (from.size > 0 && to.size === 0) {
2168
+ projectsByEntity.set(relation.to, new Set(from));
2169
+ changed = true;
2170
+ } else if (to.size > 0 && from.size === 0) {
2171
+ projectsByEntity.set(relation.from, new Set(to));
2172
+ changed = true;
2173
+ }
2174
+ }
2175
+ }
2176
+ const entityRows = data.entities.flatMap((entity) => {
2177
+ const projects = projectsByEntity.get(entity.name) ?? /* @__PURE__ */ new Set();
2178
+ const owners = projects.size > 0 ? [...projects] : [this.projectId];
2179
+ return owners.map((projectId) => ({ ...entityToRow(entity), projectId }));
2180
+ });
2181
+ const projectSet = new Set(entityRows.map((row) => String(row.projectId)));
2182
+ const relationRows = data.relations.flatMap((relation) => {
2183
+ const from = projectsByEntity.get(relation.from) ?? /* @__PURE__ */ new Set();
2184
+ const to = projectsByEntity.get(relation.to) ?? /* @__PURE__ */ new Set();
2185
+ const shared = [...from].filter((projectId) => to.has(projectId));
2186
+ const owners = shared.length > 0 ? shared : from.size === 0 && to.size === 0 ? [this.projectId] : [];
2187
+ return owners.filter((projectId) => projectSet.has(projectId)).map((projectId) => ({ projectId, from: relation.from, to: relation.to, relationType: relation.relationType }));
2188
+ });
2048
2189
  const insertAll = this.db.transaction(() => {
2049
- for (const entity of data.entities) {
2050
- this.stmtInsertEntity.run(entityToRow(entity));
2190
+ for (const entity of entityRows) {
2191
+ this.stmtInsertEntity.run(entity);
2051
2192
  }
2052
- for (const rel of data.relations) {
2053
- this.stmtInsertRelation.run({ from: rel.from, to: rel.to, relationType: rel.relationType });
2193
+ for (const relation of relationRows) {
2194
+ this.stmtInsertRelation.run(relation);
2054
2195
  }
2055
2196
  });
2056
2197
  insertAll();
@@ -2060,10 +2201,10 @@ var init_graph_store = __esm({
2060
2201
  }
2061
2202
  }
2062
2203
  loadEntities() {
2063
- return this.stmtSelectAllEntities.all().map(rowToEntity);
2204
+ return this.stmtSelectAllEntities.all(this.projectId).map(rowToEntity);
2064
2205
  }
2065
2206
  loadRelations() {
2066
- return this.stmtSelectAllRelations.all().map((row) => ({
2207
+ return this.stmtSelectAllRelations.all(this.projectId).map((row) => ({
2067
2208
  from: row.from_entity,
2068
2209
  to: row.to_entity,
2069
2210
  relationType: row.relationType
@@ -2072,7 +2213,7 @@ var init_graph_store = __esm({
2072
2213
  insertEntities(entities) {
2073
2214
  const insertAll = this.db.transaction(() => {
2074
2215
  for (const entity of entities) {
2075
- this.stmtInsertEntity.run(entityToRow(entity));
2216
+ this.stmtInsertEntity.run({ ...entityToRow(entity), projectId: this.projectId });
2076
2217
  }
2077
2218
  });
2078
2219
  insertAll();
@@ -2080,7 +2221,7 @@ var init_graph_store = __esm({
2080
2221
  insertRelations(relations) {
2081
2222
  const insertAll = this.db.transaction(() => {
2082
2223
  for (const rel of relations) {
2083
- this.stmtInsertRelation.run({ from: rel.from, to: rel.to, relationType: rel.relationType });
2224
+ this.stmtInsertRelation.run({ projectId: this.projectId, from: rel.from, to: rel.to, relationType: rel.relationType });
2084
2225
  }
2085
2226
  });
2086
2227
  insertAll();
@@ -2088,8 +2229,8 @@ var init_graph_store = __esm({
2088
2229
  deleteEntities(names) {
2089
2230
  const deleteAll = this.db.transaction(() => {
2090
2231
  for (const name of names) {
2091
- this.stmtDeleteRelationsByEntity.run(name, name);
2092
- this.stmtDeleteEntity.run(name);
2232
+ this.stmtDeleteRelationsByEntity.run(this.projectId, name, name);
2233
+ this.stmtDeleteEntity.run(this.projectId, name);
2093
2234
  }
2094
2235
  });
2095
2236
  deleteAll();
@@ -2097,7 +2238,7 @@ var init_graph_store = __esm({
2097
2238
  deleteRelations(relations) {
2098
2239
  const deleteAll = this.db.transaction(() => {
2099
2240
  for (const rel of relations) {
2100
- this.stmtDeleteRelation.run(rel.from, rel.to, rel.relationType);
2241
+ this.stmtDeleteRelation.run(this.projectId, rel.from, rel.to, rel.relationType);
2101
2242
  }
2102
2243
  });
2103
2244
  deleteAll();
@@ -2105,13 +2246,13 @@ var init_graph_store = __esm({
2105
2246
  addObservations(updates) {
2106
2247
  const updateAll = this.db.transaction(() => {
2107
2248
  for (const u of updates) {
2108
- const row = this.stmtSelectEntityByName.get(u.entityName);
2249
+ const row = this.stmtSelectEntityByName.get(this.projectId, u.entityName);
2109
2250
  if (!row) continue;
2110
2251
  const existing = safeJsonParse(row.observations, []);
2111
2252
  const newObs = u.contents.filter((c) => !existing.includes(c));
2112
2253
  if (newObs.length > 0) {
2113
2254
  existing.push(...newObs);
2114
- this.stmtUpdateEntityObs.run({ name: u.entityName, observations: JSON.stringify(existing) });
2255
+ this.stmtUpdateEntityObs.run({ projectId: this.projectId, name: u.entityName, observations: JSON.stringify(existing) });
2115
2256
  }
2116
2257
  }
2117
2258
  });
@@ -2120,11 +2261,11 @@ var init_graph_store = __esm({
2120
2261
  deleteObservations(deletions) {
2121
2262
  const deleteAll = this.db.transaction(() => {
2122
2263
  for (const d of deletions) {
2123
- const row = this.stmtSelectEntityByName.get(d.entityName);
2264
+ const row = this.stmtSelectEntityByName.get(this.projectId, d.entityName);
2124
2265
  if (!row) continue;
2125
2266
  const existing = safeJsonParse(row.observations, []);
2126
2267
  const filtered = existing.filter((o) => !d.observations.includes(o));
2127
- this.stmtUpdateEntityObs.run({ name: d.entityName, observations: JSON.stringify(filtered) });
2268
+ this.stmtUpdateEntityObs.run({ projectId: this.projectId, name: d.entityName, observations: JSON.stringify(filtered) });
2128
2269
  }
2129
2270
  });
2130
2271
  deleteAll();
@@ -2135,13 +2276,13 @@ var init_graph_store = __esm({
2135
2276
  */
2136
2277
  replaceAll(entities, relations) {
2137
2278
  const replaceAll = this.db.transaction(() => {
2138
- this.db.prepare("DELETE FROM graph_relations").run();
2139
- this.db.prepare("DELETE FROM graph_entities").run();
2279
+ this.db.prepare("DELETE FROM graph_relations WHERE projectId = ?").run(this.projectId);
2280
+ this.db.prepare("DELETE FROM graph_entities WHERE projectId = ?").run(this.projectId);
2140
2281
  for (const entity of entities) {
2141
- this.stmtInsertEntity.run(entityToRow(entity));
2282
+ this.stmtInsertEntity.run({ ...entityToRow(entity), projectId: this.projectId });
2142
2283
  }
2143
2284
  for (const rel of relations) {
2144
- this.stmtInsertRelation.run({ from: rel.from, to: rel.to, relationType: rel.relationType });
2285
+ this.stmtInsertRelation.run({ projectId: this.projectId, from: rel.from, to: rel.to, relationType: rel.relationType });
2145
2286
  }
2146
2287
  });
2147
2288
  replaceAll();
@@ -2149,8 +2290,8 @@ var init_graph_store = __esm({
2149
2290
  close() {
2150
2291
  }
2151
2292
  };
2152
- _graphStore = null;
2153
- _graphDataDir = null;
2293
+ _graphStores = /* @__PURE__ */ new Map();
2294
+ _activeGraphKey = null;
2154
2295
  }
2155
2296
  });
2156
2297
 
@@ -2165,11 +2306,14 @@ var init_graph = __esm({
2165
2306
  entities = [];
2166
2307
  relations = [];
2167
2308
  projectDir;
2309
+ projectId;
2310
+ store = null;
2168
2311
  initialized = false;
2169
2312
  /** Index: lowercase entity name → Entity for O(1) lookups */
2170
2313
  entityIndex = /* @__PURE__ */ new Map();
2171
- constructor(projectDir2) {
2314
+ constructor(projectDir2, projectId = "__default__") {
2172
2315
  this.projectDir = projectDir2;
2316
+ this.projectId = projectId;
2173
2317
  }
2174
2318
  /** Rebuild the entity name index */
2175
2319
  rebuildIndex() {
@@ -2181,10 +2325,9 @@ var init_graph = __esm({
2181
2325
  /** Load graph from SQLite on first access */
2182
2326
  async init() {
2183
2327
  if (this.initialized) return;
2184
- await initGraphStore(this.projectDir);
2185
- const store = getGraphStore();
2186
- this.entities = store.loadEntities();
2187
- this.relations = store.loadRelations();
2328
+ this.store = await initGraphStore(this.projectDir, this.projectId);
2329
+ this.entities = this.store.loadEntities();
2330
+ this.relations = this.store.loadRelations();
2188
2331
  this.rebuildIndex();
2189
2332
  this.initialized = true;
2190
2333
  }
@@ -2198,8 +2341,8 @@ var init_graph = __esm({
2198
2341
  }
2199
2342
  /** Persist current state to SQLite */
2200
2343
  async save() {
2201
- const store = getGraphStore();
2202
- store.replaceAll(this.entities, this.relations);
2344
+ if (!this.store) throw new Error("[memorix] KnowledgeGraphManager not initialized");
2345
+ this.store.replaceAll(this.entities, this.relations);
2203
2346
  }
2204
2347
  /** Create new entities (skip duplicates by name) */
2205
2348
  async createEntities(entities) {
@@ -6986,6 +7129,7 @@ var init_sqlite_store = __esm({
6986
7129
  init_sqlite_db();
6987
7130
  init_sqlite_reliability();
6988
7131
  init_sqlite_fts();
7132
+ init_file_lock();
6989
7133
  SqliteBackend = class {
6990
7134
  db = null;
6991
7135
  dataDir = "";
@@ -7149,38 +7293,43 @@ var init_sqlite_store = __esm({
7149
7293
  }
7150
7294
  // ── Migration ────────────────────────────────────────────────────
7151
7295
  async migrateFromJsonIfNeeded() {
7152
- const count2 = this.db.prepare(`SELECT COUNT(*) AS cnt FROM observations`).get();
7153
- if (count2.cnt > 0) return;
7154
7296
  const jsonPath = path10.join(this.dataDir, "observations.json");
7155
7297
  if (!fs7.existsSync(jsonPath)) return;
7156
- try {
7298
+ await withFileLock(this.dataDir, async () => {
7299
+ const count2 = this.db.prepare(`SELECT COUNT(*) AS cnt FROM observations`).get();
7300
+ if (count2.cnt > 0) return;
7157
7301
  const raw = fs7.readFileSync(jsonPath, "utf-8");
7158
- const observations2 = JSON.parse(raw);
7302
+ const parsed = JSON.parse(raw);
7303
+ if (!Array.isArray(parsed)) {
7304
+ throw new Error("[memorix] observations.json migration refused: expected a JSON array");
7305
+ }
7306
+ const observations2 = parsed;
7159
7307
  if (observations2.length === 0) return;
7308
+ if (observations2.some((obs) => !obs || !Number.isSafeInteger(obs.id) || obs.id < 1 || typeof obs.projectId !== "string")) {
7309
+ throw new Error("[memorix] observations.json migration refused: invalid observation record");
7310
+ }
7160
7311
  console.error(`[memorix] Migrating ${observations2.length} observations from JSON to SQLite...`);
7161
7312
  const insertMany = this.db.transaction((obsList) => {
7162
7313
  for (const obs of obsList) {
7163
7314
  this.stmtInsert.run(obsToRow(obs));
7164
7315
  }
7165
- });
7166
- insertMany(observations2);
7167
- const counterPath = path10.join(this.dataDir, "counter.json");
7168
- if (fs7.existsSync(counterPath)) {
7169
- try {
7316
+ const counterPath = path10.join(this.dataDir, "counter.json");
7317
+ let counterNextId;
7318
+ if (fs7.existsSync(counterPath)) {
7170
7319
  const counterData = JSON.parse(fs7.readFileSync(counterPath, "utf-8"));
7171
- const nextId2 = counterData.nextId ?? counterData.next_id ?? Math.max(...observations2.map((o) => o.id)) + 1;
7172
- this.rawSaveIdCounter(nextId2);
7173
- } catch {
7174
- this.rawSaveIdCounter(Math.max(...observations2.map((o) => o.id)) + 1);
7320
+ const candidate = counterData.nextId ?? counterData.next_id;
7321
+ if (candidate !== void 0 && (!Number.isSafeInteger(candidate) || Number(candidate) < 1)) {
7322
+ throw new Error("[memorix] counter.json migration refused: nextId must be a positive integer");
7323
+ }
7324
+ counterNextId = candidate === void 0 ? void 0 : Number(candidate);
7175
7325
  }
7176
- } else {
7177
- this.rawSaveIdCounter(Math.max(...observations2.map((o) => o.id)) + 1);
7178
- }
7179
- this.bumpGeneration();
7326
+ const maxId = Math.max(...obsList.map((obs) => obs.id));
7327
+ this.rawSaveIdCounter(Math.max(counterNextId ?? 1, maxId + 1));
7328
+ this.bumpGeneration();
7329
+ });
7330
+ insertMany(observations2);
7180
7331
  console.error(`[memorix] Migration complete. ${observations2.length} observations now in SQLite.`);
7181
- } catch (err) {
7182
- console.error(`[memorix] JSON\u2192SQLite migration failed (non-fatal, data preserved in JSON): ${err}`);
7183
- }
7332
+ });
7184
7333
  }
7185
7334
  // ── Public read ──────────────────────────────────────────────────
7186
7335
  async loadAll() {
@@ -159802,7 +159951,7 @@ var init_request_context = __esm({
159802
159951
  function getMcpServerInfo() {
159803
159952
  return {
159804
159953
  name: "memorix",
159805
- version: true ? "1.9.4" : "1.0.1"
159954
+ version: true ? "1.9.5" : "1.0.1"
159806
159955
  };
159807
159956
  }
159808
159957
  function createMcpDiscoverResult(capabilities, serverInfo) {
@@ -160197,7 +160346,7 @@ async function getSessionContext(projectDir2, projectId, limit2 = 3, reader2) {
160197
160346
  let graphNeighbors = [];
160198
160347
  if (!reader2 && activeEntities.length > 0) {
160199
160348
  try {
160200
- const graphMgr = new KnowledgeGraphManager(projectDir2);
160349
+ const graphMgr = new KnowledgeGraphManager(projectDir2, projectId);
160201
160350
  await graphMgr.init();
160202
160351
  const { relations } = await graphMgr.readGraph();
160203
160352
  const activeSet = new Set(activeEntities.map((n) => n.toLowerCase()));
@@ -166539,8 +166688,7 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
166539
166688
  break;
166540
166689
  }
166541
166690
  case "/graph": {
166542
- await initGraphStore(effectiveDataDir);
166543
- const gStore = getGraphStore();
166691
+ const gStore = await initGraphStore(effectiveDataDir, effectiveProjectId);
166544
166692
  const graph = { entities: gStore.loadEntities(), relations: gStore.loadRelations() };
166545
166693
  const graphObs = filterDashboardObservations(
166546
166694
  await getObservationStore().loadByProject(effectiveProjectId, { status: "active" }),
@@ -166584,8 +166732,8 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
166584
166732
  break;
166585
166733
  }
166586
166734
  case "/stats": {
166587
- await initGraphStore(effectiveDataDir);
166588
- const graph = { entities: getGraphStore().loadEntities(), relations: getGraphStore().loadRelations() };
166735
+ const gStore = await initGraphStore(effectiveDataDir, effectiveProjectId);
166736
+ const graph = { entities: gStore.loadEntities(), relations: gStore.loadRelations() };
166589
166737
  const observations2 = filterDashboardObservations(
166590
166738
  await getObservationStore().loadByProject(effectiveProjectId, { status: "active" }),
166591
166739
  effectiveProjectId
@@ -166778,15 +166926,15 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
166778
166926
  case "/knowledge-graph": {
166779
166927
  const { generateKnowledgeGraph: generateKnowledgeGraph2 } = await Promise.resolve().then(() => (init_knowledge_graph(), knowledge_graph_exports));
166780
166928
  const { initMiniSkillStore: initMiniSkillStore2, getMiniSkillStore: getMiniSkillStore2 } = await Promise.resolve().then(() => (init_mini_skill_store(), mini_skill_store_exports));
166781
- const { initGraphStore: initGraphStore2, getGraphStore: getGraphStore2 } = await Promise.resolve().then(() => (init_graph_store(), graph_store_exports));
166929
+ const { initGraphStore: initGraphStore2 } = await Promise.resolve().then(() => (init_graph_store(), graph_store_exports));
166782
166930
  await initMiniSkillStore2(effectiveDataDir);
166783
- await initGraphStore2(effectiveDataDir);
166931
+ const gStore = await initGraphStore2(effectiveDataDir, effectiveProjectId);
166784
166932
  const allObs = filterDashboardObservations(
166785
166933
  await getObservationStore().loadByProject(effectiveProjectId, { status: "active" }),
166786
166934
  effectiveProjectId
166787
166935
  );
166788
166936
  const skills = await getMiniSkillStore2().loadByProject(effectiveProjectId);
166789
- const fullGraph = { entities: getGraphStore2().loadEntities(), relations: getGraphStore2().loadRelations() };
166937
+ const fullGraph = { entities: gStore.loadEntities(), relations: gStore.loadRelations() };
166790
166938
  const scoped = scopeKnowledgeGraphToProject(fullGraph, allObs);
166791
166939
  const graph = generateKnowledgeGraph2({
166792
166940
  projectId: effectiveProjectId,
@@ -166983,8 +167131,7 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
166983
167131
  } else {
166984
167132
  await obsStore.remove(obsId);
166985
167133
  try {
166986
- await initGraphStore(effectiveDataDir);
166987
- const gStore = getGraphStore();
167134
+ const gStore = await initGraphStore(effectiveDataDir, effectiveProjectId);
166988
167135
  const prefix = `[#${obsId}] `;
166989
167136
  const deletions = [];
166990
167137
  for (const entity of gStore.loadEntities()) {
@@ -166999,8 +167146,8 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
166999
167146
  break;
167000
167147
  }
167001
167148
  if (apiPath === "/export") {
167002
- await initGraphStore(effectiveDataDir);
167003
- const fullGraph = { entities: getGraphStore().loadEntities(), relations: getGraphStore().loadRelations() };
167149
+ const gStore = await initGraphStore(effectiveDataDir, effectiveProjectId);
167150
+ const fullGraph = { entities: gStore.loadEntities(), relations: gStore.loadRelations() };
167004
167151
  const observations2 = filterDashboardObservations(
167005
167152
  await getObservationStore().loadByProject(effectiveProjectId, { status: "active" }),
167006
167153
  effectiveProjectId
@@ -170445,7 +170592,7 @@ async function createMemorixServer(cwd, existingServer, sharedTeam, options = {}
170445
170592
  const store = getObservationStore();
170446
170593
  console.error(`[memorix] ObservationStore backend: ${store.getBackendName()}, generation: ${store.getGeneration()}`);
170447
170594
  }
170448
- let graphManager = new KnowledgeGraphManager(projectDir2);
170595
+ let graphManager = new KnowledgeGraphManager(projectDir2, project.id);
170449
170596
  if (!deferProjectRuntimeInit) {
170450
170597
  await graphManager.init();
170451
170598
  await initObservations(projectDir2);
@@ -170457,7 +170604,7 @@ async function createMemorixServer(cwd, existingServer, sharedTeam, options = {}
170457
170604
  await initObservationStore(projectDir2);
170458
170605
  await initMiniSkillStore(projectDir2);
170459
170606
  await initSessionStore(projectDir2);
170460
- graphManager = new KnowledgeGraphManager(projectDir2);
170607
+ graphManager = new KnowledgeGraphManager(projectDir2, project.id);
170461
170608
  await graphManager.init();
170462
170609
  await initObservations(projectDir2);
170463
170610
  const indexed = await prepareSearchIndex();
@@ -175513,7 +175660,7 @@ var init_serve_shared = __esm({
175513
175660
 
175514
175661
  // src/cli/version.ts
175515
175662
  function getCliVersion() {
175516
- return true ? "1.9.4" : pkg.version;
175663
+ return true ? "1.9.5" : pkg.version;
175517
175664
  }
175518
175665
  var init_version3 = __esm({
175519
175666
  "src/cli/version.ts"() {