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
|
@@ -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.
|
|
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.
|
|
38
|
-
"@memorix/ai": "1.1.
|
|
39
|
-
"@memorix/tui": "1.1.
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|
-
|
|
6490
|
-
|
|
6491
|
-
|
|
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
|
|
6497
|
-
|
|
6498
|
-
|
|
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 (
|
|
6505
|
-
|
|
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
|
|
9650
|
-
{ kind: "function", re: /^def\s+([A-Za-z_][\w!?=]
|
|
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 =
|
|
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
|
|
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
|
|
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.
|
|
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) => {
|