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/dist/index.js CHANGED
@@ -6404,6 +6404,44 @@ var init_store = __esm({
6404
6404
  ORDER BY path, startLine
6405
6405
  LIMIT ?
6406
6406
  `).all(projectId, like, like, like, limit).map(rowToSymbol);
6407
+ }
6408
+ listSymbols(projectId) {
6409
+ return this.db.prepare(`
6410
+ SELECT * FROM code_symbols
6411
+ WHERE projectId = ? AND stale = 0
6412
+ ORDER BY path, startLine
6413
+ `).all(projectId).map(rowToSymbol);
6414
+ }
6415
+ findSymbolsByNames(projectId, names, fileIds = []) {
6416
+ const candidates = [...new Set(names.map((name) => name.trim()).filter(Boolean))];
6417
+ if (candidates.length === 0) return [];
6418
+ const candidateJson = JSON.stringify(candidates);
6419
+ const hintedFiles = [...new Set(fileIds.map((fileId) => fileId.trim()).filter(Boolean))];
6420
+ if (hintedFiles.length > 0) {
6421
+ return this.db.prepare(`
6422
+ SELECT * FROM code_symbols
6423
+ WHERE projectId = ?
6424
+ AND stale = 0
6425
+ AND name IN (SELECT value FROM json_each(?))
6426
+ AND fileId IN (SELECT value FROM json_each(?))
6427
+ ORDER BY path, startLine
6428
+ `).all(projectId, candidateJson, JSON.stringify(hintedFiles)).map(rowToSymbol);
6429
+ }
6430
+ return this.db.prepare(`
6431
+ SELECT symbols.*
6432
+ FROM code_symbols AS symbols
6433
+ INNER JOIN (
6434
+ SELECT name
6435
+ FROM code_symbols
6436
+ WHERE projectId = ?
6437
+ AND stale = 0
6438
+ AND name IN (SELECT value FROM json_each(?))
6439
+ GROUP BY name
6440
+ HAVING COUNT(*) = 1
6441
+ ) AS unambiguous ON unambiguous.name = symbols.name
6442
+ WHERE symbols.projectId = ? AND symbols.stale = 0
6443
+ ORDER BY symbols.path, symbols.startLine
6444
+ `).all(projectId, candidateJson, projectId).map(rowToSymbol);
6407
6445
  }
6408
6446
  listSymbolsForFile(fileId) {
6409
6447
  return this.db.prepare(`SELECT * FROM code_symbols WHERE fileId = ? AND stale = 0 ORDER BY startLine`).all(fileId).map(rowToSymbol);
@@ -6417,6 +6455,22 @@ var init_store = __esm({
6417
6455
  WHERE projectId = ? AND observationId = ?
6418
6456
  ORDER BY status, id
6419
6457
  `).all(projectId, observationId).map(rowToRef);
6458
+ }
6459
+ listProjectObservationRefs(projectId) {
6460
+ return this.db.prepare(`
6461
+ SELECT * FROM observation_code_refs
6462
+ WHERE projectId = ?
6463
+ ORDER BY observationId, status, id
6464
+ `).all(projectId).map(rowToRef);
6465
+ }
6466
+ listReferencedSymbols(projectId) {
6467
+ return this.db.prepare(`
6468
+ SELECT DISTINCT symbols.*
6469
+ FROM code_symbols AS symbols
6470
+ INNER JOIN observation_code_refs AS refs ON refs.symbolId = symbols.id
6471
+ WHERE refs.projectId = ? AND symbols.projectId = ? AND symbols.stale = 0
6472
+ ORDER BY symbols.path, symbols.startLine
6473
+ `).all(projectId, projectId).map(rowToSymbol);
6420
6474
  }
