skillwiki 0.8.1-beta.8 → 0.8.1

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/dist/cli.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
 
10
10
  // src/cli.ts
11
11
  import { readFileSync as readFileSync12 } from "fs";
12
- import { join as join43 } from "path";
12
+ import { join as join44 } from "path";
13
13
  import { Command as Command2 } from "commander";
14
14
 
15
15
  // ../shared/src/exit-codes.ts
@@ -500,6 +500,7 @@ import { dirname } from "path";
500
500
  import { readFile as readFile3, readdir, stat } from "fs/promises";
501
501
  import { join as join3, relative as relative2, sep as sep2 } from "path";
502
502
  var TYPED_DIRS = ["entities", "concepts", "comparisons", "queries", "meta"];
503
+ var SKIP_DIRS = /* @__PURE__ */ new Set([".git", "node_modules"]);
503
504
  async function scanVault(root) {
504
505
  try {
505
506
  await stat(join3(root, "SCHEMA.md"));
@@ -510,6 +511,7 @@ async function scanVault(root) {
510
511
  const rels = all.map((p) => ({ absPath: p, relPath: relative2(root, p).split(sep2).join("/") }));
511
512
  return ok({
512
513
  root,
514
+ allMarkdown: rels,
513
515
  typedKnowledge: rels.filter((p) => TYPED_DIRS.some((d) => p.relPath.startsWith(d + "/"))),
514
516
  raw: rels.filter((p) => p.relPath.startsWith("raw/")),
515
517
  workItems: rels.filter((p) => /^projects\/[^/]+\/work\/[^/]+\/(spec|plan|log)\.md$/.test(p.relPath)),
@@ -521,8 +523,10 @@ async function walk(dir) {
521
523
  const out = [];
522
524
  for (const e of entries) {
523
525
  const p = join3(dir, e.name);
524
- if (e.isDirectory()) out.push(...await walk(p));
525
- else if (e.isFile() && e.name.endsWith(".md")) out.push(p);
526
+ if (e.isDirectory()) {
527
+ if (SKIP_DIRS.has(e.name)) continue;
528
+ out.push(...await walk(p));
529
+ } else if (e.isFile() && e.name.endsWith(".md")) out.push(p);
526
530
  }
527
531
  return out;
528
532
  }
@@ -2579,9 +2583,9 @@ ${content}
2579
2583
  }
2580
2584
 
2581
2585
  // src/commands/lint.ts
2582
- import { existsSync as existsSync4 } from "fs";
2583
- import { readFile as readFile16, rename as rename6 } from "fs/promises";
2584
- import { join as join21 } from "path";
2586
+ import { existsSync as existsSync5 } from "fs";
2587
+ import { readFile as readFile17 } from "fs/promises";
2588
+ import { join as join22 } from "path";
2585
2589
 
2586
2590
  // src/commands/sparse-community.ts
2587
2591
  async function runSparseCommunity(input) {
@@ -2837,28 +2841,100 @@ async function runRawBodyDedup(vault) {
2837
2841
  }
2838
2842
 
2839
2843
  // src/commands/path-too-long.ts
2844
+ import { existsSync as existsSync4 } from "fs";
2845
+ import { mkdir as mkdir8, readFile as readFile16, rename as rename6, unlink as unlink3 } from "fs/promises";
2846
+ import { dirname as dirname8, join as join21, posix, resolve as resolve4 } from "path";
2840
2847
  var MAX_PATH_LENGTH = 240;
2848
+ var WINDOWS_ABSOLUTE_PATH_LIMIT = 259;
2841
2849
  async function runPathTooLong(input) {
2842
2850
  const scan = await scanVault(input.vault);
2843
2851
  if (!scan.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scan };
2844
- const allPages = [...scan.data.typedKnowledge, ...scan.data.raw, ...scan.data.workItems, ...scan.data.compound];
2845
- const violations = [];
2846
- for (const page of allPages) {
2847
- if (page.relPath.length > MAX_PATH_LENGTH) {
2848
- violations.push({ relPath: page.relPath, length: page.relPath.length });
2849
- }
2850
- }
2852
+ const violations = findPathTooLongViolations(scan.data.allMarkdown, MAX_PATH_LENGTH);
2851
2853
  if (violations.length > 0) {
2852
2854
  return {
2853
2855
  exitCode: ExitCode.LINT_HAS_ERRORS,
2854
2856
  result: ok({
2855
2857
  violations,
2856
- humanHint: violations.map((v) => `${v.relPath}: ${v.length} chars (max ${MAX_PATH_LENGTH})`).join("\n")
2858
+ humanHint: violations.map((v) => `${v.relPath}: ${v.length} chars (max ${MAX_PATH_LENGTH}) -> ${v.suggestedRelPath}`).join("\n")
2857
2859
  })
2858
2860
  };
2859
2861
  }
2860
2862
  return { exitCode: ExitCode.OK, result: ok({ violations, humanHint: "all paths within length limit" }) };
2861
2863
  }
2864
+ async function fixPathTooLong(input) {
2865
+ const scan = await scanVault(input.vault);
2866
+ if (!scan.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scan };
2867
+ const maxFixLength = maxFixPathLength(input.vault);
2868
+ const violations = findPathTooLongViolations(scan.data.allMarkdown, maxFixLength);
2869
+ const fixed = [];
2870
+ const unresolved = [];
2871
+ for (const violation of violations) {
2872
+ const target = await resolveFixTarget(input.vault, violation.relPath, violation.suggestedRelPath, maxFixLength);
2873
+ if (!target || target.relPath === violation.relPath || target.relPath.length > maxFixLength) {
2874
+ unresolved.push(violation.relPath);
2875
+ continue;
2876
+ }
2877
+ try {
2878
+ if (target.mode === "dedupe") {
2879
+ await unlink3(join21(input.vault, violation.relPath));
2880
+ } else {
2881
+ await mkdir8(dirname8(join21(input.vault, target.relPath)), { recursive: true });
2882
+ await rename6(join21(input.vault, violation.relPath), join21(input.vault, target.relPath));
2883
+ }
2884
+ fixed.push({ from: violation.relPath, to: target.relPath });
2885
+ } catch {
2886
+ unresolved.push(violation.relPath);
2887
+ }
2888
+ }
2889
+ const rewired = [];
2890
+ if (fixed.length > 0) {
2891
+ const afterScan = await scanVault(input.vault);
2892
+ if (afterScan.ok) {
2893
+ for (const page of afterScan.data.allMarkdown) {
2894
+ if (!shouldRewriteReferences(page.relPath)) continue;
2895
+ try {
2896
+ const original = await readFile16(page.absPath, "utf8");
2897
+ let updated = original;
2898
+ for (const fix of fixed) {
2899
+ updated = replacePathReferences(updated, fix.from, fix.to);
2900
+ }
2901
+ if (updated !== original) {
2902
+ const write = await safeWritePage(page.absPath, updated);
2903
+ if (write.ok) rewired.push(page.relPath);
2904
+ else unresolved.push(`${page.relPath} (rewire)`);
2905
+ }
2906
+ } catch {
2907
+ unresolved.push(`${page.relPath} (rewire)`);
2908
+ }
2909
+ }
2910
+ }
2911
+ }
2912
+ const hintLines = [
2913
+ `fixed: ${fixed.length}`,
2914
+ `rewired: ${rewired.length}`,
2915
+ `unresolved: ${unresolved.length}`
2916
+ ];
2917
+ for (const f of fixed) hintLines.push(` ${f.from} -> ${f.to}`);
2918
+ for (const u of unresolved) hintLines.push(` unresolved: ${u}`);
2919
+ return {
2920
+ exitCode: unresolved.length > 0 ? ExitCode.LINT_HAS_ERRORS : ExitCode.OK,
2921
+ result: ok({ fixed, unresolved, rewired, humanHint: hintLines.join("\n") })
2922
+ };
2923
+ }
2924
+ function findPathTooLongViolations(pages, maxLength) {
2925
+ return pages.filter((page) => page.relPath.length > maxLength).map((page) => ({
2926
+ relPath: page.relPath,
2927
+ length: page.relPath.length,
2928
+ suggestedRelPath: truncateFilename(page.relPath, maxLength)
2929
+ }));
2930
+ }
2931
+ function maxFixPathLength(vault) {
2932
+ if (process.platform !== "win32") return MAX_PATH_LENGTH;
2933
+ const root = resolve4(vault);
2934
+ const separatorBudget = root.endsWith("\\") || root.endsWith("/") ? 0 : 1;
2935
+ const absoluteSafeRelLength = WINDOWS_ABSOLUTE_PATH_LIMIT - root.length - separatorBudget;
2936
+ return Math.max(1, Math.min(MAX_PATH_LENGTH, absoluteSafeRelLength));
2937
+ }
2862
2938
  function truncateFilename(relPath, maxLength = MAX_PATH_LENGTH) {
2863
2939
  if (relPath.length <= maxLength) return relPath;
2864
2940
  const lastSlash = relPath.lastIndexOf("/");
@@ -2872,15 +2948,62 @@ function truncateFilename(relPath, maxLength = MAX_PATH_LENGTH) {
2872
2948
  const maxPrefixLen = maxLength - dirPrefix.length - suffix.length;
2873
2949
  if (maxPrefixLen <= 0) {
2874
2950
  const fallback = dirPrefix + hash + ext;
2875
- if (fallback.length > maxLength) {
2876
- const dirBudget = maxLength - suffix.length;
2877
- return dirPrefix.slice(0, Math.max(0, dirBudget)) + suffix;
2878
- }
2879
- return fallback;
2951
+ return fallback.length <= maxLength ? fallback : relPath;
2880
2952
  }
2881
2953
  const prefix = base.slice(0, maxPrefixLen).replace(/[-_\s]+$/, "");
2882
2954
  return dirPrefix + prefix + suffix;
2883
2955
  }
2956
+ async function resolveFixTarget(vault, original, preferred, maxLength) {
2957
+ for (const candidate of candidateRelPaths(preferred, maxLength)) {
2958
+ if (candidate === original || candidate.length > maxLength) continue;
2959
+ const candidatePath = join21(vault, candidate);
2960
+ if (!existsSync4(candidatePath)) return { relPath: candidate, mode: "rename" };
2961
+ if (await hasSameContent(join21(vault, original), candidatePath)) {
2962
+ return { relPath: candidate, mode: "dedupe" };
2963
+ }
2964
+ }
2965
+ return null;
2966
+ }
2967
+ function candidateRelPaths(preferred, maxLength) {
2968
+ const candidates = [preferred];
2969
+ if (preferred.length > maxLength) return candidates;
2970
+ const dir = posix.dirname(preferred) === "." ? "" : posix.dirname(preferred);
2971
+ const filename = posix.basename(preferred);
2972
+ const ext = filename.endsWith(".md") ? ".md" : "";
2973
+ const base = ext ? filename.slice(0, -3) : filename;
2974
+ const dirPrefix = dir ? `${dir}/` : "";
2975
+ for (let i = 2; i < 100; i++) {
2976
+ const suffix = `-${i}${ext}`;
2977
+ const prefixBudget = maxLength - dirPrefix.length - suffix.length;
2978
+ if (prefixBudget <= 0) break;
2979
+ candidates.push(`${dirPrefix}${base.slice(0, prefixBudget).replace(/[-_\s]+$/, "")}${suffix}`);
2980
+ }
2981
+ return candidates;
2982
+ }
2983
+ async function hasSameContent(a, b) {
2984
+ try {
2985
+ const [left, right] = await Promise.all([readFile16(a), readFile16(b)]);
2986
+ return left.equals(right);
2987
+ } catch {
2988
+ return false;
2989
+ }
2990
+ }
2991
+ function shouldRewriteReferences(relPath) {
2992
+ if (relPath.startsWith("raw/")) return false;
2993
+ if (relPath.startsWith("_archive/")) return false;
2994
+ return true;
2995
+ }
2996
+ function replacePathReferences(content, oldRelPath, newRelPath) {
2997
+ let updated = content.replaceAll(oldRelPath, newRelPath);
2998
+ const oldStem = posix.basename(oldRelPath).replace(/\.md$/, "");
2999
+ const newStem = posix.basename(newRelPath).replace(/\.md$/, "");
3000
+ if (oldStem !== newStem) {
3001
+ const oldStemEscaped = oldStem.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3002
+ const stemWikilinkRe = new RegExp(`\\[\\[${oldStemEscaped}(\\|[^\\]]*)?\\]\\]`, "g");
3003
+ updated = updated.replace(stemWikilinkRe, (_match, alias) => `[[${newStem}${alias ?? ""}]]`);
3004
+ }
3005
+ return updated;
3006
+ }
2884
3007
  function computeShortHash(input) {
2885
3008
  let hash = 2166136261;
2886
3009
  for (let i = 0; i < input.length; i++) {
@@ -3069,7 +3192,15 @@ function extractSourceEntries(rawFm) {
3069
3192
  var ERROR_ORDER = ["broken_wikilinks", "invalid_frontmatter", "raw_dedup", "broken_sources", "tag_not_in_taxonomy", "path_too_long"];
3070
3193
  var WARNING_ORDER = ["raw_body_duplicate", "raw_subdirectory_duplicate", "file_source_url", "index_incomplete", "index_link_format", "stale_page", "page_too_large", "log_rotate_needed", "orphans", "compound_refs", "legacy_citation_style", "orphaned_citations", "duplicate_frontmatter", "work_item_health", "orphaned_project_pages", "missing_overview", "missing_diagram"];
3071
3194
  var INFO_ORDER = ["bridges", "sparse_community", "page_structure", "topic_map_recommended", "frontmatter_wikilink", "wikilink_citation", "missing_tldr", "stale_sections", "cli_refs"];
3195
+ var KNOWN_BUCKETS = [...ERROR_ORDER, ...WARNING_ORDER, ...INFO_ORDER];
3072
3196
  async function runLint(input) {
3197
+ if (input.only && !KNOWN_BUCKETS.includes(input.only)) {
3198
+ return {
3199
+ exitCode: ExitCode.USAGE,
3200
+ result: { ok: false, error: "UNKNOWN_BUCKET", detail: `Unknown bucket "${input.only}". Valid: ${KNOWN_BUCKETS.join(", ")}` }
3201
+ };
3202
+ }
3203
+ const shouldFix = (bucket) => !!input.fix && (!input.only || input.only === bucket);
3073
3204
  const buckets = {};
3074
3205
  const fixed = [];
3075
3206
  const unresolved = [];
@@ -3193,7 +3324,7 @@ async function runLint(input) {
3193
3324
  let rawPath = entry.replace(/^"/, "").replace(/"$/, "").replace(/^'/, "").replace(/'$/, "");
3194
3325
  rawPath = rawPath.replace(/^\^\[/, "").replace(/\]$/, "");
3195
3326
  if (!rawPath.startsWith("raw/") && !rawPath.startsWith("_archive/raw/")) continue;
3196
- if (!existsSync4(join21(input.vault, rawPath)) && !existsSync4(join21(input.vault, rawPath + ".md")) && !rawPath.startsWith("_archive/") && !existsSync4(join21(input.vault, "_archive", rawPath)) && !existsSync4(join21(input.vault, "_archive", rawPath + ".md"))) {
3327
+ if (!existsSync5(join22(input.vault, rawPath)) && !existsSync5(join22(input.vault, rawPath + ".md")) && !rawPath.startsWith("_archive/") && !existsSync5(join22(input.vault, "_archive", rawPath)) && !existsSync5(join22(input.vault, "_archive", rawPath + ".md"))) {
3197
3328
  brokenSourceFlags.push(`${page.relPath}: ${rawPath}`);
3198
3329
  }
3199
3330
  }
@@ -3284,11 +3415,11 @@ async function runLint(input) {
3284
3415
  const slugMatch = String(entry).match(/\[\[([^\]]+)\]\]/);
3285
3416
  if (!slugMatch) continue;
3286
3417
  const slug = slugMatch[1];
3287
- const knowledgePath = join21(input.vault, "projects", slug, "knowledge.md");
3288
- if (!existsSync4(knowledgePath)) continue;
3418
+ const knowledgePath = join22(input.vault, "projects", slug, "knowledge.md");
3419
+ if (!existsSync5(knowledgePath)) continue;
3289
3420
  const pageRef = page.relPath.replace(/\.md$/, "");
3290
3421
  try {
3291
- const knowledgeContent = await readFile16(knowledgePath, "utf8");
3422
+ const knowledgeContent = await readFile17(knowledgePath, "utf8");
3292
3423
  if (!knowledgeContent.includes(`[[${pageRef}]]`)) {
3293
3424
  orphanedProjectPages.push(`${page.relPath}: not in projects/${slug}/knowledge.md`);
3294
3425
  }
@@ -3329,13 +3460,13 @@ async function runLint(input) {
3329
3460
  }
3330
3461
  }
3331
3462
  if (staleSectionFlags.length > 0) buckets.stale_sections = staleSectionFlags;
3332
- if (input.fix && legacyPages.length > 0) {
3463
+ if (shouldFix("legacy_citation_style") && legacyPages.length > 0) {
3333
3464
  const FENCE_RE2 = /```[\s\S]*?```/g;
3334
3465
  const INLINE_MARKER = /\^\[raw\/[^\]]+\]/g;
3335
3466
  for (const relPath of legacyPages) {
3336
3467
  try {
3337
3468
  const absPath = `${input.vault}/${relPath}`;
3338
- const raw = await readFile16(absPath, "utf8");
3469
+ const raw = await readFile17(absPath, "utf8");
3339
3470
  const split = splitFrontmatter(raw);
3340
3471
  if (!split.ok) {
3341
3472
  unresolved.push(relPath);
@@ -3430,11 +3561,11 @@ ${newBody}`;
3430
3561
  else delete buckets.legacy_citation_style;
3431
3562
  }
3432
3563
  }
3433
- if (input.fix && noOverview.length > 0) {
3564
+ if (shouldFix("missing_overview") && noOverview.length > 0) {
3434
3565
  for (const relPath of noOverview) {
3435
3566
  try {
3436
3567
  const absPath = `${input.vault}/${relPath}`;
3437
- const raw = await readFile16(absPath, "utf8");
3568
+ const raw = await readFile17(absPath, "utf8");
3438
3569
  const split = splitFrontmatter(raw);
3439
3570
  if (!split.ok) {
3440
3571
  unresolved.push(relPath);
@@ -3471,11 +3602,11 @@ ${trimmedBody}`;
3471
3602
  if (remaining.length > 0) buckets.missing_overview = remaining;
3472
3603
  else delete buckets.missing_overview;
3473
3604
  }
3474
- if (input.fix && missingTldrFlags.length > 0) {
3605
+ if (shouldFix("missing_tldr") && missingTldrFlags.length > 0) {
3475
3606
  for (const relPath of missingTldrFlags) {
3476
3607
  try {
3477
3608
  const absPath = `${input.vault}/${relPath}`;
3478
- const raw = await readFile16(absPath, "utf8");
3609
+ const raw = await readFile17(absPath, "utf8");
3479
3610
  const split = splitFrontmatter(raw);
3480
3611
  if (!split.ok) {
3481
3612
  unresolved.push(relPath);
@@ -3518,14 +3649,14 @@ ${lines.join("\n")}`;
3518
3649
  if (remaining.length > 0) buckets.missing_tldr = remaining;
3519
3650
  else delete buckets.missing_tldr;
3520
3651
  }
3521
- if (input.fix && wikilinkCitationFlags.length > 0) {
3652
+ if (shouldFix("wikilink_citation") && wikilinkCitationFlags.length > 0) {
3522
3653
  const WIKILINK_RE = /\[\[raw\/([^\]|]+)(?:\|[^\]]*)?\]\]/g;
3523
3654
  const FENCE_RE2 = /```[\s\S]*?```/g;
3524
3655
  const wikilinkFixed = [];
3525
3656
  for (const relPath of wikilinkCitationFlags) {
3526
3657
  try {
3527
3658
  const absPath = `${input.vault}/${relPath}`;
3528
- const raw = await readFile16(absPath, "utf8");
3659
+ const raw = await readFile17(absPath, "utf8");
3529
3660
  const split = splitFrontmatter(raw);
3530
3661
  if (!split.ok) {
3531
3662
  unresolved.push(relPath);
@@ -3607,12 +3738,12 @@ ${newBody}`;
3607
3738
  else delete buckets.wikilink_citation;
3608
3739
  }
3609
3740
  }
3610
- if (input.fix && fileSourceUrlFlags.length > 0) {
3741
+ if (shouldFix("file_source_url") && fileSourceUrlFlags.length > 0) {
3611
3742
  const FILE_FIXED = [];
3612
3743
  for (const relPath of fileSourceUrlFlags) {
3613
3744
  try {
3614
3745
  const absPath = `${input.vault}/${relPath}`;
3615
- const raw = await readFile16(absPath, "utf8");
3746
+ const raw = await readFile17(absPath, "utf8");
3616
3747
  const parts = raw.split("---", 3);
3617
3748
  if (parts.length < 3) {
3618
3749
  unresolved.push(relPath);
@@ -3647,36 +3778,11 @@ ${newBody}`;
3647
3778
  }
3648
3779
  }
3649
3780
  const pathViolations = buckets.path_too_long;
3650
- if (input.fix && pathViolations && pathViolations.length > 0) {
3651
- const pathFixed = [];
3652
- for (const v of pathViolations) {
3653
- try {
3654
- const absPath = `${input.vault}/${v.relPath}`;
3655
- const newRelPath = truncateFilename(v.relPath);
3656
- const newAbsPath = `${input.vault}/${newRelPath}`;
3657
- await rename6(absPath, newAbsPath);
3658
- const oldPathEscaped = v.relPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3659
- for (const page of allPages) {
3660
- if (page.relPath === v.relPath) continue;
3661
- const content = await readFile16(page.absPath, "utf8");
3662
- if (!content.includes(v.relPath)) continue;
3663
- let updated = content;
3664
- const citationRe = new RegExp(`\\^\\[${oldPathEscaped}\\]`, "g");
3665
- updated = updated.replace(citationRe, `^[${newRelPath}]`);
3666
- const wikilinkRe = new RegExp(`\\[\\[${oldPathEscaped}(\\|[^\\]]*)?\\]\\]`, "g");
3667
- updated = updated.replace(wikilinkRe, (_m, alias) => `[[${newRelPath}${alias ?? ""}]]`);
3668
- if (updated !== content) {
3669
- const w = await safeWritePage(page.absPath, updated);
3670
- if (!w.ok) {
3671
- unresolved.push(`${page.relPath} (rewire)`);
3672
- }
3673
- }
3674
- }
3675
- pathFixed.push(v.relPath);
3676
- } catch {
3677
- unresolved.push(v.relPath);
3678
- }
3679
- }
3781
+ if (shouldFix("path_too_long") && pathViolations && pathViolations.length > 0) {
3782
+ const pathFix = await fixPathTooLong({ vault: input.vault });
3783
+ const pathFixed = pathFix.result.ok ? pathFix.result.data.fixed.map((f) => f.from) : [];
3784
+ if (pathFix.result.ok) unresolved.push(...pathFix.result.data.unresolved);
3785
+ else unresolved.push(...pathViolations.map((v) => v.relPath));
3680
3786
  fixed.push(...pathFixed);
3681
3787
  if (pathFixed.length > 0) {
3682
3788
  const fixedSet = new Set(pathFixed);
@@ -3690,13 +3796,6 @@ ${newBody}`;
3690
3796
  const warningOut = WARNING_ORDER.flatMap((k) => buckets[k] ? [{ kind: k, items: buckets[k] }] : []);
3691
3797
  const infoOut = INFO_ORDER.flatMap((k) => buckets[k] ? [{ kind: k, items: buckets[k] }] : []);
3692
3798
  if (input.only) {
3693
- const allKnown = [...ERROR_ORDER, ...WARNING_ORDER, ...INFO_ORDER];
3694
- if (!allKnown.includes(input.only)) {
3695
- return {
3696
- exitCode: ExitCode.USAGE,
3697
- result: { ok: false, error: "UNKNOWN_BUCKET", detail: `Unknown bucket "${input.only}". Valid: ${allKnown.join(", ")}` }
3698
- };
3699
- }
3700
3799
  const match = [...errorOut, ...warningOut, ...infoOut].filter((b) => b.kind === input.only);
3701
3800
  const severity = ERROR_ORDER.includes(input.only) ? "error" : WARNING_ORDER.includes(input.only) ? "warning" : "info";
3702
3801
  const filtered = severity === "error" ? { error: match, warning: [], info: [] } : severity === "warning" ? { error: [], warning: match, info: [] } : { error: [], warning: [], info: match };
@@ -3760,14 +3859,14 @@ ${match.length === 0 ? "0 violations" : match.map((b) => ` ${b.kind}: ${b.items
3760
3859
  }
3761
3860
 
3762
3861
  // src/commands/config.ts
3763
- import { readFile as readFile17 } from "fs/promises";
3764
- import { existsSync as existsSync5 } from "fs";
3765
- import { join as join22 } from "path";
3862
+ import { readFile as readFile18 } from "fs/promises";
3863
+ import { existsSync as existsSync6 } from "fs";
3864
+ import { join as join23 } from "path";
3766
3865
  function validateKey(key) {
3767
3866
  return CONFIG_KEYS.includes(key) || isValidWikiProfileKey(key);
3768
3867
  }
3769
3868
  function configPath(home) {
3770
- return join22(home, ".skillwiki", ".env");
3869
+ return join23(home, ".skillwiki", ".env");
3771
3870
  }
3772
3871
  async function runConfigGet(input) {
3773
3872
  if (!validateKey(input.key)) {
@@ -3785,7 +3884,7 @@ async function runConfigSet(input) {
3785
3884
  try {
3786
3885
  let originalContent;
3787
3886
  try {
3788
- originalContent = await readFile17(filePath, "utf8");
3887
+ originalContent = await readFile18(filePath, "utf8");
3789
3888
  } catch {
3790
3889
  }
3791
3890
  const existing = originalContent !== void 0 ? parseDotenvText(originalContent) : {};
@@ -3817,18 +3916,18 @@ async function runConfigList(input) {
3817
3916
  }
3818
3917
  async function runConfigPath(input) {
3819
3918
  const filePath = configPath(input.home);
3820
- return { exitCode: ExitCode.OK, result: ok({ path: filePath, exists: existsSync5(filePath), humanHint: filePath }) };
3919
+ return { exitCode: ExitCode.OK, result: ok({ path: filePath, exists: existsSync6(filePath), humanHint: filePath }) };
3821
3920
  }
3822
3921
 
3823
3922
  // src/commands/doctor.ts
3824
- import { existsSync as existsSync8, lstatSync, readlinkSync, readdirSync, statSync as statSync3, readFileSync as readFileSync7 } from "fs";
3825
- import { join as join26, resolve as resolve4 } from "path";
3923
+ import { existsSync as existsSync9, lstatSync, readlinkSync, readdirSync, statSync as statSync3, readFileSync as readFileSync7 } from "fs";
3924
+ import { join as join27, resolve as resolve5 } from "path";
3826
3925
  import { execSync as execSync2 } from "child_process";
3827
3926
  import { platform as platform2 } from "os";
3828
3927
 
3829
3928
  // src/utils/auto-update.ts
3830
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
3831
- import { join as join23, dirname as dirname8 } from "path";
3929
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, existsSync as existsSync7, mkdirSync as mkdirSync3 } from "fs";
3930
+ import { join as join24, dirname as dirname9 } from "path";
3832
3931
  import { spawn } from "child_process";
3833
3932
 
3834
3933
  // src/utils/update-consts.ts
@@ -3839,7 +3938,7 @@ var CLI_DISABLE_FLAG = "--no-update-notifier";
3839
3938
 
3840
3939
  // src/utils/auto-update.ts
3841
3940
  function cachePath(home) {
3842
- return join23(home, ".skillwiki", CACHE_FILENAME);
3941
+ return join24(home, ".skillwiki", CACHE_FILENAME);
3843
3942
  }
3844
3943
  function readCacheRaw(home) {
3845
3944
  try {
@@ -3858,7 +3957,7 @@ function readCache(home) {
3858
3957
  }
3859
3958
  function writeCache(home, cache) {
3860
3959
  const p = cachePath(home);
3861
- mkdirSync3(dirname8(p), { recursive: true });
3960
+ mkdirSync3(dirname9(p), { recursive: true });
3862
3961
  writeFileSync4(p, JSON.stringify(cache, null, 2));
3863
3962
  }
3864
3963
  function latestFromCache(home, currentVersion) {
@@ -3877,7 +3976,7 @@ function triggerAutoUpdate(home, currentVersion) {
3877
3976
  const { isStale: isStale2 } = readCache(home);
3878
3977
  if (!isStale2) return;
3879
3978
  const bgScript = new URL("../auto-update-bg.js", import.meta.url).pathname;
3880
- if (!existsSync6(bgScript)) return;
3979
+ if (!existsSync7(bgScript)) return;
3881
3980
  const child = spawn(process.execPath, [bgScript, home, currentVersion], {
3882
3981
  detached: true,
3883
3982
  stdio: "ignore"
@@ -3889,12 +3988,12 @@ function triggerAutoUpdate(home, currentVersion) {
3889
3988
 
3890
3989
  // src/utils/plugin-registry.ts
3891
3990
  import { readFileSync as readFileSync5 } from "fs";
3892
- import { join as join24 } from "path";
3893
- var REGISTRY_PATH = join24(".claude", "plugins", "installed_plugins.json");
3991
+ import { join as join25 } from "path";
3992
+ var REGISTRY_PATH = join25(".claude", "plugins", "installed_plugins.json");
3894
3993
  var PLUGIN_KEY = "skillwiki@llm-wiki";
3895
3994
  function readInstalledPlugins(home) {
3896
3995
  try {
3897
- const raw = readFileSync5(join24(home, REGISTRY_PATH), "utf8");
3996
+ const raw = readFileSync5(join25(home, REGISTRY_PATH), "utf8");
3898
3997
  return JSON.parse(raw);
3899
3998
  } catch {
3900
3999
  return null;
@@ -3911,8 +4010,8 @@ function findPlugin(home, key = PLUGIN_KEY) {
3911
4010
  // src/utils/s3-mount-health.ts
3912
4011
  import { execSync } from "child_process";
3913
4012
  import { platform } from "os";
3914
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, unlinkSync as unlinkSync4, readFileSync as readFile18 } from "fs";
3915
- import { join as join25 } from "path";
4013
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, unlinkSync as unlinkSync4, readFileSync as readFile19 } from "fs";
4014
+ import { join as join26 } from "path";
3916
4015
  var OS = platform();
3917
4016
  function findRcloneMountPid() {
3918
4017
  try {
@@ -4070,7 +4169,7 @@ function detectFuseMount(vaultPath) {
4070
4169
  return null;
4071
4170
  }
4072
4171
  function writeTest(dir) {
4073
- const testFile = join25(dir, `.doctor-write-test-${process.pid}.tmp`);
4172
+ const testFile = join26(dir, `.doctor-write-test-${process.pid}.tmp`);
4074
4173
  const payload = `skillwiki doctor write test \u2014 ${Date.now()} \u2014 ${Math.random().toString(36).slice(2)}`;
4075
4174
  const start = Date.now();
4076
4175
  try {
@@ -4081,7 +4180,7 @@ function writeTest(dir) {
4081
4180
  const writeMs = Date.now() - start;
4082
4181
  const readStart = Date.now();
4083
4182
  try {
4084
- const back = readFile18(testFile, "utf8");
4183
+ const back = readFile19(testFile, "utf8");
4085
4184
  const readMs = Date.now() - readStart;
4086
4185
  if (back !== payload) {
4087
4186
  try {
@@ -4155,14 +4254,14 @@ function checkNodeVersion() {
4155
4254
  function detectCliChannels(argv, home) {
4156
4255
  const channels = [];
4157
4256
  if (argv.length >= 2 && argv[1].endsWith("cli.js")) {
4158
- const devPath = resolve4(argv[1]);
4257
+ const devPath = resolve5(argv[1]);
4159
4258
  channels.push({ name: "dev", path: devPath, isDevLink: true });
4160
4259
  }
4161
4260
  try {
4162
4261
  const whichOut = execSync2("which skillwiki 2>/dev/null", { encoding: "utf8" }).trim();
4163
4262
  if (whichOut) {
4164
4263
  const isDev = isDevSymlink(whichOut);
4165
- if (!channels.some((c) => c.path === resolve4(whichOut))) {
4264
+ if (!channels.some((c) => c.path === resolve5(whichOut))) {
4166
4265
  channels.push({ name: "npm", path: whichOut, isDevLink: isDev });
4167
4266
  }
4168
4267
  }
@@ -4170,13 +4269,13 @@ function detectCliChannels(argv, home) {
4170
4269
  }
4171
4270
  const plugin = findPlugin(home);
4172
4271
  if (plugin) {
4173
- const pluginBin = join26(plugin.installPath, "bin", "skillwiki");
4174
- if (existsSync8(pluginBin)) {
4272
+ const pluginBin = join27(plugin.installPath, "bin", "skillwiki");
4273
+ if (existsSync9(pluginBin)) {
4175
4274
  channels.push({ name: "plugin", path: pluginBin, isDevLink: false });
4176
4275
  }
4177
4276
  }
4178
- const installBin = join26(home, ".claude", "skills", "bin", "skillwiki");
4179
- if (existsSync8(installBin)) {
4277
+ const installBin = join27(home, ".claude", "skills", "bin", "skillwiki");
4278
+ if (existsSync9(installBin)) {
4180
4279
  channels.push({ name: "install", path: installBin, isDevLink: false });
4181
4280
  }
4182
4281
  return channels;
@@ -4185,7 +4284,7 @@ function isDevSymlink(binPath) {
4185
4284
  try {
4186
4285
  const st = lstatSync(binPath);
4187
4286
  if (st.isSymbolicLink()) {
4188
- const target = resolve4(binPath, "..", readlinkSync(binPath));
4287
+ const target = resolve5(binPath, "..", readlinkSync(binPath));
4189
4288
  return target.includes("packages/cli") || target.includes("packages\\cli");
4190
4289
  }
4191
4290
  } catch {
@@ -4228,7 +4327,7 @@ function checkCliChannels(argv, home) {
4228
4327
  }
4229
4328
  async function checkConfigFile(home) {
4230
4329
  const cfgPath = configPath(home);
4231
- if (!existsSync8(cfgPath)) {
4330
+ if (!existsSync9(cfgPath)) {
4232
4331
  return check("warn", "config_file", "Config file exists", `${cfgPath} not found`);
4233
4332
  }
4234
4333
  try {
@@ -4243,7 +4342,7 @@ function checkWikiPathExists(resolvedPath) {
4243
4342
  if (resolvedPath === void 0) {
4244
4343
  return check("error", "wiki_path_exists", "Vault directory exists", "Cannot check \u2014 WIKI_PATH not resolved");
4245
4344
  }
4246
- if (existsSync8(resolvedPath) && statSync3(resolvedPath).isDirectory()) {
4345
+ if (existsSync9(resolvedPath) && statSync3(resolvedPath).isDirectory()) {
4247
4346
  return check("pass", "wiki_path_exists", "Vault directory exists", resolvedPath);
4248
4347
  }
4249
4348
  return check("error", "wiki_path_exists", "Vault directory exists", `${resolvedPath} does not exist or is not a directory`);
@@ -4252,13 +4351,13 @@ function checkVaultStructure(resolvedPath) {
4252
4351
  if (resolvedPath === void 0) {
4253
4352
  return check("error", "vault_structure", "Vault structure valid", "Cannot check \u2014 WIKI_PATH not resolved");
4254
4353
  }
4255
- if (!existsSync8(resolvedPath)) {
4354
+ if (!existsSync9(resolvedPath)) {
4256
4355
  return check("error", "vault_structure", "Vault structure valid", "Cannot check \u2014 vault directory does not exist");
4257
4356
  }
4258
4357
  const missing = [];
4259
- if (!existsSync8(join26(resolvedPath, "SCHEMA.md"))) missing.push("SCHEMA.md");
4358
+ if (!existsSync9(join27(resolvedPath, "SCHEMA.md"))) missing.push("SCHEMA.md");
4260
4359
  for (const dir of ["raw", "entities", "concepts", "meta"]) {
4261
- if (!existsSync8(join26(resolvedPath, dir))) missing.push(dir + "/");
4360
+ if (!existsSync9(join27(resolvedPath, dir))) missing.push(dir + "/");
4262
4361
  }
4263
4362
  if (missing.length === 0) {
4264
4363
  return check("pass", "vault_structure", "Vault structure valid", "All required files and directories present");
@@ -4266,8 +4365,8 @@ function checkVaultStructure(resolvedPath) {
4266
4365
  return check("warn", "vault_structure", "Vault structure valid", `Missing: ${missing.join(", ")} \u2014 run \`skillwiki init\` to add CodeWiki structure`);
4267
4366
  }
4268
4367
  function checkSkillsInstalled(home, cwd) {
4269
- const srcDir = cwd ? join26(cwd, "packages", "skills") : void 0;
4270
- if (srcDir && existsSync8(srcDir)) {
4368
+ const srcDir = cwd ? join27(cwd, "packages", "skills") : void 0;
4369
+ if (srcDir && existsSync9(srcDir)) {
4271
4370
  const found = findInstalledSkillMd(srcDir);
4272
4371
  if (found.length > 0) {
4273
4372
  return check("pass", "skills_installed", "Skills installed", `${found.length} SKILL.md file(s) found (source)`);
@@ -4280,8 +4379,8 @@ function checkSkillsInstalled(home, cwd) {
4280
4379
  return check("pass", "skills_installed", "Skills installed", `${found.length} SKILL.md file(s) found (plugin v${plugin.version})`);
4281
4380
  }
4282
4381
  }
4283
- const skillsDir = join26(home, ".claude", "skills");
4284
- if (existsSync8(skillsDir)) {
4382
+ const skillsDir = join27(home, ".claude", "skills");
4383
+ if (existsSync9(skillsDir)) {
4285
4384
  const found = findInstalledSkillMd(skillsDir);
4286
4385
  if (found.length > 0) {
4287
4386
  return check("pass", "skills_installed", "Skills installed", `${found.length} SKILL.md file(s) found (CLI install)`);
@@ -4291,10 +4390,10 @@ function checkSkillsInstalled(home, cwd) {
4291
4390
  }
4292
4391
  function checkDuplicateSkills(home) {
4293
4392
  const plugin = findPlugin(home);
4294
- const skillsDir = join26(home, ".claude", "skills");
4393
+ const skillsDir = join27(home, ".claude", "skills");
4295
4394
  const agentSkillDirs = [
4296
- { label: "~/.codex/skills/", path: join26(home, ".codex", "skills") },
4297
- { label: "~/.agents/skills/", path: join26(home, ".agents", "skills") }
4395
+ { label: "~/.codex/skills/", path: join27(home, ".codex", "skills") },
4396
+ { label: "~/.agents/skills/", path: join27(home, ".agents", "skills") }
4298
4397
  ];
4299
4398
  if (!plugin) {
4300
4399
  return check("pass", "skills_duplicate", "Skills not duplicated", "Single install channel");
@@ -4371,8 +4470,8 @@ async function checkProfiles(home) {
4371
4470
  }
4372
4471
  async function checkProjectLocalOverride(cwd) {
4373
4472
  const dir = cwd ?? process.cwd();
4374
- const envPath = join26(dir, ".skillwiki", ".env");
4375
- if (existsSync8(envPath)) {
4473
+ const envPath = join27(dir, ".skillwiki", ".env");
4474
+ if (existsSync9(envPath)) {
4376
4475
  return check("pass", "project_local", "Project-local config", `Found: ${envPath}`);
4377
4476
  }
4378
4477
  return check("pass", "project_local", "Project-local config", "None");
@@ -4381,7 +4480,7 @@ function checkVaultGitRemote(resolvedPath) {
4381
4480
  if (resolvedPath === void 0) {
4382
4481
  return check("error", "vault_git_remote", "Vault git remote", "Cannot check \u2014 WIKI_PATH not resolved");
4383
4482
  }
4384
- if (!existsSync8(join26(resolvedPath, ".git"))) {
4483
+ if (!existsSync9(join27(resolvedPath, ".git"))) {
4385
4484
  return check("warn", "vault_git_remote", "Vault git remote", "Vault is not a git repository \u2014 sync features unavailable");
4386
4485
  }
4387
4486
  try {
@@ -4404,9 +4503,9 @@ function checkObsidianTemplates(resolvedPath) {
4404
4503
  return check("error", "obsidian_templates", "Obsidian templates", "Cannot check \u2014 WIKI_PATH not resolved");
4405
4504
  }
4406
4505
  const missing = [];
4407
- if (!existsSync8(join26(resolvedPath, "_Templates"))) missing.push("_Templates/");
4408
- if (!existsSync8(join26(resolvedPath, ".obsidian", "templates.json"))) missing.push(".obsidian/templates.json");
4409
- if (!existsSync8(join26(resolvedPath, ".obsidian", "app.json"))) missing.push(".obsidian/app.json");
4506
+ if (!existsSync9(join27(resolvedPath, "_Templates"))) missing.push("_Templates/");
4507
+ if (!existsSync9(join27(resolvedPath, ".obsidian", "templates.json"))) missing.push(".obsidian/templates.json");
4508
+ if (!existsSync9(join27(resolvedPath, ".obsidian", "app.json"))) missing.push(".obsidian/app.json");
4410
4509
  if (missing.length === 0) {
4411
4510
  return check("pass", "obsidian_templates", "Obsidian templates", "Template folder and config present");
4412
4511
  }
@@ -4416,8 +4515,8 @@ function checkDotStoreClean(resolvedPath) {
4416
4515
  if (resolvedPath === void 0) {
4417
4516
  return check("error", "dsstore_clean", "No .DS_Store in raw/", "Cannot check \u2014 WIKI_PATH not resolved");
4418
4517
  }
4419
- const rawDir = join26(resolvedPath, "raw");
4420
- if (!existsSync8(rawDir)) {
4518
+ const rawDir = join27(resolvedPath, "raw");
4519
+ if (!existsSync9(rawDir)) {
4421
4520
  return check("pass", "dsstore_clean", "No .DS_Store in raw/", "raw/ directory not found \u2014 check skipped");
4422
4521
  }
4423
4522
  const found = [];
@@ -4432,7 +4531,7 @@ function checkDotStoreClean(resolvedPath) {
4432
4531
  if (entry.name === ".DS_Store") {
4433
4532
  found.push(rel ? `${rel}/.DS_Store` : ".DS_Store");
4434
4533
  } else if (entry.isDirectory()) {
4435
- walk2(join26(dir, entry.name), rel ? `${rel}/${entry.name}` : entry.name);
4534
+ walk2(join27(dir, entry.name), rel ? `${rel}/${entry.name}` : entry.name);
4436
4535
  }
4437
4536
  }
4438
4537
  })(rawDir, "");
@@ -4445,7 +4544,7 @@ function checkSyncLastPush(resolvedPath) {
4445
4544
  if (resolvedPath === void 0) {
4446
4545
  return check("error", "sync_last_push", "Vault sync recency", "Cannot check \u2014 WIKI_PATH not resolved");
4447
4546
  }
4448
- if (!existsSync8(join26(resolvedPath, ".git"))) {
4547
+ if (!existsSync9(join27(resolvedPath, ".git"))) {
4449
4548
  return check("pass", "sync_last_push", "Vault sync recency", "No git repo \u2014 sync check skipped");
4450
4549
  }
4451
4550
  let timestamp;
@@ -4486,8 +4585,8 @@ function checkS3MountPerf(resolvedPath) {
4486
4585
  return check("pass", "s3_mount_perf", "S3 mount performance", "local disk");
4487
4586
  }
4488
4587
  const mountPoint = fuse.mountPoint;
4489
- const conceptsDir = join26(resolvedPath, "concepts");
4490
- if (!existsSync8(conceptsDir)) {
4588
+ const conceptsDir = join27(resolvedPath, "concepts");
4589
+ if (!existsSync9(conceptsDir)) {
4491
4590
  return check("pass", "s3_mount_perf", "S3 mount performance", `S3 FUSE mount (${mountPoint}), no concepts/ to benchmark`);
4492
4591
  }
4493
4592
  const start = Date.now();
@@ -4669,8 +4768,8 @@ function checkWriteTest(resolvedPath) {
4669
4768
  if (!fuse) {
4670
4769
  return check("pass", "s3_write_test", "S3 write test", "local disk \u2014 check skipped");
4671
4770
  }
4672
- const conceptsDir = join26(resolvedPath, "concepts");
4673
- if (!existsSync8(conceptsDir)) {
4771
+ const conceptsDir = join27(resolvedPath, "concepts");
4772
+ if (!existsSync9(conceptsDir)) {
4674
4773
  return check("pass", "s3_write_test", "S3 write test", "no concepts/ dir to test \u2014 check skipped");
4675
4774
  }
4676
4775
  const result = writeTest(conceptsDir);
@@ -4756,7 +4855,7 @@ function checkVfsCacheHealth(resolvedPath) {
4756
4855
  }
4757
4856
  function readVaultSyncConfig(home) {
4758
4857
  try {
4759
- const content = readFileSync7(join26(home, ".skillwiki", ".env"), "utf8");
4858
+ const content = readFileSync7(join27(home, ".skillwiki", ".env"), "utf8");
4760
4859
  let installed = false;
4761
4860
  let role;
4762
4861
  for (const line of content.split(/\r?\n/)) {
@@ -4790,12 +4889,12 @@ function vaultSyncChecks(input) {
4790
4889
  ];
4791
4890
  }
4792
4891
  const isMac = os === "darwin";
4793
- const logDir = input.logDir ?? (isMac ? join26(home, "Library", "Logs") : join26(home, ".local", "state", "vault-sync", "log"));
4794
- const shareDir = input.shareDir ?? (isMac ? join26(home, "Library", "Application Support", "vault-sync", "bin") : join26(home, ".local", "share", "vault-sync", "bin"));
4795
- const filterPath = input.filterPath ?? join26(home, ".config", "rclone", "wiki-push-filters.txt");
4892
+ const logDir = input.logDir ?? (isMac ? join27(home, "Library", "Logs") : join27(home, ".local", "state", "vault-sync", "log"));
4893
+ const shareDir = input.shareDir ?? (isMac ? join27(home, "Library", "Application Support", "vault-sync", "bin") : join27(home, ".local", "share", "vault-sync", "bin"));
4894
+ const filterPath = input.filterPath ?? join27(home, ".config", "rclone", "wiki-push-filters.txt");
4796
4895
  const snapshotPath = input.snapshotScriptPath ?? "/root/.hermes/scripts/wiki-snapshot-v3.sh";
4797
- const pushScriptPath = join26(shareDir, "wiki-push.sh");
4798
- const c1 = existsSync8(pushScriptPath) ? check("pass", "vault_sync_installed", "Vault sync installed", `Found: ${pushScriptPath}`) : check("error", "vault_sync_installed", "Vault sync installed", `Script not found at ${pushScriptPath} \u2014 run vault-sync-install`);
4896
+ const pushScriptPath = join27(shareDir, "wiki-push.sh");
4897
+ const c1 = existsSync9(pushScriptPath) ? check("pass", "vault_sync_installed", "Vault sync installed", `Found: ${pushScriptPath}`) : check("error", "vault_sync_installed", "Vault sync installed", `Script not found at ${pushScriptPath} \u2014 run vault-sync-install`);
4799
4898
  let c2;
4800
4899
  try {
4801
4900
  if (isMac) {
@@ -4846,7 +4945,7 @@ function vaultSyncChecks(input) {
4846
4945
  "Scheduler check failed \u2014 run vault-sync-install"
4847
4946
  );
4848
4947
  }
4849
- const logFile = join26(logDir, "wiki-push.log");
4948
+ const logFile = join27(logDir, "wiki-push.log");
4850
4949
  let c3;
4851
4950
  try {
4852
4951
  const logContent = readFileSync7(logFile, "utf8");
@@ -4905,7 +5004,7 @@ function vaultSyncChecks(input) {
4905
5004
  }
4906
5005
  }
4907
5006
  } catch {
4908
- c3 = existsSync8(logDir) ? check(
5007
+ c3 = existsSync9(logDir) ? check(
4909
5008
  "warn",
4910
5009
  "vault_sync_last_push_age",
4911
5010
  "Vault sync last push recency",
@@ -4917,7 +5016,7 @@ function vaultSyncChecks(input) {
4917
5016
  `Log directory not found at ${logDir}`
4918
5017
  );
4919
5018
  }
4920
- const fetchLogFile = join26(logDir, "wiki-fetch.log");
5019
+ const fetchLogFile = join27(logDir, "wiki-fetch.log");
4921
5020
  let cFetch;
4922
5021
  try {
4923
5022
  const logContent = readFileSync7(fetchLogFile, "utf8");
@@ -4964,7 +5063,7 @@ function vaultSyncChecks(input) {
4964
5063
  }
4965
5064
  let c4;
4966
5065
  try {
4967
- if (!existsSync8(filterPath)) {
5066
+ if (!existsSync9(filterPath)) {
4968
5067
  c4 = check(
4969
5068
  "error",
4970
5069
  "vault_sync_filter_present",
@@ -5013,7 +5112,7 @@ function vaultSyncChecks(input) {
5013
5112
  );
5014
5113
  } else {
5015
5114
  try {
5016
- if (!existsSync8(snapshotPath)) {
5115
+ if (!existsSync9(snapshotPath)) {
5017
5116
  c5 = check(
5018
5117
  "error",
5019
5118
  "vault_sync_snapshot_guard",
@@ -5059,15 +5158,15 @@ function findSkillMd(dir) {
5059
5158
  }
5060
5159
  for (const entry of entries) {
5061
5160
  if (entry.isFile() && entry.name === "SKILL.md") {
5062
- results.push(join26(dir, entry.name));
5161
+ results.push(join27(dir, entry.name));
5063
5162
  } else if (entry.isDirectory()) {
5064
- results.push(...findSkillMd(join26(dir, entry.name)));
5163
+ results.push(...findSkillMd(join27(dir, entry.name)));
5065
5164
  }
5066
5165
  }
5067
5166
  return results;
5068
5167
  }
5069
5168
  function findInstalledSkillMd(dir) {
5070
- const directSkills = findSkillNames(dir).map((name) => join26(dir, name, "SKILL.md"));
5169
+ const directSkills = findSkillNames(dir).map((name) => join27(dir, name, "SKILL.md"));
5071
5170
  return directSkills.length > 0 ? directSkills : findSkillMd(dir);
5072
5171
  }
5073
5172
  function findSkillNames(dir) {
@@ -5079,7 +5178,7 @@ function findSkillNames(dir) {
5079
5178
  return results;
5080
5179
  }
5081
5180
  for (const entry of entries) {
5082
- if (entry.isDirectory() && existsSync8(join26(dir, entry.name, "SKILL.md"))) {
5181
+ if (entry.isDirectory() && existsSync9(join27(dir, entry.name, "SKILL.md"))) {
5083
5182
  results.push(entry.name);
5084
5183
  }
5085
5184
  }
@@ -5123,7 +5222,7 @@ async function vaultMetrics(resolvedPath) {
5123
5222
  }
5124
5223
  let logLines = 0;
5125
5224
  try {
5126
- logLines = readFileSync7(join26(resolvedPath, "log.md"), "utf8").split("\n").length;
5225
+ logLines = readFileSync7(join27(resolvedPath, "log.md"), "utf8").split("\n").length;
5127
5226
  } catch {
5128
5227
  }
5129
5228
  return [
@@ -5194,8 +5293,8 @@ async function runDoctor(input) {
5194
5293
  }
5195
5294
 
5196
5295
  // src/commands/archive.ts
5197
- import { rename as rename7, mkdir as mkdir8, readFile as readFile19, writeFile as writeFile10 } from "fs/promises";
5198
- import { join as join27, dirname as dirname9 } from "path";
5296
+ import { rename as rename7, mkdir as mkdir9, readFile as readFile20, writeFile as writeFile10 } from "fs/promises";
5297
+ import { join as join28, dirname as dirname10 } from "path";
5199
5298
  function countWikilinks(body, slug) {
5200
5299
  const escaped = slug.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5201
5300
  const re = new RegExp(`\\[\\[${escaped}(?:[|#][^\\]]*)?\\]\\]`, "g");
@@ -5223,7 +5322,7 @@ async function runArchive(input) {
5223
5322
  if (!relPath) return { exitCode: ExitCode.ARCHIVE_TARGET_NOT_FOUND, result: err("ARCHIVE_TARGET_NOT_FOUND", { page: input.page }) };
5224
5323
  if (relPath.startsWith("_archive/")) return { exitCode: ExitCode.ARCHIVE_ALREADY_ARCHIVED, result: err("ARCHIVE_ALREADY_ARCHIVED", { page: relPath }) };
5225
5324
  const slug = relPath.replace(/\.md$/, "").split("/").pop();
5226
- const archivePath = join27("_archive", relPath).replace(/\\/g, "/");
5325
+ const archivePath = join28("_archive", relPath).replace(/\\/g, "/");
5227
5326
  let cascade;
5228
5327
  if (input.cascade) {
5229
5328
  const wikilinkRefs = [];
@@ -5247,7 +5346,7 @@ async function runArchive(input) {
5247
5346
  const indexRefs = [];
5248
5347
  if (!isRaw) {
5249
5348
  try {
5250
- const idx = await readFile19(join27(input.vault, "index.md"), "utf8");
5349
+ const idx = await readFile20(join28(input.vault, "index.md"), "utf8");
5251
5350
  idx.split("\n").forEach((line, i) => {
5252
5351
  if (line.includes(`[[${slug}]]`)) indexRefs.push({ line: i + 1, text: line });
5253
5352
  });
@@ -5273,8 +5372,8 @@ async function runArchive(input) {
5273
5372
  }
5274
5373
  if (input.cascade && input.apply && cascade) {
5275
5374
  for (const ref of cascade.source_array_refs) {
5276
- const absPath = join27(input.vault, ref.page);
5277
- const text = await readFile19(absPath, "utf8");
5375
+ const absPath = join28(input.vault, ref.page);
5376
+ const text = await readFile20(absPath, "utf8");
5278
5377
  const split = splitFrontmatter(text);
5279
5378
  if (!split.ok) continue;
5280
5379
  const before = split.data.rawFrontmatter;
@@ -5291,12 +5390,12 @@ ${fmRewritten}
5291
5390
  }
5292
5391
  }
5293
5392
  }
5294
- await mkdir8(dirname9(join27(input.vault, archivePath)), { recursive: true });
5393
+ await mkdir9(dirname10(join28(input.vault, archivePath)), { recursive: true });
5295
5394
  let indexUpdated = false;
5296
5395
  if (!isRaw) {
5297
- const indexPath = join27(input.vault, "index.md");
5396
+ const indexPath = join28(input.vault, "index.md");
5298
5397
  try {
5299
- const idx = await readFile19(indexPath, "utf8");
5398
+ const idx = await readFile20(indexPath, "utf8");
5300
5399
  const originalLines = idx.split("\n");
5301
5400
  const filtered = originalLines.filter((l) => !l.includes(`[[${slug}]]`));
5302
5401
  if (filtered.length !== originalLines.length) {
@@ -5307,7 +5406,7 @@ ${fmRewritten}
5307
5406
  if (e instanceof Error && "code" in e && e.code !== "ENOENT") throw e;
5308
5407
  }
5309
5408
  }
5310
- await rename7(join27(input.vault, relPath), join27(input.vault, archivePath));
5409
+ await rename7(join28(input.vault, relPath), join28(input.vault, archivePath));
5311
5410
  appendLastOp(input.vault, {
5312
5411
  operation: input.cascade ? "archive-cascade" : "archive",
5313
5412
  summary: `moved ${relPath} to ${archivePath}${input.cascade ? ` (cascade: ${cascade?.source_array_refs.length ?? 0} source arrays updated)` : ""}`,
@@ -5694,14 +5793,14 @@ ${newBody}`;
5694
5793
  // src/commands/update.ts
5695
5794
  import { execSync as execSync3 } from "child_process";
5696
5795
  import { readFileSync as readFileSync8 } from "fs";
5697
- import { join as join28 } from "path";
5796
+ import { join as join29 } from "path";
5698
5797
  function resolveGlobalSkillsRoot() {
5699
5798
  try {
5700
5799
  const globalRoot = execSync3("npm root -g", {
5701
5800
  encoding: "utf8",
5702
5801
  timeout: 5e3
5703
5802
  }).trim();
5704
- return join28(globalRoot, "skillwiki", "skills");
5803
+ return join29(globalRoot, "skillwiki", "skills");
5705
5804
  } catch {
5706
5805
  return null;
5707
5806
  }
@@ -5727,7 +5826,7 @@ async function runUpdate(input) {
5727
5826
  );
5728
5827
  const currentVersion = pkg2.version;
5729
5828
  const tag = input.distTag ?? "latest";
5730
- const target = join28(input.home, ".claude", "skills");
5829
+ const target = join29(input.home, ".claude", "skills");
5731
5830
  let latest;
5732
5831
  try {
5733
5832
  latest = execSync3(`npm view skillwiki@${tag} version`, {
@@ -5797,16 +5896,16 @@ async function runUpdate(input) {
5797
5896
 
5798
5897
  // src/commands/self-update.ts
5799
5898
  import { execSync as execSync4 } from "child_process";
5800
- import { existsSync as existsSync9, readFileSync as readFileSync9 } from "fs";
5801
- import { join as join29 } from "path";
5899
+ import { existsSync as existsSync10, readFileSync as readFileSync9 } from "fs";
5900
+ import { join as join30 } from "path";
5802
5901
  var DEFAULT_SOURCE_ROOT_SUFFIX = "/Desktop/code/llm-wiki";
5803
5902
  async function runSelfUpdate(input) {
5804
5903
  const currentVersion = JSON.parse(
5805
5904
  readFileSync9(new URL("../../package.json", import.meta.url), "utf8")
5806
5905
  ).version;
5807
5906
  const sourceRoot = input.sourceRoot ?? `${input.home}${DEFAULT_SOURCE_ROOT_SUFFIX}`;
5808
- const localPkgPath = join29(sourceRoot, "packages", "cli", "package.json");
5809
- const hasLocalSource = existsSync9(localPkgPath);
5907
+ const localPkgPath = join30(sourceRoot, "packages", "cli", "package.json");
5908
+ const hasLocalSource = existsSync10(localPkgPath);
5810
5909
  if (input.check) {
5811
5910
  let availableVersion = null;
5812
5911
  let source;
@@ -5937,10 +6036,10 @@ async function runSelfUpdate(input) {
5937
6036
  }
5938
6037
 
5939
6038
  // src/commands/transcripts.ts
5940
- import { readdir as readdir5, stat as stat7, readFile as readFile20 } from "fs/promises";
5941
- import { join as join30 } from "path";
6039
+ import { readdir as readdir5, stat as stat7, readFile as readFile21 } from "fs/promises";
6040
+ import { join as join31 } from "path";
5942
6041
  async function runTranscripts(input) {
5943
- const dir = join30(input.vault, "raw", "transcripts");
6042
+ const dir = join31(input.vault, "raw", "transcripts");
5944
6043
  let entries;
5945
6044
  try {
5946
6045
  entries = await readdir5(dir, { withFileTypes: true });
@@ -5950,8 +6049,8 @@ async function runTranscripts(input) {
5950
6049
  const transcripts = [];
5951
6050
  for (const entry of entries) {
5952
6051
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
5953
- const filePath = join30(dir, entry.name);
5954
- const content = await readFile20(filePath, "utf8");
6052
+ const filePath = join31(dir, entry.name);
6053
+ const content = await readFile21(filePath, "utf8");
5955
6054
  const fm = extractFrontmatter(content);
5956
6055
  if (!fm.ok) continue;
5957
6056
  const ingested = typeof fm.data.ingested === "string" ? fm.data.ingested : "";
@@ -5968,12 +6067,12 @@ async function runTranscripts(input) {
5968
6067
  }
5969
6068
 
5970
6069
  // src/commands/project-index.ts
5971
- import { readdir as readdir6, readFile as readFile21, writeFile as writeFile11, mkdir as mkdir9 } from "fs/promises";
5972
- import { join as join31, dirname as dirname10 } from "path";
6070
+ import { readdir as readdir6, readFile as readFile22, writeFile as writeFile11, mkdir as mkdir10 } from "fs/promises";
6071
+ import { join as join32, dirname as dirname11 } from "path";
5973
6072
  var LAYER2_DIRS = ["entities", "concepts", "comparisons", "queries", "meta"];
5974
6073
  async function runProjectIndex(input) {
5975
6074
  const slug = input.slug;
5976
- const projectDir = join31(input.vault, "projects", slug);
6075
+ const projectDir = join32(input.vault, "projects", slug);
5977
6076
  try {
5978
6077
  await readdir6(projectDir);
5979
6078
  } catch {
@@ -5984,15 +6083,15 @@ async function runProjectIndex(input) {
5984
6083
  }
5985
6084
  const wikilinkPattern = `[[${slug}]]`;
5986
6085
  const entries = [];
5987
- const compoundDir = join31(input.vault, "projects", slug, "compound");
6086
+ const compoundDir = join32(input.vault, "projects", slug, "compound");
5988
6087
  try {
5989
6088
  const compoundFiles = await readdir6(compoundDir, { withFileTypes: true });
5990
6089
  for (const entry of compoundFiles) {
5991
6090
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
5992
- const filePath = join31(compoundDir, entry.name);
6091
+ const filePath = join32(compoundDir, entry.name);
5993
6092
  let text;
5994
6093
  try {
5995
- text = await readFile21(filePath, "utf8");
6094
+ text = await readFile22(filePath, "utf8");
5996
6095
  } catch {
5997
6096
  continue;
5998
6097
  }
@@ -6009,16 +6108,16 @@ async function runProjectIndex(input) {
6009
6108
  for (const dir of LAYER2_DIRS) {
6010
6109
  let files;
6011
6110
  try {
6012
- files = await readdir6(join31(input.vault, dir), { withFileTypes: true });
6111
+ files = await readdir6(join32(input.vault, dir), { withFileTypes: true });
6013
6112
  } catch {
6014
6113
  continue;
6015
6114
  }
6016
6115
  for (const entry of files) {
6017
6116
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
6018
- const filePath = join31(input.vault, dir, entry.name);
6117
+ const filePath = join32(input.vault, dir, entry.name);
6019
6118
  let text;
6020
6119
  try {
6021
- text = await readFile21(filePath, "utf8");
6120
+ text = await readFile22(filePath, "utf8");
6022
6121
  } catch {
6023
6122
  continue;
6024
6123
  }
@@ -6039,11 +6138,11 @@ async function runProjectIndex(input) {
6039
6138
  const tb = typeOrder[b.type] ?? 99;
6040
6139
  return ta !== tb ? ta - tb : a.title.localeCompare(b.title);
6041
6140
  });
6042
- const indexPath = join31(projectDir, "knowledge.md");
6141
+ const indexPath = join32(projectDir, "knowledge.md");
6043
6142
  let existing = false;
6044
6143
  let stale = false;
6045
6144
  try {
6046
- const existingText = await readFile21(indexPath, "utf8");
6145
+ const existingText = await readFile22(indexPath, "utf8");
6047
6146
  existing = true;
6048
6147
  const existingEntries = existingText.split("\n").filter((l) => l.startsWith("- [["));
6049
6148
  const existingPages = new Set(existingEntries.map((l) => {
@@ -6083,7 +6182,7 @@ Autogenerated by \`skillwiki project-index\` on ${today}.
6083
6182
  }
6084
6183
  if (input.apply) {
6085
6184
  try {
6086
- await mkdir9(dirname10(indexPath), { recursive: true });
6185
+ await mkdir10(dirname11(indexPath), { recursive: true });
6087
6186
  await writeFile11(indexPath, body, "utf8");
6088
6187
  } catch (e) {
6089
6188
  return {
@@ -6112,10 +6211,10 @@ ${entries.map((e) => ` ${e.type}: [[${e.page.replace(/\.md$/, "")}]] \u2014 ${e
6112
6211
  }
6113
6212
 
6114
6213
  // src/commands/compound.ts
6115
- import { writeFile as writeFile12, mkdir as mkdir10, readdir as readdir7, unlink as unlink3 } from "fs/promises";
6116
- import { join as join32 } from "path";
6117
- import { existsSync as existsSync10 } from "fs";
6118
- import { readFile as readFile22 } from "fs/promises";
6214
+ import { writeFile as writeFile12, mkdir as mkdir11, readdir as readdir7, unlink as unlink4 } from "fs/promises";
6215
+ import { join as join33 } from "path";
6216
+ import { existsSync as existsSync11 } from "fs";
6217
+ import { readFile as readFile23 } from "fs/promises";
6119
6218
  var RETRO_HEADING_RE = /^## \[(\d{4}-\d{2}-\d{2})(?:\s+[^\]]+)?\] retro \| loop cycle(?: (\d+))?: (.+)$/;
6120
6219
  var FIELD_RE = {
6121
6220
  improve: /^-\s+\*?\*?Improve:?\*?\*?\s*(.+)$/m,
@@ -6213,17 +6312,17 @@ function extractRetroFields(date, cycleName, block) {
6213
6312
  };
6214
6313
  }
6215
6314
  async function runCompound(input) {
6216
- const logPath = join32(input.vault, "log.md");
6315
+ const logPath = join33(input.vault, "log.md");
6217
6316
  let logText;
6218
6317
  try {
6219
- logText = await readFile22(logPath, "utf8");
6318
+ logText = await readFile23(logPath, "utf8");
6220
6319
  } catch {
6221
6320
  return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path: logPath }) };
6222
6321
  }
6223
6322
  const entries = parseRetroEntries(logText);
6224
6323
  const promoted = [];
6225
6324
  const skipped = [];
6226
- const compoundDir = join32(input.vault, "projects", input.project, "compound");
6325
+ const compoundDir = join33(input.vault, "projects", input.project, "compound");
6227
6326
  for (const entry of entries) {
6228
6327
  const generalizeValue = entry.generalize.trim();
6229
6328
  if (!/^yes/i.test(generalizeValue)) {
@@ -6231,8 +6330,8 @@ async function runCompound(input) {
6231
6330
  continue;
6232
6331
  }
6233
6332
  const slug = slugify(entry.cycleName);
6234
- const compoundPath = join32(compoundDir, `${slug}.md`);
6235
- if (existsSync10(compoundPath)) {
6333
+ const compoundPath = join33(compoundDir, `${slug}.md`);
6334
+ if (existsSync11(compoundPath)) {
6236
6335
  skipped.push(entry.date);
6237
6336
  continue;
6238
6337
  }
@@ -6270,8 +6369,8 @@ async function runCompound(input) {
6270
6369
  ].join("\n");
6271
6370
  const content = frontmatter + "\n" + body;
6272
6371
  if (!input.dryRun) {
6273
- if (!existsSync10(compoundDir)) {
6274
- await mkdir10(compoundDir, { recursive: true });
6372
+ if (!existsSync11(compoundDir)) {
6373
+ await mkdir11(compoundDir, { recursive: true });
6275
6374
  }
6276
6375
  await writeFile12(compoundPath, content, "utf8");
6277
6376
  }
@@ -6292,23 +6391,23 @@ async function runCompound(input) {
6292
6391
  };
6293
6392
  }
6294
6393
  async function runCompoundDelete(input) {
6295
- const projectDir = join32(input.vault, "projects", input.project);
6296
- if (!existsSync10(projectDir)) {
6394
+ const projectDir = join33(input.vault, "projects", input.project);
6395
+ if (!existsSync11(projectDir)) {
6297
6396
  return {
6298
6397
  exitCode: ExitCode.PROJECT_NOT_FOUND,
6299
6398
  result: err("PROJECT_NOT_FOUND", { slug: input.project, path: projectDir })
6300
6399
  };
6301
6400
  }
6302
6401
  const entryName = input.entry.replace(/\.md$/, "");
6303
- const compoundPath = join32(projectDir, "compound", `${entryName}.md`);
6304
- if (!existsSync10(compoundPath)) {
6402
+ const compoundPath = join33(projectDir, "compound", `${entryName}.md`);
6403
+ if (!existsSync11(compoundPath)) {
6305
6404
  return {
6306
6405
  exitCode: ExitCode.FILE_NOT_FOUND,
6307
6406
  result: err("FILE_NOT_FOUND", { path: compoundPath })
6308
6407
  };
6309
6408
  }
6310
6409
  try {
6311
- await unlink3(compoundPath);
6410
+ await unlink4(compoundPath);
6312
6411
  } catch (e) {
6313
6412
  return {
6314
6413
  exitCode: ExitCode.WRITE_FAILED,
@@ -6334,8 +6433,8 @@ knowledge.md regenerated`
6334
6433
  };
6335
6434
  }
6336
6435
  async function runCompoundList(input) {
6337
- const compoundDir = join32(input.vault, "projects", input.project, "compound");
6338
- if (!existsSync10(compoundDir)) {
6436
+ const compoundDir = join33(input.vault, "projects", input.project, "compound");
6437
+ if (!existsSync11(compoundDir)) {
6339
6438
  return {
6340
6439
  exitCode: ExitCode.OK,
6341
6440
  result: ok({
@@ -6365,10 +6464,10 @@ could not read compound directory`
6365
6464
  const entries = [];
6366
6465
  for (const dirent of dirents) {
6367
6466
  if (!dirent.isFile() || !dirent.name.endsWith(".md")) continue;
6368
- const filePath = join32(compoundDir, dirent.name);
6467
+ const filePath = join33(compoundDir, dirent.name);
6369
6468
  let text;
6370
6469
  try {
6371
- text = await readFile22(filePath, "utf8");
6470
+ text = await readFile23(filePath, "utf8");
6372
6471
  } catch {
6373
6472
  continue;
6374
6473
  }
@@ -6397,9 +6496,9 @@ no compound entries found`;
6397
6496
  }
6398
6497
 
6399
6498
  // src/commands/observe.ts
6400
- import { mkdir as mkdir11, writeFile as writeFile13 } from "fs/promises";
6401
- import { existsSync as existsSync11, statSync as statSync4 } from "fs";
6402
- import { join as join33 } from "path";
6499
+ import { mkdir as mkdir12, writeFile as writeFile13 } from "fs/promises";
6500
+ import { existsSync as existsSync12, statSync as statSync4 } from "fs";
6501
+ import { join as join34 } from "path";
6403
6502
  import { createHash as createHash4 } from "crypto";
6404
6503
  var ALLOWED_KINDS = /* @__PURE__ */ new Set(["note", "bug", "task", "idea", "session-log"]);
6405
6504
  function slugify2(text) {
@@ -6422,15 +6521,15 @@ async function runObserve(input) {
6422
6521
  result: err("SCHEME_REJECTED", { message: "Text must not be empty" })
6423
6522
  };
6424
6523
  }
6425
- if (!existsSync11(input.vault) || !statSync4(input.vault).isDirectory()) {
6524
+ if (!existsSync12(input.vault) || !statSync4(input.vault).isDirectory()) {
6426
6525
  return {
6427
6526
  exitCode: ExitCode.VAULT_PATH_INVALID,
6428
6527
  result: err("VAULT_PATH_INVALID", { path: input.vault })
6429
6528
  };
6430
6529
  }
6431
- const transcriptsDir = join33(input.vault, "raw", "transcripts");
6530
+ const transcriptsDir = join34(input.vault, "raw", "transcripts");
6432
6531
  try {
6433
- await mkdir11(transcriptsDir, { recursive: true });
6532
+ await mkdir12(transcriptsDir, { recursive: true });
6434
6533
  } catch {
6435
6534
  return {
6436
6535
  exitCode: ExitCode.VAULT_PATH_INVALID,
@@ -6440,7 +6539,7 @@ async function runObserve(input) {
6440
6539
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
6441
6540
  const slug = slugify2(input.text);
6442
6541
  const fileName = `${today}-observation-${slug}.md`;
6443
- const filePath = join33(transcriptsDir, fileName);
6542
+ const filePath = join34(transcriptsDir, fileName);
6444
6543
  const body = `
6445
6544
  ${input.text.trim()}
6446
6545
  `;
@@ -6480,8 +6579,8 @@ ${input.text.trim()}
6480
6579
  }
6481
6580
 
6482
6581
  // src/commands/ingest.ts
6483
- import { readFile as readFile23, writeFile as writeFile14, mkdir as mkdir12 } from "fs/promises";
6484
- import { join as join34 } from "path";
6582
+ import { readFile as readFile24, writeFile as writeFile14, mkdir as mkdir13 } from "fs/promises";
6583
+ import { join as join35 } from "path";
6485
6584
  import { createHash as createHash5 } from "crypto";
6486
6585
  var ALLOWED_TYPES = /* @__PURE__ */ new Set(["entity", "concept", "comparison", "query"]);
6487
6586
  var TYPE_DIR = {
@@ -6640,7 +6739,7 @@ async function runIngest(input) {
6640
6739
  sourceContent = fetchResult.data.body;
6641
6740
  } else {
6642
6741
  try {
6643
- sourceContent = await readFile23(input.source, "utf8");
6742
+ sourceContent = await readFile24(input.source, "utf8");
6644
6743
  } catch {
6645
6744
  return {
6646
6745
  exitCode: ExitCode.FILE_NOT_FOUND,
@@ -6655,8 +6754,8 @@ async function runIngest(input) {
6655
6754
  const rawRelPath = `raw/articles/${slug}.md`;
6656
6755
  const typedDir = TYPE_DIR[input.type] ?? `${input.type}s`;
6657
6756
  const typedRelPath = `${typedDir}/${slug}.md`;
6658
- const rawAbsPath = join34(input.vault, rawRelPath);
6659
- const typedAbsPath = join34(input.vault, typedRelPath);
6757
+ const rawAbsPath = join35(input.vault, rawRelPath);
6758
+ const typedAbsPath = join35(input.vault, typedRelPath);
6660
6759
  const rawContent = buildRawContent(sourceUrl, today, sha256, sourceContent);
6661
6760
  const typedContent = buildTypedContent(
6662
6761
  input.title,
@@ -6719,7 +6818,7 @@ async function runIngest(input) {
6719
6818
  };
6720
6819
  }
6721
6820
  try {
6722
- await mkdir12(join34(input.vault, "raw", "articles"), { recursive: true });
6821
+ await mkdir13(join35(input.vault, "raw", "articles"), { recursive: true });
6723
6822
  await writeFile14(rawAbsPath, rawContent, "utf8");
6724
6823
  } catch (e) {
6725
6824
  return {
@@ -6728,7 +6827,7 @@ async function runIngest(input) {
6728
6827
  };
6729
6828
  }
6730
6829
  try {
6731
- await mkdir12(join34(input.vault, typedDir), { recursive: true });
6830
+ await mkdir13(join35(input.vault, typedDir), { recursive: true });
6732
6831
  await writeFile14(typedAbsPath, typedContent, "utf8");
6733
6832
  } catch (e) {
6734
6833
  return {
@@ -6907,12 +7006,12 @@ ${body}`;
6907
7006
  }
6908
7007
 
6909
7008
  // src/commands/sync.ts
6910
- import { existsSync as existsSync13 } from "fs";
6911
- import { join as join36 } from "path";
7009
+ import { existsSync as existsSync14 } from "fs";
7010
+ import { join as join37 } from "path";
6912
7011
 
6913
7012
  // src/utils/sync-lock.ts
6914
- import { existsSync as existsSync12, mkdirSync as mkdirSync4, readFileSync as readFileSync10, renameSync, unlinkSync as unlinkSync5, writeFileSync as writeFileSync6 } from "fs";
6915
- import { join as join35 } from "path";
7013
+ import { existsSync as existsSync13, mkdirSync as mkdirSync4, readFileSync as readFileSync10, renameSync, unlinkSync as unlinkSync5, writeFileSync as writeFileSync6 } from "fs";
7014
+ import { join as join36 } from "path";
6916
7015
  import { createHash as createHash6 } from "crypto";
6917
7016
  function getSessionId() {
6918
7017
  if (process.env.CLAUDE_SESSION_ID) return process.env.CLAUDE_SESSION_ID;
@@ -6920,11 +7019,11 @@ function getSessionId() {
6920
7019
  return process.pid.toString();
6921
7020
  }
6922
7021
  function lockPath(vault) {
6923
- return join35(vault, ".skillwiki", "sync.lock");
7022
+ return join36(vault, ".skillwiki", "sync.lock");
6924
7023
  }
6925
7024
  function readLock(vault) {
6926
7025
  const path = lockPath(vault);
6927
- if (!existsSync12(path)) return null;
7026
+ if (!existsSync13(path)) return null;
6928
7027
  try {
6929
7028
  const raw = readFileSync10(path, "utf8");
6930
7029
  return JSON.parse(raw);
@@ -6939,8 +7038,8 @@ function isStale(lock, now) {
6939
7038
  }
6940
7039
  function acquireLock(vault, opts = {}) {
6941
7040
  const path = lockPath(vault);
6942
- const dir = join35(vault, ".skillwiki");
6943
- if (!existsSync12(dir)) {
7041
+ const dir = join36(vault, ".skillwiki");
7042
+ if (!existsSync13(dir)) {
6944
7043
  mkdirSync4(dir, { recursive: true });
6945
7044
  }
6946
7045
  const sessionId = opts.sessionId ?? getSessionId();
@@ -6985,7 +7084,7 @@ function writeLockedFile(path, lock) {
6985
7084
  }
6986
7085
  function releaseLock(vault, opts = {}) {
6987
7086
  const path = lockPath(vault);
6988
- if (!existsSync12(path)) {
7087
+ if (!existsSync13(path)) {
6989
7088
  return { released: false };
6990
7089
  }
6991
7090
  const sessionId = opts.sessionId ?? getSessionId();
@@ -7014,7 +7113,7 @@ function releaseLock(vault, opts = {}) {
7014
7113
  function runSyncStatus(input) {
7015
7114
  const vault = input.vault;
7016
7115
  const includeStashes = input.includeStashes ?? false;
7017
- if (!existsSync13(join36(vault, ".git"))) {
7116
+ if (!existsSync14(join37(vault, ".git"))) {
7018
7117
  return {
7019
7118
  exitCode: ExitCode.VAULT_PATH_INVALID,
7020
7119
  result: ok({
@@ -7028,6 +7127,7 @@ function runSyncStatus(input) {
7028
7127
  })
7029
7128
  };
7030
7129
  }
7130
+ enableGitLongPathsOnWindows(vault);
7031
7131
  const porcelain = git(vault, ["status", "--porcelain"]);
7032
7132
  const dirty = porcelain ? porcelain.split("\n").filter((l) => l.trim().length > 0).length : 0;
7033
7133
  const revOutput = git(vault, ["rev-list", "--left-right", "--count", "origin/HEAD...HEAD"]);
@@ -7091,12 +7191,24 @@ function runSyncStatus(input) {
7091
7191
  }
7092
7192
  async function runSyncPush(input) {
7093
7193
  const vault = input.vault;
7094
- if (!existsSync13(join36(vault, ".git"))) {
7194
+ if (!existsSync14(join37(vault, ".git"))) {
7095
7195
  return {
7096
7196
  exitCode: ExitCode.VAULT_PATH_INVALID,
7097
7197
  result: err("NOT_A_GIT_REPO", { path: vault })
7098
7198
  };
7099
7199
  }
7200
+ enableGitLongPathsOnWindows(vault);
7201
+ let pathFixes = 0;
7202
+ const pathFix = await fixPathTooLong({ vault });
7203
+ if (pathFix.result.ok && pathFix.result.data.fixed.length > 0) {
7204
+ pathFixes = pathFix.result.data.fixed.length;
7205
+ appendLastOp(vault, {
7206
+ operation: "lint-fix",
7207
+ summary: `fixed ${pathFixes} long path(s)`,
7208
+ files: pathFix.result.data.fixed.flatMap((f) => [f.from, f.to]),
7209
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
7210
+ });
7211
+ }
7100
7212
  const porcelain = git(vault, ["status", "--porcelain"]);
7101
7213
  const dirtyFiles = porcelain ? porcelain.split("\n").filter((l) => l.trim().length > 0) : [];
7102
7214
  if (dirtyFiles.length === 0) {
@@ -7106,6 +7218,7 @@ async function runSyncPush(input) {
7106
7218
  files_committed: 0,
7107
7219
  commit_message: "",
7108
7220
  pushed: false,
7221
+ path_fixes: pathFixes,
7109
7222
  humanHint: "nothing to commit, working tree clean"
7110
7223
  })
7111
7224
  };
@@ -7160,7 +7273,8 @@ async function runSyncPush(input) {
7160
7273
  files_committed: dirtyFiles.length,
7161
7274
  commit_message: commitMessage,
7162
7275
  pushed: false,
7163
- humanHint: `committed ${dirtyFiles.length} file(s) but push failed: ${String(e)}`
7276
+ path_fixes: pathFixes,
7277
+ humanHint: `committed ${dirtyFiles.length} file(s)${pathFixes > 0 ? ` after ${pathFixes} long-path fix(es)` : ""} but push failed: ${String(e)}`
7164
7278
  })
7165
7279
  };
7166
7280
  }
@@ -7170,7 +7284,8 @@ async function runSyncPush(input) {
7170
7284
  files_committed: dirtyFiles.length,
7171
7285
  commit_message: commitMessage,
7172
7286
  pushed,
7173
- humanHint: `committed and pushed ${dirtyFiles.length} file(s)`
7287
+ path_fixes: pathFixes,
7288
+ humanHint: `committed and pushed ${dirtyFiles.length} file(s)${pathFixes > 0 ? ` after ${pathFixes} long-path fix(es)` : ""}`
7174
7289
  })
7175
7290
  };
7176
7291
  }
@@ -7193,14 +7308,19 @@ function enumerateStashes(vault) {
7193
7308
  }
7194
7309
  return stashes;
7195
7310
  }
7311
+ function enableGitLongPathsOnWindows(vault) {
7312
+ if (process.platform !== "win32") return;
7313
+ git(vault, ["config", "core.longpaths", "true"]);
7314
+ }
7196
7315
  async function runSyncPull(input) {
7197
7316
  const vault = input.vault;
7198
- if (!existsSync13(join36(vault, ".git"))) {
7317
+ if (!existsSync14(join37(vault, ".git"))) {
7199
7318
  return {
7200
7319
  exitCode: ExitCode.VAULT_PATH_INVALID,
7201
7320
  result: err("NOT_A_GIT_REPO", { path: vault })
7202
7321
  };
7203
7322
  }
7323
+ enableGitLongPathsOnWindows(vault);
7204
7324
  let fetched = false;
7205
7325
  try {
7206
7326
  gitStrict(vault, ["fetch", "origin"]);
@@ -7289,6 +7409,8 @@ async function runSyncPull(input) {
7289
7409
  };
7290
7410
  }
7291
7411
  }
7412
+ const pathFix = await fixPathTooLong({ vault });
7413
+ const pathFixCount = pathFix.result.ok ? pathFix.result.data.fixed.length : 0;
7292
7414
  let lintErrors = 0;
7293
7415
  let lintWarnings = 0;
7294
7416
  const lintResult = await runLint({ vault, days: 90, lines: 200, logThreshold: 500 });
@@ -7300,6 +7422,7 @@ async function runSyncPull(input) {
7300
7422
  if (filesUpdated > 0) hintParts.push(`updated ${filesUpdated} file(s)`);
7301
7423
  else hintParts.push("already up to date");
7302
7424
  if (autoResolved > 0) hintParts.push(`${autoResolved} conflict(s) auto-resolved`);
7425
+ if (pathFixCount > 0) hintParts.push(`${pathFixCount} long path(s) fixed`);
7303
7426
  if (lintErrors > 0) hintParts.push(`${lintErrors} lint error(s)`);
7304
7427
  if (lintWarnings > 0) hintParts.push(`${lintWarnings} lint warning(s)`);
7305
7428
  const exitCode = lintErrors > 0 ? ExitCode.LINT_HAS_ERRORS : lintWarnings > 0 ? ExitCode.LINT_HAS_WARNINGS : ExitCode.OK;
@@ -7363,7 +7486,7 @@ function runSyncPeers(input) {
7363
7486
  }
7364
7487
  function runSyncLock(input) {
7365
7488
  const vault = input.vault;
7366
- if (!existsSync13(vault)) {
7489
+ if (!existsSync14(vault)) {
7367
7490
  return {
7368
7491
  exitCode: ExitCode.VAULT_PATH_INVALID,
7369
7492
  result: err("VAULT_PATH_INVALID", { path: vault })
@@ -7398,7 +7521,7 @@ function runSyncLock(input) {
7398
7521
  }
7399
7522
  function runSyncUnlock(input) {
7400
7523
  const vault = input.vault;
7401
- if (!existsSync13(vault)) {
7524
+ if (!existsSync14(vault)) {
7402
7525
  return {
7403
7526
  exitCode: ExitCode.VAULT_PATH_INVALID,
7404
7527
  result: err("VAULT_PATH_INVALID", { path: vault })
@@ -7432,7 +7555,7 @@ function runSyncUnlock(input) {
7432
7555
 
7433
7556
  // src/commands/backup.ts
7434
7557
  import { statSync as statSync5, readdirSync as readdirSync2, readFileSync as readFileSync11, mkdirSync as mkdirSync5, writeFileSync as writeFileSync7 } from "fs";
7435
- import { join as join37, relative as relative3, dirname as dirname11 } from "path";
7558
+ import { join as join38, relative as relative3, dirname as dirname12 } from "path";
7436
7559
  import { PutObjectCommand, HeadObjectCommand, ListObjectsV2Command, GetObjectCommand, DeleteObjectsCommand } from "@aws-sdk/client-s3";
7437
7560
 
7438
7561
  // src/utils/s3-client.ts
@@ -7452,11 +7575,11 @@ function createS3Client(config) {
7452
7575
  }
7453
7576
 
7454
7577
  // src/commands/backup.ts
7455
- var SKIP_DIRS = /* @__PURE__ */ new Set([".git", ".obsidian", "_archive", "node_modules", ".skillwiki"]);
7578
+ var SKIP_DIRS2 = /* @__PURE__ */ new Set([".git", ".obsidian", "_archive", "node_modules", ".skillwiki"]);
7456
7579
  function* walkMarkdown(dir, base) {
7457
7580
  for (const entry of readdirSync2(dir, { withFileTypes: true })) {
7458
- if (SKIP_DIRS.has(entry.name)) continue;
7459
- const full = join37(dir, entry.name);
7581
+ if (SKIP_DIRS2.has(entry.name)) continue;
7582
+ const full = join38(dir, entry.name);
7460
7583
  if (entry.isDirectory()) {
7461
7584
  yield* walkMarkdown(full, base);
7462
7585
  } else if (entry.name.endsWith(".md")) {
@@ -7479,7 +7602,7 @@ async function runBackupSync(input) {
7479
7602
  let failed = 0;
7480
7603
  const files = [...walkMarkdown(input.vault, input.vault)];
7481
7604
  for (const relPath of files) {
7482
- const absPath = join37(input.vault, relPath);
7605
+ const absPath = join38(input.vault, relPath);
7483
7606
  const localStat = statSync5(absPath);
7484
7607
  let needsUpload = true;
7485
7608
  try {
@@ -7555,7 +7678,7 @@ async function runBackupRestore(input) {
7555
7678
  const objects = list.Contents ?? [];
7556
7679
  for (const obj of objects) {
7557
7680
  if (!obj.Key) continue;
7558
- const localPath = join37(target, obj.Key);
7681
+ const localPath = join38(target, obj.Key);
7559
7682
  try {
7560
7683
  const localStat = statSync5(localPath);
7561
7684
  if (obj.LastModified && localStat.mtime > obj.LastModified) {
@@ -7568,7 +7691,7 @@ async function runBackupRestore(input) {
7568
7691
  const resp = await client.send(new GetObjectCommand({ Bucket: input.bucket, Key: obj.Key }));
7569
7692
  const body = await resp.Body?.transformToByteArray();
7570
7693
  if (body) {
7571
- mkdirSync5(dirname11(localPath), { recursive: true });
7694
+ mkdirSync5(dirname12(localPath), { recursive: true });
7572
7695
  writeFileSync7(localPath, Buffer.from(body));
7573
7696
  downloaded++;
7574
7697
  }
@@ -7601,11 +7724,11 @@ async function runBackupRestore(input) {
7601
7724
  }
7602
7725
 
7603
7726
  // src/commands/status.ts
7604
- import { existsSync as existsSync14, statSync as statSync6 } from "fs";
7605
- import { readFile as readFile24 } from "fs/promises";
7606
- import { join as join38 } from "path";
7727
+ import { existsSync as existsSync15, statSync as statSync6 } from "fs";
7728
+ import { readFile as readFile25 } from "fs/promises";
7729
+ import { join as join39 } from "path";
7607
7730
  async function runStatus(input) {
7608
- if (!existsSync14(input.vault)) {
7731
+ if (!existsSync15(input.vault)) {
7609
7732
  return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { vault: input.vault }) };
7610
7733
  }
7611
7734
  const scan = await scanVault(input.vault);
@@ -7630,7 +7753,7 @@ async function runStatus(input) {
7630
7753
  const compound = scan.data.compound.length;
7631
7754
  let schemaVersion = "v1";
7632
7755
  try {
7633
- const schemaContent = await readFile24(join38(input.vault, "SCHEMA.md"), "utf8");
7756
+ const schemaContent = await readFile25(join39(input.vault, "SCHEMA.md"), "utf8");
7634
7757
  const versionMatch = schemaContent.match(/version:\s*["']?([^"'\s\n]+)/i);
7635
7758
  if (versionMatch) schemaVersion = versionMatch[1];
7636
7759
  } catch {
@@ -7690,8 +7813,8 @@ async function runStatus(input) {
7690
7813
  }
7691
7814
 
7692
7815
  // src/commands/seed.ts
7693
- import { mkdir as mkdir13, writeFile as writeFile15, stat as stat8 } from "fs/promises";
7694
- import { join as join39 } from "path";
7816
+ import { mkdir as mkdir14, writeFile as writeFile15, stat as stat8 } from "fs/promises";
7817
+ import { join as join40 } from "path";
7695
7818
  var TODAY = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
7696
7819
  var EXAMPLE_PAGES = {
7697
7820
  "entities/example-project.md": `---
@@ -7760,29 +7883,29 @@ Real sources are immutable after ingestion \u2014 never edit them.
7760
7883
  `;
7761
7884
  async function runSeed(input) {
7762
7885
  try {
7763
- await stat8(join39(input.vault, "SCHEMA.md"));
7886
+ await stat8(join40(input.vault, "SCHEMA.md"));
7764
7887
  } catch {
7765
7888
  return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { root: input.vault, reason: "SCHEMA.md missing \u2014 run `skillwiki init` first" }) };
7766
7889
  }
7767
7890
  const created = [];
7768
7891
  const skipped = [];
7769
7892
  for (const [relPath, content] of Object.entries(EXAMPLE_PAGES)) {
7770
- const absPath = join39(input.vault, relPath);
7893
+ const absPath = join40(input.vault, relPath);
7771
7894
  try {
7772
7895
  await stat8(absPath);
7773
7896
  skipped.push(relPath);
7774
7897
  } catch {
7775
- await mkdir13(join39(absPath, ".."), { recursive: true });
7898
+ await mkdir14(join40(absPath, ".."), { recursive: true });
7776
7899
  await writeFile15(absPath, content, "utf8");
7777
7900
  created.push(relPath);
7778
7901
  }
7779
7902
  }
7780
- const rawPath = join39(input.vault, "raw", "articles", "example-source.md");
7903
+ const rawPath = join40(input.vault, "raw", "articles", "example-source.md");
7781
7904
  try {
7782
7905
  await stat8(rawPath);
7783
7906
  skipped.push("raw/articles/example-source.md");
7784
7907
  } catch {
7785
- await mkdir13(join39(rawPath, ".."), { recursive: true });
7908
+ await mkdir14(join40(rawPath, ".."), { recursive: true });
7786
7909
  await writeFile15(rawPath, EXAMPLE_RAW, "utf8");
7787
7910
  created.push("raw/articles/example-source.md");
7788
7911
  }
@@ -7805,9 +7928,9 @@ async function runSeed(input) {
7805
7928
  }
7806
7929
 
7807
7930
  // src/commands/canvas.ts
7808
- import { readFile as readFile25, writeFile as writeFile16 } from "fs/promises";
7809
- import { existsSync as existsSync15 } from "fs";
7810
- import { join as join40 } from "path";
7931
+ import { readFile as readFile26, writeFile as writeFile16 } from "fs/promises";
7932
+ import { existsSync as existsSync16 } from "fs";
7933
+ import { join as join41 } from "path";
7811
7934
  var NODE_WIDTH = 240;
7812
7935
  var NODE_HEIGHT = 60;
7813
7936
  var COLUMN_SPACING = 400;
@@ -7885,8 +8008,8 @@ function buildCanvasEdges(adjacency) {
7885
8008
  return edges;
7886
8009
  }
7887
8010
  async function runCanvasGenerate(input) {
7888
- const graphPath = input.graphPath ?? join40(input.vault, ".skillwiki", "graph.json");
7889
- if (!existsSync15(graphPath)) {
8011
+ const graphPath = input.graphPath ?? join41(input.vault, ".skillwiki", "graph.json");
8012
+ if (!existsSync16(graphPath)) {
7890
8013
  return {
7891
8014
  exitCode: ExitCode.FILE_NOT_FOUND,
7892
8015
  result: err("FILE_NOT_FOUND", {
@@ -7897,7 +8020,7 @@ async function runCanvasGenerate(input) {
7897
8020
  }
7898
8021
  let raw;
7899
8022
  try {
7900
- raw = await readFile25(graphPath, "utf8");
8023
+ raw = await readFile26(graphPath, "utf8");
7901
8024
  } catch (e) {
7902
8025
  return {
7903
8026
  exitCode: ExitCode.FILE_NOT_FOUND,
@@ -7923,7 +8046,7 @@ async function runCanvasGenerate(input) {
7923
8046
  const nodes = buildCanvasNodes(paths);
7924
8047
  const edges = buildCanvasEdges(graph.adjacency);
7925
8048
  const canvas = { nodes, edges };
7926
- const outPath = join40(input.vault, "vault-graph.canvas");
8049
+ const outPath = join41(input.vault, "vault-graph.canvas");
7927
8050
  try {
7928
8051
  await writeFile16(outPath, JSON.stringify(canvas, null, 2));
7929
8052
  } catch (e) {
@@ -7945,8 +8068,8 @@ written: ${outPath}`
7945
8068
  }
7946
8069
 
7947
8070
  // src/commands/query.ts
7948
- import { readFile as readFile26, stat as stat9 } from "fs/promises";
7949
- import { join as join41 } from "path";
8071
+ import { readFile as readFile27, stat as stat9 } from "fs/promises";
8072
+ import { join as join42 } from "path";
7950
8073
  var W_KEYWORD = 2;
7951
8074
  var W_SOURCE_OVERLAP = 4;
7952
8075
  var W_WIKILINK = 3;
@@ -8067,7 +8190,7 @@ function computeKeywordScore(terms, title, tags, body) {
8067
8190
  return score;
8068
8191
  }
8069
8192
  async function loadOrBuildGraph(vault) {
8070
- const graphPath = join41(vault, ".skillwiki", "graph.json");
8193
+ const graphPath = join42(vault, ".skillwiki", "graph.json");
8071
8194
  let needsBuild = false;
8072
8195
  try {
8073
8196
  const fileStat = await stat9(graphPath);
@@ -8081,7 +8204,7 @@ async function loadOrBuildGraph(vault) {
8081
8204
  if (buildResult.exitCode !== 0) return null;
8082
8205
  }
8083
8206
  try {
8084
- const raw = await readFile26(graphPath, "utf8");
8207
+ const raw = await readFile27(graphPath, "utf8");
8085
8208
  return JSON.parse(raw);
8086
8209
  } catch {
8087
8210
  return null;
@@ -8089,14 +8212,14 @@ async function loadOrBuildGraph(vault) {
8089
8212
  }
8090
8213
 
8091
8214
  // src/utils/auto-commit.ts
8092
- import { existsSync as existsSync16 } from "fs";
8093
- import { join as join42 } from "path";
8215
+ import { existsSync as existsSync17 } from "fs";
8216
+ import { join as join43 } from "path";
8094
8217
  async function postCommit(vault, exitCode) {
8095
8218
  if (exitCode !== 0) return;
8096
8219
  const home = process.env.HOME ?? "";
8097
8220
  const dotenv = await parseDotenvFile(configPath(home));
8098
8221
  if (dotenv["AUTO_COMMIT"] === "false") return;
8099
- if (!existsSync16(join42(vault, ".git"))) return;
8222
+ if (!existsSync17(join43(vault, ".git"))) return;
8100
8223
  const lastOps = readLastOp(vault);
8101
8224
  if (lastOps.length === 0) return;
8102
8225
  const porcelain = git(vault, ["status", "--porcelain"]);
@@ -8147,7 +8270,7 @@ program.command("validate <file>").description("validate vault page frontmatter
8147
8270
  emit(await runValidate({ file, apply: !!opts.apply, vault }), vault);
8148
8271
  });
8149
8272
  program.command("graph").description("graph subcommands").command("build <vault>").option("--out <path>", "graph output path (default: <vault>/.skillwiki/graph.json)").option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
8150
- const out = opts.out ?? join43(vault, ".skillwiki", "graph.json");
8273
+ const out = opts.out ?? join44(vault, ".skillwiki", "graph.json");
8151
8274
  emit(await runGraphBuild({ vault, out }), vault);
8152
8275
  });
8153
8276
  var canvasCmd = program.command("canvas").description("manage Obsidian canvas files");