skillwiki 0.7.0 → 0.8.1-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
 
10
10
  // src/cli.ts
11
11
  import { readFileSync as readFileSync12 } from "fs";
12
- import { join as join41 } from "path";
12
+ import { join as join43 } from "path";
13
13
  import { Command as Command2 } from "commander";
14
14
 
15
15
  // ../shared/src/exit-codes.ts
@@ -62,7 +62,8 @@ var ExitCode = {
62
62
  BACKUP_RESTORE_CONFLICTS: 45,
63
63
  USAGE: 46,
64
64
  BODY_TRUNCATION_GUARD: 47,
65
- SYNC_LOCK_HELD: 48
65
+ SYNC_LOCK_HELD: 48,
66
+ LOG_APPEND_LOCK_HELD: 49
66
67
  };
67
68
 
68
69
  // ../shared/src/json-output.ts
@@ -2362,10 +2363,111 @@ Chronological action log. Newest entries last. Skill writes append entries; lint
2362
2363
  return { exitCode: ExitCode.OK, result: ok({ entries, threshold: input.threshold, rotated: true, rotated_to: rotatedName, humanHint: `rotated ${entries} entries to ${rotatedName}` }) };
2363
2364
  }
2364
2365
 
2366
+ // src/commands/log-append.ts
2367
+ import { readFile as readFile13, rename as rename4, writeFile as writeFile8, stat as stat6 } from "fs/promises";
2368
+ import { join as join17 } from "path";
2369
+
2370
+ // src/utils/log-lock.ts
2371
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, statSync as statSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
2372
+ import { join as join16 } from "path";
2373
+ function logLockPath(vault) {
2374
+ return join16(vault, ".skillwiki", "log-append.lock");
2375
+ }
2376
+ var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
2377
+ async function acquireLogLock(vault, opts = {}) {
2378
+ const retryMs = opts.retryMs ?? 2e3;
2379
+ const pollMs = opts.pollMs ?? 50;
2380
+ const staleMs = opts.staleMs ?? 1e4;
2381
+ const path = logLockPath(vault);
2382
+ const dir = join16(vault, ".skillwiki");
2383
+ if (!existsSync3(dir)) mkdirSync2(dir, { recursive: true });
2384
+ const deadline = Date.now() + retryMs;
2385
+ const content = JSON.stringify({ pid: process.pid, acquired: (/* @__PURE__ */ new Date()).toISOString() }) + "\n";
2386
+ for (; ; ) {
2387
+ try {
2388
+ writeFileSync2(path, content, { flag: "wx" });
2389
+ return { ok: true };
2390
+ } catch (e) {
2391
+ const err3 = e;
2392
+ if (err3.code !== "EEXIST") throw err3;
2393
+ }
2394
+ try {
2395
+ const age = Date.now() - statSync2(path).mtimeMs;
2396
+ if (age > staleMs) {
2397
+ unlinkSync2(path);
2398
+ continue;
2399
+ }
2400
+ } catch {
2401
+ continue;
2402
+ }
2403
+ if (Date.now() >= deadline) return { ok: false };
2404
+ await sleep(pollMs);
2405
+ }
2406
+ }
2407
+ function releaseLogLock(vault) {
2408
+ try {
2409
+ unlinkSync2(logLockPath(vault));
2410
+ } catch {
2411
+ }
2412
+ }
2413
+
2414
+ // src/commands/log-append.ts
2415
+ var ENTRY_RE2 = /^## \[(\d{4})-\d{2}-\d{2}\]/gm;
2416
+ async function runLogAppend(input) {
2417
+ try {
2418
+ await stat6(join17(input.vault, "SCHEMA.md"));
2419
+ } catch {
2420
+ return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { vault: input.vault }) };
2421
+ }
2422
+ const content = (input.content ?? "").trim();
2423
+ if (content.length === 0) {
2424
+ return { exitCode: ExitCode.USAGE, result: err("USAGE", { message: "--content must be a non-empty log entry" }) };
2425
+ }
2426
+ const acquired = await acquireLogLock(input.vault);
2427
+ if (!acquired.ok) {
2428
+ return { exitCode: ExitCode.LOG_APPEND_LOCK_HELD, result: err("LOG_APPEND_LOCK_HELD", { vault: input.vault }) };
2429
+ }
2430
+ const logPath = join17(input.vault, "log.md");
2431
+ try {
2432
+ let logText;
2433
+ try {
2434
+ logText = await readFile13(logPath, "utf8");
2435
+ } catch {
2436
+ return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path: logPath }) };
2437
+ }
2438
+ const entriesBefore = [...logText.matchAll(ENTRY_RE2)].length;
2439
+ const body = logText.replace(/\s+$/, "");
2440
+ const next = `${body}
2441
+
2442
+ ${content}
2443
+ `;
2444
+ try {
2445
+ const tmp = logPath + ".tmp";
2446
+ await writeFile8(tmp, next, "utf8");
2447
+ await rename4(tmp, logPath);
2448
+ } catch (e) {
2449
+ return { exitCode: ExitCode.WRITE_FAILED, result: err("WRITE_FAILED", { message: String(e) }) };
2450
+ }
2451
+ appendLastOp(input.vault, {
2452
+ operation: "log-append",
2453
+ summary: `appended log entry (${entriesBefore}->${entriesBefore + 1})`,
2454
+ files: ["log.md"],
2455
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2456
+ });
2457
+ const entriesAfter = entriesBefore + 1;
2458
+ return {
2459
+ exitCode: ExitCode.OK,
2460
+ result: ok({ entries_before: entriesBefore, entries_after: entriesAfter, appended: true, humanHint: `appended log entry (${entriesBefore}->${entriesAfter})` })
2461
+ };
2462
+ } finally {
2463
+ releaseLogLock(input.vault);
2464
+ }
2465
+ }
2466
+
2365
2467
  // src/commands/lint.ts
2366
- import { existsSync as existsSync3 } from "fs";
2367
- import { readFile as readFile15 } from "fs/promises";
2368
- import { join as join19 } from "path";
2468
+ import { existsSync as existsSync4 } from "fs";
2469
+ import { readFile as readFile16, rename as rename6 } from "fs/promises";
2470
+ import { join as join21 } from "path";
2369
2471
 
2370
2472
  // src/commands/topic-map-check.ts
2371
2473
  var DEFAULT_THRESHOLD = 200;
@@ -2387,13 +2489,13 @@ async function runTopicMapCheck(input) {
2387
2489
  }
2388
2490
 
2389
2491
  // src/commands/index-link-format.ts
2390
- import { readFile as readFile13 } from "fs/promises";
2391
- import { join as join16 } from "path";
2492
+ import { readFile as readFile14 } from "fs/promises";
2493
+ import { join as join18 } from "path";
2392
2494
  var MD_LINK_RE = /\[[^\[\]]+\]\([^)]+\.md\)/;
