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