@danielmarbach/mnemonic-mcp 0.26.1 → 0.27.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 +20 -0
- package/README.md +10 -4
- package/build/consolidate.d.ts +21 -0
- package/build/consolidate.d.ts.map +1 -1
- package/build/consolidate.js +124 -0
- package/build/consolidate.js.map +1 -1
- package/build/index.js +274 -61
- package/build/index.js.map +1 -1
- package/build/structured-content.d.ts +218 -0
- package/build/structured-content.d.ts.map +1 -1
- package/build/structured-content.js +77 -0
- package/build/structured-content.js.map +1 -1
- package/build/update-detect-changes.d.ts +42 -0
- package/build/update-detect-changes.d.ts.map +1 -0
- package/build/update-detect-changes.js +57 -0
- package/build/update-detect-changes.js.map +1 -0
- package/package.json +1 -1
package/build/index.js
CHANGED
|
@@ -12,13 +12,14 @@ import { enrichTemporalHistory } from "./temporal-interpretation.js";
|
|
|
12
12
|
import { getOrBuildProjection } from "./projections.js";
|
|
13
13
|
import { invalidateActiveProjectCache, getOrBuildVaultEmbeddings, getOrBuildVaultNoteList, getRecentSessionNoteAccesses, getRecentSessionAccessNote, getSessionCachedNote, getSessionCachedProjection, getSessionCachedProjectionTokens, recordSessionNoteAccess, setSessionCachedNote, setSessionCachedProjection, setSessionCachedProjectionTokens, } from "./cache.js";
|
|
14
14
|
import { performance } from "perf_hooks";
|
|
15
|
-
import { filterRelationships, mergeRelationshipsFromNotes, normalizeMergePlanSourceIds, resolveEffectiveConsolidationMode, } from "./consolidate.js";
|
|
15
|
+
import { aggregateMergeRisk, buildConsolidateNoteEvidence, buildGroupWarnings, filterRelationships, mergeRelationshipsFromNotes, normalizeMergePlanSourceIds, resolveEffectiveConsolidationMode, } from "./consolidate.js";
|
|
16
16
|
import { suggestAutoRelationships } from "./auto-relate.js";
|
|
17
17
|
import { computeRecallMetadataBoost, computeHybridScore, selectRecallResults, selectWorkflowResults, applyLexicalReranking, enrichRescueCandidateScores, resolveDiscoveredVaults, applyCanonicalExplanationPromotion, applyGraphSpreadingActivation, assignDenseRanks, detectTemporalQueryHint, computeTemporalRecencyBoost, shouldApplyTemporalFiltering, isWithinTemporalFilterWindow, } from "./recall.js";
|
|
18
18
|
import { shouldTriggerLexicalRescue, prepareTfIdfCorpusFromTokenizedDocuments, rankDocumentsByTfIdf, LEXICAL_RESCUE_CANDIDATE_LIMIT, LEXICAL_RESCUE_THRESHOLD, LEXICAL_RESCUE_RESULT_LIMIT, tokenize, } from "./lexical.js";
|
|
19
19
|
import { getRelationshipPreview } from "./relationships.js";
|
|
20
20
|
import { MarkdownLintError, cleanMarkdown } from "./markdown.js";
|
|
21
21
|
import { applySemanticPatches } from "./semantic-patch.js";
|
|
22
|
+
import { hasActualChanges, computeFieldsModified } from "./update-detect-changes.js";
|
|
22
23
|
import { MnemonicConfigStore, readVaultSchemaVersion } from "./config.js";
|
|
23
24
|
import { CONSOLIDATION_MODES, PROTECTED_BRANCH_BEHAVIORS, PROJECT_POLICY_SCOPES, WRITE_SCOPES, isProtectedBranch, resolveProtectedBranchBehavior, resolveProtectedBranchPatterns, resolveConsolidationMode, resolveWriteScope, } from "./project-memory-policy.js";
|
|
24
25
|
import { classifyTheme, classifyThemeWithGraduation, computeThemesWithGraduation, summarizePreview, titleCaseTheme, daysSinceUpdate, withinThemeScore, anchorScore, computeConnectionDiversity, workingStateScore, extractNextAction, } from "./project-introspection.js";
|
|
@@ -438,6 +439,34 @@ function formatRelationshipPreview(preview) {
|
|
|
438
439
|
: "";
|
|
439
440
|
return `**related (${preview.totalDirectRelations}):** ${shown}${more}`;
|
|
440
441
|
}
|
|
442
|
+
function toRecallFreshness(updatedAt) {
|
|
443
|
+
const updated = new Date(updatedAt);
|
|
444
|
+
if (Number.isNaN(updated.getTime())) {
|
|
445
|
+
return "older";
|
|
446
|
+
}
|
|
447
|
+
const ageDays = Math.max(0, (Date.now() - updated.getTime()) / (1000 * 60 * 60 * 24));
|
|
448
|
+
if (ageDays <= 1)
|
|
449
|
+
return "today";
|
|
450
|
+
if (ageDays <= 7)
|
|
451
|
+
return "thisWeek";
|
|
452
|
+
if (ageDays <= 31)
|
|
453
|
+
return "thisMonth";
|
|
454
|
+
return "older";
|
|
455
|
+
}
|
|
456
|
+
function toRecallRankBand(semanticRank) {
|
|
457
|
+
if (semanticRank !== undefined && semanticRank <= 3)
|
|
458
|
+
return "top3";
|
|
459
|
+
if (semanticRank !== undefined && semanticRank <= 10)
|
|
460
|
+
return "top10";
|
|
461
|
+
return "lower";
|
|
462
|
+
}
|
|
463
|
+
function formatRetrievalEvidenceHint(evidence, role) {
|
|
464
|
+
const rolePart = role ?? "untyped";
|
|
465
|
+
const supersedesPart = evidence.superseded
|
|
466
|
+
? ` | supersedes ${evidence.supersededCount ?? 1} note${(evidence.supersededCount ?? 1) === 1 ? "" : "s"}`
|
|
467
|
+
: "";
|
|
468
|
+
return `${evidence.rankBand} | channels: ${evidence.channels.join(", ")} | ${evidence.projectRelevant ? "project-relevant" : "cross-project"}\n${rolePart}, ${evidence.freshness}${supersedesPart}`;
|
|
469
|
+
}
|
|
441
470
|
// ── Git commit message helpers ────────────────────────────────────────────────
|
|
442
471
|
/**
|
|
443
472
|
* Extract a short human-readable summary from note content.
|
|
@@ -1899,7 +1928,8 @@ server.registerTool("recall", {
|
|
|
1899
1928
|
"Returns:\n" +
|
|
1900
1929
|
"- Ranked memory matches with scores, vault label, tags, lifecycle, and updated time\n" +
|
|
1901
1930
|
"- Bounded 1-hop relationship previews automatically attached to top results\n" +
|
|
1902
|
-
"- In temporal mode, optional compact history entries for top matches\n
|
|
1931
|
+
"- In temporal mode, optional compact history entries for top matches\n" +
|
|
1932
|
+
"- Optional retrieval evidence via `evidence: \"compact\"` for why a result ranked\n\n" +
|
|
1903
1933
|
"Read-only.\n\n" +
|
|
1904
1934
|
"Typical next step:\n" +
|
|
1905
1935
|
"- Use `get`, `update`, `relate`, or `consolidate` based on the results.",
|
|
@@ -1916,6 +1946,7 @@ server.registerTool("recall", {
|
|
|
1916
1946
|
minSimilarity: z.number().min(0).max(1).optional().default(DEFAULT_MIN_SIMILARITY),
|
|
1917
1947
|
mode: z.enum(["default", "temporal", "workflow"]).optional().default("default").describe("Use `temporal` for compact git-backed history, or `workflow` for RPIR-oriented chain reconstruction."),
|
|
1918
1948
|
verbose: z.boolean().optional().default(false).describe("Only meaningful with `mode: \"temporal\"`. Adds richer stats-based history context without returning raw diffs."),
|
|
1949
|
+
evidence: z.enum(["compact"]).optional().describe("Optional retrieval rationale. Omit for default output; use `compact` for bounded rank and lineage signals."),
|
|
1919
1950
|
tags: z.array(z.string()).optional().describe("Filter results to notes with all of these tags."),
|
|
1920
1951
|
scope: z
|
|
1921
1952
|
.enum(["project", "global", "all"])
|
|
@@ -1927,7 +1958,7 @@ server.registerTool("recall", {
|
|
|
1927
1958
|
lifecycle: z.enum(["temporary", "permanent"]).optional().describe("Filter results by lifecycle. Useful for recovering working-state with `lifecycle: temporary` after `project_memory_summary` orientation."),
|
|
1928
1959
|
}),
|
|
1929
1960
|
outputSchema: RecallResultSchema,
|
|
1930
|
-
}, async ({ query, cwd, limit, minSimilarity, mode, verbose, tags, scope, lifecycle }) => {
|
|
1961
|
+
}, async ({ query, cwd, limit, minSimilarity, mode, verbose, evidence, tags, scope, lifecycle }) => {
|
|
1931
1962
|
const t0Recall = performance.now();
|
|
1932
1963
|
await ensureBranchSynced(cwd);
|
|
1933
1964
|
const project = await resolveProject(cwd);
|
|
@@ -2048,12 +2079,14 @@ server.registerTool("recall", {
|
|
|
2048
2079
|
};
|
|
2049
2080
|
const strongestSemanticScore = scored.reduce((max, candidate) => max === undefined ? candidate.score : Math.max(max, candidate.score), undefined);
|
|
2050
2081
|
const reranked = applyLexicalReranking(scored, query, getProjectionText);
|
|
2082
|
+
const semanticCandidateIds = new Set(reranked.map((candidate) => candidate.id));
|
|
2051
2083
|
// Apply graph spreading activation: traverse related notes and boost their scores
|
|
2052
2084
|
const preSpreadIds = new Set(reranked.map((c) => c.id));
|
|
2053
2085
|
const getNoteRelationships = (id) => {
|
|
2054
2086
|
return noteRelationships.get(id);
|
|
2055
2087
|
};
|
|
2056
2088
|
const withGraphSpread = applyGraphSpreadingActivation(reranked, getNoteRelationships);
|
|
2089
|
+
const graphDiscoveredIds = new Set(withGraphSpread.filter((candidate) => !semanticCandidateIds.has(candidate.id)).map((candidate) => candidate.id));
|
|
2057
2090
|
// Resolve correct vault for graph-discovered candidates that inherited their
|
|
2058
2091
|
// entry point's vault instead of their own.
|
|
2059
2092
|
await resolveDiscoveredVaults(withGraphSpread, preSpreadIds, async (id) => {
|
|
@@ -2073,6 +2106,7 @@ server.registerTool("recall", {
|
|
|
2073
2106
|
candidate.semanticRank = rank;
|
|
2074
2107
|
});
|
|
2075
2108
|
let promoted = applyCanonicalExplanationPromotion(withGraphSpread);
|
|
2109
|
+
let rescueCandidateIds = new Set();
|
|
2076
2110
|
// Lexical rescue: when semantic results are weak, scan projections for additional candidates.
|
|
2077
2111
|
// Skip rescue when the caller set a strict minSimilarity above the default,
|
|
2078
2112
|
// because rescue candidates lack genuine semantic backing.
|
|
@@ -2080,6 +2114,7 @@ server.registerTool("recall", {
|
|
|
2080
2114
|
if (rescueAllowed && shouldTriggerLexicalRescue(strongestSemanticScore, scored.length)) {
|
|
2081
2115
|
const rescueCandidates = await collectLexicalRescueCandidates(vaults, query, temporalQueryHint, project ?? undefined, scope, tags, lifecycle, promoted);
|
|
2082
2116
|
promoted.push(...rescueCandidates);
|
|
2117
|
+
rescueCandidateIds = new Set(rescueCandidates.map((candidate) => candidate.id));
|
|
2083
2118
|
enrichRescueCandidateScores(promoted, query, getProjectionText);
|
|
2084
2119
|
promoted = applyCanonicalExplanationPromotion(promoted);
|
|
2085
2120
|
}
|
|
@@ -2098,7 +2133,7 @@ server.registerTool("recall", {
|
|
|
2098
2133
|
// Determine how many top results get relationship expansion
|
|
2099
2134
|
// Top 1 by default, top 3 if result count is small
|
|
2100
2135
|
const recallRelationshipLimit = top.length <= 3 ? 3 : 1;
|
|
2101
|
-
for (const [index, { id, score, vault, boosted }] of top.entries()) {
|
|
2136
|
+
for (const [index, { id, score, vault, boosted, semanticRank, lexicalRank, canonicalExplanationScore, metadata, isCurrentProject }] of top.entries()) {
|
|
2102
2137
|
const note = await readCachedNote(vault, id);
|
|
2103
2138
|
if (note) {
|
|
2104
2139
|
const centrality = note.relatedTo?.length ?? 0;
|
|
@@ -2135,8 +2170,30 @@ server.registerTool("recall", {
|
|
|
2135
2170
|
const provenanceLine = provenance || confidence
|
|
2136
2171
|
? `\n**confidence:** ${confidence ?? "medium"}${provenance?.recentlyChanged ? " | **recently changed**" : ""}`
|
|
2137
2172
|
: "";
|
|
2173
|
+
const supersededRelations = (note.relatedTo ?? []).filter((rel) => rel.type === "supersedes");
|
|
2174
|
+
const retrievalEvidence = evidence === "compact"
|
|
2175
|
+
? {
|
|
2176
|
+
channels: [
|
|
2177
|
+
semanticRank !== undefined ? "semantic" : undefined,
|
|
2178
|
+
lexicalRank !== undefined ? "lexical" : undefined,
|
|
2179
|
+
graphDiscoveredIds.has(id) ? "graph" : undefined,
|
|
2180
|
+
rescueCandidateIds.has(id) ? "rescue" : undefined,
|
|
2181
|
+
canonicalExplanationScore !== undefined && canonicalExplanationScore > 0 ? "canonical" : undefined,
|
|
2182
|
+
temporalQueryHint ? "temporal-boost" : undefined,
|
|
2183
|
+
].filter((value) => value !== undefined),
|
|
2184
|
+
rankBand: toRecallRankBand(semanticRank),
|
|
2185
|
+
projectRelevant: isCurrentProject,
|
|
2186
|
+
freshness: toRecallFreshness(note.updatedAt),
|
|
2187
|
+
superseded: supersededRelations.length > 0,
|
|
2188
|
+
supersededBy: supersededRelations.length > 0 ? supersededRelations[0]?.id : undefined,
|
|
2189
|
+
supersededCount: supersededRelations.length > 0 ? supersededRelations.length : undefined,
|
|
2190
|
+
}
|
|
2191
|
+
: undefined;
|
|
2192
|
+
const evidenceLine = retrievalEvidence
|
|
2193
|
+
? `\n${formatRetrievalEvidenceHint(retrievalEvidence, metadata?.role)}`
|
|
2194
|
+
: "";
|
|
2138
2195
|
// Suppress raw related IDs when enriched preview is shown to avoid duplication
|
|
2139
|
-
sections.push(`${formatNote(note, score, relationships === undefined)}${provenanceLine}${formattedHistory}${formattedRelationships}`);
|
|
2196
|
+
sections.push(`${formatNote(note, score, relationships === undefined)}${provenanceLine}${evidenceLine}${formattedHistory}${formattedRelationships}`);
|
|
2140
2197
|
structuredResults.push({
|
|
2141
2198
|
id,
|
|
2142
2199
|
title: note.title,
|
|
@@ -2153,6 +2210,7 @@ server.registerTool("recall", {
|
|
|
2153
2210
|
history,
|
|
2154
2211
|
historySummary,
|
|
2155
2212
|
relationships,
|
|
2213
|
+
retrievalEvidence,
|
|
2156
2214
|
});
|
|
2157
2215
|
if (project) {
|
|
2158
2216
|
setSessionCachedNote(project.id, vault.storage.vaultPath, note);
|
|
@@ -2316,64 +2374,136 @@ server.registerTool("update", {
|
|
|
2316
2374
|
}
|
|
2317
2375
|
}
|
|
2318
2376
|
const cleanedContent = content === undefined ? undefined : await cleanMarkdown(content);
|
|
2319
|
-
const
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
if (updated.project) {
|
|
2330
|
-
const accessCandidates = getRecentSessionNoteAccesses(updated.project)
|
|
2377
|
+
const resolvedTitle = title ?? note.title;
|
|
2378
|
+
const resolvedContent = patchedContent ?? cleanedContent ?? note.content;
|
|
2379
|
+
const resolvedTags = tags ?? note.tags;
|
|
2380
|
+
const resolvedLifecycle = lifecycle ?? note.lifecycle;
|
|
2381
|
+
const resolvedRole = role !== undefined ? role : (note.role ? note.role : undefined);
|
|
2382
|
+
const resolvedAlwaysLoad = alwaysLoad !== undefined ? alwaysLoad : note.alwaysLoad;
|
|
2383
|
+
let relatedToChanged = false;
|
|
2384
|
+
let resolvedRelatedTo = note.relatedTo;
|
|
2385
|
+
if (note.project) {
|
|
2386
|
+
const accessCandidates = getRecentSessionNoteAccesses(note.project)
|
|
2331
2387
|
.map((entry) => {
|
|
2332
|
-
const cachedNote = getSessionCachedNote(
|
|
2333
|
-
?? getRecentSessionAccessNote(
|
|
2388
|
+
const cachedNote = getSessionCachedNote(note.project, entry.vaultPath, entry.noteId)
|
|
2389
|
+
?? getRecentSessionAccessNote(note.project, entry.vaultPath, entry.noteId);
|
|
2334
2390
|
return cachedNote
|
|
2335
2391
|
? { note: cachedNote, accessedAt: entry.accessedAt, accessKind: entry.accessKind, score: entry.score }
|
|
2336
2392
|
: null;
|
|
2337
2393
|
})
|
|
2338
2394
|
.filter((entry) => entry !== null);
|
|
2339
|
-
const autoRelationships = suggestAutoRelationships(
|
|
2395
|
+
const autoRelationships = suggestAutoRelationships({
|
|
2396
|
+
...note,
|
|
2397
|
+
title: resolvedTitle,
|
|
2398
|
+
content: resolvedContent,
|
|
2399
|
+
tags: resolvedTags,
|
|
2400
|
+
lifecycle: resolvedLifecycle,
|
|
2401
|
+
alwaysLoad: resolvedAlwaysLoad,
|
|
2402
|
+
}, accessCandidates);
|
|
2340
2403
|
if (autoRelationships.length > 0) {
|
|
2341
|
-
const existing = [...(
|
|
2404
|
+
const existing = [...(note.relatedTo ?? [])];
|
|
2342
2405
|
for (const relationship of autoRelationships) {
|
|
2343
2406
|
if (!existing.some((rel) => rel.id === relationship.id && rel.type === relationship.type)) {
|
|
2344
2407
|
existing.push(relationship);
|
|
2345
2408
|
}
|
|
2346
2409
|
}
|
|
2347
|
-
|
|
2348
|
-
|
|
2410
|
+
resolvedRelatedTo = existing;
|
|
2411
|
+
relatedToChanged = true;
|
|
2412
|
+
}
|
|
2413
|
+
}
|
|
2414
|
+
const changes = computeFieldsModified({
|
|
2415
|
+
patchedContent,
|
|
2416
|
+
originalContent: note.content,
|
|
2417
|
+
contentExplicitlyProvided: content !== undefined,
|
|
2418
|
+
semanticPatchProvided: semanticPatch !== undefined && semanticPatch.length > 0,
|
|
2419
|
+
newTitle: resolvedTitle,
|
|
2420
|
+
originalTitle: note.title,
|
|
2421
|
+
titleExplicitlyProvided: title !== undefined,
|
|
2422
|
+
newLifecycle: resolvedLifecycle,
|
|
2423
|
+
originalLifecycle: note.lifecycle,
|
|
2424
|
+
lifecycleExplicitlyProvided: lifecycle !== undefined,
|
|
2425
|
+
newRole: resolvedRole,
|
|
2426
|
+
originalRole: note.role,
|
|
2427
|
+
roleExplicitlySet: role !== undefined,
|
|
2428
|
+
newTags: resolvedTags,
|
|
2429
|
+
originalTags: note.tags,
|
|
2430
|
+
tagsExplicitlyProvided: tags !== undefined,
|
|
2431
|
+
newAlwaysLoad: resolvedAlwaysLoad,
|
|
2432
|
+
originalAlwaysLoad: note.alwaysLoad,
|
|
2433
|
+
alwaysLoadExplicitlyProvided: alwaysLoad !== undefined,
|
|
2434
|
+
relatedToChanged,
|
|
2435
|
+
});
|
|
2436
|
+
const hasChanges = hasActualChanges({
|
|
2437
|
+
content: cleanedContent,
|
|
2438
|
+
originalContent: note.content,
|
|
2439
|
+
title,
|
|
2440
|
+
originalTitle: note.title,
|
|
2441
|
+
tags,
|
|
2442
|
+
originalTags: note.tags,
|
|
2443
|
+
lifecycle,
|
|
2444
|
+
originalLifecycle: note.lifecycle,
|
|
2445
|
+
role,
|
|
2446
|
+
originalRole: note.role,
|
|
2447
|
+
roleExplicitlySet: role !== undefined,
|
|
2448
|
+
alwaysLoad,
|
|
2449
|
+
originalAlwaysLoad: note.alwaysLoad,
|
|
2450
|
+
semanticPatchApplied: semanticPatch !== undefined && semanticPatch.length > 0,
|
|
2451
|
+
relatedToChanged,
|
|
2452
|
+
});
|
|
2453
|
+
if (!hasChanges) {
|
|
2454
|
+
const noOpPersistence = {
|
|
2455
|
+
notePath: vault.storage.notePath(id),
|
|
2456
|
+
embeddingPath: vault.storage.embeddingPath(id),
|
|
2457
|
+
embedding: { status: "skipped", model: embedModel, reason: "no-changes" },
|
|
2458
|
+
git: {
|
|
2459
|
+
commit: "skipped",
|
|
2460
|
+
push: "skipped",
|
|
2461
|
+
commitReason: "no-changes",
|
|
2462
|
+
pushReason: "no-changes",
|
|
2463
|
+
},
|
|
2464
|
+
durability: "local-only",
|
|
2465
|
+
};
|
|
2466
|
+
return {
|
|
2467
|
+
content: [{ type: "text", text: `No changes to memory '${id}'` }],
|
|
2468
|
+
structuredContent: {
|
|
2469
|
+
action: "updated",
|
|
2470
|
+
id,
|
|
2471
|
+
title: note.title,
|
|
2472
|
+
fieldsModified: [],
|
|
2473
|
+
timestamp: note.updatedAt,
|
|
2474
|
+
project: noteProjectRef(note),
|
|
2475
|
+
lifecycle: note.lifecycle,
|
|
2476
|
+
role: note.role,
|
|
2477
|
+
persistence: noOpPersistence,
|
|
2478
|
+
},
|
|
2479
|
+
};
|
|
2349
2480
|
}
|
|
2481
|
+
const updated = {
|
|
2482
|
+
...note,
|
|
2483
|
+
title: resolvedTitle,
|
|
2484
|
+
content: resolvedContent,
|
|
2485
|
+
tags: resolvedTags,
|
|
2486
|
+
lifecycle: resolvedLifecycle,
|
|
2487
|
+
...(role !== undefined ? { role: resolvedRole } : (note.role ? { role: note.role } : {})),
|
|
2488
|
+
alwaysLoad: resolvedAlwaysLoad,
|
|
2489
|
+
updatedAt: now,
|
|
2490
|
+
relatedTo: resolvedRelatedTo,
|
|
2491
|
+
};
|
|
2350
2492
|
await vault.storage.writeNote(updated);
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2493
|
+
const shouldReembed = patchedContent !== undefined || cleanedContent !== undefined;
|
|
2494
|
+
let embeddingStatus = { status: "skipped", reason: shouldReembed ? undefined : "metadata-only" };
|
|
2495
|
+
if (shouldReembed) {
|
|
2496
|
+
try {
|
|
2497
|
+
const text = await embedTextForNote(vault.storage, updated);
|
|
2498
|
+
const vector = await embed(text);
|
|
2499
|
+
await vault.storage.writeEmbedding({ id, model: embedModel, embedding: vector, updatedAt: now });
|
|
2500
|
+
embeddingStatus = { status: "written" };
|
|
2501
|
+
}
|
|
2502
|
+
catch (err) {
|
|
2503
|
+
embeddingStatus = { status: "skipped", reason: err instanceof Error ? err.message : String(err) };
|
|
2504
|
+
console.error(`[embedding] Re-embed failed for '${id}': ${err}`);
|
|
2505
|
+
}
|
|
2356
2506
|
}
|
|
2357
|
-
catch (err) {
|
|
2358
|
-
embeddingStatus = { status: "skipped", reason: err instanceof Error ? err.message : String(err) };
|
|
2359
|
-
console.error(`[embedding] Re-embed failed for '${id}': ${err}`);
|
|
2360
|
-
}
|
|
2361
|
-
// Build change summary (LLM-provided or auto-generated)
|
|
2362
|
-
const changes = [];
|
|
2363
|
-
if (title !== undefined && title !== note.title)
|
|
2364
|
-
changes.push("title");
|
|
2365
|
-
if (content !== undefined)
|
|
2366
|
-
changes.push("content");
|
|
2367
|
-
if (semanticPatch !== undefined)
|
|
2368
|
-
changes.push("semanticPatch");
|
|
2369
|
-
if (tags !== undefined)
|
|
2370
|
-
changes.push("tags");
|
|
2371
|
-
if (lifecycle !== undefined && lifecycle !== note.lifecycle)
|
|
2372
|
-
changes.push("lifecycle");
|
|
2373
|
-
if (role !== undefined && role !== note.role)
|
|
2374
|
-
changes.push("role");
|
|
2375
|
-
if (alwaysLoad !== undefined && alwaysLoad !== note.alwaysLoad)
|
|
2376
|
-
changes.push("alwaysLoad");
|
|
2377
2507
|
const changeDesc = changes.length > 0 ? `Updated ${changes.join(", ")}` : "No changes";
|
|
2378
2508
|
const commitSummary = summary ?? changeDesc;
|
|
2379
2509
|
const commitBody = formatCommitBody({
|
|
@@ -4260,7 +4390,8 @@ server.registerTool("consolidate", {
|
|
|
4260
4390
|
"- The canonical memory, source ids, resulting relationships, and persistence status\n\n" +
|
|
4261
4391
|
"Side effects: creates or updates the canonical note, modifies or removes source notes according to mode, git commits, and may push.\n\n" +
|
|
4262
4392
|
"Typical next step:\n" +
|
|
4263
|
-
"- Use `get` to inspect the canonical note and `recall` to confirm duplication is reduced
|
|
4393
|
+
"- Use `get` to inspect the canonical note and `recall` to confirm duplication is reduced.\n" +
|
|
4394
|
+
"- Evidence defaults on for consolidate analysis strategies and execute-merge (lifecycle, risk, warnings).",
|
|
4264
4395
|
annotations: {
|
|
4265
4396
|
readOnlyHint: false,
|
|
4266
4397
|
destructiveHint: true,
|
|
@@ -4280,7 +4411,8 @@ server.registerTool("consolidate", {
|
|
|
4280
4411
|
])
|
|
4281
4412
|
.describe("What to do: 'dry-run' = full analysis without changes, 'detect-duplicates' = find similar pairs, " +
|
|
4282
4413
|
"'find-clusters' = group by theme and relationships, 'suggest-merges' = actionable merge recommendations, " +
|
|
4283
|
-
"'execute-merge' = perform a merge (requires mergePlan), 'prune-superseded' = delete notes marked as superseded"
|
|
4414
|
+
"'execute-merge' = perform a merge (requires mergePlan), 'prune-superseded' = delete notes marked as superseded. " +
|
|
4415
|
+
"Use `evidence: true` on analysis strategies for trust/risk signals."),
|
|
4284
4416
|
mode: z
|
|
4285
4417
|
.enum(CONSOLIDATION_MODES)
|
|
4286
4418
|
.optional()
|
|
@@ -4292,6 +4424,10 @@ server.registerTool("consolidate", {
|
|
|
4292
4424
|
.optional()
|
|
4293
4425
|
.default(0.85)
|
|
4294
4426
|
.describe("Cosine similarity threshold for duplicate detection (0.85 default)"),
|
|
4427
|
+
evidence: z
|
|
4428
|
+
.boolean()
|
|
4429
|
+
.optional()
|
|
4430
|
+
.describe("Confidence signals for analysis strategies and execute-merge (lifecycle, risk, warnings). Default true for safety."),
|
|
4295
4431
|
mergePlan: z
|
|
4296
4432
|
.object({
|
|
4297
4433
|
sourceIds: z.array(z.string()).min(2).describe("Ids of notes to merge into one consolidated note"),
|
|
@@ -4310,7 +4446,7 @@ server.registerTool("consolidate", {
|
|
|
4310
4446
|
"When true, consolidate can commit on a protected branch without changing project policy."),
|
|
4311
4447
|
}),
|
|
4312
4448
|
outputSchema: ConsolidateResultSchema,
|
|
4313
|
-
}, async ({ cwd, strategy, mode, threshold, mergePlan, allowProtectedBranch = false }) => {
|
|
4449
|
+
}, async ({ cwd, strategy, mode, threshold, evidence = true, mergePlan, allowProtectedBranch = false }) => {
|
|
4314
4450
|
await ensureBranchSynced(cwd);
|
|
4315
4451
|
const project = await resolveProject(cwd);
|
|
4316
4452
|
if (!project && cwd) {
|
|
@@ -4330,16 +4466,16 @@ server.registerTool("consolidate", {
|
|
|
4330
4466
|
const defaultConsolidationMode = resolveConsolidationMode(policy);
|
|
4331
4467
|
switch (strategy) {
|
|
4332
4468
|
case "detect-duplicates":
|
|
4333
|
-
return detectDuplicates(projectNotes, threshold, project);
|
|
4469
|
+
return detectDuplicates(projectNotes, threshold, project, evidence);
|
|
4334
4470
|
case "find-clusters":
|
|
4335
4471
|
return findClusters(projectNotes, project);
|
|
4336
4472
|
case "suggest-merges":
|
|
4337
|
-
return suggestMerges(projectNotes, threshold, defaultConsolidationMode, project, mode);
|
|
4473
|
+
return suggestMerges(projectNotes, threshold, defaultConsolidationMode, project, mode, evidence);
|
|
4338
4474
|
case "execute-merge": {
|
|
4339
4475
|
if (!mergePlan) {
|
|
4340
4476
|
return { content: [{ type: "text", text: "execute-merge strategy requires a mergePlan with sourceIds and targetTitle." }], isError: true };
|
|
4341
4477
|
}
|
|
4342
|
-
const mergeResult = await executeMerge(entries, mergePlan, defaultConsolidationMode, project, cwd, mode, policy, allowProtectedBranch);
|
|
4478
|
+
const mergeResult = await executeMerge(entries, mergePlan, defaultConsolidationMode, project, cwd, mode, policy, allowProtectedBranch, evidence);
|
|
4343
4479
|
invalidateActiveProjectCache();
|
|
4344
4480
|
return mergeResult;
|
|
4345
4481
|
}
|
|
@@ -4349,20 +4485,22 @@ server.registerTool("consolidate", {
|
|
|
4349
4485
|
return pruneResult;
|
|
4350
4486
|
}
|
|
4351
4487
|
case "dry-run":
|
|
4352
|
-
return dryRunAll(projectNotes, threshold, defaultConsolidationMode, project, mode);
|
|
4488
|
+
return dryRunAll(projectNotes, threshold, defaultConsolidationMode, project, mode, evidence);
|
|
4353
4489
|
default:
|
|
4354
4490
|
return { content: [{ type: "text", text: `Unknown strategy: ${strategy}` }], isError: true };
|
|
4355
4491
|
}
|
|
4356
4492
|
});
|
|
4357
4493
|
// Consolidate helper functions
|
|
4358
|
-
async function detectDuplicates(entries, threshold, project) {
|
|
4494
|
+
async function detectDuplicates(entries, threshold, project, evidence) {
|
|
4359
4495
|
const lines = [];
|
|
4360
4496
|
lines.push(`Duplicate detection for ${project?.name ?? "global"} (similarity > ${threshold}):`);
|
|
4361
4497
|
lines.push("");
|
|
4362
4498
|
const checked = new Set();
|
|
4363
4499
|
let foundCount = 0;
|
|
4364
4500
|
const duplicates = [];
|
|
4501
|
+
const duplicatePairs = [];
|
|
4365
4502
|
const embeddings = await loadEmbeddingsByNoteId(entries);
|
|
4503
|
+
const allNotes = entries.map((entry) => entry.note);
|
|
4366
4504
|
for (let i = 0; i < entries.length; i++) {
|
|
4367
4505
|
const entryA = entries[i];
|
|
4368
4506
|
if (checked.has(entryA.note.id))
|
|
@@ -4379,10 +4517,22 @@ async function detectDuplicates(entries, threshold, project) {
|
|
|
4379
4517
|
continue;
|
|
4380
4518
|
const similarity = cosineSimilarity(embeddingA, embeddingB);
|
|
4381
4519
|
if (similarity >= threshold) {
|
|
4520
|
+
const noteAEvidence = buildConsolidateNoteEvidence(entryA.note, allNotes, entryA.note);
|
|
4521
|
+
const noteBEvidence = buildConsolidateNoteEvidence(entryB.note, allNotes, entryA.note);
|
|
4522
|
+
const groupWarnings = buildGroupWarnings([entryA.note, entryB.note], entryA.note);
|
|
4523
|
+
const pairRisk = aggregateMergeRisk([noteAEvidence.mergeRisk, noteBEvidence.mergeRisk]);
|
|
4382
4524
|
foundCount++;
|
|
4383
4525
|
lines.push(`${foundCount}. ${entryA.note.title} (${entryA.note.id})`);
|
|
4384
4526
|
lines.push(` └── ${entryB.note.title} (${entryB.note.id})`);
|
|
4385
4527
|
lines.push(` Similarity: ${similarity.toFixed(3)}`);
|
|
4528
|
+
if (evidence) {
|
|
4529
|
+
lines.push(` A: ${noteAEvidence.lifecycle}, ${noteAEvidence.role ?? "untyped"} | ${Math.round(noteAEvidence.ageDays)}d old | rel: ${noteAEvidence.relatedCount} | supersedes: ${noteAEvidence.supersededCount ?? 0} | risk: ${noteAEvidence.mergeRisk}`);
|
|
4530
|
+
lines.push(` B: ${noteBEvidence.lifecycle}, ${noteBEvidence.role ?? "untyped"} | ${Math.round(noteBEvidence.ageDays)}d old | rel: ${noteBEvidence.relatedCount} | supersedes: ${noteBEvidence.supersededCount ?? 0} | risk: ${noteBEvidence.mergeRisk}`);
|
|
4531
|
+
if (groupWarnings.length > 0) {
|
|
4532
|
+
lines.push(` Warnings: ${groupWarnings.join("; ")}`);
|
|
4533
|
+
}
|
|
4534
|
+
lines.push(` Merge risk: ${pairRisk}`);
|
|
4535
|
+
}
|
|
4386
4536
|
lines.push("");
|
|
4387
4537
|
checked.add(entryA.note.id);
|
|
4388
4538
|
checked.add(entryB.note.id);
|
|
@@ -4391,6 +4541,15 @@ async function detectDuplicates(entries, threshold, project) {
|
|
|
4391
4541
|
noteB: { id: entryB.note.id, title: entryB.note.title },
|
|
4392
4542
|
similarity,
|
|
4393
4543
|
});
|
|
4544
|
+
if (evidence) {
|
|
4545
|
+
duplicatePairs.push({
|
|
4546
|
+
similarity,
|
|
4547
|
+
noteA: noteAEvidence,
|
|
4548
|
+
noteB: noteBEvidence,
|
|
4549
|
+
warnings: groupWarnings.length > 0 ? groupWarnings : undefined,
|
|
4550
|
+
mergeRisk: pairRisk,
|
|
4551
|
+
});
|
|
4552
|
+
}
|
|
4394
4553
|
}
|
|
4395
4554
|
}
|
|
4396
4555
|
}
|
|
@@ -4407,6 +4566,7 @@ async function detectDuplicates(entries, threshold, project) {
|
|
|
4407
4566
|
project: toProjectRef(project),
|
|
4408
4567
|
notesProcessed: entries.length,
|
|
4409
4568
|
notesModified: 0,
|
|
4569
|
+
duplicatePairs: evidence ? duplicatePairs : undefined,
|
|
4410
4570
|
};
|
|
4411
4571
|
return { content: [{ type: "text", text: lines.join("\n") }], structuredContent };
|
|
4412
4572
|
}
|
|
@@ -4499,7 +4659,7 @@ function findClusters(entries, project) {
|
|
|
4499
4659
|
};
|
|
4500
4660
|
return { content: [{ type: "text", text: lines.join("\n") }], structuredContent };
|
|
4501
4661
|
}
|
|
4502
|
-
async function suggestMerges(entries, threshold, defaultConsolidationMode, project, explicitMode) {
|
|
4662
|
+
async function suggestMerges(entries, threshold, defaultConsolidationMode, project, explicitMode, evidence = false) {
|
|
4503
4663
|
const lines = [];
|
|
4504
4664
|
const modeLabel = explicitMode ?? `${defaultConsolidationMode} (project/default; all-temporary merges auto-delete)`;
|
|
4505
4665
|
lines.push(`Merge suggestions for ${project?.name ?? "global"} (mode: ${modeLabel}):`);
|
|
@@ -4507,7 +4667,9 @@ async function suggestMerges(entries, threshold, defaultConsolidationMode, proje
|
|
|
4507
4667
|
const checked = new Set();
|
|
4508
4668
|
let suggestionCount = 0;
|
|
4509
4669
|
const suggestions = [];
|
|
4670
|
+
const mergeSuggestions = [];
|
|
4510
4671
|
const embeddings = await loadEmbeddingsByNoteId(entries);
|
|
4672
|
+
const allNotes = entries.map((entry) => entry.note);
|
|
4511
4673
|
for (let i = 0; i < entries.length; i++) {
|
|
4512
4674
|
const entryA = entries[i];
|
|
4513
4675
|
if (checked.has(entryA.note.id))
|
|
@@ -4552,7 +4714,19 @@ async function suggestMerges(entries, threshold, defaultConsolidationMode, proje
|
|
|
4552
4714
|
}
|
|
4553
4715
|
}
|
|
4554
4716
|
})();
|
|
4717
|
+
const noteEvidence = sources.map((source) => buildConsolidateNoteEvidence(source.note, allNotes, entryA.note));
|
|
4718
|
+
const mergeWarnings = buildGroupWarnings(sources.map((source) => source.note), entryA.note);
|
|
4719
|
+
const mergeRisk = aggregateMergeRisk(noteEvidence.map((e) => e.mergeRisk));
|
|
4555
4720
|
lines.push(` Mode: ${effectiveMode} (${modeDescription})`);
|
|
4721
|
+
if (evidence) {
|
|
4722
|
+
for (const note of noteEvidence) {
|
|
4723
|
+
lines.push(` Evidence: ${note.title} | ${note.lifecycle}, ${note.role ?? "untyped"} | ${Math.round(note.ageDays)}d | rel:${note.relatedCount} | risk:${note.mergeRisk}`);
|
|
4724
|
+
}
|
|
4725
|
+
if (mergeWarnings.length > 0) {
|
|
4726
|
+
lines.push(` Warnings: ${mergeWarnings.join("; ")}`);
|
|
4727
|
+
}
|
|
4728
|
+
lines.push(` Merge risk: ${mergeRisk}`);
|
|
4729
|
+
}
|
|
4556
4730
|
lines.push(" To execute:");
|
|
4557
4731
|
lines.push(` consolidate({ strategy: "execute-merge", mergePlan: {`);
|
|
4558
4732
|
lines.push(` sourceIds: [${sources.map((s) => `"${s.note.id}"`).join(", ")}],`);
|
|
@@ -4564,6 +4738,16 @@ async function suggestMerges(entries, threshold, defaultConsolidationMode, proje
|
|
|
4564
4738
|
sourceIds: sources.map((s) => s.note.id),
|
|
4565
4739
|
similarities: similar.map((s) => ({ id: s.entry.note.id, similarity: s.similarity })),
|
|
4566
4740
|
});
|
|
4741
|
+
if (evidence) {
|
|
4742
|
+
mergeSuggestions.push({
|
|
4743
|
+
targetTitle: `${entryA.note.title} (consolidated)`,
|
|
4744
|
+
sourceIds: sources.map((source) => source.note.id),
|
|
4745
|
+
mode: effectiveMode,
|
|
4746
|
+
notes: noteEvidence,
|
|
4747
|
+
warnings: mergeWarnings.length > 0 ? mergeWarnings : undefined,
|
|
4748
|
+
mergeRisk,
|
|
4749
|
+
});
|
|
4750
|
+
}
|
|
4567
4751
|
checked.add(entryA.note.id);
|
|
4568
4752
|
for (const s of similar)
|
|
4569
4753
|
checked.add(s.entry.note.id);
|
|
@@ -4581,6 +4765,7 @@ async function suggestMerges(entries, threshold, defaultConsolidationMode, proje
|
|
|
4581
4765
|
project: toProjectRef(project),
|
|
4582
4766
|
notesProcessed: entries.length,
|
|
4583
4767
|
notesModified: 0,
|
|
4768
|
+
mergeSuggestions: evidence ? mergeSuggestions : undefined,
|
|
4584
4769
|
};
|
|
4585
4770
|
return { content: [{ type: "text", text: lines.join("\n") }], structuredContent };
|
|
4586
4771
|
}
|
|
@@ -4594,7 +4779,7 @@ async function loadEmbeddingsByNoteId(entries) {
|
|
|
4594
4779
|
}));
|
|
4595
4780
|
return embeddings;
|
|
4596
4781
|
}
|
|
4597
|
-
async function executeMerge(entries, mergePlan, defaultConsolidationMode, project, cwd, explicitMode, policy, allowProtectedBranch = false) {
|
|
4782
|
+
async function executeMerge(entries, mergePlan, defaultConsolidationMode, project, cwd, explicitMode, policy, allowProtectedBranch = false, evidence = true) {
|
|
4598
4783
|
const sourceIds = normalizeMergePlanSourceIds(mergePlan.sourceIds);
|
|
4599
4784
|
const targetTitle = mergePlan.targetTitle.trim();
|
|
4600
4785
|
const { content: customContent, description, summary, tags } = mergePlan;
|
|
@@ -4872,12 +5057,33 @@ async function executeMerge(entries, mergePlan, defaultConsolidationMode, projec
|
|
|
4872
5057
|
throw new Error(`Unknown consolidation mode: ${_exhaustive}`);
|
|
4873
5058
|
}
|
|
4874
5059
|
}
|
|
5060
|
+
let executeMergeEvidence;
|
|
5061
|
+
if (evidence) {
|
|
5062
|
+
const allNotes = entries.map((entry) => entry.note);
|
|
5063
|
+
const noteEvidence = sourceEntries.map((entry) => buildConsolidateNoteEvidence(entry.note, allNotes, sourceEntries[0]?.note));
|
|
5064
|
+
const mergeWarnings = buildGroupWarnings(sourceEntries.map((entry) => entry.note), sourceEntries[0]?.note);
|
|
5065
|
+
const mergeRisk = aggregateMergeRisk(noteEvidence.map((e) => e.mergeRisk));
|
|
5066
|
+
lines.push(" Evidence:");
|
|
5067
|
+
for (const note of noteEvidence) {
|
|
5068
|
+
lines.push(` ${note.title} | ${note.lifecycle}, ${note.role ?? "untyped"} | ${Math.round(note.ageDays)}d | rel:${note.relatedCount} | risk:${note.mergeRisk}`);
|
|
5069
|
+
}
|
|
5070
|
+
if (mergeWarnings.length > 0) {
|
|
5071
|
+
lines.push(` Warnings: ${mergeWarnings.join("; ")}`);
|
|
5072
|
+
}
|
|
5073
|
+
lines.push(` Merge risk: ${mergeRisk}`);
|
|
5074
|
+
executeMergeEvidence = {
|
|
5075
|
+
notes: noteEvidence,
|
|
5076
|
+
warnings: mergeWarnings.length > 0 ? mergeWarnings : undefined,
|
|
5077
|
+
mergeRisk,
|
|
5078
|
+
};
|
|
5079
|
+
}
|
|
4875
5080
|
const structuredContent = {
|
|
4876
5081
|
action: "consolidated",
|
|
4877
5082
|
strategy: "execute-merge",
|
|
4878
5083
|
project: toProjectRef(project),
|
|
4879
5084
|
notesProcessed: entries.length,
|
|
4880
5085
|
notesModified: vaultChanges.size,
|
|
5086
|
+
executeMergeEvidence,
|
|
4881
5087
|
persistence,
|
|
4882
5088
|
retry,
|
|
4883
5089
|
};
|
|
@@ -5038,14 +5244,14 @@ async function pruneSuperseded(entries, consolidationMode, project, cwd, policy,
|
|
|
5038
5244
|
};
|
|
5039
5245
|
return { content: [{ type: "text", text: lines.join("\n") }], structuredContent };
|
|
5040
5246
|
}
|
|
5041
|
-
async function dryRunAll(entries, threshold, defaultConsolidationMode, project, explicitMode) {
|
|
5247
|
+
async function dryRunAll(entries, threshold, defaultConsolidationMode, project, explicitMode, evidence = false) {
|
|
5042
5248
|
const lines = [];
|
|
5043
5249
|
lines.push(`Consolidation analysis for ${project?.name ?? "global"}:`);
|
|
5044
5250
|
const modeLabel = explicitMode ?? `${defaultConsolidationMode} (project/default; all-temporary merges auto-delete)`;
|
|
5045
5251
|
lines.push(`Mode: ${modeLabel} | Threshold: ${threshold}`);
|
|
5046
5252
|
lines.push("");
|
|
5047
5253
|
// Run all analysis strategies
|
|
5048
|
-
const dupes = await detectDuplicates(entries, threshold, project);
|
|
5254
|
+
const dupes = await detectDuplicates(entries, threshold, project, evidence);
|
|
5049
5255
|
lines.push("=== DUPLICATE DETECTION ===");
|
|
5050
5256
|
lines.push(dupes.content[0]?.text ?? "No output");
|
|
5051
5257
|
lines.push("");
|
|
@@ -5053,7 +5259,7 @@ async function dryRunAll(entries, threshold, defaultConsolidationMode, project,
|
|
|
5053
5259
|
lines.push("=== CLUSTER ANALYSIS ===");
|
|
5054
5260
|
lines.push(clusters.content[0]?.text ?? "No output");
|
|
5055
5261
|
lines.push("");
|
|
5056
|
-
const merges = await suggestMerges(entries, threshold, defaultConsolidationMode, project, explicitMode);
|
|
5262
|
+
const merges = await suggestMerges(entries, threshold, defaultConsolidationMode, project, explicitMode, evidence);
|
|
5057
5263
|
lines.push("=== MERGE SUGGESTIONS ===");
|
|
5058
5264
|
lines.push(merges.content[0]?.text ?? "No output");
|
|
5059
5265
|
const structuredContent = {
|
|
@@ -5062,6 +5268,10 @@ async function dryRunAll(entries, threshold, defaultConsolidationMode, project,
|
|
|
5062
5268
|
project: toProjectRef(project),
|
|
5063
5269
|
notesProcessed: entries.length,
|
|
5064
5270
|
notesModified: 0,
|
|
5271
|
+
duplicatePairs: dupes.structuredContent.duplicatePairs,
|
|
5272
|
+
mergeSuggestions: merges.structuredContent.mergeSuggestions,
|
|
5273
|
+
themeGroups: clusters.structuredContent.themeGroups,
|
|
5274
|
+
relationshipClusters: clusters.structuredContent.relationshipClusters,
|
|
5065
5275
|
};
|
|
5066
5276
|
return { content: [{ type: "text", text: lines.join("\n") }], structuredContent };
|
|
5067
5277
|
}
|
|
@@ -5104,6 +5314,7 @@ server.registerPrompt("mnemonic-workflow-hint", {
|
|
|
5104
5314
|
"- When unsure, prefer `recall` over `remember`.\n" +
|
|
5105
5315
|
"- For repo-related tasks, pass `cwd` so mnemonic can route project memories correctly.\n\n" +
|
|
5106
5316
|
"Workflow: `recall`/`list` -> `get` -> `update` or `remember` -> `relate`/`consolidate`/`move_memory`. Use `discover_tags` only when tag choice is ambiguous.\n\n" +
|
|
5317
|
+
"When a merge/prune decision is uncertain, use optional evidence enrichment: `recall` with `evidence: \"compact\"` and `consolidate` analysis strategies with `evidence: true`. Evidence improves confidence but is not required.\n\n" +
|
|
5107
5318
|
"Roles are optional prioritization hints, not schema. Lifecycle still governs durability. When `lifecycle` is omitted, `remember` applies soft defaults based on role: `research`, `plan`, and `review` default to `temporary`; `decision`, `summary`, and `reference` default to `permanent`. Explicit `lifecycle` always overrides the role-based default. Inferred roles are internal hints only. Prioritization is language-independent by default.\n\n" +
|
|
5108
5319
|
"### Working-state continuity\n\n" +
|
|
5109
5320
|
"Preserve in-progress work as temporary notes when continuation value is high. Recovery happens after project orientation.\n\n" +
|
|
@@ -5144,6 +5355,8 @@ server.registerPrompt("mnemonic-workflow-hint", {
|
|
|
5144
5355
|
"- Existing bug note found by `recall` -> inspect with `get` -> refine with `update`.\n" +
|
|
5145
5356
|
"- No matching note found by `recall` -> optional `discover_tags` with note context -> create with `remember`.\n" +
|
|
5146
5357
|
"- Two notes overlap heavily -> inspect -> clean up with `consolidate`.\n" +
|
|
5358
|
+
"- Unsure why a recall hit ranked high -> rerun `recall` with `evidence: \"compact\"`.\n" +
|
|
5359
|
+
"- Unsure whether to merge/prune -> run `consolidate` analysis with `evidence: true` before `execute-merge` or `prune-superseded`.\n" +
|
|
5147
5360
|
"- Resume work: `project_memory_summary` -> `recall` (lifecycle: temporary) -> continue from temporary notes.\n\n" +
|
|
5148
5361
|
"### semanticPatch format\n\n" +
|
|
5149
5362
|
"When using `update` with `semanticPatch`:\n" +
|