@hraness/wordcell 0.22.4 → 0.23.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 (80) hide show
  1. package/README.md +63 -22
  2. package/dist/authoring.js +2 -2
  3. package/dist/{authority-06eqhqam.js → authority-35cbvp1x.js} +2 -2
  4. package/dist/benchmark.js +3 -3
  5. package/dist/capture.js +6 -6
  6. package/dist/cli.js +26 -24
  7. package/dist/clip/acquire.js +3 -3
  8. package/dist/clip/args.js +1 -1
  9. package/dist/clip/cli.js +6 -6
  10. package/dist/clip/doctor.js +4 -4
  11. package/dist/evaluation-builder.js +10 -10
  12. package/dist/evaluation-kb.js +10 -10
  13. package/dist/graph-authority.js +4 -4
  14. package/dist/graph-percolation.js +6 -6
  15. package/dist/graph.js +5 -1
  16. package/dist/{index-bcknqxrq.js → index-0eacgvpv.js} +9 -1
  17. package/dist/{index-xv80vzfp.js → index-1er88ckw.js} +4 -4
  18. package/dist/{index-233z9wmn.js → index-1s8mc4hz.js} +13 -5
  19. package/dist/{index-de2w8crk.js → index-2fmnttca.js} +82 -15
  20. package/dist/{index-vnybgywh.js → index-ahyhryb8.js} +1 -1
  21. package/dist/{index-ecqpgr5y.js → index-b0khj0h5.js} +164 -9
  22. package/dist/{index-jk1sgx36.js → index-bdwcjvr4.js} +1 -1
  23. package/dist/{index-dfag79p7.js → index-byz4kzww.js} +3 -0
  24. package/dist/{index-hgve9rh2.js → index-cd75vky9.js} +96 -1
  25. package/dist/{index-rr9kzq4n.js → index-fp732bgg.js} +2 -2
  26. package/dist/{index-qbssx940.js → index-jvb7w0gg.js} +227 -9
  27. package/dist/{index-f984hw45.js → index-k86wepd8.js} +1 -1
  28. package/dist/{index-1k595z6v.js → index-mnq2wy34.js} +2 -2
  29. package/dist/{index-n386evxj.js → index-nn67kzwa.js} +1 -1
  30. package/dist/{index-fc4dr114.js → index-nwrehke7.js} +1 -1
  31. package/dist/{index-76dbn6pe.js → index-p70t8j13.js} +1 -1
  32. package/dist/{index-tf4wzjew.js → index-pgtm2nhf.js} +1 -1
  33. package/dist/{index-054mb7d3.js → index-qfqjfxaa.js} +10 -9
  34. package/dist/{index-pnt9wr59.js → index-r0m5taz2.js} +2 -2
  35. package/dist/{index-bqtqeak3.js → index-r4qs6vq4.js} +4 -4
  36. package/dist/{index-djrcc1yf.js → index-tcaq1c7f.js} +4 -4
  37. package/dist/{index-q7tbg07z.js → index-tvd2sqrx.js} +7 -7
  38. package/dist/{index-9h7zh0wt.js → index-vtwbapjr.js} +3814 -162
  39. package/dist/{index-g5vsqmdy.js → index-xwxy71ew.js} +190 -4
  40. package/dist/index.js +22 -14
  41. package/dist/pdf.js +3 -3
  42. package/dist/percolate.js +2 -2
  43. package/dist/portfolio.js +10 -10
  44. package/dist/publish.js +3 -3
  45. package/dist/sdk.js +9 -9
  46. package/dist/search.js +2 -2
  47. package/dist/semantic.js +4 -4
  48. package/dist/{support-m3t917ev.js → support-c8hy7qfa.js} +25 -23
  49. package/dist/url-intelligence.js +1 -1
  50. package/dist/workflows/decision-context.js +10 -10
  51. package/dist/workflows/index.js +10 -10
  52. package/package.json +12 -4
  53. package/skills/wordcell/AGENTS.md +1 -0
  54. package/skills/wordcell/SKILL.md +5 -3
  55. package/skills/wordcell/references/plan-structure.md +1 -1
  56. package/skills/wordcell/references/query.md +35 -1
  57. package/skills/wordcell/references/session-memory.md +207 -0
  58. package/skills/wordcell/references/url-platforms.md +1 -1
  59. package/src/authoring-import.ts +32 -0
  60. package/src/authoring-model.ts +185 -2
  61. package/src/authoring-program.ts +19 -7
  62. package/src/cli-help.ts +893 -0
  63. package/src/cli-intro.ts +1 -1
  64. package/src/cli-program.ts +575 -82
  65. package/src/cli-style.ts +132 -0
  66. package/src/clip/acquire.ts +20 -0
  67. package/src/clip/args.ts +3 -0
  68. package/src/clip/cli.ts +82 -5
  69. package/src/clip/cookie-permission.ts +241 -0
  70. package/src/clip/url-metadata-cli.ts +6 -5
  71. package/src/clip/url-metadata.ts +14 -0
  72. package/src/graph.ts +88 -13
  73. package/src/import-supermemory.ts +1629 -0
  74. package/src/mcp-server.ts +415 -0
  75. package/src/mcp-tools.ts +1122 -0
  76. package/src/mention-index.ts +194 -0
  77. package/src/pdf/cli.ts +5 -4
  78. package/src/support-profile.ts +1 -1
  79. package/src/support.ts +3 -0
  80. package/src/vault.ts +26 -2
