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.
package/CHANGELOG.md CHANGED
@@ -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
package/dist/cli/index.js CHANGED
@@ -3212,7 +3212,7 @@ ${import_picocolors.default.gray(m3)} ${s2}
3212
3212
 
3213
3213
  // src/cli/version.ts
3214
3214
  function getCliVersion() {
3215
- return true ? "1.1.9" : pkg.version;
3215
+ return true ? "1.1.10" : pkg.version;
3216
3216
  }
3217
3217
  var init_version = __esm({
3218
3218
  "src/cli/version.ts"() {
@@ -17262,6 +17262,44 @@ var init_store = __esm({
17262
17262
  ORDER BY path, startLine
17263
17263
  LIMIT ?
17264
17264
  `).all(projectId, like, like, like, limit).map(rowToSymbol);
17265
+ }
17266
+ listSymbols(projectId) {
17267
+ return this.db.prepare(`
17268
+ SELECT * FROM code_symbols
17269
+ WHERE projectId = ? AND stale = 0
17270
+ ORDER BY path, startLine
17271
+ `).all(projectId).map(rowToSymbol);
17272
+ }
17273
+ findSymbolsByNames(projectId, names, fileIds = []) {
17274
+ const candidates = [...new Set(names.map((name) => name.trim()).filter(Boolean))];
17275
+ if (candidates.length === 0) return [];
17276
+ const candidateJson = JSON.stringify(candidates);
17277
+ const hintedFiles = [...new Set(fileIds.map((fileId) => fileId.trim()).filter(Boolean))];
17278
+ if (hintedFiles.length > 0) {
17279
+ return this.db.prepare(`
17280
+ SELECT * FROM code_symbols
17281
+ WHERE projectId = ?
17282
+ AND stale = 0
17283
+ AND name IN (SELECT value FROM json_each(?))
17284
+ AND fileId IN (SELECT value FROM json_each(?))
17285
+ ORDER BY path, startLine
17286
+ `).all(projectId, candidateJson, JSON.stringify(hintedFiles)).map(rowToSymbol);
17287
+ }
17288
+ return this.db.prepare(`
17289
+ SELECT symbols.*
17290
+ FROM code_symbols AS symbols
17291
+ INNER JOIN (
17292
+ SELECT name
17293
+ FROM code_symbols
17294
+ WHERE projectId = ?
17295
+ AND stale = 0
17296
+ AND name IN (SELECT value FROM json_each(?))
17297
+ GROUP BY name
17298
+ HAVING COUNT(*) = 1
17299
+ ) AS unambiguous ON unambiguous.name = symbols.name
17300
+ WHERE symbols.projectId = ? AND symbols.stale = 0
17301
+ ORDER BY symbols.path, symbols.startLine
17302
+ `).all(projectId, candidateJson, projectId).map(rowToSymbol);
17265
17303
  }
17266
17304
  listSymbolsForFile(fileId) {
17267
17305
  return this.db.prepare(`SELECT * FROM code_symbols WHERE fileId = ? AND stale = 0 ORDER BY startLine`).all(fileId).map(rowToSymbol);
@@ -17275,6 +17313,22 @@ var init_store = __esm({
17275
17313
  WHERE projectId = ? AND observationId = ?
17276
17314
  ORDER BY status, id
17277
17315
  `).all(projectId, observationId).map(rowToRef);
17316
+ }
17317
+ listProjectObservationRefs(projectId) {
17318
+ return this.db.prepare(`
17319
+ SELECT * FROM observation_code_refs
17320
+ WHERE projectId = ?
17321
+ ORDER BY observationId, status, id
17322
+ `).all(projectId).map(rowToRef);
17323
+ }
17324
+ listReferencedSymbols(projectId) {
17325
+ return this.db.prepare(`
17326
+ SELECT DISTINCT symbols.*
17327
+ FROM code_symbols AS symbols
17328
+ INNER JOIN observation_code_refs AS refs ON refs.symbolId = symbols.id
17329
+ WHERE refs.projectId = ? AND symbols.projectId = ? AND symbols.stale = 0
17330
+ ORDER BY symbols.path, symbols.startLine
17331
+ `).all(projectId, projectId).map(rowToSymbol);
17278
17332
  }
17279
17333
  status(projectId) {
17280
17334
  const files = this.db.prepare(`SELECT COUNT(*) AS count FROM code_files WHERE projectId = ?`).get(projectId).count;
@@ -17301,6 +17355,15 @@ function mentionsSymbol(text, symbol) {
17301
17355
  const name = symbol.name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
17302
17356
  return new RegExp(`(^|[^\\w$])${name}([^\\w$]|$)`).test(text);
17303
17357
  }
17358
+ function identifierCandidates(text) {
17359
+ return [...new Set(text.match(/[A-Za-z_$][\w$]*(?:::[A-Za-z_$][\w$]*)*[!?=]?/g) ?? [])];
17360
+ }
17361
+ function codeIdentifierCandidates(text) {
17362
+ const explicit = /* @__PURE__ */ new Set();
17363
+ for (const match of text.matchAll(/`([A-Za-z_$][\w$]*(?:::[A-Za-z_$][\w$]*)*[!?=]?)`/g)) explicit.add(match[1]);
17364
+ for (const match of text.matchAll(/\b([A-Za-z_$][\w$]*(?:::[A-Za-z_$][\w$]*)*[!?=]?)\s*\(/g)) explicit.add(match[1]);
17365
+ 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));
17366
+ }
17304
17367
  function fileRef(projectId, obs, file) {
17305
17368
  return {
17306
17369
  id: makeObservationCodeRefId(projectId, obs.id, file.id),
@@ -17327,40 +17390,101 @@ function symbolRef(projectId, obs, file, symbol) {
17327
17390
  createdAt: obs.createdAt
17328
17391
  };
17329
17392
  }
17330
- async function bindObservationToCode(store2, obs) {
17393
+ function resolveObservationCodeRefs(obs, lookup) {
17331
17394
  const refs = /* @__PURE__ */ new Map();
17332
17395
  const text = observationText(obs);
17333
17396
  const candidateFiles = /* @__PURE__ */ new Map();
17334
17397
  for (const rawPath of obs.filesModified ?? []) {
17335
- const file = store2.getFile(obs.projectId, normalizeCodePath(rawPath));
17398
+ const file = lookup.findFile(normalizeCodePath(rawPath));
17336
17399
  if (!file) continue;
17337
17400
  candidateFiles.set(file.id, file);
17338
17401
  const ref = fileRef(obs.projectId, obs, file);
17339
17402
  refs.set(ref.id, ref);
17340
17403
  }
17341
- const symbols = store2.findSymbols(obs.projectId, "", 500);
17404
+ const hintedFileIds = new Set(candidateFiles.keys());
17405
+ const symbolNames = hintedFileIds.size > 0 ? identifierCandidates(text) : codeIdentifierCandidates(text);
17406
+ const symbols = lookup.findSymbols(symbolNames, [...hintedFileIds]);
17407
+ const symbolsByName = /* @__PURE__ */ new Map();
17342
17408
  for (const symbol of symbols) {
17343
- if (!mentionsSymbol(text, symbol)) continue;
17344
- const file = candidateFiles.get(symbol.fileId) ?? store2.getFile(obs.projectId, symbol.path);
17345
- if (!file) continue;
17346
- candidateFiles.set(file.id, file);
17347
- const ref = symbolRef(obs.projectId, obs, file, symbol);
17348
- refs.set(ref.id, ref);
17409
+ const group = symbolsByName.get(symbol.name) ?? [];
17410
+ group.push(symbol);
17411
+ symbolsByName.set(symbol.name, group);
17349
17412
  }
17350
- const result = [...refs.values()];
17351
- store2.replaceObservationRefs(obs.projectId, obs.id, result);
17352
- return result;
17413
+ for (const group of symbolsByName.values()) {
17414
+ const candidates = group.length === 1 ? group : [];
17415
+ for (const symbol of candidates) {
17416
+ if (!mentionsSymbol(text, symbol)) continue;
17417
+ const file = candidateFiles.get(symbol.fileId) ?? lookup.findFile(symbol.path);
17418
+ if (!file) continue;
17419
+ candidateFiles.set(file.id, file);
17420
+ const ref = symbolRef(obs.projectId, obs, file, symbol);
17421
+ refs.set(ref.id, ref);
17422
+ }
17423
+ }
17424
+ return [...refs.values()];
17425
+ }
17426
+ function createStoreLookup(store2, projectId) {
17427
+ return {
17428
+ findFile: (path29) => store2.getFile(projectId, path29) ?? void 0,
17429
+ findSymbols: (names, hintedFileIds) => store2.findSymbolsByNames(projectId, names, hintedFileIds)
17430
+ };
17431
+ }
17432
+ function createSnapshotLookup(files, symbols) {
17433
+ const filesByPath = new Map(files.map((file) => [normalizeCodePath(file.path), file]));
17434
+ const symbolsByName = /* @__PURE__ */ new Map();
17435
+ for (const symbol of symbols) {
17436
+ const group = symbolsByName.get(symbol.name) ?? [];
17437
+ group.push(symbol);
17438
+ symbolsByName.set(symbol.name, group);
17439
+ }
17440
+ return {
17441
+ findFile: (path29) => filesByPath.get(normalizeCodePath(path29)),
17442
+ findSymbols: (names, hintedFileIds) => {
17443
+ const hinted = new Set(hintedFileIds);
17444
+ const found = [];
17445
+ for (const name of names) {
17446
+ const candidates = symbolsByName.get(name) ?? [];
17447
+ found.push(...hinted.size > 0 ? candidates.filter((symbol) => hinted.has(symbol.fileId)) : candidates);
17448
+ }
17449
+ return found;
17450
+ }
17451
+ };
17452
+ }
17453
+ async function bindObservationToCode(store2, obs) {
17454
+ const refs = resolveObservationCodeRefs(obs, createStoreLookup(store2, obs.projectId));
17455
+ store2.replaceObservationRefs(obs.projectId, obs.id, refs);
17456
+ return refs;
17353
17457
  }
17354
17458
  async function backfillMissingObservationCodeRefs(store2, observations2) {
17355
17459
  let observationsBackfilled = 0;
17356
17460
  let refsBackfilled = 0;
17461
+ const boundObservationIdsByProject = /* @__PURE__ */ new Map();
17462
+ const lookupByProject = /* @__PURE__ */ new Map();
17463
+ for (const projectId of new Set(observations2.map((observation) => observation.projectId))) {
17464
+ boundObservationIdsByProject.set(
17465
+ projectId,
17466
+ new Set(store2.listProjectObservationRefs(projectId).map((ref) => ref.observationId))
17467
+ );
17468
+ lookupByProject.set(
17469
+ projectId,
17470
+ createSnapshotLookup(store2.listFiles(projectId), store2.listSymbols(projectId))
17471
+ );
17472
+ }
17473
+ const refsToInsert = [];
17357
17474
  for (const observation of observations2) {
17358
- if (store2.listObservationRefs(observation.projectId, observation.id).length > 0) continue;
17359
- const refs = await bindObservationToCode(store2, observation);
17475
+ if (boundObservationIdsByProject.get(observation.projectId)?.has(observation.id)) continue;
17476
+ if ((observation.filesModified?.length ?? 0) === 0 && codeIdentifierCandidates(observationText(observation)).length === 0) {
17477
+ continue;
17478
+ }
17479
+ const lookup = lookupByProject.get(observation.projectId);
17480
+ if (!lookup) continue;
17481
+ const refs = resolveObservationCodeRefs(observation, lookup);
17360
17482
  if (refs.length === 0) continue;
17361
17483
  observationsBackfilled += 1;
17362
17484
  refsBackfilled += refs.length;
17485
+ refsToInsert.push(...refs);
17363
17486
  }
17487
+ store2.upsertObservationRefs(refsToInsert);
17364
17488
  return {
17365
17489
  observationsScanned: observations2.length,
17366
17490
  observationsBackfilled,
@@ -26626,8 +26750,8 @@ var init_lite_provider = __esm({
26626
26750
  },
26627
26751
  ruby: {
26628
26752
  symbols: [
26629
- { kind: "class", re: /^class\s+([A-Za-z_][\w:]*?)\b/gm },
26630
- { kind: "function", re: /^def\s+([A-Za-z_][\w!?=]*)\b/gm }
26753
+ { kind: "class", re: /^class\s+([A-Za-z_][\w]*(?:::[A-Za-z_][\w]*)*)/gm },
26754
+ { kind: "function", re: /^def\s+([A-Za-z_][\w]*[!?=]?)/gm }
26631
26755
  ],
26632
26756
  imports: [/require\s+['"]([^'"]+)['"]/g]
26633
26757
  },
@@ -26703,11 +26827,12 @@ function compactSuggestedReads(paths, limit = 8, exclude) {
26703
26827
  }
26704
26828
  function collectGraph(store2, projectId, observations2, exclude) {
26705
26829
  const files = store2.listFiles(projectId);
26706
- const symbols = files.flatMap((file) => store2.listSymbolsForFile(file.id));
26830
+ const symbols = store2.listReferencedSymbols(projectId);
26707
26831
  const filesById = new Map(files.map((file) => [file.id, file]));
26708
26832
  const symbolsById = new Map(symbols.map((symbol) => [symbol.id, symbol]));
26709
26833
  const observationsById = new Map(observations2.map((obs) => [obs.id, obs]));
26710
- const refs = observations2.flatMap((obs) => store2.listObservationRefs(projectId, obs.id));
26834
+ const activeObservationIds = new Set(observations2.map((obs) => obs.id));
26835
+ const refs = store2.listProjectObservationRefs(projectId).filter((ref) => activeObservationIds.has(ref.observationId));
26711
26836
  const freshness = {
26712
26837
  current: 0,
26713
26838
  suspect: 0,
@@ -26745,9 +26870,7 @@ function collectGraph(store2, projectId, observations2, exclude) {
26745
26870
  suggestedReads: compactSuggestedReads(suggestedReads, 8, exclude)
26746
26871
  };
26747
26872
  }
26748
- function buildProjectContextOverview(input) {
26749
- const active = activeObservations(input.observations, input.project.id);
26750
- const graph = collectGraph(input.store, input.project.id, active, input.exclude);
26873
+ function overviewFromGraph(input) {
26751
26874
  const status = input.store.status(input.project.id);
26752
26875
  return {
26753
26876
  project: input.project,
@@ -26758,20 +26881,37 @@ function buildProjectContextOverview(input) {
26758
26881
  edges: status.edges,
26759
26882
  refs: status.refs,
26760
26883
  ...status.indexedAt ? { indexedAt: status.indexedAt } : {},
26761
- languages: countLanguages(graph.files)
26884
+ languages: countLanguages(input.graph.files)
26762
26885
  },
26763
26886
  memory: {
26764
26887
  total: input.observations.filter((obs) => obs.projectId === input.project.id).length,
26765
- active: active.length
26888
+ active: input.active.length
26766
26889
  },
26767
- freshness: graph.freshness,
26768
- suggestedReads: graph.suggestedReads
26890
+ freshness: input.graph.freshness,
26891
+ suggestedReads: input.graph.suggestedReads
26769
26892
  };
26770
26893
  }
26894
+ function buildProjectContextOverview(input) {
26895
+ const active = activeObservations(input.observations, input.project.id);
26896
+ const graph = collectGraph(input.store, input.project.id, active, input.exclude);
26897
+ return overviewFromGraph({
26898
+ project: input.project,
26899
+ store: input.store,
26900
+ observations: input.observations,
26901
+ active,
26902
+ graph
26903
+ });
26904
+ }
26771
26905
  function buildProjectContextExplain(input) {
26772
- const overview = buildProjectContextOverview(input);
26773
26906
  const active = activeObservations(input.observations, input.project.id);
26774
26907
  const graph = collectGraph(input.store, input.project.id, active, input.exclude);
26908
+ const overview = overviewFromGraph({
26909
+ project: input.project,
26910
+ store: input.store,
26911
+ observations: input.observations,
26912
+ active,
26913
+ graph
26914
+ });
26775
26915
  return {
26776
26916
  project: input.project,
26777
26917
  sources: graph.sources.sort((a3, b3) => a3.observationId - b3.observationId || (a3.path ?? "").localeCompare(b3.path ?? "")),
@@ -27208,18 +27348,13 @@ async function buildAutoProjectContext(input) {
27208
27348
  };
27209
27349
  }
27210
27350
  }
27211
- const overview = buildProjectContextOverview({
27212
- project: input.project,
27213
- store: store2,
27214
- observations: input.observations,
27215
- exclude
27216
- });
27217
27351
  const explain = buildProjectContextExplain({
27218
27352
  project: input.project,
27219
27353
  store: store2,
27220
27354
  observations: input.observations,
27221
27355
  exclude
27222
27356
  });
27357
+ const overview = explain.overview;
27223
27358
  return {
27224
27359
  project: input.project,
27225
27360
  ...task ? { task } : {},
@@ -56567,6 +56702,7 @@ var require_loader = __commonJS({
56567
56702
  this.legacy = options2["legacy"] || false;
56568
56703
  this.json = options2["json"] || false;
56569
56704
  this.listener = options2["listener"] || null;
56705
+ this.maxTotalMergeKeys = typeof options2["maxTotalMergeKeys"] === "number" ? options2["maxTotalMergeKeys"] : 1e4;
56570
56706
  this.implicitTypes = this.schema.compiledImplicit;
56571
56707
  this.typeMap = this.schema.compiledTypeMap;
56572
56708
  this.length = input.length;
@@ -56574,6 +56710,7 @@ var require_loader = __commonJS({
56574
56710
  this.line = 0;
56575
56711
  this.lineStart = 0;
56576
56712
  this.lineIndent = 0;
56713
+ this.totalMergeKeys = 0;
56577
56714
  this.documents = [];
56578
56715
  }
56579
56716
  function generateError(state, message) {
@@ -56658,6 +56795,9 @@ var require_loader = __commonJS({
56658
56795
  sourceKeys = Object.keys(source);
56659
56796
  for (index = 0, quantity = sourceKeys.length; index < quantity; index += 1) {
56660
56797
  key = sourceKeys[index];
56798
+ if (state.maxTotalMergeKeys !== -1 && ++state.totalMergeKeys > state.maxTotalMergeKeys) {
56799
+ throwError(state, "merge keys exceeded maxTotalMergeKeys (" + state.maxTotalMergeKeys + ")");
56800
+ }
56661
56801
  if (!_hasOwnProperty.call(destination, key)) {
56662
56802
  setProperty(destination, key, source[key]);
56663
56803
  overridableKeys[key] = true;
@@ -62016,7 +62156,7 @@ The path should point to a directory containing a .git folder.`
62016
62156
  };
62017
62157
  const server = existingServer ?? new McpServer({
62018
62158
  name: "memorix",
62019
- version: true ? "1.1.9" : "1.0.1"
62159
+ version: true ? "1.1.10" : "1.0.1"
62020
62160
  });
62021
62161
  const originalRegisterTool = server.registerTool.bind(server);
62022
62162
  server.registerTool = ((name, ...args) => {