2393
2495
  async function runIndexLinkFormat(input) {
2394
2496
  let text = "";
2395
2497
  try {
2396
- text = await readFile13(join16(input.vault, "index.md"), "utf8");
2498
+ text = await readFile14(join18(input.vault, "index.md"), "utf8");
2397
2499
  } catch {
2398
2500
  }
2399
2501
  const markdown_links = [];
@@ -2406,8 +2508,8 @@ ${markdown_links.map((l) => ` line ${l.line}: ${l.text}`).join("\n")}`;
2406
2508
  }
2407
2509
 
2408
2510
  // src/commands/dedup.ts
2409
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, unlinkSync as unlinkSync2 } from "fs";
2410
- import { join as join17 } from "path";
2511
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, unlinkSync as unlinkSync3 } from "fs";
2512
+ import { join as join19 } from "path";
2411
2513
  async function runDedup(input) {
2412
2514
  const scan = await scanVault(input.vault);
2413
2515
  if (!scan.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scan };
@@ -2435,7 +2537,7 @@ async function runDedup(input) {
2435
2537
  }
2436
2538
  }
2437
2539
  for (const page of scan.data.typedKnowledge) {
2438
- const text = readFileSync3(join17(input.vault, page.relPath), "utf-8");
2540
+ const text = readFileSync3(join19(input.vault, page.relPath), "utf-8");
2439
2541
  let updated = text;
2440
2542
  let changed = false;
2441
2543
  for (const [oldPath, newPath] of replacements) {
@@ -2453,14 +2555,14 @@ async function runDedup(input) {
2453
2555
  }
2454
2556
  }
2455
2557
  if (changed) {
2456
- writeFileSync2(join17(input.vault, page.relPath), updated);
2558
+ writeFileSync3(join19(input.vault, page.relPath), updated);
2457
2559
  rewired.push(page.relPath);
2458
2560
  }
2459
2561
  }
2460
2562
  for (const [oldPath] of replacements) {
2461
- const fullPath = join17(input.vault, oldPath);
2563
+ const fullPath = join19(input.vault, oldPath);
2462
2564
  try {
2463
- unlinkSync2(fullPath);
2565
+ unlinkSync3(fullPath);
2464
2566
  removed.push(oldPath);
2465
2567
  } catch {
2466
2568
  }
@@ -2493,9 +2595,9 @@ async function runDedup(input) {
2493
2595
  }
2494
2596
 
2495
2597
  // src/utils/safe-write.ts
2496
- import { open, readFile as readFile14, rename as rename4, unlink as unlink2, writeFile as writeFile8 } from "fs/promises";
2598
+ import { open, readFile as readFile15, rename as rename5, unlink as unlink2, writeFile as writeFile9 } from "fs/promises";
2497
2599
  import { randomBytes } from "crypto";
2498
- import { dirname as dirname7, basename, join as join18 } from "path";
2600
+ import { dirname as dirname7, basename, join as join20 } from "path";
2499
2601
  var DEFAULT_MIN_BODY_RATIO = 0.5;
2500
2602
  var DEFAULT_MIN_OLD_BODY_BYTES = 200;
2501
2603
  function bodyBytes(text) {
@@ -2505,7 +2607,7 @@ function bodyBytes(text) {
2505
2607
  }
2506
2608
  async function readIfExists(absPath) {
2507
2609
  try {
2508
- return await readFile14(absPath, "utf8");
2610
+ return await readFile15(absPath, "utf8");
2509
2611
  } catch (e) {
2510
2612
  if (e.code === "ENOENT") return null;
2511
2613
  throw e;
@@ -2544,7 +2646,7 @@ async function safeWritePage(absPath, newContent, opts = {}) {
2544
2646
  }
2545
2647
  const dir = dirname7(absPath);
2546
2648
  const tmpName = `.${basename(absPath)}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`;
2547
- const tmpPath = join18(dir, tmpName);
2649
+ const tmpPath = join20(dir, tmpName);
2548
2650
  try {
2549
2651
  const handle = await open(tmpPath, "w");
2550
2652
  try {
@@ -2556,7 +2658,7 @@ async function safeWritePage(absPath, newContent, opts = {}) {
2556
2658
  } finally {
2557
2659
  await handle.close();
2558
2660
  }
2559
- await rename4(tmpPath, absPath);
2661
+ await rename5(tmpPath, absPath);
2560
2662
  return ok({ isNew, oldBodyBytes, newBodyBytes, bodyRatio, guardSkippedSmall });
2561
2663
  } catch (e) {
2562
2664
  try {
@@ -2607,6 +2709,60 @@ async function runRawBodyDedup(vault) {
2607
2709
  };
2608
2710
  }
2609
2711
 
2712
+ // src/commands/path-too-long.ts
2713
+ var MAX_PATH_LENGTH = 240;
2714
+ async function runPathTooLong(input) {
2715
+ const scan = await scanVault(input.vault);
2716
+ if (!scan.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scan };
2717
+ const allPages = [...scan.data.typedKnowledge, ...scan.data.raw, ...scan.data.workItems, ...scan.data.compound];
2718
+ const violations = [];
2719
+ for (const page of allPages) {
2720
+ if (page.relPath.length > MAX_PATH_LENGTH) {
2721
+ violations.push({ relPath: page.relPath, length: page.relPath.length });
2722
+ }
2723
+ }
2724
+ if (violations.length > 0) {
2725
+ return {
2726
+ exitCode: ExitCode.LINT_HAS_ERRORS,
2727
+ result: ok({
2728
+ violations,
2729
+ humanHint: violations.map((v) => `${v.relPath}: ${v.length} chars (max ${MAX_PATH_LENGTH})`).join("\n")
2730
+ })
2731
+ };
2732
+ }
2733
+ return { exitCode: ExitCode.OK, result: ok({ violations, humanHint: "all paths within length limit" }) };
2734
+ }
2735
+ function truncateFilename(relPath, maxLength = MAX_PATH_LENGTH) {
2736
+ if (relPath.length <= maxLength) return relPath;
2737
+ const lastSlash = relPath.lastIndexOf("/");
2738
+ const dir = lastSlash >= 0 ? relPath.slice(0, lastSlash) : "";
2739
+ const filename = lastSlash >= 0 ? relPath.slice(lastSlash + 1) : relPath;
2740
+ const hash = computeShortHash(relPath);
2741
+ const ext = filename.endsWith(".md") ? ".md" : "";
2742
+ const base = filename.endsWith(".md") ? filename.slice(0, -3) : filename;
2743
+ const suffix = `-${hash}${ext}`;
2744
+ const dirPrefix = dir ? dir + "/" : "";
2745
+ const maxPrefixLen = maxLength - dirPrefix.length - suffix.length;
2746
+ if (maxPrefixLen <= 0) {
2747
+ const fallback = dirPrefix + hash + ext;
2748
+ if (fallback.length > maxLength) {
2749
+ const dirBudget = maxLength - suffix.length;
2750
+ return dirPrefix.slice(0, Math.max(0, dirBudget)) + suffix;
2751
+ }
2752
+ return fallback;
2753
+ }
2754
+ const prefix = base.slice(0, maxPrefixLen).replace(/[-_\s]+$/, "");
2755
+ return dirPrefix + prefix + suffix;
2756
+ }
2757
+ function computeShortHash(input) {
2758
+ let hash = 2166136261;
2759
+ for (let i = 0; i < input.length; i++) {
2760
+ hash ^= input.charCodeAt(i);
2761
+ hash = Math.imul(hash, 16777619);
2762
+ }
2763
+ return (hash >>> 0).toString(16).padStart(8, "0").slice(0, 8);
2764
+ }
2765
+
2610
2766
  // src/utils/cli-surface.ts
2611
2767
  import { Command } from "commander";
2612
2768
  function buildCliSurface() {
@@ -2639,7 +2795,7 @@ function buildCliSurface() {
2639
2795
  program2.command("config");
2640
2796
  program2.command("doctor");
2641
2797
  program2.command("status").option("--wiki <name>");
2642
- program2.command("archive").option("--wiki <name>");
2798
+ program2.command("archive").option("--wiki <name>").option("--cascade").option("--apply");
2643
2799
  program2.command("drift").option("--apply").option("--new <date>").option("--wiki <name>");
2644
2800
  program2.command("dedup").option("--apply").option("--wiki <name>");
2645
2801
  program2.command("migrate-citations").option("--dry-run").option("--wiki <name>");
@@ -2672,6 +2828,9 @@ function buildCliSurface() {
2672
2828
  syncCmd2.command("status").option("--wiki <name>");
2673
2829
  syncCmd2.command("push").option("--wiki <name>");
2674
2830
  syncCmd2.command("pull").option("--wiki <name>");
2831
+ syncCmd2.command("lock").option("--summary <text>").option("--ttl-minutes <n>").option("--force").option("--wiki <name>");
2832
+ syncCmd2.command("unlock").option("--force").option("--wiki <name>");
2833
+ syncCmd2.command("peers").option("--wiki <name>");
2675
2834
  const backupCmd2 = program2.commands.find((c) => c.name() === "backup");
2676
2835
  backupCmd2.command("sync").option("--dry-run").option("--bucket <name>").option("--endpoint <url>").option("--region <region>").option("--prune").option("--wiki <name>");
2677
2836
  backupCmd2.command("restore").option("--bucket <name>").option("--endpoint <url>").option("--region <region>").option("--target <dir>").option("--wiki <name>");
@@ -2779,7 +2938,7 @@ function extractSourceEntries(rawFm) {
2779
2938
  }
2780
2939
  return entries;
2781
2940
  }
2782
- var ERROR_ORDER = ["broken_wikilinks", "invalid_frontmatter", "raw_dedup", "broken_sources", "tag_not_in_taxonomy"];
2941
+ var ERROR_ORDER = ["broken_wikilinks", "invalid_frontmatter", "raw_dedup", "broken_sources", "tag_not_in_taxonomy", "path_too_long"];
2783
2942
  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"];
2784
2943
  var INFO_ORDER = ["bridges", "page_structure", "topic_map_recommended", "frontmatter_wikilink", "wikilink_citation", "missing_tldr", "stale_sections", "cli_refs"];
2785
2944
  async function runLint(input) {
@@ -2839,6 +2998,8 @@ async function runLint(input) {
2839
2998
  }
2840
2999
  const compoundRefs = await validateCompoundReferences(input.vault);
2841
3000
  if (compoundRefs.ok && compoundRefs.data.length > 0) buckets.compound_refs = compoundRefs.data;
3001
+ const pathCheck = await runPathTooLong({ vault: input.vault });
3002
+ if (pathCheck.result.ok && pathCheck.result.data.violations.length > 0) buckets.path_too_long = pathCheck.result.data.violations;
2842
3003
  const scan = await scanVault(input.vault);
2843
3004
  const allPages = scan.ok ? [...scan.data.typedKnowledge, ...scan.data.raw, ...scan.data.workItems, ...scan.data.compound] : [];
2844
3005
  const slugs = scan.ok ? buildSlugMap(allPages) : /* @__PURE__ */ new Map();
@@ -2900,7 +3061,7 @@ async function runLint(input) {
2900
3061
  let rawPath = entry.replace(/^"/, "").replace(/"$/, "").replace(/^'/, "").replace(/'$/, "");
2901
3062
  rawPath = rawPath.replace(/^\^\[/, "").replace(/\]$/, "");
2902
3063
  if (!rawPath.startsWith("raw/") && !rawPath.startsWith("_archive/raw/")) continue;
2903
- if (!existsSync3(join19(input.vault, rawPath)) && !existsSync3(join19(input.vault, rawPath + ".md")) && !rawPath.startsWith("_archive/") && !existsSync3(join19(input.vault, "_archive", rawPath)) && !existsSync3(join19(input.vault, "_archive", rawPath + ".md"))) {
3064
+ 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"))) {
2904
3065
  brokenSourceFlags.push(`${page.relPath}: ${rawPath}`);
2905
3066
  }
2906
3067
  }
@@ -2991,11 +3152,11 @@ async function runLint(input) {
2991
3152
  const slugMatch = String(entry).match(/\[\[([^\]]+)\]\]/);
2992
3153
  if (!slugMatch) continue;
2993
3154
  const slug = slugMatch[1];
2994
- const knowledgePath = join19(input.vault, "projects", slug, "knowledge.md");
2995
- if (!existsSync3(knowledgePath)) continue;
3155
+ const knowledgePath = join21(input.vault, "projects", slug, "knowledge.md");
3156
+ if (!existsSync4(knowledgePath)) continue;
2996
3157
  const pageRef = page.relPath.replace(/\.md$/, "");
2997
3158
  try {
2998
- const knowledgeContent = await readFile15(knowledgePath, "utf8");
3159
+ const knowledgeContent = await readFile16(knowledgePath, "utf8");
2999
3160
  if (!knowledgeContent.includes(`[[${pageRef}]]`)) {
3000
3161
  orphanedProjectPages.push(`${page.relPath}: not in projects/${slug}/knowledge.md`);
3001
3162
  }
@@ -3006,7 +3167,7 @@ async function runLint(input) {
3006
3167
  if (orphanedProjectPages.length > 0) buckets.orphaned_project_pages = orphanedProjectPages;
3007
3168
  const cliRefFlags = [];
3008
3169
  const cliSurface = buildCliSurface();
3009
- const allScanPages = [...scan.data.typedKnowledge, ...scan.data.raw, ...scan.data.workItems, ...scan.data.compound];
3170
+ const allScanPages = [...scan.data.typedKnowledge];
3010
3171
  for (const page of allScanPages) {
3011
3172
  const text = await readPage(page);
3012
3173
  const violations = validateCliRefs(text, page.relPath, cliSurface);
@@ -3042,7 +3203,7 @@ async function runLint(input) {
3042
3203
  for (const relPath of legacyPages) {
3043
3204
  try {
3044
3205
  const absPath = `${input.vault}/${relPath}`;
3045
- const raw = await readFile15(absPath, "utf8");
3206
+ const raw = await readFile16(absPath, "utf8");
3046
3207
  const split = splitFrontmatter(raw);
3047
3208
  if (!split.ok) {
3048
3209
  unresolved.push(relPath);
@@ -3141,7 +3302,7 @@ ${newBody}`;
3141
3302
  for (const relPath of noOverview) {
3142
3303
  try {
3143
3304
  const absPath = `${input.vault}/${relPath}`;
3144
- const raw = await readFile15(absPath, "utf8");
3305
+ const raw = await readFile16(absPath, "utf8");
3145
3306
  const split = splitFrontmatter(raw);
3146
3307
  if (!split.ok) {
3147
3308
  unresolved.push(relPath);
@@ -3182,7 +3343,7 @@ ${trimmedBody}`;
3182
3343
  for (const relPath of missingTldrFlags) {
3183
3344
  try {
3184
3345
  const absPath = `${input.vault}/${relPath}`;
3185
- const raw = await readFile15(absPath, "utf8");
3346
+ const raw = await readFile16(absPath, "utf8");
3186
3347
  const split = splitFrontmatter(raw);
3187
3348
  if (!split.ok) {
3188
3349
  unresolved.push(relPath);
@@ -3232,7 +3393,7 @@ ${lines.join("\n")}`;
3232
3393
  for (const relPath of wikilinkCitationFlags) {
3233
3394
  try {
3234
3395
  const absPath = `${input.vault}/${relPath}`;
3235
- const raw = await readFile15(absPath, "utf8");
3396
+ const raw = await readFile16(absPath, "utf8");
3236
3397
  const split = splitFrontmatter(raw);
3237
3398
  if (!split.ok) {
3238
3399
  unresolved.push(relPath);
@@ -3319,7 +3480,7 @@ ${newBody}`;
3319
3480
  for (const relPath of fileSourceUrlFlags) {
3320
3481
  try {
3321
3482
  const absPath = `${input.vault}/${relPath}`;
3322
- const raw = await readFile15(absPath, "utf8");
3483
+ const raw = await readFile16(absPath, "utf8");
3323
3484
  const parts = raw.split("---", 3);
3324
3485
  if (parts.length < 3) {
3325
3486
  unresolved.push(relPath);
@@ -3353,6 +3514,45 @@ ${newBody}`;
3353
3514
  else delete buckets.file_source_url;
3354
3515
  }
3355
3516
  }
3517
+ const pathViolations = buckets.path_too_long;
3518
+ if (input.fix && pathViolations && pathViolations.length > 0) {
3519
+ const pathFixed = [];
3520
+ for (const v of pathViolations) {
3521
+ try {
3522
+ const absPath = `${input.vault}/${v.relPath}`;
3523
+ const newRelPath = truncateFilename(v.relPath);
3524
+ const newAbsPath = `${input.vault}/${newRelPath}`;
3525
+ await rename6(absPath, newAbsPath);
3526
+ const oldPathEscaped = v.relPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3527
+ for (const page of allPages) {
3528
+ if (page.relPath === v.relPath) continue;
3529
+ const content = await readFile16(page.absPath, "utf8");
3530
+ if (!content.includes(v.relPath)) continue;
3531
+ let updated = content;
3532
+ const citationRe = new RegExp(`\\^\\[${oldPathEscaped}\\]`, "g");
3533
+ updated = updated.replace(citationRe, `^[${newRelPath}]`);
3534
+ const wikilinkRe = new RegExp(`\\[\\[${oldPathEscaped}(\\|[^\\]]*)?\\]\\]`, "g");
3535
+ updated = updated.replace(wikilinkRe, (_m, alias) => `[[${newRelPath}${alias ?? ""}]]`);
3536
+ if (updated !== content) {
3537
+ const w = await safeWritePage(page.absPath, updated);
3538
+ if (!w.ok) {
3539
+ unresolved.push(`${page.relPath} (rewire)`);
3540
+ }
3541
+ }
3542
+ }
3543
+ pathFixed.push(v.relPath);
3544
+ } catch {
3545
+ unresolved.push(v.relPath);
3546
+ }
3547
+ }
3548
+ fixed.push(...pathFixed);
3549
+ if (pathFixed.length > 0) {
3550
+ const fixedSet = new Set(pathFixed);
3551
+ const remaining = pathViolations.filter((v) => !fixedSet.has(v.relPath));
3552
+ if (remaining.length > 0) buckets.path_too_long = remaining;
3553
+ else delete buckets.path_too_long;
3554
+ }
3555
+ }
3356
3556
  }
3357
3557
  const errorOut = ERROR_ORDER.flatMap((k) => buckets[k] ? [{ kind: k, items: buckets[k] }] : []);
3358
3558
  const warningOut = WARNING_ORDER.flatMap((k) => buckets[k] ? [{ kind: k, items: buckets[k] }] : []);
@@ -3428,14 +3628,14 @@ ${match.length === 0 ? "0 violations" : match.map((b) => ` ${b.kind}: ${b.items
3428
3628
  }
3429
3629
 
3430
3630
  // src/commands/config.ts
3431
- import { readFile as readFile16 } from "fs/promises";
3432
- import { existsSync as existsSync4 } from "fs";
3433
- import { join as join20 } from "path";
3631
+ import { readFile as readFile17 } from "fs/promises";
3632
+ import { existsSync as existsSync5 } from "fs";
3633
+ import { join as join22 } from "path";
3434
3634
  function validateKey(key) {
3435
3635
  return CONFIG_KEYS.includes(key) || isValidWikiProfileKey(key);
3436
3636
  }
3437
3637
  function configPath(home) {
3438
- return join20(home, ".skillwiki", ".env");
3638
+ return join22(home, ".skillwiki", ".env");
3439
3639
  }
3440
3640
  async function runConfigGet(input) {
3441
3641
  if (!validateKey(input.key)) {
@@ -3453,7 +3653,7 @@ async function runConfigSet(input) {
3453
3653
  try {
3454
3654
  let originalContent;
3455
3655
  try {
3456
- originalContent = await readFile16(filePath, "utf8");
3656
+ originalContent = await readFile17(filePath, "utf8");
3457
3657
  } catch {
3458
3658
  }
3459
3659
  const existing = originalContent !== void 0 ? parseDotenvText(originalContent) : {};
@@ -3485,18 +3685,18 @@ async function runConfigList(input) {
3485
3685
  }
3486
3686
  async function runConfigPath(input) {
3487
3687
  const filePath = configPath(input.home);
3488
- return { exitCode: ExitCode.OK, result: ok({ path: filePath, exists: existsSync4(filePath), humanHint: filePath }) };
3688
+ return { exitCode: ExitCode.OK, result: ok({ path: filePath, exists: existsSync5(filePath), humanHint: filePath }) };
3489
3689
  }
3490
3690
 
3491
3691
  // src/commands/doctor.ts
3492
- import { existsSync as existsSync7, lstatSync, readlinkSync, readdirSync, statSync as statSync2, readFileSync as readFileSync7 } from "fs";
3493
- import { join as join24, resolve as resolve4 } from "path";
3692
+ import { existsSync as existsSync8, lstatSync, readlinkSync, readdirSync, statSync as statSync3, readFileSync as readFileSync7 } from "fs";
3693
+ import { join as join26, resolve as resolve4 } from "path";
3494
3694
  import { execSync as execSync2 } from "child_process";
3495
3695
  import { platform as platform2 } from "os";
3496
3696
 
3497
3697
  // src/utils/auto-update.ts
3498
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync5, mkdirSync as mkdirSync2 } from "fs";
3499
- import { join as join21, dirname as dirname8 } from "path";
3698
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
3699
+ import { join as join23, dirname as dirname8 } from "path";
3500
3700
  import { spawn } from "child_process";
3501
3701
 
3502
3702
  // src/utils/update-consts.ts
@@ -3507,7 +3707,7 @@ var CLI_DISABLE_FLAG = "--no-update-notifier";
3507
3707
 
3508
3708
  // src/utils/auto-update.ts
3509
3709
  function cachePath(home) {
3510
- return join21(home, ".skillwiki", CACHE_FILENAME);
3710
+ return join23(home, ".skillwiki", CACHE_FILENAME);
3511
3711
  }
3512
3712
  function readCacheRaw(home) {
3513
3713
  try {
@@ -3526,8 +3726,8 @@ function readCache(home) {
3526
3726
  }
3527
3727
  function writeCache(home, cache) {
3528
3728
  const p = cachePath(home);
3529
- mkdirSync2(dirname8(p), { recursive: true });
3530
- writeFileSync3(p, JSON.stringify(cache, null, 2));
3729
+ mkdirSync3(dirname8(p), { recursive: true });
3730
+ writeFileSync4(p, JSON.stringify(cache, null, 2));
3531
3731
  }
3532
3732
  function latestFromCache(home, currentVersion) {
3533
3733
  const { cache } = readCache(home);
@@ -3545,7 +3745,7 @@ function triggerAutoUpdate(home, currentVersion) {
3545
3745
  const { isStale: isStale2 } = readCache(home);
3546
3746
  if (!isStale2) return;
3547
3747
  const bgScript = new URL("../auto-update-bg.js", import.meta.url).pathname;
3548
- if (!existsSync5(bgScript)) return;
3748
+ if (!existsSync6(bgScript)) return;
3549
3749
  const child = spawn(process.execPath, [bgScript, home, currentVersion], {
3550
3750
  detached: true,
3551
3751
  stdio: "ignore"
@@ -3557,12 +3757,12 @@ function triggerAutoUpdate(home, currentVersion) {
3557
3757
 
3558
3758
  // src/utils/plugin-registry.ts
3559
3759
  import { readFileSync as readFileSync5 } from "fs";
3560
- import { join as join22 } from "path";
3561
- var REGISTRY_PATH = join22(".claude", "plugins", "installed_plugins.json");
3760
+ import { join as join24 } from "path";
3761
+ var REGISTRY_PATH = join24(".claude", "plugins", "installed_plugins.json");
3562
3762
  var PLUGIN_KEY = "skillwiki@llm-wiki";
3563
3763
  function readInstalledPlugins(home) {
3564
3764
  try {
3565
- const raw = readFileSync5(join22(home, REGISTRY_PATH), "utf8");
3765
+ const raw = readFileSync5(join24(home, REGISTRY_PATH), "utf8");
3566
3766
  return JSON.parse(raw);
3567
3767
  } catch {
3568
3768
  return null;
@@ -3579,8 +3779,8 @@ function findPlugin(home, key = PLUGIN_KEY) {
3579
3779
  // src/utils/s3-mount-health.ts
3580
3780
  import { execSync } from "child_process";
3581
3781
  import { platform } from "os";
3582
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, unlinkSync as unlinkSync3, readFileSync as readFile17 } from "fs";
3583
- import { join as join23 } from "path";
3782
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, unlinkSync as unlinkSync4, readFileSync as readFile18 } from "fs";
3783
+ import { join as join25 } from "path";
3584
3784
  var OS = platform();
3585
3785
  function findRcloneMountPid() {
3586
3786
  try {
@@ -3738,35 +3938,35 @@ function detectFuseMount(vaultPath) {
3738
3938
  return null;
3739
3939
  }
3740
3940
  function writeTest(dir) {
3741
- const testFile = join23(dir, `.doctor-write-test-${process.pid}.tmp`);
3941
+ const testFile = join25(dir, `.doctor-write-test-${process.pid}.tmp`);
3742
3942
  const payload = `skillwiki doctor write test \u2014 ${Date.now()} \u2014 ${Math.random().toString(36).slice(2)}`;
3743
3943
  const start = Date.now();
3744
3944
  try {
3745
- writeFileSync4(testFile, payload, "utf8");
3945
+ writeFileSync5(testFile, payload, "utf8");
3746
3946
  } catch (e) {
3747
3947
  return { success: false, writeMs: Date.now() - start, readMs: 0, size: 0, error: `write failed: ${e.message}` };
3748
3948
  }
3749
3949
  const writeMs = Date.now() - start;
3750
3950
  const readStart = Date.now();
3751
3951
  try {
3752
- const back = readFile17(testFile, "utf8");
3952
+ const back = readFile18(testFile, "utf8");
3753
3953
  const readMs = Date.now() - readStart;
3754
3954
  if (back !== payload) {
3755
3955
  try {
3756
- unlinkSync3(testFile);
3956
+ unlinkSync4(testFile);
3757
3957
  } catch {
3758
3958
  }
3759
3959
  return { success: false, writeMs, readMs, size: Buffer.byteLength(payload, "utf8"), error: "content mismatch \u2014 wrote and read-back differ" };
3760
3960
  }
3761
3961
  } catch (e) {
3762
3962
  try {
3763
- unlinkSync3(testFile);
3963
+ unlinkSync4(testFile);
3764
3964
  } catch {
3765
3965
  }
3766
3966
  return { success: false, writeMs, readMs: Date.now() - readStart, size: 0, error: `read failed: ${e.message}` };
3767
3967
  }
3768
3968
  try {
3769
- unlinkSync3(testFile);
3969
+ unlinkSync4(testFile);
3770
3970
  } catch {
3771
3971
  }
3772
3972
  return { success: true, writeMs, readMs: Date.now() - readStart, size: Buffer.byteLength(payload, "utf8") };
@@ -3807,13 +4007,13 @@ function detectCliChannels(argv, home) {
3807
4007
  }
3808
4008
  const plugin = findPlugin(home);
3809
4009
  if (plugin) {
3810
- const pluginBin = join24(plugin.installPath, "bin", "skillwiki");
3811
- if (existsSync7(pluginBin)) {
4010
+ const pluginBin = join26(plugin.installPath, "bin", "skillwiki");
4011
+ if (existsSync8(pluginBin)) {
3812
4012
  channels.push({ name: "plugin", path: pluginBin, isDevLink: false });
3813
4013
  }
3814
4014
  }
3815
- const installBin = join24(home, ".claude", "skills", "bin", "skillwiki");
3816
- if (existsSync7(installBin)) {
4015
+ const installBin = join26(home, ".claude", "skills", "bin", "skillwiki");
4016
+ if (existsSync8(installBin)) {
3817
4017
  channels.push({ name: "install", path: installBin, isDevLink: false });
3818
4018
  }
3819
4019
  return channels;
@@ -3865,7 +4065,7 @@ function checkCliChannels(argv, home) {
3865
4065
  }
3866
4066
  async function checkConfigFile(home) {
3867
4067
  const cfgPath = configPath(home);
3868
- if (!existsSync7(cfgPath)) {
4068
+ if (!existsSync8(cfgPath)) {
3869
4069
  return check("warn", "config_file", "Config file exists", `${cfgPath} not found`);
3870
4070
  }
3871
4071
  try {
@@ -3880,7 +4080,7 @@ function checkWikiPathExists(resolvedPath) {
3880
4080
  if (resolvedPath === void 0) {
3881
4081
  return check("error", "wiki_path_exists", "Vault directory exists", "Cannot check \u2014 WIKI_PATH not resolved");
3882
4082
  }
3883
- if (existsSync7(resolvedPath) && statSync2(resolvedPath).isDirectory()) {
4083
+ if (existsSync8(resolvedPath) && statSync3(resolvedPath).isDirectory()) {
3884
4084
  return check("pass", "wiki_path_exists", "Vault directory exists", resolvedPath);
3885
4085
  }
3886
4086
  return check("error", "wiki_path_exists", "Vault directory exists", `${resolvedPath} does not exist or is not a directory`);
@@ -3889,13 +4089,13 @@ function checkVaultStructure(resolvedPath) {
3889
4089
  if (resolvedPath === void 0) {
3890
4090
  return check("error", "vault_structure", "Vault structure valid", "Cannot check \u2014 WIKI_PATH not resolved");
3891
4091
  }
3892
- if (!existsSync7(resolvedPath)) {
4092
+ if (!existsSync8(resolvedPath)) {
3893
4093
  return check("error", "vault_structure", "Vault structure valid", "Cannot check \u2014 vault directory does not exist");
3894
4094
  }
3895
4095
  const missing = [];
3896
- if (!existsSync7(join24(resolvedPath, "SCHEMA.md"))) missing.push("SCHEMA.md");
4096
+ if (!existsSync8(join26(resolvedPath, "SCHEMA.md"))) missing.push("SCHEMA.md");
3897
4097
  for (const dir of ["raw", "entities", "concepts", "meta"]) {
3898
- if (!existsSync7(join24(resolvedPath, dir))) missing.push(dir + "/");
4098
+ if (!existsSync8(join26(resolvedPath, dir))) missing.push(dir + "/");
3899
4099
  }
3900
4100
  if (missing.length === 0) {
3901
4101
  return check("pass", "vault_structure", "Vault structure valid", "All required files and directories present");
@@ -3903,8 +4103,8 @@ function checkVaultStructure(resolvedPath) {
3903
4103
  return check("warn", "vault_structure", "Vault structure valid", `Missing: ${missing.join(", ")} \u2014 run \`skillwiki init\` to add CodeWiki structure`);
3904
4104
  }
3905
4105
  function checkSkillsInstalled(home, cwd) {
3906
- const srcDir = cwd ? join24(cwd, "packages", "skills") : void 0;
3907
- if (srcDir && existsSync7(srcDir)) {
4106
+ const srcDir = cwd ? join26(cwd, "packages", "skills") : void 0;
4107
+ if (srcDir && existsSync8(srcDir)) {
3908
4108
  const found = findSkillMd(srcDir);
3909
4109
  if (found.length > 0) {
3910
4110
  return check("pass", "skills_installed", "Skills installed", `${found.length} SKILL.md file(s) found (source)`);
@@ -3917,8 +4117,8 @@ function checkSkillsInstalled(home, cwd) {
3917
4117
  return check("pass", "skills_installed", "Skills installed", `${found.length} SKILL.md file(s) found (plugin v${plugin.version})`);
3918
4118
  }
3919
4119
  }
3920
- const skillsDir = join24(home, ".claude", "skills");
3921
- if (existsSync7(skillsDir)) {
4120
+ const skillsDir = join26(home, ".claude", "skills");
4121
+ if (existsSync8(skillsDir)) {
3922
4122
  const found = findSkillMd(skillsDir);
3923
4123
  if (found.length > 0) {
3924
4124
  return check("pass", "skills_installed", "Skills installed", `${found.length} SKILL.md file(s) found (CLI install)`);
@@ -3928,10 +4128,10 @@ function checkSkillsInstalled(home, cwd) {
3928
4128
  }
3929
4129
  function checkDuplicateSkills(home) {
3930
4130
  const plugin = findPlugin(home);
3931
- const skillsDir = join24(home, ".claude", "skills");
4131
+ const skillsDir = join26(home, ".claude", "skills");
3932
4132
  const agentSkillDirs = [
3933
- { label: "~/.codex/skills/", path: join24(home, ".codex", "skills") },
3934
- { label: "~/.agents/skills/", path: join24(home, ".agents", "skills") }
4133
+ { label: "~/.codex/skills/", path: join26(home, ".codex", "skills") },
4134
+ { label: "~/.agents/skills/", path: join26(home, ".agents", "skills") }
3935
4135
  ];
3936
4136
  if (!plugin) {
3937
4137
  return check("pass", "skills_duplicate", "Skills not duplicated", "Single install channel");
@@ -4008,8 +4208,8 @@ async function checkProfiles(home) {
4008
4208
  }
4009
4209
  async function checkProjectLocalOverride(cwd) {
4010
4210
  const dir = cwd ?? process.cwd();
4011
- const envPath = join24(dir, ".skillwiki", ".env");
4012
- if (existsSync7(envPath)) {
4211
+ const envPath = join26(dir, ".skillwiki", ".env");
4212
+ if (existsSync8(envPath)) {
4013
4213
  return check("pass", "project_local", "Project-local config", `Found: ${envPath}`);
4014
4214
  }
4015
4215
  return check("pass", "project_local", "Project-local config", "None");
@@ -4018,7 +4218,7 @@ function checkVaultGitRemote(resolvedPath) {
4018
4218
  if (resolvedPath === void 0) {
4019
4219
  return check("error", "vault_git_remote", "Vault git remote", "Cannot check \u2014 WIKI_PATH not resolved");
4020
4220
  }
4021
- if (!existsSync7(join24(resolvedPath, ".git"))) {
4221
+ if (!existsSync8(join26(resolvedPath, ".git"))) {
4022
4222
  return check("warn", "vault_git_remote", "Vault git remote", "Vault is not a git repository \u2014 sync features unavailable");
4023
4223
  }
4024
4224
  try {
@@ -4041,9 +4241,9 @@ function checkObsidianTemplates(resolvedPath) {
4041
4241
  return check("error", "obsidian_templates", "Obsidian templates", "Cannot check \u2014 WIKI_PATH not resolved");
4042
4242
  }
4043
4243
  const missing = [];
4044
- if (!existsSync7(join24(resolvedPath, "_Templates"))) missing.push("_Templates/");
4045
- if (!existsSync7(join24(resolvedPath, ".obsidian", "templates.json"))) missing.push(".obsidian/templates.json");
4046
- if (!existsSync7(join24(resolvedPath, ".obsidian", "app.json"))) missing.push(".obsidian/app.json");
4244
+ if (!existsSync8(join26(resolvedPath, "_Templates"))) missing.push("_Templates/");
4245
+ if (!existsSync8(join26(resolvedPath, ".obsidian", "templates.json"))) missing.push(".obsidian/templates.json");
4246
+ if (!existsSync8(join26(resolvedPath, ".obsidian", "app.json"))) missing.push(".obsidian/app.json");
4047
4247
  if (missing.length === 0) {
4048
4248
  return check("pass", "obsidian_templates", "Obsidian templates", "Template folder and config present");
4049
4249
  }
@@ -4053,8 +4253,8 @@ function checkDotStoreClean(resolvedPath) {
4053
4253
  if (resolvedPath === void 0) {
4054
4254
  return check("error", "dsstore_clean", "No .DS_Store in raw/", "Cannot check \u2014 WIKI_PATH not resolved");
4055
4255
  }
4056
- const rawDir = join24(resolvedPath, "raw");
4057
- if (!existsSync7(rawDir)) {
4256
+ const rawDir = join26(resolvedPath, "raw");
4257
+ if (!existsSync8(rawDir)) {
4058
4258
  return check("pass", "dsstore_clean", "No .DS_Store in raw/", "raw/ directory not found \u2014 check skipped");
4059
4259
  }
4060
4260
  const found = [];
@@ -4069,7 +4269,7 @@ function checkDotStoreClean(resolvedPath) {
4069
4269
  if (entry.name === ".DS_Store") {
4070
4270
  found.push(rel ? `${rel}/.DS_Store` : ".DS_Store");
4071
4271
  } else if (entry.isDirectory()) {
4072
- walk2(join24(dir, entry.name), rel ? `${rel}/${entry.name}` : entry.name);
4272
+ walk2(join26(dir, entry.name), rel ? `${rel}/${entry.name}` : entry.name);
4073
4273
  }
4074
4274
  }
4075
4275
  })(rawDir, "");
@@ -4082,7 +4282,7 @@ function checkSyncLastPush(resolvedPath) {
4082
4282
  if (resolvedPath === void 0) {
4083
4283
  return check("error", "sync_last_push", "Vault sync recency", "Cannot check \u2014 WIKI_PATH not resolved");
4084
4284
  }
4085
- if (!existsSync7(join24(resolvedPath, ".git"))) {
4285
+ if (!existsSync8(join26(resolvedPath, ".git"))) {
4086
4286
  return check("pass", "sync_last_push", "Vault sync recency", "No git repo \u2014 sync check skipped");
4087
4287
  }
4088
4288
  let timestamp;
@@ -4123,8 +4323,8 @@ function checkS3MountPerf(resolvedPath) {
4123
4323
  return check("pass", "s3_mount_perf", "S3 mount performance", "local disk");
4124
4324
  }
4125
4325
  const mountPoint = fuse.mountPoint;
4126
- const conceptsDir = join24(resolvedPath, "concepts");
4127
- if (!existsSync7(conceptsDir)) {
4326
+ const conceptsDir = join26(resolvedPath, "concepts");
4327
+ if (!existsSync8(conceptsDir)) {
4128
4328
  return check("pass", "s3_mount_perf", "S3 mount performance", `S3 FUSE mount (${mountPoint}), no concepts/ to benchmark`);
4129
4329
  }
4130
4330
  const start = Date.now();
@@ -4242,8 +4442,8 @@ function checkWriteTest(resolvedPath) {
4242
4442
  if (!fuse) {
4243
4443
  return check("pass", "s3_write_test", "S3 write test", "local disk \u2014 check skipped");
4244
4444
  }
4245
- const conceptsDir = join24(resolvedPath, "concepts");
4246
- if (!existsSync7(conceptsDir)) {
4445
+ const conceptsDir = join26(resolvedPath, "concepts");
4446
+ if (!existsSync8(conceptsDir)) {
4247
4447
  return check("pass", "s3_write_test", "S3 write test", "no concepts/ dir to test \u2014 check skipped");
4248
4448
  }
4249
4449
  const result = writeTest(conceptsDir);
@@ -4329,7 +4529,7 @@ function checkVfsCacheHealth(resolvedPath) {
4329
4529
  }
4330
4530
  function readVaultSyncConfig(home) {
4331
4531
  try {
4332
- const content = readFileSync7(join24(home, ".skillwiki", ".env"), "utf8");
4532
+ const content = readFileSync7(join26(home, ".skillwiki", ".env"), "utf8");
4333
4533
  let installed = false;
4334
4534
  let role;
4335
4535
  for (const line of content.split(/\r?\n/)) {
@@ -4363,12 +4563,12 @@ function vaultSyncChecks(input) {
4363
4563
  ];
4364
4564
  }
4365
4565
  const isMac = os === "darwin";
4366
- const logDir = input.logDir ?? (isMac ? join24(home, "Library", "Logs") : join24(home, ".local", "state", "vault-sync", "log"));
4367
- const shareDir = input.shareDir ?? (isMac ? join24(home, "Library", "Application Support", "vault-sync", "bin") : join24(home, ".local", "share", "vault-sync", "bin"));
4368
- const filterPath = input.filterPath ?? join24(home, ".config", "rclone", "wiki-push-filters.txt");
4566
+ const logDir = input.logDir ?? (isMac ? join26(home, "Library", "Logs") : join26(home, ".local", "state", "vault-sync", "log"));
4567
+ const shareDir = input.shareDir ?? (isMac ? join26(home, "Library", "Application Support", "vault-sync", "bin") : join26(home, ".local", "share", "vault-sync", "bin"));
4568
+ const filterPath = input.filterPath ?? join26(home, ".config", "rclone", "wiki-push-filters.txt");
4369
4569
  const snapshotPath = input.snapshotScriptPath ?? "/root/.hermes/scripts/wiki-snapshot-v3.sh";
4370
- const pushScriptPath = join24(shareDir, "wiki-push.sh");
4371
- const c1 = existsSync7(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`);
4570
+ const pushScriptPath = join26(shareDir, "wiki-push.sh");
4571
+ 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`);
4372
4572
  let c2;
4373
4573
  try {
4374
4574
  if (isMac) {
@@ -4419,7 +4619,7 @@ function vaultSyncChecks(input) {
4419
4619
  "Scheduler check failed \u2014 run vault-sync-install"
4420
4620
  );
4421
4621
  }
4422
- const logFile = join24(logDir, "wiki-push.log");
4622
+ const logFile = join26(logDir, "wiki-push.log");
4423
4623
  let c3;
4424
4624
  try {
4425
4625
  const logContent = readFileSync7(logFile, "utf8");
@@ -4478,7 +4678,7 @@ function vaultSyncChecks(input) {
4478
4678
  }
4479
4679
  }
4480
4680
  } catch {
4481
- c3 = existsSync7(logDir) ? check(
4681
+ c3 = existsSync8(logDir) ? check(
4482
4682
  "warn",
4483
4683
  "vault_sync_last_push_age",
4484
4684
  "Vault sync last push recency",
@@ -4490,7 +4690,7 @@ function vaultSyncChecks(input) {
4490
4690
  `Log directory not found at ${logDir}`
4491
4691
  );
4492
4692
  }
4493
- const fetchLogFile = join24(logDir, "wiki-fetch.log");
4693
+ const fetchLogFile = join26(logDir, "wiki-fetch.log");
4494
4694
  let cFetch;
4495
4695
  try {
4496
4696
  const logContent = readFileSync7(fetchLogFile, "utf8");
@@ -4537,7 +4737,7 @@ function vaultSyncChecks(input) {
4537
4737
  }
4538
4738
  let c4;
4539
4739
  try {
4540
- if (!existsSync7(filterPath)) {
4740
+ if (!existsSync8(filterPath)) {
4541
4741
  c4 = check(
4542
4742
  "error",
4543
4743
  "vault_sync_filter_present",
@@ -4586,7 +4786,7 @@ function vaultSyncChecks(input) {
4586
4786
  );
4587
4787
  } else {
4588
4788
  try {
4589
- if (!existsSync7(snapshotPath)) {
4789
+ if (!existsSync8(snapshotPath)) {
4590
4790
  c5 = check(
4591
4791
  "error",
4592
4792
  "vault_sync_snapshot_guard",
@@ -4632,9 +4832,9 @@ function findSkillMd(dir) {
4632
4832
  }
4633
4833
  for (const entry of entries) {
4634
4834
  if (entry.isFile() && entry.name === "SKILL.md") {
4635
- results.push(join24(dir, entry.name));
4835
+ results.push(join26(dir, entry.name));
4636
4836
  } else if (entry.isDirectory()) {
4637
- results.push(...findSkillMd(join24(dir, entry.name)));
4837
+ results.push(...findSkillMd(join26(dir, entry.name)));
4638
4838
  }
4639
4839
  }
4640
4840
  return results;
@@ -4648,7 +4848,7 @@ function findSkillNames(dir) {
4648
4848
  return results;
4649
4849
  }
4650
4850
  for (const entry of entries) {
4651
- if (entry.isDirectory() && existsSync7(join24(dir, entry.name, "SKILL.md"))) {
4851
+ if (entry.isDirectory() && existsSync8(join26(dir, entry.name, "SKILL.md"))) {
4652
4852
  results.push(entry.name);
4653
4853
  }
4654
4854
  }
@@ -4712,8 +4912,8 @@ async function runDoctor(input) {
4712
4912
  }
4713
4913
 
4714
4914
  // src/commands/archive.ts
4715
- import { rename as rename5, mkdir as mkdir8, readFile as readFile18, writeFile as writeFile9 } from "fs/promises";
4716
- import { join as join25, dirname as dirname9 } from "path";
4915
+ import { rename as rename7, mkdir as mkdir8, readFile as readFile19, writeFile as writeFile10 } from "fs/promises";
4916
+ import { join as join27, dirname as dirname9 } from "path";
4717
4917
  function countWikilinks(body, slug) {
4718
4918
  const escaped = slug.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4719
4919
  const re = new RegExp(`\\[\\[${escaped}(?:[|#][^\\]]*)?\\]\\]`, "g");
@@ -4741,7 +4941,7 @@ async function runArchive(input) {
4741
4941
  if (!relPath) return { exitCode: ExitCode.ARCHIVE_TARGET_NOT_FOUND, result: err("ARCHIVE_TARGET_NOT_FOUND", { page: input.page }) };
4742
4942
  if (relPath.startsWith("_archive/")) return { exitCode: ExitCode.ARCHIVE_ALREADY_ARCHIVED, result: err("ARCHIVE_ALREADY_ARCHIVED", { page: relPath }) };
4743
4943
  const slug = relPath.replace(/\.md$/, "").split("/").pop();
4744
- const archivePath = join25("_archive", relPath).replace(/\\/g, "/");
4944
+ const archivePath = join27("_archive", relPath).replace(/\\/g, "/");
4745
4945
  let cascade;
4746
4946
  if (input.cascade) {
4747
4947
  const wikilinkRefs = [];
@@ -4765,7 +4965,7 @@ async function runArchive(input) {
4765
4965
  const indexRefs = [];
4766
4966
  if (!isRaw) {
4767
4967
  try {
4768
- const idx = await readFile18(join25(input.vault, "index.md"), "utf8");
4968
+ const idx = await readFile19(join27(input.vault, "index.md"), "utf8");
4769
4969
  idx.split("\n").forEach((line, i) => {
4770
4970
  if (line.includes(`[[${slug}]]`)) indexRefs.push({ line: i + 1, text: line });
4771
4971
  });
@@ -4791,8 +4991,8 @@ async function runArchive(input) {
4791
4991
  }
4792
4992
  if (input.cascade && input.apply && cascade) {
4793
4993
  for (const ref of cascade.source_array_refs) {
4794
- const absPath = join25(input.vault, ref.page);
4795
- const text = await readFile18(absPath, "utf8");
4994
+ const absPath = join27(input.vault, ref.page);
4995
+ const text = await readFile19(absPath, "utf8");
4796
4996
  const split = splitFrontmatter(text);
4797
4997
  if (!split.ok) continue;
4798
4998
  const before = split.data.rawFrontmatter;
@@ -4803,29 +5003,29 @@ async function runArchive(input) {
4803
5003
  );
4804
5004
  if (fmRewritten === before) continue;
4805
5005
  if (!arraysEqual(ref.sources_after, ref.sources_before)) {
4806
- await writeFile9(absPath, `---
5006
+ await writeFile10(absPath, `---
4807
5007
  ${fmRewritten}
4808
5008
  ---${split.data.body}`, "utf8");
4809
5009
  }
4810
5010
  }
4811
5011
  }
4812
- await mkdir8(dirname9(join25(input.vault, archivePath)), { recursive: true });
5012
+ await mkdir8(dirname9(join27(input.vault, archivePath)), { recursive: true });
4813
5013
  let indexUpdated = false;
4814
5014
  if (!isRaw) {
4815
- const indexPath = join25(input.vault, "index.md");
5015
+ const indexPath = join27(input.vault, "index.md");
4816
5016
  try {
4817
- const idx = await readFile18(indexPath, "utf8");
5017
+ const idx = await readFile19(indexPath, "utf8");
4818
5018
  const originalLines = idx.split("\n");
4819
5019
  const filtered = originalLines.filter((l) => !l.includes(`[[${slug}]]`));
4820
5020
  if (filtered.length !== originalLines.length) {
4821
- await writeFile9(indexPath, filtered.join("\n"), "utf8");
5021
+ await writeFile10(indexPath, filtered.join("\n"), "utf8");
4822
5022
  indexUpdated = true;
4823
5023
  }
4824
5024
  } catch (e) {
4825
5025
  if (e instanceof Error && "code" in e && e.code !== "ENOENT") throw e;
4826
5026
  }
4827
5027
  }
4828
- await rename5(join25(input.vault, relPath), join25(input.vault, archivePath));
5028
+ await rename7(join27(input.vault, relPath), join27(input.vault, archivePath));
4829
5029
  appendLastOp(input.vault, {
4830
5030
  operation: input.cascade ? "archive-cascade" : "archive",
4831
5031
  summary: `moved ${relPath} to ${archivePath}${input.cascade ? ` (cascade: ${cascade?.source_array_refs.length ?? 0} source arrays updated)` : ""}`,
@@ -5212,14 +5412,14 @@ ${newBody}`;
5212
5412
  // src/commands/update.ts
5213
5413
  import { execSync as execSync3 } from "child_process";
5214
5414
  import { readFileSync as readFileSync8 } from "fs";
5215
- import { join as join26 } from "path";
5415
+ import { join as join28 } from "path";
5216
5416
  function resolveGlobalSkillsRoot() {
5217
5417
  try {
5218
5418
  const globalRoot = execSync3("npm root -g", {
5219
5419
  encoding: "utf8",
5220
5420
  timeout: 5e3
5221
5421
  }).trim();
5222
- return join26(globalRoot, "skillwiki", "skills");
5422
+ return join28(globalRoot, "skillwiki", "skills");
5223
5423
  } catch {
5224
5424
  return null;
5225
5425
  }
@@ -5245,7 +5445,7 @@ async function runUpdate(input) {
5245
5445
  );
5246
5446
  const currentVersion = pkg2.version;
5247
5447
  const tag = input.distTag ?? "latest";
5248
- const target = join26(input.home, ".claude", "skills");
5448
+ const target = join28(input.home, ".claude", "skills");
5249
5449
  let latest;
5250
5450
  try {
5251
5451
  latest = execSync3(`npm view skillwiki@${tag} version`, {
@@ -5315,16 +5515,16 @@ async function runUpdate(input) {
5315
5515
 
5316
5516
  // src/commands/self-update.ts
5317
5517
  import { execSync as execSync4 } from "child_process";
5318
- import { existsSync as existsSync8, readFileSync as readFileSync9 } from "fs";
5319
- import { join as join27 } from "path";
5518
+ import { existsSync as existsSync9, readFileSync as readFileSync9 } from "fs";
5519
+ import { join as join29 } from "path";
5320
5520
  var DEFAULT_SOURCE_ROOT_SUFFIX = "/Desktop/code/llm-wiki";
5321
5521
  async function runSelfUpdate(input) {
5322
5522
  const currentVersion = JSON.parse(
5323
5523
  readFileSync9(new URL("../../package.json", import.meta.url), "utf8")
5324
5524
  ).version;
5325
5525
  const sourceRoot = input.sourceRoot ?? `${input.home}${DEFAULT_SOURCE_ROOT_SUFFIX}`;
5326
- const localPkgPath = join27(sourceRoot, "packages", "cli", "package.json");
5327
- const hasLocalSource = existsSync8(localPkgPath);
5526
+ const localPkgPath = join29(sourceRoot, "packages", "cli", "package.json");
5527
+ const hasLocalSource = existsSync9(localPkgPath);
5328
5528
  if (input.check) {
5329
5529
  let availableVersion = null;
5330
5530
  let source;
@@ -5455,10 +5655,10 @@ async function runSelfUpdate(input) {
5455
5655
  }
5456
5656
 
5457
5657
  // src/commands/transcripts.ts
5458
- import { readdir as readdir5, stat as stat6, readFile as readFile19 } from "fs/promises";
5459
- import { join as join28 } from "path";
5658
+ import { readdir as readdir5, stat as stat7, readFile as readFile20 } from "fs/promises";
5659
+ import { join as join30 } from "path";
5460
5660
  async function runTranscripts(input) {
5461
- const dir = join28(input.vault, "raw", "transcripts");
5661
+ const dir = join30(input.vault, "raw", "transcripts");
5462
5662
  let entries;
5463
5663
  try {
5464
5664
  entries = await readdir5(dir, { withFileTypes: true });
@@ -5468,13 +5668,13 @@ async function runTranscripts(input) {
5468
5668
  const transcripts = [];
5469
5669
  for (const entry of entries) {
5470
5670
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
5471
- const filePath = join28(dir, entry.name);
5472
- const content = await readFile19(filePath, "utf8");
5671
+ const filePath = join30(dir, entry.name);
5672
+ const content = await readFile20(filePath, "utf8");
5473
5673
  const fm = extractFrontmatter(content);
5474
5674
  if (!fm.ok) continue;
5475
5675
  const ingested = typeof fm.data.ingested === "string" ? fm.data.ingested : "";
5476
5676
  if (input.since && ingested && ingested < input.since) continue;
5477
- const s = await stat6(filePath);
5677
+ const s = await stat7(filePath);
5478
5678
  transcripts.push({
5479
5679
  file: `raw/transcripts/${entry.name}`,
5480
5680
  ingested,
@@ -5486,12 +5686,12 @@ async function runTranscripts(input) {
5486
5686
  }
5487
5687
 
5488
5688
  // src/commands/project-index.ts
5489
- import { readdir as readdir6, readFile as readFile20, writeFile as writeFile10, mkdir as mkdir9 } from "fs/promises";
5490
- import { join as join29, dirname as dirname10 } from "path";
5689
+ import { readdir as readdir6, readFile as readFile21, writeFile as writeFile11, mkdir as mkdir9 } from "fs/promises";
5690
+ import { join as join31, dirname as dirname10 } from "path";
5491
5691
  var LAYER2_DIRS = ["entities", "concepts", "comparisons", "queries", "meta"];
5492
5692
  async function runProjectIndex(input) {
5493
5693
  const slug = input.slug;
5494
- const projectDir = join29(input.vault, "projects", slug);
5694
+ const projectDir = join31(input.vault, "projects", slug);
5495
5695
  try {
5496
5696
  await readdir6(projectDir);
5497
5697
  } catch {
@@ -5502,15 +5702,15 @@ async function runProjectIndex(input) {
5502
5702
  }
5503
5703
  const wikilinkPattern = `[[${slug}]]`;
5504
5704
  const entries = [];
5505
- const compoundDir = join29(input.vault, "projects", slug, "compound");
5705
+ const compoundDir = join31(input.vault, "projects", slug, "compound");
5506
5706
  try {
5507
5707
  const compoundFiles = await readdir6(compoundDir, { withFileTypes: true });
5508
5708
  for (const entry of compoundFiles) {
5509
5709
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
5510
- const filePath = join29(compoundDir, entry.name);
5710
+ const filePath = join31(compoundDir, entry.name);
5511
5711
  let text;
5512
5712
  try {
5513
- text = await readFile20(filePath, "utf8");
5713
+ text = await readFile21(filePath, "utf8");
5514
5714
  } catch {
5515
5715
  continue;
5516
5716
  }
@@ -5527,16 +5727,16 @@ async function runProjectIndex(input) {
5527
5727
  for (const dir of LAYER2_DIRS) {
5528
5728
  let files;
5529
5729
  try {
5530
- files = await readdir6(join29(input.vault, dir), { withFileTypes: true });
5730
+ files = await readdir6(join31(input.vault, dir), { withFileTypes: true });
5531
5731
  } catch {
5532
5732
  continue;
5533
5733
  }
5534
5734
  for (const entry of files) {
5535
5735
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
5536
- const filePath = join29(input.vault, dir, entry.name);
5736
+ const filePath = join31(input.vault, dir, entry.name);
5537
5737
  let text;
5538
5738
  try {
5539
- text = await readFile20(filePath, "utf8");
5739
+ text = await readFile21(filePath, "utf8");
5540
5740
  } catch {
5541
5741
  continue;
5542
5742
  }
@@ -5557,11 +5757,11 @@ async function runProjectIndex(input) {
5557
5757
  const tb = typeOrder[b.type] ?? 99;
5558
5758
  return ta !== tb ? ta - tb : a.title.localeCompare(b.title);
5559
5759
  });
5560
- const indexPath = join29(projectDir, "knowledge.md");
5760
+ const indexPath = join31(projectDir, "knowledge.md");
5561
5761
  let existing = false;
5562
5762
  let stale = false;
5563
5763
  try {
5564
- const existingText = await readFile20(indexPath, "utf8");
5764
+ const existingText = await readFile21(indexPath, "utf8");
5565
5765
  existing = true;
5566
5766
  const existingEntries = existingText.split("\n").filter((l) => l.startsWith("- [["));
5567
5767
  const existingPages = new Set(existingEntries.map((l) => {
@@ -5602,7 +5802,7 @@ Autogenerated by \`skillwiki project-index\` on ${today}.
5602
5802
  if (input.apply) {
5603
5803
  try {
5604
5804
  await mkdir9(dirname10(indexPath), { recursive: true });
5605
- await writeFile10(indexPath, body, "utf8");
5805
+ await writeFile11(indexPath, body, "utf8");
5606
5806
  } catch (e) {
5607
5807
  return {
5608
5808
  exitCode: ExitCode.WRITE_FAILED,
@@ -5630,10 +5830,10 @@ ${entries.map((e) => ` ${e.type}: [[${e.page.replace(/\.md$/, "")}]] \u2014 ${e
5630
5830
  }
5631
5831
 
5632
5832
  // src/commands/compound.ts
5633
- import { writeFile as writeFile11, mkdir as mkdir10, readdir as readdir7, unlink as unlink3 } from "fs/promises";
5634
- import { join as join30 } from "path";
5635
- import { existsSync as existsSync9 } from "fs";
5636
- import { readFile as readFile21 } from "fs/promises";
5833
+ import { writeFile as writeFile12, mkdir as mkdir10, readdir as readdir7, unlink as unlink3 } from "fs/promises";
5834
+ import { join as join32 } from "path";
5835
+ import { existsSync as existsSync10 } from "fs";
5836
+ import { readFile as readFile22 } from "fs/promises";
5637
5837
  var RETRO_HEADING_RE = /^## \[(\d{4}-\d{2}-\d{2})(?:\s+[^\]]+)?\] retro \| loop cycle(?: (\d+))?: (.+)$/;
5638
5838
  var FIELD_RE = {
5639
5839
  improve: /^-\s+\*?\*?Improve:?\*?\*?\s*(.+)$/m,
@@ -5731,17 +5931,17 @@ function extractRetroFields(date, cycleName, block) {
5731
5931
  };
5732
5932
  }
5733
5933
  async function runCompound(input) {
5734
- const logPath = join30(input.vault, "log.md");
5934
+ const logPath = join32(input.vault, "log.md");
5735
5935
  let logText;
5736
5936
  try {
5737
- logText = await readFile21(logPath, "utf8");
5937
+ logText = await readFile22(logPath, "utf8");
5738
5938
  } catch {
5739
5939
  return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path: logPath }) };
5740
5940
  }
5741
5941
  const entries = parseRetroEntries(logText);
5742
5942
  const promoted = [];
5743
5943
  const skipped = [];
5744
- const compoundDir = join30(input.vault, "projects", input.project, "compound");
5944
+ const compoundDir = join32(input.vault, "projects", input.project, "compound");
5745
5945
  for (const entry of entries) {
5746
5946
  const generalizeValue = entry.generalize.trim();
5747
5947
  if (!/^yes/i.test(generalizeValue)) {
@@ -5749,8 +5949,8 @@ async function runCompound(input) {
5749
5949
  continue;
5750
5950
  }
5751
5951
  const slug = slugify(entry.cycleName);
5752
- const compoundPath = join30(compoundDir, `${slug}.md`);
5753
- if (existsSync9(compoundPath)) {
5952
+ const compoundPath = join32(compoundDir, `${slug}.md`);
5953
+ if (existsSync10(compoundPath)) {
5754
5954
  skipped.push(entry.date);
5755
5955
  continue;
5756
5956
  }
@@ -5788,10 +5988,10 @@ async function runCompound(input) {
5788
5988
  ].join("\n");
5789
5989
  const content = frontmatter + "\n" + body;
5790
5990
  if (!input.dryRun) {
5791
- if (!existsSync9(compoundDir)) {
5991
+ if (!existsSync10(compoundDir)) {
5792
5992
  await mkdir10(compoundDir, { recursive: true });
5793
5993
  }
5794
- await writeFile11(compoundPath, content, "utf8");
5994
+ await writeFile12(compoundPath, content, "utf8");
5795
5995
  }
5796
5996
  promoted.push(`${slug}.md`);
5797
5997
  }
@@ -5810,16 +6010,16 @@ async function runCompound(input) {
5810
6010
  };
5811
6011
  }
5812
6012
  async function runCompoundDelete(input) {
5813
- const projectDir = join30(input.vault, "projects", input.project);
5814
- if (!existsSync9(projectDir)) {
6013
+ const projectDir = join32(input.vault, "projects", input.project);
6014
+ if (!existsSync10(projectDir)) {
5815
6015
  return {
5816
6016
  exitCode: ExitCode.PROJECT_NOT_FOUND,
5817
6017
  result: err("PROJECT_NOT_FOUND", { slug: input.project, path: projectDir })
5818
6018
  };
5819
6019
  }
5820
6020
  const entryName = input.entry.replace(/\.md$/, "");
5821
- const compoundPath = join30(projectDir, "compound", `${entryName}.md`);
5822
- if (!existsSync9(compoundPath)) {
6021
+ const compoundPath = join32(projectDir, "compound", `${entryName}.md`);
6022
+ if (!existsSync10(compoundPath)) {
5823
6023
  return {
5824
6024
  exitCode: ExitCode.FILE_NOT_FOUND,
5825
6025
  result: err("FILE_NOT_FOUND", { path: compoundPath })
@@ -5852,8 +6052,8 @@ knowledge.md regenerated`
5852
6052
  };
5853
6053
  }
5854
6054
  async function runCompoundList(input) {
5855
- const compoundDir = join30(input.vault, "projects", input.project, "compound");
5856
- if (!existsSync9(compoundDir)) {
6055
+ const compoundDir = join32(input.vault, "projects", input.project, "compound");
6056
+ if (!existsSync10(compoundDir)) {
5857
6057
  return {
5858
6058
  exitCode: ExitCode.OK,
5859
6059
  result: ok({
@@ -5883,10 +6083,10 @@ could not read compound directory`
5883
6083
  const entries = [];
5884
6084
  for (const dirent of dirents) {
5885
6085
  if (!dirent.isFile() || !dirent.name.endsWith(".md")) continue;
5886
- const filePath = join30(compoundDir, dirent.name);
6086
+ const filePath = join32(compoundDir, dirent.name);
5887
6087
  let text;
5888
6088
  try {
5889
- text = await readFile21(filePath, "utf8");
6089
+ text = await readFile22(filePath, "utf8");
5890
6090
  } catch {
5891
6091
  continue;
5892
6092
  }
@@ -5915,9 +6115,9 @@ no compound entries found`;
5915
6115
  }
5916
6116
 
5917
6117
  // src/commands/observe.ts
5918
- import { mkdir as mkdir11, writeFile as writeFile12 } from "fs/promises";
5919
- import { existsSync as existsSync10, statSync as statSync3 } from "fs";
5920
- import { join as join31 } from "path";
6118
+ import { mkdir as mkdir11, writeFile as writeFile13 } from "fs/promises";
6119
+ import { existsSync as existsSync11, statSync as statSync4 } from "fs";
6120
+ import { join as join33 } from "path";
5921
6121
  import { createHash as createHash4 } from "crypto";
5922
6122
  var ALLOWED_KINDS = /* @__PURE__ */ new Set(["note", "bug", "task", "idea", "session-log"]);
5923
6123
  function slugify2(text) {
@@ -5940,13 +6140,13 @@ async function runObserve(input) {
5940
6140
  result: err("SCHEME_REJECTED", { message: "Text must not be empty" })
5941
6141
  };
5942
6142
  }
5943
- if (!existsSync10(input.vault) || !statSync3(input.vault).isDirectory()) {
6143
+ if (!existsSync11(input.vault) || !statSync4(input.vault).isDirectory()) {
5944
6144
  return {
5945
6145
  exitCode: ExitCode.VAULT_PATH_INVALID,
5946
6146
  result: err("VAULT_PATH_INVALID", { path: input.vault })
5947
6147
  };
5948
6148
  }
5949
- const transcriptsDir = join31(input.vault, "raw", "transcripts");
6149
+ const transcriptsDir = join33(input.vault, "raw", "transcripts");
5950
6150
  try {
5951
6151
  await mkdir11(transcriptsDir, { recursive: true });
5952
6152
  } catch {
@@ -5958,7 +6158,7 @@ async function runObserve(input) {
5958
6158
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
5959
6159
  const slug = slugify2(input.text);
5960
6160
  const fileName = `${today}-observation-${slug}.md`;
5961
- const filePath = join31(transcriptsDir, fileName);
6161
+ const filePath = join33(transcriptsDir, fileName);
5962
6162
  const body = `
5963
6163
  ${input.text.trim()}
5964
6164
  `;
@@ -5976,7 +6176,7 @@ ${input.text.trim()}
5976
6176
  frontmatterLines.push("---");
5977
6177
  const content = frontmatterLines.join("\n") + body;
5978
6178
  try {
5979
- await writeFile12(filePath, content, "utf8");
6179
+ await writeFile13(filePath, content, "utf8");
5980
6180
  } catch (e) {
5981
6181
  return {
5982
6182
  exitCode: ExitCode.WRITE_FAILED,
@@ -5998,8 +6198,8 @@ ${input.text.trim()}
5998
6198
  }
5999
6199
 
6000
6200
  // src/commands/ingest.ts
6001
- import { readFile as readFile22, writeFile as writeFile13, mkdir as mkdir12 } from "fs/promises";
6002
- import { join as join32 } from "path";
6201
+ import { readFile as readFile23, writeFile as writeFile14, mkdir as mkdir12 } from "fs/promises";
6202
+ import { join as join34 } from "path";
6003
6203
  import { createHash as createHash5 } from "crypto";
6004
6204
  var ALLOWED_TYPES = /* @__PURE__ */ new Set(["entity", "concept", "comparison", "query"]);
6005
6205
  var TYPE_DIR = {
@@ -6158,7 +6358,7 @@ async function runIngest(input) {
6158
6358
  sourceContent = fetchResult.data.body;
6159
6359
  } else {
6160
6360
  try {
6161
- sourceContent = await readFile22(input.source, "utf8");
6361
+ sourceContent = await readFile23(input.source, "utf8");
6162
6362
  } catch {
6163
6363
  return {
6164
6364
  exitCode: ExitCode.FILE_NOT_FOUND,
@@ -6173,8 +6373,8 @@ async function runIngest(input) {
6173
6373
  const rawRelPath = `raw/articles/${slug}.md`;
6174
6374
  const typedDir = TYPE_DIR[input.type] ?? `${input.type}s`;
6175
6375
  const typedRelPath = `${typedDir}/${slug}.md`;
6176
- const rawAbsPath = join32(input.vault, rawRelPath);
6177
- const typedAbsPath = join32(input.vault, typedRelPath);
6376
+ const rawAbsPath = join34(input.vault, rawRelPath);
6377
+ const typedAbsPath = join34(input.vault, typedRelPath);
6178
6378
  const rawContent = buildRawContent(sourceUrl, today, sha256, sourceContent);
6179
6379
  const typedContent = buildTypedContent(
6180
6380
  input.title,
@@ -6237,8 +6437,8 @@ async function runIngest(input) {
6237
6437
  };
6238
6438
  }
6239
6439
  try {
6240
- await mkdir12(join32(input.vault, "raw", "articles"), { recursive: true });
6241
- await writeFile13(rawAbsPath, rawContent, "utf8");
6440
+ await mkdir12(join34(input.vault, "raw", "articles"), { recursive: true });
6441
+ await writeFile14(rawAbsPath, rawContent, "utf8");
6242
6442
  } catch (e) {
6243
6443
  return {
6244
6444
  exitCode: ExitCode.WRITE_FAILED,
@@ -6246,8 +6446,8 @@ async function runIngest(input) {
6246
6446
  };
6247
6447
  }
6248
6448
  try {
6249
- await mkdir12(join32(input.vault, typedDir), { recursive: true });
6250
- await writeFile13(typedAbsPath, typedContent, "utf8");
6449
+ await mkdir12(join34(input.vault, typedDir), { recursive: true });
6450
+ await writeFile14(typedAbsPath, typedContent, "utf8");
6251
6451
  } catch (e) {
6252
6452
  return {
6253
6453
  exitCode: ExitCode.WRITE_FAILED,
@@ -6425,12 +6625,12 @@ ${body}`;
6425
6625
  }
6426
6626
 
6427
6627
  // src/commands/sync.ts
6428
- import { existsSync as existsSync12 } from "fs";
6429
- import { join as join34 } from "path";
6628
+ import { existsSync as existsSync13 } from "fs";
6629
+ import { join as join36 } from "path";
6430
6630
 
6431
6631
  // src/utils/sync-lock.ts
6432
- import { existsSync as existsSync11, mkdirSync as mkdirSync3, readFileSync as readFileSync10, renameSync, unlinkSync as unlinkSync4, writeFileSync as writeFileSync5 } from "fs";
6433
- import { join as join33 } from "path";
6632
+ import { existsSync as existsSync12, mkdirSync as mkdirSync4, readFileSync as readFileSync10, renameSync, unlinkSync as unlinkSync5, writeFileSync as writeFileSync6 } from "fs";
6633
+ import { join as join35 } from "path";
6434
6634
  import { createHash as createHash6 } from "crypto";
6435
6635
  function getSessionId() {
6436
6636
  if (process.env.CLAUDE_SESSION_ID) return process.env.CLAUDE_SESSION_ID;
@@ -6438,11 +6638,11 @@ function getSessionId() {
6438
6638
  return process.pid.toString();
6439
6639
  }
6440
6640
  function lockPath(vault) {
6441
- return join33(vault, ".skillwiki", "sync.lock");
6641
+ return join35(vault, ".skillwiki", "sync.lock");
6442
6642
  }
6443
6643
  function readLock(vault) {
6444
6644
  const path = lockPath(vault);
6445
- if (!existsSync11(path)) return null;
6645
+ if (!existsSync12(path)) return null;
6446
6646
  try {
6447
6647
  const raw = readFileSync10(path, "utf8");
6448
6648
  return JSON.parse(raw);
@@ -6457,9 +6657,9 @@ function isStale(lock, now) {
6457
6657
  }
6458
6658
  function acquireLock(vault, opts = {}) {
6459
6659
  const path = lockPath(vault);
6460
- const dir = join33(vault, ".skillwiki");
6461
- if (!existsSync11(dir)) {
6462
- mkdirSync3(dir, { recursive: true });
6660
+ const dir = join35(vault, ".skillwiki");
6661
+ if (!existsSync12(dir)) {
6662
+ mkdirSync4(dir, { recursive: true });
6463
6663
  }
6464
6664
  const sessionId = opts.sessionId ?? getSessionId();
6465
6665
  const summary = opts.summary ?? "skillwiki sync";
@@ -6478,7 +6678,7 @@ function acquireLock(vault, opts = {}) {
6478
6678
  };
6479
6679
  try {
6480
6680
  const content = JSON.stringify(lock, null, 2) + "\n";
6481
- writeFileSync5(path, content, { flag: "wx" });
6681
+ writeFileSync6(path, content, { flag: "wx" });
6482
6682
  return { ok: true, lock };
6483
6683
  } catch (e) {
6484
6684
  const err3 = e;
@@ -6498,19 +6698,19 @@ function acquireLock(vault, opts = {}) {
6498
6698
  function writeLockedFile(path, lock) {
6499
6699
  const tmp = path + ".tmp";
6500
6700
  const content = JSON.stringify(lock, null, 2) + "\n";
6501
- writeFileSync5(tmp, content);
6701
+ writeFileSync6(tmp, content);
6502
6702
  renameSync(tmp, path);
6503
6703
  }
6504
6704
  function releaseLock(vault, opts = {}) {
6505
6705
  const path = lockPath(vault);
6506
- if (!existsSync11(path)) {
6706
+ if (!existsSync12(path)) {
6507
6707
  return { released: false };
6508
6708
  }
6509
6709
  const sessionId = opts.sessionId ?? getSessionId();
6510
6710
  const existing = readLock(vault);
6511
6711
  if (opts.force) {
6512
6712
  try {
6513
- unlinkSync4(path);
6713
+ unlinkSync5(path);
6514
6714
  const prior = existing && existing.session_id !== sessionId ? existing : void 0;
6515
6715
  return { released: true, prior };
6516
6716
  } catch {
@@ -6521,7 +6721,7 @@ function releaseLock(vault, opts = {}) {
6521
6721
  return { released: false };
6522
6722
  }
6523
6723
  try {
6524
- unlinkSync4(path);
6724
+ unlinkSync5(path);
6525
6725
  return { released: true };
6526
6726
  } catch {
6527
6727
  return { released: false };
@@ -6532,7 +6732,7 @@ function releaseLock(vault, opts = {}) {
6532
6732
  function runSyncStatus(input) {
6533
6733
  const vault = input.vault;
6534
6734
  const includeStashes = input.includeStashes ?? false;
6535
- if (!existsSync12(join34(vault, ".git"))) {
6735
+ if (!existsSync13(join36(vault, ".git"))) {
6536
6736
  return {
6537
6737
  exitCode: ExitCode.VAULT_PATH_INVALID,
6538
6738
  result: ok({
@@ -6609,7 +6809,7 @@ function runSyncStatus(input) {
6609
6809
  }
6610
6810
  async function runSyncPush(input) {
6611
6811
  const vault = input.vault;
6612
- if (!existsSync12(join34(vault, ".git"))) {
6812
+ if (!existsSync13(join36(vault, ".git"))) {
6613
6813
  return {
6614
6814
  exitCode: ExitCode.VAULT_PATH_INVALID,
6615
6815
  result: err("NOT_A_GIT_REPO", { path: vault })
@@ -6713,7 +6913,7 @@ function enumerateStashes(vault) {
6713
6913
  }
6714
6914
  async function runSyncPull(input) {
6715
6915
  const vault = input.vault;
6716
- if (!existsSync12(join34(vault, ".git"))) {
6916
+ if (!existsSync13(join36(vault, ".git"))) {
6717
6917
  return {
6718
6918
  exitCode: ExitCode.VAULT_PATH_INVALID,
6719
6919
  result: err("NOT_A_GIT_REPO", { path: vault })
@@ -6881,7 +7081,7 @@ function runSyncPeers(input) {
6881
7081
  }
6882
7082
  function runSyncLock(input) {
6883
7083
  const vault = input.vault;
6884
- if (!existsSync12(vault)) {
7084
+ if (!existsSync13(vault)) {
6885
7085
  return {
6886
7086
  exitCode: ExitCode.VAULT_PATH_INVALID,
6887
7087
  result: err("VAULT_PATH_INVALID", { path: vault })
@@ -6916,7 +7116,7 @@ function runSyncLock(input) {
6916
7116
  }
6917
7117
  function runSyncUnlock(input) {
6918
7118
  const vault = input.vault;
6919
- if (!existsSync12(vault)) {
7119
+ if (!existsSync13(vault)) {
6920
7120
  return {
6921
7121
  exitCode: ExitCode.VAULT_PATH_INVALID,
6922
7122
  result: err("VAULT_PATH_INVALID", { path: vault })
@@ -6949,8 +7149,8 @@ function runSyncUnlock(input) {
6949
7149
  }
6950
7150
 
6951
7151
  // src/commands/backup.ts
6952
- import { statSync as statSync4, readdirSync as readdirSync2, readFileSync as readFileSync11, mkdirSync as mkdirSync4, writeFileSync as writeFileSync6 } from "fs";
6953
- import { join as join35, relative as relative3, dirname as dirname11 } from "path";
7152
+ import { statSync as statSync5, readdirSync as readdirSync2, readFileSync as readFileSync11, mkdirSync as mkdirSync5, writeFileSync as writeFileSync7 } from "fs";
7153
+ import { join as join37, relative as relative3, dirname as dirname11 } from "path";
6954
7154
  import { PutObjectCommand, HeadObjectCommand, ListObjectsV2Command, GetObjectCommand, DeleteObjectsCommand } from "@aws-sdk/client-s3";
6955
7155
 
6956
7156
  // src/utils/s3-client.ts
@@ -6974,7 +7174,7 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([".git", ".obsidian", "_archive", "node_
6974
7174
  function* walkMarkdown(dir, base) {
6975
7175
  for (const entry of readdirSync2(dir, { withFileTypes: true })) {
6976
7176
  if (SKIP_DIRS.has(entry.name)) continue;
6977
- const full = join35(dir, entry.name);
7177
+ const full = join37(dir, entry.name);
6978
7178
  if (entry.isDirectory()) {
6979
7179
  yield* walkMarkdown(full, base);
6980
7180
  } else if (entry.name.endsWith(".md")) {
@@ -6997,8 +7197,8 @@ async function runBackupSync(input) {
6997
7197
  let failed = 0;
6998
7198
  const files = [...walkMarkdown(input.vault, input.vault)];
6999
7199
  for (const relPath of files) {
7000
- const absPath = join35(input.vault, relPath);
7001
- const localStat = statSync4(absPath);
7200
+ const absPath = join37(input.vault, relPath);
7201
+ const localStat = statSync5(absPath);
7002
7202
  let needsUpload = true;
7003
7203
  try {
7004
7204
  const head = await client.send(new HeadObjectCommand({ Bucket: input.bucket, Key: relPath }));
@@ -7073,9 +7273,9 @@ async function runBackupRestore(input) {
7073
7273
  const objects = list.Contents ?? [];
7074
7274
  for (const obj of objects) {
7075
7275
  if (!obj.Key) continue;
7076
- const localPath = join35(target, obj.Key);
7276
+ const localPath = join37(target, obj.Key);
7077
7277
  try {
7078
- const localStat = statSync4(localPath);
7278
+ const localStat = statSync5(localPath);
7079
7279
  if (obj.LastModified && localStat.mtime > obj.LastModified) {
7080
7280
  conflicts++;
7081
7281
  continue;
@@ -7086,8 +7286,8 @@ async function runBackupRestore(input) {
7086
7286
  const resp = await client.send(new GetObjectCommand({ Bucket: input.bucket, Key: obj.Key }));
7087
7287
  const body = await resp.Body?.transformToByteArray();
7088
7288
  if (body) {
7089
- mkdirSync4(dirname11(localPath), { recursive: true });
7090
- writeFileSync6(localPath, Buffer.from(body));
7289
+ mkdirSync5(dirname11(localPath), { recursive: true });
7290
+ writeFileSync7(localPath, Buffer.from(body));
7091
7291
  downloaded++;
7092
7292
  }
7093
7293
  } catch {
@@ -7119,11 +7319,11 @@ async function runBackupRestore(input) {
7119
7319
  }
7120
7320
 
7121
7321
  // src/commands/status.ts
7122
- import { existsSync as existsSync13, statSync as statSync5 } from "fs";
7123
- import { readFile as readFile23 } from "fs/promises";
7124
- import { join as join36 } from "path";
7322
+ import { existsSync as existsSync14, statSync as statSync6 } from "fs";
7323
+ import { readFile as readFile24 } from "fs/promises";
7324
+ import { join as join38 } from "path";
7125
7325
  async function runStatus(input) {
7126
- if (!existsSync13(input.vault)) {
7326
+ if (!existsSync14(input.vault)) {
7127
7327
  return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { vault: input.vault }) };
7128
7328
  }
7129
7329
  const scan = await scanVault(input.vault);
@@ -7148,7 +7348,7 @@ async function runStatus(input) {
7148
7348
  const compound = scan.data.compound.length;
7149
7349
  let schemaVersion = "v1";
7150
7350
  try {
7151
- const schemaContent = await readFile23(join36(input.vault, "SCHEMA.md"), "utf8");
7351
+ const schemaContent = await readFile24(join38(input.vault, "SCHEMA.md"), "utf8");
7152
7352
  const versionMatch = schemaContent.match(/version:\s*["']?([^"'\s\n]+)/i);
7153
7353
  if (versionMatch) schemaVersion = versionMatch[1];
7154
7354
  } catch {
@@ -7164,7 +7364,7 @@ async function runStatus(input) {
7164
7364
  let maxTime = 0;
7165
7365
  for (const page of allPages) {
7166
7366
  try {
7167
- const st = statSync5(page.absPath);
7367
+ const st = statSync6(page.absPath);
7168
7368
  if (st.mtimeMs > maxTime) {
7169
7369
  maxTime = st.mtimeMs;
7170
7370
  lastModified = st.mtime.toISOString();
@@ -7208,8 +7408,8 @@ async function runStatus(input) {
7208
7408
  }
7209
7409
 
7210
7410
  // src/commands/seed.ts
7211
- import { mkdir as mkdir13, writeFile as writeFile14, stat as stat7 } from "fs/promises";
7212
- import { join as join37 } from "path";
7411
+ import { mkdir as mkdir13, writeFile as writeFile15, stat as stat8 } from "fs/promises";
7412
+ import { join as join39 } from "path";
7213
7413
  var TODAY = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
7214
7414
  var EXAMPLE_PAGES = {
7215
7415
  "entities/example-project.md": `---
@@ -7278,30 +7478,30 @@ Real sources are immutable after ingestion \u2014 never edit them.
7278
7478
  `;
7279
7479
  async function runSeed(input) {
7280
7480
  try {
7281
- await stat7(join37(input.vault, "SCHEMA.md"));
7481
+ await stat8(join39(input.vault, "SCHEMA.md"));
7282
7482
  } catch {
7283
7483
  return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { root: input.vault, reason: "SCHEMA.md missing \u2014 run `skillwiki init` first" }) };
7284
7484
  }
7285
7485
  const created = [];
7286
7486
  const skipped = [];
7287
7487
  for (const [relPath, content] of Object.entries(EXAMPLE_PAGES)) {
7288
- const absPath = join37(input.vault, relPath);
7488
+ const absPath = join39(input.vault, relPath);
7289
7489
  try {
7290
- await stat7(absPath);
7490
+ await stat8(absPath);
7291
7491
  skipped.push(relPath);
7292
7492
  } catch {
7293
- await mkdir13(join37(absPath, ".."), { recursive: true });
7294
- await writeFile14(absPath, content, "utf8");
7493
+ await mkdir13(join39(absPath, ".."), { recursive: true });
7494
+ await writeFile15(absPath, content, "utf8");
7295
7495
  created.push(relPath);
7296
7496
  }
7297
7497
  }
7298
- const rawPath = join37(input.vault, "raw", "articles", "example-source.md");
7498
+ const rawPath = join39(input.vault, "raw", "articles", "example-source.md");
7299
7499
  try {
7300
- await stat7(rawPath);
7500
+ await stat8(rawPath);
7301
7501
  skipped.push("raw/articles/example-source.md");
7302
7502
  } catch {
7303
- await mkdir13(join37(rawPath, ".."), { recursive: true });
7304
- await writeFile14(rawPath, EXAMPLE_RAW, "utf8");
7503
+ await mkdir13(join39(rawPath, ".."), { recursive: true });
7504
+ await writeFile15(rawPath, EXAMPLE_RAW, "utf8");
7305
7505
  created.push("raw/articles/example-source.md");
7306
7506
  }
7307
7507
  if (created.length > 0) {
@@ -7323,9 +7523,9 @@ async function runSeed(input) {
7323
7523
  }
7324
7524
 
7325
7525
  // src/commands/canvas.ts
7326
- import { readFile as readFile24, writeFile as writeFile15 } from "fs/promises";
7327
- import { existsSync as existsSync14 } from "fs";
7328
- import { join as join38 } from "path";
7526
+ import { readFile as readFile25, writeFile as writeFile16 } from "fs/promises";
7527
+ import { existsSync as existsSync15 } from "fs";
7528
+ import { join as join40 } from "path";
7329
7529
  var NODE_WIDTH = 240;
7330
7530
  var NODE_HEIGHT = 60;
7331
7531
  var COLUMN_SPACING = 400;
@@ -7403,8 +7603,8 @@ function buildCanvasEdges(adjacency) {
7403
7603
  return edges;
7404
7604
  }
7405
7605
  async function runCanvasGenerate(input) {
7406
- const graphPath = input.graphPath ?? join38(input.vault, ".skillwiki", "graph.json");
7407
- if (!existsSync14(graphPath)) {
7606
+ const graphPath = input.graphPath ?? join40(input.vault, ".skillwiki", "graph.json");
7607
+ if (!existsSync15(graphPath)) {
7408
7608
  return {
7409
7609
  exitCode: ExitCode.FILE_NOT_FOUND,
7410
7610
  result: err("FILE_NOT_FOUND", {
@@ -7415,7 +7615,7 @@ async function runCanvasGenerate(input) {
7415
7615
  }
7416
7616
  let raw;
7417
7617
  try {
7418
- raw = await readFile24(graphPath, "utf8");
7618
+ raw = await readFile25(graphPath, "utf8");
7419
7619
  } catch (e) {
7420
7620
  return {
7421
7621
  exitCode: ExitCode.FILE_NOT_FOUND,
@@ -7441,9 +7641,9 @@ async function runCanvasGenerate(input) {
7441
7641
  const nodes = buildCanvasNodes(paths);
7442
7642
  const edges = buildCanvasEdges(graph.adjacency);
7443
7643
  const canvas = { nodes, edges };
7444
- const outPath = join38(input.vault, "vault-graph.canvas");
7644
+ const outPath = join40(input.vault, "vault-graph.canvas");
7445
7645
  try {
7446
- await writeFile15(outPath, JSON.stringify(canvas, null, 2));
7646
+ await writeFile16(outPath, JSON.stringify(canvas, null, 2));
7447
7647
  } catch (e) {
7448
7648
  return {
7449
7649
  exitCode: ExitCode.WRITE_FAILED,
@@ -7463,8 +7663,8 @@ written: ${outPath}`
7463
7663
  }
7464
7664
 
7465
7665
  // src/commands/query.ts
7466
- import { readFile as readFile25, stat as stat8 } from "fs/promises";
7467
- import { join as join39 } from "path";
7666
+ import { readFile as readFile26, stat as stat9 } from "fs/promises";
7667
+ import { join as join41 } from "path";
7468
7668
  var W_KEYWORD = 2;
7469
7669
  var W_SOURCE_OVERLAP = 4;
7470
7670
  var W_WIKILINK = 3;
@@ -7585,10 +7785,10 @@ function computeKeywordScore(terms, title, tags, body) {
7585
7785
  return score;
7586
7786
  }
7587
7787
  async function loadOrBuildGraph(vault) {
7588
- const graphPath = join39(vault, ".skillwiki", "graph.json");
7788
+ const graphPath = join41(vault, ".skillwiki", "graph.json");
7589
7789
  let needsBuild = false;
7590
7790
  try {
7591
- const fileStat = await stat8(graphPath);
7791
+ const fileStat = await stat9(graphPath);
7592
7792
  const ageHours = (Date.now() - fileStat.mtimeMs) / (1e3 * 60 * 60);
7593
7793
  if (ageHours > 24) needsBuild = true;
7594
7794
  } catch {
@@ -7599,7 +7799,7 @@ async function loadOrBuildGraph(vault) {
7599
7799
  if (buildResult.exitCode !== 0) return null;
7600
7800
  }
7601
7801
  try {
7602
- const raw = await readFile25(graphPath, "utf8");
7802
+ const raw = await readFile26(graphPath, "utf8");
7603
7803
  return JSON.parse(raw);
7604
7804
  } catch {
7605
7805
  return null;
@@ -7607,14 +7807,14 @@ async function loadOrBuildGraph(vault) {
7607
7807
  }
7608
7808
 
7609
7809
  // src/utils/auto-commit.ts
7610
- import { existsSync as existsSync15 } from "fs";
7611
- import { join as join40 } from "path";
7810
+ import { existsSync as existsSync16 } from "fs";
7811
+ import { join as join42 } from "path";
7612
7812
  async function postCommit(vault, exitCode) {
7613
7813
  if (exitCode !== 0) return;
7614
7814
  const home = process.env.HOME ?? "";
7615
7815
  const dotenv = await parseDotenvFile(configPath(home));
7616
7816
  if (dotenv["AUTO_COMMIT"] === "false") return;
7617
- if (!existsSync15(join40(vault, ".git"))) return;
7817
+ if (!existsSync16(join42(vault, ".git"))) return;
7618
7818
  const lastOps = readLastOp(vault);
7619
7819
  if (lastOps.length === 0) return;
7620
7820
  const porcelain = git(vault, ["status", "--porcelain"]);
@@ -7665,7 +7865,7 @@ program.command("validate <file>").description("validate vault page frontmatter
7665
7865
  emit(await runValidate({ file, apply: !!opts.apply, vault }), vault);
7666
7866
  });
7667
7867
  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) => {
7668
- const out = opts.out ?? join41(vault, ".skillwiki", "graph.json");
7868
+ const out = opts.out ?? join43(vault, ".skillwiki", "graph.json");
7669
7869
  emit(await runGraphBuild({ vault, out }), vault);
7670
7870
  });
7671
7871
  var canvasCmd = program.command("canvas").description("manage Obsidian canvas files");
@@ -7791,7 +7991,12 @@ program.command("log-rotate [vault]").description("rotate or trim the vault log
7791
7991
  if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
7792
7992
  else emit(await runLogRotate({ vault: v.vault, threshold: opts.threshold, apply: !!opts.apply }), v.vault);
7793
7993
  });
7794
- program.command("lint [vault]").description("run all vault health checks").option("--days <n>", "stale threshold", (s) => parseInt(s, 10), 90).option("--lines <n>", "pagesize threshold", (s) => parseInt(s, 10), 200).option("--log-threshold <n>", "log rotation threshold", (s) => parseInt(s, 10), 500).option("--fix", "auto-fix legacy_citation_style violations").option("--only <bucket>", "run only the specified lint bucket").option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
7994
+ program.command("log-append [vault]").description("append a single entry to the vault log under a short advisory lock").requiredOption("--content <text>", "log entry text to append").option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
7995
+ const v = await resolveVaultArg(vault, opts.wiki);
7996
+ if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
7997
+ else emit(await runLogAppend({ vault: v.vault, content: opts.content }), v.vault);
7998
+ });
7999
+ program.command("lint [vault]").description("run all vault health checks").option("--days <n>", "stale threshold", (s) => parseInt(s, 10), 90).option("--lines <n>", "pagesize threshold", (s) => parseInt(s, 10), 200).option("--log-threshold <n>", "log rotation threshold", (s) => parseInt(s, 10), 500).option("--fix", "auto-fix supported lint violations").option("--only <bucket>", "run only the specified lint bucket").option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
7795
8000
  const v = await resolveVaultArg(vault, opts.wiki);
7796
8001
  if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
7797
8002
  else emit(await runLint({