* Build a normalized TF-IDF vector for a note's tokens — the same weighting the * semantic engine (`semantic.ts`) uses per chunk, applied here at note level so * a pairwise cosine measures whole-note similarity. A normalized vector means * the dot product *is* the cosine.
(tokens: string[], idf: (term: string) => number)
| 151 | return KIND_RANK[a.kind] - KIND_RANK[b.kind]; |
| 152 | } |
| 153 | const pa = a.paths.join('\u0000'); |
| 154 | const pb = b.paths.join('\u0000'); |
| 155 | return pa.localeCompare(pb) || a.detail.localeCompare(b.detail); |
| 156 | }); |
| 157 | } |
| 158 | |
| 159 | /** |
| 160 | * Scan a corpus of notes for vault-hygiene problems and return a deterministic, |
| 161 | * stably-ordered list of findings. Pure: no I/O, no model, no mutation — the |
| 162 | * same documents always yield the same findings. |
| 163 | */ |
| 164 | export function scanVault( |
| 165 | documents: readonly MaintenanceDocument[], |
| 166 | options: ScanVaultOptions = {}, |
| 167 | ): MaintenanceFinding[] { |
| 168 | const duplicateThreshold = options.duplicateThreshold ?? DEFAULT_DUPLICATE_THRESHOLD; |
| 169 | const allPaths = documents.map((doc) => doc.path); |
| 170 | const pathSet = new Set(allPaths); |
| 171 | const contentByPath = new Map(documents.map((doc) => [doc.path, doc.content])); |
| 172 | |
| 173 | // --- Link analysis: resolved (real) out-targets, inbound sources, broken links. |
| 174 | const resolvedOut = new Map<string, Set<string>>(); |
| 175 | const inbound = new Map<string, Set<string>>(); |