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 +6 -0
- package/dist/cli/index.js +174 -34
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +157 -33
- package/dist/index.js.map +1 -1
- package/dist/memcode-runtime/CHANGELOG.md +6 -0
- package/dist/memcode-runtime/package.json +4 -4
- package/dist/sdk.js +157 -33
- package/dist/sdk.js.map +1 -1
- package/docs/dev-log/progress.txt +21 -18
- package/package.json +1 -1
- package/src/codegraph/auto-context.ts +1 -7
- package/src/codegraph/binder.ts +109 -16
- package/src/codegraph/lite-provider.ts +2 -2
- package/src/codegraph/project-context.ts +35 -12
- package/src/codegraph/store.ts +59 -0
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
|
-
|
|
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 =
|
|
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
|
|
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
|
-
|
|
6486
|
-
|
|
6487
|
-
|
|
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
|
|
6493
|
-
|
|
6494
|
-
|
|
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 (
|
|
6501
|
-
|
|
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
|
|
9646
|
-
{ kind: "function", re: /^def\s+([A-Za-z_][\w!?=]
|
|
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 =
|
|
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
|
|
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
|
|
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.
|
|
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) => {
|