@danielmarbach/mnemonic-mcp 0.16.0 → 0.18.0
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 +25 -1
- package/README.md +13 -0
- package/build/cache.d.ts +66 -0
- package/build/cache.d.ts.map +1 -0
- package/build/cache.js +149 -0
- package/build/cache.js.map +1 -0
- package/build/index.js +171 -24
- package/build/index.js.map +1 -1
- package/build/project-introspection.d.ts +12 -0
- package/build/project-introspection.d.ts.map +1 -1
- package/build/project-introspection.js +119 -0
- package/build/project-introspection.js.map +1 -1
- package/build/structured-content.d.ts +269 -23
- package/build/structured-content.d.ts.map +1 -1
- package/build/structured-content.js +17 -1
- package/build/structured-content.js.map +1 -1
- package/build/theme-validation.d.ts +14 -0
- package/build/theme-validation.d.ts.map +1 -0
- package/build/theme-validation.js +46 -0
- package/build/theme-validation.js.map +1 -0
- package/package.json +1 -1
package/build/index.js
CHANGED
|
@@ -9,13 +9,15 @@ import { NOTE_LIFECYCLES } from "./storage.js";
|
|
|
9
9
|
import { embed, cosineSimilarity, embedModel } from "./embeddings.js";
|
|
10
10
|
import { buildTemporalHistoryEntry, computeConfidence, getNoteProvenance } from "./provenance.js";
|
|
11
11
|
import { getOrBuildProjection } from "./projections.js";
|
|
12
|
+
import { invalidateActiveProjectCache, getOrBuildVaultEmbeddings, getOrBuildVaultNoteList, getSessionCachedNote, } from "./cache.js";
|
|
13
|
+
import { performance } from "perf_hooks";
|
|
12
14
|
import { filterRelationships, mergeRelationshipsFromNotes, normalizeMergePlanSourceIds, resolveEffectiveConsolidationMode, } from "./consolidate.js";
|
|
13
15
|
import { selectRecallResults } from "./recall.js";
|
|
14
16
|
import { getRelationshipPreview } from "./relationships.js";
|
|
15
17
|
import { cleanMarkdown } from "./markdown.js";
|
|
16
18
|
import { MnemonicConfigStore, readVaultSchemaVersion } from "./config.js";
|
|
17
19
|
import { CONSOLIDATION_MODES, PROTECTED_BRANCH_BEHAVIORS, PROJECT_POLICY_SCOPES, WRITE_SCOPES, isProtectedBranch, resolveProtectedBranchBehavior, resolveProtectedBranchPatterns, resolveConsolidationMode, resolveWriteScope, } from "./project-memory-policy.js";
|
|
18
|
-
import { classifyTheme, summarizePreview, titleCaseTheme, withinThemeScore, anchorScore, buildThemeCache, computeConnectionDiversity, } from "./project-introspection.js";
|
|
20
|
+
import { classifyTheme, classifyThemeWithGraduation, computeThemesWithGraduation, summarizePreview, titleCaseTheme, withinThemeScore, anchorScore, buildThemeCache, computeConnectionDiversity, } from "./project-introspection.js";
|
|
19
21
|
import { detectProject, getCurrentGitBranch, resolveProjectIdentity } from "./project.js";
|
|
20
22
|
import { VaultManager } from "./vault.js";
|
|
21
23
|
import { checkBranchChange } from "./branch-tracker.js";
|
|
@@ -360,6 +362,8 @@ async function ensureBranchSynced(cwd) {
|
|
|
360
362
|
}
|
|
361
363
|
const mainBackfill = await backfillEmbeddingsAfterSync(vaultManager.main.storage, "main vault", [], true);
|
|
362
364
|
console.error(`[branch] Main vault embedded ${mainBackfill.embedded} notes`);
|
|
365
|
+
// Vault contents changed — discard session cache so next access rebuilds from fresh state
|
|
366
|
+
invalidateActiveProjectCache();
|
|
363
367
|
return true;
|
|
364
368
|
}
|
|
365
369
|
function formatProjectIdentityText(identity) {
|
|
@@ -689,9 +693,22 @@ function buildMutationRetryContract(args) {
|
|
|
689
693
|
if (args.commit.status !== "failed") {
|
|
690
694
|
return undefined;
|
|
691
695
|
}
|
|
696
|
+
const recoveryKind = args.preferredRecovery ?? (args.mutationApplied
|
|
697
|
+
? "manual-exact-git-recovery"
|
|
698
|
+
: "no-manual-recovery");
|
|
699
|
+
const recoveryReason = recoveryKind === "rerun-tool-call-serial"
|
|
700
|
+
? "Tool-level reconciliation exists for this mutation; rerun the same tool call serially for the affected vault."
|
|
701
|
+
: recoveryKind === "manual-exact-git-recovery"
|
|
702
|
+
? "Mutation is already persisted on disk; manual git recovery is allowed only with the exact attemptedCommit values."
|
|
703
|
+
: "Mutation was not applied deterministically; manual git recovery is not authorized.";
|
|
692
704
|
return {
|
|
705
|
+
recovery: {
|
|
706
|
+
kind: recoveryKind,
|
|
707
|
+
allowed: recoveryKind !== "no-manual-recovery",
|
|
708
|
+
reason: recoveryReason,
|
|
709
|
+
},
|
|
693
710
|
attemptedCommit: {
|
|
694
|
-
|
|
711
|
+
subject: args.commitMessage,
|
|
695
712
|
body: args.commitBody,
|
|
696
713
|
files: args.files,
|
|
697
714
|
cwd: args.cwd,
|
|
@@ -704,19 +721,69 @@ function buildMutationRetryContract(args) {
|
|
|
704
721
|
rationale: args.mutationApplied
|
|
705
722
|
? "Mutation is already persisted on disk; commit can be retried deterministically."
|
|
706
723
|
: "Mutation was not applied; retry may require re-running the operation.",
|
|
724
|
+
instructions: {
|
|
725
|
+
sourceOfTruth: recoveryKind === "manual-exact-git-recovery" ? "attemptedCommit" : "tool-response",
|
|
726
|
+
useExactSubject: recoveryKind === "manual-exact-git-recovery",
|
|
727
|
+
useExactBody: recoveryKind === "manual-exact-git-recovery",
|
|
728
|
+
useExactFiles: recoveryKind === "manual-exact-git-recovery",
|
|
729
|
+
forbidInferenceFromHistory: true,
|
|
730
|
+
forbidInferenceFromTitleOrSummary: true,
|
|
731
|
+
forbidParallelSameVaultRetries: true,
|
|
732
|
+
preferToolReconciliation: recoveryKind === "rerun-tool-call-serial",
|
|
733
|
+
rerunSameToolCallSerially: recoveryKind === "rerun-tool-call-serial",
|
|
734
|
+
},
|
|
707
735
|
};
|
|
708
736
|
}
|
|
709
737
|
function formatRetrySummary(retry) {
|
|
710
738
|
if (!retry) {
|
|
711
739
|
return undefined;
|
|
712
740
|
}
|
|
713
|
-
const safety = retry.retrySafe ? "safe" : "requires review";
|
|
714
741
|
const opLabel = retry.attemptedCommit.operation === "add" ? "add" : "commit";
|
|
715
742
|
const error = retry.attemptedCommit.error;
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
743
|
+
const lines = [];
|
|
744
|
+
switch (retry.recovery.kind) {
|
|
745
|
+
case "rerun-tool-call-serial":
|
|
746
|
+
lines.push("Recovery: rerun same tool call serially");
|
|
747
|
+
lines.push(retry.recovery.reason);
|
|
748
|
+
lines.push("Rerun the same mnemonic tool call one time for the affected vault.");
|
|
749
|
+
lines.push("Do not replay same-vault mutations in parallel.");
|
|
750
|
+
lines.push("Manual git recovery is not authorized for this failure.");
|
|
751
|
+
lines.push("Git failure:");
|
|
752
|
+
lines.push(`${opLabel}: ${error}`);
|
|
753
|
+
break;
|
|
754
|
+
case "manual-exact-git-recovery":
|
|
755
|
+
lines.push("Recovery: manual exact git recovery allowed");
|
|
756
|
+
lines.push(retry.recovery.reason);
|
|
757
|
+
lines.push("Use only the exact values below. Do not infer from git history, note title, summary, or repo state.");
|
|
758
|
+
lines.push("");
|
|
759
|
+
lines.push("Commit subject:");
|
|
760
|
+
lines.push(retry.attemptedCommit.subject);
|
|
761
|
+
if (retry.attemptedCommit.body) {
|
|
762
|
+
lines.push("");
|
|
763
|
+
lines.push("Commit body:");
|
|
764
|
+
lines.push(retry.attemptedCommit.body);
|
|
765
|
+
}
|
|
766
|
+
lines.push("");
|
|
767
|
+
lines.push("Files:");
|
|
768
|
+
for (const file of retry.attemptedCommit.files) {
|
|
769
|
+
lines.push(`- ${file}`);
|
|
770
|
+
}
|
|
771
|
+
lines.push("");
|
|
772
|
+
lines.push("Git failure:");
|
|
773
|
+
lines.push(`${opLabel}: ${error}`);
|
|
774
|
+
break;
|
|
775
|
+
case "no-manual-recovery":
|
|
776
|
+
lines.push("Recovery: no manual recovery authorized");
|
|
777
|
+
lines.push(retry.recovery.reason);
|
|
778
|
+
lines.push("Git failure:");
|
|
779
|
+
lines.push(`${opLabel}: ${error}`);
|
|
780
|
+
break;
|
|
781
|
+
default: {
|
|
782
|
+
const _exhaustive = retry.recovery.kind;
|
|
783
|
+
throw new Error(`Unknown recovery kind: ${_exhaustive}`);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
return lines.join("\n");
|
|
720
787
|
}
|
|
721
788
|
function formatPersistenceSummary(persistence) {
|
|
722
789
|
const parts = [
|
|
@@ -727,14 +794,14 @@ function formatPersistenceSummary(persistence) {
|
|
|
727
794
|
if (persistence.embedding.reason) {
|
|
728
795
|
lines[0] += ` | embedding reason=${persistence.embedding.reason}`;
|
|
729
796
|
}
|
|
730
|
-
|
|
797
|
+
const retrySummary = formatRetrySummary(persistence.retry);
|
|
798
|
+
if (!retrySummary && persistence.git.commit === "failed" && persistence.git.commitError) {
|
|
731
799
|
const opLabel = persistence.git.commitOperation === "add" ? "add" : "commit";
|
|
732
800
|
lines.push(`Git ${opLabel} error: ${persistence.git.commitError}`);
|
|
733
801
|
}
|
|
734
802
|
if (persistence.git.push === "failed" && persistence.git.pushError) {
|
|
735
803
|
lines.push(`Git push error: ${persistence.git.pushError}`);
|
|
736
804
|
}
|
|
737
|
-
const retrySummary = formatRetrySummary(persistence.retry);
|
|
738
805
|
if (retrySummary) {
|
|
739
806
|
lines.push(retrySummary);
|
|
740
807
|
}
|
|
@@ -782,7 +849,7 @@ function vaultMatchesStorageScope(vault, storedIn) {
|
|
|
782
849
|
// "project-vault" covers the primary project vault and all submodule vaults.
|
|
783
850
|
return vault.isProject;
|
|
784
851
|
}
|
|
785
|
-
async function collectVisibleNotes(cwd, scope = "all", tags, storedIn = "any") {
|
|
852
|
+
async function collectVisibleNotes(cwd, scope = "all", tags, storedIn = "any", sessionProjectId) {
|
|
786
853
|
const project = await resolveProject(cwd);
|
|
787
854
|
const vaults = await vaultManager.searchOrder(cwd);
|
|
788
855
|
let filterProject = undefined;
|
|
@@ -793,8 +860,23 @@ async function collectVisibleNotes(cwd, scope = "all", tags, storedIn = "any") {
|
|
|
793
860
|
const seen = new Set();
|
|
794
861
|
const entries = [];
|
|
795
862
|
for (const vault of vaults) {
|
|
796
|
-
|
|
797
|
-
|
|
863
|
+
let rawNotes;
|
|
864
|
+
if (sessionProjectId) {
|
|
865
|
+
const cached = await getOrBuildVaultNoteList(sessionProjectId, vault);
|
|
866
|
+
if (cached !== undefined) {
|
|
867
|
+
// Apply project filter on the full cached list
|
|
868
|
+
rawNotes = filterProject !== undefined
|
|
869
|
+
? cached.filter((n) => filterProject === null ? !n.project : n.project === filterProject)
|
|
870
|
+
: cached;
|
|
871
|
+
}
|
|
872
|
+
else {
|
|
873
|
+
rawNotes = await vault.storage.listNotes(filterProject !== undefined ? { project: filterProject } : undefined);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
else {
|
|
877
|
+
rawNotes = await vault.storage.listNotes(filterProject !== undefined ? { project: filterProject } : undefined);
|
|
878
|
+
}
|
|
879
|
+
for (const note of rawNotes) {
|
|
798
880
|
if (seen.has(note.id)) {
|
|
799
881
|
continue;
|
|
800
882
|
}
|
|
@@ -950,6 +1032,7 @@ server.registerTool("detect_project", {
|
|
|
950
1032
|
"- Use `recall` or `project_memory_summary` to orient on existing memory.",
|
|
951
1033
|
annotations: {
|
|
952
1034
|
readOnlyHint: true,
|
|
1035
|
+
destructiveHint: false,
|
|
953
1036
|
idempotentHint: true,
|
|
954
1037
|
openWorldHint: false,
|
|
955
1038
|
},
|
|
@@ -1004,6 +1087,7 @@ server.registerTool("get_project_identity", {
|
|
|
1004
1087
|
"- Use `set_project_identity` only if the wrong remote is defining identity.",
|
|
1005
1088
|
annotations: {
|
|
1006
1089
|
readOnlyHint: true,
|
|
1090
|
+
destructiveHint: false,
|
|
1007
1091
|
idempotentHint: true,
|
|
1008
1092
|
openWorldHint: false,
|
|
1009
1093
|
},
|
|
@@ -1160,6 +1244,7 @@ server.registerTool("list_migrations", {
|
|
|
1160
1244
|
"- Run `execute_migration` with `dryRun: true` first.",
|
|
1161
1245
|
annotations: {
|
|
1162
1246
|
readOnlyHint: true,
|
|
1247
|
+
destructiveHint: false,
|
|
1163
1248
|
idempotentHint: true,
|
|
1164
1249
|
openWorldHint: false,
|
|
1165
1250
|
},
|
|
@@ -1442,6 +1527,7 @@ server.registerTool("remember", {
|
|
|
1442
1527
|
timestamp: now,
|
|
1443
1528
|
persistence,
|
|
1444
1529
|
};
|
|
1530
|
+
invalidateActiveProjectCache();
|
|
1445
1531
|
return {
|
|
1446
1532
|
content: [{ type: "text", text: textContent }],
|
|
1447
1533
|
structuredContent,
|
|
@@ -1580,6 +1666,7 @@ server.registerTool("get_project_memory_policy", {
|
|
|
1580
1666
|
"- Call `remember` with explicit `scope` for a one-off override, or `set_project_memory_policy` to change defaults.",
|
|
1581
1667
|
annotations: {
|
|
1582
1668
|
readOnlyHint: true,
|
|
1669
|
+
destructiveHint: false,
|
|
1583
1670
|
idempotentHint: true,
|
|
1584
1671
|
openWorldHint: false,
|
|
1585
1672
|
},
|
|
@@ -1653,6 +1740,7 @@ server.registerTool("recall", {
|
|
|
1653
1740
|
"- Use `get`, `update`, `relate`, or `consolidate` based on the results.",
|
|
1654
1741
|
annotations: {
|
|
1655
1742
|
readOnlyHint: true,
|
|
1743
|
+
destructiveHint: false,
|
|
1656
1744
|
idempotentHint: true,
|
|
1657
1745
|
openWorldHint: true,
|
|
1658
1746
|
},
|
|
@@ -1674,6 +1762,7 @@ server.registerTool("recall", {
|
|
|
1674
1762
|
}),
|
|
1675
1763
|
outputSchema: RecallResultSchema,
|
|
1676
1764
|
}, async ({ query, cwd, limit, minSimilarity, mode, verbose, tags, scope }) => {
|
|
1765
|
+
const t0Recall = performance.now();
|
|
1677
1766
|
await ensureBranchSynced(cwd);
|
|
1678
1767
|
const project = await resolveProject(cwd);
|
|
1679
1768
|
const queryVec = await embed(query);
|
|
@@ -1681,6 +1770,12 @@ server.registerTool("recall", {
|
|
|
1681
1770
|
const noteCache = new Map();
|
|
1682
1771
|
const noteCacheKey = (vault, id) => `${vault.storage.vaultPath}::${id}`;
|
|
1683
1772
|
const readCachedNote = async (vault, id) => {
|
|
1773
|
+
// Check session cache first (populated when getOrBuildVaultEmbeddings was called)
|
|
1774
|
+
if (project) {
|
|
1775
|
+
const sessionNote = getSessionCachedNote(project.id, vault.storage.vaultPath, id);
|
|
1776
|
+
if (sessionNote !== undefined)
|
|
1777
|
+
return sessionNote;
|
|
1778
|
+
}
|
|
1684
1779
|
const key = noteCacheKey(vault, id);
|
|
1685
1780
|
const cached = noteCache.get(key);
|
|
1686
1781
|
if (cached) {
|
|
@@ -1697,7 +1792,9 @@ server.registerTool("recall", {
|
|
|
1697
1792
|
}
|
|
1698
1793
|
const scored = [];
|
|
1699
1794
|
for (const vault of vaults) {
|
|
1700
|
-
const embeddings =
|
|
1795
|
+
const embeddings = project
|
|
1796
|
+
? (await getOrBuildVaultEmbeddings(project.id, vault)) ?? await vault.storage.listEmbeddings()
|
|
1797
|
+
: await vault.storage.listEmbeddings();
|
|
1701
1798
|
for (const rec of embeddings) {
|
|
1702
1799
|
const rawScore = cosineSimilarity(queryVec, rec.embedding);
|
|
1703
1800
|
if (rawScore < minSimilarity)
|
|
@@ -1792,6 +1889,7 @@ server.registerTool("recall", {
|
|
|
1792
1889
|
scope: scope || "all",
|
|
1793
1890
|
results: structuredResults,
|
|
1794
1891
|
};
|
|
1892
|
+
console.error(`[recall:timing] ${(performance.now() - t0Recall).toFixed(1)}ms`);
|
|
1795
1893
|
return {
|
|
1796
1894
|
content: [{ type: "text", text: textContent }],
|
|
1797
1895
|
structuredContent,
|
|
@@ -1942,6 +2040,7 @@ server.registerTool("update", {
|
|
|
1942
2040
|
lifecycle: updated.lifecycle,
|
|
1943
2041
|
persistence,
|
|
1944
2042
|
};
|
|
2043
|
+
invalidateActiveProjectCache();
|
|
1945
2044
|
return { content: [{ type: "text", text: `Updated memory '${id}'\n${formatPersistenceSummary(persistence)}` }], structuredContent };
|
|
1946
2045
|
});
|
|
1947
2046
|
// ── forget ────────────────────────────────────────────────────────────────────
|
|
@@ -2048,6 +2147,7 @@ server.registerTool("forget", {
|
|
|
2048
2147
|
retry,
|
|
2049
2148
|
};
|
|
2050
2149
|
const retrySummary = formatRetrySummary(retry);
|
|
2150
|
+
invalidateActiveProjectCache();
|
|
2051
2151
|
return {
|
|
2052
2152
|
content: [{
|
|
2053
2153
|
type: "text",
|
|
@@ -2075,6 +2175,7 @@ server.registerTool("get", {
|
|
|
2075
2175
|
"- Use `update`, `forget`, `move_memory`, or `relate` after inspection.",
|
|
2076
2176
|
annotations: {
|
|
2077
2177
|
readOnlyHint: true,
|
|
2178
|
+
destructiveHint: false,
|
|
2078
2179
|
idempotentHint: true,
|
|
2079
2180
|
openWorldHint: false,
|
|
2080
2181
|
},
|
|
@@ -2085,12 +2186,26 @@ server.registerTool("get", {
|
|
|
2085
2186
|
}),
|
|
2086
2187
|
outputSchema: GetResultSchema,
|
|
2087
2188
|
}, async ({ ids, cwd, includeRelationships }) => {
|
|
2189
|
+
const t0Get = performance.now();
|
|
2088
2190
|
await ensureBranchSynced(cwd);
|
|
2089
2191
|
const project = await resolveProject(cwd);
|
|
2090
2192
|
const found = [];
|
|
2091
2193
|
const notFound = [];
|
|
2092
2194
|
for (const id of ids) {
|
|
2093
|
-
|
|
2195
|
+
// Check session cache before hitting storage
|
|
2196
|
+
let result = null;
|
|
2197
|
+
if (project) {
|
|
2198
|
+
for (const vault of vaultManager.allKnownVaults()) {
|
|
2199
|
+
const cached = getSessionCachedNote(project.id, vault.storage.vaultPath, id);
|
|
2200
|
+
if (cached !== undefined) {
|
|
2201
|
+
result = { note: cached, vault };
|
|
2202
|
+
break;
|
|
2203
|
+
}
|
|
2204
|
+
}
|
|
2205
|
+
}
|
|
2206
|
+
if (!result) {
|
|
2207
|
+
result = await vaultManager.findNote(id, cwd);
|
|
2208
|
+
}
|
|
2094
2209
|
if (!result) {
|
|
2095
2210
|
notFound.push(id);
|
|
2096
2211
|
continue;
|
|
@@ -2138,6 +2253,7 @@ server.registerTool("get", {
|
|
|
2138
2253
|
notes: found,
|
|
2139
2254
|
notFound,
|
|
2140
2255
|
};
|
|
2256
|
+
console.error(`[get:timing] ${(performance.now() - t0Get).toFixed(1)}ms`);
|
|
2141
2257
|
return { content: [{ type: "text", text: lines.join("\n").trim() }], structuredContent };
|
|
2142
2258
|
});
|
|
2143
2259
|
// ── where_is_memory ───────────────────────────────────────────────────────────
|
|
@@ -2157,6 +2273,7 @@ server.registerTool("where_is_memory", {
|
|
|
2157
2273
|
"- Use `move_memory` if the storage location is wrong, or `get` for full inspection.",
|
|
2158
2274
|
annotations: {
|
|
2159
2275
|
readOnlyHint: true,
|
|
2276
|
+
destructiveHint: false,
|
|
2160
2277
|
idempotentHint: true,
|
|
2161
2278
|
openWorldHint: false,
|
|
2162
2279
|
},
|
|
@@ -2213,6 +2330,7 @@ server.registerTool("list", {
|
|
|
2213
2330
|
"- Use `get` for exact inspection or `update` / `consolidate` for cleanup.",
|
|
2214
2331
|
annotations: {
|
|
2215
2332
|
readOnlyHint: true,
|
|
2333
|
+
destructiveHint: false,
|
|
2216
2334
|
idempotentHint: true,
|
|
2217
2335
|
openWorldHint: false,
|
|
2218
2336
|
},
|
|
@@ -2323,6 +2441,7 @@ server.registerTool("discover_tags", {
|
|
|
2323
2441
|
"Read-only.",
|
|
2324
2442
|
annotations: {
|
|
2325
2443
|
readOnlyHint: true,
|
|
2444
|
+
destructiveHint: false,
|
|
2326
2445
|
idempotentHint: true,
|
|
2327
2446
|
openWorldHint: false,
|
|
2328
2447
|
},
|
|
@@ -2529,6 +2648,7 @@ server.registerTool("recent_memories", {
|
|
|
2529
2648
|
"- Use `get` for exact inspection or `update` to continue refining a recent note.",
|
|
2530
2649
|
annotations: {
|
|
2531
2650
|
readOnlyHint: true,
|
|
2651
|
+
destructiveHint: false,
|
|
2532
2652
|
idempotentHint: true,
|
|
2533
2653
|
openWorldHint: false,
|
|
2534
2654
|
},
|
|
@@ -2598,6 +2718,7 @@ server.registerTool("memory_graph", {
|
|
|
2598
2718
|
"- Use `get`, `relate`, `unrelate`, or `consolidate` based on what the graph reveals.",
|
|
2599
2719
|
annotations: {
|
|
2600
2720
|
readOnlyHint: true,
|
|
2721
|
+
destructiveHint: false,
|
|
2601
2722
|
idempotentHint: true,
|
|
2602
2723
|
openWorldHint: false,
|
|
2603
2724
|
},
|
|
@@ -2677,6 +2798,7 @@ server.registerTool("project_memory_summary", {
|
|
|
2677
2798
|
"- Use `recall` or `list` to drill down into specific areas.",
|
|
2678
2799
|
annotations: {
|
|
2679
2800
|
readOnlyHint: true,
|
|
2801
|
+
destructiveHint: false,
|
|
2680
2802
|
idempotentHint: true,
|
|
2681
2803
|
openWorldHint: false,
|
|
2682
2804
|
},
|
|
@@ -2690,8 +2812,11 @@ server.registerTool("project_memory_summary", {
|
|
|
2690
2812
|
}),
|
|
2691
2813
|
outputSchema: ProjectSummaryResultSchema,
|
|
2692
2814
|
}, async ({ cwd, maxPerTheme, recentLimit, anchorLimit, includeRelatedGlobal, relatedGlobalLimit }) => {
|
|
2815
|
+
const t0Summary = performance.now();
|
|
2693
2816
|
await ensureBranchSynced(cwd);
|
|
2694
|
-
|
|
2817
|
+
// Pre-resolve project so we can pass its id to collectVisibleNotes for session caching
|
|
2818
|
+
const preProject = await resolveProject(cwd);
|
|
2819
|
+
const { project, entries } = await collectVisibleNotes(cwd, "all", undefined, "any", preProject?.id);
|
|
2695
2820
|
if (!project) {
|
|
2696
2821
|
return { content: [{ type: "text", text: `Could not detect a project for: ${cwd}` }], isError: true };
|
|
2697
2822
|
}
|
|
@@ -2715,17 +2840,23 @@ server.registerTool("project_memory_summary", {
|
|
|
2715
2840
|
}
|
|
2716
2841
|
const policyLine = await formatProjectPolicyLine(project.id);
|
|
2717
2842
|
// Build theme cache for connection diversity scoring (project-scoped only)
|
|
2843
|
+
// This uses simple classifyTheme for consistent diversity calculations
|
|
2718
2844
|
const themeCache = buildThemeCache(projectEntries.map(e => e.note));
|
|
2719
|
-
//
|
|
2845
|
+
// Compute promoted themes from keywords (graduation system)
|
|
2846
|
+
const graduationResult = computeThemesWithGraduation(projectEntries.map(e => e.note));
|
|
2847
|
+
const promotedThemes = new Set(graduationResult.promotedThemes);
|
|
2848
|
+
// Categorize by theme with graduation (project-scoped only)
|
|
2720
2849
|
const themed = new Map();
|
|
2721
2850
|
for (const entry of projectEntries) {
|
|
2722
|
-
const theme =
|
|
2851
|
+
const theme = classifyThemeWithGraduation(entry.note, promotedThemes);
|
|
2723
2852
|
const bucket = themed.get(theme) ?? [];
|
|
2724
2853
|
bucket.push(entry);
|
|
2725
2854
|
themed.set(theme, bucket);
|
|
2726
2855
|
}
|
|
2727
|
-
// Theme order
|
|
2728
|
-
const
|
|
2856
|
+
// Theme order: fixed themes first, then promoted themes alphabetically, then "other"
|
|
2857
|
+
const fixedThemes = ["overview", "decisions", "tooling", "bugs", "architecture", "quality"];
|
|
2858
|
+
const dynamicThemes = graduationResult.promotedThemes.filter(t => !fixedThemes.includes(t));
|
|
2859
|
+
const themeOrder = [...fixedThemes, ...dynamicThemes.sort(), "other"];
|
|
2729
2860
|
// Calculate notes distribution (project-scoped only)
|
|
2730
2861
|
const projectVaultCount = projectEntries.filter(e => e.vault.isProject).length;
|
|
2731
2862
|
const mainVaultProjectEntries = projectEntries.filter(e => !e.vault.isProject);
|
|
@@ -3001,12 +3132,13 @@ server.registerTool("project_memory_summary", {
|
|
|
3001
3132
|
id: e.note.id,
|
|
3002
3133
|
title: e.note.title,
|
|
3003
3134
|
updatedAt: e.note.updatedAt,
|
|
3004
|
-
theme:
|
|
3135
|
+
theme: classifyThemeWithGraduation(e.note, promotedThemes),
|
|
3005
3136
|
})),
|
|
3006
3137
|
anchors,
|
|
3007
3138
|
orientation,
|
|
3008
3139
|
relatedGlobal,
|
|
3009
3140
|
};
|
|
3141
|
+
console.error(`[summary:timing] ${(performance.now() - t0Summary).toFixed(1)}ms`);
|
|
3010
3142
|
return { content: [{ type: "text", text: sections.join("\n") }], structuredContent };
|
|
3011
3143
|
});
|
|
3012
3144
|
// ── sync ──────────────────────────────────────────────────────────────────────
|
|
@@ -3093,6 +3225,8 @@ server.registerTool("sync", {
|
|
|
3093
3225
|
action: "synced",
|
|
3094
3226
|
vaults: vaultResults,
|
|
3095
3227
|
};
|
|
3228
|
+
// Vault contents may have changed via pull — discard session cache
|
|
3229
|
+
invalidateActiveProjectCache();
|
|
3096
3230
|
return { content: [{ type: "text", text: lines.join("\n") }], structuredContent };
|
|
3097
3231
|
});
|
|
3098
3232
|
// ── move_memory ───────────────────────────────────────────────────────────────
|
|
@@ -3244,6 +3378,7 @@ server.registerTool("move_memory", {
|
|
|
3244
3378
|
const associationText = metadataRewritten
|
|
3245
3379
|
? `Project association is now ${associationValue}.`
|
|
3246
3380
|
: `Project association remains ${associationValue}.`;
|
|
3381
|
+
invalidateActiveProjectCache();
|
|
3247
3382
|
return {
|
|
3248
3383
|
content: [{
|
|
3249
3384
|
type: "text",
|
|
@@ -3349,6 +3484,7 @@ server.registerTool("relate", {
|
|
|
3349
3484
|
cwd,
|
|
3350
3485
|
vault,
|
|
3351
3486
|
mutationApplied: true,
|
|
3487
|
+
preferredRecovery: "rerun-tool-call-serial",
|
|
3352
3488
|
});
|
|
3353
3489
|
const structuredContent = {
|
|
3354
3490
|
action: "related",
|
|
@@ -3398,6 +3534,7 @@ server.registerTool("relate", {
|
|
|
3398
3534
|
cwd,
|
|
3399
3535
|
vault,
|
|
3400
3536
|
mutationApplied: true,
|
|
3537
|
+
preferredRecovery: "rerun-tool-call-serial",
|
|
3401
3538
|
});
|
|
3402
3539
|
}
|
|
3403
3540
|
if (commitStatus.status === "committed") {
|
|
@@ -3416,6 +3553,7 @@ server.registerTool("relate", {
|
|
|
3416
3553
|
retry,
|
|
3417
3554
|
};
|
|
3418
3555
|
const retrySummary = formatRetrySummary(retry);
|
|
3556
|
+
invalidateActiveProjectCache();
|
|
3419
3557
|
return {
|
|
3420
3558
|
content: [{
|
|
3421
3559
|
type: "text",
|
|
@@ -3519,6 +3657,7 @@ server.registerTool("unrelate", {
|
|
|
3519
3657
|
cwd,
|
|
3520
3658
|
vault,
|
|
3521
3659
|
mutationApplied: true,
|
|
3660
|
+
preferredRecovery: "rerun-tool-call-serial",
|
|
3522
3661
|
});
|
|
3523
3662
|
const structuredContent = {
|
|
3524
3663
|
action: "unrelated",
|
|
@@ -3562,6 +3701,7 @@ server.registerTool("unrelate", {
|
|
|
3562
3701
|
cwd,
|
|
3563
3702
|
vault,
|
|
3564
3703
|
mutationApplied: true,
|
|
3704
|
+
preferredRecovery: "rerun-tool-call-serial",
|
|
3565
3705
|
});
|
|
3566
3706
|
}
|
|
3567
3707
|
if (commitStatus.status === "committed") {
|
|
@@ -3582,6 +3722,7 @@ server.registerTool("unrelate", {
|
|
|
3582
3722
|
retry,
|
|
3583
3723
|
};
|
|
3584
3724
|
const retrySummary = formatRetrySummary(retry);
|
|
3725
|
+
invalidateActiveProjectCache();
|
|
3585
3726
|
return {
|
|
3586
3727
|
content: [{
|
|
3587
3728
|
type: "text",
|
|
@@ -3680,13 +3821,19 @@ server.registerTool("consolidate", {
|
|
|
3680
3821
|
return findClusters(projectNotes, project);
|
|
3681
3822
|
case "suggest-merges":
|
|
3682
3823
|
return suggestMerges(projectNotes, threshold, defaultConsolidationMode, project, mode);
|
|
3683
|
-
case "execute-merge":
|
|
3824
|
+
case "execute-merge": {
|
|
3684
3825
|
if (!mergePlan) {
|
|
3685
3826
|
return { content: [{ type: "text", text: "execute-merge strategy requires a mergePlan with sourceIds and targetTitle." }], isError: true };
|
|
3686
3827
|
}
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
return
|
|
3828
|
+
const mergeResult = await executeMerge(entries, mergePlan, defaultConsolidationMode, project, cwd, mode, policy, allowProtectedBranch);
|
|
3829
|
+
invalidateActiveProjectCache();
|
|
3830
|
+
return mergeResult;
|
|
3831
|
+
}
|
|
3832
|
+
case "prune-superseded": {
|
|
3833
|
+
const pruneResult = await pruneSuperseded(projectNotes, mode ?? defaultConsolidationMode, project, cwd, policy, allowProtectedBranch);
|
|
3834
|
+
invalidateActiveProjectCache();
|
|
3835
|
+
return pruneResult;
|
|
3836
|
+
}
|
|
3690
3837
|
case "dry-run":
|
|
3691
3838
|
return dryRunAll(projectNotes, threshold, defaultConsolidationMode, project, mode);
|
|
3692
3839
|
default:
|