@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.
Files changed (2) hide show
  1. package/dist/index.js +104 -2
  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.toLowerCase(), notePath);
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.41",
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.40",
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",