6421
6475
  status(projectId) {
6422
6476
  const files = this.db.prepare(`SELECT COUNT(*) AS count FROM code_files WHERE projectId = ?`).get(projectId).count;
@@ -6443,6 +6497,15 @@ function mentionsSymbol(text, symbol) {
6443
6497
  const name = symbol.name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6444
6498
  return new RegExp(`(^|[^\\w$])${name}([^\\w$]|$)`).test(text);
6445
6499
  }
6500
+ function identifierCandidates(text) {
6501
+ return [...new Set(text.match(/[A-Za-z_$][\w$]*(?:::[A-Za-z_$][\w$]*)*[!?=]?/g) ?? [])];
6502
+ }
6503
+ function codeIdentifierCandidates(text) {
6504
+ const explicit = /* @__PURE__ */ new Set();
6505
+ for (const match of text.matchAll(/`([A-Za-z_$][\w$]*(?:::[A-Za-z_$][\w$]*)*[!?=]?)`/g)) explicit.add(match[1]);
6506
+ for (const match of text.matchAll(/\b([A-Za-z_$][\w$]*(?:::[A-Za-z_$][\w$]*)*[!?=]?)\s*\(/g)) explicit.add(match[1]);
6507
+ 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));
6508
+ }
6446
6509
  function fileRef(projectId, obs, file) {
6447
6510
  return {
6448
6511
  id: makeObservationCodeRefId(projectId, obs.id, file.id),
@@ -6469,40 +6532,101 @@ function symbolRef(projectId, obs, file, symbol) {
6469
6532
  createdAt: obs.createdAt
6470
6533
  };
6471
6534
  }
6472
- async function bindObservationToCode(store, obs) {
6535
+ function resolveObservationCodeRefs(obs, lookup) {
6473
6536
  const refs = /* @__PURE__ */ new Map();
6474
6537
  const text = observationText(obs);
6475
6538
  const candidateFiles = /* @__PURE__ */ new Map();
6476
6539
  for (const rawPath of obs.filesModified ?? []) {
6477
- const file = store.getFile(obs.projectId, normalizeCodePath(rawPath));
6540
+ const file = lookup.findFile(normalizeCodePath(rawPath));
6478
6541
  if (!file) continue;
6479
6542
  candidateFiles.set(file.id, file);
6480
6543
  const ref = fileRef(obs.projectId, obs, file);
6481
6544
  refs.set(ref.id, ref);
6482
6545
  }
6483
- const symbols = store.findSymbols(obs.projectId, "", 500);
6546
+ const hintedFileIds = new Set(candidateFiles.keys());
6547
+ const symbolNames = hintedFileIds.size > 0 ? identifierCandidates(text) : codeIdentifierCandidates(text);
6548
+ const symbols = lookup.findSymbols(symbolNames, [...hintedFileIds]);
6549
+ const symbolsByName = /* @__PURE__ */ new Map();
6484
6550
  for (const symbol of symbols) {
6485
- if (!mentionsSymbol(text, symbol)) continue;
6486
- const file = candidateFiles.get(symbol.fileId) ?? store.getFile(obs.projectId, symbol.path);
6487
- if (!file) continue;
6488
- candidateFiles.set(file.id, file);
6489
- const ref = symbolRef(obs.projectId, obs, file, symbol);
6490
- refs.set(ref.id, ref);
6551
+ const group = symbolsByName.get(symbol.name) ?? [];
6552
+ group.push(symbol);
6553
+ symbolsByName.set(symbol.name, group);
6491
6554
  }
6492
- const result = [...refs.values()];
6493
- store.replaceObservationRefs(obs.projectId, obs.id, result);
6494
- return result;
6555
+ for (const group of symbolsByName.values()) {
6556
+ const candidates = group.length === 1 ? group : [];
6557
+ for (const symbol of candidates) {
6558
+ if (!mentionsSymbol(text, symbol)) continue;
6559
+ const file = candidateFiles.get(symbol.fileId) ?? lookup.findFile(symbol.path);
6560
+ if (!file) continue;
6561
+ candidateFiles.set(file.id, file);
6562
+ const ref = symbolRef(obs.projectId, obs, file, symbol);
6563
+ refs.set(ref.id, ref);
6564
+ }
6565
+ }
6566
+ return [...refs.values()];
6567
+ }
6568
+ function createStoreLookup(store, projectId) {
6569
+ return {
6570
+ findFile: (path20) => store.getFile(projectId, path20) ?? void 0,
6571
+ findSymbols: (names, hintedFileIds) => store.findSymbolsByNames(projectId, names, hintedFileIds)
6572
+ };
6573
+ }
6574
+ function createSnapshotLookup(files, symbols) {
6575
+ const filesByPath = new Map(files.map((file) => [normalizeCodePath(file.path), file]));
6576
+ const symbolsByName = /* @__PURE__ */ new Map();
6577
+ for (const symbol of symbols) {
6578
+ const group = symbolsByName.get(symbol.name) ?? [];
6579
+ group.push(symbol);
6580
+ symbolsByName.set(symbol.name, group);
6581
+ }
6582
+ return {
6583
+ findFile: (path20) => filesByPath.get(normalizeCodePath(path20)),
6584
+ findSymbols: (names, hintedFileIds) => {
6585
+ const hinted = new Set(hintedFileIds);
6586
+ const found = [];
6587
+ for (const name of names) {
6588
+ const candidates = symbolsByName.get(name) ?? [];
6589
+ found.push(...hinted.size > 0 ? candidates.filter((symbol) => hinted.has(symbol.fileId)) : candidates);
6590
+ }
6591
+ return found;
6592
+ }
6593
+ };
6594
+ }
6595
+ async function bindObservationToCode(store, obs) {
6596
+ const refs = resolveObservationCodeRefs(obs, createStoreLookup(store, obs.projectId));
6597
+ store.replaceObservationRefs(obs.projectId, obs.id, refs);
6598
+ return refs;
6495
6599
  }
6496
6600
  async function backfillMissingObservationCodeRefs(store, observations2) {
6497
6601
  let observationsBackfilled = 0;
6498
6602
  let refsBackfilled = 0;
6603
+ const boundObservationIdsByProject = /* @__PURE__ */ new Map();
6604
+ const lookupByProject = /* @__PURE__ */ new Map();
6605
+ for (const projectId of new Set(observations2.map((observation) => observation.projectId))) {
6606
+ boundObservationIdsByProject.set(
6607
+ projectId,
6608
+ new Set(store.listProjectObservationRefs(projectId).map((ref) => ref.observationId))
6609
+ );
6610
+ lookupByProject.set(
6611
+ projectId,
6612
+ createSnapshotLookup(store.listFiles(projectId), store.listSymbols(projectId))
6613
+ );
6614
+ }
6615
+ const refsToInsert = [];
6499
6616
  for (const observation of observations2) {
6500
- if (store.listObservationRefs(observation.projectId, observation.id).length > 0) continue;
6501
- const refs = await bindObservationToCode(store, observation);
6617
+ if (boundObservationIdsByProject.get(observation.projectId)?.has(observation.id)) continue;
6618
+ if ((observation.filesModified?.length ?? 0) === 0 && codeIdentifierCandidates(observationText(observation)).length === 0) {
6619
+ continue;
6620
+ }
6621
+ const lookup = lookupByProject.get(observation.projectId);
6622
+ if (!lookup) continue;
6623
+ const refs = resolveObservationCodeRefs(observation, lookup);
6502
6624
  if (refs.length === 0) continue;
6503
6625
  observationsBackfilled += 1;
6504
6626
  refsBackfilled += refs.length;
6627
+ refsToInsert.push(...refs);
6505
6628
  }
6629
+ store.upsertObservationRefs(refsToInsert);
6506
6630
  return {
6507
6631
  observationsScanned: observations2.length,
6508
6632
  observationsBackfilled,
@@ -9642,8 +9766,8 @@ var init_lite_provider = __esm({
9642
9766
  },
9643
9767
  ruby: {
9644
9768
  symbols: [
9645
- { kind: "class", re: /^class\s+([A-Za-z_][\w:]*?)\b/gm },
9646
- { kind: "function", re: /^def\s+([A-Za-z_][\w!?=]*)\b/gm }
9769
+ { kind: "class", re: /^class\s+([A-Za-z_][\w]*(?:::[A-Za-z_][\w]*)*)/gm },
9770
+ { kind: "function", re: /^def\s+([A-Za-z_][\w]*[!?=]?)/gm }
9647
9771
  ],
9648
9772
  imports: [/require\s+['"]([^'"]+)['"]/g]
9649
9773
  },
@@ -9712,11 +9836,12 @@ function compactSuggestedReads(paths, limit = 8, exclude) {
9712
9836
  }
9713
9837
  function collectGraph(store, projectId, observations2, exclude) {
9714
9838
  const files = store.listFiles(projectId);
9715
- const symbols = files.flatMap((file) => store.listSymbolsForFile(file.id));
9839
+ const symbols = store.listReferencedSymbols(projectId);
9716
9840
  const filesById = new Map(files.map((file) => [file.id, file]));
9717
9841
  const symbolsById = new Map(symbols.map((symbol) => [symbol.id, symbol]));
9718
9842
  const observationsById = new Map(observations2.map((obs) => [obs.id, obs]));
9719
- const refs = observations2.flatMap((obs) => store.listObservationRefs(projectId, obs.id));
9843
+ const activeObservationIds = new Set(observations2.map((obs) => obs.id));
9844
+ const refs = store.listProjectObservationRefs(projectId).filter((ref) => activeObservationIds.has(ref.observationId));
9720
9845
  const freshness = {
9721
9846
  current: 0,
9722
9847
  suspect: 0,
@@ -9754,9 +9879,7 @@ function collectGraph(store, projectId, observations2, exclude) {
9754
9879
  suggestedReads: compactSuggestedReads(suggestedReads, 8, exclude)
9755
9880
  };
9756
9881
  }
9757
- function buildProjectContextOverview(input) {
9758
- const active = activeObservations(input.observations, input.project.id);
9759
- const graph = collectGraph(input.store, input.project.id, active, input.exclude);
9882
+ function overviewFromGraph(input) {
9760
9883
  const status = input.store.status(input.project.id);
9761
9884
  return {
9762
9885
  project: input.project,
@@ -9767,20 +9890,26 @@ function buildProjectContextOverview(input) {
9767
9890
  edges: status.edges,
9768
9891
  refs: status.refs,
9769
9892
  ...status.indexedAt ? { indexedAt: status.indexedAt } : {},
9770
- languages: countLanguages(graph.files)
9893
+ languages: countLanguages(input.graph.files)
9771
9894
  },
9772
9895
  memory: {
9773
9896
  total: input.observations.filter((obs) => obs.projectId === input.project.id).length,
9774
- active: active.length
9897
+ active: input.active.length
9775
9898
  },
9776
- freshness: graph.freshness,
9777
- suggestedReads: graph.suggestedReads
9899
+ freshness: input.graph.freshness,
9900
+ suggestedReads: input.graph.suggestedReads
9778
9901
  };
9779
9902
  }
9780
9903
  function buildProjectContextExplain(input) {
9781
- const overview = buildProjectContextOverview(input);
9782
9904
  const active = activeObservations(input.observations, input.project.id);
9783
9905
  const graph = collectGraph(input.store, input.project.id, active, input.exclude);
9906
+ const overview = overviewFromGraph({
9907
+ project: input.project,
9908
+ store: input.store,
9909
+ observations: input.observations,
9910
+ active,
9911
+ graph
9912
+ });
9784
9913
  return {
9785
9914
  project: input.project,
9786
9915
  sources: graph.sources.sort((a, b) => a.observationId - b.observationId || (a.path ?? "").localeCompare(b.path ?? "")),
@@ -10175,18 +10304,13 @@ async function buildAutoProjectContext(input) {
10175
10304
  };
10176
10305
  }
10177
10306
  }
10178
- const overview = buildProjectContextOverview({
10179
- project: input.project,
10180
- store,
10181
- observations: input.observations,
10182
- exclude
10183
- });
10184
10307
  const explain = buildProjectContextExplain({
10185
10308
  project: input.project,
10186
10309
  store,
10187
10310
  observations: input.observations,
10188
10311
  exclude
10189
10312
  });
10313
+ const overview = explain.overview;
10190
10314
  return {
10191
10315
  project: input.project,
10192
10316
  ...task ? { task } : {},
@@ -20066,7 +20190,7 @@ The path should point to a directory containing a .git folder.`
20066
20190
  };
20067
20191
  const server = existingServer ?? new McpServer({
20068
20192
  name: "memorix",
20069
- version: true ? "1.1.9" : "1.0.1"
20193
+ version: true ? "1.1.10" : "1.0.1"
20070
20194
  });
20071
20195
  const originalRegisterTool = server.registerTool.bind(server);
20072
20196
  server.registerTool = ((name, ...args) => {