skillwiki 0.5.4 → 0.5.5

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 readFileSync10 } from "fs";
12
- import { join as join39 } from "path";
12
+ import { join as join40 } from "path";
13
13
  import { Command as Command2 } from "commander";
14
14
 
15
15
  // ../shared/src/exit-codes.ts
@@ -3439,10 +3439,9 @@ async function runConfigPath(input) {
3439
3439
  }
3440
3440
 
3441
3441
  // src/commands/doctor.ts
3442
- import { existsSync as existsSync6, lstatSync, readlinkSync, readdirSync, readFileSync as readFileSync6, statSync as statSync2 } from "fs";
3443
- import { join as join23, resolve as resolve4 } from "path";
3444
- import { execSync } from "child_process";
3445
- import { platform } from "os";
3442
+ import { existsSync as existsSync7, lstatSync, readlinkSync, readdirSync, statSync as statSync2 } from "fs";
3443
+ import { join as join24, resolve as resolve4 } from "path";
3444
+ import { execSync as execSync2 } from "child_process";
3446
3445
 
3447
3446
  // src/utils/auto-update.ts
3448
3447
  import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync5, mkdirSync as mkdirSync2 } from "fs";
@@ -3526,6 +3525,208 @@ function findPlugin(home, key = PLUGIN_KEY) {
3526
3525
  return entries[0];
3527
3526
  }
3528
3527
 
