@velvetmonkey/flywheel-memory 2.5.13 → 2.6.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.
Files changed (2) hide show
  1. package/dist/index.js +39 -7
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -4304,10 +4304,26 @@ async function suggestRelatedLinks(content, options = {}) {
4304
4304
  (token) => contentTokens.has(token) || contentStems.has(stem(token))
4305
4305
  );
4306
4306
  const strongCooccurrence = boost >= config2.minCooccurrenceGate;
4307
- if (!hasContentOverlap && !strongCooccurrence) {
4307
+ let multiSeedOK = true;
4308
+ if (!hasContentOverlap && cooccurrenceIndex && config2.minContentMatch > 0) {
4309
+ let qualifyingSeedCount = 0;
4310
+ for (const seed of cooccurrenceSeeds) {
4311
+ const entityAssocs = cooccurrenceIndex.associations[seed];
4312
+ if (!entityAssocs) continue;
4313
+ const coocCount = entityAssocs.get(entityName) || 0;
4314
+ if (coocCount < (cooccurrenceIndex.minCount ?? 2)) continue;
4315
+ const dfEntity = cooccurrenceIndex.documentFrequency?.get(entityName) || 0;
4316
+ const dfSeed = cooccurrenceIndex.documentFrequency?.get(seed) || 0;
4317
+ if (dfEntity === 0 || dfSeed === 0) continue;
4318
+ const npmi = computeNpmi(coocCount, dfEntity, dfSeed, cooccurrenceIndex.totalNotesScanned ?? 1);
4319
+ if (npmi > 0) qualifyingSeedCount++;
4320
+ }
4321
+ multiSeedOK = qualifyingSeedCount >= 2;
4322
+ }
4323
+ if (!hasContentOverlap && !(strongCooccurrence && multiSeedOK)) {
4308
4324
  continue;
4309
4325
  }
4310
- if (hasContentOverlap || strongCooccurrence) {
4326
+ if (hasContentOverlap || strongCooccurrence && multiSeedOK) {
4311
4327
  entitiesWithAnyScoringPath.add(entityName);
4312
4328
  }
4313
4329
  const typeBoost = disabled.has("type_boost") ? 0 : getTypeBoost(category, getConfig()?.custom_categories, entityName);
@@ -4853,8 +4869,8 @@ var init_wikilinks = __esm({
4853
4869
  minWordLength: 3,
4854
4870
  minSuggestionScore: 10,
4855
4871
  // Exact match (10) or two stem matches
4856
- minMatchRatio: 0.4,
4857
- // 40% of multi-word entity must match
4872
+ minMatchRatio: 0.6,
4873
+ // 60% of multi-word entity must match (blocks 1-of-2 token FPs)
4858
4874
  requireMultipleMatches: false,
4859
4875
  stemMatchBonus: 5,
4860
4876
  // Standard bonus for stem matches
@@ -4863,8 +4879,10 @@ var init_wikilinks = __esm({
4863
4879
  fuzzyMatchBonus: 4,
4864
4880
  // Moderate fuzzy bonus
4865
4881
  contentRelevanceFloor: 5,
4866
- noRelevanceCap: 10,
4867
- minCooccurrenceGate: 5,
4882
+ noRelevanceCap: 9,
4883
+ // Below minSuggestionScore — graph-only entities can't reach threshold
4884
+ minCooccurrenceGate: 6,
4885
+ // Stronger graph signal for graph-only admission
4868
4886
  minContentMatch: 2
4869
4887
  },
4870
4888
  aggressive: {
@@ -14764,6 +14782,7 @@ function registerGraphTools(server2, getIndex, getVaultPath, getStateDb4) {
14764
14782
  async ({ path: notePath }) => {
14765
14783
  requireIndex();
14766
14784
  const index = getIndex();
14785
+ const vaultPath2 = getVaultPath();
14767
14786
  let resolvedPath = notePath;
14768
14787
  if (!notePath.endsWith(".md")) {
14769
14788
  const resolved = resolveTarget(index, notePath);
@@ -14773,7 +14792,20 @@ function registerGraphTools(server2, getIndex, getVaultPath, getStateDb4) {
14773
14792
  resolvedPath = notePath + ".md";
14774
14793
  }
14775
14794
  }
14776
- const forwardLinks = getForwardLinksForNote(index, resolvedPath);
14795
+ let forwardLinks = getForwardLinksForNote(index, resolvedPath);
14796
+ if (forwardLinks.length === 0) {
14797
+ try {
14798
+ const absolutePath = path18.join(vaultPath2, resolvedPath);
14799
+ const result = await parseNoteWithWarnings({ path: resolvedPath, absolutePath, modified: /* @__PURE__ */ new Date() });
14800
+ if (!result.skipped) {
14801
+ forwardLinks = result.note.outlinks.map((link) => {
14802
+ const rp = resolveTarget(index, link.target);
14803
+ return { target: link.target, alias: link.alias, line: link.line, resolvedPath: rp, exists: rp !== void 0 };
14804
+ });
14805
+ }
14806
+ } catch {
14807
+ }
14808
+ }
14777
14809
  const output = {
14778
14810
  note: resolvedPath,
14779
14811
  forward_link_count: forwardLinks.length,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@velvetmonkey/flywheel-memory",
3
- "version": "2.5.13",
3
+ "version": "2.6.0",
4
4
  "description": "MCP tools that search, write, and auto-link your Obsidian vault — and learn from your edits.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -56,7 +56,7 @@
56
56
  "dependencies": {
57
57
  "@huggingface/transformers": "^3.8.1",
58
58
  "@modelcontextprotocol/sdk": "^1.26.0",
59
- "@velvetmonkey/vault-core": "^2.5.13",
59
+ "@velvetmonkey/vault-core": "^2.6.0",
60
60
  "better-sqlite3": "^12.0.0",
61
61
  "chokidar": "^4.0.0",
62
62
  "gray-matter": "^4.0.3",