@velvetmonkey/flywheel-memory 2.0.148 → 2.0.150
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +179 -576
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -642,9 +642,6 @@ function loadNoteEmbeddingsForPaths(paths) {
|
|
|
642
642
|
}
|
|
643
643
|
return result;
|
|
644
644
|
}
|
|
645
|
-
function getEntityEmbedding(entityName) {
|
|
646
|
-
return getEmbMap().get(entityName) ?? null;
|
|
647
|
-
}
|
|
648
645
|
function getEntityEmbeddingsCount() {
|
|
649
646
|
const db4 = getDb();
|
|
650
647
|
if (!db4) return 0;
|
|
@@ -3396,7 +3393,8 @@ async function rebuildIndex(vaultPath2) {
|
|
|
3396
3393
|
console.error(`[Flywheel] Scanning vault for entities...`);
|
|
3397
3394
|
const startTime = Date.now();
|
|
3398
3395
|
entityIndex = await scanVaultEntities(vaultPath2, {
|
|
3399
|
-
excludeFolders: DEFAULT_EXCLUDE_FOLDERS
|
|
3396
|
+
excludeFolders: DEFAULT_EXCLUDE_FOLDERS,
|
|
3397
|
+
customCategories: getConfig()?.custom_categories
|
|
3400
3398
|
});
|
|
3401
3399
|
indexReady = true;
|
|
3402
3400
|
lastLoadedAt = Date.now();
|
|
@@ -4921,7 +4919,7 @@ function formatContent(content, format) {
|
|
|
4921
4919
|
const lines = trimmed.split("\n");
|
|
4922
4920
|
return lines.map((line, i) => {
|
|
4923
4921
|
if (i === 0) return `- ${line}`;
|
|
4924
|
-
if (line === "") return "";
|
|
4922
|
+
if (line === "") return " ";
|
|
4925
4923
|
return ` ${line}`;
|
|
4926
4924
|
}).join("\n");
|
|
4927
4925
|
}
|
|
@@ -4932,7 +4930,7 @@ function formatContent(content, format) {
|
|
|
4932
4930
|
const lines = trimmed.split("\n");
|
|
4933
4931
|
return lines.map((line, i) => {
|
|
4934
4932
|
if (i === 0) return `- [ ] ${line}`;
|
|
4935
|
-
if (line === "") return "";
|
|
4933
|
+
if (line === "") return " ";
|
|
4936
4934
|
return ` ${line}`;
|
|
4937
4935
|
}).join("\n");
|
|
4938
4936
|
}
|
|
@@ -4943,7 +4941,7 @@ function formatContent(content, format) {
|
|
|
4943
4941
|
const lines = trimmed.split("\n");
|
|
4944
4942
|
return lines.map((line, i) => {
|
|
4945
4943
|
if (i === 0) return `1. ${line}`;
|
|
4946
|
-
if (line === "") return "";
|
|
4944
|
+
if (line === "") return " ";
|
|
4947
4945
|
return ` ${line}`;
|
|
4948
4946
|
}).join("\n");
|
|
4949
4947
|
}
|
|
@@ -4959,7 +4957,7 @@ function formatContent(content, format) {
|
|
|
4959
4957
|
const indent = " ";
|
|
4960
4958
|
return lines.map((line, i) => {
|
|
4961
4959
|
if (i === 0) return `${prefix}${line}`;
|
|
4962
|
-
if (line === "") return
|
|
4960
|
+
if (line === "") return indent;
|
|
4963
4961
|
return `${indent}${line}`;
|
|
4964
4962
|
}).join("\n");
|
|
4965
4963
|
}
|
|
@@ -5036,7 +5034,7 @@ function insertInSection(content, section, newContent, position, options) {
|
|
|
5036
5034
|
if (indent) {
|
|
5037
5035
|
const contentLines = formattedContent.split("\n");
|
|
5038
5036
|
const indentedContent = contentLines.map((line) => {
|
|
5039
|
-
if (line === "") return line;
|
|
5037
|
+
if (line === "") return indent || line;
|
|
5040
5038
|
return indent + line;
|
|
5041
5039
|
}).join("\n");
|
|
5042
5040
|
lines.splice(section.contentStartLine, 0, indentedContent);
|
|
@@ -5059,7 +5057,7 @@ function insertInSection(content, section, newContent, position, options) {
|
|
|
5059
5057
|
const indent = detectSectionBaseIndentation(lines, section.contentStartLine, section.endLine);
|
|
5060
5058
|
const contentLines = formattedContent.split("\n");
|
|
5061
5059
|
const indentedContent = contentLines.map((line) => {
|
|
5062
|
-
if (line === "") return line;
|
|
5060
|
+
if (line === "") return indent || line;
|
|
5063
5061
|
return indent + line;
|
|
5064
5062
|
}).join("\n");
|
|
5065
5063
|
lines[lastContentLineIdx] = indentedContent;
|
|
@@ -5082,7 +5080,7 @@ function insertInSection(content, section, newContent, position, options) {
|
|
|
5082
5080
|
const indent = detectSectionBaseIndentation(lines, section.contentStartLine, section.endLine);
|
|
5083
5081
|
const contentLines = formattedContent.split("\n");
|
|
5084
5082
|
const indentedContent = contentLines.map((line) => {
|
|
5085
|
-
if (line === "") return line;
|
|
5083
|
+
if (line === "") return indent || line;
|
|
5086
5084
|
return indent + line;
|
|
5087
5085
|
}).join("\n");
|
|
5088
5086
|
lines.splice(insertLine, 0, indentedContent);
|
|
@@ -10718,9 +10716,8 @@ var ALL_CATEGORIES = [
|
|
|
10718
10716
|
];
|
|
10719
10717
|
var PRESETS = {
|
|
10720
10718
|
// Presets
|
|
10721
|
-
default: ["search", "read", "write", "tasks"],
|
|
10722
|
-
|
|
10723
|
-
full: ALL_CATEGORIES.filter((c) => c !== "memory"),
|
|
10719
|
+
default: ["search", "read", "write", "tasks", "memory"],
|
|
10720
|
+
full: [...ALL_CATEGORIES],
|
|
10724
10721
|
// Composable bundles (one per category)
|
|
10725
10722
|
graph: ["graph"],
|
|
10726
10723
|
schema: ["schema"],
|
|
@@ -10734,6 +10731,8 @@ var PRESETS = {
|
|
|
10734
10731
|
};
|
|
10735
10732
|
var DEFAULT_PRESET = "default";
|
|
10736
10733
|
var DEPRECATED_ALIASES = {
|
|
10734
|
+
agent: "default",
|
|
10735
|
+
// agent merged into default — memory now included
|
|
10737
10736
|
minimal: "default",
|
|
10738
10737
|
writer: "default",
|
|
10739
10738
|
// writer was default+tasks, now default includes tasks
|
|
@@ -10848,9 +10847,8 @@ var TOOL_CATEGORY = {
|
|
|
10848
10847
|
tasks: "tasks",
|
|
10849
10848
|
vault_toggle_task: "tasks",
|
|
10850
10849
|
vault_add_task: "tasks",
|
|
10851
|
-
// memory (
|
|
10850
|
+
// memory (2 tools) -- session memory
|
|
10852
10851
|
memory: "memory",
|
|
10853
|
-
recall: "memory",
|
|
10854
10852
|
brief: "memory",
|
|
10855
10853
|
// note-ops (4 tools) -- file management
|
|
10856
10854
|
vault_delete_note: "note-ops",
|
|
@@ -10889,10 +10887,12 @@ function generateInstructions(categories, registry) {
|
|
|
10889
10887
|
parts.push(`Flywheel provides tools to search, read, and write an Obsidian vault's knowledge graph.
|
|
10890
10888
|
|
|
10891
10889
|
Tool selection:
|
|
10892
|
-
1. "search" is the primary tool.
|
|
10893
|
-
|
|
10894
|
-
|
|
10895
|
-
|
|
10890
|
+
1. "search" is the primary tool. One call searches notes, entities, and memories.
|
|
10891
|
+
Each result carries: type (note/entity/memory), frontmatter, tags, aliases,
|
|
10892
|
+
backlinks (ranked by edge weight \xD7 recency), outlinks (existence-checked),
|
|
10893
|
+
section provenance, extracted dates, entity bridges, confidence scores,
|
|
10894
|
+
content snippet or preview, entity category, hub score, and timestamps.
|
|
10895
|
+
This is a decision surface \u2014 usually enough to answer without reading any files.
|
|
10896
10896
|
2. Escalate to "get_note_structure" only when you need the full markdown content
|
|
10897
10897
|
or word count. Use "get_section_content" to read one section by heading name.
|
|
10898
10898
|
3. Start with a broad search: just query text, no filters. Only add folder, tag,
|
|
@@ -10900,7 +10900,7 @@ Tool selection:
|
|
|
10900
10900
|
if (!hasEmbeddingsIndex()) {
|
|
10901
10901
|
parts.push(`
|
|
10902
10902
|
**Setup:** Run \`init_semantic\` once to build embeddings. This unlocks hybrid search (BM25 + semantic),
|
|
10903
|
-
improves
|
|
10903
|
+
improves search results, and enables similarity-based tools. Without it, search is keyword-only.`);
|
|
10904
10904
|
}
|
|
10905
10905
|
if (registry?.isMultiVault) {
|
|
10906
10906
|
parts.push(`
|
|
@@ -10915,9 +10915,9 @@ This server manages multiple vaults. Every tool has an optional "vault" paramete
|
|
|
10915
10915
|
**Frontmatter matters more than content** for Flywheel's intelligence. When creating or updating notes, always set:
|
|
10916
10916
|
- \`type:\` \u2014 drives entity categorization (person, project, technology). Without it, the category is guessed from the name alone and is often wrong.
|
|
10917
10917
|
- \`aliases:\` \u2014 alternative names so the entity is found when referred to differently. Without it, the entity is invisible to searches using alternate names.
|
|
10918
|
-
- \`description:\` \u2014 one-line summary shown in search results and used
|
|
10918
|
+
- \`description:\` \u2014 one-line summary shown in search results and used for entity ranking. Without it, search quality is degraded.
|
|
10919
10919
|
- Tags \u2014 used for filtering, suggestion scoring, and schema analysis.
|
|
10920
|
-
Good frontmatter is the highest-leverage action for improving suggestions,
|
|
10920
|
+
Good frontmatter is the highest-leverage action for improving suggestions, search, and link quality.`);
|
|
10921
10921
|
if (categories.has("read")) {
|
|
10922
10922
|
parts.push(`
|
|
10923
10923
|
## Read
|
|
@@ -10968,7 +10968,10 @@ you say "run the weekly review for this week".`);
|
|
|
10968
10968
|
parts.push(`
|
|
10969
10969
|
## Memory
|
|
10970
10970
|
|
|
10971
|
-
|
|
10971
|
+
"brief" delivers startup context (recent sessions, active entities, stored memories) \u2014 call it at
|
|
10972
|
+
conversation start. "search" finds everything \u2014 notes, entities, and memories in one call. "memory"
|
|
10973
|
+
with action "store" persists observations, facts, or preferences across sessions (e.g. key decisions,
|
|
10974
|
+
user preferences, project status).`);
|
|
10972
10975
|
}
|
|
10973
10976
|
if (categories.has("graph")) {
|
|
10974
10977
|
parts.push(`
|
|
@@ -11013,7 +11016,7 @@ import * as path37 from "path";
|
|
|
11013
11016
|
import { dirname as dirname5, join as join19 } from "path";
|
|
11014
11017
|
import { statSync as statSync6, readFileSync as readFileSync5 } from "fs";
|
|
11015
11018
|
import { fileURLToPath } from "url";
|
|
11016
|
-
import { z as
|
|
11019
|
+
import { z as z39 } from "zod";
|
|
11017
11020
|
import { getSessionId } from "@velvetmonkey/vault-core";
|
|
11018
11021
|
init_vault_scope();
|
|
11019
11022
|
|
|
@@ -13851,64 +13854,6 @@ function enrichResultLight(result, index, stateDb2) {
|
|
|
13851
13854
|
}
|
|
13852
13855
|
return enriched;
|
|
13853
13856
|
}
|
|
13854
|
-
function enrichEntityCompact(entityName, stateDb2, index) {
|
|
13855
|
-
const enriched = {};
|
|
13856
|
-
if (stateDb2) {
|
|
13857
|
-
try {
|
|
13858
|
-
const entity = getEntityByName2(stateDb2, entityName);
|
|
13859
|
-
if (entity) {
|
|
13860
|
-
enriched.category = entity.category;
|
|
13861
|
-
enriched.hub_score = entity.hubScore;
|
|
13862
|
-
if (entity.aliases.length > 0) enriched.aliases = entity.aliases;
|
|
13863
|
-
enriched.path = entity.path;
|
|
13864
|
-
}
|
|
13865
|
-
} catch {
|
|
13866
|
-
}
|
|
13867
|
-
}
|
|
13868
|
-
if (index) {
|
|
13869
|
-
const entityPath = enriched.path ?? index.entities.get(entityName.toLowerCase());
|
|
13870
|
-
if (entityPath) {
|
|
13871
|
-
const note = index.notes.get(entityPath);
|
|
13872
|
-
const normalizedPath = entityPath.toLowerCase().replace(/\.md$/, "");
|
|
13873
|
-
const backlinks = index.backlinks.get(normalizedPath) || [];
|
|
13874
|
-
enriched.backlink_count = backlinks.length;
|
|
13875
|
-
if (note) {
|
|
13876
|
-
if (Object.keys(note.frontmatter).length > 0) enriched.frontmatter = note.frontmatter;
|
|
13877
|
-
if (note.tags.length > 0) enriched.tags = note.tags;
|
|
13878
|
-
if (note.outlinks.length > 0) {
|
|
13879
|
-
enriched.outlink_names = getOutlinkNames(note.outlinks, entityPath, index, stateDb2, COMPACT_OUTLINK_NAMES);
|
|
13880
|
-
}
|
|
13881
|
-
}
|
|
13882
|
-
}
|
|
13883
|
-
}
|
|
13884
|
-
return enriched;
|
|
13885
|
-
}
|
|
13886
|
-
function enrichNoteCompact(notePath, stateDb2, index) {
|
|
13887
|
-
const enriched = {};
|
|
13888
|
-
if (!index) return enriched;
|
|
13889
|
-
const note = index.notes.get(notePath);
|
|
13890
|
-
if (!note) return enriched;
|
|
13891
|
-
const normalizedPath = notePath.toLowerCase().replace(/\.md$/, "");
|
|
13892
|
-
const backlinks = index.backlinks.get(normalizedPath) || [];
|
|
13893
|
-
if (Object.keys(note.frontmatter).length > 0) enriched.frontmatter = note.frontmatter;
|
|
13894
|
-
if (note.tags.length > 0) enriched.tags = note.tags;
|
|
13895
|
-
enriched.backlink_count = backlinks.length;
|
|
13896
|
-
enriched.modified = note.modified.toISOString();
|
|
13897
|
-
if (note.outlinks.length > 0) {
|
|
13898
|
-
enriched.outlink_names = getOutlinkNames(note.outlinks, notePath, index, stateDb2, COMPACT_OUTLINK_NAMES);
|
|
13899
|
-
}
|
|
13900
|
-
if (stateDb2) {
|
|
13901
|
-
try {
|
|
13902
|
-
const entity = getEntityByName2(stateDb2, note.title);
|
|
13903
|
-
if (entity) {
|
|
13904
|
-
enriched.category = entity.category;
|
|
13905
|
-
enriched.hub_score = entity.hubScore;
|
|
13906
|
-
}
|
|
13907
|
-
} catch {
|
|
13908
|
-
}
|
|
13909
|
-
}
|
|
13910
|
-
return enriched;
|
|
13911
|
-
}
|
|
13912
13857
|
|
|
13913
13858
|
// src/core/read/multihop.ts
|
|
13914
13859
|
import { getEntityByName as getEntityByName3, searchEntities } from "@velvetmonkey/vault-core";
|
|
@@ -14381,7 +14326,7 @@ function sortNotes(notes, sortBy, order) {
|
|
|
14381
14326
|
function registerQueryTools(server2, getIndex, getVaultPath, getStateDb3) {
|
|
14382
14327
|
server2.tool(
|
|
14383
14328
|
"search",
|
|
14384
|
-
'Search
|
|
14329
|
+
'Search everything \u2014 notes, entities, and memories \u2014 in one call. Returns a decision surface with three sections: note results (with section provenance, dates, bridges, confidence), matching entity profiles, and relevant memories.\n\nNote results carry full metadata (frontmatter, scored backlinks/outlinks, snippets). Start with just a query, no filters. Narrow with filters only if needed. Use get_note_structure for full content, get_section_content to read one section.\n\nSearches note content (FTS5 + hybrid semantic), entity profiles (people, projects, technologies), and stored memories. Hybrid results included automatically when embeddings are built (via init_semantic).\n\nExample: search({ query: "quarterly review", limit: 5 })\nExample: search({ where: { type: "project", status: "active" } })\n\nMulti-vault: omitting `vault` searches all vaults and merges results. Pass `vault` to search a specific vault.',
|
|
14385
14330
|
{
|
|
14386
14331
|
query: z5.string().optional().describe("Search query text. Required unless using metadata filters (where, has_tag, folder, etc.)"),
|
|
14387
14332
|
// Metadata filters
|
|
@@ -14765,6 +14710,7 @@ function registerSystemTools(server2, getIndex, setIndex, getVaultPath, setConfi
|
|
|
14765
14710
|
const stateDb2 = getStateDb3?.();
|
|
14766
14711
|
if (stateDb2) {
|
|
14767
14712
|
try {
|
|
14713
|
+
const config = loadConfig(stateDb2);
|
|
14768
14714
|
const entityIndex2 = await scanVaultEntities2(vaultPath2, {
|
|
14769
14715
|
excludeFolders: [
|
|
14770
14716
|
"daily-notes",
|
|
@@ -14786,7 +14732,8 @@ function registerSystemTools(server2, getIndex, setIndex, getVaultPath, setConfi
|
|
|
14786
14732
|
"articles",
|
|
14787
14733
|
"bookmarks",
|
|
14788
14734
|
"web-clips"
|
|
14789
|
-
]
|
|
14735
|
+
],
|
|
14736
|
+
customCategories: config.custom_categories
|
|
14790
14737
|
});
|
|
14791
14738
|
stateDb2.replaceAllEntities(entityIndex2);
|
|
14792
14739
|
console.error(`[Flywheel] Updated ${entityIndex2._metadata.total_entities} entities in StateDb`);
|
|
@@ -21768,410 +21715,8 @@ function registerMemoryTools(server2, getStateDb3) {
|
|
|
21768
21715
|
);
|
|
21769
21716
|
}
|
|
21770
21717
|
|
|
21771
|
-
// src/tools/read/recall.ts
|
|
21772
|
-
import { z as z26 } from "zod";
|
|
21773
|
-
import { searchEntities as searchEntitiesDb2 } from "@velvetmonkey/vault-core";
|
|
21774
|
-
init_recency();
|
|
21775
|
-
init_cooccurrence();
|
|
21776
|
-
init_wikilinks();
|
|
21777
|
-
init_edgeWeights();
|
|
21778
|
-
init_wikilinkFeedback();
|
|
21779
|
-
init_embeddings();
|
|
21780
|
-
init_stemmer();
|
|
21781
|
-
|
|
21782
|
-
// src/core/read/mmr.ts
|
|
21783
|
-
init_embeddings();
|
|
21784
|
-
function selectByMmr(candidates, limit, lambda = 0.7) {
|
|
21785
|
-
if (candidates.length <= limit) return candidates;
|
|
21786
|
-
if (candidates.length === 0) return [];
|
|
21787
|
-
const maxScore = Math.max(...candidates.map((c) => c.score));
|
|
21788
|
-
if (maxScore === 0) return candidates.slice(0, limit);
|
|
21789
|
-
const normScores = /* @__PURE__ */ new Map();
|
|
21790
|
-
for (const c of candidates) {
|
|
21791
|
-
normScores.set(c.id, c.score / maxScore);
|
|
21792
|
-
}
|
|
21793
|
-
const selected = [];
|
|
21794
|
-
const remaining = new Set(candidates.map((_, i) => i));
|
|
21795
|
-
let bestIdx = 0;
|
|
21796
|
-
let bestScore = -Infinity;
|
|
21797
|
-
for (const idx of remaining) {
|
|
21798
|
-
if (candidates[idx].score > bestScore) {
|
|
21799
|
-
bestScore = candidates[idx].score;
|
|
21800
|
-
bestIdx = idx;
|
|
21801
|
-
}
|
|
21802
|
-
}
|
|
21803
|
-
selected.push(candidates[bestIdx]);
|
|
21804
|
-
remaining.delete(bestIdx);
|
|
21805
|
-
while (selected.length < limit && remaining.size > 0) {
|
|
21806
|
-
let bestMmr = -Infinity;
|
|
21807
|
-
let bestCandidate = -1;
|
|
21808
|
-
for (const idx of remaining) {
|
|
21809
|
-
const candidate = candidates[idx];
|
|
21810
|
-
const relevance = normScores.get(candidate.id) || 0;
|
|
21811
|
-
let maxSim = 0;
|
|
21812
|
-
if (candidate.embedding !== null) {
|
|
21813
|
-
for (const sel of selected) {
|
|
21814
|
-
if (sel.embedding !== null) {
|
|
21815
|
-
const sim = cosineSimilarity(candidate.embedding, sel.embedding);
|
|
21816
|
-
if (sim > maxSim) maxSim = sim;
|
|
21817
|
-
}
|
|
21818
|
-
}
|
|
21819
|
-
}
|
|
21820
|
-
const mmr = lambda * relevance - (1 - lambda) * maxSim;
|
|
21821
|
-
if (mmr > bestMmr) {
|
|
21822
|
-
bestMmr = mmr;
|
|
21823
|
-
bestCandidate = idx;
|
|
21824
|
-
}
|
|
21825
|
-
}
|
|
21826
|
-
if (bestCandidate === -1) break;
|
|
21827
|
-
selected.push(candidates[bestCandidate]);
|
|
21828
|
-
remaining.delete(bestCandidate);
|
|
21829
|
-
}
|
|
21830
|
-
return selected;
|
|
21831
|
-
}
|
|
21832
|
-
|
|
21833
|
-
// src/tools/read/recall.ts
|
|
21834
|
-
function scoreTextRelevance(query, content) {
|
|
21835
|
-
const queryTokens = tokenize(query).map((t) => t.toLowerCase());
|
|
21836
|
-
const queryStems = queryTokens.map((t) => stem(t));
|
|
21837
|
-
const contentLower = content.toLowerCase();
|
|
21838
|
-
const contentTokens = new Set(tokenize(contentLower));
|
|
21839
|
-
const contentStems = new Set([...contentTokens].map((t) => stem(t)));
|
|
21840
|
-
let score = 0;
|
|
21841
|
-
for (let i = 0; i < queryTokens.length; i++) {
|
|
21842
|
-
const token = queryTokens[i];
|
|
21843
|
-
const stemmed = queryStems[i];
|
|
21844
|
-
if (contentTokens.has(token)) {
|
|
21845
|
-
score += 10;
|
|
21846
|
-
} else if (contentStems.has(stemmed)) {
|
|
21847
|
-
score += 5;
|
|
21848
|
-
}
|
|
21849
|
-
}
|
|
21850
|
-
if (contentLower.includes(query.toLowerCase())) {
|
|
21851
|
-
score += 15;
|
|
21852
|
-
}
|
|
21853
|
-
return score;
|
|
21854
|
-
}
|
|
21855
|
-
function getEdgeWeightBoost(entityName, edgeWeightMap) {
|
|
21856
|
-
const avgWeight = edgeWeightMap.get(entityName.toLowerCase());
|
|
21857
|
-
if (!avgWeight || avgWeight <= 1) return 0;
|
|
21858
|
-
return Math.min((avgWeight - 1) * 3, 6);
|
|
21859
|
-
}
|
|
21860
|
-
async function performRecall(stateDb2, query, options = {}) {
|
|
21861
|
-
const {
|
|
21862
|
-
max_results = 20,
|
|
21863
|
-
focus,
|
|
21864
|
-
entity,
|
|
21865
|
-
max_tokens,
|
|
21866
|
-
diversity = 1,
|
|
21867
|
-
vaultPath: vaultPath2
|
|
21868
|
-
} = options;
|
|
21869
|
-
const results = [];
|
|
21870
|
-
const recencyIndex2 = loadRecencyFromStateDb();
|
|
21871
|
-
const edgeWeightMap = getEntityEdgeWeightMap(stateDb2);
|
|
21872
|
-
const feedbackBoosts = getAllFeedbackBoosts(stateDb2);
|
|
21873
|
-
const cooccurrenceIndex2 = getCooccurrenceIndex();
|
|
21874
|
-
if (!focus || focus === "entities") {
|
|
21875
|
-
try {
|
|
21876
|
-
const entityResults = searchEntitiesDb2(stateDb2, query, max_results);
|
|
21877
|
-
for (const e of entityResults) {
|
|
21878
|
-
const textScore = scoreTextRelevance(query, `${e.name} ${e.description || ""}`);
|
|
21879
|
-
const recency = recencyIndex2 ? getRecencyBoost(e.name, recencyIndex2) : 0;
|
|
21880
|
-
const feedback = feedbackBoosts.get(e.name) ?? 0;
|
|
21881
|
-
const edgeWeight = getEdgeWeightBoost(e.name, edgeWeightMap);
|
|
21882
|
-
const total = textScore + recency + feedback + edgeWeight;
|
|
21883
|
-
if (total > 0) {
|
|
21884
|
-
results.push({
|
|
21885
|
-
type: "entity",
|
|
21886
|
-
id: e.name,
|
|
21887
|
-
content: e.description || `Entity: ${e.name} (${e.category})`,
|
|
21888
|
-
score: total,
|
|
21889
|
-
breakdown: {
|
|
21890
|
-
textRelevance: textScore,
|
|
21891
|
-
recencyBoost: recency,
|
|
21892
|
-
cooccurrenceBoost: 0,
|
|
21893
|
-
feedbackBoost: feedback,
|
|
21894
|
-
edgeWeightBoost: edgeWeight,
|
|
21895
|
-
semanticBoost: 0
|
|
21896
|
-
}
|
|
21897
|
-
});
|
|
21898
|
-
}
|
|
21899
|
-
}
|
|
21900
|
-
} catch {
|
|
21901
|
-
}
|
|
21902
|
-
}
|
|
21903
|
-
if (!focus || focus === "notes") {
|
|
21904
|
-
try {
|
|
21905
|
-
const noteResults = searchFTS5("", query, max_results);
|
|
21906
|
-
for (const n of noteResults) {
|
|
21907
|
-
const textScore = Math.max(10, scoreTextRelevance(query, `${n.title || ""} ${n.snippet || ""}`));
|
|
21908
|
-
const entityName = n.title || n.path.replace(/\.md$/, "").split("/").pop() || "";
|
|
21909
|
-
const recency = recencyIndex2 ? getRecencyBoost(entityName, recencyIndex2) : 0;
|
|
21910
|
-
const feedback = feedbackBoosts.get(entityName) ?? 0;
|
|
21911
|
-
const edgeWeight = getEdgeWeightBoost(entityName, edgeWeightMap);
|
|
21912
|
-
const total = textScore + recency + feedback + edgeWeight;
|
|
21913
|
-
results.push({
|
|
21914
|
-
type: "note",
|
|
21915
|
-
id: n.path,
|
|
21916
|
-
content: n.snippet || n.title || n.path,
|
|
21917
|
-
score: total,
|
|
21918
|
-
breakdown: {
|
|
21919
|
-
textRelevance: textScore,
|
|
21920
|
-
recencyBoost: recency,
|
|
21921
|
-
cooccurrenceBoost: 0,
|
|
21922
|
-
// applied in post-pass below
|
|
21923
|
-
feedbackBoost: feedback,
|
|
21924
|
-
edgeWeightBoost: edgeWeight,
|
|
21925
|
-
semanticBoost: 0
|
|
21926
|
-
}
|
|
21927
|
-
});
|
|
21928
|
-
}
|
|
21929
|
-
} catch {
|
|
21930
|
-
}
|
|
21931
|
-
}
|
|
21932
|
-
if (!focus || focus === "memories") {
|
|
21933
|
-
try {
|
|
21934
|
-
const memResults = searchMemories(stateDb2, {
|
|
21935
|
-
query,
|
|
21936
|
-
entity,
|
|
21937
|
-
limit: max_results
|
|
21938
|
-
});
|
|
21939
|
-
const now = Date.now();
|
|
21940
|
-
for (const m of memResults) {
|
|
21941
|
-
const textScore = scoreTextRelevance(query, `${m.key} ${m.value}`);
|
|
21942
|
-
const confidenceBoost = m.confidence * 5;
|
|
21943
|
-
let typeBoost = 0;
|
|
21944
|
-
switch (m.memory_type) {
|
|
21945
|
-
case "fact":
|
|
21946
|
-
typeBoost = 3;
|
|
21947
|
-
break;
|
|
21948
|
-
case "preference":
|
|
21949
|
-
typeBoost = 2;
|
|
21950
|
-
break;
|
|
21951
|
-
case "observation": {
|
|
21952
|
-
const ageDays = (now - m.updated_at) / 864e5;
|
|
21953
|
-
const recencyFactor = Math.max(0.2, 1 - ageDays / 7);
|
|
21954
|
-
typeBoost = 1 + 4 * recencyFactor;
|
|
21955
|
-
break;
|
|
21956
|
-
}
|
|
21957
|
-
case "summary":
|
|
21958
|
-
typeBoost = 1;
|
|
21959
|
-
break;
|
|
21960
|
-
}
|
|
21961
|
-
const memScore = textScore + confidenceBoost + typeBoost;
|
|
21962
|
-
results.push({
|
|
21963
|
-
type: "memory",
|
|
21964
|
-
id: m.key,
|
|
21965
|
-
content: m.value,
|
|
21966
|
-
score: memScore,
|
|
21967
|
-
breakdown: {
|
|
21968
|
-
textRelevance: textScore,
|
|
21969
|
-
recencyBoost: typeBoost,
|
|
21970
|
-
// type-aware boost reported as recency
|
|
21971
|
-
cooccurrenceBoost: 0,
|
|
21972
|
-
feedbackBoost: confidenceBoost,
|
|
21973
|
-
edgeWeightBoost: 0,
|
|
21974
|
-
semanticBoost: 0
|
|
21975
|
-
}
|
|
21976
|
-
});
|
|
21977
|
-
}
|
|
21978
|
-
} catch {
|
|
21979
|
-
}
|
|
21980
|
-
}
|
|
21981
|
-
if (cooccurrenceIndex2) {
|
|
21982
|
-
const seedEntities = /* @__PURE__ */ new Set();
|
|
21983
|
-
for (const r of results) {
|
|
21984
|
-
if (r.type === "entity") {
|
|
21985
|
-
seedEntities.add(r.id);
|
|
21986
|
-
} else if (r.type === "note") {
|
|
21987
|
-
const name = r.id.replace(/\.md$/, "").split("/").pop() || "";
|
|
21988
|
-
if (name) seedEntities.add(name);
|
|
21989
|
-
}
|
|
21990
|
-
}
|
|
21991
|
-
for (const r of results) {
|
|
21992
|
-
if (r.type === "entity" || r.type === "note") {
|
|
21993
|
-
const name = r.type === "entity" ? r.id : r.id.replace(/\.md$/, "").split("/").pop() || "";
|
|
21994
|
-
const boost = getCooccurrenceBoost(name, seedEntities, cooccurrenceIndex2, recencyIndex2);
|
|
21995
|
-
if (boost > 0) {
|
|
21996
|
-
r.breakdown.cooccurrenceBoost = boost;
|
|
21997
|
-
r.score += boost;
|
|
21998
|
-
}
|
|
21999
|
-
}
|
|
22000
|
-
}
|
|
22001
|
-
}
|
|
22002
|
-
if ((!focus || focus === "entities") && query.length >= 20 && hasEntityEmbeddingsIndex()) {
|
|
22003
|
-
try {
|
|
22004
|
-
const embedding = await embedTextCached(query);
|
|
22005
|
-
const semanticMatches = findSemanticallySimilarEntities(embedding, max_results);
|
|
22006
|
-
for (const match of semanticMatches) {
|
|
22007
|
-
if (match.similarity < 0.3) continue;
|
|
22008
|
-
const boost = match.similarity * 15;
|
|
22009
|
-
const existing = results.find((r) => r.type === "entity" && r.id === match.entityName);
|
|
22010
|
-
if (existing) {
|
|
22011
|
-
existing.score += boost;
|
|
22012
|
-
existing.breakdown.semanticBoost = boost;
|
|
22013
|
-
} else {
|
|
22014
|
-
results.push({
|
|
22015
|
-
type: "entity",
|
|
22016
|
-
id: match.entityName,
|
|
22017
|
-
content: `Semantically similar to: "${query}"`,
|
|
22018
|
-
score: boost,
|
|
22019
|
-
breakdown: {
|
|
22020
|
-
textRelevance: 0,
|
|
22021
|
-
recencyBoost: 0,
|
|
22022
|
-
cooccurrenceBoost: 0,
|
|
22023
|
-
feedbackBoost: 0,
|
|
22024
|
-
edgeWeightBoost: 0,
|
|
22025
|
-
semanticBoost: boost
|
|
22026
|
-
}
|
|
22027
|
-
});
|
|
22028
|
-
}
|
|
22029
|
-
}
|
|
22030
|
-
} catch {
|
|
22031
|
-
}
|
|
22032
|
-
}
|
|
22033
|
-
results.sort((a, b) => b.score - a.score);
|
|
22034
|
-
const seen = /* @__PURE__ */ new Set();
|
|
22035
|
-
const deduped = results.filter((r) => {
|
|
22036
|
-
const key = `${r.type}:${r.id}`;
|
|
22037
|
-
if (seen.has(key)) return false;
|
|
22038
|
-
seen.add(key);
|
|
22039
|
-
return true;
|
|
22040
|
-
});
|
|
22041
|
-
let selected;
|
|
22042
|
-
if (hasEmbeddingsIndex() && deduped.length > max_results) {
|
|
22043
|
-
const notePaths = deduped.filter((r) => r.type === "note").map((r) => r.id);
|
|
22044
|
-
const noteEmbeddings = loadNoteEmbeddingsForPaths(notePaths);
|
|
22045
|
-
let queryEmbedding = null;
|
|
22046
|
-
const mmrCandidates = [];
|
|
22047
|
-
for (const r of deduped) {
|
|
22048
|
-
let embedding = null;
|
|
22049
|
-
if (r.type === "entity") {
|
|
22050
|
-
embedding = getEntityEmbedding(r.id);
|
|
22051
|
-
} else if (r.type === "note") {
|
|
22052
|
-
embedding = noteEmbeddings.get(r.id) ?? null;
|
|
22053
|
-
} else if (r.type === "memory") {
|
|
22054
|
-
try {
|
|
22055
|
-
if (!queryEmbedding) queryEmbedding = await embedTextCached(query);
|
|
22056
|
-
embedding = await embedTextCached(r.content);
|
|
22057
|
-
} catch {
|
|
22058
|
-
}
|
|
22059
|
-
}
|
|
22060
|
-
mmrCandidates.push({ id: `${r.type}:${r.id}`, score: r.score, embedding });
|
|
22061
|
-
}
|
|
22062
|
-
const mmrSelected = selectByMmr(mmrCandidates, max_results, diversity);
|
|
22063
|
-
const selectedIds = new Set(mmrSelected.map((m) => m.id));
|
|
22064
|
-
selected = deduped.filter((r) => selectedIds.has(`${r.type}:${r.id}`));
|
|
22065
|
-
const orderMap = new Map(mmrSelected.map((m, i) => [m.id, i]));
|
|
22066
|
-
selected.sort((a, b) => (orderMap.get(`${a.type}:${a.id}`) ?? 0) - (orderMap.get(`${b.type}:${b.id}`) ?? 0));
|
|
22067
|
-
} else {
|
|
22068
|
-
selected = deduped.slice(0, max_results);
|
|
22069
|
-
}
|
|
22070
|
-
if (vaultPath2) {
|
|
22071
|
-
const queryTokens = tokenize(query).map((t) => t.toLowerCase());
|
|
22072
|
-
let queryEmb = null;
|
|
22073
|
-
if (hasEmbeddingsIndex()) {
|
|
22074
|
-
try {
|
|
22075
|
-
queryEmb = await embedTextCached(query);
|
|
22076
|
-
} catch {
|
|
22077
|
-
}
|
|
22078
|
-
}
|
|
22079
|
-
for (const r of selected) {
|
|
22080
|
-
if (r.type !== "note") continue;
|
|
22081
|
-
try {
|
|
22082
|
-
const absPath = vaultPath2 + "/" + r.id;
|
|
22083
|
-
const snippets = await extractBestSnippets(absPath, queryEmb, queryTokens);
|
|
22084
|
-
if (snippets.length > 0 && snippets[0].text.length > 0) {
|
|
22085
|
-
r.content = snippets[0].text;
|
|
22086
|
-
}
|
|
22087
|
-
} catch {
|
|
22088
|
-
}
|
|
22089
|
-
}
|
|
22090
|
-
}
|
|
22091
|
-
if (max_tokens) {
|
|
22092
|
-
let tokenBudget = max_tokens;
|
|
22093
|
-
const budgeted = [];
|
|
22094
|
-
for (const r of selected) {
|
|
22095
|
-
const estimatedTokens = Math.ceil(r.content.length / 4);
|
|
22096
|
-
if (tokenBudget - estimatedTokens < 0 && budgeted.length > 0) break;
|
|
22097
|
-
tokenBudget -= estimatedTokens;
|
|
22098
|
-
budgeted.push(r);
|
|
22099
|
-
}
|
|
22100
|
-
return budgeted;
|
|
22101
|
-
}
|
|
22102
|
-
return selected;
|
|
22103
|
-
}
|
|
22104
|
-
function registerRecallTools(server2, getStateDb3, getVaultPath, getIndex) {
|
|
22105
|
-
server2.tool(
|
|
22106
|
-
"recall",
|
|
22107
|
-
"Query everything the system knows about a topic. Searches across entities, notes, and memories with graph-boosted ranking.",
|
|
22108
|
-
{
|
|
22109
|
-
query: z26.string().describe('What to recall (e.g., "Project X", "meetings about auth")'),
|
|
22110
|
-
max_results: z26.number().min(1).max(100).optional().describe("Max results (default: 20)"),
|
|
22111
|
-
focus: z26.enum(["entities", "notes", "memories"]).optional().describe("Limit to a specific result type. Omit for best results (searches everything)."),
|
|
22112
|
-
entity: z26.string().optional().describe("Filter memories by entity association"),
|
|
22113
|
-
max_tokens: z26.number().optional().describe("Token budget for response (truncates lower-ranked results)"),
|
|
22114
|
-
diversity: z26.number().min(0).max(1).optional().describe("Relevance vs diversity tradeoff (0=max diversity, 1=pure relevance, default: 0.7)")
|
|
22115
|
-
},
|
|
22116
|
-
async (args) => {
|
|
22117
|
-
const stateDb2 = getStateDb3();
|
|
22118
|
-
if (!stateDb2) {
|
|
22119
|
-
return {
|
|
22120
|
-
content: [{ type: "text", text: JSON.stringify({ error: "StateDb not available" }) }],
|
|
22121
|
-
isError: true
|
|
22122
|
-
};
|
|
22123
|
-
}
|
|
22124
|
-
const results = await performRecall(stateDb2, args.query, {
|
|
22125
|
-
max_results: args.max_results,
|
|
22126
|
-
focus: args.focus,
|
|
22127
|
-
entity: args.entity,
|
|
22128
|
-
max_tokens: args.max_tokens,
|
|
22129
|
-
diversity: args.diversity,
|
|
22130
|
-
vaultPath: getVaultPath?.()
|
|
22131
|
-
});
|
|
22132
|
-
const entities = results.filter((r) => r.type === "entity");
|
|
22133
|
-
const notes = results.filter((r) => r.type === "note");
|
|
22134
|
-
const memories = results.filter((r) => r.type === "memory");
|
|
22135
|
-
const index = getIndex?.() ?? null;
|
|
22136
|
-
const enrichedNotes = notes.map((n) => ({
|
|
22137
|
-
path: n.id,
|
|
22138
|
-
snippet: n.content,
|
|
22139
|
-
...enrichNoteCompact(n.id, stateDb2, index)
|
|
22140
|
-
}));
|
|
22141
|
-
const hopResults = index ? multiHopBackfill(enrichedNotes, index, stateDb2, {
|
|
22142
|
-
maxBackfill: Math.max(5, (args.max_results ?? 10) - notes.length)
|
|
22143
|
-
}) : [];
|
|
22144
|
-
if (index) {
|
|
22145
|
-
const allNoteResults = [...enrichedNotes, ...hopResults];
|
|
22146
|
-
const expansionTerms = extractExpansionTerms(allNoteResults, args.query, index);
|
|
22147
|
-
const expansionResults = expandQuery(expansionTerms, allNoteResults, index, stateDb2);
|
|
22148
|
-
hopResults.push(...expansionResults);
|
|
22149
|
-
}
|
|
22150
|
-
return {
|
|
22151
|
-
content: [{
|
|
22152
|
-
type: "text",
|
|
22153
|
-
text: JSON.stringify({
|
|
22154
|
-
query: args.query,
|
|
22155
|
-
total: results.length,
|
|
22156
|
-
entities: entities.map((e) => ({
|
|
22157
|
-
name: e.id,
|
|
22158
|
-
description: e.content,
|
|
22159
|
-
...enrichEntityCompact(e.id, stateDb2, index)
|
|
22160
|
-
})),
|
|
22161
|
-
notes: [...enrichedNotes, ...hopResults],
|
|
22162
|
-
memories: memories.map((m) => ({
|
|
22163
|
-
key: m.id,
|
|
22164
|
-
value: m.content
|
|
22165
|
-
}))
|
|
22166
|
-
}, null, 2)
|
|
22167
|
-
}]
|
|
22168
|
-
};
|
|
22169
|
-
}
|
|
22170
|
-
);
|
|
22171
|
-
}
|
|
22172
|
-
|
|
22173
21718
|
// src/tools/read/brief.ts
|
|
22174
|
-
import { z as
|
|
21719
|
+
import { z as z26 } from "zod";
|
|
22175
21720
|
init_corrections();
|
|
22176
21721
|
function estimateTokens2(value) {
|
|
22177
21722
|
const str = JSON.stringify(value);
|
|
@@ -22313,8 +21858,8 @@ function registerBriefTools(server2, getStateDb3) {
|
|
|
22313
21858
|
"brief",
|
|
22314
21859
|
"Get a startup context briefing: recent sessions, active entities, memories, pending corrections, and vault stats. Call at conversation start.",
|
|
22315
21860
|
{
|
|
22316
|
-
max_tokens:
|
|
22317
|
-
focus:
|
|
21861
|
+
max_tokens: z26.number().optional().describe("Token budget (lower-priority sections truncated first)"),
|
|
21862
|
+
focus: z26.string().optional().describe("Focus entity or topic (filters content)")
|
|
22318
21863
|
},
|
|
22319
21864
|
async (args) => {
|
|
22320
21865
|
const stateDb2 = getStateDb3();
|
|
@@ -22365,24 +21910,24 @@ function registerBriefTools(server2, getStateDb3) {
|
|
|
22365
21910
|
}
|
|
22366
21911
|
|
|
22367
21912
|
// src/tools/write/config.ts
|
|
22368
|
-
import { z as
|
|
21913
|
+
import { z as z27 } from "zod";
|
|
22369
21914
|
import { saveFlywheelConfigToDb as saveFlywheelConfigToDb2 } from "@velvetmonkey/vault-core";
|
|
22370
21915
|
var VALID_CONFIG_KEYS = {
|
|
22371
|
-
vault_name:
|
|
22372
|
-
exclude_task_tags:
|
|
22373
|
-
exclude_analysis_tags:
|
|
22374
|
-
exclude_entities:
|
|
22375
|
-
exclude_entity_folders:
|
|
22376
|
-
wikilink_strictness:
|
|
22377
|
-
implicit_detection:
|
|
22378
|
-
implicit_patterns:
|
|
22379
|
-
adaptive_strictness:
|
|
22380
|
-
proactive_linking:
|
|
22381
|
-
proactive_min_score:
|
|
22382
|
-
proactive_max_per_file:
|
|
22383
|
-
proactive_max_per_day:
|
|
22384
|
-
custom_categories:
|
|
22385
|
-
type_boost:
|
|
21916
|
+
vault_name: z27.string(),
|
|
21917
|
+
exclude_task_tags: z27.array(z27.string()),
|
|
21918
|
+
exclude_analysis_tags: z27.array(z27.string()),
|
|
21919
|
+
exclude_entities: z27.array(z27.string()),
|
|
21920
|
+
exclude_entity_folders: z27.array(z27.string()),
|
|
21921
|
+
wikilink_strictness: z27.enum(["conservative", "balanced", "aggressive"]),
|
|
21922
|
+
implicit_detection: z27.boolean(),
|
|
21923
|
+
implicit_patterns: z27.array(z27.string()),
|
|
21924
|
+
adaptive_strictness: z27.boolean(),
|
|
21925
|
+
proactive_linking: z27.boolean(),
|
|
21926
|
+
proactive_min_score: z27.number(),
|
|
21927
|
+
proactive_max_per_file: z27.number(),
|
|
21928
|
+
proactive_max_per_day: z27.number(),
|
|
21929
|
+
custom_categories: z27.record(z27.string(), z27.object({
|
|
21930
|
+
type_boost: z27.number().optional()
|
|
22386
21931
|
}))
|
|
22387
21932
|
};
|
|
22388
21933
|
function registerConfigTools(server2, getConfig2, setConfig, getStateDb3) {
|
|
@@ -22392,9 +21937,9 @@ function registerConfigTools(server2, getConfig2, setConfig, getStateDb3) {
|
|
|
22392
21937
|
title: "Flywheel Config",
|
|
22393
21938
|
description: 'Read or update Flywheel configuration.\n- "get": Returns the current FlywheelConfig\n- "set": Updates a single config key and returns the updated config\n\nExample: flywheel_config({ mode: "get" })\nExample: flywheel_config({ mode: "set", key: "exclude_analysis_tags", value: ["habit", "daily"] })',
|
|
22394
21939
|
inputSchema: {
|
|
22395
|
-
mode:
|
|
22396
|
-
key:
|
|
22397
|
-
value:
|
|
21940
|
+
mode: z27.enum(["get", "set"]).describe("Operation mode"),
|
|
21941
|
+
key: z27.string().optional().describe("Config key to update (required for set mode)"),
|
|
21942
|
+
value: z27.unknown().optional().describe("New value for the key (required for set mode)")
|
|
22398
21943
|
}
|
|
22399
21944
|
},
|
|
22400
21945
|
async ({ mode, key, value }) => {
|
|
@@ -22450,7 +21995,7 @@ function registerConfigTools(server2, getConfig2, setConfig, getStateDb3) {
|
|
|
22450
21995
|
// src/tools/write/enrich.ts
|
|
22451
21996
|
init_wikilinks();
|
|
22452
21997
|
init_wikilinkFeedback();
|
|
22453
|
-
import { z as
|
|
21998
|
+
import { z as z28 } from "zod";
|
|
22454
21999
|
import * as fs32 from "fs/promises";
|
|
22455
22000
|
import * as path35 from "path";
|
|
22456
22001
|
import { scanVaultEntities as scanVaultEntities3, SCHEMA_VERSION as SCHEMA_VERSION2 } from "@velvetmonkey/vault-core";
|
|
@@ -22571,7 +22116,8 @@ async function executeRun(stateDb2, vaultPath2) {
|
|
|
22571
22116
|
if (entityCount === 0) {
|
|
22572
22117
|
const start = Date.now();
|
|
22573
22118
|
try {
|
|
22574
|
-
const
|
|
22119
|
+
const config = loadConfig(stateDb2);
|
|
22120
|
+
const entityIndex2 = await scanVaultEntities3(vaultPath2, { excludeFolders: EXCLUDE_FOLDERS, customCategories: config.custom_categories });
|
|
22575
22121
|
stateDb2.replaceAllEntities(entityIndex2);
|
|
22576
22122
|
const newCount = entityIndex2._metadata.total_entities;
|
|
22577
22123
|
steps.push({
|
|
@@ -22754,10 +22300,10 @@ function registerInitTools(server2, getVaultPath, getStateDb3) {
|
|
|
22754
22300
|
"vault_init",
|
|
22755
22301
|
`Initialize vault for Flywheel. Modes: "status" (check what's ready/missing), "run" (execute missing init steps), "enrich" (scan notes with zero wikilinks and apply entity links).`,
|
|
22756
22302
|
{
|
|
22757
|
-
mode:
|
|
22758
|
-
dry_run:
|
|
22759
|
-
batch_size:
|
|
22760
|
-
offset:
|
|
22303
|
+
mode: z28.enum(["status", "run", "enrich"]).default("status").describe("Operation mode (default: status)"),
|
|
22304
|
+
dry_run: z28.boolean().default(true).describe("For enrich mode: preview without modifying files (default: true)"),
|
|
22305
|
+
batch_size: z28.number().default(50).describe("For enrich mode: max notes per invocation (default: 50)"),
|
|
22306
|
+
offset: z28.number().default(0).describe("For enrich mode: skip this many eligible notes (for pagination)")
|
|
22761
22307
|
},
|
|
22762
22308
|
async ({ mode, dry_run, batch_size, offset }) => {
|
|
22763
22309
|
const stateDb2 = getStateDb3();
|
|
@@ -22784,7 +22330,7 @@ function registerInitTools(server2, getVaultPath, getStateDb3) {
|
|
|
22784
22330
|
}
|
|
22785
22331
|
|
|
22786
22332
|
// src/tools/read/metrics.ts
|
|
22787
|
-
import { z as
|
|
22333
|
+
import { z as z29 } from "zod";
|
|
22788
22334
|
function registerMetricsTools(server2, getIndex, getStateDb3) {
|
|
22789
22335
|
server2.registerTool(
|
|
22790
22336
|
"vault_growth",
|
|
@@ -22792,10 +22338,10 @@ function registerMetricsTools(server2, getIndex, getStateDb3) {
|
|
|
22792
22338
|
title: "Vault Growth",
|
|
22793
22339
|
description: 'Track vault growth over time. Modes: "current" (live snapshot), "history" (time series), "trends" (deltas vs N days ago), "index_activity" (rebuild history). Tracks 11 metrics: note_count, link_count, orphan_count, tag_count, entity_count, avg_links_per_note, link_density, connected_ratio, wikilink_accuracy, wikilink_feedback_volume, wikilink_suppressed_count.',
|
|
22794
22340
|
inputSchema: {
|
|
22795
|
-
mode:
|
|
22796
|
-
metric:
|
|
22797
|
-
days_back:
|
|
22798
|
-
limit:
|
|
22341
|
+
mode: z29.enum(["current", "history", "trends", "index_activity"]).describe("Query mode: current snapshot, historical time series, trend analysis, or index rebuild activity"),
|
|
22342
|
+
metric: z29.string().optional().describe('Filter to specific metric (e.g., "note_count"). Omit for all metrics.'),
|
|
22343
|
+
days_back: z29.number().optional().describe("Number of days to look back for history/trends (default: 30)"),
|
|
22344
|
+
limit: z29.number().optional().describe("Number of recent events to return for index_activity mode (default: 20)")
|
|
22799
22345
|
}
|
|
22800
22346
|
},
|
|
22801
22347
|
async ({ mode, metric, days_back, limit: eventLimit }) => {
|
|
@@ -22868,7 +22414,7 @@ function registerMetricsTools(server2, getIndex, getStateDb3) {
|
|
|
22868
22414
|
}
|
|
22869
22415
|
|
|
22870
22416
|
// src/tools/read/activity.ts
|
|
22871
|
-
import { z as
|
|
22417
|
+
import { z as z30 } from "zod";
|
|
22872
22418
|
function registerActivityTools(server2, getStateDb3, getSessionId2) {
|
|
22873
22419
|
server2.registerTool(
|
|
22874
22420
|
"vault_activity",
|
|
@@ -22876,10 +22422,10 @@ function registerActivityTools(server2, getStateDb3, getSessionId2) {
|
|
|
22876
22422
|
title: "Vault Activity",
|
|
22877
22423
|
description: 'Track tool usage patterns and session activity. Modes:\n- "session": Current session summary (tools called, notes accessed)\n- "sessions": List of recent sessions\n- "note_access": Notes ranked by query frequency\n- "tool_usage": Tool usage patterns (most-used tools, avg duration)',
|
|
22878
22424
|
inputSchema: {
|
|
22879
|
-
mode:
|
|
22880
|
-
session_id:
|
|
22881
|
-
days_back:
|
|
22882
|
-
limit:
|
|
22425
|
+
mode: z30.enum(["session", "sessions", "note_access", "tool_usage"]).describe("Activity query mode"),
|
|
22426
|
+
session_id: z30.string().optional().describe("Specific session ID (for session mode, defaults to current)"),
|
|
22427
|
+
days_back: z30.number().optional().describe("Number of days to look back (default: 30)"),
|
|
22428
|
+
limit: z30.number().optional().describe("Maximum results to return (default: 20)")
|
|
22883
22429
|
}
|
|
22884
22430
|
},
|
|
22885
22431
|
async ({ mode, session_id, days_back, limit: resultLimit }) => {
|
|
@@ -22946,12 +22492,65 @@ function registerActivityTools(server2, getStateDb3, getSessionId2) {
|
|
|
22946
22492
|
}
|
|
22947
22493
|
|
|
22948
22494
|
// src/tools/read/similarity.ts
|
|
22949
|
-
import { z as
|
|
22495
|
+
import { z as z31 } from "zod";
|
|
22950
22496
|
|
|
22951
22497
|
// src/core/read/similarity.ts
|
|
22952
22498
|
init_embeddings();
|
|
22953
22499
|
import * as fs33 from "fs";
|
|
22954
22500
|
import * as path36 from "path";
|
|
22501
|
+
|
|
22502
|
+
// src/core/read/mmr.ts
|
|
22503
|
+
init_embeddings();
|
|
22504
|
+
function selectByMmr(candidates, limit, lambda = 0.7) {
|
|
22505
|
+
if (candidates.length <= limit) return candidates;
|
|
22506
|
+
if (candidates.length === 0) return [];
|
|
22507
|
+
const maxScore = Math.max(...candidates.map((c) => c.score));
|
|
22508
|
+
if (maxScore === 0) return candidates.slice(0, limit);
|
|
22509
|
+
const normScores = /* @__PURE__ */ new Map();
|
|
22510
|
+
for (const c of candidates) {
|
|
22511
|
+
normScores.set(c.id, c.score / maxScore);
|
|
22512
|
+
}
|
|
22513
|
+
const selected = [];
|
|
22514
|
+
const remaining = new Set(candidates.map((_, i) => i));
|
|
22515
|
+
let bestIdx = 0;
|
|
22516
|
+
let bestScore = -Infinity;
|
|
22517
|
+
for (const idx of remaining) {
|
|
22518
|
+
if (candidates[idx].score > bestScore) {
|
|
22519
|
+
bestScore = candidates[idx].score;
|
|
22520
|
+
bestIdx = idx;
|
|
22521
|
+
}
|
|
22522
|
+
}
|
|
22523
|
+
selected.push(candidates[bestIdx]);
|
|
22524
|
+
remaining.delete(bestIdx);
|
|
22525
|
+
while (selected.length < limit && remaining.size > 0) {
|
|
22526
|
+
let bestMmr = -Infinity;
|
|
22527
|
+
let bestCandidate = -1;
|
|
22528
|
+
for (const idx of remaining) {
|
|
22529
|
+
const candidate = candidates[idx];
|
|
22530
|
+
const relevance = normScores.get(candidate.id) || 0;
|
|
22531
|
+
let maxSim = 0;
|
|
22532
|
+
if (candidate.embedding !== null) {
|
|
22533
|
+
for (const sel of selected) {
|
|
22534
|
+
if (sel.embedding !== null) {
|
|
22535
|
+
const sim = cosineSimilarity(candidate.embedding, sel.embedding);
|
|
22536
|
+
if (sim > maxSim) maxSim = sim;
|
|
22537
|
+
}
|
|
22538
|
+
}
|
|
22539
|
+
}
|
|
22540
|
+
const mmr = lambda * relevance - (1 - lambda) * maxSim;
|
|
22541
|
+
if (mmr > bestMmr) {
|
|
22542
|
+
bestMmr = mmr;
|
|
22543
|
+
bestCandidate = idx;
|
|
22544
|
+
}
|
|
22545
|
+
}
|
|
22546
|
+
if (bestCandidate === -1) break;
|
|
22547
|
+
selected.push(candidates[bestCandidate]);
|
|
22548
|
+
remaining.delete(bestCandidate);
|
|
22549
|
+
}
|
|
22550
|
+
return selected;
|
|
22551
|
+
}
|
|
22552
|
+
|
|
22553
|
+
// src/core/read/similarity.ts
|
|
22955
22554
|
var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
22956
22555
|
"the",
|
|
22957
22556
|
"be",
|
|
@@ -23225,9 +22824,9 @@ function registerSimilarityTools(server2, getIndex, getVaultPath, getStateDb3) {
|
|
|
23225
22824
|
title: "Find Similar Notes",
|
|
23226
22825
|
description: "Find notes similar to a given note using FTS5 keyword matching. When embeddings have been built (via init_semantic), automatically uses hybrid ranking (BM25 + embedding similarity via Reciprocal Rank Fusion). Already-linked notes are automatically excluded.",
|
|
23227
22826
|
inputSchema: {
|
|
23228
|
-
path:
|
|
23229
|
-
limit:
|
|
23230
|
-
diversity:
|
|
22827
|
+
path: z31.string().describe('Path to the source note (relative to vault root, e.g. "projects/alpha.md")'),
|
|
22828
|
+
limit: z31.number().optional().describe("Maximum number of similar notes to return (default: 10)"),
|
|
22829
|
+
diversity: z31.number().min(0).max(1).optional().describe("Relevance vs diversity tradeoff (0=max diversity, 1=pure relevance, default: 0.7)")
|
|
23231
22830
|
}
|
|
23232
22831
|
},
|
|
23233
22832
|
async ({ path: path39, limit, diversity }) => {
|
|
@@ -23272,7 +22871,7 @@ function registerSimilarityTools(server2, getIndex, getVaultPath, getStateDb3) {
|
|
|
23272
22871
|
|
|
23273
22872
|
// src/tools/read/semantic.ts
|
|
23274
22873
|
init_embeddings();
|
|
23275
|
-
import { z as
|
|
22874
|
+
import { z as z32 } from "zod";
|
|
23276
22875
|
import { getAllEntitiesFromDb as getAllEntitiesFromDb3 } from "@velvetmonkey/vault-core";
|
|
23277
22876
|
function registerSemanticTools(server2, getVaultPath, getStateDb3) {
|
|
23278
22877
|
server2.registerTool(
|
|
@@ -23281,7 +22880,7 @@ function registerSemanticTools(server2, getVaultPath, getStateDb3) {
|
|
|
23281
22880
|
title: "Initialize Semantic Search",
|
|
23282
22881
|
description: "Download the embedding model and build semantic search index for this vault. After running, search and find_similar automatically use hybrid ranking (BM25 + semantic). Run once per vault \u2014 subsequent calls skip already-embedded notes unless force=true.",
|
|
23283
22882
|
inputSchema: {
|
|
23284
|
-
force:
|
|
22883
|
+
force: z32.boolean().optional().describe(
|
|
23285
22884
|
"Rebuild all embeddings even if they already exist (default: false)"
|
|
23286
22885
|
)
|
|
23287
22886
|
}
|
|
@@ -23376,7 +22975,7 @@ function registerSemanticTools(server2, getVaultPath, getStateDb3) {
|
|
|
23376
22975
|
|
|
23377
22976
|
// src/tools/read/merges.ts
|
|
23378
22977
|
init_levenshtein();
|
|
23379
|
-
import { z as
|
|
22978
|
+
import { z as z33 } from "zod";
|
|
23380
22979
|
import { getAllEntitiesFromDb as getAllEntitiesFromDb4, getDismissedMergePairs, recordMergeDismissal } from "@velvetmonkey/vault-core";
|
|
23381
22980
|
function normalizeName(name) {
|
|
23382
22981
|
return name.toLowerCase().replace(/[.\-_]/g, "").replace(/js$/, "").replace(/ts$/, "");
|
|
@@ -23386,7 +22985,7 @@ function registerMergeTools2(server2, getStateDb3) {
|
|
|
23386
22985
|
"suggest_entity_merges",
|
|
23387
22986
|
"Find potential duplicate entities that could be merged based on name similarity",
|
|
23388
22987
|
{
|
|
23389
|
-
limit:
|
|
22988
|
+
limit: z33.number().optional().default(50).describe("Maximum number of suggestions to return")
|
|
23390
22989
|
},
|
|
23391
22990
|
async ({ limit }) => {
|
|
23392
22991
|
const stateDb2 = getStateDb3();
|
|
@@ -23488,11 +23087,11 @@ function registerMergeTools2(server2, getStateDb3) {
|
|
|
23488
23087
|
"dismiss_merge_suggestion",
|
|
23489
23088
|
"Permanently dismiss a merge suggestion so it never reappears",
|
|
23490
23089
|
{
|
|
23491
|
-
source_path:
|
|
23492
|
-
target_path:
|
|
23493
|
-
source_name:
|
|
23494
|
-
target_name:
|
|
23495
|
-
reason:
|
|
23090
|
+
source_path: z33.string().describe("Path of the source entity"),
|
|
23091
|
+
target_path: z33.string().describe("Path of the target entity"),
|
|
23092
|
+
source_name: z33.string().describe("Name of the source entity"),
|
|
23093
|
+
target_name: z33.string().describe("Name of the target entity"),
|
|
23094
|
+
reason: z33.string().describe("Original suggestion reason")
|
|
23496
23095
|
},
|
|
23497
23096
|
async ({ source_path, target_path, source_name, target_name, reason }) => {
|
|
23498
23097
|
const stateDb2 = getStateDb3();
|
|
@@ -23511,7 +23110,7 @@ function registerMergeTools2(server2, getStateDb3) {
|
|
|
23511
23110
|
}
|
|
23512
23111
|
|
|
23513
23112
|
// src/tools/read/temporalAnalysis.ts
|
|
23514
|
-
import { z as
|
|
23113
|
+
import { z as z34 } from "zod";
|
|
23515
23114
|
init_wikilinks();
|
|
23516
23115
|
function formatDate3(d) {
|
|
23517
23116
|
return d.toISOString().split("T")[0];
|
|
@@ -24037,9 +23636,9 @@ function registerTemporalAnalysisTools(server2, getIndex, getVaultPath, getState
|
|
|
24037
23636
|
title: "Context Around Date",
|
|
24038
23637
|
description: "Reconstruct what was happening in the vault around a specific date. Shows modified/created notes, active entities, wikilink activity, and file moves within a time window.",
|
|
24039
23638
|
inputSchema: {
|
|
24040
|
-
date:
|
|
24041
|
-
window_days:
|
|
24042
|
-
limit:
|
|
23639
|
+
date: z34.string().describe("Center date in YYYY-MM-DD format"),
|
|
23640
|
+
window_days: z34.coerce.number().default(3).describe("Days before and after the center date (default 3 = 7-day window)"),
|
|
23641
|
+
limit: z34.coerce.number().default(50).describe("Maximum number of notes to return")
|
|
24043
23642
|
}
|
|
24044
23643
|
},
|
|
24045
23644
|
async ({ date, window_days, limit: requestedLimit }) => {
|
|
@@ -24054,12 +23653,12 @@ function registerTemporalAnalysisTools(server2, getIndex, getVaultPath, getState
|
|
|
24054
23653
|
title: "Predict Stale Notes",
|
|
24055
23654
|
description: "Multi-signal staleness prediction. Scores notes by importance (backlinks, hub score, tasks, status) and staleness risk (age, entity disconnect, task urgency). Returns concrete recommendations: archive, update, review, or low_priority.",
|
|
24056
23655
|
inputSchema: {
|
|
24057
|
-
days:
|
|
24058
|
-
min_importance:
|
|
24059
|
-
include_recommendations:
|
|
24060
|
-
folder:
|
|
24061
|
-
limit:
|
|
24062
|
-
offset:
|
|
23656
|
+
days: z34.coerce.number().default(30).describe("Notes not modified in this many days (default 30)"),
|
|
23657
|
+
min_importance: z34.coerce.number().default(0).describe("Filter by minimum importance score 0-100 (default 0)"),
|
|
23658
|
+
include_recommendations: z34.boolean().default(true).describe("Include action recommendations (default true)"),
|
|
23659
|
+
folder: z34.string().optional().describe("Limit to notes in this folder"),
|
|
23660
|
+
limit: z34.coerce.number().default(30).describe("Maximum results to return (default 30)"),
|
|
23661
|
+
offset: z34.coerce.number().default(0).describe("Results to skip for pagination (default 0)")
|
|
24063
23662
|
}
|
|
24064
23663
|
},
|
|
24065
23664
|
async ({ days, min_importance, include_recommendations, folder, limit: requestedLimit, offset }) => {
|
|
@@ -24083,9 +23682,9 @@ function registerTemporalAnalysisTools(server2, getIndex, getVaultPath, getState
|
|
|
24083
23682
|
title: "Track Concept Evolution",
|
|
24084
23683
|
description: "Timeline of how an entity has evolved: link additions/removals, feedback events, category changes, co-occurrence shifts. Shows current state, chronological event history, link durability stats, and top co-occurrence neighbors.",
|
|
24085
23684
|
inputSchema: {
|
|
24086
|
-
entity:
|
|
24087
|
-
days_back:
|
|
24088
|
-
include_cooccurrence:
|
|
23685
|
+
entity: z34.string().describe("Entity name (case-insensitive)"),
|
|
23686
|
+
days_back: z34.coerce.number().default(90).describe("How far back to look (default 90 days)"),
|
|
23687
|
+
include_cooccurrence: z34.boolean().default(true).describe("Include co-occurrence neighbors (default true)")
|
|
24089
23688
|
}
|
|
24090
23689
|
},
|
|
24091
23690
|
async ({ entity, days_back, include_cooccurrence }) => {
|
|
@@ -24105,10 +23704,10 @@ function registerTemporalAnalysisTools(server2, getIndex, getVaultPath, getState
|
|
|
24105
23704
|
title: "Temporal Summary",
|
|
24106
23705
|
description: "Generate a vault pulse report for a time period. Composes context, staleness prediction, and concept evolution into a single summary. Shows activity snapshot, entity momentum, and maintenance alerts. Use for weekly/monthly/quarterly reviews.",
|
|
24107
23706
|
inputSchema: {
|
|
24108
|
-
start_date:
|
|
24109
|
-
end_date:
|
|
24110
|
-
focus_entities:
|
|
24111
|
-
limit:
|
|
23707
|
+
start_date: z34.string().describe("Start of period in YYYY-MM-DD format"),
|
|
23708
|
+
end_date: z34.string().describe("End of period in YYYY-MM-DD format"),
|
|
23709
|
+
focus_entities: z34.array(z34.string()).optional().describe("Specific entities to track evolution for (default: top 5 active entities in period)"),
|
|
23710
|
+
limit: z34.coerce.number().default(50).describe("Maximum notes to include in context snapshot")
|
|
24112
23711
|
}
|
|
24113
23712
|
},
|
|
24114
23713
|
async ({ start_date, end_date, focus_entities, limit: requestedLimit }) => {
|
|
@@ -24127,15 +23726,15 @@ function registerTemporalAnalysisTools(server2, getIndex, getVaultPath, getState
|
|
|
24127
23726
|
}
|
|
24128
23727
|
|
|
24129
23728
|
// src/tools/read/sessionHistory.ts
|
|
24130
|
-
import { z as
|
|
23729
|
+
import { z as z35 } from "zod";
|
|
24131
23730
|
function registerSessionHistoryTools(server2, getStateDb3) {
|
|
24132
23731
|
server2.tool(
|
|
24133
23732
|
"vault_session_history",
|
|
24134
23733
|
"View session history. Without session_id: lists recent sessions. With session_id: returns chronological tool invocations, notes accessed, and timing. Hierarchical sessions supported (parent ID includes child sessions).",
|
|
24135
23734
|
{
|
|
24136
|
-
session_id:
|
|
24137
|
-
include_children:
|
|
24138
|
-
limit:
|
|
23735
|
+
session_id: z35.string().optional().describe("Session ID for detail view. Omit for recent sessions list."),
|
|
23736
|
+
include_children: z35.boolean().optional().describe("Include child sessions (default: true)"),
|
|
23737
|
+
limit: z35.number().min(1).max(500).optional().describe("Max invocations to return in detail view (default: 200)")
|
|
24139
23738
|
},
|
|
24140
23739
|
async (args) => {
|
|
24141
23740
|
const stateDb2 = getStateDb3();
|
|
@@ -24183,7 +23782,7 @@ function registerSessionHistoryTools(server2, getStateDb3) {
|
|
|
24183
23782
|
}
|
|
24184
23783
|
|
|
24185
23784
|
// src/tools/read/entityHistory.ts
|
|
24186
|
-
import { z as
|
|
23785
|
+
import { z as z36 } from "zod";
|
|
24187
23786
|
|
|
24188
23787
|
// src/core/read/entityHistory.ts
|
|
24189
23788
|
function normalizeTimestamp(ts) {
|
|
@@ -24341,8 +23940,8 @@ function registerEntityHistoryTools(server2, getStateDb3) {
|
|
|
24341
23940
|
"vault_entity_history",
|
|
24342
23941
|
"Get a unified timeline of everything about an entity: when it was linked, feedback received, suggestion scores, edge weight changes, metadata mutations, memories, and corrections. Sorted chronologically with pagination.",
|
|
24343
23942
|
{
|
|
24344
|
-
entity_name:
|
|
24345
|
-
event_types:
|
|
23943
|
+
entity_name: z36.string().describe("Entity name to query (case-insensitive)"),
|
|
23944
|
+
event_types: z36.array(z36.enum([
|
|
24346
23945
|
"application",
|
|
24347
23946
|
"feedback",
|
|
24348
23947
|
"suggestion",
|
|
@@ -24351,10 +23950,10 @@ function registerEntityHistoryTools(server2, getStateDb3) {
|
|
|
24351
23950
|
"memory",
|
|
24352
23951
|
"correction"
|
|
24353
23952
|
])).optional().describe("Filter to specific event types. Omit for all types."),
|
|
24354
|
-
start_date:
|
|
24355
|
-
end_date:
|
|
24356
|
-
limit:
|
|
24357
|
-
offset:
|
|
23953
|
+
start_date: z36.string().optional().describe("Start date (YYYY-MM-DD) for date range filter"),
|
|
23954
|
+
end_date: z36.string().optional().describe("End date (YYYY-MM-DD) for date range filter"),
|
|
23955
|
+
limit: z36.number().min(1).max(200).optional().describe("Max events to return (default: 50)"),
|
|
23956
|
+
offset: z36.number().min(0).optional().describe("Offset for pagination (default: 0)")
|
|
24358
23957
|
},
|
|
24359
23958
|
async (args) => {
|
|
24360
23959
|
const stateDb2 = getStateDb3();
|
|
@@ -24386,7 +23985,7 @@ function registerEntityHistoryTools(server2, getStateDb3) {
|
|
|
24386
23985
|
}
|
|
24387
23986
|
|
|
24388
23987
|
// src/tools/read/learningReport.ts
|
|
24389
|
-
import { z as
|
|
23988
|
+
import { z as z37 } from "zod";
|
|
24390
23989
|
|
|
24391
23990
|
// src/core/read/learningReport.ts
|
|
24392
23991
|
function isoDate(d) {
|
|
@@ -24522,8 +24121,8 @@ function registerLearningReportTools(server2, getIndex, getStateDb3) {
|
|
|
24522
24121
|
"flywheel_learning_report",
|
|
24523
24122
|
"Get a narrative report of the flywheel auto-linking system's learning progress. Shows: applications by day, feedback (positive/negative), survival rate, top rejected entities, suggestion funnel (evaluations \u2192 applications \u2192 survivals), and graph growth. Use compare=true for period-over-period deltas.",
|
|
24524
24123
|
{
|
|
24525
|
-
days_back:
|
|
24526
|
-
compare:
|
|
24124
|
+
days_back: z37.number().min(1).max(365).optional().describe("Analysis window in days (default: 7). Use 1 for today, 2 for last 48h, etc."),
|
|
24125
|
+
compare: z37.boolean().optional().describe("Include comparison with the preceding equal-length period (default: false)")
|
|
24527
24126
|
},
|
|
24528
24127
|
async (args) => {
|
|
24529
24128
|
const stateDb2 = getStateDb3();
|
|
@@ -24550,7 +24149,7 @@ function registerLearningReportTools(server2, getIndex, getStateDb3) {
|
|
|
24550
24149
|
}
|
|
24551
24150
|
|
|
24552
24151
|
// src/tools/read/calibrationExport.ts
|
|
24553
|
-
import { z as
|
|
24152
|
+
import { z as z38 } from "zod";
|
|
24554
24153
|
|
|
24555
24154
|
// src/core/read/calibrationExport.ts
|
|
24556
24155
|
init_wikilinkFeedback();
|
|
@@ -24843,8 +24442,8 @@ function registerCalibrationExportTools(server2, getIndex, getStateDb3, getConfi
|
|
|
24843
24442
|
"flywheel_calibration_export",
|
|
24844
24443
|
"Export anonymized aggregate scoring data for cross-vault algorithm calibration. No entity names, note paths, or content \u2014 safe to share. Includes: suggestion funnel, per-layer contribution averages, survival rates by entity category, score distribution, suppression stats, recency/co-occurrence effectiveness, and threshold sweep.",
|
|
24845
24444
|
{
|
|
24846
|
-
days_back:
|
|
24847
|
-
include_vault_id:
|
|
24445
|
+
days_back: z38.number().min(1).max(365).optional().describe("Analysis window in days (default: 30)"),
|
|
24446
|
+
include_vault_id: z38.boolean().optional().describe("Include anonymous vault ID for longitudinal tracking (default: true)")
|
|
24848
24447
|
},
|
|
24849
24448
|
async (args) => {
|
|
24850
24449
|
const stateDb2 = getStateDb3();
|
|
@@ -25131,7 +24730,7 @@ function applyToolGating(targetServer, categories, getDb4, registry, getVaultPat
|
|
|
25131
24730
|
const schemaIdx = handlerIdx - 1;
|
|
25132
24731
|
const schema = args[schemaIdx];
|
|
25133
24732
|
if (schema && typeof schema === "object" && !Array.isArray(schema)) {
|
|
25134
|
-
schema.vault =
|
|
24733
|
+
schema.vault = z39.string().optional().describe(
|
|
25135
24734
|
`Vault name for multi-vault mode. Available: ${registry.getVaultNames().join(", ")}. Default: ${registry.primaryName}`
|
|
25136
24735
|
);
|
|
25137
24736
|
}
|
|
@@ -25257,7 +24856,6 @@ function registerAllTools(targetServer, ctx) {
|
|
|
25257
24856
|
registerLearningReportTools(targetServer, gvi, gsd);
|
|
25258
24857
|
registerCalibrationExportTools(targetServer, gvi, gsd, gcf);
|
|
25259
24858
|
registerMemoryTools(targetServer, gsd);
|
|
25260
|
-
registerRecallTools(targetServer, gsd, gvp, () => gvi() ?? null);
|
|
25261
24859
|
registerBriefTools(targetServer, gsd);
|
|
25262
24860
|
registerVaultResources(targetServer, () => gvi() ?? null);
|
|
25263
24861
|
}
|
|
@@ -25441,7 +25039,10 @@ function updateFlywheelConfig(config) {
|
|
|
25441
25039
|
flywheelConfig = config;
|
|
25442
25040
|
setWikilinkConfig(config);
|
|
25443
25041
|
const ctx = getActiveVaultContext();
|
|
25444
|
-
if (ctx)
|
|
25042
|
+
if (ctx) {
|
|
25043
|
+
ctx.flywheelConfig = config;
|
|
25044
|
+
setActiveScope(buildVaultScope(ctx));
|
|
25045
|
+
}
|
|
25445
25046
|
}
|
|
25446
25047
|
async function bootVault(ctx, startTime) {
|
|
25447
25048
|
const vp = ctx.vaultPath;
|
|
@@ -25594,6 +25195,7 @@ async function main() {
|
|
|
25594
25195
|
loadVaultCooccurrence(primaryCtx);
|
|
25595
25196
|
activateVault(primaryCtx);
|
|
25596
25197
|
await bootVault(primaryCtx, startTime);
|
|
25198
|
+
activateVault(primaryCtx);
|
|
25597
25199
|
if (vaultConfigs && vaultConfigs.length > 1) {
|
|
25598
25200
|
const secondaryConfigs = vaultConfigs.slice(1);
|
|
25599
25201
|
(async () => {
|
|
@@ -25622,7 +25224,8 @@ async function updateEntitiesInStateDb(vp, sd) {
|
|
|
25622
25224
|
const config = loadConfig(db4);
|
|
25623
25225
|
const excludeFolders = config.exclude_entity_folders?.length ? config.exclude_entity_folders : DEFAULT_ENTITY_EXCLUDE_FOLDERS;
|
|
25624
25226
|
const entityIndex2 = await scanVaultEntities4(vault, {
|
|
25625
|
-
excludeFolders
|
|
25227
|
+
excludeFolders,
|
|
25228
|
+
customCategories: config.custom_categories
|
|
25626
25229
|
});
|
|
25627
25230
|
db4.replaceAllEntities(entityIndex2);
|
|
25628
25231
|
serverLog("index", `Updated ${entityIndex2._metadata.total_entities} entities in StateDb`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@velvetmonkey/flywheel-memory",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.150",
|
|
4
4
|
"description": "MCP server that gives Claude full read/write access to your Obsidian vault. Select from 74 tools for search, backlinks, graph queries, mutations, agent memory, and hybrid semantic search.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"dependencies": {
|
|
55
55
|
"@huggingface/transformers": "^3.8.1",
|
|
56
56
|
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
57
|
-
"@velvetmonkey/vault-core": "^2.0.
|
|
57
|
+
"@velvetmonkey/vault-core": "^2.0.150",
|
|
58
58
|
"better-sqlite3": "^12.0.0",
|
|
59
59
|
"chokidar": "^4.0.0",
|
|
60
60
|
"gray-matter": "^4.0.3",
|
|
@@ -85,4 +85,4 @@
|
|
|
85
85
|
"publishConfig": {
|
|
86
86
|
"access": "public"
|
|
87
87
|
}
|
|
88
|
-
}
|
|
88
|
+
}
|