skillwiki 0.8.0 → 0.8.1-beta.2

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, rename as rename5 } 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 {
@@ -2689,11 +2791,12 @@ function buildCliSurface() {
2689
2791
  program2.command("claim").option("--project <slug>").option("--slug <slug>").option("--wiki <name>");
2690
2792
  program2.command("pagesize").option("--lines <n>").option("--wiki <name>");
2691
2793
  program2.command("log-rotate").option("--threshold <n>").option("--apply").option("--wiki <name>");
2794
+ program2.command("log-append").requiredOption("--content <text>").option("--wiki <name>");
2692
2795
  program2.command("lint").option("--days <n>").option("--lines <n>").option("--log-threshold <n>").option("--fix").option("--only <bucket>").option("--wiki <name>");
2693
2796
  program2.command("config");
2694
2797
  program2.command("doctor");
2695
2798
  program2.command("status").option("--wiki <name>");
2696
- program2.command("archive").option("--wiki <name>");
2799
+ program2.command("archive").option("--wiki <name>").option("--cascade").option("--apply");
2697
2800
  program2.command("drift").option("--apply").option("--new <date>").option("--wiki <name>");
2698
2801
  program2.command("dedup").option("--apply").option("--wiki <name>");
2699
2802
  program2.command("migrate-citations").option("--dry-run").option("--wiki <name>");
@@ -2726,6 +2829,9 @@ function buildCliSurface() {
2726
2829
  syncCmd2.command("status").option("--wiki <name>");
2727
2830
  syncCmd2.command("push").option("--wiki <name>");
2728
2831
  syncCmd2.command("pull").option("--wiki <name>");
2832
+ syncCmd2.command("lock").option("--summary <text>").option("--ttl-minutes <n>").option("--force").option("--wiki <name>");
2833
+ syncCmd2.command("unlock").option("--force").option("--wiki <name>");
2834
+ syncCmd2.command("peers").option("--wiki <name>");
2729
2835
  const backupCmd2 = program2.commands.find((c) => c.name() === "backup");
2730
2836
  backupCmd2.command("sync").option("--dry-run").option("--bucket <name>").option("--endpoint <url>").option("--region <region>").option("--prune").option("--wiki <name>");
2731
2837
  backupCmd2.command("restore").option("--bucket <name>").option("--endpoint <url>").option("--region <region>").option("--target <dir>").option("--wiki <name>");
@@ -2956,7 +3062,7 @@ async function runLint(input) {
2956
3062
  let rawPath = entry.replace(/^"/, "").replace(/"$/, "").replace(/^'/, "").replace(/'$/, "");
2957
3063
  rawPath = rawPath.replace(/^\^\[/, "").replace(/\]$/, "");
2958
3064
  if (!rawPath.startsWith("raw/") && !rawPath.startsWith("_archive/raw/")) continue;
2959
- 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"))) {
3065
+ 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"))) {
2960
3066
  brokenSourceFlags.push(`${page.relPath}: ${rawPath}`);
2961
3067
  }
2962
3068
  }
@@ -3047,11 +3153,11 @@ async function runLint(input) {
3047
3153
  const slugMatch = String(entry).match(/\[\[([^\]]+)\]\]/);
3048
3154
  if (!slugMatch) continue;
3049
3155
  const slug = slugMatch[1];
3050
- const knowledgePath = join19(input.vault, "projects", slug, "knowledge.md");
3051
- if (!existsSync3(knowledgePath)) continue;
3156
+ const knowledgePath = join21(input.vault, "projects", slug, "knowledge.md");
3157
+ if (!existsSync4(knowledgePath)) continue;
3052
3158
  const pageRef = page.relPath.replace(/\.md$/, "");
3053
3159
  try {
3054
- const knowledgeContent = await readFile15(knowledgePath, "utf8");
3160
+ const knowledgeContent = await readFile16(knowledgePath, "utf8");
3055
3161
  if (!knowledgeContent.includes(`[[${pageRef}]]`)) {
3056
3162
  orphanedProjectPages.push(`${page.relPath}: not in projects/${slug}/knowledge.md`);
3057
3163
  }
@@ -3062,7 +3168,7 @@ async function runLint(input) {
3062
3168
  if (orphanedProjectPages.length > 0) buckets.orphaned_project_pages = orphanedProjectPages;
3063
3169
  const cliRefFlags = [];
3064
3170
  const cliSurface = buildCliSurface();
3065
- const allScanPages = [...scan.data.typedKnowledge, ...scan.data.raw, ...scan.data.workItems, ...scan.data.compound];
3171
+ const allScanPages = [...scan.data.typedKnowledge];
3066
3172
  for (const page of allScanPages) {
3067
3173
  const text = await readPage(page);
3068
3174
  const violations = validateCliRefs(text, page.relPath, cliSurface);
@@ -3098,7 +3204,7 @@ async function runLint(input) {
3098
3204
  for (const relPath of legacyPages) {
3099
3205
  try {
3100
3206
  const absPath = `${input.vault}/${relPath}`;
3101
- const raw = await readFile15(absPath, "utf8");
3207
+ const raw = await readFile16(absPath, "utf8");
3102
3208
  const split = splitFrontmatter(raw);
3103
3209
  if (!split.ok) {
3104
3210
  unresolved.push(relPath);
@@ -3197,7 +3303,7 @@ ${newBody}`;
3197
3303
  for (const relPath of noOverview) {
3198
3304
  try {
3199
3305
  const absPath = `${input.vault}/${relPath}`;
3200
- const raw = await readFile15(absPath, "utf8");
3306
+ const raw = await readFile16(absPath, "utf8");
3201
3307
  const split = splitFrontmatter(raw);
3202
3308
  if (!split.ok) {
3203
3309
  unresolved.push(relPath);
@@ -3238,7 +3344,7 @@ ${trimmedBody}`;
3238
3344
  for (const relPath of missingTldrFlags) {
3239
3345
  try {
3240
3346
  const absPath = `${input.vault}/${relPath}`;
3241
- const raw = await readFile15(absPath, "utf8");
3347
+ const raw = await readFile16(absPath, "utf8");
3242
3348
  const split = splitFrontmatter(raw);
3243
3349
  if (!split.ok) {
3244
3350
  unresolved.push(relPath);
@@ -3288,7 +3394,7 @@ ${lines.join("\n")}`;
3288
3394
  for (const relPath of wikilinkCitationFlags) {
3289
3395
  try {
3290
3396
  const absPath = `${input.vault}/${relPath}`;
3291
- const raw = await readFile15(absPath, "utf8");
3397
+ const raw = await readFile16(absPath, "utf8");
3292
3398
  const split = splitFrontmatter(raw);
3293
3399
  if (!split.ok) {
3294
3400
  unresolved.push(relPath);
@@ -3375,7 +3481,7 @@ ${newBody}`;
3375
3481
  for (const relPath of fileSourceUrlFlags) {
3376
3482
  try {
3377
3483
  const absPath = `${input.vault}/${relPath}`;
3378
- const raw = await readFile15(absPath, "utf8");
3484
+ const raw = await readFile16(absPath, "utf8");
3379
3485
  const parts = raw.split("---", 3);
3380
3486
  if (parts.length < 3) {
3381
3487
  unresolved.push(relPath);
@@ -3417,11 +3523,11 @@ ${newBody}`;
3417
3523
  const absPath = `${input.vault}/${v.relPath}`;
3418
3524
  const newRelPath = truncateFilename(v.relPath);
3419
3525
  const newAbsPath = `${input.vault}/${newRelPath}`;
3420
- await rename5(absPath, newAbsPath);
3526
+ await rename6(absPath, newAbsPath);
3421
3527
  const oldPathEscaped = v.relPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3422
3528
  for (const page of allPages) {
3423
3529
  if (page.relPath === v.relPath) continue;
3424
- const content = await readFile15(page.absPath, "utf8");
3530
+ const content = await readFile16(page.absPath, "utf8");
3425
3531
  if (!content.includes(v.relPath)) continue;
3426
3532
  let updated = content;
3427
3533
  const citationRe = new RegExp(`\\^\\[${oldPathEscaped}\\]`, "g");
@@ -3523,14 +3629,14 @@ ${match.length === 0 ? "0 violations" : match.map((b) => ` ${b.kind}: ${b.items
3523
3629
  }
3524
3630
 
3525
3631
  // src/commands/config.ts
3526
- import { readFile as readFile16 } from "fs/promises";
3527
- import { existsSync as existsSync4 } from "fs";
3528
- import { join as join20 } from "path";
3632
+ import { readFile as readFile17 } from "fs/promises";
3633
+ import { existsSync as existsSync5 } from "fs";
3634
+ import { join as join22 } from "path";
3529
3635
  function validateKey(key) {
3530
3636
  return CONFIG_KEYS.includes(key) || isValidWikiProfileKey(key);
3531
3637
  }
3532
3638
  function configPath(home) {
3533
- return join20(home, ".skillwiki", ".env");
3639
+ return join22(home, ".skillwiki", ".env");
3534
3640
  }
3535
3641
  async function runConfigGet(input) {
3536
3642
  if (!validateKey(input.key)) {
@@ -3548,7 +3654,7 @@ async function runConfigSet(input) {
3548
3654
  try {
3549
3655
  let originalContent;
3550
3656
  try {
3551
- originalContent = await readFile16(filePath, "utf8");
3657
+ originalContent = await readFile17(filePath, "utf8");
3552
3658
  } catch {
3553
3659
  }
3554
3660
  const existing = originalContent !== void 0 ? parseDotenvText(originalContent) : {};
@@ -3580,18 +3686,18 @@ async function runConfigList(input) {
3580
3686
  }
3581
3687
  async function runConfigPath(input) {
3582
3688
  const filePath = configPath(input.home);
3583
- return { exitCode: ExitCode.OK, result: ok({ path: filePath, exists: existsSync4(filePath), humanHint: filePath }) };
3689
+ return { exitCode: ExitCode.OK, result: ok({ path: filePath, exists: existsSync5(filePath), humanHint: filePath }) };
3584
3690
  }
3585
3691
 
3586
3692
  // src/commands/doctor.ts
3587
- import { existsSync as existsSync7, lstatSync, readlinkSync, readdirSync, statSync as statSync2, readFileSync as readFileSync7 } from "fs";
3588
- import { join as join24, resolve as resolve4 } from "path";
3693
+ import { existsSync as existsSync8, lstatSync, readlinkSync, readdirSync, statSync as statSync3, readFileSync as readFileSync7 } from "fs";
3694
+ import { join as join26, resolve as resolve4 } from "path";
3589
3695
  import { execSync as execSync2 } from "child_process";
3590
3696
  import { platform as platform2 } from "os";
3591
3697
 
3592
3698
  // src/utils/auto-update.ts
3593
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync5, mkdirSync as mkdirSync2 } from "fs";
3594
- import { join as join21, dirname as dirname8 } from "path";
3699
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
3700
+ import { join as join23, dirname as dirname8 } from "path";
3595
3701
  import { spawn } from "child_process";
3596
3702
 
3597
3703
  // src/utils/update-consts.ts
@@ -3602,7 +3708,7 @@ var CLI_DISABLE_FLAG = "--no-update-notifier";
3602
3708
 
3603
3709
  // src/utils/auto-update.ts
3604
3710
  function cachePath(home) {
3605
- return join21(home, ".skillwiki", CACHE_FILENAME);
3711
+ return join23(home, ".skillwiki", CACHE_FILENAME);
3606
3712
  }
3607
3713
  function readCacheRaw(home) {
3608
3714
  try {
@@ -3621,8 +3727,8 @@ function readCache(home) {
3621
3727
  }
3622
3728
  function writeCache(home, cache) {
3623
3729
  const p = cachePath(home);
3624
- mkdirSync2(dirname8(p), { recursive: true });
3625
- writeFileSync3(p, JSON.stringify(cache, null, 2));
3730
+ mkdirSync3(dirname8(p), { recursive: true });
3731
+ writeFileSync4(p, JSON.stringify(cache, null, 2));
3626
3732
  }
3627
3733
  function latestFromCache(home, currentVersion) {
3628
3734
  const { cache } = readCache(home);
@@ -3640,7 +3746,7 @@ function triggerAutoUpdate(home, currentVersion) {
3640
3746
  const { isStale: isStale2 } = readCache(home);
3641
3747
  if (!isStale2) return;
3642
3748
  const bgScript = new URL("../auto-update-bg.js", import.meta.url).pathname;
3643
- if (!existsSync5(bgScript)) return;
3749
+ if (!existsSync6(bgScript)) return;
3644
3750
  const child = spawn(process.execPath, [bgScript, home, currentVersion], {
3645
3751
  detached: true,
3646
3752
  stdio: "ignore"
@@ -3652,12 +3758,12 @@ function triggerAutoUpdate(home, currentVersion) {
3652
3758
 
3653
3759
  // src/utils/plugin-registry.ts
3654
3760
  import { readFileSync as readFileSync5 } from "fs";
3655
- import { join as join22 } from "path";
3656
- var REGISTRY_PATH = join22(".claude", "plugins", "installed_plugins.json");
3761
+ import { join as join24 } from "path";
3762
+ var REGISTRY_PATH = join24(".claude", "plugins", "installed_plugins.json");
3657
3763
  var PLUGIN_KEY = "skillwiki@llm-wiki";
3658
3764
  function readInstalledPlugins(home) {
3659
3765
  try {
3660
- const raw = readFileSync5(join22(home, REGISTRY_PATH), "utf8");
3766
+ const raw = readFileSync5(join24(home, REGISTRY_PATH), "utf8");
3661
3767
  return JSON.parse(raw);
3662
3768
  } catch {
3663
3769
  return null;
@@ -3674,8 +3780,8 @@ function findPlugin(home, key = PLUGIN_KEY) {
3674
3780
  // src/utils/s3-mount-health.ts
3675
3781
  import { execSync } from "child_process";
3676
3782
  import { platform } from "os";
3677
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, unlinkSync as unlinkSync3, readFileSync as readFile17 } from "fs";
3678
- import { join as join23 } from "path";
3783
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, unlinkSync as unlinkSync4, readFileSync as readFile18 } from "fs";
3784
+ import { join as join25 } from "path";
3679
3785
  var OS = platform();
3680
3786
  function findRcloneMountPid() {
3681
3787
  try {
@@ -3833,35 +3939,35 @@ function detectFuseMount(vaultPath) {
3833
3939
  return null;
3834
3940
  }
3835
3941
  function writeTest(dir) {
3836
- const testFile = join23(dir, `.doctor-write-test-${process.pid}.tmp`);
3942
+ const testFile = join25(dir, `.doctor-write-test-${process.pid}.tmp`);
3837
3943
  const payload = `skillwiki doctor write test \u2014 ${Date.now()} \u2014 ${Math.random().toString(36).slice(2)}`;
3838
3944
  const start = Date.now();
3839
3945
  try {
3840
- writeFileSync4(testFile, payload, "utf8");
3946
+ writeFileSync5(testFile, payload, "utf8");
3841
3947
  } catch (e) {
3842
3948
  return { success: false, writeMs: Date.now() - start, readMs: 0, size: 0, error: `write failed: ${e.message}` };
3843
3949
  }
3844
3950
  const writeMs = Date.now() - start;
3845
3951
  const readStart = Date.now();
3846
3952
  try {
3847
- const back = readFile17(testFile, "utf8");
3953
+ const back = readFile18(testFile, "utf8");
3848
3954
  const readMs = Date.now() - readStart;
3849
3955
  if (back !== payload) {
3850
3956
  try {
3851
- unlinkSync3(testFile);
3957
+ unlinkSync4(testFile);
3852
3958
  } catch {
3853
3959
  }
3854
3960
  return { success: false, writeMs, readMs, size: Buffer.byteLength(payload, "utf8"), error: "content mismatch \u2014 wrote and read-back differ" };
3855
3961
  }
3856
3962
  } catch (e) {
3857
3963
  try {
3858
- unlinkSync3(testFile);
3964
+ unlinkSync4(testFile);
3859
3965
  } catch {
3860
3966
  }
3861
3967
  return { success: false, writeMs, readMs: Date.now() - readStart, size: 0, error: `read failed: ${e.message}` };
3862
3968
  }
3863
3969
  try {
3864
- unlinkSync3(testFile);
3970
+ unlinkSync4(testFile);
3865
3971
  } catch {
3866
3972
  }
3867
3973
  return { success: true, writeMs, readMs: Date.now() - readStart, size: Buffer.byteLength(payload, "utf8") };
@@ -3902,13 +4008,13 @@ function detectCliChannels(argv, home) {
3902
4008
  }
3903
4009
  const plugin = findPlugin(home);
3904
4010
  if (plugin) {
3905
- const pluginBin = join24(plugin.installPath, "bin", "skillwiki");
3906
- if (existsSync7(pluginBin)) {
4011
+ const pluginBin = join26(plugin.installPath, "bin", "skillwiki");
4012
+ if (existsSync8(pluginBin)) {
3907
4013
  channels.push({ name: "plugin", path: pluginBin, isDevLink: false });
3908
4014
  }
3909
4015
  }
3910
- const installBin = join24(home, ".claude", "skills", "bin", "skillwiki");
3911
- if (existsSync7(installBin)) {
4016
+ const installBin = join26(home, ".claude", "skills", "bin", "skillwiki");
4017
+ if (existsSync8(installBin)) {
3912
4018
  channels.push({ name: "install", path: installBin, isDevLink: false });
3913
4019
  }
3914
4020
  return channels;
@@ -3960,7 +4066,7 @@ function checkCliChannels(argv, home) {
3960
4066
  }
3961
4067
  async function checkConfigFile(home) {
3962
4068
  const cfgPath = configPath(home);
3963
- if (!existsSync7(cfgPath)) {
4069
+ if (!existsSync8(cfgPath)) {
3964
4070
  return check("warn", "config_file", "Config file exists", `${cfgPath} not found`);
3965
4071
  }
3966
4072
  try {
@@ -3975,7 +4081,7 @@ function checkWikiPathExists(resolvedPath) {
3975
4081
  if (resolvedPath === void 0) {
3976
4082
  return check("error", "wiki_path_exists", "Vault directory exists", "Cannot check \u2014 WIKI_PATH not resolved");
3977
4083
  }
3978
- if (existsSync7(resolvedPath) && statSync2(resolvedPath).isDirectory()) {
4084
+ if (existsSync8(resolvedPath) && statSync3(resolvedPath).isDirectory()) {
3979
4085
  return check("pass", "wiki_path_exists", "Vault directory exists", resolvedPath);
3980
4086
  }
3981
4087
  return check("error", "wiki_path_exists", "Vault directory exists", `${resolvedPath} does not exist or is not a directory`);
@@ -3984,13 +4090,13 @@ function checkVaultStructure(resolvedPath) {
3984
4090
  if (resolvedPath === void 0) {
3985
4091
  return check("error", "vault_structure", "Vault structure valid", "Cannot check \u2014 WIKI_PATH not resolved");
3986
4092
  }
3987
- if (!existsSync7(resolvedPath)) {
4093
+ if (!existsSync8(resolvedPath)) {
3988
4094
  return check("error", "vault_structure", "Vault structure valid", "Cannot check \u2014 vault directory does not exist");
3989
4095
  }
3990
4096
  const missing = [];
3991
- if (!existsSync7(join24(resolvedPath, "SCHEMA.md"))) missing.push("SCHEMA.md");
4097
+ if (!existsSync8(join26(resolvedPath, "SCHEMA.md"))) missing.push("SCHEMA.md");
3992
4098
  for (const dir of ["raw", "entities", "concepts", "meta"]) {
3993
- if (!existsSync7(join24(resolvedPath, dir))) missing.push(dir + "/");
4099
+ if (!existsSync8(join26(resolvedPath, dir))) missing.push(dir + "/");
3994
4100
  }
3995
4101
  if (missing.length === 0) {
3996
4102
  return check("pass", "vault_structure", "Vault structure valid", "All required files and directories present");
@@ -3998,8 +4104,8 @@ function checkVaultStructure(resolvedPath) {
3998
4104
  return check("warn", "vault_structure", "Vault structure valid", `Missing: ${missing.join(", ")} \u2014 run \`skillwiki init\` to add CodeWiki structure`);
3999
4105
  }
4000
4106
  function checkSkillsInstalled(home, cwd) {
4001
- const srcDir = cwd ? join24(cwd, "packages", "skills") : void 0;
4002
- if (srcDir && existsSync7(srcDir)) {
4107
+ const srcDir = cwd ? join26(cwd, "packages", "skills") : void 0;
4108
+ if (srcDir && existsSync8(srcDir)) {
4003
4109
  const found = findSkillMd(srcDir);
4004
4110
  if (found.length > 0) {
4005
4111
  return check("pass", "skills_installed", "Skills installed", `${found.length} SKILL.md file(s) found (source)`);
@@ -4012,8 +4118,8 @@ function checkSkillsInstalled(home, cwd) {
4012
4118
  return check("pass", "skills_installed", "Skills installed", `${found.length} SKILL.md file(s) found (plugin v${plugin.version})`);
4013
4119
  }
4014
4120
  }
4015
- const skillsDir = join24(home, ".claude", "skills");
4016
- if (existsSync7(skillsDir)) {
4121
+ const skillsDir = join26(home, ".claude", "skills");
4122
+ if (existsSync8(skillsDir)) {
4017
4123
  const found = findSkillMd(skillsDir);
4018
4124
  if (found.length > 0) {
4019
4125
  return check("pass", "skills_installed", "Skills installed", `${found.length} SKILL.md file(s) found (CLI install)`);
@@ -4023,10 +4129,10 @@ function checkSkillsInstalled(home, cwd) {
4023
4129
  }
4024
4130
  function checkDuplicateSkills(home) {
4025
4131
  const plugin = findPlugin(home);
4026
- const skillsDir = join24(home, ".claude", "skills");
4132
+ const skillsDir = join26(home, ".claude", "skills");
4027
4133
  const agentSkillDirs = [
4028
- { label: "~/.codex/skills/", path: join24(home, ".codex", "skills") },
4029
- { label: "~/.agents/skills/", path: join24(home, ".agents", "skills") }
4134
+ { label: "~/.codex/skills/", path: join26(home, ".codex", "skills") },
4135
+ { label: "~/.agents/skills/", path: join26(home, ".agents", "skills") }
4030
4136
  ];
4031
4137
  if (!plugin) {
4032
4138
  return check("pass", "skills_duplicate", "Skills not duplicated", "Single install channel");
@@ -4103,8 +4209,8 @@ async function checkProfiles(home) {
4103
4209
  }
4104
4210
  async function checkProjectLocalOverride(cwd) {
4105
4211
  const dir = cwd ?? process.cwd();
4106
- const envPath = join24(dir, ".skillwiki", ".env");
4107
- if (existsSync7(envPath)) {
4212
+ const envPath = join26(dir, ".skillwiki", ".env");
4213
+ if (existsSync8(envPath)) {
4108
4214
  return check("pass", "project_local", "Project-local config", `Found: ${envPath}`);
4109
4215
  }
4110
4216
  return check("pass", "project_local", "Project-local config", "None");
@@ -4113,7 +4219,7 @@ function checkVaultGitRemote(resolvedPath) {
4113
4219
  if (resolvedPath === void 0) {
4114
4220
  return check("error", "vault_git_remote", "Vault git remote", "Cannot check \u2014 WIKI_PATH not resolved");
4115
4221
  }
4116
- if (!existsSync7(join24(resolvedPath, ".git"))) {
4222
+ if (!existsSync8(join26(resolvedPath, ".git"))) {
4117
4223
  return check("warn", "vault_git_remote", "Vault git remote", "Vault is not a git repository \u2014 sync features unavailable");
4118
4224
  }
4119
4225
  try {
@@ -4136,9 +4242,9 @@ function checkObsidianTemplates(resolvedPath) {
4136
4242
  return check("error", "obsidian_templates", "Obsidian templates", "Cannot check \u2014 WIKI_PATH not resolved");
4137
4243
  }
4138
4244
  const missing = [];
4139
- if (!existsSync7(join24(resolvedPath, "_Templates"))) missing.push("_Templates/");
4140
- if (!existsSync7(join24(resolvedPath, ".obsidian", "templates.json"))) missing.push(".obsidian/templates.json");
4141
- if (!existsSync7(join24(resolvedPath, ".obsidian", "app.json"))) missing.push(".obsidian/app.json");
4245
+ if (!existsSync8(join26(resolvedPath, "_Templates"))) missing.push("_Templates/");
4246
+ if (!existsSync8(join26(resolvedPath, ".obsidian", "templates.json"))) missing.push(".obsidian/templates.json");
4247
+ if (!existsSync8(join26(resolvedPath, ".obsidian", "app.json"))) missing.push(".obsidian/app.json");
4142
4248
  if (missing.length === 0) {
4143
4249
  return check("pass", "obsidian_templates", "Obsidian templates", "Template folder and config present");
4144
4250
  }
@@ -4148,8 +4254,8 @@ function checkDotStoreClean(resolvedPath) {
4148
4254
  if (resolvedPath === void 0) {
4149
4255
  return check("error", "dsstore_clean", "No .DS_Store in raw/", "Cannot check \u2014 WIKI_PATH not resolved");
4150
4256
  }
4151
- const rawDir = join24(resolvedPath, "raw");
4152
- if (!existsSync7(rawDir)) {
4257
+ const rawDir = join26(resolvedPath, "raw");
4258
+ if (!existsSync8(rawDir)) {
4153
4259
  return check("pass", "dsstore_clean", "No .DS_Store in raw/", "raw/ directory not found \u2014 check skipped");
4154
4260
  }
4155
4261
  const found = [];
@@ -4164,7 +4270,7 @@ function checkDotStoreClean(resolvedPath) {
4164
4270
  if (entry.name === ".DS_Store") {
4165
4271
  found.push(rel ? `${rel}/.DS_Store` : ".DS_Store");
4166
4272
  } else if (entry.isDirectory()) {
4167
- walk2(join24(dir, entry.name), rel ? `${rel}/${entry.name}` : entry.name);
4273
+ walk2(join26(dir, entry.name), rel ? `${rel}/${entry.name}` : entry.name);
4168
4274
  }
4169
4275
  }
4170
4276
  })(rawDir, "");
@@ -4177,7 +4283,7 @@ function checkSyncLastPush(resolvedPath) {
4177
4283
  if (resolvedPath === void 0) {
4178
4284
  return check("error", "sync_last_push", "Vault sync recency", "Cannot check \u2014 WIKI_PATH not resolved");
4179
4285
  }
4180
- if (!existsSync7(join24(resolvedPath, ".git"))) {
4286
+ if (!existsSync8(join26(resolvedPath, ".git"))) {
4181
4287
  return check("pass", "sync_last_push", "Vault sync recency", "No git repo \u2014 sync check skipped");
4182
4288
  }
4183
4289
  let timestamp;
@@ -4218,8 +4324,8 @@ function checkS3MountPerf(resolvedPath) {
4218
4324
  return check("pass", "s3_mount_perf", "S3 mount performance", "local disk");
4219
4325
  }
4220
4326
  const mountPoint = fuse.mountPoint;
4221
- const conceptsDir = join24(resolvedPath, "concepts");
4222
- if (!existsSync7(conceptsDir)) {
4327
+ const conceptsDir = join26(resolvedPath, "concepts");
4328
+ if (!existsSync8(conceptsDir)) {
4223
4329
  return check("pass", "s3_mount_perf", "S3 mount performance", `S3 FUSE mount (${mountPoint}), no concepts/ to benchmark`);
4224
4330
  }
4225
4331
  const start = Date.now();
@@ -4337,8 +4443,8 @@ function checkWriteTest(resolvedPath) {
4337
4443
  if (!fuse) {
4338
4444
  return check("pass", "s3_write_test", "S3 write test", "local disk \u2014 check skipped");
4339
4445
  }
4340
- const conceptsDir = join24(resolvedPath, "concepts");
4341
- if (!existsSync7(conceptsDir)) {
4446
+ const conceptsDir = join26(resolvedPath, "concepts");
4447
+ if (!existsSync8(conceptsDir)) {
4342
4448
  return check("pass", "s3_write_test", "S3 write test", "no concepts/ dir to test \u2014 check skipped");
4343
4449
  }
4344
4450
  const result = writeTest(conceptsDir);
@@ -4424,7 +4530,7 @@ function checkVfsCacheHealth(resolvedPath) {
4424
4530
  }
4425
4531
  function readVaultSyncConfig(home) {
4426
4532
  try {
4427
- const content = readFileSync7(join24(home, ".skillwiki", ".env"), "utf8");
4533
+ const content = readFileSync7(join26(home, ".skillwiki", ".env"), "utf8");
4428
4534
  let installed = false;
4429
4535
  let role;
4430
4536
  for (const line of content.split(/\r?\n/)) {
@@ -4458,12 +4564,12 @@ function vaultSyncChecks(input) {
4458
4564
  ];
4459
4565
  }
4460
4566
  const isMac = os === "darwin";
4461
- const logDir = input.logDir ?? (isMac ? join24(home, "Library", "Logs") : join24(home, ".local", "state", "vault-sync", "log"));
4462
- const shareDir = input.shareDir ?? (isMac ? join24(home, "Library", "Application Support", "vault-sync", "bin") : join24(home, ".local", "share", "vault-sync", "bin"));
4463
- const filterPath = input.filterPath ?? join24(home, ".config", "rclone", "wiki-push-filters.txt");
4567
+ const logDir = input.logDir ?? (isMac ? join26(home, "Library", "Logs") : join26(home, ".local", "state", "vault-sync", "log"));
4568
+ const shareDir = input.shareDir ?? (isMac ? join26(home, "Library", "Application Support", "vault-sync", "bin") : join26(home, ".local", "share", "vault-sync", "bin"));
4569
+ const filterPath = input.filterPath ?? join26(home, ".config", "rclone", "wiki-push-filters.txt");
4464
4570
  const snapshotPath = input.snapshotScriptPath ?? "/root/.hermes/scripts/wiki-snapshot-v3.sh";
4465
- const pushScriptPath = join24(shareDir, "wiki-push.sh");
4466
- 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`);
4571
+ const pushScriptPath = join26(shareDir, "wiki-push.sh");
4572
+ 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`);
4467
4573
  let c2;
4468
4574
  try {
4469
4575
  if (isMac) {
@@ -4514,7 +4620,7 @@ function vaultSyncChecks(input) {
4514
4620
  "Scheduler check failed \u2014 run vault-sync-install"
4515
4621
  );
4516
4622
  }
4517
- const logFile = join24(logDir, "wiki-push.log");
4623
+ const logFile = join26(logDir, "wiki-push.log");
4518
4624
  let c3;
4519
4625
  try {
4520
4626
  const logContent = readFileSync7(logFile, "utf8");
@@ -4573,7 +4679,7 @@ function vaultSyncChecks(input) {
4573
4679
  }
4574
4680
  }
4575
4681
  } catch {
4576
- c3 = existsSync7(logDir) ? check(
4682
+ c3 = existsSync8(logDir) ? check(
4577
4683
  "warn",
4578
4684
  "vault_sync_last_push_age",
4579
4685
  "Vault sync last push recency",
@@ -4585,7 +4691,7 @@ function vaultSyncChecks(input) {
4585
4691
  `Log directory not found at ${logDir}`
4586
4692
  );
4587
4693
  }
4588
- const fetchLogFile = join24(logDir, "wiki-fetch.log");
4694
+ const fetchLogFile = join26(logDir, "wiki-fetch.log");
4589
4695
  let cFetch;
4590
4696
  try {
4591
4697
  const logContent = readFileSync7(fetchLogFile, "utf8");
@@ -4632,7 +4738,7 @@ function vaultSyncChecks(input) {
4632
4738
  }
4633
4739
  let c4;
4634
4740
  try {
4635
- if (!existsSync7(filterPath)) {
4741
+ if (!existsSync8(filterPath)) {
4636
4742
  c4 = check(
4637
4743
  "error",
4638
4744
  "vault_sync_filter_present",
@@ -4681,7 +4787,7 @@ function vaultSyncChecks(input) {
4681
4787
  );
4682
4788
  } else {
4683
4789
  try {
4684
- if (!existsSync7(snapshotPath)) {
4790
+ if (!existsSync8(snapshotPath)) {
4685
4791
  c5 = check(
4686
4792
  "error",
4687
4793
  "vault_sync_snapshot_guard",
@@ -4727,9 +4833,9 @@ function findSkillMd(dir) {
4727
4833
  }
4728
4834
  for (const entry of entries) {
4729
4835
  if (entry.isFile() && entry.name === "SKILL.md") {
4730
- results.push(join24(dir, entry.name));
4836
+ results.push(join26(dir, entry.name));
4731
4837
  } else if (entry.isDirectory()) {
4732
- results.push(...findSkillMd(join24(dir, entry.name)));
4838
+ results.push(...findSkillMd(join26(dir, entry.name)));
4733
4839
  }
4734
4840
  }
4735
4841
  return results;
@@ -4743,7 +4849,7 @@ function findSkillNames(dir) {
4743
4849
  return results;
4744
4850
  }
4745
4851
  for (const entry of entries) {
4746
- if (entry.isDirectory() && existsSync7(join24(dir, entry.name, "SKILL.md"))) {
4852
+ if (entry.isDirectory() && existsSync8(join26(dir, entry.name, "SKILL.md"))) {
4747
4853
  results.push(entry.name);
4748
4854
  }
4749
4855
  }
@@ -4807,8 +4913,8 @@ async function runDoctor(input) {
4807
4913
  }
4808
4914
 
4809
4915
  // src/commands/archive.ts
4810
- import { rename as rename6, mkdir as mkdir8, readFile as readFile18, writeFile as writeFile9 } from "fs/promises";
4811
- import { join as join25, dirname as dirname9 } from "path";
4916
+ import { rename as rename7, mkdir as mkdir8, readFile as readFile19, writeFile as writeFile10 } from "fs/promises";
4917
+ import { join as join27, dirname as dirname9 } from "path";
4812
4918
  function countWikilinks(body, slug) {
4813
4919
  const escaped = slug.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4814
4920
  const re = new RegExp(`\\[\\[${escaped}(?:[|#][^\\]]*)?\\]\\]`, "g");
@@ -4836,7 +4942,7 @@ async function runArchive(input) {
4836
4942
  if (!relPath) return { exitCode: ExitCode.ARCHIVE_TARGET_NOT_FOUND, result: err("ARCHIVE_TARGET_NOT_FOUND", { page: input.page }) };
4837
4943
  if (relPath.startsWith("_archive/")) return { exitCode: ExitCode.ARCHIVE_ALREADY_ARCHIVED, result: err("ARCHIVE_ALREADY_ARCHIVED", { page: relPath }) };
4838
4944
  const slug = relPath.replace(/\.md$/, "").split("/").pop();
4839
- const archivePath = join25("_archive", relPath).replace(/\\/g, "/");
4945
+ const archivePath = join27("_archive", relPath).replace(/\\/g, "/");
4840
4946
  let cascade;
4841
4947
  if (input.cascade) {
4842
4948
  const wikilinkRefs = [];
@@ -4860,7 +4966,7 @@ async function runArchive(input) {
4860
4966
  const indexRefs = [];
4861
4967
  if (!isRaw) {
4862
4968
  try {
4863
- const idx = await readFile18(join25(input.vault, "index.md"), "utf8");
4969
+ const idx = await readFile19(join27(input.vault, "index.md"), "utf8");
4864
4970
  idx.split("\n").forEach((line, i) => {
4865
4971
  if (line.includes(`[[${slug}]]`)) indexRefs.push({ line: i + 1, text: line });
4866
4972
  });
@@ -4886,8 +4992,8 @@ async function runArchive(input) {
4886
4992
  }
4887
4993
  if (input.cascade && input.apply && cascade) {
4888
4994
  for (const ref of cascade.source_array_refs) {
4889
- const absPath = join25(input.vault, ref.page);
4890
- const text = await readFile18(absPath, "utf8");
4995
+ const absPath = join27(input.vault, ref.page);
4996
+ const text = await readFile19(absPath, "utf8");
4891
4997
  const split = splitFrontmatter(text);
4892
4998
  if (!split.ok) continue;
4893
4999
  const before = split.data.rawFrontmatter;
@@ -4898,29 +5004,29 @@ async function runArchive(input) {
4898
5004
  );
4899
5005
  if (fmRewritten === before) continue;
4900
5006
  if (!arraysEqual(ref.sources_after, ref.sources_before)) {
4901
- await writeFile9(absPath, `---
5007
+ await writeFile10(absPath, `---
4902
5008
  ${fmRewritten}
4903
5009
  ---${split.data.body}`, "utf8");
4904
5010
  }
4905
5011
  }
4906
5012
  }
4907
- await mkdir8(dirname9(join25(input.vault, archivePath)), { recursive: true });
5013
+ await mkdir8(dirname9(join27(input.vault, archivePath)), { recursive: true });
4908
5014
  let indexUpdated = false;
4909
5015
  if (!isRaw) {
4910
- const indexPath = join25(input.vault, "index.md");
5016
+ const indexPath = join27(input.vault, "index.md");
4911
5017
  try {
4912
- const idx = await readFile18(indexPath, "utf8");
5018
+ const idx = await readFile19(indexPath, "utf8");
4913
5019
  const originalLines = idx.split("\n");
4914
5020
  const filtered = originalLines.filter((l) => !l.includes(`[[${slug}]]`));
4915
5021
  if (filtered.length !== originalLines.length) {
4916
- await writeFile9(indexPath, filtered.join("\n"), "utf8");
5022
+ await writeFile10(indexPath, filtered.join("\n"), "utf8");
4917
5023
  indexUpdated = true;
4918
5024
  }
4919
5025
  } catch (e) {
4920
5026
  if (e instanceof Error && "code" in e && e.code !== "ENOENT") throw e;
4921
5027
  }
4922
5028
  }
4923
- await rename6(join25(input.vault, relPath), join25(input.vault, archivePath));
5029
+ await rename7(join27(input.vault, relPath), join27(input.vault, archivePath));
4924
5030
  appendLastOp(input.vault, {
4925
5031
  operation: input.cascade ? "archive-cascade" : "archive",
4926
5032
  summary: `moved ${relPath} to ${archivePath}${input.cascade ? ` (cascade: ${cascade?.source_array_refs.length ?? 0} source arrays updated)` : ""}`,
@@ -5307,14 +5413,14 @@ ${newBody}`;
5307
5413
  // src/commands/update.ts
5308
5414
  import { execSync as execSync3 } from "child_process";
5309
5415
  import { readFileSync as readFileSync8 } from "fs";
5310
- import { join as join26 } from "path";
5416
+ import { join as join28 } from "path";
5311
5417
  function resolveGlobalSkillsRoot() {
5312
5418
  try {
5313
5419
  const globalRoot = execSync3("npm root -g", {
5314
5420
  encoding: "utf8",
5315
5421
  timeout: 5e3
5316
5422
  }).trim();
5317
- return join26(globalRoot, "skillwiki", "skills");
5423
+ return join28(globalRoot, "skillwiki", "skills");
5318
5424
  } catch {
5319
5425
  return null;
5320
5426
  }
@@ -5340,7 +5446,7 @@ async function runUpdate(input) {
5340
5446
  );
5341
5447
  const currentVersion = pkg2.version;
5342
5448
  const tag = input.distTag ?? "latest";
5343
- const target = join26(input.home, ".claude", "skills");
5449
+ const target = join28(input.home, ".claude", "skills");
5344
5450
  let latest;
5345
5451
  try {
5346
5452
  latest = execSync3(`npm view skillwiki@${tag} version`, {
@@ -5410,16 +5516,16 @@ async function runUpdate(input) {
5410
5516
 
5411
5517
  // src/commands/self-update.ts
5412
5518
  import { execSync as execSync4 } from "child_process";
5413
- import { existsSync as existsSync8, readFileSync as readFileSync9 } from "fs";
5414
- import { join as join27 } from "path";
5519
+ import { existsSync as existsSync9, readFileSync as readFileSync9 } from "fs";
5520
+ import { join as join29 } from "path";
5415
5521
  var DEFAULT_SOURCE_ROOT_SUFFIX = "/Desktop/code/llm-wiki";
5416
5522
  async function runSelfUpdate(input) {
5417
5523
  const currentVersion = JSON.parse(
5418
5524
  readFileSync9(new URL("../../package.json", import.meta.url), "utf8")
5419
5525
  ).version;
5420
5526
  const sourceRoot = input.sourceRoot ?? `${input.home}${DEFAULT_SOURCE_ROOT_SUFFIX}`;
5421
- const localPkgPath = join27(sourceRoot, "packages", "cli", "package.json");
5422
- const hasLocalSource = existsSync8(localPkgPath);
5527
+ const localPkgPath = join29(sourceRoot, "packages", "cli", "package.json");
5528
+ const hasLocalSource = existsSync9(localPkgPath);
5423
5529
  if (input.check) {
5424
5530
  let availableVersion = null;
5425
5531
  let source;
@@ -5550,10 +5656,10 @@ async function runSelfUpdate(input) {
5550
5656
  }
5551
5657
 
5552
5658
  // src/commands/transcripts.ts
5553
- import { readdir as readdir5, stat as stat6, readFile as readFile19 } from "fs/promises";
5554
- import { join as join28 } from "path";
5659
+ import { readdir as readdir5, stat as stat7, readFile as readFile20 } from "fs/promises";
5660
+ import { join as join30 } from "path";
5555
5661
  async function runTranscripts(input) {
5556
- const dir = join28(input.vault, "raw", "transcripts");
5662
+ const dir = join30(input.vault, "raw", "transcripts");
5557
5663
  let entries;
5558
5664
  try {
5559
5665
  entries = await readdir5(dir, { withFileTypes: true });
@@ -5563,13 +5669,13 @@ async function runTranscripts(input) {
5563
5669
  const transcripts = [];
5564
5670
  for (const entry of entries) {
5565
5671
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
5566
- const filePath = join28(dir, entry.name);
5567
- const content = await readFile19(filePath, "utf8");
5672
+ const filePath = join30(dir, entry.name);
5673
+ const content = await readFile20(filePath, "utf8");
5568
5674
  const fm = extractFrontmatter(content);
5569
5675
  if (!fm.ok) continue;
5570
5676
  const ingested = typeof fm.data.ingested === "string" ? fm.data.ingested : "";
5571
5677
  if (input.since && ingested && ingested < input.since) continue;
5572
- const s = await stat6(filePath);
5678
+ const s = await stat7(filePath);
5573
5679
  transcripts.push({
5574
5680
  file: `raw/transcripts/${entry.name}`,
5575
5681
  ingested,
@@ -5581,12 +5687,12 @@ async function runTranscripts(input) {
5581
5687
  }
5582
5688
 
5583
5689
  // src/commands/project-index.ts
5584
- import { readdir as readdir6, readFile as readFile20, writeFile as writeFile10, mkdir as mkdir9 } from "fs/promises";
5585
- import { join as join29, dirname as dirname10 } from "path";
5690
+ import { readdir as readdir6, readFile as readFile21, writeFile as writeFile11, mkdir as mkdir9 } from "fs/promises";
5691
+ import { join as join31, dirname as dirname10 } from "path";
5586
5692
  var LAYER2_DIRS = ["entities", "concepts", "comparisons", "queries", "meta"];
5587
5693
  async function runProjectIndex(input) {
5588
5694
  const slug = input.slug;
5589
- const projectDir = join29(input.vault, "projects", slug);
5695
+ const projectDir = join31(input.vault, "projects", slug);
5590
5696
  try {
5591
5697
  await readdir6(projectDir);
5592
5698
  } catch {
@@ -5597,15 +5703,15 @@ async function runProjectIndex(input) {
5597
5703
  }
5598
5704
  const wikilinkPattern = `[[${slug}]]`;
5599
5705
  const entries = [];
5600
- const compoundDir = join29(input.vault, "projects", slug, "compound");
5706
+ const compoundDir = join31(input.vault, "projects", slug, "compound");
5601
5707
  try {
5602
5708
  const compoundFiles = await readdir6(compoundDir, { withFileTypes: true });
5603
5709
  for (const entry of compoundFiles) {
5604
5710
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
5605
- const filePath = join29(compoundDir, entry.name);
5711
+ const filePath = join31(compoundDir, entry.name);
5606
5712
  let text;
5607
5713
  try {
5608
- text = await readFile20(filePath, "utf8");
5714
+ text = await readFile21(filePath, "utf8");
5609
5715
  } catch {
5610
5716
  continue;
5611
5717
  }
@@ -5622,16 +5728,16 @@ async function runProjectIndex(input) {
5622
5728
  for (const dir of LAYER2_DIRS) {
5623
5729
  let files;
5624
5730
  try {
5625
- files = await readdir6(join29(input.vault, dir), { withFileTypes: true });
5731
+ files = await readdir6(join31(input.vault, dir), { withFileTypes: true });
5626
5732
  } catch {
5627
5733
  continue;
5628
5734
  }
5629
5735
  for (const entry of files) {
5630
5736
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
5631
- const filePath = join29(input.vault, dir, entry.name);
5737
+ const filePath = join31(input.vault, dir, entry.name);
5632
5738
  let text;
5633
5739
  try {
5634
- text = await readFile20(filePath, "utf8");
5740
+ text = await readFile21(filePath, "utf8");
5635
5741
  } catch {
5636
5742
  continue;
5637
5743
  }
@@ -5652,11 +5758,11 @@ async function runProjectIndex(input) {
5652
5758
  const tb = typeOrder[b.type] ?? 99;
5653
5759
  return ta !== tb ? ta - tb : a.title.localeCompare(b.title);
5654
5760
  });
5655
- const indexPath = join29(projectDir, "knowledge.md");
5761
+ const indexPath = join31(projectDir, "knowledge.md");
5656
5762
  let existing = false;
5657
5763
  let stale = false;
5658
5764
  try {
5659
- const existingText = await readFile20(indexPath, "utf8");
5765
+ const existingText = await readFile21(indexPath, "utf8");
5660
5766
  existing = true;
5661
5767
  const existingEntries = existingText.split("\n").filter((l) => l.startsWith("- [["));
5662
5768
  const existingPages = new Set(existingEntries.map((l) => {
@@ -5697,7 +5803,7 @@ Autogenerated by \`skillwiki project-index\` on ${today}.
5697
5803
  if (input.apply) {
5698
5804
  try {
5699
5805
  await mkdir9(dirname10(indexPath), { recursive: true });
5700
- await writeFile10(indexPath, body, "utf8");
5806
+ await writeFile11(indexPath, body, "utf8");
5701
5807
  } catch (e) {
5702
5808
  return {
5703
5809
  exitCode: ExitCode.WRITE_FAILED,
@@ -5725,10 +5831,10 @@ ${entries.map((e) => ` ${e.type}: [[${e.page.replace(/\.md$/, "")}]] \u2014 ${e
5725
5831
  }
5726
5832
 
5727
5833
  // src/commands/compound.ts
5728
- import { writeFile as writeFile11, mkdir as mkdir10, readdir as readdir7, unlink as unlink3 } from "fs/promises";
5729
- import { join as join30 } from "path";
5730
- import { existsSync as existsSync9 } from "fs";
5731
- import { readFile as readFile21 } from "fs/promises";
5834
+ import { writeFile as writeFile12, mkdir as mkdir10, readdir as readdir7, unlink as unlink3 } from "fs/promises";
5835
+ import { join as join32 } from "path";
5836
+ import { existsSync as existsSync10 } from "fs";
5837
+ import { readFile as readFile22 } from "fs/promises";
5732
5838
  var RETRO_HEADING_RE = /^## \[(\d{4}-\d{2}-\d{2})(?:\s+[^\]]+)?\] retro \| loop cycle(?: (\d+))?: (.+)$/;
5733
5839
  var FIELD_RE = {
5734
5840
  improve: /^-\s+\*?\*?Improve:?\*?\*?\s*(.+)$/m,
@@ -5826,17 +5932,17 @@ function extractRetroFields(date, cycleName, block) {
5826
5932
  };
5827
5933
  }
5828
5934
  async function runCompound(input) {
5829
- const logPath = join30(input.vault, "log.md");
5935
+ const logPath = join32(input.vault, "log.md");
5830
5936
  let logText;
5831
5937
  try {
5832
- logText = await readFile21(logPath, "utf8");
5938
+ logText = await readFile22(logPath, "utf8");
5833
5939
  } catch {
5834
5940
  return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path: logPath }) };
5835
5941
  }
5836
5942
  const entries = parseRetroEntries(logText);
5837
5943
  const promoted = [];
5838
5944
  const skipped = [];
5839
- const compoundDir = join30(input.vault, "projects", input.project, "compound");
5945
+ const compoundDir = join32(input.vault, "projects", input.project, "compound");
5840
5946
  for (const entry of entries) {
5841
5947
  const generalizeValue = entry.generalize.trim();
5842
5948
  if (!/^yes/i.test(generalizeValue)) {
@@ -5844,8 +5950,8 @@ async function runCompound(input) {
5844
5950
  continue;
5845
5951
  }
5846
5952
  const slug = slugify(entry.cycleName);
5847
- const compoundPath = join30(compoundDir, `${slug}.md`);
5848
- if (existsSync9(compoundPath)) {
5953
+ const compoundPath = join32(compoundDir, `${slug}.md`);
5954
+ if (existsSync10(compoundPath)) {
5849
5955
  skipped.push(entry.date);
5850
5956
  continue;
5851
5957
  }
@@ -5883,10 +5989,10 @@ async function runCompound(input) {
5883
5989
  ].join("\n");
5884
5990
  const content = frontmatter + "\n" + body;
5885
5991
  if (!input.dryRun) {
5886
- if (!existsSync9(compoundDir)) {
5992
+ if (!existsSync10(compoundDir)) {
5887
5993
  await mkdir10(compoundDir, { recursive: true });
5888
5994
  }
5889
- await writeFile11(compoundPath, content, "utf8");
5995
+ await writeFile12(compoundPath, content, "utf8");
5890
5996
  }
5891
5997
  promoted.push(`${slug}.md`);
5892
5998
  }
@@ -5905,16 +6011,16 @@ async function runCompound(input) {
5905
6011
  };
5906
6012
  }
5907
6013
  async function runCompoundDelete(input) {
5908
- const projectDir = join30(input.vault, "projects", input.project);
5909
- if (!existsSync9(projectDir)) {
6014
+ const projectDir = join32(input.vault, "projects", input.project);
6015
+ if (!existsSync10(projectDir)) {
5910
6016
  return {
5911
6017
  exitCode: ExitCode.PROJECT_NOT_FOUND,
5912
6018
  result: err("PROJECT_NOT_FOUND", { slug: input.project, path: projectDir })
5913
6019
  };
5914
6020
  }
5915
6021
  const entryName = input.entry.replace(/\.md$/, "");
5916
- const compoundPath = join30(projectDir, "compound", `${entryName}.md`);
5917
- if (!existsSync9(compoundPath)) {
6022
+ const compoundPath = join32(projectDir, "compound", `${entryName}.md`);
6023
+ if (!existsSync10(compoundPath)) {
5918
6024
  return {
5919
6025
  exitCode: ExitCode.FILE_NOT_FOUND,
5920
6026
  result: err("FILE_NOT_FOUND", { path: compoundPath })
@@ -5947,8 +6053,8 @@ knowledge.md regenerated`
5947
6053
  };
5948
6054
  }
5949
6055
  async function runCompoundList(input) {
5950
- const compoundDir = join30(input.vault, "projects", input.project, "compound");
5951
- if (!existsSync9(compoundDir)) {
6056
+ const compoundDir = join32(input.vault, "projects", input.project, "compound");
6057
+ if (!existsSync10(compoundDir)) {
5952
6058
  return {
5953
6059
  exitCode: ExitCode.OK,
5954
6060
  result: ok({
@@ -5978,10 +6084,10 @@ could not read compound directory`
5978
6084
  const entries = [];
5979
6085
  for (const dirent of dirents) {
5980
6086
  if (!dirent.isFile() || !dirent.name.endsWith(".md")) continue;
5981
- const filePath = join30(compoundDir, dirent.name);
6087
+ const filePath = join32(compoundDir, dirent.name);
5982
6088
  let text;
5983
6089
  try {
5984
- text = await readFile21(filePath, "utf8");
6090
+ text = await readFile22(filePath, "utf8");
5985
6091
  } catch {
5986
6092
  continue;
5987
6093
  }
@@ -6010,9 +6116,9 @@ no compound entries found`;
6010
6116
  }
6011
6117
 
6012
6118
  // src/commands/observe.ts
6013
- import { mkdir as mkdir11, writeFile as writeFile12 } from "fs/promises";
6014
- import { existsSync as existsSync10, statSync as statSync3 } from "fs";
6015
- import { join as join31 } from "path";
6119
+ import { mkdir as mkdir11, writeFile as writeFile13 } from "fs/promises";
6120
+ import { existsSync as existsSync11, statSync as statSync4 } from "fs";
6121
+ import { join as join33 } from "path";
6016
6122
  import { createHash as createHash4 } from "crypto";
6017
6123
  var ALLOWED_KINDS = /* @__PURE__ */ new Set(["note", "bug", "task", "idea", "session-log"]);
6018
6124
  function slugify2(text) {
@@ -6035,13 +6141,13 @@ async function runObserve(input) {
6035
6141
  result: err("SCHEME_REJECTED", { message: "Text must not be empty" })
6036
6142
  };
6037
6143
  }
6038
- if (!existsSync10(input.vault) || !statSync3(input.vault).isDirectory()) {
6144
+ if (!existsSync11(input.vault) || !statSync4(input.vault).isDirectory()) {
6039
6145
  return {
6040
6146
  exitCode: ExitCode.VAULT_PATH_INVALID,
6041
6147
  result: err("VAULT_PATH_INVALID", { path: input.vault })
6042
6148
  };
6043
6149
  }
6044
- const transcriptsDir = join31(input.vault, "raw", "transcripts");
6150
+ const transcriptsDir = join33(input.vault, "raw", "transcripts");
6045
6151
  try {
6046
6152
  await mkdir11(transcriptsDir, { recursive: true });
6047
6153
  } catch {
@@ -6053,7 +6159,7 @@ async function runObserve(input) {
6053
6159
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
6054
6160
  const slug = slugify2(input.text);
6055
6161
  const fileName = `${today}-observation-${slug}.md`;
6056
- const filePath = join31(transcriptsDir, fileName);
6162
+ const filePath = join33(transcriptsDir, fileName);
6057
6163
  const body = `
6058
6164
  ${input.text.trim()}
6059
6165
  `;
@@ -6071,7 +6177,7 @@ ${input.text.trim()}
6071
6177
  frontmatterLines.push("---");
6072
6178
  const content = frontmatterLines.join("\n") + body;
6073
6179
  try {
6074
- await writeFile12(filePath, content, "utf8");
6180
+ await writeFile13(filePath, content, "utf8");
6075
6181
  } catch (e) {
6076
6182
  return {
6077
6183
  exitCode: ExitCode.WRITE_FAILED,
@@ -6093,8 +6199,8 @@ ${input.text.trim()}
6093
6199
  }
6094
6200
 
6095
6201
  // src/commands/ingest.ts
6096
- import { readFile as readFile22, writeFile as writeFile13, mkdir as mkdir12 } from "fs/promises";
6097
- import { join as join32 } from "path";
6202
+ import { readFile as readFile23, writeFile as writeFile14, mkdir as mkdir12 } from "fs/promises";
6203
+ import { join as join34 } from "path";
6098
6204
  import { createHash as createHash5 } from "crypto";
6099
6205
  var ALLOWED_TYPES = /* @__PURE__ */ new Set(["entity", "concept", "comparison", "query"]);
6100
6206
  var TYPE_DIR = {
@@ -6253,7 +6359,7 @@ async function runIngest(input) {
6253
6359
  sourceContent = fetchResult.data.body;
6254
6360
  } else {
6255
6361
  try {
6256
- sourceContent = await readFile22(input.source, "utf8");
6362
+ sourceContent = await readFile23(input.source, "utf8");
6257
6363
  } catch {
6258
6364
  return {
6259
6365
  exitCode: ExitCode.FILE_NOT_FOUND,
@@ -6268,8 +6374,8 @@ async function runIngest(input) {
6268
6374
  const rawRelPath = `raw/articles/${slug}.md`;
6269
6375
  const typedDir = TYPE_DIR[input.type] ?? `${input.type}s`;
6270
6376
  const typedRelPath = `${typedDir}/${slug}.md`;
6271
- const rawAbsPath = join32(input.vault, rawRelPath);
6272
- const typedAbsPath = join32(input.vault, typedRelPath);
6377
+ const rawAbsPath = join34(input.vault, rawRelPath);
6378
+ const typedAbsPath = join34(input.vault, typedRelPath);
6273
6379
  const rawContent = buildRawContent(sourceUrl, today, sha256, sourceContent);
6274
6380
  const typedContent = buildTypedContent(
6275
6381
  input.title,
@@ -6332,8 +6438,8 @@ async function runIngest(input) {
6332
6438
  };
6333
6439
  }
6334
6440
  try {
6335
- await mkdir12(join32(input.vault, "raw", "articles"), { recursive: true });
6336
- await writeFile13(rawAbsPath, rawContent, "utf8");
6441
+ await mkdir12(join34(input.vault, "raw", "articles"), { recursive: true });
6442
+ await writeFile14(rawAbsPath, rawContent, "utf8");
6337
6443
  } catch (e) {
6338
6444
  return {
6339
6445
  exitCode: ExitCode.WRITE_FAILED,
@@ -6341,8 +6447,8 @@ async function runIngest(input) {
6341
6447
  };
6342
6448
  }
6343
6449
  try {
6344
- await mkdir12(join32(input.vault, typedDir), { recursive: true });
6345
- await writeFile13(typedAbsPath, typedContent, "utf8");
6450
+ await mkdir12(join34(input.vault, typedDir), { recursive: true });
6451
+ await writeFile14(typedAbsPath, typedContent, "utf8");
6346
6452
  } catch (e) {
6347
6453
  return {
6348
6454
  exitCode: ExitCode.WRITE_FAILED,
@@ -6520,12 +6626,12 @@ ${body}`;
6520
6626
  }
6521
6627
 
6522
6628
  // src/commands/sync.ts
6523
- import { existsSync as existsSync12 } from "fs";
6524
- import { join as join34 } from "path";
6629
+ import { existsSync as existsSync13 } from "fs";
6630
+ import { join as join36 } from "path";
6525
6631
 
6526
6632
  // src/utils/sync-lock.ts
6527
- import { existsSync as existsSync11, mkdirSync as mkdirSync3, readFileSync as readFileSync10, renameSync, unlinkSync as unlinkSync4, writeFileSync as writeFileSync5 } from "fs";
6528
- import { join as join33 } from "path";
6633
+ import { existsSync as existsSync12, mkdirSync as mkdirSync4, readFileSync as readFileSync10, renameSync, unlinkSync as unlinkSync5, writeFileSync as writeFileSync6 } from "fs";
6634
+ import { join as join35 } from "path";
6529
6635
  import { createHash as createHash6 } from "crypto";
6530
6636
  function getSessionId() {
6531
6637
  if (process.env.CLAUDE_SESSION_ID) return process.env.CLAUDE_SESSION_ID;
@@ -6533,11 +6639,11 @@ function getSessionId() {
6533
6639
  return process.pid.toString();
6534
6640
  }
6535
6641
  function lockPath(vault) {
6536
- return join33(vault, ".skillwiki", "sync.lock");
6642
+ return join35(vault, ".skillwiki", "sync.lock");
6537
6643
  }
6538
6644
  function readLock(vault) {
6539
6645
  const path = lockPath(vault);
6540
- if (!existsSync11(path)) return null;
6646
+ if (!existsSync12(path)) return null;
6541
6647
  try {
6542
6648
  const raw = readFileSync10(path, "utf8");
6543
6649
  return JSON.parse(raw);
@@ -6552,9 +6658,9 @@ function isStale(lock, now) {
6552
6658
  }
6553
6659
  function acquireLock(vault, opts = {}) {
6554
6660
  const path = lockPath(vault);
6555
- const dir = join33(vault, ".skillwiki");
6556
- if (!existsSync11(dir)) {
6557
- mkdirSync3(dir, { recursive: true });
6661
+ const dir = join35(vault, ".skillwiki");
6662
+ if (!existsSync12(dir)) {
6663
+ mkdirSync4(dir, { recursive: true });
6558
6664
  }
6559
6665
  const sessionId = opts.sessionId ?? getSessionId();
6560
6666
  const summary = opts.summary ?? "skillwiki sync";
@@ -6573,7 +6679,7 @@ function acquireLock(vault, opts = {}) {
6573
6679
  };
6574
6680
  try {
6575
6681
  const content = JSON.stringify(lock, null, 2) + "\n";
6576
- writeFileSync5(path, content, { flag: "wx" });
6682
+ writeFileSync6(path, content, { flag: "wx" });
6577
6683
  return { ok: true, lock };
6578
6684
  } catch (e) {
6579
6685
  const err3 = e;
@@ -6593,19 +6699,19 @@ function acquireLock(vault, opts = {}) {
6593
6699
  function writeLockedFile(path, lock) {
6594
6700
  const tmp = path + ".tmp";
6595
6701
  const content = JSON.stringify(lock, null, 2) + "\n";
6596
- writeFileSync5(tmp, content);
6702
+ writeFileSync6(tmp, content);
6597
6703
  renameSync(tmp, path);
6598
6704
  }
6599
6705
  function releaseLock(vault, opts = {}) {
6600
6706
  const path = lockPath(vault);
6601
- if (!existsSync11(path)) {
6707
+ if (!existsSync12(path)) {
6602
6708
  return { released: false };
6603
6709
  }
6604
6710
  const sessionId = opts.sessionId ?? getSessionId();
6605
6711
  const existing = readLock(vault);
6606
6712
  if (opts.force) {
6607
6713
  try {
6608
- unlinkSync4(path);
6714
+ unlinkSync5(path);
6609
6715
  const prior = existing && existing.session_id !== sessionId ? existing : void 0;
6610
6716
  return { released: true, prior };
6611
6717
  } catch {
@@ -6616,7 +6722,7 @@ function releaseLock(vault, opts = {}) {
6616
6722
  return { released: false };
6617
6723
  }
6618
6724
  try {
6619
- unlinkSync4(path);
6725
+ unlinkSync5(path);
6620
6726
  return { released: true };
6621
6727
  } catch {
6622
6728
  return { released: false };
@@ -6627,7 +6733,7 @@ function releaseLock(vault, opts = {}) {
6627
6733
  function runSyncStatus(input) {
6628
6734
  const vault = input.vault;
6629
6735
  const includeStashes = input.includeStashes ?? false;
6630
- if (!existsSync12(join34(vault, ".git"))) {
6736
+ if (!existsSync13(join36(vault, ".git"))) {
6631
6737
  return {
6632
6738
  exitCode: ExitCode.VAULT_PATH_INVALID,
6633
6739
  result: ok({
@@ -6704,7 +6810,7 @@ function runSyncStatus(input) {
6704
6810
  }
6705
6811
  async function runSyncPush(input) {
6706
6812
  const vault = input.vault;
6707
- if (!existsSync12(join34(vault, ".git"))) {
6813
+ if (!existsSync13(join36(vault, ".git"))) {
6708
6814
  return {
6709
6815
  exitCode: ExitCode.VAULT_PATH_INVALID,
6710
6816
  result: err("NOT_A_GIT_REPO", { path: vault })
@@ -6808,7 +6914,7 @@ function enumerateStashes(vault) {
6808
6914
  }
6809
6915
  async function runSyncPull(input) {
6810
6916
  const vault = input.vault;
6811
- if (!existsSync12(join34(vault, ".git"))) {
6917
+ if (!existsSync13(join36(vault, ".git"))) {
6812
6918
  return {
6813
6919
  exitCode: ExitCode.VAULT_PATH_INVALID,
6814
6920
  result: err("NOT_A_GIT_REPO", { path: vault })
@@ -6976,7 +7082,7 @@ function runSyncPeers(input) {
6976
7082
  }
6977
7083
  function runSyncLock(input) {
6978
7084
  const vault = input.vault;
6979
- if (!existsSync12(vault)) {
7085
+ if (!existsSync13(vault)) {
6980
7086
  return {
6981
7087
  exitCode: ExitCode.VAULT_PATH_INVALID,
6982
7088
  result: err("VAULT_PATH_INVALID", { path: vault })
@@ -7011,7 +7117,7 @@ function runSyncLock(input) {
7011
7117
  }
7012
7118
  function runSyncUnlock(input) {
7013
7119
  const vault = input.vault;
7014
- if (!existsSync12(vault)) {
7120
+ if (!existsSync13(vault)) {
7015
7121
  return {
7016
7122
  exitCode: ExitCode.VAULT_PATH_INVALID,
7017
7123
  result: err("VAULT_PATH_INVALID", { path: vault })
@@ -7044,8 +7150,8 @@ function runSyncUnlock(input) {
7044
7150
  }
7045
7151
 
7046
7152
  // src/commands/backup.ts
7047
- import { statSync as statSync4, readdirSync as readdirSync2, readFileSync as readFileSync11, mkdirSync as mkdirSync4, writeFileSync as writeFileSync6 } from "fs";
7048
- import { join as join35, relative as relative3, dirname as dirname11 } from "path";
7153
+ import { statSync as statSync5, readdirSync as readdirSync2, readFileSync as readFileSync11, mkdirSync as mkdirSync5, writeFileSync as writeFileSync7 } from "fs";
7154
+ import { join as join37, relative as relative3, dirname as dirname11 } from "path";
7049
7155
  import { PutObjectCommand, HeadObjectCommand, ListObjectsV2Command, GetObjectCommand, DeleteObjectsCommand } from "@aws-sdk/client-s3";
7050
7156
 
7051
7157
  // src/utils/s3-client.ts
@@ -7069,7 +7175,7 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([".git", ".obsidian", "_archive", "node_
7069
7175
  function* walkMarkdown(dir, base) {
7070
7176
  for (const entry of readdirSync2(dir, { withFileTypes: true })) {
7071
7177
  if (SKIP_DIRS.has(entry.name)) continue;
7072
- const full = join35(dir, entry.name);
7178
+ const full = join37(dir, entry.name);
7073
7179
  if (entry.isDirectory()) {
7074
7180
  yield* walkMarkdown(full, base);
7075
7181
  } else if (entry.name.endsWith(".md")) {
@@ -7092,8 +7198,8 @@ async function runBackupSync(input) {
7092
7198
  let failed = 0;
7093
7199
  const files = [...walkMarkdown(input.vault, input.vault)];
7094
7200
  for (const relPath of files) {
7095
- const absPath = join35(input.vault, relPath);
7096
- const localStat = statSync4(absPath);
7201
+ const absPath = join37(input.vault, relPath);
7202
+ const localStat = statSync5(absPath);
7097
7203
  let needsUpload = true;
7098
7204
  try {
7099
7205
  const head = await client.send(new HeadObjectCommand({ Bucket: input.bucket, Key: relPath }));
@@ -7168,9 +7274,9 @@ async function runBackupRestore(input) {
7168
7274
  const objects = list.Contents ?? [];
7169
7275
  for (const obj of objects) {
7170
7276
  if (!obj.Key) continue;
7171
- const localPath = join35(target, obj.Key);
7277
+ const localPath = join37(target, obj.Key);
7172
7278
  try {
7173
- const localStat = statSync4(localPath);
7279
+ const localStat = statSync5(localPath);
7174
7280
  if (obj.LastModified && localStat.mtime > obj.LastModified) {
7175
7281
  conflicts++;
7176
7282
  continue;
@@ -7181,8 +7287,8 @@ async function runBackupRestore(input) {
7181
7287
  const resp = await client.send(new GetObjectCommand({ Bucket: input.bucket, Key: obj.Key }));
7182
7288
  const body = await resp.Body?.transformToByteArray();
7183
7289
  if (body) {
7184
- mkdirSync4(dirname11(localPath), { recursive: true });
7185
- writeFileSync6(localPath, Buffer.from(body));
7290
+ mkdirSync5(dirname11(localPath), { recursive: true });
7291
+ writeFileSync7(localPath, Buffer.from(body));
7186
7292
  downloaded++;
7187
7293
  }
7188
7294
  } catch {
@@ -7214,11 +7320,11 @@ async function runBackupRestore(input) {
7214
7320
  }
7215
7321
 
7216
7322
  // src/commands/status.ts
7217
- import { existsSync as existsSync13, statSync as statSync5 } from "fs";
7218
- import { readFile as readFile23 } from "fs/promises";
7219
- import { join as join36 } from "path";
7323
+ import { existsSync as existsSync14, statSync as statSync6 } from "fs";
7324
+ import { readFile as readFile24 } from "fs/promises";
7325
+ import { join as join38 } from "path";
7220
7326
  async function runStatus(input) {
7221
- if (!existsSync13(input.vault)) {
7327
+ if (!existsSync14(input.vault)) {
7222
7328
  return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { vault: input.vault }) };
7223
7329
  }
7224
7330
  const scan = await scanVault(input.vault);
@@ -7243,7 +7349,7 @@ async function runStatus(input) {
7243
7349
  const compound = scan.data.compound.length;
7244
7350
  let schemaVersion = "v1";
7245
7351
  try {
7246
- const schemaContent = await readFile23(join36(input.vault, "SCHEMA.md"), "utf8");
7352
+ const schemaContent = await readFile24(join38(input.vault, "SCHEMA.md"), "utf8");
7247
7353
  const versionMatch = schemaContent.match(/version:\s*["']?([^"'\s\n]+)/i);
7248
7354
  if (versionMatch) schemaVersion = versionMatch[1];
7249
7355
  } catch {
@@ -7259,7 +7365,7 @@ async function runStatus(input) {
7259
7365
  let maxTime = 0;
7260
7366
  for (const page of allPages) {
7261
7367
  try {
7262
- const st = statSync5(page.absPath);
7368
+ const st = statSync6(page.absPath);
7263
7369
  if (st.mtimeMs > maxTime) {
7264
7370
  maxTime = st.mtimeMs;
7265
7371
  lastModified = st.mtime.toISOString();
@@ -7303,8 +7409,8 @@ async function runStatus(input) {
7303
7409
  }
7304
7410
 
7305
7411
  // src/commands/seed.ts
7306
- import { mkdir as mkdir13, writeFile as writeFile14, stat as stat7 } from "fs/promises";
7307
- import { join as join37 } from "path";
7412
+ import { mkdir as mkdir13, writeFile as writeFile15, stat as stat8 } from "fs/promises";
7413
+ import { join as join39 } from "path";
7308
7414
  var TODAY = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
7309
7415
  var EXAMPLE_PAGES = {
7310
7416
  "entities/example-project.md": `---
@@ -7373,30 +7479,30 @@ Real sources are immutable after ingestion \u2014 never edit them.
7373
7479
  `;
7374
7480
  async function runSeed(input) {
7375
7481
  try {
7376
- await stat7(join37(input.vault, "SCHEMA.md"));
7482
+ await stat8(join39(input.vault, "SCHEMA.md"));
7377
7483
  } catch {
7378
7484
  return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { root: input.vault, reason: "SCHEMA.md missing \u2014 run `skillwiki init` first" }) };
7379
7485
  }
7380
7486
  const created = [];
7381
7487
  const skipped = [];
7382
7488
  for (const [relPath, content] of Object.entries(EXAMPLE_PAGES)) {
7383
- const absPath = join37(input.vault, relPath);
7489
+ const absPath = join39(input.vault, relPath);
7384
7490
  try {
7385
- await stat7(absPath);
7491
+ await stat8(absPath);
7386
7492
  skipped.push(relPath);
7387
7493
  } catch {
7388
- await mkdir13(join37(absPath, ".."), { recursive: true });
7389
- await writeFile14(absPath, content, "utf8");
7494
+ await mkdir13(join39(absPath, ".."), { recursive: true });
7495
+ await writeFile15(absPath, content, "utf8");
7390
7496
  created.push(relPath);
7391
7497
  }
7392
7498
  }
7393
- const rawPath = join37(input.vault, "raw", "articles", "example-source.md");
7499
+ const rawPath = join39(input.vault, "raw", "articles", "example-source.md");
7394
7500
  try {
7395
- await stat7(rawPath);
7501
+ await stat8(rawPath);
7396
7502
  skipped.push("raw/articles/example-source.md");
7397
7503
  } catch {
7398
- await mkdir13(join37(rawPath, ".."), { recursive: true });
7399
- await writeFile14(rawPath, EXAMPLE_RAW, "utf8");
7504
+ await mkdir13(join39(rawPath, ".."), { recursive: true });
7505
+ await writeFile15(rawPath, EXAMPLE_RAW, "utf8");
7400
7506
  created.push("raw/articles/example-source.md");
7401
7507
  }
7402
7508
  if (created.length > 0) {
@@ -7418,9 +7524,9 @@ async function runSeed(input) {
7418
7524
  }
7419
7525
 
7420
7526
  // src/commands/canvas.ts
7421
- import { readFile as readFile24, writeFile as writeFile15 } from "fs/promises";
7422
- import { existsSync as existsSync14 } from "fs";
7423
- import { join as join38 } from "path";
7527
+ import { readFile as readFile25, writeFile as writeFile16 } from "fs/promises";
7528
+ import { existsSync as existsSync15 } from "fs";
7529
+ import { join as join40 } from "path";
7424
7530
  var NODE_WIDTH = 240;
7425
7531
  var NODE_HEIGHT = 60;
7426
7532
  var COLUMN_SPACING = 400;
@@ -7498,8 +7604,8 @@ function buildCanvasEdges(adjacency) {
7498
7604
  return edges;
7499
7605
  }
7500
7606
  async function runCanvasGenerate(input) {
7501
- const graphPath = input.graphPath ?? join38(input.vault, ".skillwiki", "graph.json");
7502
- if (!existsSync14(graphPath)) {
7607
+ const graphPath = input.graphPath ?? join40(input.vault, ".skillwiki", "graph.json");
7608
+ if (!existsSync15(graphPath)) {
7503
7609
  return {
7504
7610
  exitCode: ExitCode.FILE_NOT_FOUND,
7505
7611
  result: err("FILE_NOT_FOUND", {
@@ -7510,7 +7616,7 @@ async function runCanvasGenerate(input) {
7510
7616
  }
7511
7617
  let raw;
7512
7618
  try {
7513
- raw = await readFile24(graphPath, "utf8");
7619
+ raw = await readFile25(graphPath, "utf8");
7514
7620
  } catch (e) {
7515
7621
  return {
7516
7622
  exitCode: ExitCode.FILE_NOT_FOUND,
@@ -7536,9 +7642,9 @@ async function runCanvasGenerate(input) {
7536
7642
  const nodes = buildCanvasNodes(paths);
7537
7643
  const edges = buildCanvasEdges(graph.adjacency);
7538
7644
  const canvas = { nodes, edges };
7539
- const outPath = join38(input.vault, "vault-graph.canvas");
7645
+ const outPath = join40(input.vault, "vault-graph.canvas");
7540
7646
  try {
7541
- await writeFile15(outPath, JSON.stringify(canvas, null, 2));
7647
+ await writeFile16(outPath, JSON.stringify(canvas, null, 2));
7542
7648
  } catch (e) {
7543
7649
  return {
7544
7650
  exitCode: ExitCode.WRITE_FAILED,
@@ -7558,8 +7664,8 @@ written: ${outPath}`
7558
7664
  }
7559
7665
 
7560
7666
  // src/commands/query.ts
7561
- import { readFile as readFile25, stat as stat8 } from "fs/promises";
7562
- import { join as join39 } from "path";
7667
+ import { readFile as readFile26, stat as stat9 } from "fs/promises";
7668
+ import { join as join41 } from "path";
7563
7669
  var W_KEYWORD = 2;
7564
7670
  var W_SOURCE_OVERLAP = 4;
7565
7671
  var W_WIKILINK = 3;
@@ -7680,10 +7786,10 @@ function computeKeywordScore(terms, title, tags, body) {
7680
7786
  return score;
7681
7787
  }
7682
7788
  async function loadOrBuildGraph(vault) {
7683
- const graphPath = join39(vault, ".skillwiki", "graph.json");
7789
+ const graphPath = join41(vault, ".skillwiki", "graph.json");
7684
7790
  let needsBuild = false;
7685
7791
  try {
7686
- const fileStat = await stat8(graphPath);
7792
+ const fileStat = await stat9(graphPath);
7687
7793
  const ageHours = (Date.now() - fileStat.mtimeMs) / (1e3 * 60 * 60);
7688
7794
  if (ageHours > 24) needsBuild = true;
7689
7795
  } catch {
@@ -7694,7 +7800,7 @@ async function loadOrBuildGraph(vault) {
7694
7800
  if (buildResult.exitCode !== 0) return null;
7695
7801
  }
7696
7802
  try {
7697
- const raw = await readFile25(graphPath, "utf8");
7803
+ const raw = await readFile26(graphPath, "utf8");
7698
7804
  return JSON.parse(raw);
7699
7805
  } catch {
7700
7806
  return null;
@@ -7702,14 +7808,14 @@ async function loadOrBuildGraph(vault) {
7702
7808
  }
7703
7809
 
7704
7810
  // src/utils/auto-commit.ts
7705
- import { existsSync as existsSync15 } from "fs";
7706
- import { join as join40 } from "path";
7811
+ import { existsSync as existsSync16 } from "fs";
7812
+ import { join as join42 } from "path";
7707
7813
  async function postCommit(vault, exitCode) {
7708
7814
  if (exitCode !== 0) return;
7709
7815
  const home = process.env.HOME ?? "";
7710
7816
  const dotenv = await parseDotenvFile(configPath(home));
7711
7817
  if (dotenv["AUTO_COMMIT"] === "false") return;
7712
- if (!existsSync15(join40(vault, ".git"))) return;
7818
+ if (!existsSync16(join42(vault, ".git"))) return;
7713
7819
  const lastOps = readLastOp(vault);
7714
7820
  if (lastOps.length === 0) return;
7715
7821
  const porcelain = git(vault, ["status", "--porcelain"]);
@@ -7760,7 +7866,7 @@ program.command("validate <file>").description("validate vault page frontmatter
7760
7866
  emit(await runValidate({ file, apply: !!opts.apply, vault }), vault);
7761
7867
  });
7762
7868
  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) => {
7763
- const out = opts.out ?? join41(vault, ".skillwiki", "graph.json");
7869
+ const out = opts.out ?? join43(vault, ".skillwiki", "graph.json");
7764
7870
  emit(await runGraphBuild({ vault, out }), vault);
7765
7871
  });
7766
7872
  var canvasCmd = program.command("canvas").description("manage Obsidian canvas files");
@@ -7886,6 +7992,11 @@ program.command("log-rotate [vault]").description("rotate or trim the vault log
7886
7992
  if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
7887
7993
  else emit(await runLogRotate({ vault: v.vault, threshold: opts.threshold, apply: !!opts.apply }), v.vault);
7888
7994
  });
7995
+ 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) => {
7996
+ const v = await resolveVaultArg(vault, opts.wiki);
7997
+ if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
7998
+ else emit(await runLogAppend({ vault: v.vault, content: opts.content }), v.vault);
7999
+ });
7889
8000
  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) => {
7890
8001
  const v = await resolveVaultArg(vault, opts.wiki);
7891
8002
  if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });