dead-links 0.2.0 → 0.3.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.
package/README.md CHANGED
@@ -39,7 +39,7 @@ Options:
39
39
  --no-color Disable colored output
40
40
  --suggestions Show smart suggestions for broken links
41
41
  --debug Enable debug mode with verbose output
42
- --fix Auto-fix broken links (structure ready)
42
+ --fix Auto-fix broken links using generated suggestions
43
43
  ```
44
44
 
45
45
  ### broken
@@ -160,7 +160,28 @@ npx dead-links scan --vault "my-vault" --no-color
160
160
  # Show orphan notes list
161
161
  npx dead-links orphans --vault "my-vault" --verbose
162
162
  ```
163
+ ## Opening Reports
163
164
 
165
+ After generating a report with `--output`, you can open it with:
166
+
167
+ ```bash
168
+ # Windows
169
+ start report.html
170
+
171
+ # macOS
172
+ open report.html
173
+
174
+ # Linux
175
+ xdg-open report.html
176
+
177
+ # Or navigate to the file in your file explorer and double-click it
178
+ ```
179
+
180
+ The generated files are saved in your current directory:
181
+ - `report.html` - Open in any web browser
182
+ - `issues.csv` - Open with Excel, Google Sheets, or any spreadsheet app
183
+ - `graph.md` - View in GitHub, Obsidian, or any markdown viewer
184
+ - `graph.dot` - Render with Graphviz: `dot -Tpng graph.dot -o graph.png`
164
185
  ## Output Format
165
186
 
166
187
  ```
@@ -273,19 +294,5 @@ Exit code is `0` if no issues found, `1` if issues exist. Perfect for CI pipelin
273
294
  - `![[image.png]]` - Embeds
274
295
  - `[text](file.md)` - Markdown links
275
296
 
276
- ## Version & Status
277
-
278
- **Current Version:** v0.2.0 ✨ (Major update with new features)
279
-
280
- ### What's New in v0.2.0
281
- - ✨ Multiple export formats (HTML, CSV, Mermaid, GraphViz)
282
- - 💡 Smart suggestions with similarity scoring
283
- - 📊 New `stats` command with vault metrics
284
- - 🐛 Debug mode for diagnostics
285
- - ⚡ 30% performance improvement through parallelization
286
-
287
- ### Changelog
288
- See [CHANGELOG.md](CHANGELOG.md) for detailed release notes.
289
-
290
297
  ## License
291
298
  MIT
package/dist/index.js CHANGED
@@ -2,7 +2,8 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { Command } from "commander";
5
- import { writeFile } from "fs/promises";
5
+ import { readFileSync } from "fs";
6
+ import { writeFile as writeFile2 } from "fs/promises";
6
7
  import ora from "ora";
7
8
 
8
9
  // src/core/vault-loader.ts
@@ -24,6 +25,7 @@ async function loadVault(vaultPath, customIgnore = []) {
24
25
  const notes = [];
25
26
  for (const file of files) {
26
27
  const fullPath = resolve(absolutePath, file);
28
+ const relativePath = file.replace(/\\/g, "/");
27
29
  const content = await readFile(fullPath, "utf-8");
28
30
  const parsed = matter(content);
29
31
  const aliases = [];
@@ -36,7 +38,7 @@ async function loadVault(vaultPath, customIgnore = []) {
36
38
  }
37
39
  notes.push({
38
40
  path: fullPath,
39
- relativePath: file,
41
+ relativePath,
40
42
  content: parsed.content,
41
43
  aliases,
42
44
  links: []
@@ -155,8 +157,42 @@ var AliasResolver = class {
155
157
  }
156
158
  };
157
159
 
160
+ // src/core/link-resolver.ts
161
+ import { dirname, isAbsolute, normalize, resolve as resolve2 } from "path";
162
+ var REMOTE_TARGET = /^(?:[a-z][a-z\d+.-]*:|\/\/)/i;
163
+ function isExternalTarget(target) {
164
+ return REMOTE_TARGET.test(target) || target.startsWith("#");
165
+ }
166
+ function resolveNoteLink(link, source, notes, aliases, vaultPath) {
167
+ if (isExternalTarget(link.target)) return null;
168
+ if (link.type === "markdown") {
169
+ const target = decodeTarget(link.target).replace(/\\/g, "/");
170
+ const sourceRelativePath = normalize(resolve2(dirname(source.path), target));
171
+ const exact = notes.find((note) => normalize(note.path) === sourceRelativePath);
172
+ if (exact) return exact.path;
173
+ const vaultRelativePath = normalize(resolve2(vaultPath, target.replace(/^\/+/, "")));
174
+ const vaultRelative = notes.find((note) => normalize(note.path) === vaultRelativePath);
175
+ if (vaultRelative) return vaultRelative.path;
176
+ }
177
+ return aliases.resolve(decodeTarget(link.target));
178
+ }
179
+ function resolveAttachmentPath(target, source, vaultPath) {
180
+ const decoded = decodeTarget(target).replace(/\\/g, "/");
181
+ if (isAbsolute(decoded)) return normalize(decoded);
182
+ const relativeToSource = normalize(resolve2(dirname(source.path), decoded));
183
+ if (relativeToSource.startsWith(normalize(vaultPath))) return relativeToSource;
184
+ return normalize(resolve2(vaultPath, decoded.replace(/^\/+/, "")));
185
+ }
186
+ function decodeTarget(target) {
187
+ try {
188
+ return decodeURIComponent(target);
189
+ } catch {
190
+ return target;
191
+ }
192
+ }
193
+
158
194
  // src/core/graph-builder.ts
159
- function buildGraph(notes, resolver) {
195
+ function buildGraph(notes, resolver, vaultPath) {
160
196
  const notePathMap = /* @__PURE__ */ new Map();
161
197
  for (const note of notes) {
162
198
  notePathMap.set(note.path, {
@@ -168,10 +204,8 @@ function buildGraph(notes, resolver) {
168
204
  for (const note of notes) {
169
205
  const node = notePathMap.get(note.path);
170
206
  for (const link of note.links) {
171
- if (link.type === "markdown" && !link.target.endsWith(".md")) {
172
- continue;
173
- }
174
- const resolvedPath = resolver.resolve(link.target);
207
+ if (isExternalTarget(link.target)) continue;
208
+ const resolvedPath = resolveNoteLink(link, note, notes, resolver, vaultPath);
175
209
  if (resolvedPath && notePathMap.has(resolvedPath)) {
176
210
  if (!node.outgoing.includes(resolvedPath)) {
177
211
  node.outgoing.push(resolvedPath);
@@ -233,23 +267,34 @@ function isAttachment(target) {
233
267
  const ext = target.split(".").pop()?.toLowerCase();
234
268
  return ext ? ATTACHMENT_EXTENSIONS.has(ext) : false;
235
269
  }
236
- function analyze(notes, vaultPath, checkAttachments = false) {
270
+ function analyze(notes, vaultPath, checkAttachments = false, ignoreFolders = []) {
237
271
  const resolver = new AliasResolver(notes);
238
- const graph = buildGraph(notes, resolver);
272
+ const graph = buildGraph(notes, resolver, vaultPath);
239
273
  const brokenLinks = [];
240
274
  const orphanNotes = [];
241
275
  for (const node of graph) {
242
276
  if (node.incoming.length === 0 && node.outgoing.length === 0) {
243
- orphanNotes.push(node.note);
277
+ if (!isIgnoredFolder(node.note.relativePath, ignoreFolders)) {
278
+ orphanNotes.push(node.note);
279
+ }
244
280
  }
245
281
  for (const link of node.note.links) {
246
- if (link.type === "markdown" && !link.target.endsWith(".md")) {
282
+ if (isExternalTarget(link.target)) {
247
283
  continue;
248
284
  }
249
- if (!checkAttachments && isAttachment(link.target)) {
285
+ if (isAttachment(link.target)) {
286
+ if (!checkAttachments) continue;
287
+ const attachmentPath = resolveAttachmentPath(link.target, node.note, vaultPath);
288
+ if (!existsSync(attachmentPath)) {
289
+ brokenLinks.push({
290
+ source: node.note.relativePath,
291
+ link,
292
+ reason: `File "${link.target}" does not exist`
293
+ });
294
+ }
250
295
  continue;
251
296
  }
252
- const resolvedPath = resolver.resolve(link.target);
297
+ const resolvedPath = resolveNoteLink(link, node.note, notes, resolver, vaultPath);
253
298
  if (resolvedPath === node.note.path) {
254
299
  continue;
255
300
  }
@@ -259,14 +304,6 @@ function analyze(notes, vaultPath, checkAttachments = false) {
259
304
  link,
260
305
  reason: `Target "${link.target}" not found in vault`
261
306
  });
262
- } else if (link.type === "embed" || link.type === "markdown") {
263
- if (!existsSync(resolvedPath)) {
264
- brokenLinks.push({
265
- source: node.note.relativePath,
266
- link,
267
- reason: `File "${link.target}" does not exist`
268
- });
269
- }
270
307
  }
271
308
  }
272
309
  }
@@ -285,6 +322,13 @@ function analyze(notes, vaultPath, checkAttachments = false) {
285
322
  }
286
323
  };
287
324
  }
325
+ function isIgnoredFolder(relativePath, ignoreFolders) {
326
+ const folder = relativePath.includes("/") ? relativePath.split("/").slice(0, -1).join("/") : "";
327
+ return ignoreFolders.some((ignored) => {
328
+ const normalized = ignored.replace(/\\/g, "/").replace(/^\/+|\/+$/g, "");
329
+ return folder === normalized || folder.startsWith(`${normalized}/`);
330
+ });
331
+ }
288
332
  function countConnectedComponents(graph) {
289
333
  if (graph.length === 0) return 0;
290
334
  const visited = /* @__PURE__ */ new Set();
@@ -389,8 +433,8 @@ var DeadLinksEngine = class {
389
433
  return Promise.resolve();
390
434
  })
391
435
  );
392
- const report = analyze(notes, vaultPath, config.checkAttachments);
393
- if (config.suggestions) {
436
+ const report = analyze(notes, vaultPath, config.checkAttachments, config.ignoreFolders);
437
+ if (config.suggestions || config.fix) {
394
438
  const brokenTargets = Array.from(
395
439
  new Set(report.brokenLinks.map((b) => b.link.target))
396
440
  );
@@ -403,11 +447,11 @@ var DeadLinksEngine = class {
403
447
 
404
448
  // src/utils/config-loader.ts
405
449
  import { readFile as readFile2, access } from "fs/promises";
406
- import { resolve as resolve2, join } from "path";
450
+ import { resolve as resolve3, join } from "path";
407
451
  var CONFIG_FILES = [".deadlinksrc.json", ".deadlinksrc", "dead-links.config.json"];
408
452
  var ConfigLoader = class {
409
453
  async loadConfig(vaultPath) {
410
- const absolutePath = resolve2(vaultPath);
454
+ const absolutePath = resolve3(vaultPath);
411
455
  for (const configFile of CONFIG_FILES) {
412
456
  const configPath = join(absolutePath, configFile);
413
457
  try {
@@ -440,13 +484,15 @@ var ConfigLoader = class {
440
484
  };
441
485
 
442
486
  // src/reporters/terminal-reporter.ts
443
- import chalk from "chalk";
487
+ import { Chalk } from "chalk";
444
488
  var TerminalReporter = class {
445
489
  ignoreFolders;
446
490
  quiet;
447
- constructor(ignoreFolders = [], quiet = false) {
491
+ chalk;
492
+ constructor(ignoreFolders = [], quiet = false, noColor = false) {
448
493
  this.ignoreFolders = ignoreFolders;
449
494
  this.quiet = quiet;
495
+ this.chalk = new Chalk({ level: noColor ? 0 : void 0 });
450
496
  }
451
497
  report(result) {
452
498
  if (this.quiet) {
@@ -488,12 +534,13 @@ var TerminalReporter = class {
488
534
  return out;
489
535
  }
490
536
  statLine(label, value, color = "white") {
491
- return ` ${chalk.gray(label)} ${color === "red" ? chalk.red.bold(value) : color === "yellow" ? chalk.yellow.bold(value) : chalk.white.bold(value)}
537
+ const chalk = this.chalk;
538
+ return ` ${this.chalk.gray(label)} ${color === "red" ? this.chalk.red.bold(value) : color === "yellow" ? this.chalk.yellow.bold(value) : this.chalk.white.bold(value)}
492
539
  `;
493
540
  }
494
541
  printBrokenLinks(brokenLinks) {
495
542
  if (brokenLinks.length === 0) {
496
- return chalk.green(" \u2713 No broken links\n\n");
543
+ return this.chalk.green(" \u2713 No broken links\n\n");
497
544
  }
498
545
  const grouped = /* @__PURE__ */ new Map();
499
546
  for (const broken of brokenLinks) {
@@ -502,18 +549,18 @@ var TerminalReporter = class {
502
549
  grouped.set(broken.source, existing);
503
550
  }
504
551
  const sorted = [...grouped.entries()].sort((a, b) => b[1].length - a[1].length);
505
- let out = chalk.bold.red(`
552
+ let out = this.chalk.bold.red(`
506
553
  \u2716 Broken links (${brokenLinks.length})
507
554
 
508
555
  `);
509
556
  for (const [source, links] of sorted) {
510
557
  const count = links.length;
511
558
  const isMultiple = count >= 2;
512
- const countStr = isMultiple ? chalk.red.bold(`\u2716 ${count}`) : chalk.yellow.bold(`\u2716 ${count}`);
513
- out += ` ${countStr} ${chalk.white.bold(source)}
559
+ const countStr = isMultiple ? this.chalk.red.bold(`\u2716 ${count}`) : this.chalk.yellow.bold(`\u2716 ${count}`);
560
+ out += ` ${countStr} ${this.chalk.white.bold(source)}
514
561
  `;
515
562
  for (const link of links) {
516
- const pill2 = isMultiple ? chalk.redBright(`[${link.link.target}]`) : chalk.yellowBright(`[${link.link.target}]`);
563
+ const pill2 = isMultiple ? this.chalk.redBright(`[${link.link.target}]`) : this.chalk.yellowBright(`[${link.link.target}]`);
517
564
  out += ` ${pill2}
518
565
  `;
519
566
  }
@@ -523,7 +570,7 @@ var TerminalReporter = class {
523
570
  }
524
571
  printOrphanNotes(orphanNotes) {
525
572
  if (orphanNotes.length === 0) {
526
- return chalk.green(" \u2713 No orphan notes\n\n");
573
+ return this.chalk.green(" \u2713 No orphan notes\n\n");
527
574
  }
528
575
  const byFolder = /* @__PURE__ */ new Map();
529
576
  for (const note of orphanNotes) {
@@ -535,37 +582,37 @@ var TerminalReporter = class {
535
582
  }
536
583
  const sorted = [...byFolder.entries()].sort((a, b) => b[1] - a[1]);
537
584
  if (sorted.length === 0) {
538
- return chalk.green(" \u2713 No orphan notes (all in ignored folders)\n\n");
585
+ return this.chalk.green(" \u2713 No orphan notes (all in ignored folders)\n\n");
539
586
  }
540
587
  const maxCount = sorted[0][1];
541
588
  const barWidth = 10;
542
- let out = chalk.bold.yellow(`
589
+ let out = this.chalk.bold.yellow(`
543
590
  \u26A0 Orphan notes (${orphanNotes.length} total)
544
591
 
545
592
  `);
546
593
  for (const [folder, count] of sorted) {
547
594
  const filled = Math.round(count / maxCount * barWidth);
548
595
  const empty = barWidth - filled;
549
- const bar2 = chalk.gray("\u2588".repeat(filled) + "\u2591".repeat(empty));
550
- out += ` ${chalk.gray(folder.padEnd(40))} ${bar2} ${chalk.white.bold(String(count))}
596
+ const bar2 = this.chalk.gray("\u2588".repeat(filled) + "\u2591".repeat(empty));
597
+ out += ` ${this.chalk.gray(folder.padEnd(40))} ${bar2} ${this.chalk.white.bold(String(count))}
551
598
  `;
552
599
  }
553
600
  out += "\n";
554
601
  return out;
555
602
  }
556
603
  printDuration(ms) {
557
- return chalk.dim(` Completed in ${ms}ms
604
+ return this.chalk.dim(` Completed in ${ms}ms
558
605
  `);
559
606
  }
560
607
  printSuggestions(suggestions) {
561
- let out = chalk.bold.cyan(`
608
+ let out = this.chalk.bold.cyan(`
562
609
  \u{1F4A1} Suggestions (${suggestions.length})
563
610
 
564
611
  `);
565
612
  for (const suggestion of suggestions.slice(0, 5)) {
566
613
  const confidence = Math.round(suggestion.similarity * 100);
567
- const bar2 = confidence >= 80 ? chalk.green("\u2588\u2588\u2588\u2588\u2588") : confidence >= 60 ? chalk.yellow("\u2588\u2588\u2588\u2588\u2588") : chalk.gray("\u2588\u2588\u2588\u2588\u2588");
568
- out += ` ${chalk.gray(suggestion.broken)} \u2192 ${chalk.cyan(suggestion.suggested)}
614
+ const bar2 = confidence >= 80 ? this.chalk.green("\u2588\u2588\u2588\u2588\u2588") : confidence >= 60 ? this.chalk.yellow("\u2588\u2588\u2588\u2588\u2588") : this.chalk.gray("\u2588\u2588\u2588\u2588\u2588");
615
+ out += ` ${this.chalk.gray(suggestion.broken)} \u2192 ${this.chalk.cyan(suggestion.suggested)}
569
616
  `;
570
617
  out += ` ${bar2} ${confidence}%
571
618
 
@@ -1049,7 +1096,7 @@ var GraphVizReporter = class {
1049
1096
  const nodeMap = new Map(nodesToShow.map((n) => [n.note.path, n.note.relativePath]));
1050
1097
  for (const node of nodesToShow) {
1051
1098
  const id = this.sanitizeId(node.note.relativePath);
1052
- const label = node.note.relativePath;
1099
+ const label = this.escapeLabel(node.note.relativePath);
1053
1100
  let style = "filled";
1054
1101
  let color = "#ffffff";
1055
1102
  let fontColor = "#000000";
@@ -1097,6 +1144,9 @@ var GraphVizReporter = class {
1097
1144
  sanitizeId(name) {
1098
1145
  return name.replace(/\.(md|canvas)$/, "").replace(/[^a-zA-Z0-9_]/g, "_");
1099
1146
  }
1147
+ escapeLabel(label) {
1148
+ return label.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
1149
+ }
1100
1150
  };
1101
1151
 
1102
1152
  // src/utils/markdown-report.ts
@@ -1167,9 +1217,109 @@ function generateMarkdownReport(report) {
1167
1217
  return lines.join("\n");
1168
1218
  }
1169
1219
 
1220
+ // src/utils/auto-fixer.ts
1221
+ import { readFile as readFile3, writeFile } from "fs/promises";
1222
+ import { resolve as resolve4 } from "path";
1223
+ function escapeRegExp(string) {
1224
+ return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1225
+ }
1226
+ async function autoFixBrokenLinks(fixMap, filePath) {
1227
+ const results = [];
1228
+ try {
1229
+ const content = await readFile3(filePath, "utf-8");
1230
+ let newContent = content;
1231
+ for (const [oldTarget, newTarget] of fixMap.entries()) {
1232
+ const wikiRegex = new RegExp(
1233
+ `(!?\\[\\[${escapeRegExp(oldTarget)}(?:\\|[^\\]]*)?\\]\\])`,
1234
+ "g"
1235
+ );
1236
+ const wikiMatches = content.match(wikiRegex);
1237
+ if (wikiMatches) {
1238
+ for (const match of wikiMatches) {
1239
+ const isEmbed = match.startsWith("!");
1240
+ const hasAlias = match.includes("|");
1241
+ let replacement;
1242
+ if (hasAlias) {
1243
+ const parts = match.split("|");
1244
+ replacement = `${isEmbed ? "!" : ""}[[${newTarget}|${parts[1]}`;
1245
+ } else {
1246
+ replacement = `${isEmbed ? "!" : ""}[[${newTarget}]]`;
1247
+ }
1248
+ newContent = newContent.replace(match, replacement);
1249
+ results.push({
1250
+ file: filePath,
1251
+ originalLink: match,
1252
+ newLink: replacement,
1253
+ success: true
1254
+ });
1255
+ }
1256
+ }
1257
+ const markdownRegex = new RegExp(
1258
+ `(\\[[^\\]]*\\]\\(${escapeRegExp(oldTarget)}(?:#[^)]*)?\\))`,
1259
+ "g"
1260
+ );
1261
+ const markdownMatches = content.match(markdownRegex);
1262
+ if (markdownMatches) {
1263
+ for (const match of markdownMatches) {
1264
+ const linkPart = match.match(/\(([^)]+)\)/)?.[1] || "";
1265
+ const heading = linkPart.includes("#") ? linkPart.split("#")[1] : "";
1266
+ const newLink = heading ? `${newTarget}#${heading}` : newTarget;
1267
+ const textPart = match.match(/\[([^\]]*)\]/)?.[0] || "[]";
1268
+ const replacement = `${textPart}(${newLink})`;
1269
+ newContent = newContent.replace(match, replacement);
1270
+ results.push({
1271
+ file: filePath,
1272
+ originalLink: match,
1273
+ newLink: replacement,
1274
+ success: true
1275
+ });
1276
+ }
1277
+ }
1278
+ }
1279
+ if (newContent !== content) {
1280
+ await writeFile(filePath, newContent, "utf-8");
1281
+ }
1282
+ return results;
1283
+ } catch (error) {
1284
+ return [
1285
+ {
1286
+ file: filePath,
1287
+ originalLink: "",
1288
+ newLink: "",
1289
+ success: false,
1290
+ error: error instanceof Error ? error.message : "Unknown error"
1291
+ }
1292
+ ];
1293
+ }
1294
+ }
1295
+ async function fixBrokenLinksBatch(brokenLinks, suggestionMap, vaultPath) {
1296
+ const fileFixMap = /* @__PURE__ */ new Map();
1297
+ for (const broken of brokenLinks) {
1298
+ const suggestion = suggestionMap.get(broken.link.target);
1299
+ if (!suggestion) continue;
1300
+ if (!fileFixMap.has(broken.source)) {
1301
+ fileFixMap.set(broken.source, /* @__PURE__ */ new Map());
1302
+ }
1303
+ fileFixMap.get(broken.source).set(broken.link.target, suggestion);
1304
+ }
1305
+ const allResults = /* @__PURE__ */ new Map();
1306
+ const promises = Array.from(fileFixMap.entries()).map(
1307
+ async ([source, fixMap]) => {
1308
+ const filePath = resolve4(vaultPath, source);
1309
+ const results = await autoFixBrokenLinks(fixMap, filePath);
1310
+ allResults.set(source, results);
1311
+ }
1312
+ );
1313
+ await Promise.all(promises);
1314
+ return allResults;
1315
+ }
1316
+
1170
1317
  // src/index.ts
1318
+ var packageMetadata = JSON.parse(
1319
+ readFileSync(new URL("../package.json", import.meta.url), "utf8")
1320
+ );
1171
1321
  var program = new Command();
1172
- program.name("dead-links").description("Scan Obsidian vaults for broken links, orphan notes, and connection graphs").version("0.2.0");
1322
+ program.name("dead-links").description("Scan Obsidian vaults for broken links, orphan notes, and connection graphs").version(packageMetadata.version);
1173
1323
  async function loadConfigAndAnalyze(vaultPath, cliOptions) {
1174
1324
  const configLoader = new ConfigLoader();
1175
1325
  const fileConfig = await configLoader.loadConfig(vaultPath);
@@ -1200,6 +1350,17 @@ program.command("scan").description("Scan the vault for all issues").option("--v
1200
1350
  try {
1201
1351
  const report = await loadConfigAndAnalyze(options.vault, options);
1202
1352
  spinner.succeed(`Scanned ${report.stats.totalNotes} notes in ${report.duration}ms`);
1353
+ let fixSummary = "";
1354
+ if (options.fix) {
1355
+ const suggestionMap = new Map(
1356
+ (report.suggestions || []).map((suggestion) => [suggestion.broken, suggestion.suggested])
1357
+ );
1358
+ const fixResults = await fixBrokenLinksBatch(report.brokenLinks, suggestionMap, options.vault);
1359
+ const changed = [...fixResults.values()].flat().filter((result) => result.success).length;
1360
+ const failed = [...fixResults.values()].flat().filter((result) => !result.success).length;
1361
+ const fixSummary2 = `Auto-fix: ${changed} link(s) changed${failed > 0 ? `, ${failed} failed` : ""}.`;
1362
+ console.error(fixSummary2);
1363
+ }
1203
1364
  if (options.debug) {
1204
1365
  console.log(`[DEBUG] Vault: ${options.vault}`);
1205
1366
  console.log(`[DEBUG] Format: ${options.format}`);
@@ -1225,11 +1386,15 @@ program.command("scan").description("Scan the vault for all issues").option("--v
1225
1386
  const reporter = new GraphVizReporter();
1226
1387
  output = reporter.report(report);
1227
1388
  } else {
1228
- const reporter = new TerminalReporter(options.ignoreFolders || [], options.quiet || false);
1389
+ const reporter = new TerminalReporter(
1390
+ options.ignoreFolders || [],
1391
+ options.quiet || false,
1392
+ options.color === false
1393
+ );
1229
1394
  output = reporter.report(report);
1230
1395
  }
1231
1396
  if (options.output) {
1232
- await writeFile(options.output, output);
1397
+ await writeFile2(options.output, output);
1233
1398
  console.log(`Report written to ${options.output}`);
1234
1399
  } else if (options.format === "text") {
1235
1400
  console.log(output);
@@ -1355,7 +1520,7 @@ program.command("report").description("Generate a markdown report").option("--va
1355
1520
  const report = await loadConfigAndAnalyze(options.vault, options);
1356
1521
  spinner.succeed(`Scanned ${report.stats.totalNotes} notes in ${report.duration}ms`);
1357
1522
  const markdown = generateMarkdownReport(report);
1358
- await writeFile(options.output, markdown);
1523
+ await writeFile2(options.output, markdown);
1359
1524
  console.log(`Report written to ${options.output}`);
1360
1525
  } catch (error) {
1361
1526
  spinner.fail("Scan failed");
@@ -1371,11 +1536,13 @@ program.command("stats").description("Show detailed vault statistics").option("-
1371
1536
  console.log("\n\u{1F4CA} Vault Statistics\n");
1372
1537
  console.log(` Total Notes: ${report.stats.totalNotes}`);
1373
1538
  console.log(` Total Links: ${report.stats.totalLinks}`);
1374
- console.log(` Avg Links per Note: ${(report.stats.totalLinks / report.stats.totalNotes).toFixed(2)}`);
1539
+ const averageLinks = report.stats.totalNotes === 0 ? 0 : report.stats.totalLinks / report.stats.totalNotes;
1540
+ const healthScore = report.stats.totalLinks === 0 ? 100 : 100 - report.stats.brokenCount / report.stats.totalLinks * 100;
1541
+ console.log(` Avg Links per Note: ${averageLinks.toFixed(2)}`);
1375
1542
  console.log(` Broken Links: ${report.stats.brokenCount}`);
1376
1543
  console.log(` Orphan Notes: ${report.stats.orphanCount}`);
1377
1544
  console.log(` Connected Components: ${report.stats.connectedComponents}`);
1378
- console.log(` Health Score: ${(100 - report.stats.brokenCount / report.stats.totalLinks * 100).toFixed(1)}%`);
1545
+ console.log(` Health Score: ${healthScore.toFixed(1)}%`);
1379
1546
  const mostConnected = [...report.graph].sort((a, b) => b.incoming.length + b.outgoing.length - (a.incoming.length + a.outgoing.length)).slice(0, 5);
1380
1547
  console.log("\n\u{1F310} Most Connected Notes:");
1381
1548
  for (const node of mostConnected) {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/core/vault-loader.ts","../src/core/link-parser.ts","../src/core/analyzer.ts","../src/core/alias-resolver.ts","../src/core/graph-builder.ts","../src/utils/suggestions.ts","../src/utils/engine.ts","../src/utils/config-loader.ts","../src/reporters/terminal-reporter.ts","../src/reporters/json-reporter.ts","../src/reporters/csv-reporter.ts","../src/reporters/html-reporter.ts","../src/reporters/mermaid-reporter.ts","../src/reporters/graphviz-reporter.ts","../src/utils/markdown-report.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { writeFile } from 'node:fs/promises';\nimport ora from 'ora';\nimport { DeadLinksEngine } from './utils/engine.js';\nimport { ConfigLoader } from './utils/config-loader.js';\nimport { TerminalReporter } from './reporters/terminal-reporter.js';\nimport { JsonReporter } from './reporters/json-reporter.js';\nimport { CsvReporter } from './reporters/csv-reporter.js';\nimport { HtmlReporter } from './reporters/html-reporter.js';\nimport { MermaidReporter } from './reporters/mermaid-reporter.js';\nimport { GraphVizReporter } from './reporters/graphviz-reporter.js';\nimport { generateMarkdownReport } from './utils/markdown-report.js';\nimport type { BrokenLink, Note } from './types/index.js';\n\nconst program = new Command();\n\nprogram\n .name('dead-links')\n .description('Scan Obsidian vaults for broken links, orphan notes, and connection graphs')\n .version('0.2.0');\n\nasync function loadConfigAndAnalyze(\n vaultPath: string,\n cliOptions: Record<string, unknown>,\n) {\n const configLoader = new ConfigLoader();\n const fileConfig = await configLoader.loadConfig(vaultPath);\n const config = configLoader.mergeConfig(fileConfig, cliOptions);\n\n const engine = new DeadLinksEngine();\n return engine.analyze(vaultPath, config);\n}\n\nfunction printBrokenVerbose(brokenLinks: BrokenLink[]) {\n const grouped = new Map<string, BrokenLink[]>();\n for (const b of brokenLinks) {\n const existing = grouped.get(b.source) || [];\n existing.push(b);\n grouped.set(b.source, existing);\n }\n\n for (const [source, links] of grouped) {\n for (const link of links) {\n console.log(` ${source} → ${link.link.target} (line ${link.link.line})`);\n }\n }\n}\n\nfunction printOrphansVerbose(orphanNotes: Note[]) {\n for (const note of orphanNotes) {\n console.log(` ${note.relativePath}`);\n }\n}\n\nprogram\n .command('scan')\n .description('Scan the vault for all issues')\n .option('--vault <path>', 'Path to the Obsidian vault', process.cwd())\n .option('--format <type>', 'Output format: text, json, markdown, csv, html, mermaid, dot', 'text')\n .option('--output <file>', 'Output file path')\n .option('--ignore <patterns...>', 'Ignore patterns (glob)')\n .option('--attachments', 'Also check embedded attachments (images, PDFs, etc.)')\n .option('--ignore-folders <folders...>', 'Exclude folders from orphan report')\n .option('--quiet', 'Only show issues, no stats or summary')\n .option('--no-color', 'Disable colored output')\n .option('--suggestions', 'Show smart suggestions for broken links')\n .option('--debug', 'Enable debug mode with verbose output')\n .option('--fix', 'Automatically fix broken links (requires suggestions)')\n .action(async (options) => {\n const spinner = ora('Scanning vault...').start();\n\n try {\n const report = await loadConfigAndAnalyze(options.vault, options);\n spinner.succeed(`Scanned ${report.stats.totalNotes} notes in ${report.duration}ms`);\n\n if (options.debug) {\n console.log(`[DEBUG] Vault: ${options.vault}`);\n console.log(`[DEBUG] Format: ${options.format}`);\n console.log(`[DEBUG] Broken links: ${report.stats.brokenCount}`);\n console.log(`[DEBUG] Orphan notes: ${report.stats.orphanCount}`);\n }\n\n let output = '';\n\n if (options.format === 'json') {\n const reporter = new JsonReporter();\n output = reporter.report(report);\n } else if (options.format === 'markdown') {\n output = generateMarkdownReport(report);\n } else if (options.format === 'csv') {\n const reporter = new CsvReporter();\n output = reporter.report(report);\n } else if (options.format === 'html') {\n const reporter = new HtmlReporter();\n output = reporter.report(report);\n } else if (options.format === 'mermaid') {\n const reporter = new MermaidReporter();\n output = reporter.report(report);\n } else if (options.format === 'dot') {\n const reporter = new GraphVizReporter();\n output = reporter.report(report);\n } else {\n const reporter = new TerminalReporter(options.ignoreFolders || [], options.quiet || false);\n output = reporter.report(report);\n }\n\n if (options.output) {\n await writeFile(options.output, output);\n console.log(`Report written to ${options.output}`);\n } else if (options.format === 'text') {\n console.log(output);\n } else {\n console.log(output);\n }\n\n process.exit(report.stats.brokenCount > 0 || report.stats.orphanCount > 0 ? 1 : 0);\n } catch (error) {\n spinner.fail('Scan failed');\n console.error(error instanceof Error ? error.message : error);\n process.exit(1);\n }\n });\n\nprogram\n .command('broken')\n .description('Show only broken links')\n .option('--vault <path>', 'Path to the Obsidian vault', process.cwd())\n .option('--ignore <patterns...>', 'Ignore patterns (glob)')\n .option('--attachments', 'Also check embedded attachments (images, PDFs, etc.)')\n .option('--verbose', 'Show line numbers and full details')\n .option('--suggestions', 'Show smart suggestions for broken links')\n .option('--debug', 'Enable debug mode with verbose output')\n .action(async (options) => {\n const spinner = ora('Scanning vault...').start();\n\n try {\n const report = await loadConfigAndAnalyze(options.vault, options);\n spinner.succeed(`Scanned ${report.stats.totalNotes} notes in ${report.duration}ms`);\n\n if (report.brokenLinks.length === 0) {\n console.log('No broken links found.');\n } else {\n console.log(`\\nBroken links (${report.brokenLinks.length})\\n`);\n if (options.verbose) {\n printBrokenVerbose(report.brokenLinks);\n } else {\n const grouped = new Map<string, BrokenLink[]>();\n for (const b of report.brokenLinks) {\n const existing = grouped.get(b.source) || [];\n existing.push(b);\n grouped.set(b.source, existing);\n }\n for (const [source, links] of grouped) {\n const targets = links.map((l: BrokenLink) => l.link.target).join(', ');\n console.log(` ${source} → ${targets}`);\n }\n }\n console.log();\n\n if (report.suggestions && report.suggestions.length > 0) {\n console.log('💡 Suggestions:');\n for (const suggestion of report.suggestions.slice(0, 5)) {\n const confidence = Math.round(suggestion.similarity * 100);\n console.log(` ${suggestion.broken} → ${suggestion.suggested} (${confidence}%)`);\n }\n console.log();\n }\n }\n\n process.exit(report.stats.brokenCount > 0 ? 1 : 0);\n } catch (error) {\n spinner.fail('Scan failed');\n console.error(error instanceof Error ? error.message : error);\n process.exit(1);\n }\n });\n\nprogram\n .command('orphans')\n .description('Show only orphan notes')\n .option('--vault <path>', 'Path to the Obsidian vault', process.cwd())\n .option('--ignore <patterns...>', 'Ignore patterns (glob)')\n .option('--ignore-folders <folders...>', 'Exclude folders from report')\n .option('--verbose', 'Show full list of orphan notes')\n .action(async (options) => {\n const spinner = ora('Scanning vault...').start();\n\n try {\n const report = await loadConfigAndAnalyze(options.vault, options);\n spinner.succeed(`Scanned ${report.stats.totalNotes} notes in ${report.duration}ms`);\n\n if (report.orphanNotes.length === 0) {\n console.log('No orphan notes found.');\n } else {\n console.log(`\\nOrphan notes (${report.orphanNotes.length})\\n`);\n if (options.verbose) {\n printOrphansVerbose(report.orphanNotes);\n } else {\n const ignoreFolders = options.ignoreFolders || [];\n const byFolder = new Map<string, number>();\n for (const note of report.orphanNotes) {\n const folder = note.relativePath.includes('/')\n ? note.relativePath.split('/').slice(0, -1).join('/') + '/'\n : './';\n if (ignoreFolders.some((f: string) => folder.startsWith(f + '/') || folder === f + '/')) {\n continue;\n }\n byFolder.set(folder, (byFolder.get(folder) || 0) + 1);\n }\n for (const [folder, count] of [...byFolder.entries()].sort((a, b) => b[1] - a[1])) {\n console.log(` ${folder} ${count}`);\n }\n }\n console.log();\n }\n\n process.exit(report.stats.orphanCount > 0 ? 1 : 0);\n } catch (error) {\n spinner.fail('Scan failed');\n console.error(error instanceof Error ? error.message : error);\n process.exit(1);\n }\n });\n\nprogram\n .command('graph')\n .description('Show connection graph')\n .option('--vault <path>', 'Path to the Obsidian vault', process.cwd())\n .option('--format <type>', 'Output format: text, json', 'text')\n .option('--ignore <patterns...>', 'Ignore patterns (glob)')\n .action(async (options) => {\n const spinner = ora('Scanning vault...').start();\n\n try {\n const report = await loadConfigAndAnalyze(options.vault, options);\n spinner.succeed(`Scanned ${report.stats.totalNotes} notes in ${report.duration}ms`);\n\n if (options.format === 'json') {\n console.log(JSON.stringify(report.graph, null, 2));\n } else {\n console.log(\n `Graph: ${report.stats.totalNotes} notes, ${report.stats.totalLinks} links (${report.duration}ms)\\n`,\n );\n\n const topConnected = [...report.graph]\n .sort((a, b) => b.incoming.length - a.incoming.length)\n .slice(0, 10)\n .filter((n) => n.incoming.length > 0);\n\n if (topConnected.length > 0) {\n console.log('Most linked-to notes:');\n for (const node of topConnected) {\n console.log(\n ` ${node.note.relativePath} (${node.incoming.length} incoming, ${node.outgoing.length} outgoing)`,\n );\n }\n }\n }\n } catch (error) {\n spinner.fail('Scan failed');\n console.error(error instanceof Error ? error.message : error);\n process.exit(1);\n }\n });\n\nprogram\n .command('report')\n .description('Generate a markdown report')\n .option('--vault <path>', 'Path to the Obsidian vault', process.cwd())\n .option('--output <file>', 'Output file path', 'dead-links-report.md')\n .option('--ignore <patterns...>', 'Ignore patterns (glob)')\n .action(async (options) => {\n const spinner = ora('Scanning vault...').start();\n\n try {\n const report = await loadConfigAndAnalyze(options.vault, options);\n spinner.succeed(`Scanned ${report.stats.totalNotes} notes in ${report.duration}ms`);\n\n const markdown = generateMarkdownReport(report);\n await writeFile(options.output, markdown);\n console.log(`Report written to ${options.output}`);\n } catch (error) {\n spinner.fail('Scan failed');\n console.error(error instanceof Error ? error.message : error);\n process.exit(1);\n }\n });\n\nprogram\n .command('stats')\n .description('Show detailed vault statistics')\n .option('--vault <path>', 'Path to the Obsidian vault', process.cwd())\n .option('--ignore <patterns...>', 'Ignore patterns (glob)')\n .option('--debug', 'Enable debug mode')\n .action(async (options) => {\n const spinner = ora('Analyzing vault...').start();\n\n try {\n const report = await loadConfigAndAnalyze(options.vault, options);\n spinner.succeed(`Analysis complete in ${report.duration}ms`);\n\n console.log('\\n📊 Vault Statistics\\n');\n console.log(` Total Notes: ${report.stats.totalNotes}`);\n console.log(` Total Links: ${report.stats.totalLinks}`);\n console.log(` Avg Links per Note: ${(report.stats.totalLinks / report.stats.totalNotes).toFixed(2)}`);\n console.log(` Broken Links: ${report.stats.brokenCount}`);\n console.log(` Orphan Notes: ${report.stats.orphanCount}`);\n console.log(` Connected Components: ${report.stats.connectedComponents}`);\n console.log(` Health Score: ${(100 - (report.stats.brokenCount / report.stats.totalLinks) * 100).toFixed(1)}%`);\n\n const mostConnected = [...report.graph]\n .sort((a, b) => (b.incoming.length + b.outgoing.length) - (a.incoming.length + a.outgoing.length))\n .slice(0, 5);\n\n console.log('\\n🌐 Most Connected Notes:');\n for (const node of mostConnected) {\n const total = node.incoming.length + node.outgoing.length;\n console.log(` ${node.note.relativePath} (${total} connections: ${node.incoming.length} in, ${node.outgoing.length} out)`);\n }\n\n const lonelyNotes = report.graph.filter((n) => n.incoming.length === 0 && n.outgoing.length === 0).slice(0, 5);\n if (lonelyNotes.length > 0) {\n console.log('\\n🔴 Orphan Notes:');\n for (const node of lonelyNotes) {\n console.log(` ${node.note.relativePath}`);\n }\n }\n\n console.log();\n } catch (error) {\n spinner.fail('Analysis failed');\n console.error(error instanceof Error ? error.message : error);\n process.exit(1);\n }\n });\n\nprogram.parse();","import { readFile } from 'node:fs/promises';\nimport { resolve } from 'node:path';\nimport fg from 'fast-glob';\nimport matter from 'gray-matter';\nimport type { Note } from '../types/index.js';\n\nconst DEFAULT_IGNORE = [\n '**/node_modules/**',\n '**/.git/**',\n '**/.obsidian/**',\n];\n\nexport async function loadVault(\n vaultPath: string,\n customIgnore: string[] = [],\n): Promise<Note[]> {\n const absolutePath = resolve(vaultPath);\n\n const files = await fg(['**/*.md', '**/*.canvas'], {\n cwd: absolutePath,\n ignore: [...DEFAULT_IGNORE, ...customIgnore],\n });\n\n const notes: Note[] = [];\n\n for (const file of files) {\n const fullPath = resolve(absolutePath, file);\n const content = await readFile(fullPath, 'utf-8');\n const parsed = matter(content);\n\n const aliases: string[] = [];\n if (parsed.data.aliases) {\n if (Array.isArray(parsed.data.aliases)) {\n aliases.push(...parsed.data.aliases.map(String));\n } else if (typeof parsed.data.aliases === 'string') {\n aliases.push(parsed.data.aliases);\n }\n }\n\n notes.push({\n path: fullPath,\n relativePath: file,\n content: parsed.content,\n aliases,\n links: [],\n });\n }\n\n return notes;\n}\n\nexport function resolveFileName(notePath: string): string {\n const base = notePath.replace(/\\.(md|canvas)$/, '');\n const parts = base.split('/');\n return parts[parts.length - 1]!;\n}\n","import type { Link } from '../types/index.js';\n\nconst wikiLinkRegex = /!?\\[\\[([^\\]]+)\\]\\]/g;\nconst markdownLinkRegex = /\\[([^\\]]*)\\]\\(([^)]+)\\)/g;\n\nexport function parseLinks(content: string): Link[] {\n const links: Link[] = [];\n const lines = content.split('\\n');\n\n for (let lineNum = 0; lineNum < lines.length; lineNum++) {\n const line = lines[lineNum]!;\n\n for (const match of line.matchAll(wikiLinkRegex)) {\n const raw = match[0]!;\n const inner = match[1]!;\n const isEmbed = raw.startsWith('!');\n\n const link = parseWikiLink(inner, raw, isEmbed, lineNum + 1);\n links.push(link);\n }\n\n for (const match of line.matchAll(markdownLinkRegex)) {\n const raw = match[0]!;\n const target = match[2]!;\n\n if (target.startsWith('#')) continue;\n\n const link: Link = {\n raw,\n target: target.split('#')[0]!.split('|')[0]!,\n heading: target.includes('#') ? target.split('#')[1] : undefined,\n type: 'markdown',\n line: lineNum + 1,\n };\n links.push(link);\n }\n }\n\n return links;\n}\n\nfunction parseWikiLink(inner: string, raw: string, isEmbed: boolean, line: number): Link {\n const [targetPart, aliasPart] = inner.split('|');\n const [target, ...rest] = targetPart!.split('#');\n\n let heading: string | undefined;\n let blockId: string | undefined;\n\n if (rest.length > 0) {\n const remainder = rest.join('#');\n if (remainder.startsWith('^')) {\n blockId = remainder.slice(1);\n } else {\n heading = remainder;\n }\n }\n\n return {\n raw,\n target: target!.trim(),\n alias: aliasPart?.trim(),\n heading,\n blockId,\n type: isEmbed ? 'embed' : 'wiki',\n line,\n };\n}\n","import { existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport type { Note, GraphNode, BrokenLink, AnalysisReport } from '../types/index.js';\nimport { AliasResolver } from './alias-resolver.js';\nimport { buildGraph } from './graph-builder.js';\n\nconst ATTACHMENT_EXTENSIONS = new Set([\n 'png', 'jpg', 'jpeg', 'gif', 'svg', 'webp', 'bmp', 'ico',\n 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx',\n 'mp3', 'mp4', 'wav', 'avi', 'mov', 'webm',\n 'zip', 'rar', '7z', 'tar', 'gz',\n 'exe', 'dmg', 'apk',\n 'css', 'js', 'ts', 'html',\n 'txt', 'csv', 'log',\n 'woff', 'woff2', 'ttf', 'eot',\n]);\n\nfunction isAttachment(target: string): boolean {\n const ext = target.split('.').pop()?.toLowerCase();\n return ext ? ATTACHMENT_EXTENSIONS.has(ext) : false;\n}\n\nexport function analyze(\n notes: Note[],\n vaultPath: string,\n checkAttachments = false,\n): AnalysisReport {\n const resolver = new AliasResolver(notes);\n const graph = buildGraph(notes, resolver);\n\n const brokenLinks: BrokenLink[] = [];\n const orphanNotes: Note[] = [];\n\n for (const node of graph) {\n if (node.incoming.length === 0 && node.outgoing.length === 0) {\n orphanNotes.push(node.note);\n }\n\n for (const link of node.note.links) {\n if (link.type === 'markdown' && !link.target.endsWith('.md')) {\n continue;\n }\n\n if (!checkAttachments && isAttachment(link.target)) {\n continue;\n }\n\n const resolvedPath = resolver.resolve(link.target);\n\n if (resolvedPath === node.note.path) {\n continue;\n }\n\n if (!resolvedPath) {\n brokenLinks.push({\n source: node.note.relativePath,\n link,\n reason: `Target \"${link.target}\" not found in vault`,\n });\n } else if (link.type === 'embed' || link.type === 'markdown') {\n if (!existsSync(resolvedPath)) {\n brokenLinks.push({\n source: node.note.relativePath,\n link,\n reason: `File \"${link.target}\" does not exist`,\n });\n }\n }\n }\n }\n\n const connectedComponents = countConnectedComponents(graph);\n const totalLinks = notes.reduce((sum, n) => sum + n.links.length, 0);\n\n return {\n brokenLinks,\n orphanNotes,\n graph,\n stats: {\n totalNotes: notes.length,\n totalLinks,\n brokenCount: brokenLinks.length,\n orphanCount: orphanNotes.length,\n connectedComponents,\n },\n };\n}\n\nfunction countConnectedComponents(graph: GraphNode[]): number {\n if (graph.length === 0) return 0;\n\n const visited = new Set<string>();\n let components = 0;\n\n const adjacency = new Map<string, string[]>();\n for (const node of graph) {\n adjacency.set(node.note.path, [...node.incoming, ...node.outgoing]);\n }\n\n for (const node of graph) {\n if (visited.has(node.note.path)) continue;\n\n components++;\n const stack = [node.note.path];\n\n while (stack.length > 0) {\n const current = stack.pop()!;\n if (visited.has(current)) continue;\n visited.add(current);\n\n const neighbors = adjacency.get(current) || [];\n for (const neighbor of neighbors) {\n if (!visited.has(neighbor)) {\n stack.push(neighbor);\n }\n }\n }\n }\n\n return components;\n}\n","import type { Note } from '../types/index.js';\n\nexport class AliasResolver {\n private aliasToNote: Map<string, string> = new Map();\n private fileNameToNote: Map<string, string> = new Map();\n private basenameToNote: Map<string, string> = new Map();\n\n constructor(notes: Note[]) {\n for (const note of notes) {\n const fileName = note.relativePath.replace(/\\.(md|canvas)$/, '');\n const basename = fileName.split('/').pop()!;\n\n this.fileNameToNote.set(fileName.toLowerCase(), note.path);\n this.basenameToNote.set(basename.toLowerCase(), note.path);\n\n try {\n this.fileNameToNote.set(decodeURIComponent(fileName).toLowerCase(), note.path);\n this.basenameToNote.set(decodeURIComponent(basename).toLowerCase(), note.path);\n } catch {\n // skip malformed URIs\n }\n\n for (const alias of note.aliases) {\n this.aliasToNote.set(alias.toLowerCase(), note.path);\n }\n }\n }\n\n resolve(target: string): string | null {\n const normalized = target.toLowerCase().trim();\n\n if (this.aliasToNote.has(normalized)) {\n return this.aliasToNote.get(normalized)!;\n }\n\n if (this.fileNameToNote.has(normalized)) {\n return this.fileNameToNote.get(normalized)!;\n }\n\n if (this.basenameToNote.has(normalized)) {\n return this.basenameToNote.get(normalized)!;\n }\n\n const decoded = (() => {\n try {\n return decodeURIComponent(normalized);\n } catch {\n return normalized;\n }\n })();\n\n if (this.aliasToNote.has(decoded)) {\n return this.aliasToNote.get(decoded)!;\n }\n if (this.fileNameToNote.has(decoded)) {\n return this.fileNameToNote.get(decoded)!;\n }\n if (this.basenameToNote.has(decoded)) {\n return this.basenameToNote.get(decoded)!;\n }\n\n return null;\n }\n}\n","import type { Note, GraphNode, Link } from '../types/index.js';\nimport { AliasResolver } from './alias-resolver.js';\n\nexport function buildGraph(notes: Note[], resolver: AliasResolver): GraphNode[] {\n const notePathMap = new Map<string, GraphNode>();\n\n for (const note of notes) {\n notePathMap.set(note.path, {\n note,\n incoming: [],\n outgoing: [],\n });\n }\n\n for (const note of notes) {\n const node = notePathMap.get(note.path)!;\n\n for (const link of note.links) {\n if (link.type === 'markdown' && !link.target.endsWith('.md')) {\n continue;\n }\n\n const resolvedPath = resolver.resolve(link.target);\n\n if (resolvedPath && notePathMap.has(resolvedPath)) {\n if (!node.outgoing.includes(resolvedPath)) {\n node.outgoing.push(resolvedPath);\n }\n\n const targetNode = notePathMap.get(resolvedPath)!;\n if (!targetNode.incoming.includes(note.path)) {\n targetNode.incoming.push(note.path);\n }\n }\n }\n }\n\n return Array.from(notePathMap.values());\n}\n","import type { Note } from '../types/index.js';\r\n\r\nexport interface Suggestion {\r\n broken: string;\r\n suggested: string;\r\n similarity: number;\r\n source: string;\r\n}\r\n\r\nfunction levenshteinDistance(a: string, b: string): number {\r\n const matrix: number[][] = Array(b.length + 1)\r\n .fill(null)\r\n .map(() => Array(a.length + 1).fill(0));\r\n\r\n for (let i = 0; i <= a.length; i++) matrix[0]![i] = i;\r\n for (let j = 0; j <= b.length; j++) matrix[j]![0] = j;\r\n\r\n for (let j = 1; j <= b.length; j++) {\r\n for (let i = 1; i <= a.length; i++) {\r\n if (a[i - 1] === b[j - 1]) {\r\n matrix[j]![i] = matrix[j - 1]![i - 1]!;\r\n } else {\r\n matrix[j]![i] = Math.min(\r\n matrix[j]![i - 1]! + 1,\r\n matrix[j - 1]![i]! + 1,\r\n matrix[j - 1]![i - 1]! + 1,\r\n );\r\n }\r\n }\r\n }\r\n\r\n return matrix[b.length]![a.length]!;\r\n}\r\n\r\nfunction calculateSimilarity(a: string, b: string): number {\r\n const distance = levenshteinDistance(\r\n a.toLowerCase(),\r\n b.toLowerCase(),\r\n );\r\n const maxLen = Math.max(a.length, b.length);\r\n return Math.max(0, 1 - distance / maxLen);\r\n}\r\n\r\nexport function generateSuggestions(\r\n brokenTargets: string[],\r\n allNotes: Note[],\r\n threshold = 0.6,\r\n): Suggestion[] {\r\n const availableNotes = allNotes.map((n) => ({\r\n name: n.relativePath.replace(/\\.(md|canvas)$/, ''),\r\n basename: n.relativePath.split('/').pop()?.replace(/\\.(md|canvas)$/, '') || '',\r\n aliases: n.aliases,\r\n }));\r\n\r\n const suggestions: Suggestion[] = [];\r\n\r\n for (const broken of brokenTargets) {\r\n const brokenBasename = broken.split('/').pop() || broken;\r\n const candidates: Array<{ suggestion: string; similarity: number }> = [];\r\n\r\n for (const note of availableNotes) {\r\n const basenameScore = calculateSimilarity(brokenBasename, note.basename);\r\n const fullScore = calculateSimilarity(broken, note.name);\r\n\r\n // Check aliases too\r\n for (const alias of note.aliases) {\r\n const aliasScore = calculateSimilarity(broken, alias);\r\n if (aliasScore > basenameScore && aliasScore > fullScore) {\r\n candidates.push({ suggestion: note.name, similarity: aliasScore });\r\n }\r\n }\r\n\r\n if (basenameScore >= fullScore && basenameScore > threshold) {\r\n candidates.push({ suggestion: note.name, similarity: basenameScore });\r\n } else if (fullScore > threshold) {\r\n candidates.push({ suggestion: note.name, similarity: fullScore });\r\n }\r\n }\r\n\r\n // Sort by similarity and take top suggestion\r\n if (candidates.length > 0) {\r\n const best = candidates.sort((a, b) => b.similarity - a.similarity)[0]!;\r\n suggestions.push({\r\n broken,\r\n suggested: best.suggestion,\r\n similarity: best.similarity,\r\n source: broken,\r\n });\r\n }\r\n }\r\n\r\n return suggestions.sort((a, b) => b.similarity - a.similarity);\r\n}\r\n","import { loadVault } from '../core/vault-loader.js';\nimport { parseLinks } from '../core/link-parser.js';\nimport { analyze } from '../core/analyzer.js';\nimport { generateSuggestions } from './suggestions.js';\nimport type { AnalysisReport, DeadLinksConfig } from '../types/index.js';\n\nexport class DeadLinksEngine {\n async analyze(\n vaultPath: string,\n config: DeadLinksConfig = {},\n ): Promise<AnalysisReport> {\n const startTime = Date.now();\n\n const notes = await loadVault(vaultPath, config.ignore);\n\n // Parallelize link parsing\n await Promise.all(\n notes.map((note) => {\n note.links = parseLinks(note.content);\n return Promise.resolve();\n }),\n );\n\n const report = analyze(notes, vaultPath, config.checkAttachments);\n\n // Generate suggestions for broken links\n if (config.suggestions) {\n const brokenTargets = Array.from(\n new Set(report.brokenLinks.map((b) => b.link.target)),\n );\n report.suggestions = generateSuggestions(brokenTargets, notes, 0.65);\n }\n\n report.duration = Date.now() - startTime;\n\n return report;\n }\n}\n","import { readFile, access } from 'node:fs/promises';\nimport { resolve, join } from 'node:path';\nimport type { DeadLinksConfig } from '../types/index.js';\n\nconst CONFIG_FILES = ['.deadlinksrc.json', '.deadlinksrc', 'dead-links.config.json'];\n\nexport class ConfigLoader {\n async loadConfig(vaultPath: string): Promise<DeadLinksConfig> {\n const absolutePath = resolve(vaultPath);\n\n for (const configFile of CONFIG_FILES) {\n const configPath = join(absolutePath, configFile);\n\n try {\n await access(configPath);\n const content = await readFile(configPath, 'utf-8');\n const parsed = JSON.parse(content) as DeadLinksConfig;\n\n if (parsed.ignore && !Array.isArray(parsed.ignore)) {\n continue;\n }\n if (parsed.ignoreFolders && !Array.isArray(parsed.ignoreFolders)) {\n continue;\n }\n\n return parsed;\n } catch {\n continue;\n }\n }\n\n return {};\n }\n\n mergeConfig(\n fileConfig: DeadLinksConfig,\n cliOptions: Record<string, unknown>,\n ): DeadLinksConfig {\n return {\n ignore: (cliOptions.ignore as string[] | undefined) || fileConfig.ignore,\n checkAttachments:\n (cliOptions.attachments as boolean | undefined) ??\n fileConfig.checkAttachments ??\n false,\n ignoreFolders:\n (cliOptions.ignoreFolders as string[] | undefined) ||\n fileConfig.ignoreFolders,\n suggestions:\n (cliOptions.suggestions as boolean | undefined) ??\n fileConfig.suggestions ??\n false,\n debug:\n (cliOptions.debug as boolean | undefined) ??\n fileConfig.debug ??\n false,\n fix:\n (cliOptions.fix as boolean | undefined) ??\n fileConfig.fix ??\n false,\n };\n }\n}\n","import chalk from 'chalk';\nimport type {\n AnalysisReport,\n BrokenLink,\n Note,\n} from '../types/index.js';\n\nexport class TerminalReporter {\n private ignoreFolders: string[];\n private quiet: boolean;\n\n constructor(ignoreFolders: string[] = [], quiet = false) {\n this.ignoreFolders = ignoreFolders;\n this.quiet = quiet;\n }\n\n report(result: AnalysisReport): string {\n if (this.quiet) {\n return this.reportQuiet(result);\n }\n\n let output = '';\n output += this.printStats(result);\n output += this.printBrokenLinks(result.brokenLinks);\n output += this.printOrphanNotes(result.orphanNotes);\n if (result.suggestions && result.suggestions.length > 0) {\n output += this.printSuggestions(result.suggestions);\n }\n output += this.printDuration(result.duration);\n return output;\n }\n\n private reportQuiet(result: AnalysisReport): string {\n const lines: string[] = [];\n\n for (const broken of result.brokenLinks) {\n lines.push(`${broken.source} -> ${broken.link.target}`);\n }\n\n for (const note of result.orphanNotes) {\n const folder = note.relativePath.includes('/')\n ? note.relativePath.split('/').slice(0, -1).join('/') + '/'\n : './';\n if (this.ignoreFolders.some((f) => folder.startsWith(f + '/') || folder === f + '/')) {\n continue;\n }\n lines.push(`orphan: ${note.relativePath}`);\n }\n\n return lines.join('\\n');\n }\n\n private printStats(report: AnalysisReport): string {\n const pad = 14;\n let out = '\\n';\n out += this.statLine('Notes'.padEnd(pad), String(report.stats.totalNotes));\n out += this.statLine('Links'.padEnd(pad), String(report.stats.totalLinks));\n out += this.statLine('Broken links'.padEnd(pad), String(report.stats.brokenCount), 'red');\n out += this.statLine('Orphan notes'.padEnd(pad), String(report.stats.orphanCount), 'yellow');\n out += this.statLine('Components'.padEnd(pad), String(report.stats.connectedComponents));\n out += '\\n';\n return out;\n }\n\n private statLine(label: string, value: string, color: 'red' | 'yellow' | 'white' = 'white'): string {\n return ` ${chalk.gray(label)} ${color === 'red' ? chalk.red.bold(value) : color === 'yellow' ? chalk.yellow.bold(value) : chalk.white.bold(value)}\\n`;\n }\n\n private printBrokenLinks(brokenLinks: BrokenLink[]): string {\n if (brokenLinks.length === 0) {\n return chalk.green(' ✓ No broken links\\n\\n');\n }\n\n const grouped = new Map<string, BrokenLink[]>();\n for (const broken of brokenLinks) {\n const existing = grouped.get(broken.source) || [];\n existing.push(broken);\n grouped.set(broken.source, existing);\n }\n\n const sorted = [...grouped.entries()].sort((a, b) => b[1].length - a[1].length);\n\n let out = chalk.bold.red(`\\n✖ Broken links (${brokenLinks.length})\\n\\n`);\n\n for (const [source, links] of sorted) {\n const count = links.length;\n const isMultiple = count >= 2;\n\n const countStr = isMultiple ? chalk.red.bold(`✖ ${count}`) : chalk.yellow.bold(`✖ ${count}`);\n out += ` ${countStr} ${chalk.white.bold(source)}\\n`;\n\n for (const link of links) {\n const pill = isMultiple\n ? chalk.redBright(`[${link.link.target}]`)\n : chalk.yellowBright(`[${link.link.target}]`);\n out += ` ${pill}\\n`;\n }\n out += '\\n';\n }\n\n return out;\n }\n\n private printOrphanNotes(orphanNotes: Note[]): string {\n if (orphanNotes.length === 0) {\n return chalk.green(' ✓ No orphan notes\\n\\n');\n }\n\n const byFolder = new Map<string, number>();\n for (const note of orphanNotes) {\n const folder = note.relativePath.includes('/')\n ? note.relativePath.split('/').slice(0, -1).join('/') + '/'\n : './';\n\n if (this.ignoreFolders.some((f) => folder.startsWith(f + '/') || folder === f + '/')) {\n continue;\n }\n\n byFolder.set(folder, (byFolder.get(folder) || 0) + 1);\n }\n\n const sorted = [...byFolder.entries()].sort((a, b) => b[1] - a[1]);\n\n if (sorted.length === 0) {\n return chalk.green(' ✓ No orphan notes (all in ignored folders)\\n\\n');\n }\n\n const maxCount = sorted[0]![1];\n const barWidth = 10;\n\n let out = chalk.bold.yellow(`\\n⚠ Orphan notes (${orphanNotes.length} total)\\n\\n`);\n\n for (const [folder, count] of sorted) {\n const filled = Math.round((count / maxCount) * barWidth);\n const empty = barWidth - filled;\n const bar = chalk.gray('█'.repeat(filled) + '░'.repeat(empty));\n out += ` ${chalk.gray(folder.padEnd(40))} ${bar} ${chalk.white.bold(String(count))}\\n`;\n }\n\n out += '\\n';\n return out;\n }\n\n private printDuration(ms: number): string {\n return chalk.dim(` Completed in ${ms}ms\\n`);\n }\n\n private printSuggestions(\n suggestions: Array<{ broken: string; suggested: string; similarity: number }>,\n ): string {\n let out = chalk.bold.cyan(`\\n💡 Suggestions (${suggestions.length})\\n\\n`);\n\n for (const suggestion of suggestions.slice(0, 5)) {\n const confidence = Math.round(suggestion.similarity * 100);\n const bar =\n confidence >= 80\n ? chalk.green('█████')\n : confidence >= 60\n ? chalk.yellow('█████')\n : chalk.gray('█████');\n\n out += ` ${chalk.gray(suggestion.broken)} → ${chalk.cyan(suggestion.suggested)}\\n`;\n out += ` ${bar} ${confidence}%\\n\\n`;\n }\n\n return out;\n }\n}\n","import type { AnalysisReport } from '../types/index.js';\n\nexport class JsonReporter {\n report(result: AnalysisReport): string {\n return JSON.stringify(\n {\n stats: result.stats,\n brokenLinks: result.brokenLinks,\n orphanNotes: result.orphanNotes.map((n) => ({\n path: n.relativePath,\n })),\n graph: result.graph.map((n) => ({\n note: n.note.relativePath,\n incoming: n.incoming.length,\n outgoing: n.outgoing.length,\n })),\n duration: result.duration,\n },\n null,\n 2,\n );\n }\n}\n","import type { AnalysisReport } from '../types/index.js';\r\n\r\nexport class CsvReporter {\r\n report(result: AnalysisReport): string {\r\n const lines: string[] = [];\r\n\r\n // Broken Links CSV\r\n lines.push('# BROKEN LINKS');\r\n lines.push('Source,Target,Type,Line,Reason');\r\n\r\n for (const broken of result.brokenLinks) {\r\n const source = this.escapeCsv(broken.source);\r\n const target = this.escapeCsv(broken.link.target);\r\n const type = broken.link.type;\r\n const line = broken.link.line;\r\n const reason = this.escapeCsv(broken.reason);\r\n\r\n lines.push(`${source},${target},${type},${line},\"${reason}\"`);\r\n }\r\n\r\n // Orphan Notes CSV\r\n lines.push('\\n# ORPHAN NOTES');\r\n lines.push('Note,Path');\r\n\r\n for (const orphan of result.orphanNotes) {\r\n const name = this.escapeCsv(orphan.relativePath);\r\n lines.push(`${name},\"${name}\"`);\r\n }\r\n\r\n // Graph Stats CSV\r\n lines.push('\\n# GRAPH STATISTICS');\r\n lines.push('Metric,Value');\r\n lines.push(`Total Notes,${result.stats.totalNotes}`);\r\n lines.push(`Total Links,${result.stats.totalLinks}`);\r\n lines.push(`Broken Links,${result.stats.brokenCount}`);\r\n lines.push(`Orphan Notes,${result.stats.orphanCount}`);\r\n lines.push(`Connected Components,${result.stats.connectedComponents}`);\r\n lines.push(`Duration (ms),${result.duration}`);\r\n\r\n return lines.join('\\n');\r\n }\r\n\r\n private escapeCsv(value: string): string {\r\n if (value.includes(',') || value.includes('\"') || value.includes('\\n')) {\r\n return `\"${value.replace(/\"/g, '\"\"')}\"`;\r\n }\r\n return value;\r\n }\r\n}\r\n","import type { AnalysisReport } from '../types/index.js';\r\n\r\nexport class HtmlReporter {\r\n report(result: AnalysisReport): string {\r\n const brokenLinksHtml = this.renderBrokenLinks(result.brokenLinks);\r\n const orphanNotesHtml = this.renderOrphanNotes(result.orphanNotes);\r\n const graphHtml = this.renderGraph(result.graph);\r\n const statsHtml = this.renderStats(result.stats, result.duration);\r\n\r\n return `<!DOCTYPE html>\r\n<html lang=\"en\">\r\n<head>\r\n <meta charset=\"UTF-8\">\r\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n <title>Dead Links Report</title>\r\n <style>\r\n * { margin: 0; padding: 0; box-sizing: border-box; }\r\n body {\r\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\r\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\r\n min-height: 100vh;\r\n padding: 20px;\r\n }\r\n .container {\r\n max-width: 1400px;\r\n margin: 0 auto;\r\n background: white;\r\n border-radius: 12px;\r\n box-shadow: 0 20px 60px rgba(0,0,0,0.3);\r\n overflow: hidden;\r\n }\r\n header {\r\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\r\n color: white;\r\n padding: 40px;\r\n text-align: center;\r\n }\r\n header h1 { font-size: 2.5em; margin-bottom: 10px; }\r\n header p { font-size: 1.1em; opacity: 0.9; }\r\n .content { padding: 40px; }\r\n .section {\r\n margin-bottom: 40px;\r\n border-left: 4px solid #667eea;\r\n padding-left: 20px;\r\n }\r\n .section h2 {\r\n font-size: 1.8em;\r\n margin-bottom: 20px;\r\n color: #333;\r\n }\r\n .stats-grid {\r\n display: grid;\r\n grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\r\n gap: 20px;\r\n margin-bottom: 40px;\r\n }\r\n .stat-card {\r\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\r\n color: white;\r\n padding: 20px;\r\n border-radius: 8px;\r\n text-align: center;\r\n }\r\n .stat-card .value { font-size: 2.5em; font-weight: bold; }\r\n .stat-card .label { font-size: 0.9em; opacity: 0.9; margin-top: 10px; }\r\n table {\r\n width: 100%;\r\n border-collapse: collapse;\r\n margin-top: 20px;\r\n }\r\n th {\r\n background: #f5f5f5;\r\n padding: 12px;\r\n text-align: left;\r\n border-bottom: 2px solid #667eea;\r\n font-weight: 600;\r\n }\r\n td {\r\n padding: 12px;\r\n border-bottom: 1px solid #eee;\r\n }\r\n tr:hover { background: #f9f9f9; }\r\n .badge {\r\n display: inline-block;\r\n padding: 4px 8px;\r\n border-radius: 4px;\r\n font-size: 0.85em;\r\n font-weight: 500;\r\n }\r\n .badge-wiki { background: #dbeafe; color: #1e40af; }\r\n .badge-embed { background: #fed7aa; color: #92400e; }\r\n .badge-markdown { background: #d1fae5; color: #065f46; }\r\n .error { color: #dc2626; }\r\n .success { color: #16a34a; }\r\n .tabs {\r\n display: flex;\r\n gap: 0;\r\n border-bottom: 2px solid #eee;\r\n margin-bottom: 20px;\r\n }\r\n .tab-button {\r\n padding: 12px 20px;\r\n border: none;\r\n background: none;\r\n cursor: pointer;\r\n font-size: 1em;\r\n border-bottom: 3px solid transparent;\r\n color: #666;\r\n transition: all 0.3s;\r\n }\r\n .tab-button.active {\r\n color: #667eea;\r\n border-bottom-color: #667eea;\r\n }\r\n .tab-content { display: none; }\r\n .tab-content.active { display: block; }\r\n .filter-box {\r\n margin: 20px 0;\r\n padding: 15px;\r\n background: #f9f9f9;\r\n border-radius: 8px;\r\n }\r\n .filter-box input {\r\n width: 100%;\r\n padding: 8px;\r\n border: 1px solid #ddd;\r\n border-radius: 4px;\r\n font-size: 1em;\r\n }\r\n footer {\r\n background: #f5f5f5;\r\n padding: 20px;\r\n text-align: center;\r\n color: #666;\r\n font-size: 0.9em;\r\n }\r\n </style>\r\n</head>\r\n<body>\r\n <div class=\"container\">\r\n <header>\r\n <h1>🔍 Dead Links Report</h1>\r\n <p>Obsidian Vault Analysis</p>\r\n </header>\r\n\r\n <div class=\"content\">\r\n ${statsHtml}\r\n ${brokenLinksHtml}\r\n ${orphanNotesHtml}\r\n ${graphHtml}\r\n </div>\r\n\r\n <footer>\r\n Generated on ${new Date().toLocaleString()}\r\n </footer>\r\n </div>\r\n\r\n <script>\r\n function setupTabs() {\r\n const buttons = document.querySelectorAll('.tab-button');\r\n const contents = document.querySelectorAll('.tab-content');\r\n\r\n buttons.forEach((button, index) => {\r\n button.addEventListener('click', () => {\r\n buttons.forEach(b => b.classList.remove('active'));\r\n contents.forEach(c => c.classList.remove('active'));\r\n button.classList.add('active');\r\n contents[index].classList.add('active');\r\n });\r\n });\r\n\r\n // Activate first tab\r\n if (buttons.length > 0) buttons[0].classList.add('active');\r\n if (contents.length > 0) contents[0].classList.add('active');\r\n }\r\n\r\n function setupFilter(tableId, inputId) {\r\n const input = document.getElementById(inputId);\r\n const table = document.getElementById(tableId);\r\n if (!input || !table) return;\r\n\r\n input.addEventListener('keyup', (e) => {\r\n const filter = e.target.value.toLowerCase();\r\n const rows = table.querySelectorAll('tbody tr');\r\n rows.forEach(row => {\r\n const text = row.textContent.toLowerCase();\r\n row.style.display = text.includes(filter) ? '' : 'none';\r\n });\r\n });\r\n }\r\n\r\n document.addEventListener('DOMContentLoaded', () => {\r\n setupTabs();\r\n setupFilter('broken-table', 'broken-filter');\r\n setupFilter('orphan-table', 'orphan-filter');\r\n });\r\n </script>\r\n</body>\r\n</html>`;\r\n }\r\n\r\n private renderStats(\r\n stats: {\r\n totalNotes: number;\r\n totalLinks: number;\r\n brokenCount: number;\r\n orphanCount: number;\r\n connectedComponents: number;\r\n },\r\n duration: number,\r\n ): string {\r\n const healthColor =\r\n stats.brokenCount === 0 && stats.orphanCount === 0 ? '#16a34a' : '#dc2626';\r\n\r\n return `\r\n <div class=\"section\">\r\n <h2>📊 Overview</h2>\r\n <div class=\"stats-grid\">\r\n <div class=\"stat-card\">\r\n <div class=\"value\">${stats.totalNotes}</div>\r\n <div class=\"label\">Total Notes</div>\r\n </div>\r\n <div class=\"stat-card\">\r\n <div class=\"value\">${stats.totalLinks}</div>\r\n <div class=\"label\">Total Links</div>\r\n </div>\r\n <div class=\"stat-card\" style=\"background: linear-gradient(135deg, #dc2626 0%, #991b1b 100%);\">\r\n <div class=\"value\">${stats.brokenCount}</div>\r\n <div class=\"label\">Broken Links</div>\r\n </div>\r\n <div class=\"stat-card\" style=\"background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);\">\r\n <div class=\"value\">${stats.orphanCount}</div>\r\n <div class=\"label\">Orphan Notes</div>\r\n </div>\r\n <div class=\"stat-card\" style=\"background: linear-gradient(135deg, ${healthColor} 0%, ${healthColor === '#16a34a' ? '#15803d' : '#991b1b'} 100%);\">\r\n <div class=\"value\">${stats.connectedComponents}</div>\r\n <div class=\"label\">Connected Components</div>\r\n </div>\r\n <div class=\"stat-card\" style=\"background: linear-gradient(135deg, #8b5cf6 0%, #6d28d9 100%);\">\r\n <div class=\"value\">${duration}ms</div>\r\n <div class=\"label\">Scan Duration</div>\r\n </div>\r\n </div>\r\n </div>\r\n `;\r\n }\r\n\r\n private renderBrokenLinks(brokenLinks: any[]): string {\r\n if (brokenLinks.length === 0) {\r\n return `\r\n <div class=\"section\">\r\n <h2>✅ Broken Links</h2>\r\n <p class=\"success\">No broken links found!</p>\r\n </div>\r\n `;\r\n }\r\n\r\n const rows = brokenLinks\r\n .map(\r\n (link) => `\r\n <tr>\r\n <td>${this.escapeHtml(link.source)}</td>\r\n <td>${this.escapeHtml(link.link.target)}</td>\r\n <td><span class=\"badge badge-${link.link.type}\">${link.link.type}</span></td>\r\n <td>${link.link.line}</td>\r\n <td>${this.escapeHtml(link.reason)}</td>\r\n </tr>\r\n `,\r\n )\r\n .join('');\r\n\r\n return `\r\n <div class=\"section\">\r\n <h2>🔴 Broken Links (${brokenLinks.length})</h2>\r\n <div class=\"filter-box\">\r\n <input type=\"text\" id=\"broken-filter\" placeholder=\"Filter by source or target...\" />\r\n </div>\r\n <table id=\"broken-table\">\r\n <thead>\r\n <tr>\r\n <th>Source</th>\r\n <th>Target</th>\r\n <th>Type</th>\r\n <th>Line</th>\r\n <th>Reason</th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n ${rows}\r\n </tbody>\r\n </table>\r\n </div>\r\n `;\r\n }\r\n\r\n private renderOrphanNotes(orphanNotes: any[]): string {\r\n if (orphanNotes.length === 0) {\r\n return `\r\n <div class=\"section\">\r\n <h2>✅ Orphan Notes</h2>\r\n <p class=\"success\">No orphan notes found!</p>\r\n </div>\r\n `;\r\n }\r\n\r\n const rows = orphanNotes\r\n .map(\r\n (note) => `\r\n <tr>\r\n <td>${this.escapeHtml(note.relativePath)}</td>\r\n </tr>\r\n `,\r\n )\r\n .join('');\r\n\r\n return `\r\n <div class=\"section\">\r\n <h2>🟡 Orphan Notes (${orphanNotes.length})</h2>\r\n <div class=\"filter-box\">\r\n <input type=\"text\" id=\"orphan-filter\" placeholder=\"Filter by path...\" />\r\n </div>\r\n <table id=\"orphan-table\">\r\n <thead>\r\n <tr>\r\n <th>Path</th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n ${rows}\r\n </tbody>\r\n </table>\r\n </div>\r\n `;\r\n }\r\n\r\n private renderGraph(graph: any[]): string {\r\n const mostConnected = [...graph]\r\n .sort((a, b) => (b.incoming.length + b.outgoing.length) - (a.incoming.length + a.outgoing.length))\r\n .slice(0, 5);\r\n\r\n const rows = mostConnected\r\n .map(\r\n (node) => `\r\n <tr>\r\n <td>${this.escapeHtml(node.note.relativePath)}</td>\r\n <td>${node.incoming.length}</td>\r\n <td>${node.outgoing.length}</td>\r\n <td>${node.incoming.length + node.outgoing.length}</td>\r\n </tr>\r\n `,\r\n )\r\n .join('');\r\n\r\n return `\r\n <div class=\"section\">\r\n <h2>🌐 Most Connected Notes</h2>\r\n <table>\r\n <thead>\r\n <tr>\r\n <th>Note</th>\r\n <th>Incoming</th>\r\n <th>Outgoing</th>\r\n <th>Total</th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n ${rows}\r\n </tbody>\r\n </table>\r\n </div>\r\n `;\r\n }\r\n\r\n private escapeHtml(text: string): string {\r\n return text\r\n .replace(/&/g, '&amp;')\r\n .replace(/</g, '&lt;')\r\n .replace(/>/g, '&gt;')\r\n .replace(/\"/g, '&quot;')\r\n .replace(/'/g, '&#039;');\r\n }\r\n}\r\n","import type { AnalysisReport } from '../types/index.js';\r\n\r\nexport class MermaidReporter {\r\n report(result: AnalysisReport, maxNodes = 50): string {\r\n let output = 'graph LR\\n';\r\n\r\n // Limit nodes for readability\r\n const nodesToShow = result.graph.slice(0, maxNodes);\r\n const nodeMap = new Map(nodesToShow.map((n) => [n.note.path, n.note.relativePath]));\r\n\r\n // Add nodes\r\n for (const node of nodesToShow) {\r\n const id = this.sanitizeId(node.note.relativePath);\r\n const label = this.escapeLabel(node.note.relativePath);\r\n\r\n if (result.orphanNotes.some((o) => o.path === node.note.path)) {\r\n output += ` ${id}[\"<b>🔴 ${label}</b>\"]:::orphan\\n`;\r\n } else if (node.incoming.length === 0) {\r\n output += ` ${id}[\"📄 ${label}\"]:::source\\n`;\r\n } else if (node.outgoing.length === 0) {\r\n output += ` ${id}[\"📌 ${label}\"]:::sink\\n`;\r\n } else {\r\n output += ` ${id}[\"${label}\"]\\n`;\r\n }\r\n }\r\n\r\n // Add edges\r\n const addedEdges = new Set<string>();\r\n for (const node of nodesToShow) {\r\n const sourceId = this.sanitizeId(node.note.relativePath);\r\n\r\n for (const targetPath of node.outgoing) {\r\n const targetName = nodeMap.get(targetPath);\r\n if (!targetName) continue;\r\n\r\n const targetId = this.sanitizeId(targetName);\r\n const edgeKey = `${sourceId}-${targetId}`;\r\n\r\n if (!addedEdges.has(edgeKey)) {\r\n output += ` ${sourceId} --> ${targetId}\\n`;\r\n addedEdges.add(edgeKey);\r\n }\r\n }\r\n }\r\n\r\n // Add styling\r\n output += '\\n classDef orphan fill:#ff6b6b,stroke:#c92a2a,color:#fff\\n';\r\n output += ' classDef source fill:#4dabf7,stroke:#1971c2,color:#fff\\n';\r\n output += ' classDef sink fill:#51cf66,stroke:#2f9e44,color:#fff\\n';\r\n\r\n if (result.graph.length > maxNodes) {\r\n output += `\\n note[\"Graph limited to ${maxNodes} of ${result.graph.length} nodes for readability\"]\\n`;\r\n }\r\n\r\n return output;\r\n }\r\n\r\n private sanitizeId(name: string): string {\r\n return name\r\n .replace(/\\.(md|canvas)$/, '')\r\n .replace(/[^a-zA-Z0-9_]/g, '_')\r\n .substring(0, 30);\r\n }\r\n\r\n private escapeLabel(name: string): string {\r\n return name.replace(/\"/g, '\\\\\"').substring(0, 50);\r\n }\r\n}\r\n","import type { AnalysisReport } from '../types/index.js';\r\n\r\nexport class GraphVizReporter {\r\n report(result: AnalysisReport, maxNodes = 100): string {\r\n let output = 'digraph VaultGraph {\\n';\r\n output += ' rankdir=LR;\\n';\r\n output += ' node [shape=box, style=rounded];\\n';\r\n output += ' edge [arrowsize=0.5];\\n\\n';\r\n\r\n const nodesToShow = result.graph.slice(0, maxNodes);\r\n const nodeMap = new Map(nodesToShow.map((n) => [n.note.path, n.note.relativePath]));\r\n\r\n // Add nodes with styling\r\n for (const node of nodesToShow) {\r\n const id = this.sanitizeId(node.note.relativePath);\r\n const label = node.note.relativePath;\r\n let style = 'filled';\r\n let color = '#ffffff';\r\n let fontColor = '#000000';\r\n let shape = 'box';\r\n\r\n if (result.orphanNotes.some((o) => o.path === node.note.path)) {\r\n color = '#ff6b6b';\r\n fontColor = '#ffffff';\r\n shape = 'box';\r\n } else if (node.incoming.length === 0) {\r\n color = '#4dabf7';\r\n fontColor = '#ffffff';\r\n } else if (node.outgoing.length === 0) {\r\n color = '#51cf66';\r\n fontColor = '#ffffff';\r\n }\r\n\r\n output += ` \"${id}\" [label=\"${label}\", fillcolor=\"${color}\", fontcolor=\"${fontColor}\", shape=${shape}];\\n`;\r\n }\r\n\r\n output += '\\n';\r\n\r\n // Add edges\r\n const addedEdges = new Set<string>();\r\n for (const node of nodesToShow) {\r\n const sourceId = this.sanitizeId(node.note.relativePath);\r\n\r\n for (const targetPath of node.outgoing) {\r\n const targetName = nodeMap.get(targetPath);\r\n if (!targetName) continue;\r\n\r\n const targetId = this.sanitizeId(targetName);\r\n const edgeKey = `${sourceId}->${targetId}`;\r\n\r\n if (!addedEdges.has(edgeKey)) {\r\n output += ` \"${sourceId}\" -> \"${targetId}\";\\n`;\r\n addedEdges.add(edgeKey);\r\n }\r\n }\r\n }\r\n\r\n output += '\\n // Legend\\n';\r\n output += ' subgraph legend {\\n';\r\n output += ' rank=sink;\\n';\r\n output += ' orphan [label=\"🔴 Orphan\", fillcolor=\"#ff6b6b\", fontcolor=\"white\"];\\n';\r\n output += ' source [label=\"📄 Source (no incoming)\", fillcolor=\"#4dabf7\", fontcolor=\"white\"];\\n';\r\n output += ' sink [label=\"📌 Sink (no outgoing)\", fillcolor=\"#51cf66\", fontcolor=\"white\"];\\n';\r\n output += ' }\\n';\r\n\r\n output += '}\\n';\r\n\r\n return output;\r\n }\r\n\r\n private sanitizeId(name: string): string {\r\n return name.replace(/\\.(md|canvas)$/, '').replace(/[^a-zA-Z0-9_]/g, '_');\r\n }\r\n}\r\n","import type { AnalysisReport } from '../types/index.js';\n\nfunction pill(target: string, isMultiple: boolean): string {\n return isMultiple ? `[${target}]` : `[${target}]`;\n}\n\nfunction bar(count: number, max: number, width: number): string {\n const filled = Math.round((count / max) * width);\n const empty = width - filled;\n return '█'.repeat(filled) + '░'.repeat(empty);\n}\n\nexport function generateMarkdownReport(report: AnalysisReport): string {\n const lines: string[] = [];\n\n lines.push('# Dead Links Report');\n lines.push('');\n lines.push(`Generated: ${new Date().toISOString()}`);\n lines.push(`Completed in ${report.duration}ms`);\n lines.push('');\n\n lines.push('## Stats');\n lines.push('');\n lines.push(`| Metric | Value |`);\n lines.push(`|--------|-------|`);\n lines.push(`| Notes | ${report.stats.totalNotes} |`);\n lines.push(`| Links | ${report.stats.totalLinks} |`);\n lines.push(`| Broken | ${report.stats.brokenCount} |`);\n lines.push(`| Orphans | ${report.stats.orphanCount} |`);\n lines.push(`| Components | ${report.stats.connectedComponents} |`);\n lines.push('');\n\n lines.push('## Broken Links');\n lines.push('');\n\n if (report.brokenLinks.length === 0) {\n lines.push('No broken links found.');\n } else {\n const grouped = new Map<string, typeof report.brokenLinks>();\n for (const b of report.brokenLinks) {\n const existing = grouped.get(b.source) || [];\n existing.push(b);\n grouped.set(b.source, existing);\n }\n\n const sorted = [...grouped.entries()].sort((a, b) => b[1].length - a[1].length);\n\n for (const [source, links] of sorted) {\n const count = links.length;\n lines.push(`### ${count > 1 ? '✖' : '✖'} ${count} ${source}`);\n lines.push('');\n for (const link of links) {\n lines.push(`- ${pill(link.link.target, count > 1)}`);\n }\n lines.push('');\n }\n }\n\n lines.push('## Orphan Notes');\n lines.push('');\n\n if (report.orphanNotes.length === 0) {\n lines.push('No orphan notes found.');\n } else {\n const byFolder = new Map<string, number>();\n for (const note of report.orphanNotes) {\n const folder = note.relativePath.includes('/')\n ? note.relativePath.split('/').slice(0, -1).join('/') + '/'\n : './';\n byFolder.set(folder, (byFolder.get(folder) || 0) + 1);\n }\n\n const sorted = [...byFolder.entries()].sort((a, b) => b[1] - a[1]);\n const maxCount = sorted[0]?.[1] ?? 1;\n\n for (const [folder, count] of sorted) {\n lines.push(`- ${folder.padEnd(40)} ${bar(count, maxCount, 10)} ${count}`);\n }\n }\n\n lines.push('');\n\n return lines.join('\\n');\n}\n"],"mappings":"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","names":["readFile","resolve","pill","bar"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/core/vault-loader.ts","../src/core/link-parser.ts","../src/core/analyzer.ts","../src/core/alias-resolver.ts","../src/core/link-resolver.ts","../src/core/graph-builder.ts","../src/utils/suggestions.ts","../src/utils/engine.ts","../src/utils/config-loader.ts","../src/reporters/terminal-reporter.ts","../src/reporters/json-reporter.ts","../src/reporters/csv-reporter.ts","../src/reporters/html-reporter.ts","../src/reporters/mermaid-reporter.ts","../src/reporters/graphviz-reporter.ts","../src/utils/markdown-report.ts","../src/utils/auto-fixer.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { readFileSync } from 'node:fs';\nimport { writeFile } from 'node:fs/promises';\nimport ora from 'ora';\nimport { DeadLinksEngine } from './utils/engine.js';\nimport { ConfigLoader } from './utils/config-loader.js';\nimport { TerminalReporter } from './reporters/terminal-reporter.js';\nimport { JsonReporter } from './reporters/json-reporter.js';\nimport { CsvReporter } from './reporters/csv-reporter.js';\nimport { HtmlReporter } from './reporters/html-reporter.js';\nimport { MermaidReporter } from './reporters/mermaid-reporter.js';\nimport { GraphVizReporter } from './reporters/graphviz-reporter.js';\nimport { generateMarkdownReport } from './utils/markdown-report.js';\nimport { fixBrokenLinksBatch } from './utils/auto-fixer.js';\nimport { Chalk } from 'chalk';\n\nconst packageMetadata = JSON.parse(\n readFileSync(new URL('../package.json', import.meta.url), 'utf8'),\n) as { version: string };\nimport type { BrokenLink, Note } from './types/index.js';\n\nconst program = new Command();\n\nprogram\n .name('dead-links')\n .description('Scan Obsidian vaults for broken links, orphan notes, and connection graphs')\n .version(packageMetadata.version);\n\nasync function loadConfigAndAnalyze(\n vaultPath: string,\n cliOptions: Record<string, unknown>,\n) {\n const configLoader = new ConfigLoader();\n const fileConfig = await configLoader.loadConfig(vaultPath);\n const config = configLoader.mergeConfig(fileConfig, cliOptions);\n\n const engine = new DeadLinksEngine();\n return engine.analyze(vaultPath, config);\n}\n\nfunction printBrokenVerbose(brokenLinks: BrokenLink[]) {\n const grouped = new Map<string, BrokenLink[]>();\n for (const b of brokenLinks) {\n const existing = grouped.get(b.source) || [];\n existing.push(b);\n grouped.set(b.source, existing);\n }\n\n for (const [source, links] of grouped) {\n for (const link of links) {\n console.log(` ${source} → ${link.link.target} (line ${link.link.line})`);\n }\n }\n}\n\nfunction printOrphansVerbose(orphanNotes: Note[]) {\n for (const note of orphanNotes) {\n console.log(` ${note.relativePath}`);\n }\n}\n\nprogram\n .command('scan')\n .description('Scan the vault for all issues')\n .option('--vault <path>', 'Path to the Obsidian vault', process.cwd())\n .option('--format <type>', 'Output format: text, json, markdown, csv, html, mermaid, dot', 'text')\n .option('--output <file>', 'Output file path')\n .option('--ignore <patterns...>', 'Ignore patterns (glob)')\n .option('--attachments', 'Also check embedded attachments (images, PDFs, etc.)')\n .option('--ignore-folders <folders...>', 'Exclude folders from orphan report')\n .option('--quiet', 'Only show issues, no stats or summary')\n .option('--no-color', 'Disable colored output')\n .option('--suggestions', 'Show smart suggestions for broken links')\n .option('--debug', 'Enable debug mode with verbose output')\n .option('--fix', 'Automatically fix broken links (requires suggestions)')\n .action(async (options) => {\n const spinner = ora('Scanning vault...').start();\n\n try {\n const report = await loadConfigAndAnalyze(options.vault, options);\n spinner.succeed(`Scanned ${report.stats.totalNotes} notes in ${report.duration}ms`);\n\n let fixSummary = '';\n if (options.fix) {\n const suggestionMap = new Map(\n (report.suggestions || []).map((suggestion) => [suggestion.broken, suggestion.suggested]),\n );\n const fixResults = await fixBrokenLinksBatch(report.brokenLinks, suggestionMap, options.vault);\n const changed = [...fixResults.values()].flat().filter((result) => result.success).length;\n const failed = [...fixResults.values()].flat().filter((result) => !result.success).length;\n const fixSummary = `Auto-fix: ${changed} link(s) changed${failed > 0 ? `, ${failed} failed` : ''}.`;\n console.error(fixSummary);\n }\n\n if (options.debug) {\n console.log(`[DEBUG] Vault: ${options.vault}`);\n console.log(`[DEBUG] Format: ${options.format}`);\n console.log(`[DEBUG] Broken links: ${report.stats.brokenCount}`);\n console.log(`[DEBUG] Orphan notes: ${report.stats.orphanCount}`);\n }\n\n let output = '';\n\n if (options.format === 'json') {\n const reporter = new JsonReporter();\n output = reporter.report(report);\n } else if (options.format === 'markdown') {\n output = generateMarkdownReport(report);\n } else if (options.format === 'csv') {\n const reporter = new CsvReporter();\n output = reporter.report(report);\n } else if (options.format === 'html') {\n const reporter = new HtmlReporter();\n output = reporter.report(report);\n } else if (options.format === 'mermaid') {\n const reporter = new MermaidReporter();\n output = reporter.report(report);\n } else if (options.format === 'dot') {\n const reporter = new GraphVizReporter();\n output = reporter.report(report);\n } else {\n const reporter = new TerminalReporter(\n options.ignoreFolders || [],\n options.quiet || false,\n options.color === false,\n );\n output = reporter.report(report);\n }\n\n if (options.output) {\n await writeFile(options.output, output);\n console.log(`Report written to ${options.output}`);\n } else if (options.format === 'text') {\n console.log(output);\n } else {\n console.log(output);\n }\n\n process.exit(report.stats.brokenCount > 0 || report.stats.orphanCount > 0 ? 1 : 0);\n } catch (error) {\n spinner.fail('Scan failed');\n console.error(error instanceof Error ? error.message : error);\n process.exit(1);\n }\n });\n\nprogram\n .command('broken')\n .description('Show only broken links')\n .option('--vault <path>', 'Path to the Obsidian vault', process.cwd())\n .option('--ignore <patterns...>', 'Ignore patterns (glob)')\n .option('--attachments', 'Also check embedded attachments (images, PDFs, etc.)')\n .option('--verbose', 'Show line numbers and full details')\n .option('--suggestions', 'Show smart suggestions for broken links')\n .option('--debug', 'Enable debug mode with verbose output')\n .action(async (options) => {\n const spinner = ora('Scanning vault...').start();\n\n try {\n const report = await loadConfigAndAnalyze(options.vault, options);\n spinner.succeed(`Scanned ${report.stats.totalNotes} notes in ${report.duration}ms`);\n\n if (report.brokenLinks.length === 0) {\n console.log('No broken links found.');\n } else {\n console.log(`\\nBroken links (${report.brokenLinks.length})\\n`);\n if (options.verbose) {\n printBrokenVerbose(report.brokenLinks);\n } else {\n const grouped = new Map<string, BrokenLink[]>();\n for (const b of report.brokenLinks) {\n const existing = grouped.get(b.source) || [];\n existing.push(b);\n grouped.set(b.source, existing);\n }\n for (const [source, links] of grouped) {\n const targets = links.map((l: BrokenLink) => l.link.target).join(', ');\n console.log(` ${source} → ${targets}`);\n }\n }\n console.log();\n\n if (report.suggestions && report.suggestions.length > 0) {\n console.log('💡 Suggestions:');\n for (const suggestion of report.suggestions.slice(0, 5)) {\n const confidence = Math.round(suggestion.similarity * 100);\n console.log(` ${suggestion.broken} → ${suggestion.suggested} (${confidence}%)`);\n }\n console.log();\n }\n }\n\n process.exit(report.stats.brokenCount > 0 ? 1 : 0);\n } catch (error) {\n spinner.fail('Scan failed');\n console.error(error instanceof Error ? error.message : error);\n process.exit(1);\n }\n });\n\nprogram\n .command('orphans')\n .description('Show only orphan notes')\n .option('--vault <path>', 'Path to the Obsidian vault', process.cwd())\n .option('--ignore <patterns...>', 'Ignore patterns (glob)')\n .option('--ignore-folders <folders...>', 'Exclude folders from report')\n .option('--verbose', 'Show full list of orphan notes')\n .action(async (options) => {\n const spinner = ora('Scanning vault...').start();\n\n try {\n const report = await loadConfigAndAnalyze(options.vault, options);\n spinner.succeed(`Scanned ${report.stats.totalNotes} notes in ${report.duration}ms`);\n\n if (report.orphanNotes.length === 0) {\n console.log('No orphan notes found.');\n } else {\n console.log(`\\nOrphan notes (${report.orphanNotes.length})\\n`);\n if (options.verbose) {\n printOrphansVerbose(report.orphanNotes);\n } else {\n const ignoreFolders = options.ignoreFolders || [];\n const byFolder = new Map<string, number>();\n for (const note of report.orphanNotes) {\n const folder = note.relativePath.includes('/')\n ? note.relativePath.split('/').slice(0, -1).join('/') + '/'\n : './';\n if (ignoreFolders.some((f: string) => folder.startsWith(f + '/') || folder === f + '/')) {\n continue;\n }\n byFolder.set(folder, (byFolder.get(folder) || 0) + 1);\n }\n for (const [folder, count] of [...byFolder.entries()].sort((a, b) => b[1] - a[1])) {\n console.log(` ${folder} ${count}`);\n }\n }\n console.log();\n }\n\n process.exit(report.stats.orphanCount > 0 ? 1 : 0);\n } catch (error) {\n spinner.fail('Scan failed');\n console.error(error instanceof Error ? error.message : error);\n process.exit(1);\n }\n });\n\nprogram\n .command('graph')\n .description('Show connection graph')\n .option('--vault <path>', 'Path to the Obsidian vault', process.cwd())\n .option('--format <type>', 'Output format: text, json', 'text')\n .option('--ignore <patterns...>', 'Ignore patterns (glob)')\n .action(async (options) => {\n const spinner = ora('Scanning vault...').start();\n\n try {\n const report = await loadConfigAndAnalyze(options.vault, options);\n spinner.succeed(`Scanned ${report.stats.totalNotes} notes in ${report.duration}ms`);\n\n if (options.format === 'json') {\n console.log(JSON.stringify(report.graph, null, 2));\n } else {\n console.log(\n `Graph: ${report.stats.totalNotes} notes, ${report.stats.totalLinks} links (${report.duration}ms)\\n`,\n );\n\n const topConnected = [...report.graph]\n .sort((a, b) => b.incoming.length - a.incoming.length)\n .slice(0, 10)\n .filter((n) => n.incoming.length > 0);\n\n if (topConnected.length > 0) {\n console.log('Most linked-to notes:');\n for (const node of topConnected) {\n console.log(\n ` ${node.note.relativePath} (${node.incoming.length} incoming, ${node.outgoing.length} outgoing)`,\n );\n }\n }\n }\n } catch (error) {\n spinner.fail('Scan failed');\n console.error(error instanceof Error ? error.message : error);\n process.exit(1);\n }\n });\n\nprogram\n .command('report')\n .description('Generate a markdown report')\n .option('--vault <path>', 'Path to the Obsidian vault', process.cwd())\n .option('--output <file>', 'Output file path', 'dead-links-report.md')\n .option('--ignore <patterns...>', 'Ignore patterns (glob)')\n .action(async (options) => {\n const spinner = ora('Scanning vault...').start();\n\n try {\n const report = await loadConfigAndAnalyze(options.vault, options);\n spinner.succeed(`Scanned ${report.stats.totalNotes} notes in ${report.duration}ms`);\n\n const markdown = generateMarkdownReport(report);\n await writeFile(options.output, markdown);\n console.log(`Report written to ${options.output}`);\n } catch (error) {\n spinner.fail('Scan failed');\n console.error(error instanceof Error ? error.message : error);\n process.exit(1);\n }\n });\n\nprogram\n .command('stats')\n .description('Show detailed vault statistics')\n .option('--vault <path>', 'Path to the Obsidian vault', process.cwd())\n .option('--ignore <patterns...>', 'Ignore patterns (glob)')\n .option('--debug', 'Enable debug mode')\n .action(async (options) => {\n const spinner = ora('Analyzing vault...').start();\n\n try {\n const report = await loadConfigAndAnalyze(options.vault, options);\n spinner.succeed(`Analysis complete in ${report.duration}ms`);\n\n console.log('\\n📊 Vault Statistics\\n');\n console.log(` Total Notes: ${report.stats.totalNotes}`);\n console.log(` Total Links: ${report.stats.totalLinks}`);\n const averageLinks = report.stats.totalNotes === 0 ? 0 : report.stats.totalLinks / report.stats.totalNotes;\n const healthScore = report.stats.totalLinks === 0\n ? 100\n : 100 - (report.stats.brokenCount / report.stats.totalLinks) * 100;\n console.log(` Avg Links per Note: ${averageLinks.toFixed(2)}`);\n console.log(` Broken Links: ${report.stats.brokenCount}`);\n console.log(` Orphan Notes: ${report.stats.orphanCount}`);\n console.log(` Connected Components: ${report.stats.connectedComponents}`);\n console.log(` Health Score: ${healthScore.toFixed(1)}%`);\n\n const mostConnected = [...report.graph]\n .sort((a, b) => (b.incoming.length + b.outgoing.length) - (a.incoming.length + a.outgoing.length))\n .slice(0, 5);\n\n console.log('\\n🌐 Most Connected Notes:');\n for (const node of mostConnected) {\n const total = node.incoming.length + node.outgoing.length;\n console.log(` ${node.note.relativePath} (${total} connections: ${node.incoming.length} in, ${node.outgoing.length} out)`);\n }\n\n const lonelyNotes = report.graph.filter((n) => n.incoming.length === 0 && n.outgoing.length === 0).slice(0, 5);\n if (lonelyNotes.length > 0) {\n console.log('\\n🔴 Orphan Notes:');\n for (const node of lonelyNotes) {\n console.log(` ${node.note.relativePath}`);\n }\n }\n\n console.log();\n } catch (error) {\n spinner.fail('Analysis failed');\n console.error(error instanceof Error ? error.message : error);\n process.exit(1);\n }\n });\n\nprogram.parse();\n","import { readFile } from 'node:fs/promises';\nimport { resolve } from 'node:path';\nimport fg from 'fast-glob';\nimport matter from 'gray-matter';\nimport type { Note } from '../types/index.js';\n\nconst DEFAULT_IGNORE = [\n '**/node_modules/**',\n '**/.git/**',\n '**/.obsidian/**',\n];\n\nexport async function loadVault(\n vaultPath: string,\n customIgnore: string[] = [],\n): Promise<Note[]> {\n const absolutePath = resolve(vaultPath);\n\n const files = await fg(['**/*.md', '**/*.canvas'], {\n cwd: absolutePath,\n ignore: [...DEFAULT_IGNORE, ...customIgnore],\n });\n\n const notes: Note[] = [];\n\n for (const file of files) {\n const fullPath = resolve(absolutePath, file);\n const relativePath = file.replace(/\\\\/g, '/');\n const content = await readFile(fullPath, 'utf-8');\n const parsed = matter(content);\n\n const aliases: string[] = [];\n if (parsed.data.aliases) {\n if (Array.isArray(parsed.data.aliases)) {\n aliases.push(...parsed.data.aliases.map(String));\n } else if (typeof parsed.data.aliases === 'string') {\n aliases.push(parsed.data.aliases);\n }\n }\n\n notes.push({\n path: fullPath,\n relativePath,\n content: parsed.content,\n aliases,\n links: [],\n });\n }\n\n return notes;\n}\n\nexport function resolveFileName(notePath: string): string {\n const base = notePath.replace(/\\.(md|canvas)$/, '');\n const parts = base.split('/');\n return parts[parts.length - 1]!;\n}\n","import type { Link } from '../types/index.js';\n\nconst wikiLinkRegex = /!?\\[\\[([^\\]]+)\\]\\]/g;\nconst markdownLinkRegex = /\\[([^\\]]*)\\]\\(([^)]+)\\)/g;\n\nexport function parseLinks(content: string): Link[] {\n const links: Link[] = [];\n const lines = content.split('\\n');\n\n for (let lineNum = 0; lineNum < lines.length; lineNum++) {\n const line = lines[lineNum]!;\n\n for (const match of line.matchAll(wikiLinkRegex)) {\n const raw = match[0]!;\n const inner = match[1]!;\n const isEmbed = raw.startsWith('!');\n\n const link = parseWikiLink(inner, raw, isEmbed, lineNum + 1);\n links.push(link);\n }\n\n for (const match of line.matchAll(markdownLinkRegex)) {\n const raw = match[0]!;\n const target = match[2]!;\n\n if (target.startsWith('#')) continue;\n\n const link: Link = {\n raw,\n target: target.split('#')[0]!.split('|')[0]!,\n heading: target.includes('#') ? target.split('#')[1] : undefined,\n type: 'markdown',\n line: lineNum + 1,\n };\n links.push(link);\n }\n }\n\n return links;\n}\n\nfunction parseWikiLink(inner: string, raw: string, isEmbed: boolean, line: number): Link {\n const [targetPart, aliasPart] = inner.split('|');\n const [target, ...rest] = targetPart!.split('#');\n\n let heading: string | undefined;\n let blockId: string | undefined;\n\n if (rest.length > 0) {\n const remainder = rest.join('#');\n if (remainder.startsWith('^')) {\n blockId = remainder.slice(1);\n } else {\n heading = remainder;\n }\n }\n\n return {\n raw,\n target: target!.trim(),\n alias: aliasPart?.trim(),\n heading,\n blockId,\n type: isEmbed ? 'embed' : 'wiki',\n line,\n };\n}\n","import { existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport type { Note, GraphNode, BrokenLink, AnalysisReport } from '../types/index.js';\nimport { AliasResolver } from './alias-resolver.js';\nimport { buildGraph } from './graph-builder.js';\nimport { isExternalTarget, resolveAttachmentPath, resolveNoteLink } from './link-resolver.js';\n\nconst ATTACHMENT_EXTENSIONS = new Set([\n 'png', 'jpg', 'jpeg', 'gif', 'svg', 'webp', 'bmp', 'ico',\n 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx',\n 'mp3', 'mp4', 'wav', 'avi', 'mov', 'webm',\n 'zip', 'rar', '7z', 'tar', 'gz',\n 'exe', 'dmg', 'apk',\n 'css', 'js', 'ts', 'html',\n 'txt', 'csv', 'log',\n 'woff', 'woff2', 'ttf', 'eot',\n]);\n\nfunction isAttachment(target: string): boolean {\n const ext = target.split('.').pop()?.toLowerCase();\n return ext ? ATTACHMENT_EXTENSIONS.has(ext) : false;\n}\n\nexport function analyze(\n notes: Note[],\n vaultPath: string,\n checkAttachments = false,\n ignoreFolders: string[] = [],\n): AnalysisReport {\n const resolver = new AliasResolver(notes);\n const graph = buildGraph(notes, resolver, vaultPath);\n\n const brokenLinks: BrokenLink[] = [];\n const orphanNotes: Note[] = [];\n\n for (const node of graph) {\n if (node.incoming.length === 0 && node.outgoing.length === 0) {\n if (!isIgnoredFolder(node.note.relativePath, ignoreFolders)) {\n orphanNotes.push(node.note);\n }\n }\n\n for (const link of node.note.links) {\n if (isExternalTarget(link.target)) {\n continue;\n }\n\n if (isAttachment(link.target)) {\n if (!checkAttachments) continue;\n const attachmentPath = resolveAttachmentPath(link.target, node.note, vaultPath);\n if (!existsSync(attachmentPath)) {\n brokenLinks.push({\n source: node.note.relativePath,\n link,\n reason: `File \"${link.target}\" does not exist`,\n });\n }\n continue;\n }\n\n const resolvedPath = resolveNoteLink(link, node.note, notes, resolver, vaultPath);\n\n if (resolvedPath === node.note.path) {\n continue;\n }\n\n if (!resolvedPath) {\n brokenLinks.push({\n source: node.note.relativePath,\n link,\n reason: `Target \"${link.target}\" not found in vault`,\n });\n }\n }\n }\n\n const connectedComponents = countConnectedComponents(graph);\n const totalLinks = notes.reduce((sum, n) => sum + n.links.length, 0);\n\n return {\n brokenLinks,\n orphanNotes,\n graph,\n stats: {\n totalNotes: notes.length,\n totalLinks,\n brokenCount: brokenLinks.length,\n orphanCount: orphanNotes.length,\n connectedComponents,\n },\n };\n}\n\nfunction isIgnoredFolder(relativePath: string, ignoreFolders: string[]): boolean {\n const folder = relativePath.includes('/')\n ? relativePath.split('/').slice(0, -1).join('/')\n : '';\n return ignoreFolders.some((ignored) => {\n const normalized = ignored.replace(/\\\\/g, '/').replace(/^\\/+|\\/+$/g, '');\n return folder === normalized || folder.startsWith(`${normalized}/`);\n });\n}\n\nfunction countConnectedComponents(graph: GraphNode[]): number {\n if (graph.length === 0) return 0;\n\n const visited = new Set<string>();\n let components = 0;\n\n const adjacency = new Map<string, string[]>();\n for (const node of graph) {\n adjacency.set(node.note.path, [...node.incoming, ...node.outgoing]);\n }\n\n for (const node of graph) {\n if (visited.has(node.note.path)) continue;\n\n components++;\n const stack = [node.note.path];\n\n while (stack.length > 0) {\n const current = stack.pop()!;\n if (visited.has(current)) continue;\n visited.add(current);\n\n const neighbors = adjacency.get(current) || [];\n for (const neighbor of neighbors) {\n if (!visited.has(neighbor)) {\n stack.push(neighbor);\n }\n }\n }\n }\n\n return components;\n}\n","import type { Note } from '../types/index.js';\n\nexport class AliasResolver {\n private aliasToNote: Map<string, string> = new Map();\n private fileNameToNote: Map<string, string> = new Map();\n private basenameToNote: Map<string, string> = new Map();\n\n constructor(notes: Note[]) {\n for (const note of notes) {\n const fileName = note.relativePath.replace(/\\.(md|canvas)$/, '');\n const basename = fileName.split('/').pop()!;\n\n this.fileNameToNote.set(fileName.toLowerCase(), note.path);\n this.basenameToNote.set(basename.toLowerCase(), note.path);\n\n try {\n this.fileNameToNote.set(decodeURIComponent(fileName).toLowerCase(), note.path);\n this.basenameToNote.set(decodeURIComponent(basename).toLowerCase(), note.path);\n } catch {\n // skip malformed URIs\n }\n\n for (const alias of note.aliases) {\n this.aliasToNote.set(alias.toLowerCase(), note.path);\n }\n }\n }\n\n resolve(target: string): string | null {\n const normalized = target.toLowerCase().trim();\n\n if (this.aliasToNote.has(normalized)) {\n return this.aliasToNote.get(normalized)!;\n }\n\n if (this.fileNameToNote.has(normalized)) {\n return this.fileNameToNote.get(normalized)!;\n }\n\n if (this.basenameToNote.has(normalized)) {\n return this.basenameToNote.get(normalized)!;\n }\n\n const decoded = (() => {\n try {\n return decodeURIComponent(normalized);\n } catch {\n return normalized;\n }\n })();\n\n if (this.aliasToNote.has(decoded)) {\n return this.aliasToNote.get(decoded)!;\n }\n if (this.fileNameToNote.has(decoded)) {\n return this.fileNameToNote.get(decoded)!;\n }\n if (this.basenameToNote.has(decoded)) {\n return this.basenameToNote.get(decoded)!;\n }\n\n return null;\n }\n}\n","import { dirname, isAbsolute, normalize, resolve } from 'node:path';\nimport type { Link, Note } from '../types/index.js';\nimport { AliasResolver } from './alias-resolver.js';\n\nconst REMOTE_TARGET = /^(?:[a-z][a-z\\d+.-]*:|\\/\\/)/i;\n\nexport function isExternalTarget(target: string): boolean {\n return REMOTE_TARGET.test(target) || target.startsWith('#');\n}\n\nexport function resolveNoteLink(\n link: Link,\n source: Note,\n notes: Note[],\n aliases: AliasResolver,\n vaultPath: string,\n): string | null {\n if (isExternalTarget(link.target)) return null;\n\n if (link.type === 'markdown') {\n const target = decodeTarget(link.target).replace(/\\\\/g, '/');\n const sourceRelativePath = normalize(resolve(dirname(source.path), target));\n const exact = notes.find((note) => normalize(note.path) === sourceRelativePath);\n if (exact) return exact.path;\n\n // A vault-relative Markdown destination is also valid in common Obsidian vaults.\n const vaultRelativePath = normalize(resolve(vaultPath, target.replace(/^\\/+/, '')));\n const vaultRelative = notes.find((note) => normalize(note.path) === vaultRelativePath);\n if (vaultRelative) return vaultRelative.path;\n }\n\n return aliases.resolve(decodeTarget(link.target));\n}\n\nexport function resolveAttachmentPath(\n target: string,\n source: Note,\n vaultPath: string,\n): string {\n const decoded = decodeTarget(target).replace(/\\\\/g, '/');\n if (isAbsolute(decoded)) return normalize(decoded);\n\n const relativeToSource = normalize(resolve(dirname(source.path), decoded));\n if (relativeToSource.startsWith(normalize(vaultPath))) return relativeToSource;\n\n return normalize(resolve(vaultPath, decoded.replace(/^\\/+/, '')));\n}\n\nfunction decodeTarget(target: string): string {\n try {\n return decodeURIComponent(target);\n } catch {\n return target;\n }\n}\n","import type { Note, GraphNode } from '../types/index.js';\nimport { AliasResolver } from './alias-resolver.js';\nimport { isExternalTarget, resolveNoteLink } from './link-resolver.js';\n\nexport function buildGraph(notes: Note[], resolver: AliasResolver, vaultPath: string): GraphNode[] {\n const notePathMap = new Map<string, GraphNode>();\n\n for (const note of notes) {\n notePathMap.set(note.path, {\n note,\n incoming: [],\n outgoing: [],\n });\n }\n\n for (const note of notes) {\n const node = notePathMap.get(note.path)!;\n\n for (const link of note.links) {\n if (isExternalTarget(link.target)) continue;\n\n const resolvedPath = resolveNoteLink(link, note, notes, resolver, vaultPath);\n\n if (resolvedPath && notePathMap.has(resolvedPath)) {\n if (!node.outgoing.includes(resolvedPath)) {\n node.outgoing.push(resolvedPath);\n }\n\n const targetNode = notePathMap.get(resolvedPath)!;\n if (!targetNode.incoming.includes(note.path)) {\n targetNode.incoming.push(note.path);\n }\n }\n }\n }\n\n return Array.from(notePathMap.values());\n}\n","import type { Note } from '../types/index.js';\r\n\r\nexport interface Suggestion {\r\n broken: string;\r\n suggested: string;\r\n similarity: number;\r\n source: string;\r\n}\r\n\r\nfunction levenshteinDistance(a: string, b: string): number {\r\n const matrix: number[][] = Array(b.length + 1)\r\n .fill(null)\r\n .map(() => Array(a.length + 1).fill(0));\r\n\r\n for (let i = 0; i <= a.length; i++) matrix[0]![i] = i;\r\n for (let j = 0; j <= b.length; j++) matrix[j]![0] = j;\r\n\r\n for (let j = 1; j <= b.length; j++) {\r\n for (let i = 1; i <= a.length; i++) {\r\n if (a[i - 1] === b[j - 1]) {\r\n matrix[j]![i] = matrix[j - 1]![i - 1]!;\r\n } else {\r\n matrix[j]![i] = Math.min(\r\n matrix[j]![i - 1]! + 1,\r\n matrix[j - 1]![i]! + 1,\r\n matrix[j - 1]![i - 1]! + 1,\r\n );\r\n }\r\n }\r\n }\r\n\r\n return matrix[b.length]![a.length]!;\r\n}\r\n\r\nfunction calculateSimilarity(a: string, b: string): number {\r\n const distance = levenshteinDistance(\r\n a.toLowerCase(),\r\n b.toLowerCase(),\r\n );\r\n const maxLen = Math.max(a.length, b.length);\r\n return Math.max(0, 1 - distance / maxLen);\r\n}\r\n\r\nexport function generateSuggestions(\r\n brokenTargets: string[],\r\n allNotes: Note[],\r\n threshold = 0.6,\r\n): Suggestion[] {\r\n const availableNotes = allNotes.map((n) => ({\r\n name: n.relativePath.replace(/\\.(md|canvas)$/, ''),\r\n basename: n.relativePath.split('/').pop()?.replace(/\\.(md|canvas)$/, '') || '',\r\n aliases: n.aliases,\r\n }));\r\n\r\n const suggestions: Suggestion[] = [];\r\n\r\n for (const broken of brokenTargets) {\r\n const brokenBasename = broken.split('/').pop() || broken;\r\n const candidates: Array<{ suggestion: string; similarity: number }> = [];\r\n\r\n for (const note of availableNotes) {\r\n const basenameScore = calculateSimilarity(brokenBasename, note.basename);\r\n const fullScore = calculateSimilarity(broken, note.name);\r\n\r\n // Check aliases too\r\n for (const alias of note.aliases) {\r\n const aliasScore = calculateSimilarity(broken, alias);\r\n if (aliasScore > basenameScore && aliasScore > fullScore) {\r\n candidates.push({ suggestion: note.name, similarity: aliasScore });\r\n }\r\n }\r\n\r\n if (basenameScore >= fullScore && basenameScore > threshold) {\r\n candidates.push({ suggestion: note.name, similarity: basenameScore });\r\n } else if (fullScore > threshold) {\r\n candidates.push({ suggestion: note.name, similarity: fullScore });\r\n }\r\n }\r\n\r\n // Sort by similarity and take top suggestion\r\n if (candidates.length > 0) {\r\n const best = candidates.sort((a, b) => b.similarity - a.similarity)[0]!;\r\n suggestions.push({\r\n broken,\r\n suggested: best.suggestion,\r\n similarity: best.similarity,\r\n source: broken,\r\n });\r\n }\r\n }\r\n\r\n return suggestions.sort((a, b) => b.similarity - a.similarity);\r\n}\r\n","import { loadVault } from '../core/vault-loader.js';\nimport { parseLinks } from '../core/link-parser.js';\nimport { analyze } from '../core/analyzer.js';\nimport { generateSuggestions } from './suggestions.js';\nimport type { AnalysisReport, DeadLinksConfig } from '../types/index.js';\n\nexport class DeadLinksEngine {\n async analyze(\n vaultPath: string,\n config: DeadLinksConfig = {},\n ): Promise<AnalysisReport> {\n const startTime = Date.now();\n\n const notes = await loadVault(vaultPath, config.ignore);\n\n // Parallelize link parsing\n await Promise.all(\n notes.map((note) => {\n note.links = parseLinks(note.content);\n return Promise.resolve();\n }),\n );\n\n const report = analyze(notes, vaultPath, config.checkAttachments, config.ignoreFolders);\n\n // Generate suggestions for broken links\n if (config.suggestions || config.fix) {\n const brokenTargets = Array.from(\n new Set(report.brokenLinks.map((b) => b.link.target)),\n );\n report.suggestions = generateSuggestions(brokenTargets, notes, 0.65);\n }\n\n report.duration = Date.now() - startTime;\n\n return report;\n }\n}\n","import { readFile, access } from 'node:fs/promises';\nimport { resolve, join } from 'node:path';\nimport type { DeadLinksConfig } from '../types/index.js';\n\nconst CONFIG_FILES = ['.deadlinksrc.json', '.deadlinksrc', 'dead-links.config.json'];\n\nexport class ConfigLoader {\n async loadConfig(vaultPath: string): Promise<DeadLinksConfig> {\n const absolutePath = resolve(vaultPath);\n\n for (const configFile of CONFIG_FILES) {\n const configPath = join(absolutePath, configFile);\n\n try {\n await access(configPath);\n const content = await readFile(configPath, 'utf-8');\n const parsed = JSON.parse(content) as DeadLinksConfig;\n\n if (parsed.ignore && !Array.isArray(parsed.ignore)) {\n continue;\n }\n if (parsed.ignoreFolders && !Array.isArray(parsed.ignoreFolders)) {\n continue;\n }\n\n return parsed;\n } catch {\n continue;\n }\n }\n\n return {};\n }\n\n mergeConfig(\n fileConfig: DeadLinksConfig,\n cliOptions: Record<string, unknown>,\n ): DeadLinksConfig {\n return {\n ignore: (cliOptions.ignore as string[] | undefined) || fileConfig.ignore,\n checkAttachments:\n (cliOptions.attachments as boolean | undefined) ??\n fileConfig.checkAttachments ??\n false,\n ignoreFolders:\n (cliOptions.ignoreFolders as string[] | undefined) ||\n fileConfig.ignoreFolders,\n suggestions:\n (cliOptions.suggestions as boolean | undefined) ??\n fileConfig.suggestions ??\n false,\n debug:\n (cliOptions.debug as boolean | undefined) ??\n fileConfig.debug ??\n false,\n fix:\n (cliOptions.fix as boolean | undefined) ??\n fileConfig.fix ??\n false,\n };\n }\n}\n","import { Chalk } from 'chalk';\nimport type {\n AnalysisReport,\n BrokenLink,\n Note,\n} from '../types/index.js';\n\nexport class TerminalReporter {\n private ignoreFolders: string[];\n private quiet: boolean;\n private chalk: Chalk;\n\n constructor(ignoreFolders: string[] = [], quiet = false, noColor = false) {\n this.ignoreFolders = ignoreFolders;\n this.quiet = quiet;\n this.chalk = new Chalk({ level: noColor ? 0 : undefined });\n }\n\n report(result: AnalysisReport): string {\n if (this.quiet) {\n return this.reportQuiet(result);\n }\n\n let output = '';\n output += this.printStats(result);\n output += this.printBrokenLinks(result.brokenLinks);\n output += this.printOrphanNotes(result.orphanNotes);\n if (result.suggestions && result.suggestions.length > 0) {\n output += this.printSuggestions(result.suggestions);\n }\n output += this.printDuration(result.duration);\n return output;\n }\n\n private reportQuiet(result: AnalysisReport): string {\n const lines: string[] = [];\n\n for (const broken of result.brokenLinks) {\n lines.push(`${broken.source} -> ${broken.link.target}`);\n }\n\n for (const note of result.orphanNotes) {\n const folder = note.relativePath.includes('/')\n ? note.relativePath.split('/').slice(0, -1).join('/') + '/'\n : './';\n if (this.ignoreFolders.some((f) => folder.startsWith(f + '/') || folder === f + '/')) {\n continue;\n }\n lines.push(`orphan: ${note.relativePath}`);\n }\n\n return lines.join('\\n');\n }\n\n private printStats(report: AnalysisReport): string {\n const pad = 14;\n let out = '\\n';\n out += this.statLine('Notes'.padEnd(pad), String(report.stats.totalNotes));\n out += this.statLine('Links'.padEnd(pad), String(report.stats.totalLinks));\n out += this.statLine('Broken links'.padEnd(pad), String(report.stats.brokenCount), 'red');\n out += this.statLine('Orphan notes'.padEnd(pad), String(report.stats.orphanCount), 'yellow');\n out += this.statLine('Components'.padEnd(pad), String(report.stats.connectedComponents));\n out += '\\n';\n return out;\n }\n\n private statLine(label: string, value: string, color: 'red' | 'yellow' | 'white' = 'white'): string {\n const chalk = this.chalk;\n return ` ${this.chalk.gray(label)} ${color === 'red' ? this.chalk.red.bold(value) : color === 'yellow' ? this.chalk.yellow.bold(value) : this.chalk.white.bold(value)}\\n`;\n }\n\n private printBrokenLinks(brokenLinks: BrokenLink[]): string {\n if (brokenLinks.length === 0) {\n return this.chalk.green(' ✓ No broken links\\n\\n');\n }\n\n const grouped = new Map<string, BrokenLink[]>();\n for (const broken of brokenLinks) {\n const existing = grouped.get(broken.source) || [];\n existing.push(broken);\n grouped.set(broken.source, existing);\n }\n\n const sorted = [...grouped.entries()].sort((a, b) => b[1].length - a[1].length);\n\n let out = this.chalk.bold.red(`\\n✖ Broken links (${brokenLinks.length})\\n\\n`);\n\n for (const [source, links] of sorted) {\n const count = links.length;\n const isMultiple = count >= 2;\n\n const countStr = isMultiple ? this.chalk.red.bold(`✖ ${count}`) : this.chalk.yellow.bold(`✖ ${count}`);\n out += ` ${countStr} ${this.chalk.white.bold(source)}\\n`;\n\n for (const link of links) {\n const pill = isMultiple\n ? this.chalk.redBright(`[${link.link.target}]`)\n : this.chalk.yellowBright(`[${link.link.target}]`);\n out += ` ${pill}\\n`;\n }\n out += '\\n';\n }\n\n return out;\n }\n\n private printOrphanNotes(orphanNotes: Note[]): string {\n if (orphanNotes.length === 0) {\n return this.chalk.green(' ✓ No orphan notes\\n\\n');\n }\n\n const byFolder = new Map<string, number>();\n for (const note of orphanNotes) {\n const folder = note.relativePath.includes('/')\n ? note.relativePath.split('/').slice(0, -1).join('/') + '/'\n : './';\n\n if (this.ignoreFolders.some((f) => folder.startsWith(f + '/') || folder === f + '/')) {\n continue;\n }\n\n byFolder.set(folder, (byFolder.get(folder) || 0) + 1);\n }\n\n const sorted = [...byFolder.entries()].sort((a, b) => b[1] - a[1]);\n\n if (sorted.length === 0) {\n return this.chalk.green(' ✓ No orphan notes (all in ignored folders)\\n\\n');\n }\n\n const maxCount = sorted[0]![1];\n const barWidth = 10;\n\n let out = this.chalk.bold.yellow(`\\n⚠ Orphan notes (${orphanNotes.length} total)\\n\\n`);\n\n for (const [folder, count] of sorted) {\n const filled = Math.round((count / maxCount) * barWidth);\n const empty = barWidth - filled;\n const bar = this.chalk.gray('█'.repeat(filled) + '░'.repeat(empty));\n out += ` ${this.chalk.gray(folder.padEnd(40))} ${bar} ${this.chalk.white.bold(String(count))}\\n`;\n }\n\n out += '\\n';\n return out;\n }\n\n private printDuration(ms: number): string {\n return this.chalk.dim(` Completed in ${ms}ms\\n`);\n }\n\n private printSuggestions(\n suggestions: Array<{ broken: string; suggested: string; similarity: number }>,\n ): string {\n let out = this.chalk.bold.cyan(`\\n💡 Suggestions (${suggestions.length})\\n\\n`);\n\n for (const suggestion of suggestions.slice(0, 5)) {\n const confidence = Math.round(suggestion.similarity * 100);\n const bar =\n confidence >= 80\n ? this.chalk.green('█████')\n : confidence >= 60\n ? this.chalk.yellow('█████')\n : this.chalk.gray('█████');\n\n out += ` ${this.chalk.gray(suggestion.broken)} → ${this.chalk.cyan(suggestion.suggested)}\\n`;\n out += ` ${bar} ${confidence}%\\n\\n`;\n }\n\n return out;\n }\n}\n","import type { AnalysisReport } from '../types/index.js';\n\nexport class JsonReporter {\n report(result: AnalysisReport): string {\n return JSON.stringify(\n {\n stats: result.stats,\n brokenLinks: result.brokenLinks,\n orphanNotes: result.orphanNotes.map((n) => ({\n path: n.relativePath,\n })),\n graph: result.graph.map((n) => ({\n note: n.note.relativePath,\n incoming: n.incoming.length,\n outgoing: n.outgoing.length,\n })),\n duration: result.duration,\n },\n null,\n 2,\n );\n }\n}\n","import type { AnalysisReport } from '../types/index.js';\r\n\r\nexport class CsvReporter {\r\n report(result: AnalysisReport): string {\r\n const lines: string[] = [];\r\n\r\n // Broken Links CSV\r\n lines.push('# BROKEN LINKS');\r\n lines.push('Source,Target,Type,Line,Reason');\r\n\r\n for (const broken of result.brokenLinks) {\r\n const source = this.escapeCsv(broken.source);\r\n const target = this.escapeCsv(broken.link.target);\r\n const type = broken.link.type;\r\n const line = broken.link.line;\r\n const reason = this.escapeCsv(broken.reason);\r\n\r\n lines.push(`${source},${target},${type},${line},\"${reason}\"`);\r\n }\r\n\r\n // Orphan Notes CSV\r\n lines.push('\\n# ORPHAN NOTES');\r\n lines.push('Note,Path');\r\n\r\n for (const orphan of result.orphanNotes) {\r\n const name = this.escapeCsv(orphan.relativePath);\r\n lines.push(`${name},\"${name}\"`);\r\n }\r\n\r\n // Graph Stats CSV\r\n lines.push('\\n# GRAPH STATISTICS');\r\n lines.push('Metric,Value');\r\n lines.push(`Total Notes,${result.stats.totalNotes}`);\r\n lines.push(`Total Links,${result.stats.totalLinks}`);\r\n lines.push(`Broken Links,${result.stats.brokenCount}`);\r\n lines.push(`Orphan Notes,${result.stats.orphanCount}`);\r\n lines.push(`Connected Components,${result.stats.connectedComponents}`);\r\n lines.push(`Duration (ms),${result.duration}`);\r\n\r\n return lines.join('\\n');\r\n }\r\n\r\n private escapeCsv(value: string): string {\r\n if (value.includes(',') || value.includes('\"') || value.includes('\\n')) {\r\n return `\"${value.replace(/\"/g, '\"\"')}\"`;\r\n }\r\n return value;\r\n }\r\n}\r\n","import type { AnalysisReport, BrokenLink, GraphNode, Note } from '../types/index.js';\n\r\nexport class HtmlReporter {\r\n report(result: AnalysisReport): string {\r\n const brokenLinksHtml = this.renderBrokenLinks(result.brokenLinks);\r\n const orphanNotesHtml = this.renderOrphanNotes(result.orphanNotes);\r\n const graphHtml = this.renderGraph(result.graph);\r\n const statsHtml = this.renderStats(result.stats, result.duration);\r\n\r\n return `<!DOCTYPE html>\r\n<html lang=\"en\">\r\n<head>\r\n <meta charset=\"UTF-8\">\r\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n <title>Dead Links Report</title>\r\n <style>\r\n * { margin: 0; padding: 0; box-sizing: border-box; }\r\n body {\r\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\r\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\r\n min-height: 100vh;\r\n padding: 20px;\r\n }\r\n .container {\r\n max-width: 1400px;\r\n margin: 0 auto;\r\n background: white;\r\n border-radius: 12px;\r\n box-shadow: 0 20px 60px rgba(0,0,0,0.3);\r\n overflow: hidden;\r\n }\r\n header {\r\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\r\n color: white;\r\n padding: 40px;\r\n text-align: center;\r\n }\r\n header h1 { font-size: 2.5em; margin-bottom: 10px; }\r\n header p { font-size: 1.1em; opacity: 0.9; }\r\n .content { padding: 40px; }\r\n .section {\r\n margin-bottom: 40px;\r\n border-left: 4px solid #667eea;\r\n padding-left: 20px;\r\n }\r\n .section h2 {\r\n font-size: 1.8em;\r\n margin-bottom: 20px;\r\n color: #333;\r\n }\r\n .stats-grid {\r\n display: grid;\r\n grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\r\n gap: 20px;\r\n margin-bottom: 40px;\r\n }\r\n .stat-card {\r\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\r\n color: white;\r\n padding: 20px;\r\n border-radius: 8px;\r\n text-align: center;\r\n }\r\n .stat-card .value { font-size: 2.5em; font-weight: bold; }\r\n .stat-card .label { font-size: 0.9em; opacity: 0.9; margin-top: 10px; }\r\n table {\r\n width: 100%;\r\n border-collapse: collapse;\r\n margin-top: 20px;\r\n }\r\n th {\r\n background: #f5f5f5;\r\n padding: 12px;\r\n text-align: left;\r\n border-bottom: 2px solid #667eea;\r\n font-weight: 600;\r\n }\r\n td {\r\n padding: 12px;\r\n border-bottom: 1px solid #eee;\r\n }\r\n tr:hover { background: #f9f9f9; }\r\n .badge {\r\n display: inline-block;\r\n padding: 4px 8px;\r\n border-radius: 4px;\r\n font-size: 0.85em;\r\n font-weight: 500;\r\n }\r\n .badge-wiki { background: #dbeafe; color: #1e40af; }\r\n .badge-embed { background: #fed7aa; color: #92400e; }\r\n .badge-markdown { background: #d1fae5; color: #065f46; }\r\n .error { color: #dc2626; }\r\n .success { color: #16a34a; }\r\n .tabs {\r\n display: flex;\r\n gap: 0;\r\n border-bottom: 2px solid #eee;\r\n margin-bottom: 20px;\r\n }\r\n .tab-button {\r\n padding: 12px 20px;\r\n border: none;\r\n background: none;\r\n cursor: pointer;\r\n font-size: 1em;\r\n border-bottom: 3px solid transparent;\r\n color: #666;\r\n transition: all 0.3s;\r\n }\r\n .tab-button.active {\r\n color: #667eea;\r\n border-bottom-color: #667eea;\r\n }\r\n .tab-content { display: none; }\r\n .tab-content.active { display: block; }\r\n .filter-box {\r\n margin: 20px 0;\r\n padding: 15px;\r\n background: #f9f9f9;\r\n border-radius: 8px;\r\n }\r\n .filter-box input {\r\n width: 100%;\r\n padding: 8px;\r\n border: 1px solid #ddd;\r\n border-radius: 4px;\r\n font-size: 1em;\r\n }\r\n footer {\r\n background: #f5f5f5;\r\n padding: 20px;\r\n text-align: center;\r\n color: #666;\r\n font-size: 0.9em;\r\n }\r\n </style>\r\n</head>\r\n<body>\r\n <div class=\"container\">\r\n <header>\r\n <h1>🔍 Dead Links Report</h1>\r\n <p>Obsidian Vault Analysis</p>\r\n </header>\r\n\r\n <div class=\"content\">\r\n ${statsHtml}\r\n ${brokenLinksHtml}\r\n ${orphanNotesHtml}\r\n ${graphHtml}\r\n </div>\r\n\r\n <footer>\r\n Generated on ${new Date().toLocaleString()}\r\n </footer>\r\n </div>\r\n\r\n <script>\r\n function setupTabs() {\r\n const buttons = document.querySelectorAll('.tab-button');\r\n const contents = document.querySelectorAll('.tab-content');\r\n\r\n buttons.forEach((button, index) => {\r\n button.addEventListener('click', () => {\r\n buttons.forEach(b => b.classList.remove('active'));\r\n contents.forEach(c => c.classList.remove('active'));\r\n button.classList.add('active');\r\n contents[index].classList.add('active');\r\n });\r\n });\r\n\r\n // Activate first tab\r\n if (buttons.length > 0) buttons[0].classList.add('active');\r\n if (contents.length > 0) contents[0].classList.add('active');\r\n }\r\n\r\n function setupFilter(tableId, inputId) {\r\n const input = document.getElementById(inputId);\r\n const table = document.getElementById(tableId);\r\n if (!input || !table) return;\r\n\r\n input.addEventListener('keyup', (e) => {\r\n const filter = e.target.value.toLowerCase();\r\n const rows = table.querySelectorAll('tbody tr');\r\n rows.forEach(row => {\r\n const text = row.textContent.toLowerCase();\r\n row.style.display = text.includes(filter) ? '' : 'none';\r\n });\r\n });\r\n }\r\n\r\n document.addEventListener('DOMContentLoaded', () => {\r\n setupTabs();\r\n setupFilter('broken-table', 'broken-filter');\r\n setupFilter('orphan-table', 'orphan-filter');\r\n });\r\n </script>\r\n</body>\r\n</html>`;\r\n }\r\n\r\n private renderStats(\r\n stats: {\r\n totalNotes: number;\r\n totalLinks: number;\r\n brokenCount: number;\r\n orphanCount: number;\r\n connectedComponents: number;\r\n },\r\n duration: number,\r\n ): string {\r\n const healthColor =\r\n stats.brokenCount === 0 && stats.orphanCount === 0 ? '#16a34a' : '#dc2626';\r\n\r\n return `\r\n <div class=\"section\">\r\n <h2>📊 Overview</h2>\r\n <div class=\"stats-grid\">\r\n <div class=\"stat-card\">\r\n <div class=\"value\">${stats.totalNotes}</div>\r\n <div class=\"label\">Total Notes</div>\r\n </div>\r\n <div class=\"stat-card\">\r\n <div class=\"value\">${stats.totalLinks}</div>\r\n <div class=\"label\">Total Links</div>\r\n </div>\r\n <div class=\"stat-card\" style=\"background: linear-gradient(135deg, #dc2626 0%, #991b1b 100%);\">\r\n <div class=\"value\">${stats.brokenCount}</div>\r\n <div class=\"label\">Broken Links</div>\r\n </div>\r\n <div class=\"stat-card\" style=\"background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);\">\r\n <div class=\"value\">${stats.orphanCount}</div>\r\n <div class=\"label\">Orphan Notes</div>\r\n </div>\r\n <div class=\"stat-card\" style=\"background: linear-gradient(135deg, ${healthColor} 0%, ${healthColor === '#16a34a' ? '#15803d' : '#991b1b'} 100%);\">\r\n <div class=\"value\">${stats.connectedComponents}</div>\r\n <div class=\"label\">Connected Components</div>\r\n </div>\r\n <div class=\"stat-card\" style=\"background: linear-gradient(135deg, #8b5cf6 0%, #6d28d9 100%);\">\r\n <div class=\"value\">${duration}ms</div>\r\n <div class=\"label\">Scan Duration</div>\r\n </div>\r\n </div>\r\n </div>\r\n `;\r\n }\r\n\r\n private renderBrokenLinks(brokenLinks: BrokenLink[]): string {\n if (brokenLinks.length === 0) {\r\n return `\r\n <div class=\"section\">\r\n <h2>✅ Broken Links</h2>\r\n <p class=\"success\">No broken links found!</p>\r\n </div>\r\n `;\r\n }\r\n\r\n const rows = brokenLinks\r\n .map(\r\n (link) => `\r\n <tr>\r\n <td>${this.escapeHtml(link.source)}</td>\r\n <td>${this.escapeHtml(link.link.target)}</td>\r\n <td><span class=\"badge badge-${link.link.type}\">${link.link.type}</span></td>\r\n <td>${link.link.line}</td>\r\n <td>${this.escapeHtml(link.reason)}</td>\r\n </tr>\r\n `,\r\n )\r\n .join('');\r\n\r\n return `\r\n <div class=\"section\">\r\n <h2>🔴 Broken Links (${brokenLinks.length})</h2>\r\n <div class=\"filter-box\">\r\n <input type=\"text\" id=\"broken-filter\" placeholder=\"Filter by source or target...\" />\r\n </div>\r\n <table id=\"broken-table\">\r\n <thead>\r\n <tr>\r\n <th>Source</th>\r\n <th>Target</th>\r\n <th>Type</th>\r\n <th>Line</th>\r\n <th>Reason</th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n ${rows}\r\n </tbody>\r\n </table>\r\n </div>\r\n `;\r\n }\r\n\r\n private renderOrphanNotes(orphanNotes: Note[]): string {\n if (orphanNotes.length === 0) {\r\n return `\r\n <div class=\"section\">\r\n <h2>✅ Orphan Notes</h2>\r\n <p class=\"success\">No orphan notes found!</p>\r\n </div>\r\n `;\r\n }\r\n\r\n const rows = orphanNotes\r\n .map(\r\n (note) => `\r\n <tr>\r\n <td>${this.escapeHtml(note.relativePath)}</td>\r\n </tr>\r\n `,\r\n )\r\n .join('');\r\n\r\n return `\r\n <div class=\"section\">\r\n <h2>🟡 Orphan Notes (${orphanNotes.length})</h2>\r\n <div class=\"filter-box\">\r\n <input type=\"text\" id=\"orphan-filter\" placeholder=\"Filter by path...\" />\r\n </div>\r\n <table id=\"orphan-table\">\r\n <thead>\r\n <tr>\r\n <th>Path</th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n ${rows}\r\n </tbody>\r\n </table>\r\n </div>\r\n `;\r\n }\r\n\r\n private renderGraph(graph: GraphNode[]): string {\n const mostConnected = [...graph]\r\n .sort((a, b) => (b.incoming.length + b.outgoing.length) - (a.incoming.length + a.outgoing.length))\r\n .slice(0, 5);\r\n\r\n const rows = mostConnected\r\n .map(\r\n (node) => `\r\n <tr>\r\n <td>${this.escapeHtml(node.note.relativePath)}</td>\r\n <td>${node.incoming.length}</td>\r\n <td>${node.outgoing.length}</td>\r\n <td>${node.incoming.length + node.outgoing.length}</td>\r\n </tr>\r\n `,\r\n )\r\n .join('');\r\n\r\n return `\r\n <div class=\"section\">\r\n <h2>🌐 Most Connected Notes</h2>\r\n <table>\r\n <thead>\r\n <tr>\r\n <th>Note</th>\r\n <th>Incoming</th>\r\n <th>Outgoing</th>\r\n <th>Total</th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n ${rows}\r\n </tbody>\r\n </table>\r\n </div>\r\n `;\r\n }\r\n\r\n private escapeHtml(text: string): string {\r\n return text\r\n .replace(/&/g, '&amp;')\r\n .replace(/</g, '&lt;')\r\n .replace(/>/g, '&gt;')\r\n .replace(/\"/g, '&quot;')\r\n .replace(/'/g, '&#039;');\r\n }\r\n}\r\n","import type { AnalysisReport } from '../types/index.js';\r\n\r\nexport class MermaidReporter {\r\n report(result: AnalysisReport, maxNodes = 50): string {\r\n let output = 'graph LR\\n';\r\n\r\n // Limit nodes for readability\r\n const nodesToShow = result.graph.slice(0, maxNodes);\r\n const nodeMap = new Map(nodesToShow.map((n) => [n.note.path, n.note.relativePath]));\r\n\r\n // Add nodes\r\n for (const node of nodesToShow) {\r\n const id = this.sanitizeId(node.note.relativePath);\r\n const label = this.escapeLabel(node.note.relativePath);\r\n\r\n if (result.orphanNotes.some((o) => o.path === node.note.path)) {\r\n output += ` ${id}[\"<b>🔴 ${label}</b>\"]:::orphan\\n`;\r\n } else if (node.incoming.length === 0) {\r\n output += ` ${id}[\"📄 ${label}\"]:::source\\n`;\r\n } else if (node.outgoing.length === 0) {\r\n output += ` ${id}[\"📌 ${label}\"]:::sink\\n`;\r\n } else {\r\n output += ` ${id}[\"${label}\"]\\n`;\r\n }\r\n }\r\n\r\n // Add edges\r\n const addedEdges = new Set<string>();\r\n for (const node of nodesToShow) {\r\n const sourceId = this.sanitizeId(node.note.relativePath);\r\n\r\n for (const targetPath of node.outgoing) {\r\n const targetName = nodeMap.get(targetPath);\r\n if (!targetName) continue;\r\n\r\n const targetId = this.sanitizeId(targetName);\r\n const edgeKey = `${sourceId}-${targetId}`;\r\n\r\n if (!addedEdges.has(edgeKey)) {\r\n output += ` ${sourceId} --> ${targetId}\\n`;\r\n addedEdges.add(edgeKey);\r\n }\r\n }\r\n }\r\n\r\n // Add styling\r\n output += '\\n classDef orphan fill:#ff6b6b,stroke:#c92a2a,color:#fff\\n';\r\n output += ' classDef source fill:#4dabf7,stroke:#1971c2,color:#fff\\n';\r\n output += ' classDef sink fill:#51cf66,stroke:#2f9e44,color:#fff\\n';\r\n\r\n if (result.graph.length > maxNodes) {\r\n output += `\\n note[\"Graph limited to ${maxNodes} of ${result.graph.length} nodes for readability\"]\\n`;\r\n }\r\n\r\n return output;\r\n }\r\n\r\n private sanitizeId(name: string): string {\r\n return name\r\n .replace(/\\.(md|canvas)$/, '')\r\n .replace(/[^a-zA-Z0-9_]/g, '_')\r\n .substring(0, 30);\r\n }\r\n\r\n private escapeLabel(name: string): string {\r\n return name.replace(/\"/g, '\\\\\"').substring(0, 50);\r\n }\r\n}\r\n","import type { AnalysisReport } from '../types/index.js';\r\n\r\nexport class GraphVizReporter {\r\n report(result: AnalysisReport, maxNodes = 100): string {\r\n let output = 'digraph VaultGraph {\\n';\r\n output += ' rankdir=LR;\\n';\r\n output += ' node [shape=box, style=rounded];\\n';\r\n output += ' edge [arrowsize=0.5];\\n\\n';\r\n\r\n const nodesToShow = result.graph.slice(0, maxNodes);\r\n const nodeMap = new Map(nodesToShow.map((n) => [n.note.path, n.note.relativePath]));\r\n\r\n // Add nodes with styling\r\n for (const node of nodesToShow) {\r\n const id = this.sanitizeId(node.note.relativePath);\r\n const label = this.escapeLabel(node.note.relativePath);\n let style = 'filled';\r\n let color = '#ffffff';\r\n let fontColor = '#000000';\r\n let shape = 'box';\r\n\r\n if (result.orphanNotes.some((o) => o.path === node.note.path)) {\r\n color = '#ff6b6b';\r\n fontColor = '#ffffff';\r\n shape = 'box';\r\n } else if (node.incoming.length === 0) {\r\n color = '#4dabf7';\r\n fontColor = '#ffffff';\r\n } else if (node.outgoing.length === 0) {\r\n color = '#51cf66';\r\n fontColor = '#ffffff';\r\n }\r\n\r\n output += ` \"${id}\" [label=\"${label}\", fillcolor=\"${color}\", fontcolor=\"${fontColor}\", shape=${shape}];\\n`;\r\n }\r\n\r\n output += '\\n';\r\n\r\n // Add edges\r\n const addedEdges = new Set<string>();\r\n for (const node of nodesToShow) {\r\n const sourceId = this.sanitizeId(node.note.relativePath);\r\n\r\n for (const targetPath of node.outgoing) {\r\n const targetName = nodeMap.get(targetPath);\r\n if (!targetName) continue;\r\n\r\n const targetId = this.sanitizeId(targetName);\r\n const edgeKey = `${sourceId}->${targetId}`;\r\n\r\n if (!addedEdges.has(edgeKey)) {\r\n output += ` \"${sourceId}\" -> \"${targetId}\";\\n`;\r\n addedEdges.add(edgeKey);\r\n }\r\n }\r\n }\r\n\r\n output += '\\n // Legend\\n';\r\n output += ' subgraph legend {\\n';\r\n output += ' rank=sink;\\n';\r\n output += ' orphan [label=\"🔴 Orphan\", fillcolor=\"#ff6b6b\", fontcolor=\"white\"];\\n';\r\n output += ' source [label=\"📄 Source (no incoming)\", fillcolor=\"#4dabf7\", fontcolor=\"white\"];\\n';\r\n output += ' sink [label=\"📌 Sink (no outgoing)\", fillcolor=\"#51cf66\", fontcolor=\"white\"];\\n';\r\n output += ' }\\n';\r\n\r\n output += '}\\n';\r\n\r\n return output;\r\n }\r\n\r\n private sanitizeId(name: string): string {\n return name.replace(/\\.(md|canvas)$/, '').replace(/[^a-zA-Z0-9_]/g, '_');\n }\n\n private escapeLabel(label: string): string {\n return label.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"');\n }\n}\n","import type { AnalysisReport } from '../types/index.js';\n\nfunction pill(target: string, isMultiple: boolean): string {\n return isMultiple ? `[${target}]` : `[${target}]`;\n}\n\nfunction bar(count: number, max: number, width: number): string {\n const filled = Math.round((count / max) * width);\n const empty = width - filled;\n return '█'.repeat(filled) + '░'.repeat(empty);\n}\n\nexport function generateMarkdownReport(report: AnalysisReport): string {\n const lines: string[] = [];\n\n lines.push('# Dead Links Report');\n lines.push('');\n lines.push(`Generated: ${new Date().toISOString()}`);\n lines.push(`Completed in ${report.duration}ms`);\n lines.push('');\n\n lines.push('## Stats');\n lines.push('');\n lines.push(`| Metric | Value |`);\n lines.push(`|--------|-------|`);\n lines.push(`| Notes | ${report.stats.totalNotes} |`);\n lines.push(`| Links | ${report.stats.totalLinks} |`);\n lines.push(`| Broken | ${report.stats.brokenCount} |`);\n lines.push(`| Orphans | ${report.stats.orphanCount} |`);\n lines.push(`| Components | ${report.stats.connectedComponents} |`);\n lines.push('');\n\n lines.push('## Broken Links');\n lines.push('');\n\n if (report.brokenLinks.length === 0) {\n lines.push('No broken links found.');\n } else {\n const grouped = new Map<string, typeof report.brokenLinks>();\n for (const b of report.brokenLinks) {\n const existing = grouped.get(b.source) || [];\n existing.push(b);\n grouped.set(b.source, existing);\n }\n\n const sorted = [...grouped.entries()].sort((a, b) => b[1].length - a[1].length);\n\n for (const [source, links] of sorted) {\n const count = links.length;\n lines.push(`### ${count > 1 ? '✖' : '✖'} ${count} ${source}`);\n lines.push('');\n for (const link of links) {\n lines.push(`- ${pill(link.link.target, count > 1)}`);\n }\n lines.push('');\n }\n }\n\n lines.push('## Orphan Notes');\n lines.push('');\n\n if (report.orphanNotes.length === 0) {\n lines.push('No orphan notes found.');\n } else {\n const byFolder = new Map<string, number>();\n for (const note of report.orphanNotes) {\n const folder = note.relativePath.includes('/')\n ? note.relativePath.split('/').slice(0, -1).join('/') + '/'\n : './';\n byFolder.set(folder, (byFolder.get(folder) || 0) + 1);\n }\n\n const sorted = [...byFolder.entries()].sort((a, b) => b[1] - a[1]);\n const maxCount = sorted[0]?.[1] ?? 1;\n\n for (const [folder, count] of sorted) {\n lines.push(`- ${folder.padEnd(40)} ${bar(count, maxCount, 10)} ${count}`);\n }\n }\n\n lines.push('');\n\n return lines.join('\\n');\n}\n","import { readFile, writeFile } from 'node:fs/promises';\nimport { resolve } from 'node:path';\nimport type { BrokenLink } from '../types/index.js';\n\r\nexport interface FixResult {\r\n file: string;\r\n originalLink: string;\r\n newLink: string;\r\n success: boolean;\r\n error?: string;\r\n}\r\n\r\nfunction escapeRegExp(string: string): string {\r\n return string.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\r\n}\r\n\r\nexport async function autoFixBrokenLinks(\r\n fixMap: Map<string, string>,\r\n filePath: string,\r\n): Promise<FixResult[]> {\r\n const results: FixResult[] = [];\r\n\r\n try {\r\n const content = await readFile(filePath, 'utf-8');\r\n let newContent = content;\r\n\r\n for (const [oldTarget, newTarget] of fixMap.entries()) {\r\n // For wiki links: [[oldTarget]] or ![[oldTarget]]\r\n const wikiRegex = new RegExp(\r\n `(!?\\\\[\\\\[${escapeRegExp(oldTarget)}(?:\\\\|[^\\\\]]*)?\\\\]\\\\])`,\r\n 'g',\r\n );\r\n const wikiMatches = content.match(wikiRegex);\r\n if (wikiMatches) {\r\n for (const match of wikiMatches) {\r\n const isEmbed = match.startsWith('!');\r\n const hasAlias = match.includes('|');\r\n let replacement: string;\r\n\r\n if (hasAlias) {\r\n const parts = match.split('|');\r\n replacement = `${isEmbed ? '!' : ''}[[${newTarget}|${parts[1]}`;\r\n } else {\r\n replacement = `${isEmbed ? '!' : ''}[[${newTarget}]]`;\r\n }\r\n\r\n newContent = newContent.replace(match, replacement);\r\n results.push({\r\n file: filePath,\r\n originalLink: match,\r\n newLink: replacement,\r\n success: true,\r\n });\r\n }\r\n }\r\n\r\n // For markdown links: [text](oldTarget) or [text](oldTarget#heading)\r\n const markdownRegex = new RegExp(\r\n `(\\\\[[^\\\\]]*\\\\]\\\\(${escapeRegExp(oldTarget)}(?:#[^)]*)?\\\\))`,\r\n 'g',\r\n );\r\n const markdownMatches = content.match(markdownRegex);\r\n if (markdownMatches) {\r\n for (const match of markdownMatches) {\r\n const linkPart = match.match(/\\(([^)]+)\\)/)?.[1] || '';\r\n const heading = linkPart.includes('#') ? linkPart.split('#')[1] : '';\r\n const newLink = heading ? `${newTarget}#${heading}` : newTarget;\r\n const textPart = match.match(/\\[([^\\]]*)\\]/)?.[0] || '[]';\r\n const replacement = `${textPart}(${newLink})`;\r\n\r\n newContent = newContent.replace(match, replacement);\r\n results.push({\r\n file: filePath,\r\n originalLink: match,\r\n newLink: replacement,\r\n success: true,\r\n });\r\n }\r\n }\r\n }\r\n\r\n // Only write if content changed\r\n if (newContent !== content) {\r\n await writeFile(filePath, newContent, 'utf-8');\r\n }\r\n\r\n return results;\r\n } catch (error) {\r\n return [\r\n {\r\n file: filePath,\r\n originalLink: '',\r\n newLink: '',\r\n success: false,\r\n error: error instanceof Error ? error.message : 'Unknown error',\r\n },\r\n ];\r\n }\r\n}\r\n\r\nexport async function fixBrokenLinksBatch(\n brokenLinks: BrokenLink[],\n suggestionMap: Map<string, string>,\n vaultPath: string,\n): Promise<Map<string, FixResult[]>> {\n const fileFixMap = new Map<string, Map<string, string>>();\r\n\r\n // Group fixes by file\r\n for (const broken of brokenLinks) {\r\n const suggestion = suggestionMap.get(broken.link.target);\r\n if (!suggestion) continue;\r\n\r\n if (!fileFixMap.has(broken.source)) {\r\n fileFixMap.set(broken.source, new Map());\r\n }\r\n fileFixMap.get(broken.source)!.set(broken.link.target, suggestion);\r\n }\r\n\r\n const allResults = new Map<string, FixResult[]>();\r\n\r\n // Process files in parallel\r\n const promises = Array.from(fileFixMap.entries()).map(\n async ([source, fixMap]) => {\n const filePath = resolve(vaultPath, source);\n const results = await autoFixBrokenLinks(fixMap, filePath);\n allResults.set(source, results);\n },\n );\n\r\n await Promise.all(promises);\r\n\r\n return allResults;\r\n}\r\n"],"mappings":"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","names":["writeFile","resolve","readFile","resolve","pill","bar","readFile","resolve","fixSummary","writeFile"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dead-links",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "CLI tool to scan Obsidian vaults for broken links, orphan notes, and connection graphs",
5
5
  "type": "module",
6
6
  "bin": {
@@ -9,13 +9,6 @@
9
9
  "files": [
10
10
  "dist"
11
11
  ],
12
- "scripts": {
13
- "build": "tsup",
14
- "dev": "tsup --watch",
15
- "test": "vitest",
16
- "lint": "eslint .",
17
- "format": "prettier --write ."
18
- },
19
12
  "keywords": [
20
13
  "obsidian",
21
14
  "vault",
@@ -39,17 +32,16 @@
39
32
  "engines": {
40
33
  "node": ">=22.0.0"
41
34
  },
42
- "packageManager": "pnpm@11.0.8",
43
35
  "dependencies": {
44
36
  "commander": "^14.0.3",
45
37
  "fast-glob": "^3.3.3",
46
38
  "gray-matter": "^4.0.3",
39
+ "chalk": "^5.6.2",
47
40
  "ora": "^9.4.0"
48
41
  },
49
42
  "devDependencies": {
50
43
  "@eslint/js": "^10.0.1",
51
44
  "@types/node": "^25.8.0",
52
- "chalk": "^5.6.2",
53
45
  "eslint": "^10.4.0",
54
46
  "eslint-config-prettier": "^10.1.8",
55
47
  "globals": "^17.6.0",
@@ -57,5 +49,12 @@
57
49
  "tsup": "^8.5.1",
58
50
  "typescript": "^6.0.3",
59
51
  "vitest": "^4.1.6"
52
+ },
53
+ "scripts": {
54
+ "build": "tsup",
55
+ "dev": "tsup --watch",
56
+ "test": "vitest",
57
+ "lint": "eslint .",
58
+ "format": "prettier --write ."
60
59
  }
61
- }
60
+ }