@velvetmonkey/vault-core 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/wikilinks.js +41 -0
  2. package/package.json +1 -1
package/dist/wikilinks.js CHANGED
@@ -571,11 +571,18 @@ export function resolveAliasWikilinks(content, entities, options = {}) {
571
571
  // Key: alias (lowercase if caseInsensitive)
572
572
  // Value: { entityName: canonical name, aliasText: original alias casing }
573
573
  const aliasMap = new Map();
574
+ // Track ambiguous aliases (shared by multiple entities) — skip these to avoid wrong resolution
575
+ const ambiguousAliases = new Set();
574
576
  for (const entity of entities) {
575
577
  if (typeof entity === 'string')
576
578
  continue;
577
579
  for (const alias of entity.aliases) {
578
580
  const key = caseInsensitive ? alias.toLowerCase() : alias;
581
+ const existing = aliasMap.get(key);
582
+ if (existing && existing.entityName !== entity.name) {
583
+ // Two different entities claim this alias — mark as ambiguous
584
+ ambiguousAliases.add(key);
585
+ }
579
586
  aliasMap.set(key, { entityName: entity.name, aliasText: alias });
580
587
  }
581
588
  // Also map the entity name itself so we can detect if target already points to entity
@@ -611,6 +618,10 @@ export function resolveAliasWikilinks(content, entities, options = {}) {
611
618
  // Target doesn't match any alias or entity name - leave unchanged
612
619
  continue;
613
620
  }
621
+ // Skip ambiguous aliases — multiple entities claim this alias, resolution would be arbitrary
622
+ if (ambiguousAliases.has(targetKey)) {
623
+ continue;
624
+ }
614
625
  // Check if already pointing to the entity name (no resolution needed)
615
626
  const entityNameKey = caseInsensitive ? aliasInfo.entityName.toLowerCase() : aliasInfo.entityName;
616
627
  if (targetKey === entityNameKey) {
@@ -1116,6 +1127,36 @@ export function detectImplicitEntities(content, config = {}) {
1116
1127
  }
1117
1128
  }
1118
1129
  }
1130
+ // Guard: max 4 words — longer phrases are almost always prose, not entity names
1131
+ const wordCount = text.split(/\s+/).length;
1132
+ if (wordCount > 4)
1133
+ continue;
1134
+ // Guard: max 40 chars
1135
+ if (text.length > 40)
1136
+ continue;
1137
+ // Guard: strip trailing punctuation from match text
1138
+ const stripped = text.replace(/[,.:;!?]+$/, '');
1139
+ if (stripped.length < minEntityLength)
1140
+ continue;
1141
+ if (stripped !== text) {
1142
+ end = start + stripped.length;
1143
+ text = stripped;
1144
+ }
1145
+ // Guard: sentence-start capitalization — if match begins at start of line
1146
+ // (after list marker or newline), first word cap is positional, not semantic.
1147
+ // Require at least 2 capitalized words remaining after the first.
1148
+ if (start > 0) {
1149
+ const lineStart = content.lastIndexOf('\n', start - 1) + 1;
1150
+ const before = content.substring(lineStart, start).trim();
1151
+ // After list marker (- * >) or empty (line start), first cap is positional
1152
+ if (before === '' || /^[-*>]+$/.test(before) || /^\d+\.$/.test(before)) {
1153
+ // Already trimmed sentence starters above; this catches the remaining
1154
+ // cases where the first word is capitalized only because of its position
1155
+ const wordsArr = text.split(/\s+/);
1156
+ if (wordsArr.length <= 2)
1157
+ continue; // Too few words to trust positional cap
1158
+ }
1159
+ }
1119
1160
  if (!shouldExclude(text) && !isProtected(start, end)) {
1120
1161
  detected.push({ text, start, end, pattern: 'proper-nouns' });
1121
1162
  seenTexts.add(text.toLowerCase());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@velvetmonkey/vault-core",
3
- "version": "2.0.135",
3
+ "version": "2.0.136",
4
4
  "description": "Shared vault utilities for Flywheel ecosystem (entity scanning, wikilinks, protected zones)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",