skillwiki 0.8.1-beta.6 → 0.8.1-beta.9

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,91 @@ 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 } from "path";
2840
2847
  var MAX_PATH_LENGTH = 240;
2841
2848
  async function runPathTooLong(input) {
2842
2849
  const scan = await scanVault(input.vault);
2843
2850
  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
- }
2851
+ const violations = findPathTooLongViolations(scan.data.allMarkdown);
2851
2852
  if (violations.length > 0) {
2852
2853
  return {
2853
2854
  exitCode: ExitCode.LINT_HAS_ERRORS,
2854
2855
  result: ok({
2855
2856
  violations,
2856
- humanHint: violations.map((v) => `${v.relPath}: ${v.length} chars (max ${MAX_PATH_LENGTH})`).join("\n")
2857
+ humanHint: violations.map((v) => `${v.relPath}: ${v.length} chars (max ${MAX_PATH_LENGTH}) -> ${v.suggestedRelPath}`).join("\n")
2857
2858
  })
2858
2859
  };
2859
2860
  }
2860
2861
  return { exitCode: ExitCode.OK, result: ok({ violations, humanHint: "all paths within length limit" }) };
2861
2862
  }
2863
+ async function fixPathTooLong(input) {
2864
+ const scan = await scanVault(input.vault);
2865
+ if (!scan.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scan };
2866
+ const violations = findPathTooLongViolations(scan.data.allMarkdown);
2867
+ const fixed = [];
2868
+ const unresolved = [];
2869
+ for (const violation of violations) {
2870
+ const target = await resolveFixTarget(input.vault, violation.relPath, violation.suggestedRelPath);
2871
+ if (!target || target.relPath === violation.relPath || target.relPath.length > MAX_PATH_LENGTH) {
2872
+ unresolved.push(violation.relPath);
2873
+ continue;
2874
+ }
2875
+ try {
2876
+ if (target.mode === "dedupe") {
2877
+ await unlink3(join21(input.vault, violation.relPath));
2878
+ } else {
2879
+ await mkdir8(dirname8(join21(input.vault, target.relPath)), { recursive: true });
2880
+ await rename6(join21(input.vault, violation.relPath), join21(input.vault, target.relPath));
2881
+ }
2882
+ fixed.push({ from: violation.relPath, to: target.relPath });
2883
+ } catch {
2884
+ unresolved.push(violation.relPath);
2885
+ }
2886
+ }
2887
+ const rewired = [];
2888
+ if (fixed.length > 0) {
2889
+ const afterScan = await scanVault(input.vault);
2890
+ if (afterScan.ok) {
2891
+ for (const page of afterScan.data.allMarkdown) {
2892
+ if (!shouldRewriteReferences(page.relPath)) continue;
2893
+ try {
2894
+ const original = await readFile16(page.absPath, "utf8");
2895
+ let updated = original;
2896
+ for (const fix of fixed) {
2897
+ updated = replacePathReferences(updated, fix.from, fix.to);
2898
+ }
2899
+ if (updated !== original) {
2900
+ const write = await safeWritePage(page.absPath, updated);
2901
+ if (write.ok) rewired.push(page.relPath);
2902
+ else unresolved.push(`${page.relPath} (rewire)`);
2903
+ }
2904
+ } catch {
2905
+ unresolved.push(`${page.relPath} (rewire)`);
2906
+ }
2907
+ }
2908
+ }
2909
+ }
2910
+ const hintLines = [
2911
+ `fixed: ${fixed.length}`,
2912
+ `rewired: ${rewired.length}`,
2913
+ `unresolved: ${unresolved.length}`
2914
+ ];
2915
+ for (const f of fixed) hintLines.push(` ${f.from} -> ${f.to}`);
2916
+ for (const u of unresolved) hintLines.push(` unresolved: ${u}`);
2917
+ return {
2918
+ exitCode: unresolved.length > 0 ? ExitCode.LINT_HAS_ERRORS : ExitCode.OK,
2919
+ result: ok({ fixed, unresolved, rewired, humanHint: hintLines.join("\n") })
2920
+ };
2921
+ }
2922
+ function findPathTooLongViolations(pages) {
2923
+ return pages.filter((page) => page.relPath.length > MAX_PATH_LENGTH).map((page) => ({
2924
+ relPath: page.relPath,
2925
+ length: page.relPath.length,
2926
+ suggestedRelPath: truncateFilename(page.relPath)
2927
+ }));
2928
+ }
2862
2929
  function truncateFilename(relPath, maxLength = MAX_PATH_LENGTH) {
2863
2930
  if (relPath.length <= maxLength) return relPath;
2864
2931
  const lastSlash = relPath.lastIndexOf("/");
@@ -2872,15 +2939,62 @@ function truncateFilename(relPath, maxLength = MAX_PATH_LENGTH) {
2872
2939
  const maxPrefixLen = maxLength - dirPrefix.length - suffix.length;
2873
2940
  if (maxPrefixLen <= 0) {
2874
2941
  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;
2942
+ return fallback.length <= maxLength ? fallback : relPath;
2880
2943
  }
2881
2944
  const prefix = base.slice(0, maxPrefixLen).replace(/[-_\s]+$/, "");
2882
2945
  return dirPrefix + prefix + suffix;
2883
2946
  }
2947
+ async function resolveFixTarget(vault, original, preferred) {
2948
+ for (const candidate of candidateRelPaths(preferred)) {
2949
+ if (candidate === original || candidate.length > MAX_PATH_LENGTH) continue;
2950
+ const candidatePath = join21(vault, candidate);
2951
+ if (!existsSync4(candidatePath)) return { relPath: candidate, mode: "rename" };
2952
+ if (await hasSameContent(join21(vault, original), candidatePath)) {
2953
+ return { relPath: candidate, mode: "dedupe" };
2954
+ }
2955
+ }
2956
+ return null;
2957
+ }
2958
+ function candidateRelPaths(preferred) {
2959
+ const candidates = [preferred];
2960
+ if (preferred.length > MAX_PATH_LENGTH) return candidates;
2961
+ const dir = posix.dirname(preferred) === "." ? "" : posix.dirname(preferred);
2962
+ const filename = posix.basename(preferred);
2963
+ const ext = filename.endsWith(".md") ? ".md" : "";
2964
+ const base = ext ? filename.slice(0, -3) : filename;
2965
+ const dirPrefix = dir ? `${dir}/` : "";
2966
+ for (let i = 2; i < 100; i++) {
2967
+ const suffix = `-${i}${ext}`;
2968
+ const prefixBudget = MAX_PATH_LENGTH - dirPrefix.length - suffix.length;
2969
+ if (prefixBudget <= 0) break;
2970
+ candidates.push(`${dirPrefix}${base.slice(0, prefixBudget).replace(/[-_\s]+$/, "")}${suffix}`);
2971
+ }
2972
+ return candidates;
2973
+ }
2974
+ async function hasSameContent(a, b) {
2975
+ try {
2976
+ const [left, right] = await Promise.all([readFile16(a), readFile16(b)]);
2977
+ return left.equals(right);
2978
+ } catch {
2979
+ return false;
2980
+ }
2981
+ }
2982
+ function shouldRewriteReferences(relPath) {
2983
+ if (relPath.startsWith("raw/")) return false;
2984
+ if (relPath.startsWith("_archive/")) return false;
2985
+ return true;
2986
+ }
2987
+ function replacePathReferences(content, oldRelPath, newRelPath) {
2988
+ let updated = content.replaceAll(oldRelPath, newRelPath);
2989
+ const oldStem = posix.basename(oldRelPath).replace(/\.md$/, "");
2990
+ const newStem = posix.basename(newRelPath).replace(/\.md$/, "");
2991
+ if (oldStem !== newStem) {
2992
+ const oldStemEscaped = oldStem.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2993
+ const stemWikilinkRe = new RegExp(`\\[\\[${oldStemEscaped}(\\|[^\\]]*)?\\]\\]`, "g");
2994
+ updated = updated.replace(stemWikilinkRe, (_match, alias) => `[[${newStem}${alias ?? ""}]]`);
2995
+ }
2996
+ return updated;
2997
+ }
2884
2998
  function computeShortHash(input) {
2885
2999
  let hash = 2166136261;
2886
3000
  for (let i = 0; i < input.length; i++) {
@@ -3069,7 +3183,15 @@ function extractSourceEntries(rawFm) {
3069
3183
  var ERROR_ORDER = ["broken_wikilinks", "invalid_frontmatter", "raw_dedup", "broken_sources", "tag_not_in_taxonomy", "path_too_long"];
3070
3184
  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
3185
  var INFO_ORDER = ["bridges", "sparse_community", "page_structure", "topic_map_recommended", "frontmatter_wikilink", "wikilink_citation", "missing_tldr", "stale_sections", "cli_refs"];
3186
+ var KNOWN_BUCKETS = [...ERROR_ORDER, ...WARNING_ORDER, ...INFO_ORDER];
3072
3187
  async function runLint(input) {
3188
+ if (input.only && !KNOWN_BUCKETS.includes(input.only)) {
3189
+ return {
3190
+ exitCode: ExitCode.USAGE,
3191
+ result: { ok: false, error: "UNKNOWN_BUCKET", detail: `Unknown bucket "${input.only}". Valid: ${KNOWN_BUCKETS.join(", ")}` }
3192
+ };
3193
+ }
3194
+ const shouldFix = (bucket) => !!input.fix && (!input.only || input.only === bucket);
3073
3195
  const buckets = {};
3074
3196
  const fixed = [];
3075
3197
  const unresolved = [];
@@ -3193,7 +3315,7 @@ async function runLint(input) {
3193
3315
  let rawPath = entry.replace(/^"/, "").replace(/"$/, "").replace(/^'/, "").replace(/'$/, "");
3194
3316
  rawPath = rawPath.replace(/^\^\[/, "").replace(/\]$/, "");
3195
3317
  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"))) {
3318
+ 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
3319
  brokenSourceFlags.push(`${page.relPath}: ${rawPath}`);
3198
3320
  }
3199
3321
  }
@@ -3284,11 +3406,11 @@ async function runLint(input) {
3284
3406
  const slugMatch = String(entry).match(/\[\[([^\]]+)\]\]/);
3285
3407
  if (!slugMatch) continue;
3286
3408
  const slug = slugMatch[1];
3287
- const knowledgePath = join21(input.vault, "projects", slug, "knowledge.md");
3288
- if (!existsSync4(knowledgePath)) continue;
3409
+ const knowledgePath = join22(input.vault, "projects", slug, "knowledge.md");
3410
+ if (!existsSync5(knowledgePath)) continue;
3289
3411
  const pageRef = page.relPath.replace(/\.md$/, "");
3290
3412
  try {
3291
- const knowledgeContent = await readFile16(knowledgePath, "utf8");
3413
+ const knowledgeContent = await readFile17(knowledgePath, "utf8");
3292
3414
  if (!knowledgeContent.includes(`[[${pageRef}]]`)) {
3293
3415
  orphanedProjectPages.push(`${page.relPath}: not in projects/${slug}/knowledge.md`);
3294
3416
  }
@@ -3329,13 +3451,13 @@ async function runLint(input) {
3329
3451
  }
3330
3452
  }
3331
3453
  if (staleSectionFlags.length > 0) buckets.stale_sections = staleSectionFlags;
3332
- if (input.fix && legacyPages.length > 0) {
3454
+ if (shouldFix("legacy_citation_style") && legacyPages.length > 0) {
3333
3455
  const FENCE_RE2 = /```[\s\S]*?```/g;
3334
3456
  const INLINE_MARKER = /\^\[raw\/[^\]]+\]/g;
3335
3457
  for (const relPath of legacyPages) {
3336
3458
  try {
3337
3459
  const absPath = `${input.vault}/${relPath}`;
3338
- const raw = await readFile16(absPath, "utf8");
3460
+ const raw = await readFile17(absPath, "utf8");
3339
3461
  const split = splitFrontmatter(raw);
3340
3462
  if (!split.ok) {
3341
3463
  unresolved.push(relPath);
@@ -3430,11 +3552,11 @@ ${newBody}`;
3430
3552
  else delete buckets.legacy_citation_style;
3431
3553
  }
3432
3554
  }
3433
- if (input.fix && noOverview.length > 0) {
3555
+ if (shouldFix("missing_overview") && noOverview.length > 0) {
3434
3556
  for (const relPath of noOverview) {
3435
3557
  try {
3436
3558
  const absPath = `${input.vault}/${relPath}`;
3437
- const raw = await readFile16(absPath, "utf8");
3559
+ const raw = await readFile17(absPath, "utf8");
3438
3560
  const split = splitFrontmatter(raw);
3439
3561
  if (!split.ok) {
3440
3562
  unresolved.push(relPath);
@@ -3471,11 +3593,11 @@ ${trimmedBody}`;
3471
3593
  if (remaining.length > 0) buckets.missing_overview = remaining;
3472
3594
  else delete buckets.missing_overview;
3473
3595
  }
3474
- if (input.fix && missingTldrFlags.length > 0) {
3596
+ if (shouldFix("missing_tldr") && missingTldrFlags.length > 0) {
3475
3597
  for (const relPath of missingTldrFlags) {
3476
3598
  try {
3477
3599
  const absPath = `${input.vault}/${relPath}`;
3478
- const raw = await readFile16(absPath, "utf8");
3600
+ const raw = await readFile17(absPath, "utf8");
3479
3601
  const split = splitFrontmatter(raw);
3480
3602
  if (!split.ok) {
3481
3603
  unresolved.push(relPath);
@@ -3518,14 +3640,14 @@ ${lines.join("\n")}`;
3518
3640
  if (remaining.length > 0) buckets.missing_tldr = remaining;
3519
3641
  else delete buckets.missing_tldr;
3520
3642
  }
3521
- if (input.fix && wikilinkCitationFlags.length > 0) {
3643
+ if (shouldFix("wikilink_citation") && wikilinkCitationFlags.length > 0) {
3522
3644
  const WIKILINK_RE = /\[\[raw\/([^\]|]+)(?:\|[^\]]*)?\]\]/g;
3523
3645
  const FENCE_RE2 = /```[\s\S]*?```/g;
3524
3646
  const wikilinkFixed = [];
3525
3647
  for (const relPath of wikilinkCitationFlags) {
3526
3648
  try {
3527
3649
  const absPath = `${input.vault}/${relPath}`;
3528
- const raw = await readFile16(absPath, "utf8");
3650
+ const raw = await readFile17(absPath, "utf8");
3529
3651
  const split = splitFrontmatter(raw);
3530
3652
  if (!split.ok) {
3531
3653
  unresolved.push(relPath);
@@ -3607,12 +3729,12 @@ ${newBody}`;
3607
3729
  else delete buckets.wikilink_citation;
3608
3730
  }
3609
3731
  }
3610
- if (input.fix && fileSourceUrlFlags.length > 0) {
3732
+ if (shouldFix("file_source_url") && fileSourceUrlFlags.length > 0) {
3611
3733
  const FILE_FIXED = [];
3612
3734
  for (const relPath of fileSourceUrlFlags) {
3613
3735
  try {
3614
3736
  const absPath = `${input.vault}/${relPath}`;
3615
- const raw = await readFile16(absPath, "utf8");
3737
+ const raw = await readFile17(absPath, "utf8");
3616
3738
  const parts = raw.split("---", 3);
3617
3739
  if (parts.length < 3) {
3618
3740
  unresolved.push(relPath);
@@ -3647,36 +3769,11 @@ ${newBody}`;
3647
3769
  }
3648
3770
  }
3649
3771
  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
- }
3772
+ if (shouldFix("path_too_long") && pathViolations && pathViolations.length > 0) {
3773
+ const pathFix = await fixPathTooLong({ vault: input.vault });
3774
+ const pathFixed = pathFix.result.ok ? pathFix.result.data.fixed.map((f) => f.from) : [];
3775
+ if (pathFix.result.ok) unresolved.push(...pathFix.result.data.unresolved);
3776
+ else unresolved.push(...pathViolations.map((v) => v.relPath));
3680
3777
  fixed.push(...pathFixed);
3681
3778
  if (pathFixed.length > 0) {
3682
3779
  const fixedSet = new Set(pathFixed);
@@ -3690,13 +3787,6 @@ ${newBody}`;
3690
3787
  const warningOut = WARNING_ORDER.flatMap((k) => buckets[k] ? [{ kind: k, items: buckets[k] }] : []);
3691
3788
  const infoOut = INFO_ORDER.flatMap((k) => buckets[k] ? [{ kind: k, items: buckets[k] }] : []);
3692
3789
  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
3790
  const match = [...errorOut, ...warningOut, ...infoOut].filter((b) => b.kind === input.only);
3701
3791
  const severity = ERROR_ORDER.includes(input.only) ? "error" : WARNING_ORDER.includes(input.only) ? "warning" : "info";
3702
3792
  const filtered = severity === "error" ? { error: match, warning: [], info: [] } : severity === "warning" ? { error: [], warning: match, info: [] } : { error: [], warning: [], info: match };
@@ -3760,14 +3850,14 @@ ${match.length === 0 ? "0 violations" : match.map((b) => ` ${b.kind}: ${b.items
3760
3850
  }
3761
3851
 
3762
3852
  // 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";
3853
+ import { readFile as readFile18 } from "fs/promises";
3854
+ import { existsSync as existsSync6 } from "fs";
3855
+ import { join as join23 } from "path";
3766
3856
  function validateKey(key) {
3767
3857
  return CONFIG_KEYS.includes(key) || isValidWikiProfileKey(key);
3768
3858
  }
3769
3859
  function configPath(home) {
3770
- return join22(home, ".skillwiki", ".env");
3860
+ return join23(home, ".skillwiki", ".env");
3771
3861
  }
3772
3862
  async function runConfigGet(input) {
3773
3863
  if (!validateKey(input.key)) {
@@ -3785,7 +3875,7 @@ async function runConfigSet(input) {
3785
3875
  try {
3786
3876
  let originalContent;
3787
3877
  try {
3788
- originalContent = await readFile17(filePath, "utf8");
3878
+ originalContent = await readFile18(filePath, "utf8");
3789
3879
  } catch {
3790
3880
  }
3791
3881
  const existing = originalContent !== void 0 ? parseDotenvText(originalContent) : {};
@@ -3817,18 +3907,18 @@ async function runConfigList(input) {
3817
3907
  }
3818
3908
  async function runConfigPath(input) {
3819
3909
  const filePath = configPath(input.home);
3820
- return { exitCode: ExitCode.OK, result: ok({ path: filePath, exists: existsSync5(filePath), humanHint: filePath }) };
3910
+ return { exitCode: ExitCode.OK, result: ok({ path: filePath, exists: existsSync6(filePath), humanHint: filePath }) };
3821
3911
  }
3822
3912
 
3823
3913
  // 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";
3914
+ import { existsSync as existsSync9, lstatSync, readlinkSync, readdirSync, statSync as statSync3, readFileSync as readFileSync7 } from "fs";
3915
+ import { join as join27, resolve as resolve4 } from "path";
3826
3916
  import { execSync as execSync2 } from "child_process";
3827
3917
  import { platform as platform2 } from "os";
3828
3918
 
3829
3919
  // 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";
3920
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, existsSync as existsSync7, mkdirSync as mkdirSync3 } from "fs";
3921
+ import { join as join24, dirname as dirname9 } from "path";
3832
3922
  import { spawn } from "child_process";
3833
3923
 
3834
3924
  // src/utils/update-consts.ts
@@ -3839,7 +3929,7 @@ var CLI_DISABLE_FLAG = "--no-update-notifier";
3839
3929
 
3840
3930
  // src/utils/auto-update.ts
3841
3931
  function cachePath(home) {
3842
- return join23(home, ".skillwiki", CACHE_FILENAME);
3932
+ return join24(home, ".skillwiki", CACHE_FILENAME);
3843
3933
  }
3844
3934
  function readCacheRaw(home) {
3845
3935
  try {
@@ -3858,7 +3948,7 @@ function readCache(home) {
3858
3948
  }
3859
3949
  function writeCache(home, cache) {
3860
3950
  const p = cachePath(home);
3861
- mkdirSync3(dirname8(p), { recursive: true });
3951
+ mkdirSync3(dirname9(p), { recursive: true });
3862
3952
  writeFileSync4(p, JSON.stringify(cache, null, 2));
3863
3953
  }
3864
3954
  function latestFromCache(home, currentVersion) {
@@ -3877,7 +3967,7 @@ function triggerAutoUpdate(home, currentVersion) {
3877
3967
  const { isStale: isStale2 } = readCache(home);
3878
3968
  if (!isStale2) return;
3879
3969
  const bgScript = new URL("../auto-update-bg.js", import.meta.url).pathname;
3880
- if (!existsSync6(bgScript)) return;
3970
+ if (!existsSync7(bgScript)) return;
3881
3971
  const child = spawn(process.execPath, [bgScript, home, currentVersion], {
3882
3972
  detached: true,
3883
3973
  stdio: "ignore"
@@ -3889,12 +3979,12 @@ function triggerAutoUpdate(home, currentVersion) {
3889
3979
 
3890
3980
  // src/utils/plugin-registry.ts
3891
3981
  import { readFileSync as readFileSync5 } from "fs";
3892
- import { join as join24 } from "path";
3893
- var REGISTRY_PATH = join24(".claude", "plugins", "installed_plugins.json");
3982
+ import { join as join25 } from "path";
3983
+ var REGISTRY_PATH = join25(".claude", "plugins", "installed_plugins.json");
3894
3984
  var PLUGIN_KEY = "skillwiki@llm-wiki";
3895
3985
  function readInstalledPlugins(home) {
3896
3986
  try {
3897
- const raw = readFileSync5(join24(home, REGISTRY_PATH), "utf8");
3987
+ const raw = readFileSync5(join25(home, REGISTRY_PATH), "utf8");
3898
3988
  return JSON.parse(raw);
3899
3989
  } catch {
3900
3990
  return null;
@@ -3911,8 +4001,8 @@ function findPlugin(home, key = PLUGIN_KEY) {
3911
4001
  // src/utils/s3-mount-health.ts
3912
4002
  import { execSync } from "child_process";
3913
4003
  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";
4004
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, unlinkSync as unlinkSync4, readFileSync as readFile19 } from "fs";
4005
+ import { join as join26 } from "path";
3916
4006
  var OS = platform();
3917
4007
  function findRcloneMountPid() {
3918
4008
  try {
@@ -4070,7 +4160,7 @@ function detectFuseMount(vaultPath) {
4070
4160
  return null;
4071
4161
  }
4072
4162
  function writeTest(dir) {
4073
- const testFile = join25(dir, `.doctor-write-test-${process.pid}.tmp`);
4163
+ const testFile = join26(dir, `.doctor-write-test-${process.pid}.tmp`);
4074
4164
  const payload = `skillwiki doctor write test \u2014 ${Date.now()} \u2014 ${Math.random().toString(36).slice(2)}`;
4075
4165
  const start = Date.now();
4076
4166
  try {
@@ -4081,7 +4171,7 @@ function writeTest(dir) {
4081
4171
  const writeMs = Date.now() - start;
4082
4172
  const readStart = Date.now();
4083
4173
  try {
4084
- const back = readFile18(testFile, "utf8");
4174
+ const back = readFile19(testFile, "utf8");
4085
4175
  const readMs = Date.now() - readStart;
4086
4176
  if (back !== payload) {
4087
4177
  try {
@@ -4170,13 +4260,13 @@ function detectCliChannels(argv, home) {
4170
4260
  }
4171
4261
  const plugin = findPlugin(home);
4172
4262
  if (plugin) {
4173
- const pluginBin = join26(plugin.installPath, "bin", "skillwiki");
4174
- if (existsSync8(pluginBin)) {
4263
+ const pluginBin = join27(plugin.installPath, "bin", "skillwiki");
4264
+ if (existsSync9(pluginBin)) {
4175
4265
  channels.push({ name: "plugin", path: pluginBin, isDevLink: false });
4176
4266
  }
4177
4267
  }
4178
- const installBin = join26(home, ".claude", "skills", "bin", "skillwiki");
4179
- if (existsSync8(installBin)) {
4268
+ const installBin = join27(home, ".claude", "skills", "bin", "skillwiki");
4269
+ if (existsSync9(installBin)) {
4180
4270
  channels.push({ name: "install", path: installBin, isDevLink: false });
4181
4271
  }
4182
4272
  return channels;
@@ -4228,7 +4318,7 @@ function checkCliChannels(argv, home) {
4228
4318
  }
4229
4319
  async function checkConfigFile(home) {
4230
4320
  const cfgPath = configPath(home);
4231
- if (!existsSync8(cfgPath)) {
4321
+ if (!existsSync9(cfgPath)) {
4232
4322
  return check("warn", "config_file", "Config file exists", `${cfgPath} not found`);
4233
4323
  }
4234
4324
  try {
@@ -4243,7 +4333,7 @@ function checkWikiPathExists(resolvedPath) {
4243
4333
  if (resolvedPath === void 0) {
4244
4334
  return check("error", "wiki_path_exists", "Vault directory exists", "Cannot check \u2014 WIKI_PATH not resolved");
4245
4335
  }
4246
- if (existsSync8(resolvedPath) && statSync3(resolvedPath).isDirectory()) {
4336
+ if (existsSync9(resolvedPath) && statSync3(resolvedPath).isDirectory()) {
4247
4337
  return check("pass", "wiki_path_exists", "Vault directory exists", resolvedPath);
4248
4338
  }
4249
4339
  return check("error", "wiki_path_exists", "Vault directory exists", `${resolvedPath} does not exist or is not a directory`);
@@ -4252,13 +4342,13 @@ function checkVaultStructure(resolvedPath) {
4252
4342
  if (resolvedPath === void 0) {
4253
4343
  return check("error", "vault_structure", "Vault structure valid", "Cannot check \u2014 WIKI_PATH not resolved");
4254
4344
  }
4255
- if (!existsSync8(resolvedPath)) {
4345
+ if (!existsSync9(resolvedPath)) {
4256
4346
  return check("error", "vault_structure", "Vault structure valid", "Cannot check \u2014 vault directory does not exist");
4257
4347
  }
4258
4348
  const missing = [];
4259
- if (!existsSync8(join26(resolvedPath, "SCHEMA.md"))) missing.push("SCHEMA.md");
4349
+ if (!existsSync9(join27(resolvedPath, "SCHEMA.md"))) missing.push("SCHEMA.md");
4260
4350
  for (const dir of ["raw", "entities", "concepts", "meta"]) {
4261
- if (!existsSync8(join26(resolvedPath, dir))) missing.push(dir + "/");
4351
+ if (!existsSync9(join27(resolvedPath, dir))) missing.push(dir + "/");
4262
4352
  }
4263
4353
  if (missing.length === 0) {
4264
4354
  return check("pass", "vault_structure", "Vault structure valid", "All required files and directories present");
@@ -4266,23 +4356,23 @@ function checkVaultStructure(resolvedPath) {
4266
4356
  return check("warn", "vault_structure", "Vault structure valid", `Missing: ${missing.join(", ")} \u2014 run \`skillwiki init\` to add CodeWiki structure`);
4267
4357
  }
4268
4358
  function checkSkillsInstalled(home, cwd) {
4269
- const srcDir = cwd ? join26(cwd, "packages", "skills") : void 0;
4270
- if (srcDir && existsSync8(srcDir)) {
4271
- const found = findSkillMd(srcDir);
4359
+ const srcDir = cwd ? join27(cwd, "packages", "skills") : void 0;
4360
+ if (srcDir && existsSync9(srcDir)) {
4361
+ const found = findInstalledSkillMd(srcDir);
4272
4362
  if (found.length > 0) {
4273
4363
  return check("pass", "skills_installed", "Skills installed", `${found.length} SKILL.md file(s) found (source)`);
4274
4364
  }
4275
4365
  }
4276
4366
  const plugin = findPlugin(home);
4277
4367
  if (plugin) {
4278
- const found = findSkillMd(plugin.installPath);
4368
+ const found = findInstalledSkillMd(plugin.installPath);
4279
4369
  if (found.length > 0) {
4280
4370
  return check("pass", "skills_installed", "Skills installed", `${found.length} SKILL.md file(s) found (plugin v${plugin.version})`);
4281
4371
  }
4282
4372
  }
4283
- const skillsDir = join26(home, ".claude", "skills");
4284
- if (existsSync8(skillsDir)) {
4285
- const found = findSkillMd(skillsDir);
4373
+ const skillsDir = join27(home, ".claude", "skills");
4374
+ if (existsSync9(skillsDir)) {
4375
+ const found = findInstalledSkillMd(skillsDir);
4286
4376
  if (found.length > 0) {
4287
4377
  return check("pass", "skills_installed", "Skills installed", `${found.length} SKILL.md file(s) found (CLI install)`);
4288
4378
  }
@@ -4291,10 +4381,10 @@ function checkSkillsInstalled(home, cwd) {
4291
4381
  }
4292
4382
  function checkDuplicateSkills(home) {
4293
4383
  const plugin = findPlugin(home);
4294
- const skillsDir = join26(home, ".claude", "skills");
4384
+ const skillsDir = join27(home, ".claude", "skills");
4295
4385
  const agentSkillDirs = [
4296
- { label: "~/.codex/skills/", path: join26(home, ".codex", "skills") },
4297
- { label: "~/.agents/skills/", path: join26(home, ".agents", "skills") }
4386
+ { label: "~/.codex/skills/", path: join27(home, ".codex", "skills") },
4387
+ { label: "~/.agents/skills/", path: join27(home, ".agents", "skills") }
4298
4388
  ];
4299
4389
  if (!plugin) {
4300
4390
  return check("pass", "skills_duplicate", "Skills not duplicated", "Single install channel");
@@ -4371,8 +4461,8 @@ async function checkProfiles(home) {
4371
4461
  }
4372
4462
  async function checkProjectLocalOverride(cwd) {
4373
4463
  const dir = cwd ?? process.cwd();
4374
- const envPath = join26(dir, ".skillwiki", ".env");
4375
- if (existsSync8(envPath)) {
4464
+ const envPath = join27(dir, ".skillwiki", ".env");
4465
+ if (existsSync9(envPath)) {
4376
4466
  return check("pass", "project_local", "Project-local config", `Found: ${envPath}`);
4377
4467
  }
4378
4468
  return check("pass", "project_local", "Project-local config", "None");
@@ -4381,7 +4471,7 @@ function checkVaultGitRemote(resolvedPath) {
4381
4471
  if (resolvedPath === void 0) {
4382
4472
  return check("error", "vault_git_remote", "Vault git remote", "Cannot check \u2014 WIKI_PATH not resolved");
4383
4473
  }
4384
- if (!existsSync8(join26(resolvedPath, ".git"))) {
4474
+ if (!existsSync9(join27(resolvedPath, ".git"))) {
4385
4475
  return check("warn", "vault_git_remote", "Vault git remote", "Vault is not a git repository \u2014 sync features unavailable");
4386
4476
  }
4387
4477
  try {
@@ -4404,9 +4494,9 @@ function checkObsidianTemplates(resolvedPath) {
4404
4494
  return check("error", "obsidian_templates", "Obsidian templates", "Cannot check \u2014 WIKI_PATH not resolved");
4405
4495
  }
4406
4496
  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");
4497
+ if (!existsSync9(join27(resolvedPath, "_Templates"))) missing.push("_Templates/");
4498
+ if (!existsSync9(join27(resolvedPath, ".obsidian", "templates.json"))) missing.push(".obsidian/templates.json");
4499
+ if (!existsSync9(join27(resolvedPath, ".obsidian", "app.json"))) missing.push(".obsidian/app.json");
4410
4500
  if (missing.length === 0) {
4411
4501
  return check("pass", "obsidian_templates", "Obsidian templates", "Template folder and config present");
4412
4502
  }
@@ -4416,8 +4506,8 @@ function checkDotStoreClean(resolvedPath) {
4416
4506
  if (resolvedPath === void 0) {
4417
4507
  return check("error", "dsstore_clean", "No .DS_Store in raw/", "Cannot check \u2014 WIKI_PATH not resolved");
4418
4508
  }
4419
- const rawDir = join26(resolvedPath, "raw");
4420
- if (!existsSync8(rawDir)) {
4509
+ const rawDir = join27(resolvedPath, "raw");
4510
+ if (!existsSync9(rawDir)) {
4421
4511
  return check("pass", "dsstore_clean", "No .DS_Store in raw/", "raw/ directory not found \u2014 check skipped");
4422
4512
  }
4423
4513
  const found = [];
@@ -4432,7 +4522,7 @@ function checkDotStoreClean(resolvedPath) {
4432
4522
  if (entry.name === ".DS_Store") {
4433
4523
  found.push(rel ? `${rel}/.DS_Store` : ".DS_Store");
4434
4524
  } else if (entry.isDirectory()) {
4435
- walk2(join26(dir, entry.name), rel ? `${rel}/${entry.name}` : entry.name);
4525
+ walk2(join27(dir, entry.name), rel ? `${rel}/${entry.name}` : entry.name);
4436
4526
  }
4437
4527
  }
4438
4528
  })(rawDir, "");
@@ -4445,7 +4535,7 @@ function checkSyncLastPush(resolvedPath) {
4445
4535
  if (resolvedPath === void 0) {
4446
4536
  return check("error", "sync_last_push", "Vault sync recency", "Cannot check \u2014 WIKI_PATH not resolved");
4447
4537
  }
4448
- if (!existsSync8(join26(resolvedPath, ".git"))) {
4538
+ if (!existsSync9(join27(resolvedPath, ".git"))) {
4449
4539
  return check("pass", "sync_last_push", "Vault sync recency", "No git repo \u2014 sync check skipped");
4450
4540
  }
4451
4541
  let timestamp;
@@ -4486,8 +4576,8 @@ function checkS3MountPerf(resolvedPath) {
4486
4576
  return check("pass", "s3_mount_perf", "S3 mount performance", "local disk");
4487
4577
  }
4488
4578
  const mountPoint = fuse.mountPoint;
4489
- const conceptsDir = join26(resolvedPath, "concepts");
4490
- if (!existsSync8(conceptsDir)) {
4579
+ const conceptsDir = join27(resolvedPath, "concepts");
4580
+ if (!existsSync9(conceptsDir)) {
4491
4581
  return check("pass", "s3_mount_perf", "S3 mount performance", `S3 FUSE mount (${mountPoint}), no concepts/ to benchmark`);
4492
4582
  }
4493
4583
  const start = Date.now();
@@ -4669,8 +4759,8 @@ function checkWriteTest(resolvedPath) {
4669
4759
  if (!fuse) {
4670
4760
  return check("pass", "s3_write_test", "S3 write test", "local disk \u2014 check skipped");
4671
4761
  }
4672
- const conceptsDir = join26(resolvedPath, "concepts");
4673
- if (!existsSync8(conceptsDir)) {
4762
+ const conceptsDir = join27(resolvedPath, "concepts");
4763
+ if (!existsSync9(conceptsDir)) {
4674
4764
  return check("pass", "s3_write_test", "S3 write test", "no concepts/ dir to test \u2014 check skipped");
4675
4765
  }
4676
4766
  const result = writeTest(conceptsDir);
@@ -4756,7 +4846,7 @@ function checkVfsCacheHealth(resolvedPath) {
4756
4846
  }
4757
4847
  function readVaultSyncConfig(home) {
4758
4848
  try {
4759
- const content = readFileSync7(join26(home, ".skillwiki", ".env"), "utf8");
4849
+ const content = readFileSync7(join27(home, ".skillwiki", ".env"), "utf8");
4760
4850
  let installed = false;
4761
4851
  let role;
4762
4852
  for (const line of content.split(/\r?\n/)) {
@@ -4790,12 +4880,12 @@ function vaultSyncChecks(input) {
4790
4880
  ];
4791
4881
  }
4792
4882
  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");
4883
+ const logDir = input.logDir ?? (isMac ? join27(home, "Library", "Logs") : join27(home, ".local", "state", "vault-sync", "log"));
4884
+ const shareDir = input.shareDir ?? (isMac ? join27(home, "Library", "Application Support", "vault-sync", "bin") : join27(home, ".local", "share", "vault-sync", "bin"));
4885
+ const filterPath = input.filterPath ?? join27(home, ".config", "rclone", "wiki-push-filters.txt");
4796
4886
  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`);
4887
+ const pushScriptPath = join27(shareDir, "wiki-push.sh");
4888
+ 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
4889
  let c2;
4800
4890
  try {
4801
4891
  if (isMac) {
@@ -4846,7 +4936,7 @@ function vaultSyncChecks(input) {
4846
4936
  "Scheduler check failed \u2014 run vault-sync-install"
4847
4937
  );
4848
4938
  }
4849
- const logFile = join26(logDir, "wiki-push.log");
4939
+ const logFile = join27(logDir, "wiki-push.log");
4850
4940
  let c3;
4851
4941
  try {
4852
4942
  const logContent = readFileSync7(logFile, "utf8");
@@ -4905,7 +4995,7 @@ function vaultSyncChecks(input) {
4905
4995
  }
4906
4996
  }
4907
4997
  } catch {
4908
- c3 = existsSync8(logDir) ? check(
4998
+ c3 = existsSync9(logDir) ? check(
4909
4999
  "warn",
4910
5000
  "vault_sync_last_push_age",
4911
5001
  "Vault sync last push recency",
@@ -4917,7 +5007,7 @@ function vaultSyncChecks(input) {
4917
5007
  `Log directory not found at ${logDir}`
4918
5008
  );
4919
5009
  }
4920
- const fetchLogFile = join26(logDir, "wiki-fetch.log");
5010
+ const fetchLogFile = join27(logDir, "wiki-fetch.log");
4921
5011
  let cFetch;
4922
5012
  try {
4923
5013
  const logContent = readFileSync7(fetchLogFile, "utf8");
@@ -4964,7 +5054,7 @@ function vaultSyncChecks(input) {
4964
5054
  }
4965
5055
  let c4;
4966
5056
  try {
4967
- if (!existsSync8(filterPath)) {
5057
+ if (!existsSync9(filterPath)) {
4968
5058
  c4 = check(
4969
5059
  "error",
4970
5060
  "vault_sync_filter_present",
@@ -5013,7 +5103,7 @@ function vaultSyncChecks(input) {
5013
5103
  );
5014
5104
  } else {
5015
5105
  try {
5016
- if (!existsSync8(snapshotPath)) {
5106
+ if (!existsSync9(snapshotPath)) {
5017
5107
  c5 = check(
5018
5108
  "error",
5019
5109
  "vault_sync_snapshot_guard",
@@ -5059,13 +5149,17 @@ function findSkillMd(dir) {
5059
5149
  }
5060
5150
  for (const entry of entries) {
5061
5151
  if (entry.isFile() && entry.name === "SKILL.md") {
5062
- results.push(join26(dir, entry.name));
5152
+ results.push(join27(dir, entry.name));
5063
5153
  } else if (entry.isDirectory()) {
5064
- results.push(...findSkillMd(join26(dir, entry.name)));
5154
+ results.push(...findSkillMd(join27(dir, entry.name)));
5065
5155
  }
5066
5156
  }
5067
5157
  return results;
5068
5158
  }
5159
+ function findInstalledSkillMd(dir) {
5160
+ const directSkills = findSkillNames(dir).map((name) => join27(dir, name, "SKILL.md"));
5161
+ return directSkills.length > 0 ? directSkills : findSkillMd(dir);
5162
+ }
5069
5163
  function findSkillNames(dir) {
5070
5164
  const results = [];
5071
5165
  let entries;
@@ -5075,7 +5169,7 @@ function findSkillNames(dir) {
5075
5169
  return results;
5076
5170
  }
5077
5171
  for (const entry of entries) {
5078
- if (entry.isDirectory() && existsSync8(join26(dir, entry.name, "SKILL.md"))) {
5172
+ if (entry.isDirectory() && existsSync9(join27(dir, entry.name, "SKILL.md"))) {
5079
5173
  results.push(entry.name);
5080
5174
  }
5081
5175
  }
@@ -5119,7 +5213,7 @@ async function vaultMetrics(resolvedPath) {
5119
5213
  }
5120
5214
  let logLines = 0;
5121
5215
  try {
5122
- logLines = readFileSync7(join26(resolvedPath, "log.md"), "utf8").split("\n").length;
5216
+ logLines = readFileSync7(join27(resolvedPath, "log.md"), "utf8").split("\n").length;
5123
5217
  } catch {
5124
5218
  }
5125
5219
  return [
@@ -5190,8 +5284,8 @@ async function runDoctor(input) {
5190
5284
  }
5191
5285
 
5192
5286
  // src/commands/archive.ts
5193
- import { rename as rename7, mkdir as mkdir8, readFile as readFile19, writeFile as writeFile10 } from "fs/promises";
5194
- import { join as join27, dirname as dirname9 } from "path";
5287
+ import { rename as rename7, mkdir as mkdir9, readFile as readFile20, writeFile as writeFile10 } from "fs/promises";
5288
+ import { join as join28, dirname as dirname10 } from "path";
5195
5289
  function countWikilinks(body, slug) {
5196
5290
  const escaped = slug.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5197
5291
  const re = new RegExp(`\\[\\[${escaped}(?:[|#][^\\]]*)?\\]\\]`, "g");
@@ -5219,7 +5313,7 @@ async function runArchive(input) {
5219
5313
  if (!relPath) return { exitCode: ExitCode.ARCHIVE_TARGET_NOT_FOUND, result: err("ARCHIVE_TARGET_NOT_FOUND", { page: input.page }) };
5220
5314
  if (relPath.startsWith("_archive/")) return { exitCode: ExitCode.ARCHIVE_ALREADY_ARCHIVED, result: err("ARCHIVE_ALREADY_ARCHIVED", { page: relPath }) };
5221
5315
  const slug = relPath.replace(/\.md$/, "").split("/").pop();
5222
- const archivePath = join27("_archive", relPath).replace(/\\/g, "/");
5316
+ const archivePath = join28("_archive", relPath).replace(/\\/g, "/");
5223
5317
  let cascade;
5224
5318
  if (input.cascade) {
5225
5319
  const wikilinkRefs = [];
@@ -5243,7 +5337,7 @@ async function runArchive(input) {
5243
5337
  const indexRefs = [];
5244
5338
  if (!isRaw) {
5245
5339
  try {
5246
- const idx = await readFile19(join27(input.vault, "index.md"), "utf8");
5340
+ const idx = await readFile20(join28(input.vault, "index.md"), "utf8");
5247
5341
  idx.split("\n").forEach((line, i) => {
5248
5342
  if (line.includes(`[[${slug}]]`)) indexRefs.push({ line: i + 1, text: line });
5249
5343
  });
@@ -5269,8 +5363,8 @@ async function runArchive(input) {
5269
5363
  }
5270
5364
  if (input.cascade && input.apply && cascade) {
5271
5365
  for (const ref of cascade.source_array_refs) {
5272
- const absPath = join27(input.vault, ref.page);
5273
- const text = await readFile19(absPath, "utf8");
5366
+ const absPath = join28(input.vault, ref.page);
5367
+ const text = await readFile20(absPath, "utf8");
5274
5368
  const split = splitFrontmatter(text);
5275
5369
  if (!split.ok) continue;
5276
5370
  const before = split.data.rawFrontmatter;
@@ -5287,12 +5381,12 @@ ${fmRewritten}
5287
5381
  }
5288
5382
  }
5289
5383
  }
5290
- await mkdir8(dirname9(join27(input.vault, archivePath)), { recursive: true });
5384
+ await mkdir9(dirname10(join28(input.vault, archivePath)), { recursive: true });
5291
5385
  let indexUpdated = false;
5292
5386
  if (!isRaw) {
5293
- const indexPath = join27(input.vault, "index.md");
5387
+ const indexPath = join28(input.vault, "index.md");
5294
5388
  try {
5295
- const idx = await readFile19(indexPath, "utf8");
5389
+ const idx = await readFile20(indexPath, "utf8");
5296
5390
  const originalLines = idx.split("\n");
5297
5391
  const filtered = originalLines.filter((l) => !l.includes(`[[${slug}]]`));
5298
5392
  if (filtered.length !== originalLines.length) {
@@ -5303,7 +5397,7 @@ ${fmRewritten}
5303
5397
  if (e instanceof Error && "code" in e && e.code !== "ENOENT") throw e;
5304
5398
  }
5305
5399
  }
5306
- await rename7(join27(input.vault, relPath), join27(input.vault, archivePath));
5400
+ await rename7(join28(input.vault, relPath), join28(input.vault, archivePath));
5307
5401
  appendLastOp(input.vault, {
5308
5402
  operation: input.cascade ? "archive-cascade" : "archive",
5309
5403
  summary: `moved ${relPath} to ${archivePath}${input.cascade ? ` (cascade: ${cascade?.source_array_refs.length ?? 0} source arrays updated)` : ""}`,
@@ -5690,14 +5784,14 @@ ${newBody}`;
5690
5784
  // src/commands/update.ts
5691
5785
  import { execSync as execSync3 } from "child_process";
5692
5786
  import { readFileSync as readFileSync8 } from "fs";
5693
- import { join as join28 } from "path";
5787
+ import { join as join29 } from "path";
5694
5788
  function resolveGlobalSkillsRoot() {
5695
5789
  try {
5696
5790
  const globalRoot = execSync3("npm root -g", {
5697
5791
  encoding: "utf8",
5698
5792
  timeout: 5e3
5699
5793
  }).trim();
5700
- return join28(globalRoot, "skillwiki", "skills");
5794
+ return join29(globalRoot, "skillwiki", "skills");
5701
5795
  } catch {
5702
5796
  return null;
5703
5797
  }
@@ -5723,7 +5817,7 @@ async function runUpdate(input) {
5723
5817
  );
5724
5818
  const currentVersion = pkg2.version;
5725
5819
  const tag = input.distTag ?? "latest";
5726
- const target = join28(input.home, ".claude", "skills");
5820
+ const target = join29(input.home, ".claude", "skills");
5727
5821
  let latest;
5728
5822
  try {
5729
5823
  latest = execSync3(`npm view skillwiki@${tag} version`, {
@@ -5793,16 +5887,16 @@ async function runUpdate(input) {
5793
5887
 
5794
5888
  // src/commands/self-update.ts
5795
5889
  import { execSync as execSync4 } from "child_process";
5796
- import { existsSync as existsSync9, readFileSync as readFileSync9 } from "fs";
5797
- import { join as join29 } from "path";
5890
+ import { existsSync as existsSync10, readFileSync as readFileSync9 } from "fs";
5891
+ import { join as join30 } from "path";
5798
5892
  var DEFAULT_SOURCE_ROOT_SUFFIX = "/Desktop/code/llm-wiki";
5799
5893
  async function runSelfUpdate(input) {
5800
5894
  const currentVersion = JSON.parse(
5801
5895
  readFileSync9(new URL("../../package.json", import.meta.url), "utf8")
5802
5896
  ).version;
5803
5897
  const sourceRoot = input.sourceRoot ?? `${input.home}${DEFAULT_SOURCE_ROOT_SUFFIX}`;
5804
- const localPkgPath = join29(sourceRoot, "packages", "cli", "package.json");
5805
- const hasLocalSource = existsSync9(localPkgPath);
5898
+ const localPkgPath = join30(sourceRoot, "packages", "cli", "package.json");
5899
+ const hasLocalSource = existsSync10(localPkgPath);
5806
5900
  if (input.check) {
5807
5901
  let availableVersion = null;
5808
5902
  let source;
@@ -5933,10 +6027,10 @@ async function runSelfUpdate(input) {
5933
6027
  }
5934
6028
 
5935
6029
  // src/commands/transcripts.ts
5936
- import { readdir as readdir5, stat as stat7, readFile as readFile20 } from "fs/promises";
5937
- import { join as join30 } from "path";
6030
+ import { readdir as readdir5, stat as stat7, readFile as readFile21 } from "fs/promises";
6031
+ import { join as join31 } from "path";
5938
6032
  async function runTranscripts(input) {
5939
- const dir = join30(input.vault, "raw", "transcripts");
6033
+ const dir = join31(input.vault, "raw", "transcripts");
5940
6034
  let entries;
5941
6035
  try {
5942
6036
  entries = await readdir5(dir, { withFileTypes: true });
@@ -5946,8 +6040,8 @@ async function runTranscripts(input) {
5946
6040
  const transcripts = [];
5947
6041
  for (const entry of entries) {
5948
6042
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
5949
- const filePath = join30(dir, entry.name);
5950
- const content = await readFile20(filePath, "utf8");
6043
+ const filePath = join31(dir, entry.name);
6044
+ const content = await readFile21(filePath, "utf8");
5951
6045
  const fm = extractFrontmatter(content);
5952
6046
  if (!fm.ok) continue;
5953
6047
  const ingested = typeof fm.data.ingested === "string" ? fm.data.ingested : "";
@@ -5964,12 +6058,12 @@ async function runTranscripts(input) {
5964
6058
  }
5965
6059
 
5966
6060
  // src/commands/project-index.ts
5967
- import { readdir as readdir6, readFile as readFile21, writeFile as writeFile11, mkdir as mkdir9 } from "fs/promises";
5968
- import { join as join31, dirname as dirname10 } from "path";
6061
+ import { readdir as readdir6, readFile as readFile22, writeFile as writeFile11, mkdir as mkdir10 } from "fs/promises";
6062
+ import { join as join32, dirname as dirname11 } from "path";
5969
6063
  var LAYER2_DIRS = ["entities", "concepts", "comparisons", "queries", "meta"];
5970
6064
  async function runProjectIndex(input) {
5971
6065
  const slug = input.slug;
5972
- const projectDir = join31(input.vault, "projects", slug);
6066
+ const projectDir = join32(input.vault, "projects", slug);
5973
6067
  try {
5974
6068
  await readdir6(projectDir);
5975
6069
  } catch {
@@ -5980,15 +6074,15 @@ async function runProjectIndex(input) {
5980
6074
  }
5981
6075
  const wikilinkPattern = `[[${slug}]]`;
5982
6076
  const entries = [];
5983
- const compoundDir = join31(input.vault, "projects", slug, "compound");
6077
+ const compoundDir = join32(input.vault, "projects", slug, "compound");
5984
6078
  try {
5985
6079
  const compoundFiles = await readdir6(compoundDir, { withFileTypes: true });
5986
6080
  for (const entry of compoundFiles) {
5987
6081
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
5988
- const filePath = join31(compoundDir, entry.name);
6082
+ const filePath = join32(compoundDir, entry.name);
5989
6083
  let text;
5990
6084
  try {
5991
- text = await readFile21(filePath, "utf8");
6085
+ text = await readFile22(filePath, "utf8");
5992
6086
  } catch {
5993
6087
  continue;
5994
6088
  }
@@ -6005,16 +6099,16 @@ async function runProjectIndex(input) {
6005
6099
  for (const dir of LAYER2_DIRS) {
6006
6100
  let files;
6007
6101
  try {
6008
- files = await readdir6(join31(input.vault, dir), { withFileTypes: true });
6102
+ files = await readdir6(join32(input.vault, dir), { withFileTypes: true });
6009
6103
  } catch {
6010
6104
  continue;
6011
6105
  }
6012
6106
  for (const entry of files) {
6013
6107
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
6014
- const filePath = join31(input.vault, dir, entry.name);
6108
+ const filePath = join32(input.vault, dir, entry.name);
6015
6109
  let text;
6016
6110
  try {
6017
- text = await readFile21(filePath, "utf8");
6111
+ text = await readFile22(filePath, "utf8");
6018
6112
  } catch {
6019
6113
  continue;
6020
6114
  }
@@ -6035,11 +6129,11 @@ async function runProjectIndex(input) {
6035
6129
  const tb = typeOrder[b.type] ?? 99;
6036
6130
  return ta !== tb ? ta - tb : a.title.localeCompare(b.title);
6037
6131
  });
6038
- const indexPath = join31(projectDir, "knowledge.md");
6132
+ const indexPath = join32(projectDir, "knowledge.md");
6039
6133
  let existing = false;
6040
6134
  let stale = false;
6041
6135
  try {
6042
- const existingText = await readFile21(indexPath, "utf8");
6136
+ const existingText = await readFile22(indexPath, "utf8");
6043
6137
  existing = true;
6044
6138
  const existingEntries = existingText.split("\n").filter((l) => l.startsWith("- [["));
6045
6139
  const existingPages = new Set(existingEntries.map((l) => {
@@ -6079,7 +6173,7 @@ Autogenerated by \`skillwiki project-index\` on ${today}.
6079
6173
  }
6080
6174
  if (input.apply) {
6081
6175
  try {
6082
- await mkdir9(dirname10(indexPath), { recursive: true });
6176
+ await mkdir10(dirname11(indexPath), { recursive: true });
6083
6177
  await writeFile11(indexPath, body, "utf8");
6084
6178
  } catch (e) {
6085
6179
  return {
@@ -6108,10 +6202,10 @@ ${entries.map((e) => ` ${e.type}: [[${e.page.replace(/\.md$/, "")}]] \u2014 ${e
6108
6202
  }
6109
6203
 
6110
6204
  // src/commands/compound.ts
6111
- import { writeFile as writeFile12, mkdir as mkdir10, readdir as readdir7, unlink as unlink3 } from "fs/promises";
6112
- import { join as join32 } from "path";
6113
- import { existsSync as existsSync10 } from "fs";
6114
- import { readFile as readFile22 } from "fs/promises";
6205
+ import { writeFile as writeFile12, mkdir as mkdir11, readdir as readdir7, unlink as unlink4 } from "fs/promises";
6206
+ import { join as join33 } from "path";
6207
+ import { existsSync as existsSync11 } from "fs";
6208
+ import { readFile as readFile23 } from "fs/promises";
6115
6209
  var RETRO_HEADING_RE = /^## \[(\d{4}-\d{2}-\d{2})(?:\s+[^\]]+)?\] retro \| loop cycle(?: (\d+))?: (.+)$/;
6116
6210
  var FIELD_RE = {
6117
6211
  improve: /^-\s+\*?\*?Improve:?\*?\*?\s*(.+)$/m,
@@ -6209,17 +6303,17 @@ function extractRetroFields(date, cycleName, block) {
6209
6303
  };
6210
6304
  }
6211
6305
  async function runCompound(input) {
6212
- const logPath = join32(input.vault, "log.md");
6306
+ const logPath = join33(input.vault, "log.md");
6213
6307
  let logText;
6214
6308
  try {
6215
- logText = await readFile22(logPath, "utf8");
6309
+ logText = await readFile23(logPath, "utf8");
6216
6310
  } catch {
6217
6311
  return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path: logPath }) };
6218
6312
  }
6219
6313
  const entries = parseRetroEntries(logText);
6220
6314
  const promoted = [];
6221
6315
  const skipped = [];
6222
- const compoundDir = join32(input.vault, "projects", input.project, "compound");
6316
+ const compoundDir = join33(input.vault, "projects", input.project, "compound");
6223
6317
  for (const entry of entries) {
6224
6318
  const generalizeValue = entry.generalize.trim();
6225
6319
  if (!/^yes/i.test(generalizeValue)) {
@@ -6227,8 +6321,8 @@ async function runCompound(input) {
6227
6321
  continue;
6228
6322
  }
6229
6323
  const slug = slugify(entry.cycleName);
6230
- const compoundPath = join32(compoundDir, `${slug}.md`);
6231
- if (existsSync10(compoundPath)) {
6324
+ const compoundPath = join33(compoundDir, `${slug}.md`);
6325
+ if (existsSync11(compoundPath)) {
6232
6326
  skipped.push(entry.date);
6233
6327
  continue;
6234
6328
  }
@@ -6266,8 +6360,8 @@ async function runCompound(input) {
6266
6360
  ].join("\n");
6267
6361
  const content = frontmatter + "\n" + body;
6268
6362
  if (!input.dryRun) {
6269
- if (!existsSync10(compoundDir)) {
6270
- await mkdir10(compoundDir, { recursive: true });
6363
+ if (!existsSync11(compoundDir)) {
6364
+ await mkdir11(compoundDir, { recursive: true });
6271
6365
  }
6272
6366
  await writeFile12(compoundPath, content, "utf8");
6273
6367
  }
@@ -6288,23 +6382,23 @@ async function runCompound(input) {
6288
6382
  };
6289
6383
  }
6290
6384
  async function runCompoundDelete(input) {
6291
- const projectDir = join32(input.vault, "projects", input.project);
6292
- if (!existsSync10(projectDir)) {
6385
+ const projectDir = join33(input.vault, "projects", input.project);
6386
+ if (!existsSync11(projectDir)) {
6293
6387
  return {
6294
6388
  exitCode: ExitCode.PROJECT_NOT_FOUND,
6295
6389
  result: err("PROJECT_NOT_FOUND", { slug: input.project, path: projectDir })
6296
6390
  };
6297
6391
  }
6298
6392
  const entryName = input.entry.replace(/\.md$/, "");
6299
- const compoundPath = join32(projectDir, "compound", `${entryName}.md`);
6300
- if (!existsSync10(compoundPath)) {
6393
+ const compoundPath = join33(projectDir, "compound", `${entryName}.md`);
6394
+ if (!existsSync11(compoundPath)) {
6301
6395
  return {
6302
6396
  exitCode: ExitCode.FILE_NOT_FOUND,
6303
6397
  result: err("FILE_NOT_FOUND", { path: compoundPath })
6304
6398
  };
6305
6399
  }
6306
6400
  try {
6307
- await unlink3(compoundPath);
6401
+ await unlink4(compoundPath);
6308
6402
  } catch (e) {
6309
6403
  return {
6310
6404
  exitCode: ExitCode.WRITE_FAILED,
@@ -6330,8 +6424,8 @@ knowledge.md regenerated`
6330
6424
  };
6331
6425
  }
6332
6426
  async function runCompoundList(input) {
6333
- const compoundDir = join32(input.vault, "projects", input.project, "compound");
6334
- if (!existsSync10(compoundDir)) {
6427
+ const compoundDir = join33(input.vault, "projects", input.project, "compound");
6428
+ if (!existsSync11(compoundDir)) {
6335
6429
  return {
6336
6430
  exitCode: ExitCode.OK,
6337
6431
  result: ok({
@@ -6361,10 +6455,10 @@ could not read compound directory`
6361
6455
  const entries = [];
6362
6456
  for (const dirent of dirents) {
6363
6457
  if (!dirent.isFile() || !dirent.name.endsWith(".md")) continue;
6364
- const filePath = join32(compoundDir, dirent.name);
6458
+ const filePath = join33(compoundDir, dirent.name);
6365
6459
  let text;
6366
6460
  try {
6367
- text = await readFile22(filePath, "utf8");
6461
+ text = await readFile23(filePath, "utf8");
6368
6462
  } catch {
6369
6463
  continue;
6370
6464
  }
@@ -6393,9 +6487,9 @@ no compound entries found`;
6393
6487
  }
6394
6488
 
6395
6489
  // src/commands/observe.ts
6396
- import { mkdir as mkdir11, writeFile as writeFile13 } from "fs/promises";
6397
- import { existsSync as existsSync11, statSync as statSync4 } from "fs";
6398
- import { join as join33 } from "path";
6490
+ import { mkdir as mkdir12, writeFile as writeFile13 } from "fs/promises";
6491
+ import { existsSync as existsSync12, statSync as statSync4 } from "fs";
6492
+ import { join as join34 } from "path";
6399
6493
  import { createHash as createHash4 } from "crypto";
6400
6494
  var ALLOWED_KINDS = /* @__PURE__ */ new Set(["note", "bug", "task", "idea", "session-log"]);
6401
6495
  function slugify2(text) {
@@ -6418,15 +6512,15 @@ async function runObserve(input) {
6418
6512
  result: err("SCHEME_REJECTED", { message: "Text must not be empty" })
6419
6513
  };
6420
6514
  }
6421
- if (!existsSync11(input.vault) || !statSync4(input.vault).isDirectory()) {
6515
+ if (!existsSync12(input.vault) || !statSync4(input.vault).isDirectory()) {
6422
6516
  return {
6423
6517
  exitCode: ExitCode.VAULT_PATH_INVALID,
6424
6518
  result: err("VAULT_PATH_INVALID", { path: input.vault })
6425
6519
  };
6426
6520
  }
6427
- const transcriptsDir = join33(input.vault, "raw", "transcripts");
6521
+ const transcriptsDir = join34(input.vault, "raw", "transcripts");
6428
6522
  try {
6429
- await mkdir11(transcriptsDir, { recursive: true });
6523
+ await mkdir12(transcriptsDir, { recursive: true });
6430
6524
  } catch {
6431
6525
  return {
6432
6526
  exitCode: ExitCode.VAULT_PATH_INVALID,
@@ -6436,7 +6530,7 @@ async function runObserve(input) {
6436
6530
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
6437
6531
  const slug = slugify2(input.text);
6438
6532
  const fileName = `${today}-observation-${slug}.md`;
6439
- const filePath = join33(transcriptsDir, fileName);
6533
+ const filePath = join34(transcriptsDir, fileName);
6440
6534
  const body = `
6441
6535
  ${input.text.trim()}
6442
6536
  `;
@@ -6476,8 +6570,8 @@ ${input.text.trim()}
6476
6570
  }
6477
6571
 
6478
6572
  // src/commands/ingest.ts
6479
- import { readFile as readFile23, writeFile as writeFile14, mkdir as mkdir12 } from "fs/promises";
6480
- import { join as join34 } from "path";
6573
+ import { readFile as readFile24, writeFile as writeFile14, mkdir as mkdir13 } from "fs/promises";
6574
+ import { join as join35 } from "path";
6481
6575
  import { createHash as createHash5 } from "crypto";
6482
6576
  var ALLOWED_TYPES = /* @__PURE__ */ new Set(["entity", "concept", "comparison", "query"]);
6483
6577
  var TYPE_DIR = {
@@ -6636,7 +6730,7 @@ async function runIngest(input) {
6636
6730
  sourceContent = fetchResult.data.body;
6637
6731
  } else {
6638
6732
  try {
6639
- sourceContent = await readFile23(input.source, "utf8");
6733
+ sourceContent = await readFile24(input.source, "utf8");
6640
6734
  } catch {
6641
6735
  return {
6642
6736
  exitCode: ExitCode.FILE_NOT_FOUND,
@@ -6651,8 +6745,8 @@ async function runIngest(input) {
6651
6745
  const rawRelPath = `raw/articles/${slug}.md`;
6652
6746
  const typedDir = TYPE_DIR[input.type] ?? `${input.type}s`;
6653
6747
  const typedRelPath = `${typedDir}/${slug}.md`;
6654
- const rawAbsPath = join34(input.vault, rawRelPath);
6655
- const typedAbsPath = join34(input.vault, typedRelPath);
6748
+ const rawAbsPath = join35(input.vault, rawRelPath);
6749
+ const typedAbsPath = join35(input.vault, typedRelPath);
6656
6750
  const rawContent = buildRawContent(sourceUrl, today, sha256, sourceContent);
6657
6751
  const typedContent = buildTypedContent(
6658
6752
  input.title,
@@ -6715,7 +6809,7 @@ async function runIngest(input) {
6715
6809
  };
6716
6810
  }
6717
6811
  try {
6718
- await mkdir12(join34(input.vault, "raw", "articles"), { recursive: true });
6812
+ await mkdir13(join35(input.vault, "raw", "articles"), { recursive: true });
6719
6813
  await writeFile14(rawAbsPath, rawContent, "utf8");
6720
6814
  } catch (e) {
6721
6815
  return {
@@ -6724,7 +6818,7 @@ async function runIngest(input) {
6724
6818
  };
6725
6819
  }
6726
6820
  try {
6727
- await mkdir12(join34(input.vault, typedDir), { recursive: true });
6821
+ await mkdir13(join35(input.vault, typedDir), { recursive: true });
6728
6822
  await writeFile14(typedAbsPath, typedContent, "utf8");
6729
6823
  } catch (e) {
6730
6824
  return {
@@ -6903,12 +6997,12 @@ ${body}`;
6903
6997
  }
6904
6998
 
6905
6999
  // src/commands/sync.ts
6906
- import { existsSync as existsSync13 } from "fs";
6907
- import { join as join36 } from "path";
7000
+ import { existsSync as existsSync14 } from "fs";
7001
+ import { join as join37 } from "path";
6908
7002
 
6909
7003
  // src/utils/sync-lock.ts
6910
- import { existsSync as existsSync12, mkdirSync as mkdirSync4, readFileSync as readFileSync10, renameSync, unlinkSync as unlinkSync5, writeFileSync as writeFileSync6 } from "fs";
6911
- import { join as join35 } from "path";
7004
+ import { existsSync as existsSync13, mkdirSync as mkdirSync4, readFileSync as readFileSync10, renameSync, unlinkSync as unlinkSync5, writeFileSync as writeFileSync6 } from "fs";
7005
+ import { join as join36 } from "path";
6912
7006
  import { createHash as createHash6 } from "crypto";
6913
7007
  function getSessionId() {
6914
7008
  if (process.env.CLAUDE_SESSION_ID) return process.env.CLAUDE_SESSION_ID;
@@ -6916,11 +7010,11 @@ function getSessionId() {
6916
7010
  return process.pid.toString();
6917
7011
  }
6918
7012
  function lockPath(vault) {
6919
- return join35(vault, ".skillwiki", "sync.lock");
7013
+ return join36(vault, ".skillwiki", "sync.lock");
6920
7014
  }
6921
7015
  function readLock(vault) {
6922
7016
  const path = lockPath(vault);
6923
- if (!existsSync12(path)) return null;
7017
+ if (!existsSync13(path)) return null;
6924
7018
  try {
6925
7019
  const raw = readFileSync10(path, "utf8");
6926
7020
  return JSON.parse(raw);
@@ -6935,8 +7029,8 @@ function isStale(lock, now) {
6935
7029
  }
6936
7030
  function acquireLock(vault, opts = {}) {
6937
7031
  const path = lockPath(vault);
6938
- const dir = join35(vault, ".skillwiki");
6939
- if (!existsSync12(dir)) {
7032
+ const dir = join36(vault, ".skillwiki");
7033
+ if (!existsSync13(dir)) {
6940
7034
  mkdirSync4(dir, { recursive: true });
6941
7035
  }
6942
7036
  const sessionId = opts.sessionId ?? getSessionId();
@@ -6981,7 +7075,7 @@ function writeLockedFile(path, lock) {
6981
7075
  }
6982
7076
  function releaseLock(vault, opts = {}) {
6983
7077
  const path = lockPath(vault);
6984
- if (!existsSync12(path)) {
7078
+ if (!existsSync13(path)) {
6985
7079
  return { released: false };
6986
7080
  }
6987
7081
  const sessionId = opts.sessionId ?? getSessionId();
@@ -7010,7 +7104,7 @@ function releaseLock(vault, opts = {}) {
7010
7104
  function runSyncStatus(input) {
7011
7105
  const vault = input.vault;
7012
7106
  const includeStashes = input.includeStashes ?? false;
7013
- if (!existsSync13(join36(vault, ".git"))) {
7107
+ if (!existsSync14(join37(vault, ".git"))) {
7014
7108
  return {
7015
7109
  exitCode: ExitCode.VAULT_PATH_INVALID,
7016
7110
  result: ok({
@@ -7087,12 +7181,23 @@ function runSyncStatus(input) {
7087
7181
  }
7088
7182
  async function runSyncPush(input) {
7089
7183
  const vault = input.vault;
7090
- if (!existsSync13(join36(vault, ".git"))) {
7184
+ if (!existsSync14(join37(vault, ".git"))) {
7091
7185
  return {
7092
7186
  exitCode: ExitCode.VAULT_PATH_INVALID,
7093
7187
  result: err("NOT_A_GIT_REPO", { path: vault })
7094
7188
  };
7095
7189
  }
7190
+ let pathFixes = 0;
7191
+ const pathFix = await fixPathTooLong({ vault });
7192
+ if (pathFix.result.ok && pathFix.result.data.fixed.length > 0) {
7193
+ pathFixes = pathFix.result.data.fixed.length;
7194
+ appendLastOp(vault, {
7195
+ operation: "lint-fix",
7196
+ summary: `fixed ${pathFixes} long path(s)`,
7197
+ files: pathFix.result.data.fixed.flatMap((f) => [f.from, f.to]),
7198
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
7199
+ });
7200
+ }
7096
7201
  const porcelain = git(vault, ["status", "--porcelain"]);
7097
7202
  const dirtyFiles = porcelain ? porcelain.split("\n").filter((l) => l.trim().length > 0) : [];
7098
7203
  if (dirtyFiles.length === 0) {
@@ -7102,6 +7207,7 @@ async function runSyncPush(input) {
7102
7207
  files_committed: 0,
7103
7208
  commit_message: "",
7104
7209
  pushed: false,
7210
+ path_fixes: pathFixes,
7105
7211
  humanHint: "nothing to commit, working tree clean"
7106
7212
  })
7107
7213
  };
@@ -7156,7 +7262,8 @@ async function runSyncPush(input) {
7156
7262
  files_committed: dirtyFiles.length,
7157
7263
  commit_message: commitMessage,
7158
7264
  pushed: false,
7159
- humanHint: `committed ${dirtyFiles.length} file(s) but push failed: ${String(e)}`
7265
+ path_fixes: pathFixes,
7266
+ humanHint: `committed ${dirtyFiles.length} file(s)${pathFixes > 0 ? ` after ${pathFixes} long-path fix(es)` : ""} but push failed: ${String(e)}`
7160
7267
  })
7161
7268
  };
7162
7269
  }
@@ -7166,7 +7273,8 @@ async function runSyncPush(input) {
7166
7273
  files_committed: dirtyFiles.length,
7167
7274
  commit_message: commitMessage,
7168
7275
  pushed,
7169
- humanHint: `committed and pushed ${dirtyFiles.length} file(s)`
7276
+ path_fixes: pathFixes,
7277
+ humanHint: `committed and pushed ${dirtyFiles.length} file(s)${pathFixes > 0 ? ` after ${pathFixes} long-path fix(es)` : ""}`
7170
7278
  })
7171
7279
  };
7172
7280
  }
@@ -7191,7 +7299,7 @@ function enumerateStashes(vault) {
7191
7299
  }
7192
7300
  async function runSyncPull(input) {
7193
7301
  const vault = input.vault;
7194
- if (!existsSync13(join36(vault, ".git"))) {
7302
+ if (!existsSync14(join37(vault, ".git"))) {
7195
7303
  return {
7196
7304
  exitCode: ExitCode.VAULT_PATH_INVALID,
7197
7305
  result: err("NOT_A_GIT_REPO", { path: vault })
@@ -7285,6 +7393,8 @@ async function runSyncPull(input) {
7285
7393
  };
7286
7394
  }
7287
7395
  }
7396
+ const pathFix = await fixPathTooLong({ vault });
7397
+ const pathFixCount = pathFix.result.ok ? pathFix.result.data.fixed.length : 0;
7288
7398
  let lintErrors = 0;
7289
7399
  let lintWarnings = 0;
7290
7400
  const lintResult = await runLint({ vault, days: 90, lines: 200, logThreshold: 500 });
@@ -7296,6 +7406,7 @@ async function runSyncPull(input) {
7296
7406
  if (filesUpdated > 0) hintParts.push(`updated ${filesUpdated} file(s)`);
7297
7407
  else hintParts.push("already up to date");
7298
7408
  if (autoResolved > 0) hintParts.push(`${autoResolved} conflict(s) auto-resolved`);
7409
+ if (pathFixCount > 0) hintParts.push(`${pathFixCount} long path(s) fixed`);
7299
7410
  if (lintErrors > 0) hintParts.push(`${lintErrors} lint error(s)`);
7300
7411
  if (lintWarnings > 0) hintParts.push(`${lintWarnings} lint warning(s)`);
7301
7412
  const exitCode = lintErrors > 0 ? ExitCode.LINT_HAS_ERRORS : lintWarnings > 0 ? ExitCode.LINT_HAS_WARNINGS : ExitCode.OK;
@@ -7359,7 +7470,7 @@ function runSyncPeers(input) {
7359
7470
  }
7360
7471
  function runSyncLock(input) {
7361
7472
  const vault = input.vault;
7362
- if (!existsSync13(vault)) {
7473
+ if (!existsSync14(vault)) {
7363
7474
  return {
7364
7475
  exitCode: ExitCode.VAULT_PATH_INVALID,
7365
7476
  result: err("VAULT_PATH_INVALID", { path: vault })
@@ -7394,7 +7505,7 @@ function runSyncLock(input) {
7394
7505
  }
7395
7506
  function runSyncUnlock(input) {
7396
7507
  const vault = input.vault;
7397
- if (!existsSync13(vault)) {
7508
+ if (!existsSync14(vault)) {
7398
7509
  return {
7399
7510
  exitCode: ExitCode.VAULT_PATH_INVALID,
7400
7511
  result: err("VAULT_PATH_INVALID", { path: vault })
@@ -7428,7 +7539,7 @@ function runSyncUnlock(input) {
7428
7539
 
7429
7540
  // src/commands/backup.ts
7430
7541
  import { statSync as statSync5, readdirSync as readdirSync2, readFileSync as readFileSync11, mkdirSync as mkdirSync5, writeFileSync as writeFileSync7 } from "fs";
7431
- import { join as join37, relative as relative3, dirname as dirname11 } from "path";
7542
+ import { join as join38, relative as relative3, dirname as dirname12 } from "path";
7432
7543
  import { PutObjectCommand, HeadObjectCommand, ListObjectsV2Command, GetObjectCommand, DeleteObjectsCommand } from "@aws-sdk/client-s3";
7433
7544
 
7434
7545
  // src/utils/s3-client.ts
@@ -7448,11 +7559,11 @@ function createS3Client(config) {
7448
7559
  }
7449
7560
 
7450
7561
  // src/commands/backup.ts
7451
- var SKIP_DIRS = /* @__PURE__ */ new Set([".git", ".obsidian", "_archive", "node_modules", ".skillwiki"]);
7562
+ var SKIP_DIRS2 = /* @__PURE__ */ new Set([".git", ".obsidian", "_archive", "node_modules", ".skillwiki"]);
7452
7563
  function* walkMarkdown(dir, base) {
7453
7564
  for (const entry of readdirSync2(dir, { withFileTypes: true })) {
7454
- if (SKIP_DIRS.has(entry.name)) continue;
7455
- const full = join37(dir, entry.name);
7565
+ if (SKIP_DIRS2.has(entry.name)) continue;
7566
+ const full = join38(dir, entry.name);
7456
7567
  if (entry.isDirectory()) {
7457
7568
  yield* walkMarkdown(full, base);
7458
7569
  } else if (entry.name.endsWith(".md")) {
@@ -7475,7 +7586,7 @@ async function runBackupSync(input) {
7475
7586
  let failed = 0;
7476
7587
  const files = [...walkMarkdown(input.vault, input.vault)];
7477
7588
  for (const relPath of files) {
7478
- const absPath = join37(input.vault, relPath);
7589
+ const absPath = join38(input.vault, relPath);
7479
7590
  const localStat = statSync5(absPath);
7480
7591
  let needsUpload = true;
7481
7592
  try {
@@ -7551,7 +7662,7 @@ async function runBackupRestore(input) {
7551
7662
  const objects = list.Contents ?? [];
7552
7663
  for (const obj of objects) {
7553
7664
  if (!obj.Key) continue;
7554
- const localPath = join37(target, obj.Key);
7665
+ const localPath = join38(target, obj.Key);
7555
7666
  try {
7556
7667
  const localStat = statSync5(localPath);
7557
7668
  if (obj.LastModified && localStat.mtime > obj.LastModified) {
@@ -7564,7 +7675,7 @@ async function runBackupRestore(input) {
7564
7675
  const resp = await client.send(new GetObjectCommand({ Bucket: input.bucket, Key: obj.Key }));
7565
7676
  const body = await resp.Body?.transformToByteArray();
7566
7677
  if (body) {
7567
- mkdirSync5(dirname11(localPath), { recursive: true });
7678
+ mkdirSync5(dirname12(localPath), { recursive: true });
7568
7679
  writeFileSync7(localPath, Buffer.from(body));
7569
7680
  downloaded++;
7570
7681
  }
@@ -7597,11 +7708,11 @@ async function runBackupRestore(input) {
7597
7708
  }
7598
7709
 
7599
7710
  // src/commands/status.ts
7600
- import { existsSync as existsSync14, statSync as statSync6 } from "fs";
7601
- import { readFile as readFile24 } from "fs/promises";
7602
- import { join as join38 } from "path";
7711
+ import { existsSync as existsSync15, statSync as statSync6 } from "fs";
7712
+ import { readFile as readFile25 } from "fs/promises";
7713
+ import { join as join39 } from "path";
7603
7714
  async function runStatus(input) {
7604
- if (!existsSync14(input.vault)) {
7715
+ if (!existsSync15(input.vault)) {
7605
7716
  return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { vault: input.vault }) };
7606
7717
  }
7607
7718
  const scan = await scanVault(input.vault);
@@ -7626,7 +7737,7 @@ async function runStatus(input) {
7626
7737
  const compound = scan.data.compound.length;
7627
7738
  let schemaVersion = "v1";
7628
7739
  try {
7629
- const schemaContent = await readFile24(join38(input.vault, "SCHEMA.md"), "utf8");
7740
+ const schemaContent = await readFile25(join39(input.vault, "SCHEMA.md"), "utf8");
7630
7741
  const versionMatch = schemaContent.match(/version:\s*["']?([^"'\s\n]+)/i);
7631
7742
  if (versionMatch) schemaVersion = versionMatch[1];
7632
7743
  } catch {
@@ -7686,8 +7797,8 @@ async function runStatus(input) {
7686
7797
  }
7687
7798
 
7688
7799
  // src/commands/seed.ts
7689
- import { mkdir as mkdir13, writeFile as writeFile15, stat as stat8 } from "fs/promises";
7690
- import { join as join39 } from "path";
7800
+ import { mkdir as mkdir14, writeFile as writeFile15, stat as stat8 } from "fs/promises";
7801
+ import { join as join40 } from "path";
7691
7802
  var TODAY = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
7692
7803
  var EXAMPLE_PAGES = {
7693
7804
  "entities/example-project.md": `---
@@ -7756,29 +7867,29 @@ Real sources are immutable after ingestion \u2014 never edit them.
7756
7867
  `;
7757
7868
  async function runSeed(input) {
7758
7869
  try {
7759
- await stat8(join39(input.vault, "SCHEMA.md"));
7870
+ await stat8(join40(input.vault, "SCHEMA.md"));
7760
7871
  } catch {
7761
7872
  return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { root: input.vault, reason: "SCHEMA.md missing \u2014 run `skillwiki init` first" }) };
7762
7873
  }
7763
7874
  const created = [];
7764
7875
  const skipped = [];
7765
7876
  for (const [relPath, content] of Object.entries(EXAMPLE_PAGES)) {
7766
- const absPath = join39(input.vault, relPath);
7877
+ const absPath = join40(input.vault, relPath);
7767
7878
  try {
7768
7879
  await stat8(absPath);
7769
7880
  skipped.push(relPath);
7770
7881
  } catch {
7771
- await mkdir13(join39(absPath, ".."), { recursive: true });
7882
+ await mkdir14(join40(absPath, ".."), { recursive: true });
7772
7883
  await writeFile15(absPath, content, "utf8");
7773
7884
  created.push(relPath);
7774
7885
  }
7775
7886
  }
7776
- const rawPath = join39(input.vault, "raw", "articles", "example-source.md");
7887
+ const rawPath = join40(input.vault, "raw", "articles", "example-source.md");
7777
7888
  try {
7778
7889
  await stat8(rawPath);
7779
7890
  skipped.push("raw/articles/example-source.md");
7780
7891
  } catch {
7781
- await mkdir13(join39(rawPath, ".."), { recursive: true });
7892
+ await mkdir14(join40(rawPath, ".."), { recursive: true });
7782
7893
  await writeFile15(rawPath, EXAMPLE_RAW, "utf8");
7783
7894
  created.push("raw/articles/example-source.md");
7784
7895
  }
@@ -7801,9 +7912,9 @@ async function runSeed(input) {
7801
7912
  }
7802
7913
 
7803
7914
  // src/commands/canvas.ts
7804
- import { readFile as readFile25, writeFile as writeFile16 } from "fs/promises";
7805
- import { existsSync as existsSync15 } from "fs";
7806
- import { join as join40 } from "path";
7915
+ import { readFile as readFile26, writeFile as writeFile16 } from "fs/promises";
7916
+ import { existsSync as existsSync16 } from "fs";
7917
+ import { join as join41 } from "path";
7807
7918
  var NODE_WIDTH = 240;
7808
7919
  var NODE_HEIGHT = 60;
7809
7920
  var COLUMN_SPACING = 400;
@@ -7881,8 +7992,8 @@ function buildCanvasEdges(adjacency) {
7881
7992
  return edges;
7882
7993
  }
7883
7994
  async function runCanvasGenerate(input) {
7884
- const graphPath = input.graphPath ?? join40(input.vault, ".skillwiki", "graph.json");
7885
- if (!existsSync15(graphPath)) {
7995
+ const graphPath = input.graphPath ?? join41(input.vault, ".skillwiki", "graph.json");
7996
+ if (!existsSync16(graphPath)) {
7886
7997
  return {
7887
7998
  exitCode: ExitCode.FILE_NOT_FOUND,
7888
7999
  result: err("FILE_NOT_FOUND", {
@@ -7893,7 +8004,7 @@ async function runCanvasGenerate(input) {
7893
8004
  }
7894
8005
  let raw;
7895
8006
  try {
7896
- raw = await readFile25(graphPath, "utf8");
8007
+ raw = await readFile26(graphPath, "utf8");
7897
8008
  } catch (e) {
7898
8009
  return {
7899
8010
  exitCode: ExitCode.FILE_NOT_FOUND,
@@ -7919,7 +8030,7 @@ async function runCanvasGenerate(input) {
7919
8030
  const nodes = buildCanvasNodes(paths);
7920
8031
  const edges = buildCanvasEdges(graph.adjacency);
7921
8032
  const canvas = { nodes, edges };
7922
- const outPath = join40(input.vault, "vault-graph.canvas");
8033
+ const outPath = join41(input.vault, "vault-graph.canvas");
7923
8034
  try {
7924
8035
  await writeFile16(outPath, JSON.stringify(canvas, null, 2));
7925
8036
  } catch (e) {
@@ -7941,8 +8052,8 @@ written: ${outPath}`
7941
8052
  }
7942
8053
 
7943
8054
  // src/commands/query.ts
7944
- import { readFile as readFile26, stat as stat9 } from "fs/promises";
7945
- import { join as join41 } from "path";
8055
+ import { readFile as readFile27, stat as stat9 } from "fs/promises";
8056
+ import { join as join42 } from "path";
7946
8057
  var W_KEYWORD = 2;
7947
8058
  var W_SOURCE_OVERLAP = 4;
7948
8059
  var W_WIKILINK = 3;
@@ -8063,7 +8174,7 @@ function computeKeywordScore(terms, title, tags, body) {
8063
8174
  return score;
8064
8175
  }
8065
8176
  async function loadOrBuildGraph(vault) {
8066
- const graphPath = join41(vault, ".skillwiki", "graph.json");
8177
+ const graphPath = join42(vault, ".skillwiki", "graph.json");
8067
8178
  let needsBuild = false;
8068
8179
  try {
8069
8180
  const fileStat = await stat9(graphPath);
@@ -8077,7 +8188,7 @@ async function loadOrBuildGraph(vault) {
8077
8188
  if (buildResult.exitCode !== 0) return null;
8078
8189
  }
8079
8190
  try {
8080
- const raw = await readFile26(graphPath, "utf8");
8191
+ const raw = await readFile27(graphPath, "utf8");
8081
8192
  return JSON.parse(raw);
8082
8193
  } catch {
8083
8194
  return null;
@@ -8085,14 +8196,14 @@ async function loadOrBuildGraph(vault) {
8085
8196
  }
8086
8197
 
8087
8198
  // src/utils/auto-commit.ts
8088
- import { existsSync as existsSync16 } from "fs";
8089
- import { join as join42 } from "path";
8199
+ import { existsSync as existsSync17 } from "fs";
8200
+ import { join as join43 } from "path";
8090
8201
  async function postCommit(vault, exitCode) {
8091
8202
  if (exitCode !== 0) return;
8092
8203
  const home = process.env.HOME ?? "";
8093
8204
  const dotenv = await parseDotenvFile(configPath(home));
8094
8205
  if (dotenv["AUTO_COMMIT"] === "false") return;
8095
- if (!existsSync16(join42(vault, ".git"))) return;
8206
+ if (!existsSync17(join43(vault, ".git"))) return;
8096
8207
  const lastOps = readLastOp(vault);
8097
8208
  if (lastOps.length === 0) return;
8098
8209
  const porcelain = git(vault, ["status", "--porcelain"]);
@@ -8143,7 +8254,7 @@ program.command("validate <file>").description("validate vault page frontmatter
8143
8254
  emit(await runValidate({ file, apply: !!opts.apply, vault }), vault);
8144
8255
  });
8145
8256
  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) => {
8146
- const out = opts.out ?? join43(vault, ".skillwiki", "graph.json");
8257
+ const out = opts.out ?? join44(vault, ".skillwiki", "graph.json");
8147
8258
  emit(await runGraphBuild({ vault, out }), vault);
8148
8259
  });
8149
8260
  var canvasCmd = program.command("canvas").description("manage Obsidian canvas files");