memorix 1.1.9 → 1.1.10

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.
@@ -2,6 +2,12 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [1.1.10] - 2026-07-13
6
+
7
+ ### Fixed
8
+ - **Large-project Memory Autopilot timeouts** -- `memorix_project_context` now reuses one CodeGraph snapshot per request and performs first-use code-ref backfill in memory with one batched write instead of per-memory database scans and transactions. Prose-only and ambiguous symbol mentions no longer create large volumes of noisy code references, keeping first-use context generation responsive with thousands of active memories.
9
+ - **Ruby CodeGraph symbol fidelity** -- Ruby namespaces and punctuated method names such as `Foo::Bar`, `save!`, `valid?`, and `name=` now remain intact through Lite indexing and memory-to-code binding.
10
+
5
11
  ## [1.1.9] - 2026-07-12
6
12
 
7
13
  ### Added
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memorix/memcode",
3
- "version": "1.1.9",
3
+ "version": "1.1.10",
4
4
  "description": "Memorix-native coding agent CLI with terminal chat, project memory, hooks, and session management",
5
5
  "type": "module",
6
6
  "piConfig": {
@@ -34,9 +34,9 @@
34
34
  "prepublishOnly": "npm run clean && npm run build"
35
35
  },
36
36
  "dependencies": {
37
- "@memorix/agent-core": "1.1.9",
38
- "@memorix/ai": "1.1.9",
39
- "@memorix/tui": "1.1.9",
37
+ "@memorix/agent-core": "1.1.10",
38
+ "@memorix/ai": "1.1.10",
39
+ "@memorix/tui": "1.1.10",
40
40
  "@opentui/core": "^0.3.4",
41
41
  "@opentui/react": "^0.3.4",
42
42
  "@silvia-odwyer/photon-node": "0.3.4",
package/dist/sdk.js CHANGED
@@ -6408,6 +6408,44 @@ var init_store = __esm({
6408
6408
  ORDER BY path, startLine
6409
6409
  LIMIT ?
6410
6410
  `).all(projectId, like, like, like, limit).map(rowToSymbol);
6411
+ }
6412
+ listSymbols(projectId) {
6413
+ return this.db.prepare(`
6414
+ SELECT * FROM code_symbols
6415
+ WHERE projectId = ? AND stale = 0
6416
+ ORDER BY path, startLine
6417
+ `).all(projectId).map(rowToSymbol);
6418
+ }
6419
+ findSymbolsByNames(projectId, names, fileIds = []) {
6420
+ const candidates = [...new Set(names.map((name) => name.trim()).filter(Boolean))];
6421
+ if (candidates.length === 0) return [];
6422
+ const candidateJson = JSON.stringify(candidates);
6423
+ const hintedFiles = [...new Set(fileIds.map((fileId) => fileId.trim()).filter(Boolean))];
6424
+ if (hintedFiles.length > 0) {
6425
+ return this.db.prepare(`
6426
+ SELECT * FROM code_symbols
6427
+ WHERE projectId = ?
6428
+ AND stale = 0
6429
+ AND name IN (SELECT value FROM json_each(?))
6430
+ AND fileId IN (SELECT value FROM json_each(?))
6431
+ ORDER BY path, startLine
6432
+ `).all(projectId, candidateJson, JSON.stringify(hintedFiles)).map(rowToSymbol);
6433
+ }
6434
+ return this.db.prepare(`
6435
+ SELECT symbols.*
6436
+ FROM code_symbols AS symbols
6437
+ INNER JOIN (
6438
+ SELECT name
6439
+ FROM code_symbols
6440
+ WHERE projectId = ?
6441
+ AND stale = 0
6442
+ AND name IN (SELECT value FROM json_each(?))
6443
+ GROUP BY name
6444
+ HAVING COUNT(*) = 1
6445
+ ) AS unambiguous ON unambiguous.name = symbols.name
6446
+ WHERE symbols.projectId = ? AND symbols.stale = 0
6447
+ ORDER BY symbols.path, symbols.startLine
6448
+ `).all(projectId, candidateJson, projectId).map(rowToSymbol);
6411
6449
  }
6412
6450
  listSymbolsForFile(fileId) {
6413
6451
  return this.db.prepare(`SELECT * FROM code_symbols WHERE fileId = ? AND stale = 0 ORDER BY startLine`).all(fileId).map(rowToSymbol);
@@ -6421,6 +6459,22 @@ var init_store = __esm({
6421
6459
  WHERE projectId = ? AND observationId = ?
6422
6460
  ORDER BY status, id
6423
6461
  `).all(projectId, observationId).map(rowToRef);
6462
+ }
6463
+ listProjectObservationRefs(projectId) {
6464
+ return this.db.prepare(`
6465
+ SELECT * FROM observation_code_refs
6466
+ WHERE projectId = ?
6467
+ ORDER BY observationId, status, id
6468
+ `).all(projectId).map(rowToRef);
6469
+ }
6470
+ listReferencedSymbols(projectId) {
6471
+ return this.db.prepare(`
6472
+ SELECT DISTINCT symbols.*
6473
+ FROM code_symbols AS symbols
6474
+ INNER JOIN observation_code_refs AS refs ON refs.symbolId = symbols.id
6475
+ WHERE refs.projectId = ? AND symbols.projectId = ? AND symbols.stale = 0
6476
+ ORDER BY symbols.path, symbols.startLine
6477
+ `).all(projectId, projectId).map(rowToSymbol);
6424
6478
  }
6425
6479
  status(projectId) {
6426
6480
  const files = this.db.prepare(`SELECT COUNT(*) AS count FROM code_files WHERE projectId = ?`).get(projectId).count;
@@ -6447,6 +6501,15 @@ function mentionsSymbol(text, symbol) {
6447
6501
  const name = symbol.name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6448
6502
  return new RegExp(`(^|[^\\w$])${name}([^\\w$]|$)`).test(text);
6449
6503
  }
6504
+ function identifierCandidates(text) {
6505
+ return [...new Set(text.match(/[A-Za-z_$][\w$]*(?:::[A-Za-z_$][\w$]*)*[!?=]?/g) ?? [])];
6506
+ }
6507
+ function codeIdentifierCandidates(text) {
6508
+ const explicit = /* @__PURE__ */ new Set();
6509
+ for (const match of text.matchAll(/`([A-Za-z_$][\w$]*(?:::[A-Za-z_$][\w$]*)*[!?=]?)`/g)) explicit.add(match[1]);
6510
+ for (const match of text.matchAll(/\b([A-Za-z_$][\w$]*(?:::[A-Za-z_$][\w$]*)*[!?=]?)\s*\(/g)) explicit.add(match[1]);
6511
+ return identifierCandidates(text).filter((candidate) => explicit.has(candidate) || candidate.includes("::") || /[!?=]$/.test(candidate) || /^[A-Z]/.test(candidate) || /[_$]/.test(candidate) || /[a-z0-9][A-Z]/.test(candidate) || /[A-Z]{2}/.test(candidate));
6512
+ }
6450
6513
  function fileRef(projectId, obs, file) {
6451
6514
  return {
6452
6515
  id: makeObservationCodeRefId(projectId, obs.id, file.id),
@@ -6473,40 +6536,101 @@ function symbolRef(projectId, obs, file, symbol) {
6473
6536
  createdAt: obs.createdAt
6474
6537
  };
6475
6538
  }
6476
- async function bindObservationToCode(store, obs) {
6539
+ function resolveObservationCodeRefs(obs, lookup) {
6477
6540
  const refs = /* @__PURE__ */ new Map();
6478
6541
  const text = observationText(obs);
6479
6542
  const candidateFiles = /* @__PURE__ */ new Map();
6480
6543
  for (const rawPath of obs.filesModified ?? []) {
6481
- const file = store.getFile(obs.projectId, normalizeCodePath(rawPath));
6544
+ const file = lookup.findFile(normalizeCodePath(rawPath));
6482
6545
  if (!file) continue;
6483
6546
  candidateFiles.set(file.id, file);
6484
6547
  const ref = fileRef(obs.projectId, obs, file);
6485
6548
  refs.set(ref.id, ref);
6486
6549
  }
6487
- const symbols = store.findSymbols(obs.projectId, "", 500);
6550
+ const hintedFileIds = new Set(candidateFiles.keys());
6551
+ const symbolNames = hintedFileIds.size > 0 ? identifierCandidates(text) : codeIdentifierCandidates(text);
6552
+ const symbols = lookup.findSymbols(symbolNames, [...hintedFileIds]);
6553
+ const symbolsByName = /* @__PURE__ */ new Map();
6488
6554
  for (const symbol of symbols) {
6489
- if (!mentionsSymbol(text, symbol)) continue;
6490
- const file = candidateFiles.get(symbol.fileId) ?? store.getFile(obs.projectId, symbol.path);
6491
- if (!file) continue;
6492
- candidateFiles.set(file.id, file);
6493
- const ref = symbolRef(obs.projectId, obs, file, symbol);
6494
- refs.set(ref.id, ref);
6555
+ const group = symbolsByName.get(symbol.name) ?? [];
6556
+ group.push(symbol);
6557
+ symbolsByName.set(symbol.name, group);
6495
6558
  }
6496
- const result = [...refs.values()];
6497
- store.replaceObservationRefs(obs.projectId, obs.id, result);
6498
- return result;
6559
+ for (const group of symbolsByName.values()) {
6560
+ const candidates = group.length === 1 ? group : [];
6561
+ for (const symbol of candidates) {
6562
+ if (!mentionsSymbol(text, symbol)) continue;
6563
+ const file = candidateFiles.get(symbol.fileId) ?? lookup.findFile(symbol.path);
6564
+ if (!file) continue;
6565
+ candidateFiles.set(file.id, file);
6566
+ const ref = symbolRef(obs.projectId, obs, file, symbol);
6567
+ refs.set(ref.id, ref);
6568
+ }
6569
+ }
6570
+ return [...refs.values()];
6571
+ }
6572
+ function createStoreLookup(store, projectId) {
6573
+ return {
6574
+ findFile: (path20) => store.getFile(projectId, path20) ?? void 0,
6575
+ findSymbols: (names, hintedFileIds) => store.findSymbolsByNames(projectId, names, hintedFileIds)
6576
+ };
6577
+ }
6578
+ function createSnapshotLookup(files, symbols) {
6579
+ const filesByPath = new Map(files.map((file) => [normalizeCodePath(file.path), file]));
6580
+ const symbolsByName = /* @__PURE__ */ new Map();
6581
+ for (const symbol of symbols) {
6582
+ const group = symbolsByName.get(symbol.name) ?? [];
6583
+ group.push(symbol);
6584
+ symbolsByName.set(symbol.name, group);
6585
+ }
6586
+ return {
6587
+ findFile: (path20) => filesByPath.get(normalizeCodePath(path20)),
6588
+ findSymbols: (names, hintedFileIds) => {
6589
+ const hinted = new Set(hintedFileIds);
6590
+ const found = [];
6591
+ for (const name of names) {
6592
+ const candidates = symbolsByName.get(name) ?? [];
6593
+ found.push(...hinted.size > 0 ? candidates.filter((symbol) => hinted.has(symbol.fileId)) : candidates);
6594
+ }
6595
+ return found;
6596
+ }
6597
+ };
6598
+ }
6599
+ async function bindObservationToCode(store, obs) {
6600
+ const refs = resolveObservationCodeRefs(obs, createStoreLookup(store, obs.projectId));
6601
+ store.replaceObservationRefs(obs.projectId, obs.id, refs);
6602
+ return refs;
6499
6603
  }
6500
6604
  async function backfillMissingObservationCodeRefs(store, observations2) {
6501
6605
  let observationsBackfilled = 0;
6502
6606
  let refsBackfilled = 0;
6607
+ const boundObservationIdsByProject = /* @__PURE__ */ new Map();
6608
+ const lookupByProject = /* @__PURE__ */ new Map();
6609
+ for (const projectId of new Set(observations2.map((observation) => observation.projectId))) {
6610
+ boundObservationIdsByProject.set(
6611
+ projectId,
6612
+ new Set(store.listProjectObservationRefs(projectId).map((ref) => ref.observationId))
6613
+ );
6614
+ lookupByProject.set(
6615
+ projectId,
6616
+ createSnapshotLookup(store.listFiles(projectId), store.listSymbols(projectId))
6617
+ );
6618
+ }
6619
+ const refsToInsert = [];
6503
6620
  for (const observation of observations2) {
6504
- if (store.listObservationRefs(observation.projectId, observation.id).length > 0) continue;
6505
- const refs = await bindObservationToCode(store, observation);
6621
+ if (boundObservationIdsByProject.get(observation.projectId)?.has(observation.id)) continue;
6622
+ if ((observation.filesModified?.length ?? 0) === 0 && codeIdentifierCandidates(observationText(observation)).length === 0) {
6623
+ continue;
6624
+ }
6625
+ const lookup = lookupByProject.get(observation.projectId);
6626
+ if (!lookup) continue;
6627
+ const refs = resolveObservationCodeRefs(observation, lookup);
6506
6628
  if (refs.length === 0) continue;
6507
6629
  observationsBackfilled += 1;
6508
6630
  refsBackfilled += refs.length;
6631
+ refsToInsert.push(...refs);
6509
6632
  }
6633
+ store.upsertObservationRefs(refsToInsert);
6510
6634
  return {
6511
6635
  observationsScanned: observations2.length,
6512
6636
  observationsBackfilled,
@@ -9646,8 +9770,8 @@ var init_lite_provider = __esm({
9646
9770
  },
9647
9771
  ruby: {
9648
9772
  symbols: [
9649
- { kind: "class", re: /^class\s+([A-Za-z_][\w:]*?)\b/gm },
9650
- { kind: "function", re: /^def\s+([A-Za-z_][\w!?=]*)\b/gm }
9773
+ { kind: "class", re: /^class\s+([A-Za-z_][\w]*(?:::[A-Za-z_][\w]*)*)/gm },
9774
+ { kind: "function", re: /^def\s+([A-Za-z_][\w]*[!?=]?)/gm }
9651
9775
  ],
9652
9776
  imports: [/require\s+['"]([^'"]+)['"]/g]
9653
9777
  },
@@ -9716,11 +9840,12 @@ function compactSuggestedReads(paths, limit = 8, exclude) {
9716
9840
  }
9717
9841
  function collectGraph(store, projectId, observations2, exclude) {
9718
9842
  const files = store.listFiles(projectId);
9719
- const symbols = files.flatMap((file) => store.listSymbolsForFile(file.id));
9843
+ const symbols = store.listReferencedSymbols(projectId);
9720
9844
  const filesById = new Map(files.map((file) => [file.id, file]));
9721
9845
  const symbolsById = new Map(symbols.map((symbol) => [symbol.id, symbol]));
9722
9846
  const observationsById = new Map(observations2.map((obs) => [obs.id, obs]));
9723
- const refs = observations2.flatMap((obs) => store.listObservationRefs(projectId, obs.id));
9847
+ const activeObservationIds = new Set(observations2.map((obs) => obs.id));
9848
+ const refs = store.listProjectObservationRefs(projectId).filter((ref) => activeObservationIds.has(ref.observationId));
9724
9849
  const freshness = {
9725
9850
  current: 0,
9726
9851
  suspect: 0,
@@ -9758,9 +9883,7 @@ function collectGraph(store, projectId, observations2, exclude) {
9758
9883
  suggestedReads: compactSuggestedReads(suggestedReads, 8, exclude)
9759
9884
  };
9760
9885
  }
9761
- function buildProjectContextOverview(input) {
9762
- const active = activeObservations(input.observations, input.project.id);
9763
- const graph = collectGraph(input.store, input.project.id, active, input.exclude);
9886
+ function overviewFromGraph(input) {
9764
9887
  const status = input.store.status(input.project.id);
9765
9888
  return {
9766
9889
  project: input.project,
@@ -9771,20 +9894,26 @@ function buildProjectContextOverview(input) {
9771
9894
  edges: status.edges,
9772
9895
  refs: status.refs,
9773
9896
  ...status.indexedAt ? { indexedAt: status.indexedAt } : {},
9774
- languages: countLanguages(graph.files)
9897
+ languages: countLanguages(input.graph.files)
9775
9898
  },
9776
9899
  memory: {
9777
9900
  total: input.observations.filter((obs) => obs.projectId === input.project.id).length,
9778
- active: active.length
9901
+ active: input.active.length
9779
9902
  },
9780
- freshness: graph.freshness,
9781
- suggestedReads: graph.suggestedReads
9903
+ freshness: input.graph.freshness,
9904
+ suggestedReads: input.graph.suggestedReads
9782
9905
  };
9783
9906
  }
9784
9907
  function buildProjectContextExplain(input) {
9785
- const overview = buildProjectContextOverview(input);
9786
9908
  const active = activeObservations(input.observations, input.project.id);
9787
9909
  const graph = collectGraph(input.store, input.project.id, active, input.exclude);
9910
+ const overview = overviewFromGraph({
9911
+ project: input.project,
9912
+ store: input.store,
9913
+ observations: input.observations,
9914
+ active,
9915
+ graph
9916
+ });
9788
9917
  return {
9789
9918
  project: input.project,
9790
9919
  sources: graph.sources.sort((a, b) => a.observationId - b.observationId || (a.path ?? "").localeCompare(b.path ?? "")),
@@ -10179,18 +10308,13 @@ async function buildAutoProjectContext(input) {
10179
10308
  };
10180
10309
  }
10181
10310
  }
10182
- const overview = buildProjectContextOverview({
10183
- project: input.project,
10184
- store,
10185
- observations: input.observations,
10186
- exclude
10187
- });
10188
10311
  const explain = buildProjectContextExplain({
10189
10312
  project: input.project,
10190
10313
  store,
10191
10314
  observations: input.observations,
10192
10315
  exclude
10193
10316
  });
10317
+ const overview = explain.overview;
10194
10318
  return {
10195
10319
  project: input.project,
10196
10320
  ...task ? { task } : {},
@@ -19981,7 +20105,7 @@ The path should point to a directory containing a .git folder.`
19981
20105
  };
19982
20106
  const server = existingServer ?? new McpServer({
19983
20107
  name: "memorix",
19984
- version: true ? "1.1.9" : "1.0.1"
20108
+ version: true ? "1.1.10" : "1.0.1"
19985
20109
  });
19986
20110
  const originalRegisterTool = server.registerTool.bind(server);
19987
20111
  server.registerTool = ((name, ...args) => {