package/src/graph.ts CHANGED
@@ -9,6 +9,8 @@ import {
9
9
  type Node,
10
10
  } from "yaml";
11
11
  import { parseQualifiedDocumentUri, type QualifiedDocumentUri } from "./portfolio-identity.js";
12
+ import { cleanMentionBoundary, MentionIndexBudgetError, MentionMatcher, preflightMentionInput, type MentionIndexBudgetKind, type MentionIndexLimits } from "./mention-index.js";
13
+ export type { MentionIndexLimits } from "./mention-index.js";
12
14
 
13
15
  export const catalogStart = "<!-- kb:catalog:start -->";
14
16
  export const catalogEnd = "<!-- kb:catalog:end -->";
@@ -22,7 +24,8 @@ export type VaultAnalysisBudgetKind =
22
24
  | "notes"
23
25
  | "connection-observations"
24
26
  | "mention-pairs"
25
- | "mentions";
27
+ | "mentions"
28
+ | MentionIndexBudgetKind;
26
29
 
27
30
  /** A stable failure for callers that need to distinguish bounded graph work. */
28
31
  export class VaultAnalysisBudgetError extends RangeError {
@@ -214,6 +217,11 @@ export type AnalyzeVaultOptions = {
214
217
  catalogNoteId?: string;
215
218
  };
216
219
 
220
+ /** Complete discovery indexes phrases instead of enumerating every note pair. */
221
+ export type CompleteAnalyzeVaultOptions = AnalyzeVaultOptions & {
222
+ readonly mentionIndexLimits?: MentionIndexLimits;
223
+ };
224
+
217
225
  export type VaultAnalysis = {
218
226
  noteCount: number;
219
227
  contextualLinks: readonly ResolvedLink[];
@@ -240,6 +248,14 @@ type MetadataParseResult =
240
248
  | { readonly ok: true; readonly value: MetadataValue }
241
249
  | { readonly ok: false };
242
250
 
251
+ /**
252
+ * The vault's number rule for frontmatter values: finite, and a safe integer
253
+ * when integral. Writers share it so every note they write reads back.
254
+ */
255
+ export function isMetadataNumber(value: number): boolean {
256
+ return Number.isFinite(value) && (!Number.isInteger(value) || Number.isSafeInteger(value));
257
+ }
258
+
243
259
  function parsedMetadataValue(
244
260
  value: unknown,
245
261
  ancestors: WeakSet<object>,
@@ -248,10 +264,7 @@ function parsedMetadataValue(
248
264
  return { ok: true, value };
249
265
  }
250
266
  if (typeof value === "number") {
251
- return Number.isFinite(value)
252
- && (!Number.isInteger(value) || Number.isSafeInteger(value))
253
- ? { ok: true, value }
254
- : { ok: false };
267
+ return isMetadataNumber(value) ? { ok: true, value } : { ok: false };
255
268
  }
256
269
  if (typeof value !== "object" || ancestors.has(value)) return { ok: false };
257
270
 
@@ -1015,17 +1028,10 @@ function compareRelationIssues(left: RelationIssue, right: RelationIssue): numbe
1015
1028
  || targetComparison;
1016
1029
  }
1017
1030
 
1018
- const wordCharacter = (value: string): boolean => /[A-Za-z0-9]/.test(value);
1019
-
1020
1031
  function phraseOffset(lowerHaystack: string, lowerPhrase: string): number {
1021
1032
  let offset = lowerHaystack.indexOf(lowerPhrase);
1022
1033
  while (offset !== -1) {
1023
- const before = offset === 0 ? "" : lowerHaystack[offset - 1] ?? "";
1024
- const afterIndex = offset + lowerPhrase.length;
1025
- const after = afterIndex >= lowerHaystack.length ? "" : lowerHaystack[afterIndex] ?? "";
1026
- const startsClean = !wordCharacter(lowerPhrase[0] ?? "") || !wordCharacter(before);
1027
- const endsClean = !wordCharacter(lowerPhrase.at(-1) ?? "") || !wordCharacter(after);
1028
- if (startsClean && endsClean) return offset;
1034
+ if (cleanMentionBoundary(lowerHaystack, lowerPhrase, offset)) return offset;
1029
1035
  offset = lowerHaystack.indexOf(lowerPhrase, offset + 1);
1030
1036
  }
1031
1037
  return -1;
@@ -1093,6 +1099,33 @@ function checkedAnalysisLimit(
1093
1099
  export function analyzeVault(
1094
1100
  notes: readonly Note[],
1095
1101
  options: AnalyzeVaultOptions = {},
1102
+ ): VaultAnalysis {
1103
+ return analyzeVaultInternal(notes, options);
1104
+ }
1105
+
1106
+ /**
1107
+ * Return the same complete graph using a finite phrase index. maxMentionPairs
1108
+ * counts matching source/target candidates; index work and repeated matches
1109
+ * have separate aggregate limits. Exhaustion always throws, never truncates.
1110
+ */
1111
+ export function analyzeVaultComplete(
1112
+ notes: readonly Note[],
1113
+ options: CompleteAnalyzeVaultOptions = {},
1114
+ ): VaultAnalysis {
1115
+ try {
1116
+ return analyzeVaultInternal(notes, options, options.mentionIndexLimits ?? {});
1117
+ } catch (error: unknown) {
1118
+ if (error instanceof MentionIndexBudgetError) {
1119
+ throw new VaultAnalysisBudgetError(error.kind, error.limit, error.message);
1120
+ }
1121
+ throw error;
1122
+ }
1123
+ }
1124
+
1125
+ function analyzeVaultInternal(
1126
+ notes: readonly Note[],
1127
+ options: AnalyzeVaultOptions,
1128
+ mentionIndexLimits?: MentionIndexLimits,
1096
1129
  ): VaultAnalysis {
1097
1130
  const maxNotes = checkedAnalysisLimit(
1098
1131
  options.maxNotes,
@@ -1121,6 +1154,13 @@ export function analyzeVault(
1121
1154
  `Vault analysis exceeds the ${maxNotes} note limit.`,
1122
1155
  );
1123
1156
  }
1157
+ const mentionInput = mentionIndexLimits === undefined ? undefined : preflightMentionInput((function* () {
1158
+ for (const note of notes) {
1159
+ yield note.searchableText;
1160
+ yield note.title;
1161
+ yield* note.aliases;
1162
+ }
1163
+ })(), mentionIndexLimits);
1124
1164
  const catalogNoteId = withoutMarkdownExtension(
1125
1165
  normalizeVaultPath(options.catalogNoteId ?? "index"),
1126
1166
  );
@@ -1332,7 +1372,42 @@ export function analyzeVault(
1332
1372
  : uniquePhrasesByTarget(suggestionNotes);
1333
1373
  const mentions: MentionCandidate[] = [];
1334
1374
  let mentionPairs = 0;
1375
+ const matcher = mentionIndexLimits === undefined ? undefined : new MentionMatcher(
1376
+ [...phrasesByTarget].flatMap(([targetId, phrases]) => phrases.map((phrase, rank) => ({ ...phrase, targetId, rank }))),
1377
+ mentionIndexLimits,
1378
+ mentionInput!.rawInputCodeUnits,
1379
+ mentionInput!.work,
1380
+ );
1381
+ const targetsById = new Map<string, { readonly note: Note; readonly order: number }[]>();
1382
+ if (matcher !== undefined) suggestionNotes.forEach((note, order) => {
1383
+ const targets = targetsById.get(note.id) ?? [];
1384
+ targets.push({ note, order });
1385
+ targetsById.set(note.id, targets);
1386
+ });
1335
1387
  for (const source of suggestionNotes) {
1388
+ if (matcher !== undefined) {
1389
+ if (scopedMentionNotes.length === 0) continue;
1390
+ const found = matcher.scan(source.searchableText.toLocaleLowerCase("en-US"));
1391
+ const targets = [...found].flatMap(([id, match]) => (targetsById.get(id) ?? []).map((target) => ({ ...target, match })))
1392
+ .filter(({ note }) => source.id !== note.id && (options.mentionScope === undefined
1393
+ || scopedMentionIds.has(source.id) || scopedMentionIds.has(note.id)))
1394
+ .sort((left, right) => left.order - right.order);
1395
+ let newlineOffsets: readonly number[] | undefined;
1396
+ for (const { note: target, match } of targets) {
1397
+ if (mentionPairs >= maxMentionPairs) throw new VaultAnalysisBudgetError(
1398
+ "mention-pairs", maxMentionPairs, `Vault analysis exceeds the ${maxMentionPairs} mention-pair limit.`,
1399
+ );
1400
+ mentionPairs += 1;
1401
+ if (linkedPairs.has(pairKey(source.id, target.id))) continue;
1402
+ if (mentions.length >= maxMentions) throw new VaultAnalysisBudgetError(
1403
+ "mentions", maxMentions, `Vault analysis exceeds the ${maxMentions} mention limit.`,
1404
+ );
1405
+ newlineOffsets ??= matcher.newlineOffsets(source.searchableText);
1406
+ mentions.push({ source: source.path, target: target.path, phrase: match.pattern.phrase,
1407
+ line: matcher.lineAt(newlineOffsets, match.offset) });
1408
+ }
1409
+ continue;
1410
+ }
1336
1411
  const targets = options.mentionScope === undefined
1337
1412
  || scopedMentionIds.has(source.id)
1338
1413
  ? suggestionNotes