@velvetmonkey/flywheel-memory 2.0.41 → 2.0.42
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 +104 -2
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -3703,7 +3703,7 @@ function trackWikilinkApplications(stateDb2, notePath, entities) {
|
|
|
3703
3703
|
`);
|
|
3704
3704
|
const transaction = stateDb2.db.transaction(() => {
|
|
3705
3705
|
for (const entity of entities) {
|
|
3706
|
-
upsert.run(entity
|
|
3706
|
+
upsert.run(entity, notePath);
|
|
3707
3707
|
}
|
|
3708
3708
|
});
|
|
3709
3709
|
transaction();
|
|
@@ -3776,7 +3776,7 @@ function processImplicitFeedback(stateDb2, notePath, currentContent) {
|
|
|
3776
3776
|
);
|
|
3777
3777
|
const transaction = stateDb2.db.transaction(() => {
|
|
3778
3778
|
for (const entity of tracked) {
|
|
3779
|
-
if (!currentLinks.has(entity)) {
|
|
3779
|
+
if (!currentLinks.has(entity.toLowerCase())) {
|
|
3780
3780
|
recordFeedback(stateDb2, entity, "implicit:removed", notePath, false);
|
|
3781
3781
|
markRemoved.run(entity, notePath);
|
|
3782
3782
|
removed.push(entity);
|
|
@@ -13965,6 +13965,108 @@ ${trimmedSource}`;
|
|
|
13965
13965
|
}
|
|
13966
13966
|
}
|
|
13967
13967
|
);
|
|
13968
|
+
server2.tool(
|
|
13969
|
+
"absorb_as_alias",
|
|
13970
|
+
"Absorb an entity name as an alias of a target note: adds alias to target frontmatter and rewrites all [[source]] links to [[target|source]]. Lighter than merge_entities \u2014 no source note required, no content append, no deletion.",
|
|
13971
|
+
{
|
|
13972
|
+
source_name: z16.string().describe('The entity name to absorb (e.g. "Foo")'),
|
|
13973
|
+
target_path: z16.string().describe('Vault-relative path of the target entity note (e.g. "entities/Bar.md")')
|
|
13974
|
+
},
|
|
13975
|
+
async ({ source_name, target_path }) => {
|
|
13976
|
+
try {
|
|
13977
|
+
if (!validatePath(vaultPath2, target_path)) {
|
|
13978
|
+
const result2 = {
|
|
13979
|
+
success: false,
|
|
13980
|
+
message: "Invalid target path: path traversal not allowed",
|
|
13981
|
+
path: target_path
|
|
13982
|
+
};
|
|
13983
|
+
return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
|
|
13984
|
+
}
|
|
13985
|
+
let targetContent;
|
|
13986
|
+
let targetFrontmatter;
|
|
13987
|
+
try {
|
|
13988
|
+
const target = await readVaultFile(vaultPath2, target_path);
|
|
13989
|
+
targetContent = target.content;
|
|
13990
|
+
targetFrontmatter = target.frontmatter;
|
|
13991
|
+
} catch {
|
|
13992
|
+
const result2 = {
|
|
13993
|
+
success: false,
|
|
13994
|
+
message: `Target file not found: ${target_path}`,
|
|
13995
|
+
path: target_path
|
|
13996
|
+
};
|
|
13997
|
+
return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
|
|
13998
|
+
}
|
|
13999
|
+
const targetTitle = getTitleFromPath(target_path);
|
|
14000
|
+
const existingAliases = extractAliases2(targetFrontmatter);
|
|
14001
|
+
const deduped = new Set(existingAliases);
|
|
14002
|
+
if (source_name.toLowerCase() !== targetTitle.toLowerCase()) {
|
|
14003
|
+
deduped.add(source_name);
|
|
14004
|
+
}
|
|
14005
|
+
targetFrontmatter.aliases = Array.from(deduped);
|
|
14006
|
+
await writeVaultFile(vaultPath2, target_path, targetContent, targetFrontmatter);
|
|
14007
|
+
const backlinks = await findBacklinks(vaultPath2, source_name, []);
|
|
14008
|
+
let totalBacklinksUpdated = 0;
|
|
14009
|
+
const modifiedFiles = [];
|
|
14010
|
+
for (const backlink of backlinks) {
|
|
14011
|
+
if (backlink.path === target_path) continue;
|
|
14012
|
+
let fileData;
|
|
14013
|
+
try {
|
|
14014
|
+
fileData = await readVaultFile(vaultPath2, backlink.path);
|
|
14015
|
+
} catch {
|
|
14016
|
+
continue;
|
|
14017
|
+
}
|
|
14018
|
+
let content = fileData.content;
|
|
14019
|
+
let linksUpdated = 0;
|
|
14020
|
+
const pattern = new RegExp(
|
|
14021
|
+
`\\[\\[${escapeRegex(source_name)}(\\|[^\\]]+)?\\]\\]`,
|
|
14022
|
+
"gi"
|
|
14023
|
+
);
|
|
14024
|
+
content = content.replace(pattern, (_match, displayPart) => {
|
|
14025
|
+
linksUpdated++;
|
|
14026
|
+
if (displayPart) {
|
|
14027
|
+
return `[[${targetTitle}${displayPart}]]`;
|
|
14028
|
+
}
|
|
14029
|
+
if (source_name.toLowerCase() === targetTitle.toLowerCase()) {
|
|
14030
|
+
return `[[${targetTitle}]]`;
|
|
14031
|
+
}
|
|
14032
|
+
return `[[${targetTitle}|${source_name}]]`;
|
|
14033
|
+
});
|
|
14034
|
+
if (linksUpdated > 0) {
|
|
14035
|
+
await writeVaultFile(vaultPath2, backlink.path, content, fileData.frontmatter);
|
|
14036
|
+
totalBacklinksUpdated += linksUpdated;
|
|
14037
|
+
modifiedFiles.push(backlink.path);
|
|
14038
|
+
}
|
|
14039
|
+
}
|
|
14040
|
+
initializeEntityIndex(vaultPath2).catch((err) => {
|
|
14041
|
+
console.error(`[Flywheel] Entity cache rebuild failed: ${err}`);
|
|
14042
|
+
});
|
|
14043
|
+
const aliasAdded = source_name.toLowerCase() !== targetTitle.toLowerCase();
|
|
14044
|
+
const previewLines = [
|
|
14045
|
+
`Absorbed: "${source_name}" \u2192 "${targetTitle}"`,
|
|
14046
|
+
`Alias added: ${aliasAdded ? source_name : "no (matches target title)"}`,
|
|
14047
|
+
`Backlinks updated: ${totalBacklinksUpdated}`
|
|
14048
|
+
];
|
|
14049
|
+
if (modifiedFiles.length > 0) {
|
|
14050
|
+
previewLines.push(`Files modified: ${modifiedFiles.join(", ")}`);
|
|
14051
|
+
}
|
|
14052
|
+
const result = {
|
|
14053
|
+
success: true,
|
|
14054
|
+
message: `Absorbed "${source_name}" as alias of "${targetTitle}"`,
|
|
14055
|
+
path: target_path,
|
|
14056
|
+
preview: previewLines.join("\n"),
|
|
14057
|
+
backlinks_updated: totalBacklinksUpdated
|
|
14058
|
+
};
|
|
14059
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
14060
|
+
} catch (error) {
|
|
14061
|
+
const result = {
|
|
14062
|
+
success: false,
|
|
14063
|
+
message: `Failed to absorb as alias: ${error instanceof Error ? error.message : String(error)}`,
|
|
14064
|
+
path: target_path
|
|
14065
|
+
};
|
|
14066
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
14067
|
+
}
|
|
14068
|
+
}
|
|
14069
|
+
);
|
|
13968
14070
|
}
|
|
13969
14071
|
|
|
13970
14072
|
// src/tools/write/system.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@velvetmonkey/flywheel-memory",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.42",
|
|
4
4
|
"description": "MCP server that gives Claude full read/write access to your Obsidian vault. 42 tools for search, backlinks, graph queries, mutations, and hybrid semantic search.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
54
|
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
55
|
-
"@velvetmonkey/vault-core": "^2.0.
|
|
55
|
+
"@velvetmonkey/vault-core": "^2.0.42",
|
|
56
56
|
"better-sqlite3": "^11.0.0",
|
|
57
57
|
"chokidar": "^4.0.0",
|
|
58
58
|
"gray-matter": "^4.0.3",
|