@velvetmonkey/flywheel-memory 2.0.135 → 2.0.136

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 +43 -12
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -3303,8 +3303,10 @@ __export(wikilinks_exports, {
3303
3303
  initializeEntityIndex: () => initializeEntityIndex,
3304
3304
  isEntityIndexReady: () => isEntityIndexReady,
3305
3305
  isLikelyArticleTitle: () => isLikelyArticleTitle,
3306
+ isValidWikilinkText: () => isValidWikilinkText,
3306
3307
  maybeApplyWikilinks: () => maybeApplyWikilinks,
3307
3308
  processWikilinks: () => processWikilinks,
3309
+ sanitizeWikilinks: () => sanitizeWikilinks,
3308
3310
  setCooccurrenceIndex: () => setCooccurrenceIndex,
3309
3311
  setWikilinkConfig: () => setWikilinkConfig,
3310
3312
  setWriteStateDb: () => setWriteStateDb,
@@ -3473,6 +3475,35 @@ function sortEntitiesByPriority(entities, notePath) {
3473
3475
  return priorityB - priorityA;
3474
3476
  });
3475
3477
  }
3478
+ function isValidWikilinkText(text) {
3479
+ const target = text.includes("|") ? text.split("|")[0] : text;
3480
+ if (target !== target.trim()) return false;
3481
+ const trimmed = target.trim();
3482
+ if (trimmed.length === 0) return false;
3483
+ if (/[?!;]/.test(trimmed)) return false;
3484
+ if (/[,.]$/.test(trimmed)) return false;
3485
+ if (trimmed.includes(">")) return false;
3486
+ if (/^[*#\-]/.test(trimmed)) return false;
3487
+ if (trimmed.length > 60) return false;
3488
+ const words = trimmed.split(/\s+/);
3489
+ if (words.length > 5) return false;
3490
+ if (words.length > 3 && trimmed === trimmed.toLowerCase()) return false;
3491
+ if (words.length > 2 && /\w'\w/.test(trimmed)) return false;
3492
+ if (words.length > 3 && /(?:ing|tion|ment|ness|ould|ould|ight)$/i.test(words[words.length - 1])) return false;
3493
+ return true;
3494
+ }
3495
+ function sanitizeWikilinks(content) {
3496
+ const removed = [];
3497
+ const sanitized = content.replace(/\[\[([^\]]+?)\]\]/g, (fullMatch, inner) => {
3498
+ if (isValidWikilinkText(inner)) {
3499
+ return fullMatch;
3500
+ }
3501
+ removed.push(inner);
3502
+ const display = inner.includes("|") ? inner.split("|")[1] : inner;
3503
+ return display;
3504
+ });
3505
+ return { content: sanitized, removed };
3506
+ }
3476
3507
  function processWikilinks(content, notePath, existingContent) {
3477
3508
  if (!isEntityIndexReady() || !entityIndex) {
3478
3509
  return {
@@ -3514,8 +3545,9 @@ function processWikilinks(content, notePath, existingContent) {
3514
3545
  caseInsensitive: true,
3515
3546
  alreadyLinked: step1LinkedEntities
3516
3547
  });
3548
+ const wordCount = content.split(/\s+/).length;
3517
3549
  const cfg = getConfig();
3518
- const implicitEnabled = cfg?.implicit_detection !== false;
3550
+ const implicitEnabled = cfg?.implicit_detection !== false && wordCount <= 500;
3519
3551
  const validPatterns = new Set(ALL_IMPLICIT_PATTERNS);
3520
3552
  const implicitPatterns = cfg?.implicit_patterns?.length ? cfg.implicit_patterns.filter((p) => validPatterns.has(p)) : [...ALL_IMPLICIT_PATTERNS];
3521
3553
  const implicitMatches = detectImplicitEntities(result.content, {
@@ -3551,23 +3583,22 @@ function processWikilinks(content, notePath, existingContent) {
3551
3583
  }
3552
3584
  }
3553
3585
  newImplicits = nonOverlapping;
3586
+ let finalContent = result.content;
3587
+ let implicitEntities;
3554
3588
  if (newImplicits.length > 0) {
3555
- let processedContent = result.content;
3556
3589
  for (let i = newImplicits.length - 1; i >= 0; i--) {
3557
3590
  const m = newImplicits[i];
3558
- processedContent = processedContent.slice(0, m.start) + `[[${m.text}]]` + processedContent.slice(m.end);
3591
+ finalContent = finalContent.slice(0, m.start) + `[[${m.text}]]` + finalContent.slice(m.end);
3559
3592
  }
3560
- return {
3561
- content: processedContent,
3562
- linksAdded: resolved.linksAdded + result.linksAdded + newImplicits.length,
3563
- linkedEntities: [...resolved.linkedEntities, ...result.linkedEntities],
3564
- implicitEntities: newImplicits.map((m) => m.text)
3565
- };
3593
+ implicitEntities = newImplicits.map((m) => m.text);
3566
3594
  }
3595
+ const { content: sanitizedContent, removed } = sanitizeWikilinks(finalContent);
3596
+ const totalLinksAdded = resolved.linksAdded + result.linksAdded + (newImplicits.length - removed.length);
3567
3597
  return {
3568
- content: result.content,
3569
- linksAdded: resolved.linksAdded + result.linksAdded,
3570
- linkedEntities: [...resolved.linkedEntities, ...result.linkedEntities]
3598
+ content: sanitizedContent,
3599
+ linksAdded: Math.max(0, totalLinksAdded),
3600
+ linkedEntities: [...resolved.linkedEntities, ...result.linkedEntities],
3601
+ ...implicitEntities ? { implicitEntities } : {}
3571
3602
  };
3572
3603
  }
3573
3604
  function maybeApplyWikilinks(content, skipWikilinks, notePath, existingContent) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@velvetmonkey/flywheel-memory",
3
- "version": "2.0.135",
3
+ "version": "2.0.136",
4
4
  "description": "MCP server that gives Claude full read/write access to your Obsidian vault. Select from 69 tools for search, backlinks, graph queries, mutations, agent memory, and hybrid semantic search.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -53,7 +53,7 @@
53
53
  },
54
54
  "dependencies": {
55
55
  "@modelcontextprotocol/sdk": "^1.25.1",
56
- "@velvetmonkey/vault-core": "2.0.135",
56
+ "@velvetmonkey/vault-core": "^2.0.136",
57
57
  "better-sqlite3": "^11.0.0",
58
58
  "chokidar": "^4.0.0",
59
59
  "gray-matter": "^4.0.3",