3528
+ // src/utils/s3-mount-health.ts
3529
+ import { execSync } from "child_process";
3530
+ import { platform } from "os";
3531
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, unlinkSync as unlinkSync3, readFileSync as readFile17 } from "fs";
3532
+ import { join as join23 } from "path";
3533
+ var OS = platform();
3534
+ function findRcloneMountPid() {
3535
+ try {
3536
+ const out = execSync("pgrep -f 'rclone.*mount'", {
3537
+ encoding: "utf8",
3538
+ timeout: 2e3,
3539
+ stdio: ["pipe", "pipe", "pipe"]
3540
+ }).trim();
3541
+ const pids = out.split("\n").filter(Boolean);
3542
+ if (pids.length === 0) return null;
3543
+ return parseInt(pids[0], 10);
3544
+ } catch {
3545
+ try {
3546
+ const out = execSync("ps aux", { encoding: "utf8", timeout: 2e3, stdio: ["pipe", "pipe", "pipe"] });
3547
+ for (const line of out.split("\n")) {
3548
+ if (line.includes("rclone") && line.includes("mount") && !line.includes("grep")) {
3549
+ const parts = line.trim().split(/\s+/);
3550
+ if (parts.length >= 2) return parseInt(parts[1], 10);
3551
+ }
3552
+ }
3553
+ } catch {
3554
+ }
3555
+ return null;
3556
+ }
3557
+ }
3558
+ function parseRcloneFlags(pid) {
3559
+ const flags = /* @__PURE__ */ new Map();
3560
+ try {
3561
+ const args = getRcloneArgs(pid);
3562
+ for (let i = 0; i < args.length; i++) {
3563
+ const arg = args[i];
3564
+ if (arg.startsWith("--") && arg.includes("=")) {
3565
+ const eq = arg.indexOf("=");
3566
+ flags.set(arg.slice(0, eq), arg.slice(eq + 1));
3567
+ } else if (arg.startsWith("--")) {
3568
+ const next = args[i + 1];
3569
+ if (next && !next.startsWith("-")) {
3570
+ flags.set(arg, next);
3571
+ i++;
3572
+ } else {
3573
+ flags.set(arg, "");
3574
+ }
3575
+ }
3576
+ }
3577
+ } catch {
3578
+ }
3579
+ return flags;
3580
+ }
3581
+ function getRcloneVersion() {
3582
+ try {
3583
+ const out = execSync("rclone version", {
3584
+ encoding: "utf8",
3585
+ timeout: 3e3,
3586
+ stdio: ["pipe", "pipe", "pipe"]
3587
+ });
3588
+ const match = out.match(/rclone\s+v(\d+)\.(\d+)\.(\d+)/i);
3589
+ if (!match) return null;
3590
+ return {
3591
+ major: parseInt(match[1], 10),
3592
+ minor: parseInt(match[2], 10),
3593
+ patch: parseInt(match[3], 10),
3594
+ raw: out.split("\n")[0].trim()
3595
+ };
3596
+ } catch {
3597
+ return null;
3598
+ }
3599
+ }
3600
+ function extractRcloneFs(args) {
3601
+ let foundMount = false;
3602
+ for (const arg of args) {
3603
+ if (arg === "mount") {
3604
+ foundMount = true;
3605
+ continue;
3606
+ }
3607
+ if (foundMount && arg.includes(":") && !arg.startsWith("-") && !arg.startsWith("/")) {
3608
+ return arg;
3609
+ }
3610
+ }
3611
+ return null;
3612
+ }
3613
+ function getRcloneArgs(pid) {
3614
+ try {
3615
+ if (OS === "linux") {
3616
+ const raw = readFileSync6(`/proc/${pid}/cmdline`);
3617
+ return new TextDecoder().decode(raw).split("\0").filter(Boolean);
3618
+ } else {
3619
+ const out = execSync(`ps -o args= -p ${pid}`, {
3620
+ encoding: "utf8",
3621
+ timeout: 2e3,
3622
+ stdio: ["pipe", "pipe", "pipe"]
3623
+ }).trim();
3624
+ return out.split(/\s+/);
3625
+ }
3626
+ } catch {
3627
+ return [];
3628
+ }
3629
+ }
3630
+ function queryRcloneRC(rcAddr, fs) {
3631
+ try {
3632
+ const payload = JSON.stringify({ fs });
3633
+ const out = execSync(
3634
+ `curl -s --max-time 3 -X POST "http://${rcAddr}/vfs/stats" -H "Content-Type: application/json" -d '${payload}' 2>/dev/null`,
3635
+ { encoding: "utf8", timeout: 5e3, stdio: ["pipe", "pipe", "pipe"] }
3636
+ );
3637
+ if (!out.trim()) return null;
3638
+ const data = JSON.parse(out);
3639
+ if (data.status && data.status >= 400) {
3640
+ return { error: data.error || `RC error (status ${data.status})`, erroredFiles: 0, uploadsInProgress: 0, uploadsQueued: 0, outOfSpace: false, bytesUsed: 0, files: 0, totalSize: "unknown" };
3641
+ }
3642
+ const dc = data.diskCache || {};
3643
+ return {
3644
+ erroredFiles: dc.erroredFiles ?? 0,
3645
+ uploadsInProgress: dc.uploadsInProgress ?? 0,
3646
+ uploadsQueued: dc.uploadsQueued ?? 0,
3647
+ outOfSpace: dc.outOfSpace ?? false,
3648
+ bytesUsed: dc.bytesUsed ?? 0,
3649
+ files: dc.files ?? 0,
3650
+ totalSize: data.totalSize || "unknown"
3651
+ };
3652
+ } catch {
3653
+ return { error: "RC endpoint unreachable", erroredFiles: 0, uploadsInProgress: 0, uploadsQueued: 0, outOfSpace: false, bytesUsed: 0, files: 0, totalSize: "unknown" };
3654
+ }
3655
+ }
3656
+ function detectFuseMount(vaultPath) {
3657
+ try {
3658
+ if (OS === "linux") {
3659
+ const mounts = readFileSync6("/proc/mounts", "utf8");
3660
+ let best = null;
3661
+ for (const line of mounts.split("\n")) {
3662
+ const parts = line.split(" ");
3663
+ if (parts.length < 3) continue;
3664
+ const point = parts[1];
3665
+ const fs = parts[2];
3666
+ if (vaultPath.startsWith(point) && (!best || point.length > best.point.length)) {
3667
+ best = { point, fs };
3668
+ }
3669
+ }
3670
+ if (best && best.fs.includes("fuse")) return { mountPoint: best.point, fsType: best.fs };
3671
+ } else if (OS === "darwin") {
3672
+ const out = execSync("mount", { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
3673
+ let best = null;
3674
+ for (const line of out.split("\n")) {
3675
+ const match = line.match(/^(\S+) on (\S+) \((.*?)\)/);
3676
+ if (!match) continue;
3677
+ const point = match[2];
3678
+ const opts = match[3];
3679
+ if (opts.includes("fuse") && vaultPath.startsWith(point) && (!best || point.length > best.point.length)) {
3680
+ best = { point, fsType: `fuse.${match[1].split(":")[0] || "unknown"}` };
3681
+ }
3682
+ }
3683
+ if (best) return best;
3684
+ }
3685
+ } catch {
3686
+ }
3687
+ return null;
3688
+ }
3689
+ function writeTest(dir) {
3690
+ const testFile = join23(dir, `.doctor-write-test-${process.pid}.tmp`);
3691
+ const payload = `skillwiki doctor write test \u2014 ${Date.now()} \u2014 ${Math.random().toString(36).slice(2)}`;
3692
+ const start = Date.now();
3693
+ try {
3694
+ writeFileSync4(testFile, payload, "utf8");
3695
+ } catch (e) {
3696
+ return { success: false, writeMs: Date.now() - start, readMs: 0, size: 0, error: `write failed: ${e.message}` };
3697
+ }
3698
+ const writeMs = Date.now() - start;
3699
+ const readStart = Date.now();
3700
+ try {
3701
+ const back = readFile17(testFile, "utf8");
3702
+ const readMs = Date.now() - readStart;
3703
+ if (back !== payload) {
3704
+ try {
3705
+ unlinkSync3(testFile);
3706
+ } catch {
3707
+ }
3708
+ return { success: false, writeMs, readMs, size: Buffer.byteLength(payload, "utf8"), error: "content mismatch \u2014 wrote and read-back differ" };
3709
+ }
3710
+ } catch (e) {
3711
+ try {
3712
+ unlinkSync3(testFile);
3713
+ } catch {
3714
+ }
3715
+ return { success: false, writeMs, readMs: Date.now() - readStart, size: 0, error: `read failed: ${e.message}` };
3716
+ }
3717
+ try {
3718
+ unlinkSync3(testFile);
3719
+ } catch {
3720
+ }
3721
+ return { success: true, writeMs, readMs: Date.now() - readStart, size: Buffer.byteLength(payload, "utf8") };
3722
+ }
3723
+ var FLAG_THRESHOLDS = {
3724
+ "--vfs-write-back": { min: 15, unit: "s", label: "VFS write-back window" },
3725
+ "--vfs-write-wait": { min: 10, unit: "s", label: "VFS write-wait" },
3726
+ "--vfs-cache-max-age": { min: 24, unit: "h", label: "VFS cache max age" }
3727
+ };
3728
+ var MIN_RCLONE_VERSION = { major: 1, minor: 65, patch: 0 };
3729
+
3529
3730
  // src/commands/doctor.ts
3530
3731
  function check(status, id, label, detail) {
3531
3732
  return { id, label, status, detail };
@@ -3544,7 +3745,7 @@ function detectCliChannels(argv, home) {
3544
3745
  channels.push({ name: "dev", path: devPath, isDevLink: true });
3545
3746
  }
3546
3747
  try {
3547
- const whichOut = execSync("which skillwiki 2>/dev/null", { encoding: "utf8" }).trim();
3748
+ const whichOut = execSync2("which skillwiki 2>/dev/null", { encoding: "utf8" }).trim();
3548
3749
  if (whichOut) {
3549
3750
  const isDev = isDevSymlink(whichOut);
3550
3751
  if (!channels.some((c) => c.path === resolve4(whichOut))) {
@@ -3555,13 +3756,13 @@ function detectCliChannels(argv, home) {
3555
3756
  }
3556
3757
  const plugin = findPlugin(home);
3557
3758
  if (plugin) {
3558
- const pluginBin = join23(plugin.installPath, "bin", "skillwiki");
3559
- if (existsSync6(pluginBin)) {
3759
+ const pluginBin = join24(plugin.installPath, "bin", "skillwiki");
3760
+ if (existsSync7(pluginBin)) {
3560
3761
  channels.push({ name: "plugin", path: pluginBin, isDevLink: false });
3561
3762
  }
3562
3763
  }
3563
- const installBin = join23(home, ".claude", "skills", "bin", "skillwiki");
3564
- if (existsSync6(installBin)) {
3764
+ const installBin = join24(home, ".claude", "skills", "bin", "skillwiki");
3765
+ if (existsSync7(installBin)) {
3565
3766
  channels.push({ name: "install", path: installBin, isDevLink: false });
3566
3767
  }
3567
3768
  return channels;
@@ -3613,7 +3814,7 @@ function checkCliChannels(argv, home) {
3613
3814
  }
3614
3815
  async function checkConfigFile(home) {
3615
3816
  const cfgPath = configPath(home);
3616
- if (!existsSync6(cfgPath)) {
3817
+ if (!existsSync7(cfgPath)) {
3617
3818
  return check("warn", "config_file", "Config file exists", `${cfgPath} not found`);
3618
3819
  }
3619
3820
  try {
@@ -3628,7 +3829,7 @@ function checkWikiPathExists(resolvedPath) {
3628
3829
  if (resolvedPath === void 0) {
3629
3830
  return check("error", "wiki_path_exists", "Vault directory exists", "Cannot check \u2014 WIKI_PATH not resolved");
3630
3831
  }
3631
- if (existsSync6(resolvedPath) && statSync2(resolvedPath).isDirectory()) {
3832
+ if (existsSync7(resolvedPath) && statSync2(resolvedPath).isDirectory()) {
3632
3833
  return check("pass", "wiki_path_exists", "Vault directory exists", resolvedPath);
3633
3834
  }
3634
3835
  return check("error", "wiki_path_exists", "Vault directory exists", `${resolvedPath} does not exist or is not a directory`);
@@ -3637,13 +3838,13 @@ function checkVaultStructure(resolvedPath) {
3637
3838
  if (resolvedPath === void 0) {
3638
3839
  return check("error", "vault_structure", "Vault structure valid", "Cannot check \u2014 WIKI_PATH not resolved");
3639
3840
  }
3640
- if (!existsSync6(resolvedPath)) {
3841
+ if (!existsSync7(resolvedPath)) {
3641
3842
  return check("error", "vault_structure", "Vault structure valid", "Cannot check \u2014 vault directory does not exist");
3642
3843
  }
3643
3844
  const missing = [];
3644
- if (!existsSync6(join23(resolvedPath, "SCHEMA.md"))) missing.push("SCHEMA.md");
3845
+ if (!existsSync7(join24(resolvedPath, "SCHEMA.md"))) missing.push("SCHEMA.md");
3645
3846
  for (const dir of ["raw", "entities", "concepts", "meta"]) {
3646
- if (!existsSync6(join23(resolvedPath, dir))) missing.push(dir + "/");
3847
+ if (!existsSync7(join24(resolvedPath, dir))) missing.push(dir + "/");
3647
3848
  }
3648
3849
  if (missing.length === 0) {
3649
3850
  return check("pass", "vault_structure", "Vault structure valid", "All required files and directories present");
@@ -3651,8 +3852,8 @@ function checkVaultStructure(resolvedPath) {
3651
3852
  return check("warn", "vault_structure", "Vault structure valid", `Missing: ${missing.join(", ")} \u2014 run \`skillwiki init\` to add CodeWiki structure`);
3652
3853
  }
3653
3854
  function checkSkillsInstalled(home, cwd) {
3654
- const srcDir = cwd ? join23(cwd, "packages", "skills") : void 0;
3655
- if (srcDir && existsSync6(srcDir)) {
3855
+ const srcDir = cwd ? join24(cwd, "packages", "skills") : void 0;
3856
+ if (srcDir && existsSync7(srcDir)) {
3656
3857
  const found = findSkillMd(srcDir);
3657
3858
  if (found.length > 0) {
3658
3859
  return check("pass", "skills_installed", "Skills installed", `${found.length} SKILL.md file(s) found (source)`);
@@ -3665,8 +3866,8 @@ function checkSkillsInstalled(home, cwd) {
3665
3866
  return check("pass", "skills_installed", "Skills installed", `${found.length} SKILL.md file(s) found (plugin v${plugin.version})`);
3666
3867
  }
3667
3868
  }
3668
- const skillsDir = join23(home, ".claude", "skills");
3669
- if (existsSync6(skillsDir)) {
3869
+ const skillsDir = join24(home, ".claude", "skills");
3870
+ if (existsSync7(skillsDir)) {
3670
3871
  const found = findSkillMd(skillsDir);
3671
3872
  if (found.length > 0) {
3672
3873
  return check("pass", "skills_installed", "Skills installed", `${found.length} SKILL.md file(s) found (CLI install)`);
@@ -3676,10 +3877,10 @@ function checkSkillsInstalled(home, cwd) {
3676
3877
  }
3677
3878
  function checkDuplicateSkills(home) {
3678
3879
  const plugin = findPlugin(home);
3679
- const skillsDir = join23(home, ".claude", "skills");
3880
+ const skillsDir = join24(home, ".claude", "skills");
3680
3881
  const agentSkillDirs = [
3681
- { label: "~/.codex/skills/", path: join23(home, ".codex", "skills") },
3682
- { label: "~/.agents/skills/", path: join23(home, ".agents", "skills") }
3882
+ { label: "~/.codex/skills/", path: join24(home, ".codex", "skills") },
3883
+ { label: "~/.agents/skills/", path: join24(home, ".agents", "skills") }
3683
3884
  ];
3684
3885
  if (!plugin) {
3685
3886
  return check("pass", "skills_duplicate", "Skills not duplicated", "Single install channel");
@@ -3756,8 +3957,8 @@ async function checkProfiles(home) {
3756
3957
  }
3757
3958
  async function checkProjectLocalOverride(cwd) {
3758
3959
  const dir = cwd ?? process.cwd();
3759
- const envPath = join23(dir, ".skillwiki", ".env");
3760
- if (existsSync6(envPath)) {
3960
+ const envPath = join24(dir, ".skillwiki", ".env");
3961
+ if (existsSync7(envPath)) {
3761
3962
  return check("pass", "project_local", "Project-local config", `Found: ${envPath}`);
3762
3963
  }
3763
3964
  return check("pass", "project_local", "Project-local config", "None");
@@ -3766,17 +3967,17 @@ function checkVaultGitRemote(resolvedPath) {
3766
3967
  if (resolvedPath === void 0) {
3767
3968
  return check("error", "vault_git_remote", "Vault git remote", "Cannot check \u2014 WIKI_PATH not resolved");
3768
3969
  }
3769
- if (!existsSync6(join23(resolvedPath, ".git"))) {
3970
+ if (!existsSync7(join24(resolvedPath, ".git"))) {
3770
3971
  return check("warn", "vault_git_remote", "Vault git remote", "Vault is not a git repository \u2014 sync features unavailable");
3771
3972
  }
3772
3973
  try {
3773
- const remote = execSync("git remote", { cwd: resolvedPath, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim();
3974
+ const remote = execSync2("git remote", { cwd: resolvedPath, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim();
3774
3975
  if (!remote) {
3775
3976
  return check("warn", "vault_git_remote", "Vault git remote", "No remote configured \u2014 push/pull unavailable");
3776
3977
  }
3777
3978
  let branch = "(no commits yet)";
3778
3979
  try {
3779
- branch = execSync("git rev-parse --abbrev-ref HEAD", { cwd: resolvedPath, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim();
3980
+ branch = execSync2("git rev-parse --abbrev-ref HEAD", { cwd: resolvedPath, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim();
3780
3981
  } catch {
3781
3982
  }
3782
3983
  return check("pass", "vault_git_remote", "Vault git remote", `Remote: ${remote.split("\n")[0]}, branch: ${branch}`);
@@ -3789,9 +3990,9 @@ function checkObsidianTemplates(resolvedPath) {
3789
3990
  return check("error", "obsidian_templates", "Obsidian templates", "Cannot check \u2014 WIKI_PATH not resolved");
3790
3991
  }
3791
3992
  const missing = [];
3792
- if (!existsSync6(join23(resolvedPath, "_Templates"))) missing.push("_Templates/");
3793
- if (!existsSync6(join23(resolvedPath, ".obsidian", "templates.json"))) missing.push(".obsidian/templates.json");
3794
- if (!existsSync6(join23(resolvedPath, ".obsidian", "app.json"))) missing.push(".obsidian/app.json");
3993
+ if (!existsSync7(join24(resolvedPath, "_Templates"))) missing.push("_Templates/");
3994
+ if (!existsSync7(join24(resolvedPath, ".obsidian", "templates.json"))) missing.push(".obsidian/templates.json");
3995
+ if (!existsSync7(join24(resolvedPath, ".obsidian", "app.json"))) missing.push(".obsidian/app.json");
3795
3996
  if (missing.length === 0) {
3796
3997
  return check("pass", "obsidian_templates", "Obsidian templates", "Template folder and config present");
3797
3998
  }
@@ -3801,8 +4002,8 @@ function checkDotStoreClean(resolvedPath) {
3801
4002
  if (resolvedPath === void 0) {
3802
4003
  return check("error", "dsstore_clean", "No .DS_Store in raw/", "Cannot check \u2014 WIKI_PATH not resolved");
3803
4004
  }
3804
- const rawDir = join23(resolvedPath, "raw");
3805
- if (!existsSync6(rawDir)) {
4005
+ const rawDir = join24(resolvedPath, "raw");
4006
+ if (!existsSync7(rawDir)) {
3806
4007
  return check("pass", "dsstore_clean", "No .DS_Store in raw/", "raw/ directory not found \u2014 check skipped");
3807
4008
  }
3808
4009
  const found = [];
@@ -3817,7 +4018,7 @@ function checkDotStoreClean(resolvedPath) {
3817
4018
  if (entry.name === ".DS_Store") {
3818
4019
  found.push(rel ? `${rel}/.DS_Store` : ".DS_Store");
3819
4020
  } else if (entry.isDirectory()) {
3820
- walk2(join23(dir, entry.name), rel ? `${rel}/${entry.name}` : entry.name);
4021
+ walk2(join24(dir, entry.name), rel ? `${rel}/${entry.name}` : entry.name);
3821
4022
  }
3822
4023
  }
3823
4024
  })(rawDir, "");
@@ -3830,12 +4031,12 @@ function checkSyncLastPush(resolvedPath) {
3830
4031
  if (resolvedPath === void 0) {
3831
4032
  return check("error", "sync_last_push", "Vault sync recency", "Cannot check \u2014 WIKI_PATH not resolved");
3832
4033
  }
3833
- if (!existsSync6(join23(resolvedPath, ".git"))) {
4034
+ if (!existsSync7(join24(resolvedPath, ".git"))) {
3834
4035
  return check("pass", "sync_last_push", "Vault sync recency", "No git repo \u2014 sync check skipped");
3835
4036
  }
3836
4037
  let timestamp;
3837
4038
  try {
3838
- const out = execSync("git log -1 --format=%ct origin/HEAD", {
4039
+ const out = execSync2("git log -1 --format=%ct origin/HEAD", {
3839
4040
  cwd: resolvedPath,
3840
4041
  encoding: "utf8",
3841
4042
  stdio: ["pipe", "pipe", "pipe"]
@@ -3843,7 +4044,7 @@ function checkSyncLastPush(resolvedPath) {
3843
4044
  timestamp = parseInt(out, 10);
3844
4045
  } catch {
3845
4046
  try {
3846
- const out = execSync("git log -1 --format=%ct HEAD", {
4047
+ const out = execSync2("git log -1 --format=%ct HEAD", {
3847
4048
  cwd: resolvedPath,
3848
4049
  encoding: "utf8",
3849
4050
  stdio: ["pipe", "pipe", "pipe"]
@@ -3862,56 +4063,23 @@ function checkSyncLastPush(resolvedPath) {
3862
4063
  }
3863
4064
  return check("pass", "sync_last_push", "Vault sync recency", `Last push: ${dateStr} (${daysSince2} day(s) ago)`);
3864
4065
  }
3865
- function detectFuseMount(vaultPath) {
3866
- const os = platform();
3867
- try {
3868
- if (os === "linux") {
3869
- const mounts = readFileSync6("/proc/mounts", "utf8");
3870
- let best = null;
3871
- for (const line of mounts.split("\n")) {
3872
- const parts = line.split(" ");
3873
- if (parts.length < 3) continue;
3874
- const point = parts[1];
3875
- const fs = parts[2];
3876
- if (vaultPath.startsWith(point) && (!best || point.length > best.point.length)) {
3877
- best = { point, fs };
3878
- }
3879
- }
3880
- if (best && best.fs.includes("fuse")) return best.point;
3881
- } else if (os === "darwin") {
3882
- const out = execSync("mount", { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
3883
- let best = null;
3884
- for (const line of out.split("\n")) {
3885
- const match = line.match(/^(\S+) on (\S+) \((.*?)\)/);
3886
- if (!match) continue;
3887
- const point = match[2];
3888
- const opts = match[3];
3889
- if (opts.includes("fuse") && vaultPath.startsWith(point) && (!best || point.length > best.point.length)) {
3890
- best = { point };
3891
- }
3892
- }
3893
- if (best) return best.point;
3894
- }
3895
- } catch {
3896
- }
3897
- return null;
3898
- }
3899
4066
  function checkS3MountPerf(resolvedPath) {
3900
4067
  if (resolvedPath === void 0) {
3901
4068
  return check("pass", "s3_mount_perf", "S3 mount performance", "No vault path \u2014 check skipped");
3902
4069
  }
3903
- const mountPoint = detectFuseMount(resolvedPath);
3904
- if (!mountPoint) {
4070
+ const fuse = detectFuseMount(resolvedPath);
4071
+ if (!fuse) {
3905
4072
  return check("pass", "s3_mount_perf", "S3 mount performance", "local disk");
3906
4073
  }
3907
- const conceptsDir = join23(resolvedPath, "concepts");
3908
- if (!existsSync6(conceptsDir)) {
4074
+ const mountPoint = fuse.mountPoint;
4075
+ const conceptsDir = join24(resolvedPath, "concepts");
4076
+ if (!existsSync7(conceptsDir)) {
3909
4077
  return check("pass", "s3_mount_perf", "S3 mount performance", `S3 FUSE mount (${mountPoint}), no concepts/ to benchmark`);
3910
4078
  }
3911
4079
  const start = Date.now();
3912
4080
  let timedOut = false;
3913
4081
  try {
3914
- execSync(`rg -l "." "${conceptsDir}"`, {
4082
+ execSync2(`rg -l "." "${conceptsDir}"`, {
3915
4083
  timeout: 5e3,
3916
4084
  encoding: "utf8",
3917
4085
  stdio: ["pipe", "pipe", "pipe"]
@@ -3944,6 +4112,170 @@ function checkS3MountPerf(resolvedPath) {
3944
4112
  `S3 FUSE mount, cache warm (rg scan: ${elapsed.toFixed(3)}s)`
3945
4113
  );
3946
4114
  }
4115
+ function checkRcloneFlagAudit(resolvedPath) {
4116
+ if (!resolvedPath) {
4117
+ return check("pass", "rclone_flags", "rclone VFS flags", "No vault path \u2014 check skipped");
4118
+ }
4119
+ const fuse = detectFuseMount(resolvedPath);
4120
+ if (!fuse) {
4121
+ return check("pass", "rclone_flags", "rclone VFS flags", "local disk \u2014 check skipped");
4122
+ }
4123
+ const pid = findRcloneMountPid();
4124
+ if (pid === null) {
4125
+ return check("warn", "rclone_flags", "rclone VFS flags", `S3 FUSE mount (${fuse.mountPoint}) but no rclone process found \u2014 cannot audit flags`);
4126
+ }
4127
+ const flags = parseRcloneFlags(pid);
4128
+ if (flags.size === 0) {
4129
+ return check("warn", "rclone_flags", "rclone VFS flags", `rclone PID ${pid} found but could not parse flags`);
4130
+ }
4131
+ const warnings = [];
4132
+ for (const [flag, threshold] of Object.entries(FLAG_THRESHOLDS)) {
4133
+ const raw = flags.get(flag);
4134
+ if (raw === void 0) {
4135
+ warnings.push(`${flag} not set (default may be unsafe)`);
4136
+ continue;
4137
+ }
4138
+ const value = parseFloat(raw);
4139
+ if (isNaN(value)) continue;
4140
+ let inSeconds = value;
4141
+ if (raw.endsWith("h")) inSeconds = value * 3600;
4142
+ else if (raw.endsWith("m")) inSeconds = value * 60;
4143
+ const thresholdSec = threshold.unit === "h" ? threshold.min * 3600 : threshold.unit === "m" ? threshold.min * 60 : threshold.min;
4144
+ if (inSeconds < thresholdSec) {
4145
+ warnings.push(`${flag}=${raw} (recommended \u2265${threshold.min}${threshold.unit})`);
4146
+ }
4147
+ }
4148
+ const cacheMode = flags.get("--vfs-cache-mode");
4149
+ if (!cacheMode) {
4150
+ warnings.push("--vfs-cache-mode not set (recommended: full)");
4151
+ } else if (cacheMode !== "full") {
4152
+ warnings.push(`--vfs-cache-mode=${cacheMode} (recommended: full)`);
4153
+ }
4154
+ if (!flags.has("--log-file")) {
4155
+ warnings.push("--log-file not set \u2014 no rclone error log configured");
4156
+ }
4157
+ if (warnings.length > 0) {
4158
+ return check("warn", "rclone_flags", "rclone VFS flags", warnings.join("; "));
4159
+ }
4160
+ return check("pass", "rclone_flags", "rclone VFS flags", `PID ${pid}: all critical flags at safe values`);
4161
+ }
4162
+ function checkRcloneVersion(resolvedPath) {
4163
+ if (!resolvedPath) {
4164
+ return check("pass", "rclone_version", "rclone version", "No vault path \u2014 check skipped");
4165
+ }
4166
+ const fuse = detectFuseMount(resolvedPath);
4167
+ if (!fuse) {
4168
+ return check("pass", "rclone_version", "rclone version", "local disk \u2014 check skipped");
4169
+ }
4170
+ const ver = getRcloneVersion();
4171
+ if (!ver) {
4172
+ return check("warn", "rclone_version", "rclone version", "rclone not found on PATH \u2014 cannot verify version");
4173
+ }
4174
+ const min = MIN_RCLONE_VERSION;
4175
+ const tooOld = ver.major < min.major || ver.major === min.major && ver.minor < min.minor || ver.major === min.major && ver.minor === min.minor && ver.patch < min.patch;
4176
+ if (tooOld) {
4177
+ return check(
4178
+ "warn",
4179
+ "rclone_version",
4180
+ "rclone version",
4181
+ `${ver.raw} \u2014 upgrade to \u2265v${min.major}.${min.minor}.${min.patch} for --vfs-write-wait support (current version may silently ignore this flag)`
4182
+ );
4183
+ }
4184
+ return check("pass", "rclone_version", "rclone version", ver.raw);
4185
+ }
4186
+ function checkWriteTest(resolvedPath) {
4187
+ if (!resolvedPath) {
4188
+ return check("pass", "s3_write_test", "S3 write test", "No vault path \u2014 check skipped");
4189
+ }
4190
+ const fuse = detectFuseMount(resolvedPath);
4191
+ if (!fuse) {
4192
+ return check("pass", "s3_write_test", "S3 write test", "local disk \u2014 check skipped");
4193
+ }
4194
+ const conceptsDir = join24(resolvedPath, "concepts");
4195
+ if (!existsSync7(conceptsDir)) {
4196
+ return check("pass", "s3_write_test", "S3 write test", "no concepts/ dir to test \u2014 check skipped");
4197
+ }
4198
+ const result = writeTest(conceptsDir);
4199
+ if (result.success) {
4200
+ const totalMs = result.writeMs + result.readMs;
4201
+ if (totalMs > 3e3) {
4202
+ return check(
4203
+ "warn",
4204
+ "s3_write_test",
4205
+ "S3 write test",
4206
+ `write+read ${totalMs}ms (write ${result.writeMs}ms, read ${result.readMs}ms, ${result.size}B) \u2014 S3 mount is slow`
4207
+ );
4208
+ }
4209
+ return check(
4210
+ "pass",
4211
+ "s3_write_test",
4212
+ "S3 write test",
4213
+ `write+read ${totalMs}ms (write ${result.writeMs}ms, read ${result.readMs}ms)`
4214
+ );
4215
+ }
4216
+ return check(
4217
+ "warn",
4218
+ "s3_write_test",
4219
+ "S3 write test",
4220
+ `${result.error} \u2014 S3 mount may have a stale FUSE handle or write-back failure`
4221
+ );
4222
+ }
4223
+ function checkVfsCacheHealth(resolvedPath) {
4224
+ if (!resolvedPath) {
4225
+ return check("pass", "vfs_cache_health", "VFS cache health", "No vault path \u2014 check skipped");
4226
+ }
4227
+ const fuse = detectFuseMount(resolvedPath);
4228
+ if (!fuse) {
4229
+ return check("pass", "vfs_cache_health", "VFS cache health", "local disk \u2014 check skipped");
4230
+ }
4231
+ const pid = findRcloneMountPid();
4232
+ if (pid === null) {
4233
+ return check("warn", "vfs_cache_health", "VFS cache health", "no rclone process found \u2014 cannot query VFS stats");
4234
+ }
4235
+ const flags = parseRcloneFlags(pid);
4236
+ const rcAddr = flags.get("--rc-addr") || "127.0.0.1:5572";
4237
+ if (!flags.has("--rc")) {
4238
+ return check(
4239
+ "info",
4240
+ "vfs_cache_health",
4241
+ "VFS cache health",
4242
+ `rclone RC not enabled \u2014 add --rc --rc-addr ${rcAddr} to enable cache health monitoring`
4243
+ );
4244
+ }
4245
+ const args = getRcloneArgs(pid);
4246
+ const fs = extractRcloneFs(args) || "unknown:";
4247
+ const stats = queryRcloneRC(rcAddr, fs || "unknown:");
4248
+ if (!stats) {
4249
+ return check(
4250
+ "warn",
4251
+ "vfs_cache_health",
4252
+ "VFS cache health",
4253
+ `RC endpoint ${rcAddr} unreachable \u2014 is rclone --rc enabled?`
4254
+ );
4255
+ }
4256
+ if (stats.error) {
4257
+ return check("warn", "vfs_cache_health", "VFS cache health", stats.error);
4258
+ }
4259
+ const issues = [];
4260
+ if (stats.uploadsInProgress > 0) issues.push(`${stats.uploadsInProgress} upload(s) in progress`);
4261
+ if (stats.uploadsQueued > 10) issues.push(`${stats.uploadsQueued} upload(s) queued (backlog)`);
4262
+ if (stats.erroredFiles > 0) issues.push(`${stats.erroredFiles} errored file(s)`);
4263
+ if (stats.outOfSpace) issues.push("cache disk full");
4264
+ if (issues.length > 0) {
4265
+ return check(
4266
+ "warn",
4267
+ "vfs_cache_health",
4268
+ "VFS cache health",
4269
+ `${stats.files} files, ${stats.bytesUsed} bytes \u2014 ${issues.join("; ")}`
4270
+ );
4271
+ }
4272
+ return check(
4273
+ "pass",
4274
+ "vfs_cache_health",
4275
+ "VFS cache health",
4276
+ `${stats.files} files, ${(stats.bytesUsed / 1024 / 1024).toFixed(1)}MB \u2014 clean (0 errored, 0 pending)`
4277
+ );
4278
+ }
3947
4279
  function findSkillMd(dir) {
3948
4280
  const results = [];
3949
4281
  let entries;
@@ -3954,9 +4286,9 @@ function findSkillMd(dir) {
3954
4286
  }
3955
4287
  for (const entry of entries) {
3956
4288
  if (entry.isFile() && entry.name === "SKILL.md") {
3957
- results.push(join23(dir, entry.name));
4289
+ results.push(join24(dir, entry.name));
3958
4290
  } else if (entry.isDirectory()) {
3959
- results.push(...findSkillMd(join23(dir, entry.name)));
4291
+ results.push(...findSkillMd(join24(dir, entry.name)));
3960
4292
  }
3961
4293
  }
3962
4294
  return results;
@@ -3970,7 +4302,7 @@ function findSkillNames(dir) {
3970
4302
  return results;
3971
4303
  }
3972
4304
  for (const entry of entries) {
3973
- if (entry.isDirectory() && existsSync6(join23(dir, entry.name, "SKILL.md"))) {
4305
+ if (entry.isDirectory() && existsSync7(join24(dir, entry.name, "SKILL.md"))) {
3974
4306
  results.push(entry.name);
3975
4307
  }
3976
4308
  }
@@ -3997,6 +4329,10 @@ async function runDoctor(input) {
3997
4329
  checks.push(checkSyncLastPush(resolvedPath));
3998
4330
  checks.push(checkDotStoreClean(resolvedPath));
3999
4331
  checks.push(checkS3MountPerf(resolvedPath));
4332
+ checks.push(checkRcloneFlagAudit(resolvedPath));
4333
+ checks.push(checkRcloneVersion(resolvedPath));
4334
+ checks.push(checkWriteTest(resolvedPath));
4335
+ checks.push(checkVfsCacheHealth(resolvedPath));
4000
4336
  checks.push(checkSkillsInstalled(input.home, input.cwd));
4001
4337
  checks.push(checkDuplicateSkills(input.home));
4002
4338
  checks.push(checkNpmUpdate(input.home, input.currentVersion));
@@ -4024,8 +4360,8 @@ async function runDoctor(input) {
4024
4360
  }
4025
4361
 
4026
4362
  // src/commands/archive.ts
4027
- import { rename as rename5, mkdir as mkdir8, readFile as readFile17, writeFile as writeFile9 } from "fs/promises";
4028
- import { join as join24, dirname as dirname9 } from "path";
4363
+ import { rename as rename5, mkdir as mkdir8, readFile as readFile18, writeFile as writeFile9 } from "fs/promises";
4364
+ import { join as join25, dirname as dirname9 } from "path";
4029
4365
  async function runArchive(input) {
4030
4366
  const scan = await scanVault(input.vault);
4031
4367
  if (!scan.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scan };
@@ -4041,13 +4377,13 @@ async function runArchive(input) {
4041
4377
  }
4042
4378
  if (!relPath) return { exitCode: ExitCode.ARCHIVE_TARGET_NOT_FOUND, result: err("ARCHIVE_TARGET_NOT_FOUND", { page: input.page }) };
4043
4379
  if (relPath.startsWith("_archive/")) return { exitCode: ExitCode.ARCHIVE_ALREADY_ARCHIVED, result: err("ARCHIVE_ALREADY_ARCHIVED", { page: relPath }) };
4044
- const archivePath = join24("_archive", relPath).replace(/\\/g, "/");
4045
- await mkdir8(dirname9(join24(input.vault, archivePath)), { recursive: true });
4380
+ const archivePath = join25("_archive", relPath).replace(/\\/g, "/");
4381
+ await mkdir8(dirname9(join25(input.vault, archivePath)), { recursive: true });
4046
4382
  let indexUpdated = false;
4047
4383
  if (!isRaw) {
4048
- const indexPath = join24(input.vault, "index.md");
4384
+ const indexPath = join25(input.vault, "index.md");
4049
4385
  try {
4050
- const idx = await readFile17(indexPath, "utf8");
4386
+ const idx = await readFile18(indexPath, "utf8");
4051
4387
  const slug = relPath.replace(/\.md$/, "").split("/").pop();
4052
4388
  const originalLines = idx.split("\n");
4053
4389
  const filtered = originalLines.filter((l) => !l.includes(`[[${slug}]]`));
@@ -4059,7 +4395,7 @@ async function runArchive(input) {
4059
4395
  if (e instanceof Error && "code" in e && e.code !== "ENOENT") throw e;
4060
4396
  }
4061
4397
  }
4062
- await rename5(join24(input.vault, relPath), join24(input.vault, archivePath));
4398
+ await rename5(join25(input.vault, relPath), join25(input.vault, archivePath));
4063
4399
  appendLastOp(input.vault, {
4064
4400
  operation: "archive",
4065
4401
  summary: `moved ${relPath} to ${archivePath}`,
@@ -4432,16 +4768,16 @@ ${newBody}`;
4432
4768
  }
4433
4769
 
4434
4770
  // src/commands/update.ts
4435
- import { execSync as execSync2 } from "child_process";
4771
+ import { execSync as execSync3 } from "child_process";
4436
4772
  import { readFileSync as readFileSync7 } from "fs";
4437
- import { join as join25 } from "path";
4773
+ import { join as join26 } from "path";
4438
4774
  function resolveGlobalSkillsRoot() {
4439
4775
  try {
4440
- const globalRoot = execSync2("npm root -g", {
4776
+ const globalRoot = execSync3("npm root -g", {
4441
4777
  encoding: "utf8",
4442
4778
  timeout: 5e3
4443
4779
  }).trim();
4444
- return join25(globalRoot, "skillwiki", "skills");
4780
+ return join26(globalRoot, "skillwiki", "skills");
4445
4781
  } catch {
4446
4782
  return null;
4447
4783
  }
@@ -4467,10 +4803,10 @@ async function runUpdate(input) {
4467
4803
  );
4468
4804
  const currentVersion = pkg2.version;
4469
4805
  const tag = input.distTag ?? "latest";
4470
- const target = join25(input.home, ".claude", "skills");
4806
+ const target = join26(input.home, ".claude", "skills");
4471
4807
  let latest;
4472
4808
  try {
4473
- latest = execSync2(`npm view skillwiki@${tag} version`, {
4809
+ latest = execSync3(`npm view skillwiki@${tag} version`, {
4474
4810
  encoding: "utf8",
4475
4811
  timeout: 15e3
4476
4812
  }).trim();
@@ -4500,7 +4836,7 @@ async function runUpdate(input) {
4500
4836
  };
4501
4837
  }
4502
4838
  try {
4503
- execSync2(`npm install -g skillwiki@${tag}`, {
4839
+ execSync3(`npm install -g skillwiki@${tag}`, {
4504
4840
  stdio: "pipe",
4505
4841
  timeout: 6e4
4506
4842
  });
@@ -4536,17 +4872,17 @@ async function runUpdate(input) {
4536
4872
  }
4537
4873
 
4538
4874
  // src/commands/self-update.ts
4539
- import { execSync as execSync3 } from "child_process";
4540
- import { existsSync as existsSync7, readFileSync as readFileSync8 } from "fs";
4541
- import { join as join26 } from "path";
4875
+ import { execSync as execSync4 } from "child_process";
4876
+ import { existsSync as existsSync8, readFileSync as readFileSync8 } from "fs";
4877
+ import { join as join27 } from "path";
4542
4878
  var DEFAULT_SOURCE_ROOT_SUFFIX = "/Desktop/code/llm-wiki";
4543
4879
  async function runSelfUpdate(input) {
4544
4880
  const currentVersion = JSON.parse(
4545
4881
  readFileSync8(new URL("../../package.json", import.meta.url), "utf8")
4546
4882
  ).version;
4547
4883
  const sourceRoot = input.sourceRoot ?? `${input.home}${DEFAULT_SOURCE_ROOT_SUFFIX}`;
4548
- const localPkgPath = join26(sourceRoot, "packages", "cli", "package.json");
4549
- const hasLocalSource = existsSync7(localPkgPath);
4884
+ const localPkgPath = join27(sourceRoot, "packages", "cli", "package.json");
4885
+ const hasLocalSource = existsSync8(localPkgPath);
4550
4886
  if (input.check) {
4551
4887
  let availableVersion = null;
4552
4888
  let source;
@@ -4560,7 +4896,7 @@ async function runSelfUpdate(input) {
4560
4896
  } else {
4561
4897
  source = "npm";
4562
4898
  try {
4563
- availableVersion = execSync3("npm view skillwiki@latest version", {
4899
+ availableVersion = execSync4("npm view skillwiki@latest version", {
4564
4900
  encoding: "utf8",
4565
4901
  timeout: 15e3
4566
4902
  }).trim();
@@ -4586,7 +4922,7 @@ async function runSelfUpdate(input) {
4586
4922
  }
4587
4923
  if (hasLocalSource) {
4588
4924
  try {
4589
- execSync3("npm run build -w packages/cli", {
4925
+ execSync4("npm run build -w packages/cli", {
4590
4926
  cwd: sourceRoot,
4591
4927
  stdio: "pipe",
4592
4928
  timeout: 6e4
@@ -4598,7 +4934,7 @@ async function runSelfUpdate(input) {
4598
4934
  };
4599
4935
  }
4600
4936
  try {
4601
- execSync3("npm link ./packages/cli", {
4937
+ execSync4("npm link ./packages/cli", {
4602
4938
  cwd: sourceRoot,
4603
4939
  stdio: "pipe",
4604
4940
  timeout: 3e4
@@ -4630,7 +4966,7 @@ async function runSelfUpdate(input) {
4630
4966
  }
4631
4967
  let latestVersion;
4632
4968
  try {
4633
- latestVersion = execSync3("npm view skillwiki@latest version", {
4969
+ latestVersion = execSync4("npm view skillwiki@latest version", {
4634
4970
  encoding: "utf8",
4635
4971
  timeout: 15e3
4636
4972
  }).trim();
@@ -4653,7 +4989,7 @@ async function runSelfUpdate(input) {
4653
4989
  };
4654
4990
  }
4655
4991
  try {
4656
- execSync3("npm install -g skillwiki@latest", {
4992
+ execSync4("npm install -g skillwiki@latest", {
4657
4993
  stdio: "pipe",
4658
4994
  timeout: 6e4
4659
4995
  });
@@ -4677,10 +5013,10 @@ async function runSelfUpdate(input) {
4677
5013
  }
4678
5014
 
4679
5015
  // src/commands/transcripts.ts
4680
- import { readdir as readdir5, stat as stat6, readFile as readFile18 } from "fs/promises";
4681
- import { join as join27 } from "path";
5016
+ import { readdir as readdir5, stat as stat6, readFile as readFile19 } from "fs/promises";
5017
+ import { join as join28 } from "path";
4682
5018
  async function runTranscripts(input) {
4683
- const dir = join27(input.vault, "raw", "transcripts");
5019
+ const dir = join28(input.vault, "raw", "transcripts");
4684
5020
  let entries;
4685
5021
  try {
4686
5022
  entries = await readdir5(dir, { withFileTypes: true });
@@ -4690,8 +5026,8 @@ async function runTranscripts(input) {
4690
5026
  const transcripts = [];
4691
5027
  for (const entry of entries) {
4692
5028
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
4693
- const filePath = join27(dir, entry.name);
4694
- const content = await readFile18(filePath, "utf8");
5029
+ const filePath = join28(dir, entry.name);
5030
+ const content = await readFile19(filePath, "utf8");
4695
5031
  const fm = extractFrontmatter(content);
4696
5032
  if (!fm.ok) continue;
4697
5033
  const ingested = typeof fm.data.ingested === "string" ? fm.data.ingested : "";
@@ -4708,12 +5044,12 @@ async function runTranscripts(input) {
4708
5044
  }
4709
5045
 
4710
5046
  // src/commands/project-index.ts
4711
- import { readdir as readdir6, readFile as readFile19, writeFile as writeFile10, mkdir as mkdir9 } from "fs/promises";
4712
- import { join as join28, dirname as dirname10 } from "path";
5047
+ import { readdir as readdir6, readFile as readFile20, writeFile as writeFile10, mkdir as mkdir9 } from "fs/promises";
5048
+ import { join as join29, dirname as dirname10 } from "path";
4713
5049
  var LAYER2_DIRS = ["entities", "concepts", "comparisons", "queries", "meta"];
4714
5050
  async function runProjectIndex(input) {
4715
5051
  const slug = input.slug;
4716
- const projectDir = join28(input.vault, "projects", slug);
5052
+ const projectDir = join29(input.vault, "projects", slug);
4717
5053
  try {
4718
5054
  await readdir6(projectDir);
4719
5055
  } catch {
@@ -4724,15 +5060,15 @@ async function runProjectIndex(input) {
4724
5060
  }
4725
5061
  const wikilinkPattern = `[[${slug}]]`;
4726
5062
  const entries = [];
4727
- const compoundDir = join28(input.vault, "projects", slug, "compound");
5063
+ const compoundDir = join29(input.vault, "projects", slug, "compound");
4728
5064
  try {
4729
5065
  const compoundFiles = await readdir6(compoundDir, { withFileTypes: true });
4730
5066
  for (const entry of compoundFiles) {
4731
5067
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
4732
- const filePath = join28(compoundDir, entry.name);
5068
+ const filePath = join29(compoundDir, entry.name);
4733
5069
  let text;
4734
5070
  try {
4735
- text = await readFile19(filePath, "utf8");
5071
+ text = await readFile20(filePath, "utf8");
4736
5072
  } catch {
4737
5073
  continue;
4738
5074
  }
@@ -4749,16 +5085,16 @@ async function runProjectIndex(input) {
4749
5085
  for (const dir of LAYER2_DIRS) {
4750
5086
  let files;
4751
5087
  try {
4752
- files = await readdir6(join28(input.vault, dir), { withFileTypes: true });
5088
+ files = await readdir6(join29(input.vault, dir), { withFileTypes: true });
4753
5089
  } catch {
4754
5090
  continue;
4755
5091
  }
4756
5092
  for (const entry of files) {
4757
5093
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
4758
- const filePath = join28(input.vault, dir, entry.name);
5094
+ const filePath = join29(input.vault, dir, entry.name);
4759
5095
  let text;
4760
5096
  try {
4761
- text = await readFile19(filePath, "utf8");
5097
+ text = await readFile20(filePath, "utf8");
4762
5098
  } catch {
4763
5099
  continue;
4764
5100
  }
@@ -4779,11 +5115,11 @@ async function runProjectIndex(input) {
4779
5115
  const tb = typeOrder[b.type] ?? 99;
4780
5116
  return ta !== tb ? ta - tb : a.title.localeCompare(b.title);
4781
5117
  });
4782
- const indexPath = join28(projectDir, "knowledge.md");
5118
+ const indexPath = join29(projectDir, "knowledge.md");
4783
5119
  let existing = false;
4784
5120
  let stale = false;
4785
5121
  try {
4786
- const existingText = await readFile19(indexPath, "utf8");
5122
+ const existingText = await readFile20(indexPath, "utf8");
4787
5123
  existing = true;
4788
5124
  const existingEntries = existingText.split("\n").filter((l) => l.startsWith("- [["));
4789
5125
  const existingPages = new Set(existingEntries.map((l) => {
@@ -4853,9 +5189,9 @@ ${entries.map((e) => ` ${e.type}: [[${e.page.replace(/\.md$/, "")}]] \u2014 ${e
4853
5189
 
4854
5190
  // src/commands/compound.ts
4855
5191
  import { writeFile as writeFile11, mkdir as mkdir10, readdir as readdir7, unlink as unlink3 } from "fs/promises";
4856
- import { join as join29 } from "path";
4857
- import { existsSync as existsSync8 } from "fs";
4858
- import { readFile as readFile20 } from "fs/promises";
5192
+ import { join as join30 } from "path";
5193
+ import { existsSync as existsSync9 } from "fs";
5194
+ import { readFile as readFile21 } from "fs/promises";
4859
5195
  var RETRO_HEADING_RE = /^## \[(\d{4}-\d{2}-\d{2})(?:\s+[^\]]+)?\] retro \| loop cycle(?: (\d+))?: (.+)$/;
4860
5196
  var FIELD_RE = {
4861
5197
  improve: /^-\s+\*?\*?Improve:?\*?\*?\s*(.+)$/m,
@@ -4953,17 +5289,17 @@ function extractRetroFields(date, cycleName, block) {
4953
5289
  };
4954
5290
  }
4955
5291
  async function runCompound(input) {
4956
- const logPath = join29(input.vault, "log.md");
5292
+ const logPath = join30(input.vault, "log.md");
4957
5293
  let logText;
4958
5294
  try {
4959
- logText = await readFile20(logPath, "utf8");
5295
+ logText = await readFile21(logPath, "utf8");
4960
5296
  } catch {
4961
5297
  return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path: logPath }) };
4962
5298
  }
4963
5299
  const entries = parseRetroEntries(logText);
4964
5300
  const promoted = [];
4965
5301
  const skipped = [];
4966
- const compoundDir = join29(input.vault, "projects", input.project, "compound");
5302
+ const compoundDir = join30(input.vault, "projects", input.project, "compound");
4967
5303
  for (const entry of entries) {
4968
5304
  const generalizeValue = entry.generalize.trim();
4969
5305
  if (!/^yes/i.test(generalizeValue)) {
@@ -4971,8 +5307,8 @@ async function runCompound(input) {
4971
5307
  continue;
4972
5308
  }
4973
5309
  const slug = slugify(entry.cycleName);
4974
- const compoundPath = join29(compoundDir, `${slug}.md`);
4975
- if (existsSync8(compoundPath)) {
5310
+ const compoundPath = join30(compoundDir, `${slug}.md`);
5311
+ if (existsSync9(compoundPath)) {
4976
5312
  skipped.push(entry.date);
4977
5313
  continue;
4978
5314
  }
@@ -5010,7 +5346,7 @@ async function runCompound(input) {
5010
5346
  ].join("\n");
5011
5347
  const content = frontmatter + "\n" + body;
5012
5348
  if (!input.dryRun) {
5013
- if (!existsSync8(compoundDir)) {
5349
+ if (!existsSync9(compoundDir)) {
5014
5350
  await mkdir10(compoundDir, { recursive: true });
5015
5351
  }
5016
5352
  await writeFile11(compoundPath, content, "utf8");
@@ -5032,16 +5368,16 @@ async function runCompound(input) {
5032
5368
  };
5033
5369
  }
5034
5370
  async function runCompoundDelete(input) {
5035
- const projectDir = join29(input.vault, "projects", input.project);
5036
- if (!existsSync8(projectDir)) {
5371
+ const projectDir = join30(input.vault, "projects", input.project);
5372
+ if (!existsSync9(projectDir)) {
5037
5373
  return {
5038
5374
  exitCode: ExitCode.PROJECT_NOT_FOUND,
5039
5375
  result: err("PROJECT_NOT_FOUND", { slug: input.project, path: projectDir })
5040
5376
  };
5041
5377
  }
5042
5378
  const entryName = input.entry.replace(/\.md$/, "");
5043
- const compoundPath = join29(projectDir, "compound", `${entryName}.md`);
5044
- if (!existsSync8(compoundPath)) {
5379
+ const compoundPath = join30(projectDir, "compound", `${entryName}.md`);
5380
+ if (!existsSync9(compoundPath)) {
5045
5381
  return {
5046
5382
  exitCode: ExitCode.FILE_NOT_FOUND,
5047
5383
  result: err("FILE_NOT_FOUND", { path: compoundPath })
@@ -5074,8 +5410,8 @@ knowledge.md regenerated`
5074
5410
  };
5075
5411
  }
5076
5412
  async function runCompoundList(input) {
5077
- const compoundDir = join29(input.vault, "projects", input.project, "compound");
5078
- if (!existsSync8(compoundDir)) {
5413
+ const compoundDir = join30(input.vault, "projects", input.project, "compound");
5414
+ if (!existsSync9(compoundDir)) {
5079
5415
  return {
5080
5416
  exitCode: ExitCode.OK,
5081
5417
  result: ok({
@@ -5105,10 +5441,10 @@ could not read compound directory`
5105
5441
  const entries = [];
5106
5442
  for (const dirent of dirents) {
5107
5443
  if (!dirent.isFile() || !dirent.name.endsWith(".md")) continue;
5108
- const filePath = join29(compoundDir, dirent.name);
5444
+ const filePath = join30(compoundDir, dirent.name);
5109
5445
  let text;
5110
5446
  try {
5111
- text = await readFile20(filePath, "utf8");
5447
+ text = await readFile21(filePath, "utf8");
5112
5448
  } catch {
5113
5449
  continue;
5114
5450
  }
@@ -5138,8 +5474,8 @@ no compound entries found`;
5138
5474
 
5139
5475
  // src/commands/observe.ts
5140
5476
  import { mkdir as mkdir11, writeFile as writeFile12 } from "fs/promises";
5141
- import { existsSync as existsSync9, statSync as statSync3 } from "fs";
5142
- import { join as join30 } from "path";
5477
+ import { existsSync as existsSync10, statSync as statSync3 } from "fs";
5478
+ import { join as join31 } from "path";
5143
5479
  import { createHash as createHash4 } from "crypto";
5144
5480
  var ALLOWED_KINDS = /* @__PURE__ */ new Set(["note", "bug", "task", "idea", "session-log"]);
5145
5481
  function slugify2(text) {
@@ -5162,13 +5498,13 @@ async function runObserve(input) {
5162
5498
  result: err("SCHEME_REJECTED", { message: "Text must not be empty" })
5163
5499
  };
5164
5500
  }
5165
- if (!existsSync9(input.vault) || !statSync3(input.vault).isDirectory()) {
5501
+ if (!existsSync10(input.vault) || !statSync3(input.vault).isDirectory()) {
5166
5502
  return {
5167
5503
  exitCode: ExitCode.VAULT_PATH_INVALID,
5168
5504
  result: err("VAULT_PATH_INVALID", { path: input.vault })
5169
5505
  };
5170
5506
  }
5171
- const transcriptsDir = join30(input.vault, "raw", "transcripts");
5507
+ const transcriptsDir = join31(input.vault, "raw", "transcripts");
5172
5508
  try {
5173
5509
  await mkdir11(transcriptsDir, { recursive: true });
5174
5510
  } catch {
@@ -5180,7 +5516,7 @@ async function runObserve(input) {
5180
5516
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
5181
5517
  const slug = slugify2(input.text);
5182
5518
  const fileName = `${today}-observation-${slug}.md`;
5183
- const filePath = join30(transcriptsDir, fileName);
5519
+ const filePath = join31(transcriptsDir, fileName);
5184
5520
  const body = `
5185
5521
  ${input.text.trim()}
5186
5522
  `;
@@ -5220,8 +5556,8 @@ ${input.text.trim()}
5220
5556
  }
5221
5557
 
5222
5558
  // src/commands/ingest.ts
5223
- import { readFile as readFile21, writeFile as writeFile13, mkdir as mkdir12 } from "fs/promises";
5224
- import { join as join31 } from "path";
5559
+ import { readFile as readFile22, writeFile as writeFile13, mkdir as mkdir12 } from "fs/promises";
5560
+ import { join as join32 } from "path";
5225
5561
  import { createHash as createHash5 } from "crypto";
5226
5562
  var ALLOWED_TYPES = /* @__PURE__ */ new Set(["entity", "concept", "comparison", "query"]);
5227
5563
  var TYPE_DIR = {
@@ -5380,7 +5716,7 @@ async function runIngest(input) {
5380
5716
  sourceContent = fetchResult.data.body;
5381
5717
  } else {
5382
5718
  try {
5383
- sourceContent = await readFile21(input.source, "utf8");
5719
+ sourceContent = await readFile22(input.source, "utf8");
5384
5720
  } catch {
5385
5721
  return {
5386
5722
  exitCode: ExitCode.FILE_NOT_FOUND,
@@ -5395,8 +5731,8 @@ async function runIngest(input) {
5395
5731
  const rawRelPath = `raw/articles/${slug}.md`;
5396
5732
  const typedDir = TYPE_DIR[input.type] ?? `${input.type}s`;
5397
5733
  const typedRelPath = `${typedDir}/${slug}.md`;
5398
- const rawAbsPath = join31(input.vault, rawRelPath);
5399
- const typedAbsPath = join31(input.vault, typedRelPath);
5734
+ const rawAbsPath = join32(input.vault, rawRelPath);
5735
+ const typedAbsPath = join32(input.vault, typedRelPath);
5400
5736
  const rawContent = buildRawContent(sourceUrl, today, sha256, sourceContent);
5401
5737
  const typedContent = buildTypedContent(
5402
5738
  input.title,
@@ -5459,7 +5795,7 @@ async function runIngest(input) {
5459
5795
  };
5460
5796
  }
5461
5797
  try {
5462
- await mkdir12(join31(input.vault, "raw", "articles"), { recursive: true });
5798
+ await mkdir12(join32(input.vault, "raw", "articles"), { recursive: true });
5463
5799
  await writeFile13(rawAbsPath, rawContent, "utf8");
5464
5800
  } catch (e) {
5465
5801
  return {
@@ -5468,7 +5804,7 @@ async function runIngest(input) {
5468
5804
  };
5469
5805
  }
5470
5806
  try {
5471
- await mkdir12(join31(input.vault, typedDir), { recursive: true });
5807
+ await mkdir12(join32(input.vault, typedDir), { recursive: true });
5472
5808
  await writeFile13(typedAbsPath, typedContent, "utf8");
5473
5809
  } catch (e) {
5474
5810
  return {
@@ -5647,11 +5983,11 @@ ${body}`;
5647
5983
  }
5648
5984
 
5649
5985
  // src/commands/sync.ts
5650
- import { existsSync as existsSync10 } from "fs";
5651
- import { join as join32 } from "path";
5986
+ import { existsSync as existsSync11 } from "fs";
5987
+ import { join as join33 } from "path";
5652
5988
  function runSyncStatus(input) {
5653
5989
  const vault = input.vault;
5654
- if (!existsSync10(join32(vault, ".git"))) {
5990
+ if (!existsSync11(join33(vault, ".git"))) {
5655
5991
  return {
5656
5992
  exitCode: ExitCode.VAULT_PATH_INVALID,
5657
5993
  result: ok({
@@ -5720,7 +6056,7 @@ function runSyncStatus(input) {
5720
6056
  }
5721
6057
  async function runSyncPush(input) {
5722
6058
  const vault = input.vault;
5723
- if (!existsSync10(join32(vault, ".git"))) {
6059
+ if (!existsSync11(join33(vault, ".git"))) {
5724
6060
  return {
5725
6061
  exitCode: ExitCode.VAULT_PATH_INVALID,
5726
6062
  result: err("NOT_A_GIT_REPO", { path: vault })
@@ -5805,7 +6141,7 @@ async function runSyncPush(input) {
5805
6141
  }
5806
6142
  async function runSyncPull(input) {
5807
6143
  const vault = input.vault;
5808
- if (!existsSync10(join32(vault, ".git"))) {
6144
+ if (!existsSync11(join33(vault, ".git"))) {
5809
6145
  return {
5810
6146
  exitCode: ExitCode.VAULT_PATH_INVALID,
5811
6147
  result: err("NOT_A_GIT_REPO", { path: vault })
@@ -5880,8 +6216,8 @@ async function runSyncPull(input) {
5880
6216
  }
5881
6217
 
5882
6218
  // src/commands/backup.ts
5883
- import { statSync as statSync4, readdirSync as readdirSync2, readFileSync as readFileSync9, mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "fs";
5884
- import { join as join33, relative as relative3, dirname as dirname11 } from "path";
6219
+ import { statSync as statSync4, readdirSync as readdirSync2, readFileSync as readFileSync9, mkdirSync as mkdirSync3, writeFileSync as writeFileSync5 } from "fs";
6220
+ import { join as join34, relative as relative3, dirname as dirname11 } from "path";
5885
6221
  import { PutObjectCommand, HeadObjectCommand, ListObjectsV2Command, GetObjectCommand, DeleteObjectsCommand } from "@aws-sdk/client-s3";
5886
6222
 
5887
6223
  // src/utils/s3-client.ts
@@ -5905,7 +6241,7 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([".git", ".obsidian", "_archive", "node_
5905
6241
  function* walkMarkdown(dir, base) {
5906
6242
  for (const entry of readdirSync2(dir, { withFileTypes: true })) {
5907
6243
  if (SKIP_DIRS.has(entry.name)) continue;
5908
- const full = join33(dir, entry.name);
6244
+ const full = join34(dir, entry.name);
5909
6245
  if (entry.isDirectory()) {
5910
6246
  yield* walkMarkdown(full, base);
5911
6247
  } else if (entry.name.endsWith(".md")) {
@@ -5928,7 +6264,7 @@ async function runBackupSync(input) {
5928
6264
  let failed = 0;
5929
6265
  const files = [...walkMarkdown(input.vault, input.vault)];
5930
6266
  for (const relPath of files) {
5931
- const absPath = join33(input.vault, relPath);
6267
+ const absPath = join34(input.vault, relPath);
5932
6268
  const localStat = statSync4(absPath);
5933
6269
  let needsUpload = true;
5934
6270
  try {
@@ -6004,7 +6340,7 @@ async function runBackupRestore(input) {
6004
6340
  const objects = list.Contents ?? [];
6005
6341
  for (const obj of objects) {
6006
6342
  if (!obj.Key) continue;
6007
- const localPath = join33(target, obj.Key);
6343
+ const localPath = join34(target, obj.Key);
6008
6344
  try {
6009
6345
  const localStat = statSync4(localPath);
6010
6346
  if (obj.LastModified && localStat.mtime > obj.LastModified) {
@@ -6018,7 +6354,7 @@ async function runBackupRestore(input) {
6018
6354
  const body = await resp.Body?.transformToByteArray();
6019
6355
  if (body) {
6020
6356
  mkdirSync3(dirname11(localPath), { recursive: true });
6021
- writeFileSync4(localPath, Buffer.from(body));
6357
+ writeFileSync5(localPath, Buffer.from(body));
6022
6358
  downloaded++;
6023
6359
  }
6024
6360
  } catch {
@@ -6050,11 +6386,11 @@ async function runBackupRestore(input) {
6050
6386
  }
6051
6387
 
6052
6388
  // src/commands/status.ts
6053
- import { existsSync as existsSync11, statSync as statSync5 } from "fs";
6054
- import { readFile as readFile22 } from "fs/promises";
6055
- import { join as join34 } from "path";
6389
+ import { existsSync as existsSync12, statSync as statSync5 } from "fs";
6390
+ import { readFile as readFile23 } from "fs/promises";
6391
+ import { join as join35 } from "path";
6056
6392
  async function runStatus(input) {
6057
- if (!existsSync11(input.vault)) {
6393
+ if (!existsSync12(input.vault)) {
6058
6394
  return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { vault: input.vault }) };
6059
6395
  }
6060
6396
  const scan = await scanVault(input.vault);
@@ -6079,7 +6415,7 @@ async function runStatus(input) {
6079
6415
  const compound = scan.data.compound.length;
6080
6416
  let schemaVersion = "v1";
6081
6417
  try {
6082
- const schemaContent = await readFile22(join34(input.vault, "SCHEMA.md"), "utf8");
6418
+ const schemaContent = await readFile23(join35(input.vault, "SCHEMA.md"), "utf8");
6083
6419
  const versionMatch = schemaContent.match(/version:\s*["']?([^"'\s\n]+)/i);
6084
6420
  if (versionMatch) schemaVersion = versionMatch[1];
6085
6421
  } catch {
@@ -6140,7 +6476,7 @@ async function runStatus(input) {
6140
6476
 
6141
6477
  // src/commands/seed.ts
6142
6478
  import { mkdir as mkdir13, writeFile as writeFile14, stat as stat7 } from "fs/promises";
6143
- import { join as join35 } from "path";
6479
+ import { join as join36 } from "path";
6144
6480
  var TODAY = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
6145
6481
  var EXAMPLE_PAGES = {
6146
6482
  "entities/example-project.md": `---
@@ -6209,29 +6545,29 @@ Real sources are immutable after ingestion \u2014 never edit them.
6209
6545
  `;
6210
6546
  async function runSeed(input) {
6211
6547
  try {
6212
- await stat7(join35(input.vault, "SCHEMA.md"));
6548
+ await stat7(join36(input.vault, "SCHEMA.md"));
6213
6549
  } catch {
6214
6550
  return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { root: input.vault, reason: "SCHEMA.md missing \u2014 run `skillwiki init` first" }) };
6215
6551
  }
6216
6552
  const created = [];
6217
6553
  const skipped = [];
6218
6554
  for (const [relPath, content] of Object.entries(EXAMPLE_PAGES)) {
6219
- const absPath = join35(input.vault, relPath);
6555
+ const absPath = join36(input.vault, relPath);
6220
6556
  try {
6221
6557
  await stat7(absPath);
6222
6558
  skipped.push(relPath);
6223
6559
  } catch {
6224
- await mkdir13(join35(absPath, ".."), { recursive: true });
6560
+ await mkdir13(join36(absPath, ".."), { recursive: true });
6225
6561
  await writeFile14(absPath, content, "utf8");
6226
6562
  created.push(relPath);
6227
6563
  }
6228
6564
  }
6229
- const rawPath = join35(input.vault, "raw", "articles", "example-source.md");
6565
+ const rawPath = join36(input.vault, "raw", "articles", "example-source.md");
6230
6566
  try {
6231
6567
  await stat7(rawPath);
6232
6568
  skipped.push("raw/articles/example-source.md");
6233
6569
  } catch {
6234
- await mkdir13(join35(rawPath, ".."), { recursive: true });
6570
+ await mkdir13(join36(rawPath, ".."), { recursive: true });
6235
6571
  await writeFile14(rawPath, EXAMPLE_RAW, "utf8");
6236
6572
  created.push("raw/articles/example-source.md");
6237
6573
  }
@@ -6254,9 +6590,9 @@ async function runSeed(input) {
6254
6590
  }
6255
6591
 
6256
6592
  // src/commands/canvas.ts
6257
- import { readFile as readFile23, writeFile as writeFile15 } from "fs/promises";
6258
- import { existsSync as existsSync12 } from "fs";
6259
- import { join as join36 } from "path";
6593
+ import { readFile as readFile24, writeFile as writeFile15 } from "fs/promises";
6594
+ import { existsSync as existsSync13 } from "fs";
6595
+ import { join as join37 } from "path";
6260
6596
  var NODE_WIDTH = 240;
6261
6597
  var NODE_HEIGHT = 60;
6262
6598
  var COLUMN_SPACING = 400;
@@ -6334,8 +6670,8 @@ function buildCanvasEdges(adjacency) {
6334
6670
  return edges;
6335
6671
  }
6336
6672
  async function runCanvasGenerate(input) {
6337
- const graphPath = input.graphPath ?? join36(input.vault, ".skillwiki", "graph.json");
6338
- if (!existsSync12(graphPath)) {
6673
+ const graphPath = input.graphPath ?? join37(input.vault, ".skillwiki", "graph.json");
6674
+ if (!existsSync13(graphPath)) {
6339
6675
  return {
6340
6676
  exitCode: ExitCode.FILE_NOT_FOUND,
6341
6677
  result: err("FILE_NOT_FOUND", {
@@ -6346,7 +6682,7 @@ async function runCanvasGenerate(input) {
6346
6682
  }
6347
6683
  let raw;
6348
6684
  try {
6349
- raw = await readFile23(graphPath, "utf8");
6685
+ raw = await readFile24(graphPath, "utf8");
6350
6686
  } catch (e) {
6351
6687
  return {
6352
6688
  exitCode: ExitCode.FILE_NOT_FOUND,
@@ -6372,7 +6708,7 @@ async function runCanvasGenerate(input) {
6372
6708
  const nodes = buildCanvasNodes(paths);
6373
6709
  const edges = buildCanvasEdges(graph.adjacency);
6374
6710
  const canvas = { nodes, edges };
6375
- const outPath = join36(input.vault, "vault-graph.canvas");
6711
+ const outPath = join37(input.vault, "vault-graph.canvas");
6376
6712
  try {
6377
6713
  await writeFile15(outPath, JSON.stringify(canvas, null, 2));
6378
6714
  } catch (e) {
@@ -6394,8 +6730,8 @@ written: ${outPath}`
6394
6730
  }
6395
6731
 
6396
6732
  // src/commands/query.ts
6397
- import { readFile as readFile24, stat as stat8 } from "fs/promises";
6398
- import { join as join37 } from "path";
6733
+ import { readFile as readFile25, stat as stat8 } from "fs/promises";
6734
+ import { join as join38 } from "path";
6399
6735
  var W_KEYWORD = 2;
6400
6736
  var W_SOURCE_OVERLAP = 4;
6401
6737
  var W_WIKILINK = 3;
@@ -6516,7 +6852,7 @@ function computeKeywordScore(terms, title, tags, body) {
6516
6852
  return score;
6517
6853
  }
6518
6854
  async function loadOrBuildGraph(vault) {
6519
- const graphPath = join37(vault, ".skillwiki", "graph.json");
6855
+ const graphPath = join38(vault, ".skillwiki", "graph.json");
6520
6856
  let needsBuild = false;
6521
6857
  try {
6522
6858
  const fileStat = await stat8(graphPath);
@@ -6530,7 +6866,7 @@ async function loadOrBuildGraph(vault) {
6530
6866
  if (buildResult.exitCode !== 0) return null;
6531
6867
  }
6532
6868
  try {
6533
- const raw = await readFile24(graphPath, "utf8");
6869
+ const raw = await readFile25(graphPath, "utf8");
6534
6870
  return JSON.parse(raw);
6535
6871
  } catch {
6536
6872
  return null;
@@ -6538,14 +6874,14 @@ async function loadOrBuildGraph(vault) {
6538
6874
  }
6539
6875
 
6540
6876
  // src/utils/auto-commit.ts
6541
- import { existsSync as existsSync13 } from "fs";
6542
- import { join as join38 } from "path";
6877
+ import { existsSync as existsSync14 } from "fs";
6878
+ import { join as join39 } from "path";
6543
6879
  async function postCommit(vault, exitCode) {
6544
6880
  if (exitCode !== 0) return;
6545
6881
  const home = process.env.HOME ?? "";
6546
6882
  const dotenv = await parseDotenvFile(configPath(home));
6547
6883
  if (dotenv["AUTO_COMMIT"] === "false") return;
6548
- if (!existsSync13(join38(vault, ".git"))) return;
6884
+ if (!existsSync14(join39(vault, ".git"))) return;
6549
6885
  const lastOps = readLastOp(vault);
6550
6886
  if (lastOps.length === 0) return;
6551
6887
  const porcelain = git(vault, ["status", "--porcelain"]);
@@ -6596,7 +6932,7 @@ program.command("validate <file>").description("validate vault page frontmatter
6596
6932
  emit(await runValidate({ file, apply: !!opts.apply, vault }), vault);
6597
6933
  });
6598
6934
  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) => {
6599
- const out = opts.out ?? join39(vault, ".skillwiki", "graph.json");
6935
+ const out = opts.out ?? join40(vault, ".skillwiki", "graph.json");
6600
6936
  emit(await runGraphBuild({ vault, out }), vault);
6601
6937
  });
6602
6938
  var canvasCmd = program.command("canvas").description("manage Obsidian canvas files");