skillwiki 0.9.60 → 0.9.62

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.
@@ -328,34 +328,162 @@ function getErrorMessage(e) {
328
328
  return e instanceof Error ? e.message : String(e);
329
329
  }
330
330
 
331
- // src/commands/validate.ts
332
- import { readFile, writeFile } from "fs/promises";
333
- import { join, resolve, relative, sep } from "path";
331
+ // src/commands/log-append.ts
332
+ import { readFile as readFile2, stat } from "fs/promises";
333
+ import { join as join4 } from "path";
334
334
 
335
- // src/parsers/frontmatter.ts
336
- import yaml from "js-yaml";
337
- var FM_OPEN = /^---\r?\n/;
338
- function splitFrontmatter(text) {
339
- if (!FM_OPEN.test(text)) return ok({ rawFrontmatter: "", body: text, bodyStart: 0 });
340
- const afterOpen = text.replace(FM_OPEN, "");
341
- const closeIdx = afterOpen.search(/\r?\n---\r?\n/);
342
- if (closeIdx === -1) return err("MISSING_CLOSING_DELIMITER");
343
- const rawFrontmatter = afterOpen.slice(0, closeIdx);
344
- const closeMatch = afterOpen.slice(closeIdx).match(/\r?\n---\r?\n/);
345
- const bodyStart = text.length - (afterOpen.length - closeIdx - closeMatch[0].length);
346
- const body = text.slice(bodyStart);
347
- return ok({ rawFrontmatter, body, bodyStart });
335
+ // src/utils/last-op.ts
336
+ import { readFileSync, writeFileSync, mkdirSync, unlinkSync, existsSync } from "fs";
337
+ import { join } from "path";
338
+ var LAST_OP_DIR = ".skillwiki";
339
+ var LAST_OP_FILE = "last-op.json";
340
+ function lastOpPath(vault) {
341
+ return join(vault, LAST_OP_DIR, LAST_OP_FILE);
348
342
  }
349
- function extractFrontmatter(text) {
350
- const split = splitFrontmatter(text);
351
- if (!split.ok) return split;
352
- if (!split.data.rawFrontmatter) return ok({});
343
+ function readLastOp(vault) {
344
+ const p = lastOpPath(vault);
345
+ if (!existsSync(p)) return [];
353
346
  try {
354
- const parsed = yaml.load(split.data.rawFrontmatter, { schema: yaml.JSON_SCHEMA });
355
- if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return ok({});
356
- return ok(parsed);
357
- } catch (e) {
358
- return err("INVALID_FRONTMATTER", { message: getErrorMessage(e) });
347
+ const raw = readFileSync(p, "utf8");
348
+ const parsed = JSON.parse(raw);
349
+ if (!Array.isArray(parsed)) {
350
+ unlinkSync(p);
351
+ return [];
352
+ }
353
+ return parsed;
354
+ } catch {
355
+ try {
356
+ unlinkSync(p);
357
+ } catch (_e) {
358
+ }
359
+ return [];
360
+ }
361
+ }
362
+ function appendLastOp(vault, entry) {
363
+ const existing = readLastOp(vault);
364
+ existing.push(entry);
365
+ const dir = join(vault, LAST_OP_DIR);
366
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
367
+ writeFileSync(lastOpPath(vault), JSON.stringify(existing, null, 2), "utf8");
368
+ }
369
+ function clearLastOp(vault) {
370
+ const p = lastOpPath(vault);
371
+ try {
372
+ unlinkSync(p);
373
+ } catch (_e) {
374
+ }
375
+ }
376
+
377
+ // src/utils/atomic-write.ts
378
+ import { randomBytes } from "crypto";
379
+ import { open, readFile, rename, unlink } from "fs/promises";
380
+ import { basename, dirname, join as join2 } from "path";
381
+ async function readExisting(path) {
382
+ try {
383
+ return await readFile(path, "utf8");
384
+ } catch (error) {
385
+ if (error.code === "ENOENT") return null;
386
+ throw error;
387
+ }
388
+ }
389
+ async function atomicWriteText(path, text) {
390
+ let existing;
391
+ try {
392
+ existing = await readExisting(path);
393
+ } catch (error) {
394
+ return err("WRITE_FAILED", { path, phase: "read-existing", message: String(error) });
395
+ }
396
+ if (existing === text) return ok({ changed: false, existed: true });
397
+ const tmp = join2(
398
+ dirname(path),
399
+ `.${basename(path)}.${process.pid}.${randomBytes(8).toString("hex")}.tmp`
400
+ );
401
+ try {
402
+ const handle = await open(tmp, "wx");
403
+ try {
404
+ await handle.writeFile(text, "utf8");
405
+ try {
406
+ await handle.sync();
407
+ } catch {
408
+ }
409
+ } finally {
410
+ await handle.close();
411
+ }
412
+ await rename(tmp, path);
413
+ return ok({ changed: true, existed: existing !== null });
414
+ } catch (error) {
415
+ try {
416
+ await unlink(tmp);
417
+ } catch {
418
+ }
419
+ return err("WRITE_FAILED", { path, phase: "atomic-write", message: String(error) });
420
+ }
421
+ }
422
+
423
+ // src/utils/log-lock.ts
424
+ import { randomBytes as randomBytes2 } from "crypto";
425
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, statSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
426
+ import { join as join3 } from "path";
427
+ function logLockPath(vault) {
428
+ return join3(vault, ".skillwiki", "log-append.lock");
429
+ }
430
+ var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
431
+ function readLogLock(path) {
432
+ try {
433
+ return JSON.parse(readFileSync2(path, "utf8"));
434
+ } catch {
435
+ return null;
436
+ }
437
+ }
438
+ async function acquireLogLock(vault, opts = {}) {
439
+ const retryMs = opts.retryMs ?? 2e3;
440
+ const pollMs = opts.pollMs ?? 50;
441
+ const staleMs = opts.staleMs ?? 1e4;
442
+ const reclaimStale = opts.reclaimStale ?? true;
443
+ const path = logLockPath(vault);
444
+ const dir = join3(vault, ".skillwiki");
445
+ if (!existsSync2(dir)) mkdirSync2(dir, { recursive: true });
446
+ const deadline = Date.now() + retryMs;
447
+ const ownerToken = randomBytes2(16).toString("hex");
448
+ const acquired = (/* @__PURE__ */ new Date()).toISOString();
449
+ const content = JSON.stringify({ pid: process.pid, owner_token: ownerToken, acquired }) + "\n";
450
+ for (; ; ) {
451
+ try {
452
+ writeFileSync2(path, content, { flag: "wx" });
453
+ return ok({ vault, path, ownerToken, acquired });
454
+ } catch (error) {
455
+ const fsError = error;
456
+ if (fsError.code !== "EEXIST") {
457
+ return err("WRITE_FAILED", { path, message: String(error) });
458
+ }
459
+ }
460
+ if (reclaimStale) {
461
+ try {
462
+ const age = Date.now() - statSync(path).mtimeMs;
463
+ if (age > staleMs) {
464
+ unlinkSync2(path);
465
+ continue;
466
+ }
467
+ } catch {
468
+ continue;
469
+ }
470
+ }
471
+ if (Date.now() >= deadline) return err("LOG_APPEND_LOCK_HELD", { vault });
472
+ await sleep(pollMs);
473
+ }
474
+ }
475
+ function releaseLogLock(handle) {
476
+ const existing = readLogLock(handle.path);
477
+ if (!existing || existing.owner_token !== handle.ownerToken || existing.acquired !== handle.acquired) {
478
+ return err("LOG_APPEND_LOCK_HELD", {
479
+ message: "log append lock ownership changed; refusing release"
480
+ });
481
+ }
482
+ try {
483
+ unlinkSync2(handle.path);
484
+ return ok({ released: true });
485
+ } catch (error) {
486
+ return err("WRITE_FAILED", { path: handle.path, message: String(error) });
359
487
  }
360
488
  }
361
489
 
@@ -499,15 +627,490 @@ function redactSensitiveContent(text, opts = {}) {
499
627
  };
500
628
  }
501
629
 
630
+ // src/commands/log-append.ts
631
+ var ENTRY_RE = /^## \[(\d{4})-\d{2}-\d{2}\]/gm;
632
+ function operationMarker(operationId) {
633
+ if (!/^[0-9a-f]{64}$/.test(operationId)) {
634
+ return err("USAGE", { message: "operationId must be a SHA-256 hex string" });
635
+ }
636
+ return ok(`<!-- skillwiki-page-publish:${operationId} -->`);
637
+ }
638
+ async function appendWhileLocked(logPath, content, marker) {
639
+ let logText;
640
+ try {
641
+ logText = await readFile2(logPath, "utf8");
642
+ } catch {
643
+ return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path: logPath }) };
644
+ }
645
+ const entriesBefore = [...logText.matchAll(ENTRY_RE)].length;
646
+ if (marker && logText.includes(marker)) {
647
+ return {
648
+ exitCode: ExitCode.OK,
649
+ result: ok({
650
+ entries_before: entriesBefore,
651
+ entries_after: entriesBefore,
652
+ appended: false,
653
+ humanHint: `publication operation already appended (${entriesBefore} entries)`
654
+ })
655
+ };
656
+ }
657
+ const body = logText.replace(/\s+$/, "");
658
+ const appendedContent = marker ? `${content}
659
+ ${marker}` : content;
660
+ const written = await atomicWriteText(logPath, `${body}
661
+
662
+ ${appendedContent}
663
+ `);
664
+ if (!written.ok) {
665
+ return { exitCode: ExitCode.WRITE_FAILED, result: written };
666
+ }
667
+ const entriesAfter = entriesBefore + 1;
668
+ return {
669
+ exitCode: ExitCode.OK,
670
+ result: ok({
671
+ entries_before: entriesBefore,
672
+ entries_after: entriesAfter,
673
+ appended: true,
674
+ humanHint: `appended log entry (${entriesBefore}->${entriesAfter})`
675
+ })
676
+ };
677
+ }
678
+ async function runLogAppend(input) {
679
+ try {
680
+ await stat(join4(input.vault, "SCHEMA.md"));
681
+ } catch {
682
+ return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { vault: input.vault }) };
683
+ }
684
+ const content = (input.content ?? "").trim();
685
+ if (content.length === 0) {
686
+ return { exitCode: ExitCode.USAGE, result: err("USAGE", { message: "--content must be a non-empty log entry" }) };
687
+ }
688
+ const sensitive = scanSensitiveContent(content, { file: "log.md" });
689
+ if (sensitive.length > 0) {
690
+ return {
691
+ exitCode: ExitCode.SENSITIVE_CONTENT_DETECTED,
692
+ result: err("SENSITIVE_CONTENT_DETECTED", { file: "log.md", findings: sensitive })
693
+ };
694
+ }
695
+ let marker;
696
+ if (input.operationId !== void 0) {
697
+ const operation = operationMarker(input.operationId);
698
+ if (!operation.ok) return { exitCode: ExitCode.USAGE, result: operation };
699
+ marker = operation.data;
700
+ }
701
+ const acquired = await acquireLogLock(input.vault, input.strictLock ? { reclaimStale: false } : {});
702
+ if (!acquired.ok) {
703
+ if (acquired.error === "WRITE_FAILED") {
704
+ return { exitCode: ExitCode.WRITE_FAILED, result: acquired };
705
+ }
706
+ return { exitCode: ExitCode.LOG_APPEND_LOCK_HELD, result: err("LOG_APPEND_LOCK_HELD", { vault: input.vault }) };
707
+ }
708
+ const lockHandle = acquired.data;
709
+ const logPath = join4(input.vault, "log.md");
710
+ let outcome;
711
+ let released;
712
+ try {
713
+ outcome = await appendWhileLocked(logPath, content, marker);
714
+ } catch (error) {
715
+ outcome = {
716
+ exitCode: ExitCode.WRITE_FAILED,
717
+ result: err("WRITE_FAILED", { stage: "log-append", message: String(error) })
718
+ };
719
+ } finally {
720
+ released = releaseLogLock(lockHandle);
721
+ }
722
+ if (released === void 0 || !released.ok) {
723
+ return {
724
+ exitCode: ExitCode.WRITE_FAILED,
725
+ result: err("WRITE_FAILED", { stage: "log-unlock" })
726
+ };
727
+ }
728
+ if (outcome === void 0) {
729
+ return {
730
+ exitCode: ExitCode.WRITE_FAILED,
731
+ result: err("WRITE_FAILED", { stage: "log-append" })
732
+ };
733
+ }
734
+ if (outcome.result.ok && outcome.result.data.appended && input.recordLastOp !== false) {
735
+ try {
736
+ appendLastOp(input.vault, {
737
+ operation: "log-append",
738
+ summary: `appended log entry (${outcome.result.data.entries_before}->${outcome.result.data.entries_after})`,
739
+ files: ["log.md"],
740
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
741
+ });
742
+ } catch (error) {
743
+ return {
744
+ exitCode: ExitCode.WRITE_FAILED,
745
+ result: err("WRITE_FAILED", { stage: "last-op", message: String(error) })
746
+ };
747
+ }
748
+ }
749
+ return outcome;
750
+ }
751
+
752
+ // src/utils/sync-lock.ts
753
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, renameSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync3 } from "fs";
754
+ import { join as join5 } from "path";
755
+ import { createHash as createHash2, randomBytes as randomBytes3 } from "crypto";
756
+ function getEnvSessionId() {
757
+ if (process.env.CLAUDE_SESSION_ID) return process.env.CLAUDE_SESSION_ID;
758
+ if (process.env.SKILLWIKI_SESSION_ID) return process.env.SKILLWIKI_SESSION_ID;
759
+ return void 0;
760
+ }
761
+ function getSessionId() {
762
+ const envSessionId = getEnvSessionId();
763
+ if (envSessionId) return envSessionId;
764
+ return process.pid.toString();
765
+ }
766
+ function getCwdHash(cwd) {
767
+ const path = cwd || process.cwd();
768
+ const hash = createHash2("sha256").update(path).digest("hex");
769
+ return hash.slice(0, 8);
770
+ }
771
+ function getCliSessionId(cwd) {
772
+ const envSessionId = getEnvSessionId();
773
+ if (envSessionId) return envSessionId;
774
+ return `cli-${getCwdHash(cwd)}`;
775
+ }
776
+ function lockPath(vault) {
777
+ return join5(vault, ".skillwiki", "sync.lock");
778
+ }
779
+ function readLock(vault) {
780
+ const path = lockPath(vault);
781
+ if (!existsSync3(path)) return null;
782
+ try {
783
+ const raw = readFileSync3(path, "utf8");
784
+ return JSON.parse(raw);
785
+ } catch {
786
+ return null;
787
+ }
788
+ }
789
+ function isStale(lock, now) {
790
+ const nowTime = (now ?? /* @__PURE__ */ new Date()).getTime();
791
+ const expiresTime = new Date(lock.expires).getTime();
792
+ return expiresTime < nowTime;
793
+ }
794
+ function acquireLock(vault, opts = {}) {
795
+ const path = lockPath(vault);
796
+ const dir = join5(vault, ".skillwiki");
797
+ if (!existsSync3(dir)) {
798
+ mkdirSync3(dir, { recursive: true });
799
+ }
800
+ const sessionId = opts.sessionId ?? getSessionId();
801
+ const summary = opts.summary ?? "skillwiki sync";
802
+ const ttlMinutes = opts.ttlMinutes ?? 30;
803
+ const force = opts.force ?? false;
804
+ const now = /* @__PURE__ */ new Date();
805
+ const acquired = now.toISOString();
806
+ const expires = new Date(now.getTime() + ttlMinutes * 60 * 1e3).toISOString();
807
+ const lock = {
808
+ session_id: sessionId,
809
+ pid: process.pid,
810
+ cwd: process.cwd(),
811
+ summary,
812
+ acquired,
813
+ expires
814
+ };
815
+ try {
816
+ const content = JSON.stringify(lock, null, 2) + "\n";
817
+ writeFileSync3(path, content, { flag: "wx" });
818
+ return { ok: true, lock };
819
+ } catch (e) {
820
+ const err2 = e;
821
+ if (err2.code !== "EEXIST") throw err2;
822
+ }
823
+ const existing = readLock(vault);
824
+ if (!existing) {
825
+ writeLockedFile(path, lock);
826
+ return { ok: true, lock };
827
+ }
828
+ if (force || isStale(existing)) {
829
+ writeLockedFile(path, lock);
830
+ return { ok: true, lock };
831
+ }
832
+ return { ok: false, held: existing };
833
+ }
834
+ function writeLockedFile(path, lock) {
835
+ const tmp = path + ".tmp";
836
+ const content = JSON.stringify(lock, null, 2) + "\n";
837
+ writeFileSync3(tmp, content);
838
+ renameSync(tmp, path);
839
+ }
840
+ function releaseLock(vault, opts = {}) {
841
+ const path = lockPath(vault);
842
+ if (!existsSync3(path)) {
843
+ return { released: false };
844
+ }
845
+ const sessionId = opts.sessionId ?? getSessionId();
846
+ const existing = readLock(vault);
847
+ if (opts.force) {
848
+ try {
849
+ unlinkSync3(path);
850
+ const prior = existing && existing.session_id !== sessionId ? existing : void 0;
851
+ return { released: true, prior };
852
+ } catch {
853
+ return { released: false };
854
+ }
855
+ }
856
+ if (!existing || existing.session_id !== sessionId) {
857
+ return { released: false };
858
+ }
859
+ try {
860
+ unlinkSync3(path);
861
+ return { released: true };
862
+ } catch {
863
+ return { released: false };
864
+ }
865
+ }
866
+ function acquireOwnedSyncLock(vault, opts) {
867
+ const ownerToken = randomBytes3(16).toString("hex");
868
+ const sessionId = `publish-${process.pid}-${ownerToken.slice(0, 12)}`;
869
+ const now = /* @__PURE__ */ new Date();
870
+ const lock = {
871
+ session_id: sessionId,
872
+ owner_token: ownerToken,
873
+ pid: process.pid,
874
+ cwd: process.cwd(),
875
+ summary: opts.summary,
876
+ acquired: now.toISOString(),
877
+ expires: new Date(now.getTime() + opts.ttlMinutes * 6e4).toISOString()
878
+ };
879
+ const path = lockPath(vault);
880
+ try {
881
+ mkdirSync3(join5(vault, ".skillwiki"), { recursive: true });
882
+ } catch (error) {
883
+ return err("WRITE_FAILED", { path, message: String(error) });
884
+ }
885
+ try {
886
+ writeFileSync3(path, JSON.stringify(lock, null, 2) + "\n", { flag: "wx" });
887
+ return ok({ vault, path, sessionId, ownerToken, acquired: lock.acquired });
888
+ } catch (error) {
889
+ if (error.code === "EEXIST") {
890
+ const held = readLock(vault);
891
+ return err("SYNC_LOCK_HELD", { vault, held, malformed: held === null });
892
+ }
893
+ return err("WRITE_FAILED", { path, message: String(error) });
894
+ }
895
+ }
896
+ function releaseOwnedSyncLock(handle) {
897
+ const existing = readLock(handle.vault);
898
+ if (!existing || existing.session_id !== handle.sessionId || existing.owner_token !== handle.ownerToken || existing.acquired !== handle.acquired) {
899
+ return err("SYNC_LOCK_HELD", {
900
+ message: "publication lock ownership changed; refusing release"
901
+ });
902
+ }
903
+ try {
904
+ unlinkSync3(handle.path);
905
+ return ok({ released: true });
906
+ } catch (error) {
907
+ return err("WRITE_FAILED", { path: handle.path, message: String(error) });
908
+ }
909
+ }
910
+
502
911
  // src/commands/validate.ts
503
- var TYPE_TO_SECTION = {
912
+ import { createHash as createHash3 } from "crypto";
913
+ import { readFile as readFile4 } from "fs/promises";
914
+ import { resolve as resolve2, relative as relative2, sep as sep2 } from "path";
915
+
916
+ // src/parsers/frontmatter.ts
917
+ import yaml from "js-yaml";
918
+ var FM_OPEN = /^---\r?\n/;
919
+ function splitFrontmatter(text) {
920
+ if (!FM_OPEN.test(text)) return ok({ rawFrontmatter: "", body: text, bodyStart: 0 });
921
+ const afterOpen = text.replace(FM_OPEN, "");
922
+ const closeIdx = afterOpen.search(/\r?\n---\r?\n/);
923
+ if (closeIdx === -1) return err("MISSING_CLOSING_DELIMITER");
924
+ const rawFrontmatter = afterOpen.slice(0, closeIdx);
925
+ const closeMatch = afterOpen.slice(closeIdx).match(/\r?\n---\r?\n/);
926
+ const bodyStart = text.length - (afterOpen.length - closeIdx - closeMatch[0].length);
927
+ const body = text.slice(bodyStart);
928
+ return ok({ rawFrontmatter, body, bodyStart });
929
+ }
930
+ function extractFrontmatter(text) {
931
+ const split = splitFrontmatter(text);
932
+ if (!split.ok) return split;
933
+ if (!split.data.rawFrontmatter) return ok({});
934
+ try {
935
+ const parsed = yaml.load(split.data.rawFrontmatter, { schema: yaml.JSON_SCHEMA });
936
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return ok({});
937
+ return ok(parsed);
938
+ } catch (e) {
939
+ return err("INVALID_FRONTMATTER", { message: getErrorMessage(e) });
940
+ }
941
+ }
942
+
943
+ // src/utils/index-entry.ts
944
+ import { readFile as readFile3 } from "fs/promises";
945
+ import { join as join6 } from "path";
946
+ var TYPE_SECTION = {
504
947
  entity: "Entities",
505
948
  concept: "Concepts",
506
949
  comparison: "Comparisons",
507
950
  query: "Queries",
508
- summary: "Summaries",
509
951
  meta: "Meta"
510
952
  };
953
+ function renderIndexUpsert(text, input) {
954
+ const section = TYPE_SECTION[input.type];
955
+ if (!section) return err("SCHEME_REJECTED", { type: input.type });
956
+ const ref = input.target.replace(/\.md$/, "");
957
+ if (text.includes(`[[${ref}]]`)) return ok({ text, changed: false });
958
+ if (/[\r\n]/.test(input.title)) {
959
+ return err("SCHEME_REJECTED", { message: "index title must be one line" });
960
+ }
961
+ const newline = text.includes("\r\n") ? "\r\n" : "\n";
962
+ const header = `## ${section}`;
963
+ const entry = `- [[${ref}]] \u2014 ${input.title}`;
964
+ const heading = new RegExp(`^${header}[ \\t]*(?=\\r?$)`, "m").exec(text);
965
+ if (heading?.index !== void 0) {
966
+ const afterHeading = heading.index + heading[0].length;
967
+ const nextHeading = /^##[ \t]+/m.exec(text.slice(afterHeading));
968
+ const sectionEnd = nextHeading?.index === void 0 ? text.length : afterHeading + nextHeading.index;
969
+ const sectionText = text.slice(afterHeading, sectionEnd);
970
+ const trailingWhitespace2 = /(?:\r?\n[ \t]*)*$/.exec(sectionText)?.[0] ?? "";
971
+ const insertAt2 = sectionEnd - trailingWhitespace2.length;
972
+ const before2 = text.slice(0, insertAt2);
973
+ const leadingNewline = before2.endsWith("\n") ? "" : newline;
974
+ return ok({
975
+ text: before2 + leadingNewline + entry + text.slice(insertAt2),
976
+ changed: true
977
+ });
978
+ }
979
+ const trailingWhitespace = /(?:\r?\n[ \t]*)*$/.exec(text)?.[0] ?? "";
980
+ const insertAt = text.length - trailingWhitespace.length;
981
+ const before = text.slice(0, insertAt);
982
+ const separator = before.length === 0 ? "" : newline + newline;
983
+ return ok({
984
+ text: before + separator + header + newline + entry + text.slice(insertAt),
985
+ changed: true
986
+ });
987
+ }
988
+ async function upsertIndexEntry(input) {
989
+ const path = join6(input.vault, "index.md");
990
+ let current;
991
+ try {
992
+ current = await readFile3(path, "utf8");
993
+ } catch (error) {
994
+ return err("FILE_NOT_FOUND", { path, message: String(error) });
995
+ }
996
+ const rendered = renderIndexUpsert(current, input);
997
+ if (!rendered.ok) return rendered;
998
+ if (!rendered.data.changed) return ok({ changed: false });
999
+ const written = await atomicWriteText(path, rendered.data.text);
1000
+ return written.ok ? ok({ changed: written.data.changed }) : written;
1001
+ }
1002
+
1003
+ // src/utils/typed-page.ts
1004
+ import { lstatSync, realpathSync } from "fs";
1005
+ import { dirname as dirname2, posix, relative, resolve, sep } from "path";
1006
+ var TYPE_DIRECTORY = {
1007
+ entity: "entities",
1008
+ concept: "concepts",
1009
+ comparison: "comparisons",
1010
+ query: "queries",
1011
+ meta: "meta"
1012
+ };
1013
+ function validateTypedTarget(target) {
1014
+ const segments = target.split("/");
1015
+ if (target.length === 0 || posix.isAbsolute(target) || target.includes("\\") || posix.normalize(target) !== target || segments.some((segment) => segment === "" || segment === "." || segment === "..") || !/^(entities|concepts|comparisons|queries|meta)\/[a-z0-9][a-z0-9._/-]*\.md$/.test(target)) {
1016
+ return err("VAULT_PATH_INVALID", { target, message: "unsafe typed-page target" });
1017
+ }
1018
+ return ok(target);
1019
+ }
1020
+ function assertTargetInsideVault(vault, target) {
1021
+ const validated = validateTypedTarget(target);
1022
+ if (!validated.ok) return validated;
1023
+ let vaultReal;
1024
+ try {
1025
+ vaultReal = realpathSync(vault);
1026
+ } catch {
1027
+ return err("VAULT_PATH_INVALID", { target, message: "vault realpath failed" });
1028
+ }
1029
+ const absolutePath2 = resolve(vaultReal, target);
1030
+ const parent = dirname2(absolutePath2);
1031
+ let parentReal;
1032
+ try {
1033
+ parentReal = realpathSync(parent);
1034
+ } catch {
1035
+ return err("VAULT_PATH_INVALID", { target, message: "target parent realpath failed" });
1036
+ }
1037
+ const parentRelative = relative(vaultReal, parentReal).split(sep).join("/");
1038
+ if (parentRelative === ".." || parentRelative.startsWith("../")) {
1039
+ return err("VAULT_PATH_INVALID", { target, message: "target parent escapes vault" });
1040
+ }
1041
+ if (parentReal !== parent) {
1042
+ return err("VAULT_PATH_INVALID", { target, message: "target parent may not be a symlink alias" });
1043
+ }
1044
+ let existingRealPath;
1045
+ try {
1046
+ const targetStat = lstatSync(absolutePath2);
1047
+ if (targetStat.isSymbolicLink()) {
1048
+ return err("VAULT_PATH_INVALID", { target, message: "target may not be a symlink" });
1049
+ }
1050
+ if (!targetStat.isFile()) {
1051
+ return err("VAULT_PATH_INVALID", { target, message: "existing target must be a regular file" });
1052
+ }
1053
+ try {
1054
+ existingRealPath = realpathSync(absolutePath2);
1055
+ } catch {
1056
+ return err("VAULT_PATH_INVALID", { target, message: "target realpath failed" });
1057
+ }
1058
+ } catch (error) {
1059
+ if (error.code !== "ENOENT") {
1060
+ return err("VAULT_PATH_INVALID", { target, message: "target lstat failed" });
1061
+ }
1062
+ }
1063
+ return ok({ absolutePath: absolutePath2, existingRealPath });
1064
+ }
1065
+ function invalidFrontmatter(target, issues) {
1066
+ return err("INVALID_FRONTMATTER", {
1067
+ target,
1068
+ errors: issues.map((issue) => ({ path: issue.path.join("."), message: issue.message }))
1069
+ });
1070
+ }
1071
+ function prepareTypedPage(content, target) {
1072
+ const safeTarget = validateTypedTarget(target);
1073
+ if (!safeTarget.ok) return safeTarget;
1074
+ const sensitive = scanSensitiveContent(content, { file: target });
1075
+ if (sensitive.length > 0) {
1076
+ return err("SENSITIVE_CONTENT_DETECTED", { file: target, findings: sensitive });
1077
+ }
1078
+ const frontmatter = extractFrontmatter(content);
1079
+ if (!frontmatter.ok) return frontmatter;
1080
+ const detected = detectSchema(frontmatter.data);
1081
+ if (detected.schema === "typed-knowledge") {
1082
+ const parsed = TypedKnowledgeSchema.safeParse(frontmatter.data);
1083
+ if (!parsed.success) return invalidFrontmatter(target, parsed.error.issues);
1084
+ const expectedDirectory = TYPE_DIRECTORY[parsed.data.type];
1085
+ if (!expectedDirectory || !target.startsWith(`${expectedDirectory}/`)) {
1086
+ return err("SCHEME_REJECTED", { target, type: parsed.data.type, message: "frontmatter type does not match target directory" });
1087
+ }
1088
+ return ok({
1089
+ target,
1090
+ title: parsed.data.title,
1091
+ type: parsed.data.type,
1092
+ tags: [...parsed.data.tags],
1093
+ content
1094
+ });
1095
+ }
1096
+ if (detected.schema === "meta") {
1097
+ const parsed = MetaSchema.safeParse(frontmatter.data);
1098
+ if (!parsed.success) return invalidFrontmatter(target, parsed.error.issues);
1099
+ if (!target.startsWith("meta/")) {
1100
+ return err("SCHEME_REJECTED", { target, type: "meta", message: "frontmatter type does not match target directory" });
1101
+ }
1102
+ return ok({
1103
+ target,
1104
+ title: parsed.data.title,
1105
+ type: "meta",
1106
+ tags: [...parsed.data.tags],
1107
+ content
1108
+ });
1109
+ }
1110
+ return invalidFrontmatter(target, []);
1111
+ }
1112
+
1113
+ // src/commands/validate.ts
511
1114
  var SCHEMAS = {
512
1115
  "typed-knowledge": TypedKnowledgeSchema,
513
1116
  "raw": RawSourceSchema,
@@ -518,7 +1121,7 @@ var SCHEMAS = {
518
1121
  async function runValidate(input) {
519
1122
  let text;
520
1123
  try {
521
- text = await readFile(input.file, "utf8");
1124
+ text = await readFile4(input.file, "utf8");
522
1125
  } catch {
523
1126
  return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path: input.file }) };
524
1127
  }
@@ -569,18 +1172,80 @@ ${errors.map((e) => ` ${e.path}: ${e.message}`).join("\n")}` })
569
1172
  let logUpdated = false;
570
1173
  let applyHint = "";
571
1174
  if (input.apply && input.vault) {
572
- const absFile = resolve(input.file);
573
- const absVault = resolve(input.vault);
574
- const relPath = relative(absVault, absFile).split(sep).join("/");
1175
+ const absFile = resolve2(input.file);
1176
+ const absVault = resolve2(input.vault);
1177
+ const relPath = relative2(absVault, absFile).split(sep2).join("/");
575
1178
  if (relPath.startsWith("..")) {
576
1179
  return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { reason: `file ${input.file} is not inside vault ${input.vault}` }) };
577
1180
  }
578
- const pageType = "type" in parsed.data && typeof parsed.data.type === "string" ? parsed.data.type : "";
579
- const title = typeof parsed.data.title === "string" ? parsed.data.title : relPath.replace(/\.md$/, "");
1181
+ const operationId = createHash3("sha256").update("skillwiki-validate-apply-v1\0").update(relPath).update("\0").update(text).digest("hex");
580
1182
  if (det.schema === "typed-knowledge" || det.schema === "meta") {
581
- indexUpdated = await addToIndex(input.vault, relPath, title, pageType);
1183
+ const prepared = prepareTypedPage(text, relPath);
1184
+ if (!prepared.ok) {
1185
+ return { exitCode: ExitCode.INVALID_FRONTMATTER, result: prepared };
1186
+ }
1187
+ let lock;
1188
+ try {
1189
+ lock = acquireOwnedSyncLock(input.vault, {
1190
+ summary: `validate --apply ${relPath}`,
1191
+ ttlMinutes: 1
1192
+ });
1193
+ } catch (error) {
1194
+ return {
1195
+ exitCode: ExitCode.WRITE_FAILED,
1196
+ result: err("WRITE_FAILED", { stage: "lock", message: String(error) })
1197
+ };
1198
+ }
1199
+ if (!lock.ok) {
1200
+ return {
1201
+ exitCode: lock.error === "SYNC_LOCK_HELD" ? ExitCode.SYNC_LOCK_HELD : ExitCode.WRITE_FAILED,
1202
+ result: lock
1203
+ };
1204
+ }
1205
+ let index;
1206
+ let released;
1207
+ try {
1208
+ index = await upsertIndexEntry({
1209
+ vault: input.vault,
1210
+ target: relPath,
1211
+ title: prepared.data.title,
1212
+ type: prepared.data.type
1213
+ });
1214
+ } catch (error) {
1215
+ index = err("WRITE_FAILED", { stage: "index", message: String(error) });
1216
+ } finally {
1217
+ released = releaseOwnedSyncLock(lock.data);
1218
+ }
1219
+ if (released === void 0 || !released.ok) {
1220
+ return {
1221
+ exitCode: ExitCode.WRITE_FAILED,
1222
+ result: err("WRITE_FAILED", { stage: "unlock", detail: released?.detail })
1223
+ };
1224
+ }
1225
+ if (index === void 0 || !index.ok) {
1226
+ return {
1227
+ exitCode: ExitCode.WRITE_FAILED,
1228
+ result: index ?? err("WRITE_FAILED", { stage: "index" })
1229
+ };
1230
+ }
1231
+ indexUpdated = index.data.changed;
1232
+ }
1233
+ const logged = await runLogAppend({
1234
+ vault: input.vault,
1235
+ content: `## [${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}] validate | added: ${relPath}`,
1236
+ operationId,
1237
+ strictLock: true
1238
+ });
1239
+ if (!logged.result.ok) {
1240
+ return { exitCode: logged.exitCode, result: logged.result };
1241
+ }
1242
+ if (logged.exitCode !== ExitCode.OK) {
1243
+ return {
1244
+ exitCode: logged.exitCode,
1245
+ result: err("WRITE_FAILED", { message: "log append returned inconsistent success state" })
1246
+ };
582
1247
  }
583
- logUpdated = await appendToLog(input.vault, relPath);
1248
+ logUpdated = logged.result.data.appended;
584
1249
  if (indexUpdated) applyHint += `
585
1250
  index: added [[${relPath.replace(/\.md$/, "")}]]`;
586
1251
  if (logUpdated) applyHint += "\n log: appended entry";
@@ -594,69 +1259,15 @@ ${errors.map((e) => ` ${e.path}: ${e.message}`).join("\n")}` })
594
1259
  humanHint: `VALID (${det.schema})${applyHint}`
595
1260
  }) };
596
1261
  }
597
- async function addToIndex(vault, relPath, title, pageType) {
598
- const section = TYPE_TO_SECTION[pageType];
599
- if (!section) return false;
600
- const indexPath = join(vault, "index.md");
601
- let text;
602
- try {
603
- text = await readFile(indexPath, "utf8");
604
- } catch {
605
- return false;
606
- }
607
- const ref = relPath.replace(/\.md$/, "");
608
- if (text.includes(`[[${ref}]]`)) return false;
609
- const entry = `- [[${ref}]] \u2014 ${title}`;
610
- const lines = text.split("\n");
611
- const sectionLine = `## ${section}`;
612
- const sectionIdx = lines.findIndex((l) => l.trim() === sectionLine);
613
- if (sectionIdx === -1) {
614
- while (lines.length > 0 && lines[lines.length - 1].trim() === "") lines.pop();
615
- lines.push("", sectionLine, entry);
616
- } else {
617
- let endIdx = sectionIdx + 1;
618
- while (endIdx < lines.length) {
619
- if (lines[endIdx].startsWith("## ")) break;
620
- endIdx++;
621
- }
622
- let insertAt = endIdx;
623
- while (insertAt > sectionIdx + 1 && lines[insertAt - 1].trim() === "") insertAt--;
624
- lines.splice(insertAt, 0, entry);
625
- }
626
- try {
627
- await writeFile(indexPath, lines.join("\n"), "utf8");
628
- } catch {
629
- return false;
630
- }
631
- return true;
632
- }
633
- async function appendToLog(vault, relPath) {
634
- const logPath = join(vault, "log.md");
635
- let text;
636
- try {
637
- text = await readFile(logPath, "utf8");
638
- } catch {
639
- return false;
640
- }
641
- const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
642
- const entry = `
643
- ## [${today}] validate | added: ${relPath}`;
644
- try {
645
- await writeFile(logPath, text.trimEnd() + entry, "utf8");
646
- } catch {
647
- return false;
648
- }
649
- return true;
650
- }
651
1262
 
652
1263
  // src/commands/graph.ts
653
- import { writeFile as writeFile2, mkdir } from "fs/promises";
654
- import { dirname } from "path";
1264
+ import { writeFile, mkdir } from "fs/promises";
1265
+ import { dirname as dirname3 } from "path";
655
1266
 
656
1267
  // src/utils/vault.ts
657
- import { existsSync, readFileSync } from "fs";
658
- import { readFile as readFile2, readdir, stat } from "fs/promises";
659
- import { join as join2, relative as relative2, sep as sep2 } from "path";
1268
+ import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
1269
+ import { readFile as readFile5, readdir, stat as stat2 } from "fs/promises";
1270
+ import { join as join7, relative as relative3, sep as sep3 } from "path";
660
1271
  var TYPED_DIRS = ["entities", "concepts", "comparisons", "queries", "meta"];
661
1272
  var SKIP_DIRS = /* @__PURE__ */ new Set([".git", "node_modules"]);
662
1273
  var DEFAULT_IO_CONCURRENCY = 1;
@@ -681,11 +1292,11 @@ function resolveReadOnlyVaultRootWithMounts(root, mounts) {
681
1292
  return { root, mirrored: false };
682
1293
  }
683
1294
  const explicitMirror = process.env.SKILLWIKI_VAULT_READ_MIRROR;
684
- if (explicitMirror && existsSync(join2(explicitMirror, "SCHEMA.md"))) {
1295
+ if (explicitMirror && existsSync4(join7(explicitMirror, "SCHEMA.md"))) {
685
1296
  return { root: explicitMirror, mirrored: explicitMirror !== root };
686
1297
  }
687
1298
  const siblingMirror = `${root}-git`;
688
- if (isRcloneFuseVaultFromMounts(root, mounts) && existsSync(join2(siblingMirror, "SCHEMA.md"))) {
1299
+ if (isRcloneFuseVaultFromMounts(root, mounts) && existsSync4(join7(siblingMirror, "SCHEMA.md"))) {
689
1300
  return { root: siblingMirror, mirrored: true };
690
1301
  }
691
1302
  return { root, mirrored: false };
@@ -693,7 +1304,7 @@ function resolveReadOnlyVaultRootWithMounts(root, mounts) {
693
1304
  function resolveReadOnlyVaultRoot(root) {
694
1305
  let mounts = "";
695
1306
  try {
696
- mounts = readFileSync("/proc/mounts", "utf8");
1307
+ mounts = readFileSync4("/proc/mounts", "utf8");
697
1308
  } catch {
698
1309
  }
699
1310
  return resolveReadOnlyVaultRootWithMounts(root, mounts);
@@ -713,12 +1324,12 @@ async function mapWithConcurrency(items, limit, mapper) {
713
1324
  }
714
1325
  async function scanVault(root) {
715
1326
  try {
716
- await stat(join2(root, "SCHEMA.md"));
1327
+ await stat2(join7(root, "SCHEMA.md"));
717
1328
  } catch {
718
1329
  return err("VAULT_PATH_INVALID", { root, reason: "SCHEMA.md missing" });
719
1330
  }
720
1331
  const all = await walk(root);
721
- const rels = all.map((p) => ({ absPath: p, relPath: relative2(root, p).split(sep2).join("/") }));
1332
+ const rels = all.map((p) => ({ absPath: p, relPath: relative3(root, p).split(sep3).join("/") }));
722
1333
  return ok({
723
1334
  root,
724
1335
  allMarkdown: rels,
@@ -733,7 +1344,7 @@ async function walk(dir) {
733
1344
  const out = [];
734
1345
  const subdirs = [];
735
1346
  for (const e of entries) {
736
- const p = join2(dir, e.name);
1347
+ const p = join7(dir, e.name);
737
1348
  if (e.isDirectory()) {
738
1349
  if (SKIP_DIRS.has(e.name)) continue;
739
1350
  subdirs.push(p);
@@ -744,7 +1355,7 @@ async function walk(dir) {
744
1355
  return out;
745
1356
  }
746
1357
  async function readPage(p) {
747
- return readFile2(p.absPath, "utf8");
1358
+ return readFile5(p.absPath, "utf8");
748
1359
  }
749
1360
  async function readPageCached(p, cache) {
750
1361
  if (!cache) return readPage(p);
@@ -907,8 +1518,8 @@ async function runGraphBuild(input) {
907
1518
  const adamicAdar = computeAdamicAdar(adjacency);
908
1519
  const edge_count = Object.values(adjacency).reduce((acc, arr) => acc + arr.length, 0);
909
1520
  try {
910
- await mkdir(dirname(input.out), { recursive: true });
911
- await writeFile2(input.out, JSON.stringify({ adjacency, adamicAdar }, null, 2));
1521
+ await mkdir(dirname3(input.out), { recursive: true });
1522
+ await writeFile(input.out, JSON.stringify({ adjacency, adamicAdar }, null, 2));
912
1523
  } catch (e) {
913
1524
  return { exitCode: ExitCode.WRITE_FAILED, result: err("WRITE_FAILED", { message: String(e) }) };
914
1525
  }
@@ -951,8 +1562,8 @@ function computeAdamicAdar(adj) {
951
1562
  }
952
1563
 
953
1564
  // src/utils/dotenv.ts
954
- import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir2 } from "fs/promises";
955
- import { dirname as dirname2 } from "path";
1565
+ import { readFile as readFile6, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
1566
+ import { dirname as dirname4 } from "path";
956
1567
  var CONFIG_KEYS = [
957
1568
  "WIKI_PATH",
958
1569
  "WIKI_LANG",
@@ -993,7 +1604,7 @@ function parseDotenvText(text) {
993
1604
  async function parseDotenvFile(path) {
994
1605
  let text;
995
1606
  try {
996
- text = await readFile3(path, "utf8");
1607
+ text = await readFile6(path, "utf8");
997
1608
  } catch {
998
1609
  return {};
999
1610
  }
@@ -1001,8 +1612,8 @@ async function parseDotenvFile(path) {
1001
1612
  }
1002
1613
  async function writeDotenv(filePath, entries, originalContent) {
1003
1614
  const lines = originalContent !== void 0 ? updateLines(originalContent, entries) : freshLines(entries);
1004
- await mkdir2(dirname2(filePath), { recursive: true });
1005
- await writeFile3(filePath, lines.join("\n") + "\n", "utf8");
1615
+ await mkdir2(dirname4(filePath), { recursive: true });
1616
+ await writeFile2(filePath, lines.join("\n") + "\n", "utf8");
1006
1617
  }
1007
1618
  function freshLines(entries) {
1008
1619
  const out = [];
@@ -1045,7 +1656,7 @@ function updateLines(originalContent, entries) {
1045
1656
  }
1046
1657
 
1047
1658
  // src/utils/wiki-path.ts
1048
- import { join as join3 } from "path";
1659
+ import { join as join8 } from "path";
1049
1660
  async function resolveInitTimePath(input) {
1050
1661
  const chain = [];
1051
1662
  if (input.flag !== void 0 && input.flag.length > 0) {
@@ -1058,27 +1669,27 @@ async function resolveInitTimePath(input) {
1058
1669
  return { path: input.envValue, source: "env", ...input.explain ? { chain } : {} };
1059
1670
  }
1060
1671
  if (input.explain) chain.push({ source: "env", matched: false });
1061
- const sw = await parseDotenvFile(join3(input.home, ".skillwiki", ".env"));
1672
+ const sw = await parseDotenvFile(join8(input.home, ".skillwiki", ".env"));
1062
1673
  if (sw.WIKI_PATH !== void 0) {
1063
1674
  if (input.explain) chain.push({ source: "skillwiki-dotenv", matched: true, value: sw.WIKI_PATH });
1064
1675
  return { path: sw.WIKI_PATH, source: "skillwiki-dotenv", ...input.explain ? { chain } : {} };
1065
1676
  }
1066
1677
  if (input.explain) chain.push({ source: "skillwiki-dotenv", matched: false });
1067
- const hermes = await parseDotenvFile(join3(input.home, ".hermes", ".env"));
1678
+ const hermes = await parseDotenvFile(join8(input.home, ".hermes", ".env"));
1068
1679
  if (hermes.WIKI_PATH !== void 0) {
1069
1680
  if (input.explain) chain.push({ source: "hermes-dotenv", matched: true, value: hermes.WIKI_PATH });
1070
1681
  return { path: hermes.WIKI_PATH, source: "hermes-dotenv", ...input.explain ? { chain } : {} };
1071
1682
  }
1072
1683
  if (input.explain) chain.push({ source: "hermes-dotenv", matched: false });
1073
1684
  if (input.cwd) {
1074
- const projCfg = await parseDotenvFile(join3(input.cwd, ".skillwiki", ".env"));
1685
+ const projCfg = await parseDotenvFile(join8(input.cwd, ".skillwiki", ".env"));
1075
1686
  if (projCfg.WIKI_PATH !== void 0) {
1076
1687
  if (input.explain) chain.push({ source: "project-dotenv", matched: true, value: projCfg.WIKI_PATH });
1077
1688
  return { path: projCfg.WIKI_PATH, source: "project-dotenv", ...input.explain ? { chain } : {} };
1078
1689
  }
1079
1690
  }
1080
1691
  if (input.explain) chain.push({ source: "project-dotenv", matched: false });
1081
- const fallback = join3(input.home, "wiki");
1692
+ const fallback = join8(input.home, "wiki");
1082
1693
  if (input.explain) chain.push({ source: "default", matched: true, value: fallback });
1083
1694
  return { path: fallback, source: "default", ...input.explain ? { chain } : {} };
1084
1695
  }
@@ -1089,7 +1700,7 @@ async function resolveRuntimePath(input) {
1089
1700
  return ok({ path: input.flag, source: "flag", ...input.explain ? { chain } : {} });
1090
1701
  }
1091
1702
  if (input.explain) chain.push({ source: "flag", matched: false });
1092
- const swGlobal = await parseDotenvFile(join3(input.home, ".skillwiki", ".env"));
1703
+ const swGlobal = await parseDotenvFile(join8(input.home, ".skillwiki", ".env"));
1093
1704
  const wikiName = input.wiki;
1094
1705
  if (wikiName !== void 0 && wikiName.length > 0) {
1095
1706
  if (wikiName.toLowerCase() === "default") {
@@ -1133,7 +1744,7 @@ async function resolveRuntimePath(input) {
1133
1744
  }
1134
1745
  if (input.explain) chain.push({ source: "env", matched: false });
1135
1746
  if (input.cwd) {
1136
- const projCfg = await parseDotenvFile(join3(input.cwd, ".skillwiki", ".env"));
1747
+ const projCfg = await parseDotenvFile(join8(input.cwd, ".skillwiki", ".env"));
1137
1748
  if (projCfg.WIKI_PATH !== void 0) {
1138
1749
  if (input.explain) chain.push({ source: "project-dotenv", matched: true, value: projCfg.WIKI_PATH });
1139
1750
  return ok({ path: projCfg.WIKI_PATH, source: "project-dotenv", ...input.explain ? { chain } : {} });
@@ -1253,8 +1864,8 @@ function simulateRemoval(adj, removed) {
1253
1864
  }
1254
1865
 
1255
1866
  // src/commands/audit.ts
1256
- import { readFile as readFile4, stat as stat3 } from "fs/promises";
1257
- import { dirname as dirname3, resolve as resolve2, join as join5 } from "path";
1867
+ import { readFile as readFile7, stat as stat4 } from "fs/promises";
1868
+ import { dirname as dirname5, resolve as resolve3, join as join10 } from "path";
1258
1869
 
1259
1870
  // src/parsers/citations.ts
1260
1871
  var FENCE2 = /```[\s\S]*?```/g;
@@ -1364,9 +1975,9 @@ function hasWikilinkCitations(body) {
1364
1975
  }
1365
1976
 
1366
1977
  // src/utils/raw-source.ts
1367
- import { existsSync as existsSync2 } from "fs";
1368
- import { stat as stat2 } from "fs/promises";
1369
- import { join as join4 } from "path";
1978
+ import { existsSync as existsSync5 } from "fs";
1979
+ import { stat as stat3 } from "fs/promises";
1980
+ import { join as join9 } from "path";
1370
1981
  function normalizeRawSourceTarget(entry) {
1371
1982
  let target = entry.trim().replace(/^"/, "").replace(/"$/, "").replace(/^'/, "").replace(/'$/, "");
1372
1983
  target = target.replace(/^\^\[/, "").replace(/\]$/, "");
@@ -1376,21 +1987,21 @@ function normalizeRawSourceTarget(entry) {
1376
1987
  function rawSourceTargetCandidates(vault, target) {
1377
1988
  const normalized = normalizeRawSourceTarget(target);
1378
1989
  if (!normalized) return [];
1379
- const candidates = [join4(vault, normalized)];
1380
- if (!normalized.endsWith(".md")) candidates.push(join4(vault, `${normalized}.md`));
1990
+ const candidates = [join9(vault, normalized)];
1991
+ if (!normalized.endsWith(".md")) candidates.push(join9(vault, `${normalized}.md`));
1381
1992
  if (normalized.startsWith("raw/")) {
1382
- candidates.push(join4(vault, "_archive", normalized));
1383
- if (!normalized.endsWith(".md")) candidates.push(join4(vault, "_archive", `${normalized}.md`));
1993
+ candidates.push(join9(vault, "_archive", normalized));
1994
+ if (!normalized.endsWith(".md")) candidates.push(join9(vault, "_archive", `${normalized}.md`));
1384
1995
  }
1385
1996
  return [...new Set(candidates)];
1386
1997
  }
1387
1998
  function rawSourceTargetExistsSync(vault, target) {
1388
- return rawSourceTargetCandidates(vault, target).some((candidate) => existsSync2(candidate));
1999
+ return rawSourceTargetCandidates(vault, target).some((candidate) => existsSync5(candidate));
1389
2000
  }
1390
2001
  async function rawSourceTargetExists(vault, target) {
1391
2002
  for (const candidate of rawSourceTargetCandidates(vault, target)) {
1392
2003
  try {
1393
- await stat2(candidate);
2004
+ await stat3(candidate);
1394
2005
  return true;
1395
2006
  } catch {
1396
2007
  }
@@ -1402,7 +2013,7 @@ async function rawSourceTargetExists(vault, target) {
1402
2013
  async function runAudit(input) {
1403
2014
  let text;
1404
2015
  try {
1405
- text = await readFile4(input.file, "utf8");
2016
+ text = await readFile7(input.file, "utf8");
1406
2017
  } catch {
1407
2018
  return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path: input.file }) };
1408
2019
  }
@@ -1410,7 +2021,7 @@ async function runAudit(input) {
1410
2021
  if (!fm.ok) return { exitCode: ExitCode.INVALID_FRONTMATTER, result: fm };
1411
2022
  const split = splitFrontmatter(text);
1412
2023
  const body = split.ok ? split.data.body : text;
1413
- const vault = await findVaultRoot(dirname3(resolve2(input.file)));
2024
+ const vault = await findVaultRoot(dirname5(resolve3(input.file)));
1414
2025
  if (!vault) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID") };
1415
2026
  const markers = extractCitationMarkers(body);
1416
2027
  const resolved = await Promise.all(markers.map(async (m) => {
@@ -1455,11 +2066,11 @@ async function findVaultRoot(start) {
1455
2066
  let cur = start;
1456
2067
  for (let i = 0; i < 20; i++) {
1457
2068
  try {
1458
- await stat3(join5(cur, "SCHEMA.md"));
2069
+ await stat4(join10(cur, "SCHEMA.md"));
1459
2070
  return cur;
1460
2071
  } catch {
1461
2072
  }
1462
- const parent = dirname3(cur);
2073
+ const parent = dirname5(cur);
1463
2074
  if (parent === cur) return null;
1464
2075
  cur = parent;
1465
2076
  }
@@ -1547,32 +2158,123 @@ ${broken.map((b) => ` ${b.page}:[[${b.slug}]] (line ${b.line})`).join("\n")}` }
1547
2158
  }
1548
2159
 
1549
2160
  // src/commands/tag-audit.ts
1550
- import { readFile as readFile5 } from "fs/promises";
1551
- import { join as join6 } from "path";
2161
+ import { readFile as readFile8 } from "fs/promises";
2162
+ import { join as join11 } from "path";
1552
2163
 
1553
2164
  // src/parsers/taxonomy.ts
1554
2165
  import yaml2 from "js-yaml";
1555
- var FENCE_RE = /^##\s+Tag Taxonomy\s*$[\s\S]*?```yaml\s*\n([\s\S]*?)\n```/m;
1556
- function extractTaxonomy(schemaText) {
1557
- const m = schemaText.match(FENCE_RE);
1558
- if (!m) return err("NO_TAXONOMY_BLOCK", { message: "No fenced YAML taxonomy block found in SCHEMA.md" });
2166
+ var TAG_SLUG_RE = /^[a-z0-9][a-z0-9_./-]*$/;
2167
+ function taxonomyItemIndent(yamlText) {
2168
+ const lines = yamlText.split(/\r?\n/);
2169
+ const taxonomyLine = lines.findIndex((line) => /^taxonomy:[ \t]*(?:#.*)?$/.test(line));
2170
+ if (taxonomyLine === -1) return void 0;
2171
+ for (const line of lines.slice(taxonomyLine + 1)) {
2172
+ if (/^[ \t]*(?:#.*)?$/.test(line)) continue;
2173
+ return /^([ \t]+)-[ \t]+/.exec(line)?.[1];
2174
+ }
2175
+ return void 0;
2176
+ }
2177
+ function parseTaxonomyDocument(schemaText) {
2178
+ const heading = /^##[ \t]+Tag Taxonomy[ \t]*\r?$/m.exec(schemaText);
2179
+ if (!heading || heading.index === void 0) {
2180
+ return err("NO_TAXONOMY_BLOCK", { message: "Tag Taxonomy heading not found" });
2181
+ }
2182
+ const afterHeading = heading.index + heading[0].length;
2183
+ const unboundedTail = schemaText.slice(afterHeading);
2184
+ const nextHeading = /^#{1,2}[ \t]+/m.exec(unboundedTail);
2185
+ const sectionEnd = nextHeading?.index === void 0 ? schemaText.length : afterHeading + nextHeading.index;
2186
+ const sectionText = schemaText.slice(afterHeading, sectionEnd);
2187
+ const open2 = /^```yaml[ \t]*\r?$/m.exec(sectionText);
2188
+ if (!open2 || open2.index === void 0) {
2189
+ return err("NO_TAXONOMY_BLOCK", { message: "Fenced YAML taxonomy block not found" });
2190
+ }
2191
+ const openStart = afterHeading + open2.index;
2192
+ const yamlStart = openStart + open2[0].length + 1;
2193
+ const afterOpen = schemaText.slice(yamlStart, sectionEnd);
2194
+ const close = /^```[ \t]*\r?$/m.exec(afterOpen);
2195
+ if (!close || close.index === void 0) {
2196
+ return err("NO_TAXONOMY_BLOCK", { message: "Taxonomy closing fence not found" });
2197
+ }
2198
+ const closingFenceStart = yamlStart + close.index;
2199
+ const newline = schemaText.slice(closingFenceStart - 2, closingFenceStart) === "\r\n" ? "\r\n" : "\n";
2200
+ const yamlEnd = closingFenceStart - newline.length;
2201
+ const yamlText = schemaText.slice(yamlStart, yamlEnd);
1559
2202
  let parsed;
1560
2203
  try {
1561
- parsed = yaml2.load(m[1], { schema: yaml2.JSON_SCHEMA });
1562
- } catch (e) {
1563
- return err("INVALID_FRONTMATTER", { message: getErrorMessage(e) });
2204
+ parsed = yaml2.load(yamlText, { schema: yaml2.JSON_SCHEMA });
2205
+ } catch (error) {
2206
+ return err("INVALID_FRONTMATTER", { message: getErrorMessage(error) });
1564
2207
  }
1565
- if (parsed === null || typeof parsed !== "object") {
2208
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
1566
2209
  return err("INVALID_FRONTMATTER", { message: "taxonomy block is not an object" });
1567
2210
  }
1568
- const tax = parsed.taxonomy;
1569
- if (!Array.isArray(tax)) {
1570
- return err("INVALID_FRONTMATTER", { message: "taxonomy key missing or not an array" });
1571
- }
1572
- if (!tax.every((x) => typeof x === "string")) {
2211
+ const tags = parsed.taxonomy;
2212
+ if (!Array.isArray(tags) || !tags.every((tag) => typeof tag === "string")) {
1573
2213
  return err("INVALID_FRONTMATTER", { message: "taxonomy must be a list of strings" });
1574
2214
  }
1575
- return ok(tax);
2215
+ const itemIndent = taxonomyItemIndent(yamlText) ?? " ";
2216
+ return ok({ tags, yamlStart, yamlEnd, closingFenceStart, newline, itemIndent });
2217
+ }
2218
+ function extractTaxonomy(schemaText) {
2219
+ const parsed = parseTaxonomyDocument(schemaText);
2220
+ if ("error" in parsed) return err(parsed.error, parsed.detail);
2221
+ return ok(parsed.data.tags);
2222
+ }
2223
+ function renderTag(tag) {
2224
+ const roundTrip = yaml2.load(`value: ${tag}
2225
+ `, { schema: yaml2.JSON_SCHEMA });
2226
+ return typeof roundTrip.value === "string" && roundTrip.value === tag ? tag : JSON.stringify(tag);
2227
+ }
2228
+ function taxonomyCommentForPage(page, date, reason) {
2229
+ const cycle = /^queries\/\d{4}-\d{2}-\d{2}-research-cycle-(\d+)-report\.md$/.exec(page);
2230
+ const chosen = reason?.trim() || (cycle ? `research-cycle ${cycle[1]} taxonomy reconciliation` : `taxonomy reconciliation for ${page}`);
2231
+ if (!/^[A-Za-z0-9][A-Za-z0-9 ._/-]{0,159}$/.test(chosen)) {
2232
+ return err("SCHEME_REJECTED", {
2233
+ message: "reconciliation reason contains unsupported characters or is too long"
2234
+ });
2235
+ }
2236
+ return ok(`# -- added ${date}: ${chosen} --`);
2237
+ }
2238
+ function reconcileTaxonomyDocument(schemaText, input) {
2239
+ const document = parseTaxonomyDocument(schemaText);
2240
+ if ("error" in document) return err(document.error, document.detail);
2241
+ const requested = [...new Set(input.tags)].sort();
2242
+ const existingSet = new Set(document.data.tags);
2243
+ const missing = requested.filter((tag) => !existingSet.has(tag));
2244
+ const invalid = missing.filter((tag) => !TAG_SLUG_RE.test(tag));
2245
+ if (invalid.length > 0) {
2246
+ return err("SCHEME_REJECTED", { message: "invalid taxonomy tag", tags: invalid });
2247
+ }
2248
+ if (missing.length === 0) {
2249
+ return ok({
2250
+ text: schemaText,
2251
+ requested,
2252
+ existing: document.data.tags,
2253
+ missing: [],
2254
+ added: [],
2255
+ changed: false
2256
+ });
2257
+ }
2258
+ const yamlText = schemaText.slice(document.data.yamlStart, document.data.yamlEnd);
2259
+ const itemIndent = taxonomyItemIndent(yamlText);
2260
+ if (!itemIndent) {
2261
+ return err("SCHEME_REJECTED", {
2262
+ message: "taxonomy reconciliation requires a block-style taxonomy list"
2263
+ });
2264
+ }
2265
+ const { newline, closingFenceStart } = document.data;
2266
+ const comment = `${itemIndent}${input.comment}`;
2267
+ const items = missing.map((tag) => `${itemIndent}- ${renderTag(tag)}`);
2268
+ const block = `${comment}${newline}${items.join(newline)}${newline}`;
2269
+ const text = schemaText.slice(0, closingFenceStart) + block + schemaText.slice(closingFenceStart);
2270
+ return ok({
2271
+ text,
2272
+ requested,
2273
+ existing: document.data.tags,
2274
+ missing,
2275
+ added: missing,
2276
+ changed: true
2277
+ });
1576
2278
  }
1577
2279
 
1578
2280
  // src/commands/tag-audit.ts
@@ -1580,7 +2282,7 @@ async function runTagAudit(input) {
1580
2282
  const scanResult = input.scan ? ok(input.scan) : await scanVault(input.vault);
1581
2283
  if (!scanResult.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scanResult };
1582
2284
  const scan = scanResult.data;
1583
- const schemaText = await readFile5(join6(input.vault, "SCHEMA.md"), "utf8");
2285
+ const schemaText = await readFile8(join11(input.vault, "SCHEMA.md"), "utf8");
1584
2286
  const tax = extractTaxonomy(schemaText);
1585
2287
  if (!tax.ok) return { exitCode: ExitCode.INVALID_FRONTMATTER, result: tax };
1586
2288
  const allowed = new Set(tax.data);
@@ -1612,14 +2314,14 @@ async function runTagAudit(input) {
1612
2314
  }
1613
2315
 
1614
2316
  // src/commands/index-check.ts
1615
- import { readFile as readFile6 } from "fs/promises";
1616
- import { join as join7 } from "path";
2317
+ import { readFile as readFile9 } from "fs/promises";
2318
+ import { join as join12 } from "path";
1617
2319
  async function runIndexCheck(input) {
1618
2320
  const scan = input.scan ? ok(input.scan) : await scanVault(input.vault);
1619
2321
  if (!scan.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scan };
1620
2322
  let indexText = "";
1621
2323
  try {
1622
- indexText = await readFile6(join7(input.vault, "index.md"), "utf8");
2324
+ indexText = await readFile9(join12(input.vault, "index.md"), "utf8");
1623
2325
  } catch {
1624
2326
  }
1625
2327
  const indexSlugsLower = /* @__PURE__ */ new Map();
@@ -1658,8 +2360,8 @@ async function runIndexCheck(input) {
1658
2360
  }
1659
2361
 
1660
2362
  // src/commands/stale.ts
1661
- import { readdir as readdir2, rename, mkdir as mkdir3, readFile as readFile7 } from "fs/promises";
1662
- import { join as join9 } from "path";
2363
+ import { readdir as readdir2, rename as rename2, mkdir as mkdir3, readFile as readFile10 } from "fs/promises";
2364
+ import { join as join13 } from "path";
1663
2365
 
1664
2366
  // src/parsers/expiry-annotations.ts
1665
2367
  var HEADING_RE = /^#{1,6}\s+(.+)$/;
@@ -1695,48 +2397,6 @@ function parseExpiryAnnotations(content, pagePath) {
1695
2397
  return annotations;
1696
2398
  }
1697
2399
 
1698
- // src/utils/last-op.ts
1699
- import { readFileSync as readFileSync2, writeFileSync, mkdirSync, unlinkSync, existsSync as existsSync3 } from "fs";
1700
- import { join as join8 } from "path";
1701
- var LAST_OP_DIR = ".skillwiki";
1702
- var LAST_OP_FILE = "last-op.json";
1703
- function lastOpPath(vault) {
1704
- return join8(vault, LAST_OP_DIR, LAST_OP_FILE);
1705
- }
1706
- function readLastOp(vault) {
1707
- const p = lastOpPath(vault);
1708
- if (!existsSync3(p)) return [];
1709
- try {
1710
- const raw = readFileSync2(p, "utf8");
1711
- const parsed = JSON.parse(raw);
1712
- if (!Array.isArray(parsed)) {
1713
- unlinkSync(p);
1714
- return [];
1715
- }
1716
- return parsed;
1717
- } catch {
1718
- try {
1719
- unlinkSync(p);
1720
- } catch (_e) {
1721
- }
1722
- return [];
1723
- }
1724
- }
1725
- function appendLastOp(vault, entry) {
1726
- const existing = readLastOp(vault);
1727
- existing.push(entry);
1728
- const dir = join8(vault, LAST_OP_DIR);
1729
- if (!existsSync3(dir)) mkdirSync(dir, { recursive: true });
1730
- writeFileSync(lastOpPath(vault), JSON.stringify(existing, null, 2), "utf8");
1731
- }
1732
- function clearLastOp(vault) {
1733
- const p = lastOpPath(vault);
1734
- try {
1735
- unlinkSync(p);
1736
- } catch (_e) {
1737
- }
1738
- }
1739
-
1740
2400
  // src/commands/stale.ts
1741
2401
  function daysSince(isoDate2) {
1742
2402
  return Math.floor((Date.now() - Date.parse(isoDate2)) / 864e5);
@@ -1750,7 +2410,7 @@ async function runStale(input) {
1750
2410
  const archived = [];
1751
2411
  const workDirs = /* @__PURE__ */ new Map();
1752
2412
  const workDirsBySlug = /* @__PURE__ */ new Map();
1753
- const projectsDir = join9(input.vault, "projects");
2413
+ const projectsDir = join13(input.vault, "projects");
1754
2414
  let projectSlugs = [];
1755
2415
  try {
1756
2416
  projectSlugs = (await readdir2(projectsDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name);
@@ -1763,7 +2423,7 @@ async function runStale(input) {
1763
2423
  projectSlugs = [input.project];
1764
2424
  }
1765
2425
  for (const slug of projectSlugs) {
1766
- const workPath = join9(projectsDir, slug, "work");
2426
+ const workPath = join13(projectsDir, slug, "work");
1767
2427
  let entries;
1768
2428
  try {
1769
2429
  entries = await readdir2(workPath, { withFileTypes: true });
@@ -1774,7 +2434,7 @@ async function runStale(input) {
1774
2434
  for (const e of entries) {
1775
2435
  if (!e.isDirectory()) continue;
1776
2436
  const relDir = `projects/${slug}/work/${e.name}`;
1777
- const absDir = join9(workPath, e.name);
2437
+ const absDir = join13(workPath, e.name);
1778
2438
  let status = "";
1779
2439
  let files;
1780
2440
  try {
@@ -1787,7 +2447,7 @@ async function runStale(input) {
1787
2447
  for (const f of files) {
1788
2448
  if (!f.endsWith(".md")) continue;
1789
2449
  try {
1790
- const fm = extractFrontmatter(await readFile7(join9(absDir, f), "utf8"));
2450
+ const fm = extractFrontmatter(await readFile10(join13(absDir, f), "utf8"));
1791
2451
  if (fm.ok && typeof fm.data.status === "string") {
1792
2452
  status = fm.data.status;
1793
2453
  break;
@@ -1883,9 +2543,9 @@ async function runStale(input) {
1883
2543
  }
1884
2544
  }
1885
2545
  await mapWithConcurrency([...workDirs.keys()], vaultIoConcurrency(), async (relDir) => {
1886
- const specPath = join9(input.vault, relDir, "spec.md");
2546
+ const specPath = join13(input.vault, relDir, "spec.md");
1887
2547
  try {
1888
- const specContent = await readFile7(specPath, "utf8");
2548
+ const specContent = await readFile10(specPath, "utf8");
1889
2549
  const specFm = extractFrontmatter(specContent);
1890
2550
  if (specFm.ok && typeof specFm.data.source === "string") {
1891
2551
  const sourcePath = specFm.data.source;
@@ -1914,7 +2574,7 @@ async function runStale(input) {
1914
2574
  if (daysSince(dateStr) < input.days) continue;
1915
2575
  let files;
1916
2576
  try {
1917
- files = await readdir2(join9(input.vault, relDir));
2577
+ files = await readdir2(join13(input.vault, relDir));
1918
2578
  } catch {
1919
2579
  continue;
1920
2580
  }
@@ -1984,7 +2644,7 @@ async function runStale(input) {
1984
2644
  staleSections.push(...staleSectionResults.flat());
1985
2645
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
1986
2646
  if (input.archive) {
1987
- const archiveDir = join9(input.vault, "_archive", today);
2647
+ const archiveDir = join13(input.vault, "_archive", today);
1988
2648
  await mkdir3(archiveDir, { recursive: true });
1989
2649
  const citedRawPaths = /* @__PURE__ */ new Set();
1990
2650
  for (const page of scan.typedKnowledge) {
@@ -2000,9 +2660,9 @@ async function runStale(input) {
2000
2660
  }
2001
2661
  for (const t of staleTranscripts) {
2002
2662
  if (citedRawPaths.has(t.path) || citedRawPaths.has(t.path.replace(/\.md$/, ""))) continue;
2003
- const dest = join9(archiveDir, t.path.split("/").pop());
2663
+ const dest = join13(archiveDir, t.path.split("/").pop());
2004
2664
  try {
2005
- await rename(join9(input.vault, t.path), dest);
2665
+ await rename2(join13(input.vault, t.path), dest);
2006
2666
  archived.push(t.path);
2007
2667
  } catch {
2008
2668
  }
@@ -2012,18 +2672,18 @@ async function runStale(input) {
2012
2672
  if (parts.length >= 4 && parts[0] === "projects") {
2013
2673
  const slug = parts[1];
2014
2674
  const itemName = parts[3];
2015
- const histDir = join9(input.vault, "projects", slug, "history", "archived-work");
2675
+ const histDir = join13(input.vault, "projects", slug, "history", "archived-work");
2016
2676
  await mkdir3(histDir, { recursive: true });
2017
- const dest = join9(histDir, itemName);
2677
+ const dest = join13(histDir, itemName);
2018
2678
  try {
2019
- await rename(join9(input.vault, w.path), dest);
2679
+ await rename2(join13(input.vault, w.path), dest);
2020
2680
  archived.push(w.path);
2021
2681
  } catch {
2022
2682
  }
2023
2683
  } else {
2024
- const dest = join9(archiveDir, w.path.replace(/\//g, "_"));
2684
+ const dest = join13(archiveDir, w.path.replace(/\//g, "_"));
2025
2685
  try {
2026
- await rename(join9(input.vault, w.path), dest);
2686
+ await rename2(join13(input.vault, w.path), dest);
2027
2687
  archived.push(w.path);
2028
2688
  } catch {
2029
2689
  }
@@ -2078,23 +2738,23 @@ async function runPagesize(input) {
2078
2738
  }
2079
2739
 
2080
2740
  // src/commands/log-rotate.ts
2081
- import { readFile as readFile8, rename as rename2, writeFile as writeFile4, stat as stat4 } from "fs/promises";
2082
- import { join as join10 } from "path";
2083
- var ENTRY_RE = /^## \[(\d{4})-\d{2}-\d{2}\]/gm;
2741
+ import { readFile as readFile11, rename as rename3, writeFile as writeFile3, stat as stat5 } from "fs/promises";
2742
+ import { join as join14 } from "path";
2743
+ var ENTRY_RE2 = /^## \[(\d{4})-\d{2}-\d{2}\]/gm;
2084
2744
  async function runLogRotate(input) {
2085
2745
  try {
2086
- await stat4(join10(input.vault, "SCHEMA.md"));
2746
+ await stat5(join14(input.vault, "SCHEMA.md"));
2087
2747
  } catch {
2088
2748
  return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { vault: input.vault }) };
2089
2749
  }
2090
- const logPath = join10(input.vault, "log.md");
2750
+ const logPath = join14(input.vault, "log.md");
2091
2751
  let logText;
2092
2752
  try {
2093
- logText = await readFile8(logPath, "utf8");
2753
+ logText = await readFile11(logPath, "utf8");
2094
2754
  } catch {
2095
2755
  return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path: logPath }) };
2096
2756
  }
2097
- const matches = [...logText.matchAll(ENTRY_RE)];
2757
+ const matches = [...logText.matchAll(ENTRY_RE2)];
2098
2758
  const entries = matches.length;
2099
2759
  if (entries < input.threshold) {
2100
2760
  return { exitCode: ExitCode.OK, result: ok({ entries, threshold: input.threshold, rotated: false, humanHint: `${entries}/${input.threshold} entries \u2014 no rotation needed` }) };
@@ -2107,9 +2767,9 @@ async function runLogRotate(input) {
2107
2767
  }
2108
2768
  const newestYear = matches[matches.length - 1][1];
2109
2769
  const rotatedName = `log-${newestYear}.md`;
2110
- const rotatedPath = join10(input.vault, rotatedName);
2770
+ const rotatedPath = join14(input.vault, rotatedName);
2111
2771
  try {
2112
- await rename2(logPath, rotatedPath);
2772
+ await rename3(logPath, rotatedPath);
2113
2773
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
2114
2774
  const fresh = `# Vault Log
2115
2775
 
@@ -2119,7 +2779,7 @@ Chronological action log. Newest entries last. Skill writes append entries; lint
2119
2779
 
2120
2780
  - Previous log moved to ${rotatedName}
2121
2781
  `;
2122
- await writeFile4(logPath, fresh, "utf8");
2782
+ await writeFile3(logPath, fresh, "utf8");
2123
2783
  } catch (e) {
2124
2784
  return { exitCode: ExitCode.WRITE_FAILED, result: err("WRITE_FAILED", { message: String(e) }) };
2125
2785
  }
@@ -2152,13 +2812,13 @@ async function runTopicMapCheck(input) {
2152
2812
  }
2153
2813
 
2154
2814
  // src/commands/index-link-format.ts
2155
- import { readFile as readFile9 } from "fs/promises";
2156
- import { join as join11 } from "path";
2815
+ import { readFile as readFile12 } from "fs/promises";
2816
+ import { join as join15 } from "path";
2157
2817
  var MD_LINK_RE = /\[[^\[\]]+\]\([^)]+\.md\)/;
2158
2818
  async function runIndexLinkFormat(input) {
2159
2819
  let text = "";
2160
2820
  try {
2161
- text = await readFile9(join11(input.vault, "index.md"), "utf8");
2821
+ text = await readFile12(join15(input.vault, "index.md"), "utf8");
2162
2822
  } catch {
2163
2823
  }
2164
2824
  const markdown_links = [];
@@ -2171,9 +2831,9 @@ ${markdown_links.map((l) => ` line ${l.line}: ${l.text}`).join("\n")}`;
2171
2831
  }
2172
2832
 
2173
2833
  // src/commands/dedup.ts
2174
- import { createHash as createHash2 } from "crypto";
2175
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2, unlinkSync as unlinkSync2 } from "fs";
2176
- import { dirname as dirname4, join as join12, resolve as resolve3 } from "path";
2834
+ import { createHash as createHash4 } from "crypto";
2835
+ import { mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync4, unlinkSync as unlinkSync4 } from "fs";
2836
+ import { dirname as dirname6, join as join16, resolve as resolve4 } from "path";
2177
2837
 
2178
2838
  // src/utils/rclone.ts
2179
2839
  import { execFile } from "child_process";
@@ -2265,7 +2925,7 @@ async function runDedup(input) {
2265
2925
  const manifest = safeEntries.length > 0 ? {
2266
2926
  version: 1,
2267
2927
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
2268
- vault: resolve3(input.vault),
2928
+ vault: resolve4(input.vault),
2269
2929
  entries: safeEntries
2270
2930
  } : void 0;
2271
2931
  const remote = await planAndMaybePruneRemote(input, safeEntries);
@@ -2277,8 +2937,8 @@ async function runDedup(input) {
2277
2937
  }
2278
2938
  if (input.manifestOut && manifest) {
2279
2939
  try {
2280
- mkdirSync2(dirname4(input.manifestOut), { recursive: true });
2281
- writeFileSync2(input.manifestOut, `${JSON.stringify(manifest, null, 2)}
2940
+ mkdirSync4(dirname6(input.manifestOut), { recursive: true });
2941
+ writeFileSync4(input.manifestOut, `${JSON.stringify(manifest, null, 2)}
2282
2942
  `, "utf-8");
2283
2943
  } catch (e) {
2284
2944
  return { exitCode: ExitCode.WRITE_FAILED, result: err("WRITE_FAILED", { path: input.manifestOut, message: String(e) }) };
@@ -2292,7 +2952,7 @@ async function runDedup(input) {
2292
2952
  }
2293
2953
  }
2294
2954
  for (const page of scan.allMarkdown.filter((p) => !p.relPath.startsWith("raw/"))) {
2295
- const text = readFileSync3(join12(input.vault, page.relPath), "utf-8");
2955
+ const text = readFileSync5(join16(input.vault, page.relPath), "utf-8");
2296
2956
  let updated = text;
2297
2957
  let changed = false;
2298
2958
  for (const [oldPath, newPath] of replacements) {
@@ -2310,14 +2970,14 @@ async function runDedup(input) {
2310
2970
  }
2311
2971
  }
2312
2972
  if (changed) {
2313
- writeFileSync2(join12(input.vault, page.relPath), updated);
2973
+ writeFileSync4(join16(input.vault, page.relPath), updated);
2314
2974
  rewired.push(page.relPath);
2315
2975
  }
2316
2976
  }
2317
2977
  for (const oldPath of replacements.keys()) {
2318
- const fullPath = join12(input.vault, oldPath);
2978
+ const fullPath = join16(input.vault, oldPath);
2319
2979
  try {
2320
- unlinkSync2(fullPath);
2980
+ unlinkSync4(fullPath);
2321
2981
  removed.push(oldPath);
2322
2982
  } catch {
2323
2983
  }
@@ -2409,14 +3069,14 @@ function buildSafeEntries(vault, duplicates, unsafe) {
2409
3069
  return entries;
2410
3070
  }
2411
3071
  function hashRawBody(vault, relPath) {
2412
- const text = readFileSync3(join12(vault, relPath), "utf-8");
3072
+ const text = readFileSync5(join16(vault, relPath), "utf-8");
2413
3073
  const split = splitFrontmatter(text);
2414
3074
  const body = split.ok ? split.data.body : text;
2415
- return createHash2("sha256").update(body).digest("hex");
3075
+ return createHash4("sha256").update(body).digest("hex");
2416
3076
  }
2417
3077
  function readManifest(path) {
2418
3078
  try {
2419
- const parsed = JSON.parse(readFileSync3(path, "utf-8"));
3079
+ const parsed = JSON.parse(readFileSync5(path, "utf-8"));
2420
3080
  if (parsed.version !== 1 || !Array.isArray(parsed.entries)) {
2421
3081
  return err("INVALID_FRONTMATTER", { message: "dedup manifest must have version 1 and entries[]" });
2422
3082
  }
@@ -2432,9 +3092,7 @@ async function planAndMaybePruneRemote(input, entries) {
2432
3092
  }
2433
3093
 
2434
3094
  // src/utils/safe-write.ts
2435
- import { open, readFile as readFile10, rename as rename3, unlink, writeFile as writeFile5 } from "fs/promises";
2436
- import { randomBytes } from "crypto";
2437
- import { dirname as dirname5, basename, join as join13 } from "path";
3095
+ import { readFile as readFile13, writeFile as writeFile4 } from "fs/promises";
2438
3096
  var DEFAULT_MIN_BODY_RATIO = 0.5;
2439
3097
  var DEFAULT_MIN_OLD_BODY_BYTES = 200;
2440
3098
  function bodyBytes(text) {
@@ -2444,7 +3102,7 @@ function bodyBytes(text) {
2444
3102
  }
2445
3103
  async function readIfExists(absPath) {
2446
3104
  try {
2447
- return await readFile10(absPath, "utf8");
3105
+ return await readFile13(absPath, "utf8");
2448
3106
  } catch (e) {
2449
3107
  if (e.code === "ENOENT") return null;
2450
3108
  throw e;
@@ -2478,32 +3136,16 @@ async function safeWritePage(absPath, newContent, opts = {}) {
2478
3136
  });
2479
3137
  }
2480
3138
  }
2481
- if (!isNew && oldContent === newContent) {
2482
- return ok({ isNew: false, oldBodyBytes, newBodyBytes, bodyRatio, guardSkippedSmall });
2483
- }
2484
- const dir = dirname5(absPath);
2485
- const tmpName = `.${basename(absPath)}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`;
2486
- const tmpPath = join13(dir, tmpName);
2487
- try {
2488
- const handle = await open(tmpPath, "w");
2489
- try {
2490
- await handle.writeFile(newContent, "utf8");
2491
- try {
2492
- await handle.sync();
2493
- } catch {
2494
- }
2495
- } finally {
2496
- await handle.close();
2497
- }
2498
- await rename3(tmpPath, absPath);
2499
- return ok({ isNew, oldBodyBytes, newBodyBytes, bodyRatio, guardSkippedSmall });
2500
- } catch (e) {
2501
- try {
2502
- await unlink(tmpPath);
2503
- } catch {
2504
- }
2505
- return err("WRITE_FAILED", { path: absPath, phase: "atomic-write", message: String(e) });
2506
- }
3139
+ const written = await atomicWriteText(absPath, newContent);
3140
+ if (!written.ok) return written;
3141
+ return ok({
3142
+ changed: written.data.changed,
3143
+ isNew,
3144
+ oldBodyBytes,
3145
+ newBodyBytes,
3146
+ bodyRatio,
3147
+ guardSkippedSmall
3148
+ });
2507
3149
  }
2508
3150
 
2509
3151
  // src/commands/frontmatter-fix.ts
@@ -2583,10 +3225,10 @@ ${newBody}`;
2583
3225
  }
2584
3226
 
2585
3227
  // src/commands/lint.ts
2586
- import { existsSync as existsSync5 } from "fs";
2587
- import { readFile as readFile12, readdir as readdir3 } from "fs/promises";
2588
- import { createHash as createHash4 } from "crypto";
2589
- import { join as join15, relative as relative3, sep as sep3 } from "path";
3228
+ import { existsSync as existsSync7 } from "fs";
3229
+ import { readFile as readFile15, readdir as readdir3 } from "fs/promises";
3230
+ import { createHash as createHash6 } from "crypto";
3231
+ import { join as join18, relative as relative4, sep as sep4 } from "path";
2590
3232
 
2591
3233
  // src/commands/sparse-community.ts
2592
3234
  async function runSparseCommunity(input) {
@@ -2602,7 +3244,7 @@ async function runSparseCommunity(input) {
2602
3244
  }
2603
3245
 
2604
3246
  // src/commands/raw-body-dedup.ts
2605
- import { createHash as createHash3 } from "crypto";
3247
+ import { createHash as createHash5 } from "crypto";
2606
3248
  async function runRawBodyDedup(vault, scan, pageTextCache) {
2607
3249
  const scanResult = scan ? ok(scan) : await scanVault(vault);
2608
3250
  if (!scanResult.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scanResult };
@@ -2611,7 +3253,7 @@ async function runRawBodyDedup(vault, scan, pageTextCache) {
2611
3253
  const text = await readPageCached(raw, pageTextCache);
2612
3254
  const split = splitFrontmatter(text);
2613
3255
  if (!split.ok) return null;
2614
- const bodyHash = createHash3("sha256").update(split.data.body).digest("hex");
3256
+ const bodyHash = createHash5("sha256").update(split.data.body).digest("hex");
2615
3257
  const fm = extractFrontmatter(text);
2616
3258
  let fmSha256 = null;
2617
3259
  if (fm.ok && typeof fm.data.sha256 === "string" && fm.data.sha256.length === 64) {
@@ -2643,9 +3285,9 @@ async function runRawBodyDedup(vault, scan, pageTextCache) {
2643
3285
  }
2644
3286
 
2645
3287
  // src/commands/path-too-long.ts
2646
- import { existsSync as existsSync4 } from "fs";
2647
- import { mkdir as mkdir4, readFile as readFile11, rename as rename4, unlink as unlink2 } from "fs/promises";
2648
- import { dirname as dirname6, join as join14, posix, resolve as resolve4 } from "path";
3288
+ import { existsSync as existsSync6 } from "fs";
3289
+ import { mkdir as mkdir4, readFile as readFile14, rename as rename4, unlink as unlink2 } from "fs/promises";
3290
+ import { dirname as dirname7, join as join17, posix as posix2, resolve as resolve5 } from "path";
2649
3291
  var MAX_PATH_LENGTH = 240;
2650
3292
  var WINDOWS_ABSOLUTE_PATH_LIMIT = 259;
2651
3293
  async function runPathTooLong(input) {
@@ -2678,10 +3320,10 @@ async function fixPathTooLong(input) {
2678
3320
  }
2679
3321
  try {
2680
3322
  if (target.mode === "dedupe") {
2681
- await unlink2(join14(input.vault, violation.relPath));
3323
+ await unlink2(join17(input.vault, violation.relPath));
2682
3324
  } else {
2683
- await mkdir4(dirname6(join14(input.vault, target.relPath)), { recursive: true });
2684
- await rename4(join14(input.vault, violation.relPath), join14(input.vault, target.relPath));
3325
+ await mkdir4(dirname7(join17(input.vault, target.relPath)), { recursive: true });
3326
+ await rename4(join17(input.vault, violation.relPath), join17(input.vault, target.relPath));
2685
3327
  }
2686
3328
  fixed.push({ from: violation.relPath, to: target.relPath });
2687
3329
  } catch {
@@ -2695,7 +3337,7 @@ async function fixPathTooLong(input) {
2695
3337
  for (const page of afterScan.data.allMarkdown) {
2696
3338
  if (!shouldRewriteReferences(page.relPath)) continue;
2697
3339
  try {
2698
- const original = await readFile11(page.absPath, "utf8");
3340
+ const original = await readFile14(page.absPath, "utf8");
2699
3341
  let updated = original;
2700
3342
  for (const fix of fixed) {
2701
3343
  updated = replacePathReferences(updated, fix.from, fix.to);
@@ -2732,7 +3374,7 @@ function findPathTooLongViolations(pages, maxLength) {
2732
3374
  }
2733
3375
  function maxFixPathLength(vault) {
2734
3376
  if (process.platform !== "win32") return MAX_PATH_LENGTH;
2735
- const root = resolve4(vault);
3377
+ const root = resolve5(vault);
2736
3378
  const separatorBudget = root.endsWith("\\") || root.endsWith("/") ? 0 : 1;
2737
3379
  const absoluteSafeRelLength = WINDOWS_ABSOLUTE_PATH_LIMIT - root.length - separatorBudget;
2738
3380
  return Math.max(1, Math.min(MAX_PATH_LENGTH, absoluteSafeRelLength));
@@ -2758,9 +3400,9 @@ function truncateFilename(relPath, maxLength = MAX_PATH_LENGTH) {
2758
3400
  async function resolveFixTarget(vault, original, preferred, maxLength) {
2759
3401
  for (const candidate of candidateRelPaths(preferred, maxLength)) {
2760
3402
  if (candidate === original || candidate.length > maxLength) continue;
2761
- const candidatePath = join14(vault, candidate);
2762
- if (!existsSync4(candidatePath)) return { relPath: candidate, mode: "rename" };
2763
- if (await hasSameContent(join14(vault, original), candidatePath)) {
3403
+ const candidatePath = join17(vault, candidate);
3404
+ if (!existsSync6(candidatePath)) return { relPath: candidate, mode: "rename" };
3405
+ if (await hasSameContent(join17(vault, original), candidatePath)) {
2764
3406
  return { relPath: candidate, mode: "dedupe" };
2765
3407
  }
2766
3408
  }
@@ -2769,8 +3411,8 @@ async function resolveFixTarget(vault, original, preferred, maxLength) {
2769
3411
  function candidateRelPaths(preferred, maxLength) {
2770
3412
  const candidates = [preferred];
2771
3413
  if (preferred.length > maxLength) return candidates;
2772
- const dir = posix.dirname(preferred) === "." ? "" : posix.dirname(preferred);
2773
- const filename = posix.basename(preferred);
3414
+ const dir = posix2.dirname(preferred) === "." ? "" : posix2.dirname(preferred);
3415
+ const filename = posix2.basename(preferred);
2774
3416
  const ext = filename.endsWith(".md") ? ".md" : "";
2775
3417
  const base = ext ? filename.slice(0, -3) : filename;
2776
3418
  const dirPrefix = dir ? `${dir}/` : "";
@@ -2784,7 +3426,7 @@ function candidateRelPaths(preferred, maxLength) {
2784
3426
  }
2785
3427
  async function hasSameContent(a, b) {
2786
3428
  try {
2787
- const [left, right] = await Promise.all([readFile11(a), readFile11(b)]);
3429
+ const [left, right] = await Promise.all([readFile14(a), readFile14(b)]);
2788
3430
  return left.equals(right);
2789
3431
  } catch {
2790
3432
  return false;
@@ -2797,8 +3439,8 @@ function shouldRewriteReferences(relPath) {
2797
3439
  }
2798
3440
  function replacePathReferences(content, oldRelPath, newRelPath) {
2799
3441
  let updated = content.replaceAll(oldRelPath, newRelPath);
2800
- const oldStem = posix.basename(oldRelPath).replace(/\.md$/, "");
2801
- const newStem = posix.basename(newRelPath).replace(/\.md$/, "");
3442
+ const oldStem = posix2.basename(oldRelPath).replace(/\.md$/, "");
3443
+ const newStem = posix2.basename(newRelPath).replace(/\.md$/, "");
2802
3444
  if (oldStem !== newStem) {
2803
3445
  const oldStemEscaped = oldStem.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2804
3446
  const stemWikilinkRe = new RegExp(`\\[\\[${oldStemEscaped}(\\|[^\\]]*)?\\]\\]`, "g");
@@ -2859,6 +3501,7 @@ function buildCliSurface() {
2859
3501
  program.command("transcripts").option("--since <date>").option("--wiki <name>");
2860
3502
  program.command("project-index").option("--apply").option("--wiki <name>");
2861
3503
  program.command("compound");
3504
+ program.command("tag");
2862
3505
  program.command("tag-sync").option("--dry-run").option("--wiki <name>");
2863
3506
  program.command("sync");
2864
3507
  program.command("backup");
@@ -2868,6 +3511,7 @@ function buildCliSurface() {
2868
3511
  program.command("memory");
2869
3512
  program.command("ingest").requiredOption("--vault <path>").requiredOption("--type <type>").requiredOption("--title <title>").option("--tags <csv>").option("--provenance <provenance>").option("--dry-run");
2870
3513
  program.command("fleet");
3514
+ program.command("page");
2871
3515
  const graphCmd = program.commands.find((c) => c.name() === "graph");
2872
3516
  graphCmd.command("build").option("--out <path>").option("--wiki <name>");
2873
3517
  const canvasCmd = program.commands.find((c) => c.name() === "canvas");
@@ -2881,6 +3525,10 @@ function buildCliSurface() {
2881
3525
  compoundCmd.command("promote").requiredOption("--project <slug>").option("--dry-run").option("--wiki <name>");
2882
3526
  compoundCmd.command("list").requiredOption("--project <slug>").option("--wiki <name>");
2883
3527
  compoundCmd.command("delete").requiredOption("--project <slug>").option("--wiki <name>");
3528
+ const tagCmd = program.commands.find((c) => c.name() === "tag");
3529
+ tagCmd.command("reconcile").requiredOption("--page <path>").option("--from <path>").option("--tags <csv>").option("--reason <text>").option("--write").option("--wiki <name>");
3530
+ const pageCmd = program.commands.find((c) => c.name() === "page");
3531
+ pageCmd.command("publish").requiredOption("--target <path>").option("--log-note <text>").option("--write").option("--wiki <name>");
2884
3532
  const syncCmd = program.commands.find((c) => c.name() === "sync");
2885
3533
  syncCmd.command("status").option("--wiki <name>").option("--include-stashes").option("--include-remote-health").option("--check-snapshotter");
2886
3534
  syncCmd.command("push").option("--wiki <name>");
@@ -3189,7 +3837,7 @@ function recomputeRawSha256IfPresent(content) {
3189
3837
  const split = splitFrontmatter(content);
3190
3838
  if (!split.ok) return content;
3191
3839
  if (!/^sha256:\s*[0-9a-f]{64}$/m.test(split.data.rawFrontmatter)) return content;
3192
- const sha256 = createHash4("sha256").update(Buffer.from(split.data.body, "utf8")).digest("hex");
3840
+ const sha256 = createHash6("sha256").update(Buffer.from(split.data.body, "utf8")).digest("hex");
3193
3841
  const rawFrontmatter = split.data.rawFrontmatter.replace(/^sha256:\s*[0-9a-f]{64}$/m, `sha256: ${sha256}`);
3194
3842
  return `---
3195
3843
  ${rawFrontmatter}
@@ -3237,24 +3885,24 @@ async function walkMarkdownFiles(absDir, vaultRoot) {
3237
3885
  const entries = await readdir3(absDir, { withFileTypes: true });
3238
3886
  const pages = [];
3239
3887
  for (const entry of entries) {
3240
- const absPath = join15(absDir, entry.name);
3888
+ const absPath = join18(absDir, entry.name);
3241
3889
  if (entry.isDirectory()) {
3242
3890
  if (entry.name === ".git" || entry.name === "node_modules") continue;
3243
3891
  pages.push(...await walkMarkdownFiles(absPath, vaultRoot));
3244
3892
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
3245
- pages.push({ absPath, relPath: relative3(vaultRoot, absPath).split(sep3).join("/") });
3893
+ pages.push({ absPath, relPath: relative4(vaultRoot, absPath).split(sep4).join("/") });
3246
3894
  }
3247
3895
  }
3248
3896
  return pages;
3249
3897
  }
3250
3898
  async function collectCliRefsPages(vault) {
3251
- if (!existsSync5(join15(vault, "SCHEMA.md"))) {
3899
+ if (!existsSync7(join18(vault, "SCHEMA.md"))) {
3252
3900
  return err("VAULT_PATH_INVALID", { root: vault, reason: "SCHEMA.md missing" });
3253
3901
  }
3254
3902
  const pages = [];
3255
3903
  for (const dir of CLI_REFS_TYPED_DIRS) {
3256
- const absDir = join15(vault, dir);
3257
- if (!existsSync5(absDir)) continue;
3904
+ const absDir = join18(vault, dir);
3905
+ if (!existsSync7(absDir)) continue;
3258
3906
  pages.push(...await walkMarkdownFiles(absDir, vault));
3259
3907
  }
3260
3908
  return ok(pages);
@@ -3375,7 +4023,7 @@ async function applyFileSourceUrlFix(input, scan, fileSourceUrlFlags, fileSource
3375
4023
  for (const relPath of fileSourceUrlFrontmatterFlags) {
3376
4024
  try {
3377
4025
  const absPath = `${input.vault}/${relPath}`;
3378
- const raw = await readFile12(absPath, "utf8");
4026
+ const raw = await readFile15(absPath, "utf8");
3379
4027
  const parts = raw.split("---", 3);
3380
4028
  if (parts.length < 3) {
3381
4029
  unresolved.push(relPath);
@@ -3768,8 +4416,8 @@ async function runLint(input) {
3768
4416
  const readKnowledgeContent = (slug) => {
3769
4417
  const existing = knowledgeContentCache.get(slug);
3770
4418
  if (existing) return existing;
3771
- const knowledgePath = join15(lintVault, "projects", slug, "knowledge.md");
3772
- const pending = existsSync5(knowledgePath) ? readFile12(knowledgePath, "utf8").catch(() => null) : Promise.resolve(null);
4419
+ const knowledgePath = join18(lintVault, "projects", slug, "knowledge.md");
4420
+ const pending = existsSync7(knowledgePath) ? readFile15(knowledgePath, "utf8").catch(() => null) : Promise.resolve(null);
3773
4421
  knowledgeContentCache.set(slug, pending);
3774
4422
  return pending;
3775
4423
  };
@@ -3870,12 +4518,12 @@ async function runLint(input) {
3870
4518
  else delete buckets.sensitive_content;
3871
4519
  }
3872
4520
  if (shouldFix("legacy_citation_style") && legacyPages.length > 0) {
3873
- const FENCE_RE2 = /```[\s\S]*?```/g;
4521
+ const FENCE_RE = /```[\s\S]*?```/g;
3874
4522
  const INLINE_MARKER = /\^\[raw\/[^\]]+\]/g;
3875
4523
  for (const relPath of legacyPages) {
3876
4524
  try {
3877
4525
  const absPath = `${input.vault}/${relPath}`;
3878
- const raw = await readFile12(absPath, "utf8");
4526
+ const raw = await readFile15(absPath, "utf8");
3879
4527
  const split = splitFrontmatter(raw);
3880
4528
  if (!split.ok) {
3881
4529
  unresolved.push(relPath);
@@ -3883,7 +4531,7 @@ async function runLint(input) {
3883
4531
  }
3884
4532
  const body = split.data.body;
3885
4533
  const rawFm = split.data.rawFrontmatter;
3886
- const stripped = body.replace(FENCE_RE2, "");
4534
+ const stripped = body.replace(FENCE_RE, "");
3887
4535
  const lines = stripped.split("\n");
3888
4536
  const inlineMarkers = [];
3889
4537
  let inSources = false;
@@ -3974,7 +4622,7 @@ ${newBody}`;
3974
4622
  for (const relPath of noOverview) {
3975
4623
  try {
3976
4624
  const absPath = `${input.vault}/${relPath}`;
3977
- const raw = await readFile12(absPath, "utf8");
4625
+ const raw = await readFile15(absPath, "utf8");
3978
4626
  const split = splitFrontmatter(raw);
3979
4627
  if (!split.ok) {
3980
4628
  unresolved.push(relPath);
@@ -4015,7 +4663,7 @@ ${trimmedBody}`;
4015
4663
  for (const relPath of missingTldrFlags) {
4016
4664
  try {
4017
4665
  const absPath = `${input.vault}/${relPath}`;
4018
- const raw = await readFile12(absPath, "utf8");
4666
+ const raw = await readFile15(absPath, "utf8");
4019
4667
  const split = splitFrontmatter(raw);
4020
4668
  if (!split.ok) {
4021
4669
  unresolved.push(relPath);
@@ -4060,12 +4708,12 @@ ${lines.join("\n")}`;
4060
4708
  }
4061
4709
  if (shouldFix("wikilink_citation") && wikilinkCitationFlags.length > 0) {
4062
4710
  const WIKILINK_RE = /\[\[raw\/([^\]|]+)(?:\|[^\]]*)?\]\]/g;
4063
- const FENCE_RE2 = /```[\s\S]*?```/g;
4711
+ const FENCE_RE = /```[\s\S]*?```/g;
4064
4712
  const wikilinkFixed = [];
4065
4713
  for (const relPath of wikilinkCitationFlags) {
4066
4714
  try {
4067
4715
  const absPath = `${input.vault}/${relPath}`;
4068
- const raw = await readFile12(absPath, "utf8");
4716
+ const raw = await readFile15(absPath, "utf8");
4069
4717
  const split = splitFrontmatter(raw);
4070
4718
  if (!split.ok) {
4071
4719
  unresolved.push(relPath);
@@ -4073,7 +4721,7 @@ ${lines.join("\n")}`;
4073
4721
  }
4074
4722
  const body = split.data.body;
4075
4723
  const rawFm = split.data.rawFrontmatter;
4076
- const stripped = body.replace(FENCE_RE2, "");
4724
+ const stripped = body.replace(FENCE_RE, "");
4077
4725
  const wikilinkMatches = [...stripped.matchAll(WIKILINK_RE)];
4078
4726
  if (wikilinkMatches.length === 0) {
4079
4727
  unresolved.push(relPath);
@@ -4406,14 +5054,14 @@ async function runSyncLintDelta(input) {
4406
5054
  }
4407
5055
 
4408
5056
  // src/commands/config.ts
4409
- import { readFile as readFile13 } from "fs/promises";
4410
- import { existsSync as existsSync6 } from "fs";
4411
- import { join as join16 } from "path";
5057
+ import { readFile as readFile16 } from "fs/promises";
5058
+ import { existsSync as existsSync8 } from "fs";
5059
+ import { join as join19 } from "path";
4412
5060
  function validateKey(key) {
4413
5061
  return CONFIG_KEYS.includes(key) || isValidWikiProfileKey(key);
4414
5062
  }
4415
5063
  function configPath(home) {
4416
- return join16(home, ".skillwiki", ".env");
5064
+ return join19(home, ".skillwiki", ".env");
4417
5065
  }
4418
5066
  async function runConfigGet(input) {
4419
5067
  if (!validateKey(input.key)) {
@@ -4431,7 +5079,7 @@ async function runConfigSet(input) {
4431
5079
  try {
4432
5080
  let originalContent;
4433
5081
  try {
4434
- originalContent = await readFile13(filePath, "utf8");
5082
+ originalContent = await readFile16(filePath, "utf8");
4435
5083
  } catch {
4436
5084
  }
4437
5085
  const existing = originalContent !== void 0 ? parseDotenvText(originalContent) : {};
@@ -4463,15 +5111,15 @@ async function runConfigList(input) {
4463
5111
  }
4464
5112
  async function runConfigPath(input) {
4465
5113
  const filePath = configPath(input.home);
4466
- return { exitCode: ExitCode.OK, result: ok({ path: filePath, exists: existsSync6(filePath), humanHint: filePath }) };
5114
+ return { exitCode: ExitCode.OK, result: ok({ path: filePath, exists: existsSync8(filePath), humanHint: filePath }) };
4467
5115
  }
4468
5116
 
4469
5117
  // src/commands/fleet.ts
4470
- import { readFile as readFile14 } from "fs/promises";
5118
+ import { readFile as readFile17 } from "fs/promises";
4471
5119
  import { hostname as nodeHostname, userInfo } from "os";
4472
- import { join as join17 } from "path";
5120
+ import { join as join20 } from "path";
4473
5121
  import yaml3 from "js-yaml";
4474
- var FLEET_REL_PATH = join17("projects", "llm-wiki", "architecture", "fleet.yaml");
5122
+ var FLEET_REL_PATH = join20("projects", "llm-wiki", "architecture", "fleet.yaml");
4475
5123
  async function runFleetValidate(input) {
4476
5124
  const loaded = await loadFleetManifest(input.file);
4477
5125
  if (!loaded.ok) {
@@ -4502,7 +5150,7 @@ async function runFleetContext(input) {
4502
5150
  const osHostname = input.osHostname ?? safeEnvValue(env.HOSTNAME) ?? nodeHostname();
4503
5151
  const user = input.user ?? safeEnvValue(env.USER) ?? safeUserName();
4504
5152
  const vault = input.vault ?? safeEnvValue(env.WIKI_PATH);
4505
- const file = input.file ?? (vault ? join17(vault, FLEET_REL_PATH) : void 0);
5153
+ const file = input.file ?? (vault ? join20(vault, FLEET_REL_PATH) : void 0);
4506
5154
  const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
4507
5155
  const loaded = file ? await loadFleetManifest(file) : { ok: false, error: "FILE_NOT_FOUND" };
4508
5156
  if (!loaded.ok) {
@@ -4622,7 +5270,7 @@ function fleetContextEnv(input) {
4622
5270
  const home = input.home ?? env.HOME ?? "";
4623
5271
  const osHostname = input.osHostname ?? safeEnvValue(env.HOSTNAME) ?? nodeHostname();
4624
5272
  const vault = input.vault ?? safeEnvValue(env.WIKI_PATH);
4625
- const file = input.file ?? (vault ? join17(vault, FLEET_REL_PATH) : void 0);
5273
+ const file = input.file ?? (vault ? join20(vault, FLEET_REL_PATH) : void 0);
4626
5274
  return { env, home, osHostname, vault, file };
4627
5275
  }
4628
5276
  async function loadFleetManifestAndHost(input) {
@@ -4682,7 +5330,7 @@ function satelliteGateFromFleetLoad(load) {
4682
5330
  async function loadFleetManifest(file) {
4683
5331
  let text;
4684
5332
  try {
4685
- text = await readFile14(file, "utf8");
5333
+ text = await readFile17(file, "utf8");
4686
5334
  } catch {
4687
5335
  return { ok: false, error: "FILE_NOT_FOUND" };
4688
5336
  }
@@ -4756,7 +5404,7 @@ async function resolveFleetHostId(input) {
4756
5404
  }
4757
5405
  trace.push({ source: "AGENT_HOST_ID", status: "unset" });
4758
5406
  if (input.home) {
4759
- const dotenv = await parseDotenvFile(join17(input.home, ".skillwiki", ".env"));
5407
+ const dotenv = await parseDotenvFile(join20(input.home, ".skillwiki", ".env"));
4760
5408
  if (dotenv.SKILLWIKI_HOST_ID) {
4761
5409
  trace.push({ source: "~/.skillwiki/.env:SKILLWIKI_HOST_ID", status: "matched", value: dotenv.SKILLWIKI_HOST_ID });
4762
5410
  return { hostId: dotenv.SKILLWIKI_HOST_ID, source: "~/.skillwiki/.env:SKILLWIKI_HOST_ID", trace };
@@ -4929,20 +5577,20 @@ function safeUserName() {
4929
5577
  }
4930
5578
 
4931
5579
  // src/commands/doctor.ts
4932
- import { existsSync as existsSync12, lstatSync, readlinkSync, readdirSync as readdirSync3, statSync, readFileSync as readFileSync9 } from "fs";
4933
- import { join as join23, resolve as resolve5 } from "path";
5580
+ import { existsSync as existsSync14, lstatSync as lstatSync2, readlinkSync, readdirSync as readdirSync3, statSync as statSync2, readFileSync as readFileSync11 } from "fs";
5581
+ import { join as join26, resolve as resolve6 } from "path";
4934
5582
  import { execSync as execSync2 } from "child_process";
4935
5583
  import { platform as platform2 } from "os";
4936
5584
 
4937
5585
  // src/utils/plugin-registry.ts
4938
- import { existsSync as existsSync7, readdirSync, readFileSync as readFileSync4 } from "fs";
4939
- import { join as join18 } from "path";
4940
- var REGISTRY_PATH = join18(".claude", "plugins", "installed_plugins.json");
4941
- var CODEX_CONFIG_PATH = join18(".codex", "config.toml");
5586
+ import { existsSync as existsSync9, readdirSync, readFileSync as readFileSync6 } from "fs";
5587
+ import { join as join21 } from "path";
5588
+ var REGISTRY_PATH = join21(".claude", "plugins", "installed_plugins.json");
5589
+ var CODEX_CONFIG_PATH = join21(".codex", "config.toml");
4942
5590
  var PLUGIN_KEY = "skillwiki@llm-wiki";
4943
5591
  function readInstalledPlugins(home) {
4944
5592
  try {
4945
- const raw = readFileSync4(join18(home, REGISTRY_PATH), "utf8");
5593
+ const raw = readFileSync6(join21(home, REGISTRY_PATH), "utf8");
4946
5594
  return JSON.parse(raw);
4947
5595
  } catch {
4948
5596
  return null;
@@ -4978,8 +5626,8 @@ function findPluginInstallations(home, key = PLUGIN_KEY) {
4978
5626
  function findCodexPlugin(home, key, pluginName, marketplace) {
4979
5627
  const config = readCodexPluginConfig(home, key, marketplace);
4980
5628
  if (!config?.enabled) return null;
4981
- const cacheRoot = join18(home, ".codex", "plugins", "cache", marketplace, pluginName);
4982
- if (!existsSync7(cacheRoot)) return null;
5629
+ const cacheRoot = join21(home, ".codex", "plugins", "cache", marketplace, pluginName);
5630
+ if (!existsSync9(cacheRoot)) return null;
4983
5631
  let versions;
4984
5632
  try {
4985
5633
  versions = readdirSync(cacheRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
@@ -4994,7 +5642,7 @@ function findCodexPlugin(home, key, pluginName, marketplace) {
4994
5642
  key,
4995
5643
  pluginName,
4996
5644
  marketplace,
4997
- installPath: join18(cacheRoot, version),
5645
+ installPath: join21(cacheRoot, version),
4998
5646
  version,
4999
5647
  sourceType: config.sourceType,
5000
5648
  source: config.source
@@ -5011,7 +5659,7 @@ function parsePluginKey(key) {
5011
5659
  function readCodexPluginConfig(home, key, marketplace) {
5012
5660
  let raw;
5013
5661
  try {
5014
- raw = readFileSync4(join18(home, CODEX_CONFIG_PATH), "utf8");
5662
+ raw = readFileSync6(join21(home, CODEX_CONFIG_PATH), "utf8");
5015
5663
  } catch {
5016
5664
  return null;
5017
5665
  }
@@ -5053,8 +5701,8 @@ function parseTomlScalar(rawValue) {
5053
5701
  }
5054
5702
 
5055
5703
  // src/utils/conflict-markers.ts
5056
- import { existsSync as existsSync8, readdirSync as readdirSync2, readFileSync as readFileSync5 } from "fs";
5057
- import { join as join19 } from "path";
5704
+ import { existsSync as existsSync10, readdirSync as readdirSync2, readFileSync as readFileSync7 } from "fs";
5705
+ import { join as join22 } from "path";
5058
5706
  function scanConflictMarkerBlocksInText(relPath, text) {
5059
5707
  const findings = [];
5060
5708
  const lines = text.split(/\r?\n/);
@@ -5105,21 +5753,21 @@ function walkMarkdownFiles2(root, dir, rel, out) {
5105
5753
  for (const entry of entries) {
5106
5754
  if (entry.isDirectory()) {
5107
5755
  if (PRUNE_DIRS.has(entry.name)) continue;
5108
- walkMarkdownFiles2(root, join19(dir, entry.name), rel ? `${rel}/${entry.name}` : entry.name, out);
5756
+ walkMarkdownFiles2(root, join22(dir, entry.name), rel ? `${rel}/${entry.name}` : entry.name, out);
5109
5757
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
5110
5758
  out.push(rel ? `${rel}/${entry.name}` : entry.name);
5111
5759
  }
5112
5760
  }
5113
5761
  }
5114
5762
  function scanVaultConflictMarkers(vaultRoot) {
5115
- if (!existsSync8(vaultRoot)) return [];
5763
+ if (!existsSync10(vaultRoot)) return [];
5116
5764
  const relPaths = [];
5117
5765
  walkMarkdownFiles2(vaultRoot, vaultRoot, "", relPaths);
5118
5766
  const all = [];
5119
5767
  for (const rel of relPaths) {
5120
5768
  let text;
5121
5769
  try {
5122
- text = readFileSync5(join19(vaultRoot, rel), "utf8");
5770
+ text = readFileSync7(join22(vaultRoot, rel), "utf8");
5123
5771
  } catch {
5124
5772
  continue;
5125
5773
  }
@@ -5129,8 +5777,8 @@ function scanVaultConflictMarkers(vaultRoot) {
5129
5777
  }
5130
5778
 
5131
5779
  // src/utils/remote-health.ts
5132
- import { existsSync as existsSync9, readFileSync as readFileSync6 } from "fs";
5133
- import { join as join20 } from "path";
5780
+ import { existsSync as existsSync11, readFileSync as readFileSync8 } from "fs";
5781
+ import { join as join23 } from "path";
5134
5782
  import { execFileSync } from "child_process";
5135
5783
  var REMOTE_PROBE_TIMEOUT_MS = 3e3;
5136
5784
  var defaultExec = (file, args, cwd) => execFileSync(file, args, {
@@ -5141,7 +5789,7 @@ var defaultExec = (file, args, cwd) => execFileSync(file, args, {
5141
5789
  }).trim();
5142
5790
  function readWikiS3RemoteConfigured(home) {
5143
5791
  try {
5144
- const content = readFileSync6(join20(home, ".skillwiki", ".env"), "utf8");
5792
+ const content = readFileSync8(join23(home, ".skillwiki", ".env"), "utf8");
5145
5793
  for (const line of content.split(/\r?\n/)) {
5146
5794
  const trimmed = line.trim();
5147
5795
  if (!trimmed || trimmed.startsWith("#")) continue;
@@ -5166,7 +5814,7 @@ function resolveWikiS3Remote(input) {
5166
5814
  return readWikiS3RemoteConfigured(input.home);
5167
5815
  }
5168
5816
  function probeGithubReachability(vaultPath, exec = defaultExec) {
5169
- if (!existsSync9(join20(vaultPath, ".git"))) return "unknown";
5817
+ if (!existsSync11(join23(vaultPath, ".git"))) return "unknown";
5170
5818
  try {
5171
5819
  exec("git", ["remote", "get-url", "origin"], vaultPath);
5172
5820
  } catch {
@@ -5231,11 +5879,11 @@ function probeRemoteHealth(input) {
5231
5879
  }
5232
5880
 
5233
5881
  // src/utils/satellite-run-health.ts
5234
- import { existsSync as existsSync10, readFileSync as readFileSync7 } from "fs";
5235
- import { join as join21 } from "path";
5882
+ import { existsSync as existsSync12, readFileSync as readFileSync9 } from "fs";
5883
+ import { join as join24 } from "path";
5236
5884
  var SATELLITE_STALE_MS = 26 * 60 * 60 * 1e3;
5237
5885
  function satelliteLatestRunPath(vault) {
5238
- return join21(vault, ".skillwiki", "agent-memory-trends", "latest-run.json");
5886
+ return join24(vault, ".skillwiki", "agent-memory-trends", "latest-run.json");
5239
5887
  }
5240
5888
  function isFailedRunStatus(status) {
5241
5889
  return status === "fail" || status === "failure";
@@ -5257,9 +5905,9 @@ function readSatelliteLatestRunFromText(text) {
5257
5905
  }
5258
5906
  function readSatelliteLatestRun(vault) {
5259
5907
  const latestPath = satelliteLatestRunPath(vault);
5260
- if (!existsSync10(latestPath)) return null;
5908
+ if (!existsSync12(latestPath)) return null;
5261
5909
  try {
5262
- return parseLatestRunFile(readFileSync7(latestPath, "utf8"));
5910
+ return parseLatestRunFile(readFileSync9(latestPath, "utf8"));
5263
5911
  } catch {
5264
5912
  return null;
5265
5913
  }
@@ -5288,8 +5936,8 @@ function evaluateSatelliteRunHealth(vault, now) {
5288
5936
  // src/utils/s3-mount-health.ts
5289
5937
  import { execSync } from "child_process";
5290
5938
  import { platform } from "os";
5291
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync3, unlinkSync as unlinkSync3, readFileSync as readFile15 } from "fs";
5292
- import { join as join22 } from "path";
5939
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync5, unlinkSync as unlinkSync5, readFileSync as readFile18 } from "fs";
5940
+ import { join as join25 } from "path";
5293
5941
  var OS = platform();
5294
5942
  function findRcloneMountPid() {
5295
5943
  try {
@@ -5373,7 +6021,7 @@ function extractRcloneFs(args) {
5373
6021
  function getRcloneArgs(pid) {
5374
6022
  try {
5375
6023
  if (OS === "linux") {
5376
- const raw = readFileSync8(`/proc/${pid}/cmdline`);
6024
+ const raw = readFileSync10(`/proc/${pid}/cmdline`);
5377
6025
  return new TextDecoder().decode(raw).split("\0").filter(Boolean);
5378
6026
  } else {
5379
6027
  const out = execSync(`ps -o args= -p ${pid}`, {
@@ -5416,7 +6064,7 @@ function queryRcloneRC(rcAddr, fs) {
5416
6064
  function detectFuseMount(vaultPath) {
5417
6065
  try {
5418
6066
  if (OS === "linux") {
5419
- const mounts = readFileSync8("/proc/mounts", "utf8");
6067
+ const mounts = readFileSync10("/proc/mounts", "utf8");
5420
6068
  let best = null;
5421
6069
  for (const line of mounts.split("\n")) {
5422
6070
  const parts = line.split(" ");
@@ -5447,35 +6095,35 @@ function detectFuseMount(vaultPath) {
5447
6095
  return null;
5448
6096
  }
5449
6097
  function writeTest(dir) {
5450
- const testFile = join22(dir, `.doctor-write-test-${process.pid}.tmp`);
6098
+ const testFile = join25(dir, `.doctor-write-test-${process.pid}.tmp`);
5451
6099
  const payload = `skillwiki doctor write test \u2014 ${Date.now()} \u2014 ${Math.random().toString(36).slice(2)}`;
5452
6100
  const start = Date.now();
5453
6101
  try {
5454
- writeFileSync3(testFile, payload, "utf8");
6102
+ writeFileSync5(testFile, payload, "utf8");
5455
6103
  } catch (e) {
5456
6104
  return { success: false, writeMs: Date.now() - start, readMs: 0, size: 0, error: `write failed: ${e.message}` };
5457
6105
  }
5458
6106
  const writeMs = Date.now() - start;
5459
6107
  const readStart = Date.now();
5460
6108
  try {
5461
- const back = readFile15(testFile, "utf8");
6109
+ const back = readFile18(testFile, "utf8");
5462
6110
  const readMs = Date.now() - readStart;
5463
6111
  if (back !== payload) {
5464
6112
  try {
5465
- unlinkSync3(testFile);
6113
+ unlinkSync5(testFile);
5466
6114
  } catch {
5467
6115
  }
5468
6116
  return { success: false, writeMs, readMs, size: Buffer.byteLength(payload, "utf8"), error: "content mismatch \u2014 wrote and read-back differ" };
5469
6117
  }
5470
6118
  } catch (e) {
5471
6119
  try {
5472
- unlinkSync3(testFile);
6120
+ unlinkSync5(testFile);
5473
6121
  } catch {
5474
6122
  }
5475
6123
  return { success: false, writeMs, readMs: Date.now() - readStart, size: 0, error: `read failed: ${e.message}` };
5476
6124
  }
5477
6125
  try {
5478
- unlinkSync3(testFile);
6126
+ unlinkSync5(testFile);
5479
6127
  } catch {
5480
6128
  }
5481
6129
  return { success: true, writeMs, readMs: Date.now() - readStart, size: Buffer.byteLength(payload, "utf8") };
@@ -5532,14 +6180,14 @@ function checkNodeVersion() {
5532
6180
  function detectCliChannels(argv, home) {
5533
6181
  const channels = [];
5534
6182
  if (argv.length >= 2 && argv[1].endsWith("cli.js")) {
5535
- const devPath = resolve5(argv[1]);
6183
+ const devPath = resolve6(argv[1]);
5536
6184
  channels.push({ name: "dev", path: devPath, isDevLink: true });
5537
6185
  }
5538
6186
  try {
5539
6187
  const whichOut = execSync2("which skillwiki 2>/dev/null", { encoding: "utf8" }).trim();
5540
6188
  if (whichOut) {
5541
6189
  const isDev = isDevSymlink(whichOut);
5542
- if (!channels.some((c) => c.path === resolve5(whichOut))) {
6190
+ if (!channels.some((c) => c.path === resolve6(whichOut))) {
5543
6191
  channels.push({ name: "npm", path: whichOut, isDevLink: isDev });
5544
6192
  }
5545
6193
  }
@@ -5547,22 +6195,22 @@ function detectCliChannels(argv, home) {
5547
6195
  }
5548
6196
  const plugin = findPlugin(home);
5549
6197
  if (plugin) {
5550
- const pluginBin = join23(plugin.installPath, "bin", "skillwiki");
5551
- if (existsSync12(pluginBin)) {
6198
+ const pluginBin = join26(plugin.installPath, "bin", "skillwiki");
6199
+ if (existsSync14(pluginBin)) {
5552
6200
  channels.push({ name: "plugin", path: pluginBin, isDevLink: false });
5553
6201
  }
5554
6202
  }
5555
- const installBin = join23(home, ".claude", "skills", "bin", "skillwiki");
5556
- if (existsSync12(installBin)) {
6203
+ const installBin = join26(home, ".claude", "skills", "bin", "skillwiki");
6204
+ if (existsSync14(installBin)) {
5557
6205
  channels.push({ name: "install", path: installBin, isDevLink: false });
5558
6206
  }
5559
6207
  return channels;
5560
6208
  }
5561
6209
  function isDevSymlink(binPath) {
5562
6210
  try {
5563
- const st = lstatSync(binPath);
6211
+ const st = lstatSync2(binPath);
5564
6212
  if (st.isSymbolicLink()) {
5565
- const target = resolve5(binPath, "..", readlinkSync(binPath));
6213
+ const target = resolve6(binPath, "..", readlinkSync(binPath));
5566
6214
  return target.includes("packages/cli") || target.includes("packages\\cli");
5567
6215
  }
5568
6216
  } catch {
@@ -5614,7 +6262,7 @@ function isDevSourceRun(argv) {
5614
6262
  }
5615
6263
  async function checkConfigFile(home) {
5616
6264
  const cfgPath = configPath(home);
5617
- if (!existsSync12(cfgPath)) {
6265
+ if (!existsSync14(cfgPath)) {
5618
6266
  return check("warn", "config_file", "Config file exists", `${cfgPath} not found`);
5619
6267
  }
5620
6268
  try {
@@ -5629,7 +6277,7 @@ function checkWikiPathExists(resolvedPath) {
5629
6277
  if (resolvedPath === void 0) {
5630
6278
  return check("error", "wiki_path_exists", "Vault directory exists", "Cannot check \u2014 WIKI_PATH not resolved");
5631
6279
  }
5632
- if (existsSync12(resolvedPath) && statSync(resolvedPath).isDirectory()) {
6280
+ if (existsSync14(resolvedPath) && statSync2(resolvedPath).isDirectory()) {
5633
6281
  return check("pass", "wiki_path_exists", "Vault directory exists", resolvedPath);
5634
6282
  }
5635
6283
  return check("error", "wiki_path_exists", "Vault directory exists", `${resolvedPath} does not exist or is not a directory`);
@@ -5638,13 +6286,13 @@ function checkVaultStructure(resolvedPath) {
5638
6286
  if (resolvedPath === void 0) {
5639
6287
  return check("error", "vault_structure", "Vault structure valid", "Cannot check \u2014 WIKI_PATH not resolved");
5640
6288
  }
5641
- if (!existsSync12(resolvedPath)) {
6289
+ if (!existsSync14(resolvedPath)) {
5642
6290
  return check("error", "vault_structure", "Vault structure valid", "Cannot check \u2014 vault directory does not exist");
5643
6291
  }
5644
6292
  const missing = [];
5645
- if (!existsSync12(join23(resolvedPath, "SCHEMA.md"))) missing.push("SCHEMA.md");
6293
+ if (!existsSync14(join26(resolvedPath, "SCHEMA.md"))) missing.push("SCHEMA.md");
5646
6294
  for (const dir of ["raw", "entities", "concepts", "meta"]) {
5647
- if (!existsSync12(join23(resolvedPath, dir))) missing.push(dir + "/");
6295
+ if (!existsSync14(join26(resolvedPath, dir))) missing.push(dir + "/");
5648
6296
  }
5649
6297
  if (missing.length === 0) {
5650
6298
  return check("pass", "vault_structure", "Vault structure valid", "All required files and directories present");
@@ -5652,8 +6300,8 @@ function checkVaultStructure(resolvedPath) {
5652
6300
  return check("warn", "vault_structure", "Vault structure valid", `Missing: ${missing.join(", ")} \u2014 run \`skillwiki init\` to add CodeWiki structure`);
5653
6301
  }
5654
6302
  function checkSkillsInstalled(home, cwd) {
5655
- const srcDir = cwd ? join23(cwd, "packages", "skills") : void 0;
5656
- if (srcDir && existsSync12(srcDir)) {
6303
+ const srcDir = cwd ? join26(cwd, "packages", "skills") : void 0;
6304
+ if (srcDir && existsSync14(srcDir)) {
5657
6305
  const found = findInstalledSkillMd(srcDir);
5658
6306
  if (found.length > 0) {
5659
6307
  return check("pass", "skills_installed", "Skills installed", `${found.length} SKILL.md file(s) found (source)`);
@@ -5666,8 +6314,8 @@ function checkSkillsInstalled(home, cwd) {
5666
6314
  return check("pass", "skills_installed", "Skills installed", `${found.length} SKILL.md file(s) found (plugin v${plugin.version})`);
5667
6315
  }
5668
6316
  }
5669
- const skillsDir = join23(home, ".claude", "skills");
5670
- if (existsSync12(skillsDir)) {
6317
+ const skillsDir = join26(home, ".claude", "skills");
6318
+ if (existsSync14(skillsDir)) {
5671
6319
  const found = findInstalledSkillMd(skillsDir);
5672
6320
  if (found.length > 0) {
5673
6321
  return check("pass", "skills_installed", "Skills installed", `${found.length} SKILL.md file(s) found (CLI install)`);
@@ -5677,10 +6325,10 @@ function checkSkillsInstalled(home, cwd) {
5677
6325
  }
5678
6326
  function checkDuplicateSkills(home) {
5679
6327
  const plugin = findPlugin(home);
5680
- const skillsDir = join23(home, ".claude", "skills");
6328
+ const skillsDir = join26(home, ".claude", "skills");
5681
6329
  const agentSkillDirs = [
5682
- { label: "~/.codex/skills/", path: join23(home, ".codex", "skills") },
5683
- { label: "~/.agents/skills/", path: join23(home, ".agents", "skills") }
6330
+ { label: "~/.codex/skills/", path: join26(home, ".codex", "skills") },
6331
+ { label: "~/.agents/skills/", path: join26(home, ".agents", "skills") }
5684
6332
  ];
5685
6333
  if (!plugin) {
5686
6334
  return check("pass", "skills_duplicate", "Skills not duplicated", "Single install channel");
@@ -5783,8 +6431,8 @@ async function checkProfiles(home) {
5783
6431
  }
5784
6432
  async function checkProjectLocalOverride(cwd) {
5785
6433
  const dir = cwd ?? process.cwd();
5786
- const envPath = join23(dir, ".skillwiki", ".env");
5787
- if (existsSync12(envPath)) {
6434
+ const envPath = join26(dir, ".skillwiki", ".env");
6435
+ if (existsSync14(envPath)) {
5788
6436
  return check("pass", "project_local", "Project-local config", `Found: ${envPath}`);
5789
6437
  }
5790
6438
  return check("pass", "project_local", "Project-local config", "None");
@@ -5793,7 +6441,7 @@ function checkVaultGitRemote(resolvedPath) {
5793
6441
  if (resolvedPath === void 0) {
5794
6442
  return check("error", "vault_git_remote", "Vault git remote", "Cannot check \u2014 WIKI_PATH not resolved");
5795
6443
  }
5796
- if (!existsSync12(join23(resolvedPath, ".git"))) {
6444
+ if (!existsSync14(join26(resolvedPath, ".git"))) {
5797
6445
  return check("warn", "vault_git_remote", "Vault git remote", "Vault is not a git repository \u2014 sync features unavailable");
5798
6446
  }
5799
6447
  try {
@@ -5816,9 +6464,9 @@ function checkObsidianTemplates(resolvedPath) {
5816
6464
  return check("error", "obsidian_templates", "Obsidian templates", "Cannot check \u2014 WIKI_PATH not resolved");
5817
6465
  }
5818
6466
  const missing = [];
5819
- if (!existsSync12(join23(resolvedPath, "_Templates"))) missing.push("_Templates/");
5820
- if (!existsSync12(join23(resolvedPath, ".obsidian", "templates.json"))) missing.push(".obsidian/templates.json");
5821
- if (!existsSync12(join23(resolvedPath, ".obsidian", "app.json"))) missing.push(".obsidian/app.json");
6467
+ if (!existsSync14(join26(resolvedPath, "_Templates"))) missing.push("_Templates/");
6468
+ if (!existsSync14(join26(resolvedPath, ".obsidian", "templates.json"))) missing.push(".obsidian/templates.json");
6469
+ if (!existsSync14(join26(resolvedPath, ".obsidian", "app.json"))) missing.push(".obsidian/app.json");
5822
6470
  if (missing.length === 0) {
5823
6471
  return check("pass", "obsidian_templates", "Obsidian templates", "Template folder and config present");
5824
6472
  }
@@ -5828,8 +6476,8 @@ function checkDotStoreClean(resolvedPath) {
5828
6476
  if (resolvedPath === void 0) {
5829
6477
  return check("error", "dsstore_clean", "No .DS_Store in raw/", "Cannot check \u2014 WIKI_PATH not resolved");
5830
6478
  }
5831
- const rawDir = join23(resolvedPath, "raw");
5832
- if (!existsSync12(rawDir)) {
6479
+ const rawDir = join26(resolvedPath, "raw");
6480
+ if (!existsSync14(rawDir)) {
5833
6481
  return check("pass", "dsstore_clean", "No .DS_Store in raw/", "raw/ directory not found \u2014 check skipped");
5834
6482
  }
5835
6483
  const found = [];
@@ -5844,7 +6492,7 @@ function checkDotStoreClean(resolvedPath) {
5844
6492
  if (entry.name === ".DS_Store") {
5845
6493
  found.push(rel ? `${rel}/.DS_Store` : ".DS_Store");
5846
6494
  } else if (entry.isDirectory()) {
5847
- walk2(join23(dir, entry.name), rel ? `${rel}/${entry.name}` : entry.name);
6495
+ walk2(join26(dir, entry.name), rel ? `${rel}/${entry.name}` : entry.name);
5848
6496
  }
5849
6497
  }
5850
6498
  })(rawDir, "");
@@ -5875,7 +6523,7 @@ function checkSyncLastPush(resolvedPath) {
5875
6523
  if (resolvedPath === void 0) {
5876
6524
  return check("error", "sync_last_push", "Vault sync recency", "Cannot check \u2014 WIKI_PATH not resolved");
5877
6525
  }
5878
- if (!existsSync12(join23(resolvedPath, ".git"))) {
6526
+ if (!existsSync14(join26(resolvedPath, ".git"))) {
5879
6527
  return check("pass", "sync_last_push", "Vault sync recency", "No git repo \u2014 sync check skipped");
5880
6528
  }
5881
6529
  let timestamp;
@@ -5923,7 +6571,7 @@ function checkVaultGitDirty(resolvedPath) {
5923
6571
  if (resolvedPath === void 0) {
5924
6572
  return check("pass", "vault_git_dirty", "Vault git dirty state", "No vault path \u2014 check skipped");
5925
6573
  }
5926
- if (!existsSync12(join23(resolvedPath, ".git"))) {
6574
+ if (!existsSync14(join26(resolvedPath, ".git"))) {
5927
6575
  return check("pass", "vault_git_dirty", "Vault git dirty state", "No git repo \u2014 check skipped");
5928
6576
  }
5929
6577
  try {
@@ -5991,7 +6639,7 @@ function remoteMainHash(resolvedPath) {
5991
6639
  }
5992
6640
  function checkStaleRemoteMain(resolvedPath) {
5993
6641
  if (resolvedPath === void 0) return void 0;
5994
- if (!existsSync12(join23(resolvedPath, ".git"))) return void 0;
6642
+ if (!existsSync14(join26(resolvedPath, ".git"))) return void 0;
5995
6643
  const localOrigin = gitRefHash(resolvedPath, "origin/main");
5996
6644
  if (!localOrigin) return void 0;
5997
6645
  const remoteMain = remoteMainHash(resolvedPath);
@@ -6007,7 +6655,7 @@ function checkVaultLocalGit(resolvedPath) {
6007
6655
  if (resolvedPath === void 0) {
6008
6656
  return check("warn", "vault_local_git", "Vault local git", "Cannot check \u2014 WIKI_PATH not resolved");
6009
6657
  }
6010
- if (!existsSync12(join23(resolvedPath, ".git"))) {
6658
+ if (!existsSync14(join26(resolvedPath, ".git"))) {
6011
6659
  return check("warn", "vault_local_git", "Vault local git", "Not a git repository - sync features unavailable");
6012
6660
  }
6013
6661
  try {
@@ -6026,7 +6674,7 @@ function checkVaultGithubRemote(resolvedPath, exec) {
6026
6674
  if (resolvedPath === void 0) {
6027
6675
  return check("pass", "vault_github_remote", "Vault GitHub remote", "No vault path \u2014 check skipped");
6028
6676
  }
6029
- if (!existsSync12(join23(resolvedPath, ".git"))) {
6677
+ if (!existsSync14(join26(resolvedPath, ".git"))) {
6030
6678
  return check("pass", "vault_github_remote", "Vault GitHub remote", "No git repo \u2014 check skipped");
6031
6679
  }
6032
6680
  const state = probeGithubReachability(resolvedPath, exec);
@@ -6070,7 +6718,7 @@ function checkVaultPromotionLag(resolvedPath) {
6070
6718
  if (resolvedPath === void 0) {
6071
6719
  return check("pass", "vault_promotion_lag", "Vault promotion lag", "No vault path \u2014 check skipped");
6072
6720
  }
6073
- if (!existsSync12(join23(resolvedPath, ".git"))) {
6721
+ if (!existsSync14(join26(resolvedPath, ".git"))) {
6074
6722
  return check("pass", "vault_promotion_lag", "Vault promotion lag", "No git repo \u2014 check skipped");
6075
6723
  }
6076
6724
  try {
@@ -6097,7 +6745,7 @@ function checkVaultGitComparison(resolvedPath, id, label, range, nonZeroSuffix,
6097
6745
  if (resolvedPath === void 0) {
6098
6746
  return check("pass", id, label, "No vault path \u2014 check skipped");
6099
6747
  }
6100
- if (!existsSync12(join23(resolvedPath, ".git"))) {
6748
+ if (!existsSync14(join26(resolvedPath, ".git"))) {
6101
6749
  return check("pass", id, label, "No git repo \u2014 check skipped");
6102
6750
  }
6103
6751
  if (!hasOriginMain(resolvedPath)) {
@@ -6125,7 +6773,7 @@ function checkSatelliteLastRun(vaultPath, satelliteExpected) {
6125
6773
  return check("pass", "satellite_job_last_run", "Satellite job last run", "No vault path \u2014 check skipped");
6126
6774
  }
6127
6775
  const latestPath = satelliteLatestRunPath(vaultPath);
6128
- if (!existsSync12(latestPath)) {
6776
+ if (!existsSync14(latestPath)) {
6129
6777
  return check("pass", "satellite_job_last_run", "Satellite job last run", "No latest-run.json \u2014 satellite has not run yet");
6130
6778
  }
6131
6779
  try {
@@ -6213,11 +6861,11 @@ async function checkFleetIdentity(input) {
6213
6861
  }
6214
6862
  function pullLogPaths(home) {
6215
6863
  const paths = platform2() === "darwin" ? [
6216
- join23(home, "Library", "Logs", "wiki-pull.log"),
6217
- join23(home, ".local", "state", "vault-sync", "log", "wiki-pull.log")
6864
+ join26(home, "Library", "Logs", "wiki-pull.log"),
6865
+ join26(home, ".local", "state", "vault-sync", "log", "wiki-pull.log")
6218
6866
  ] : [
6219
- join23(home, ".local", "state", "vault-sync", "log", "wiki-pull.log"),
6220
- join23(home, "Library", "Logs", "wiki-pull.log")
6867
+ join26(home, ".local", "state", "vault-sync", "log", "wiki-pull.log"),
6868
+ join26(home, "Library", "Logs", "wiki-pull.log")
6221
6869
  ];
6222
6870
  return [...new Set(paths)];
6223
6871
  }
@@ -6229,12 +6877,12 @@ function isRecentLogLine(line, nowMs) {
6229
6877
  return nowMs - ts <= 24 * 60 * 60 * 1e3;
6230
6878
  }
6231
6879
  function checkVaultGitPullFailures(home) {
6232
- const path = pullLogPaths(home).find((p) => existsSync12(p));
6880
+ const path = pullLogPaths(home).find((p) => existsSync14(p));
6233
6881
  if (!path) {
6234
6882
  return check("pass", "vault_git_pull_failures", "Vault pull failures", "No wiki-pull.log found \u2014 check skipped");
6235
6883
  }
6236
6884
  try {
6237
- const lines = readFileSync9(path, "utf8").split(/\r?\n/).filter(Boolean);
6885
+ const lines = readFileSync11(path, "utf8").split(/\r?\n/).filter(Boolean);
6238
6886
  const now = Date.now();
6239
6887
  const failures = lines.filter(
6240
6888
  (line) => isRecentLogLine(line, now) && /(pre-push pull failed|FAIL .*pull|FAIL .*rebase|cannot pull with rebase|unstaged changes)/i.test(line)
@@ -6257,8 +6905,8 @@ function checkS3MountPerf(resolvedPath) {
6257
6905
  return check("pass", "s3_mount_perf", "S3 mount performance", "local disk");
6258
6906
  }
6259
6907
  const mountPoint = fuse.mountPoint;
6260
- const conceptsDir = join23(resolvedPath, "concepts");
6261
- if (!existsSync12(conceptsDir)) {
6908
+ const conceptsDir = join26(resolvedPath, "concepts");
6909
+ if (!existsSync14(conceptsDir)) {
6262
6910
  return check("pass", "s3_mount_perf", "S3 mount performance", `S3 FUSE mount (${mountPoint}), no concepts/ to benchmark`);
6263
6911
  }
6264
6912
  const start = Date.now();
@@ -6440,8 +7088,8 @@ function checkWriteTest(resolvedPath) {
6440
7088
  if (!fuse) {
6441
7089
  return check("pass", "s3_write_test", "S3 write test", "local disk \u2014 check skipped");
6442
7090
  }
6443
- const conceptsDir = join23(resolvedPath, "concepts");
6444
- if (!existsSync12(conceptsDir)) {
7091
+ const conceptsDir = join26(resolvedPath, "concepts");
7092
+ if (!existsSync14(conceptsDir)) {
6445
7093
  return check("pass", "s3_write_test", "S3 write test", "no concepts/ dir to test \u2014 check skipped");
6446
7094
  }
6447
7095
  const result = writeTest(conceptsDir);
@@ -6527,7 +7175,7 @@ function checkVfsCacheHealth(resolvedPath) {
6527
7175
  }
6528
7176
  function readVaultSyncConfig(home) {
6529
7177
  try {
6530
- const content = readFileSync9(join23(home, ".skillwiki", ".env"), "utf8");
7178
+ const content = readFileSync11(join26(home, ".skillwiki", ".env"), "utf8");
6531
7179
  let installed = false;
6532
7180
  let role;
6533
7181
  let serviceScope;
@@ -6556,7 +7204,7 @@ function readVaultSyncConfig(home) {
6556
7204
  }
6557
7205
  function readKeyFromEnvFile(path, keys) {
6558
7206
  try {
6559
- const content = readFileSync9(path, "utf8");
7207
+ const content = readFileSync11(path, "utf8");
6560
7208
  for (const line of content.split(/\r?\n/)) {
6561
7209
  const trimmed = line.trim();
6562
7210
  if (trimmed.length === 0 || trimmed.startsWith("#")) continue;
@@ -6578,7 +7226,7 @@ function resolveSnapshotGitWorktree(config) {
6578
7226
  if (fromProfile) return fromProfile;
6579
7227
  }
6580
7228
  const defaultPath = "/root/wiki-git";
6581
- return existsSync12(defaultPath) ? defaultPath : void 0;
7229
+ return existsSync14(defaultPath) ? defaultPath : void 0;
6582
7230
  }
6583
7231
  function vaultSyncChecks(input) {
6584
7232
  const os = input.os ?? platform2();
@@ -6595,16 +7243,16 @@ function vaultSyncChecks(input) {
6595
7243
  ];
6596
7244
  }
6597
7245
  const isMac = os === "darwin";
6598
- const logDir = input.logDir ?? (isMac ? join23(home, "Library", "Logs") : join23(home, ".local", "state", "vault-sync", "log"));
6599
- const shareDir = input.shareDir ?? (isMac ? join23(home, "Library", "Application Support", "vault-sync", "bin") : join23(home, ".local", "share", "vault-sync", "bin"));
6600
- const filterPath = input.filterPath ?? join23(home, ".config", "rclone", "wiki-push-filters.txt");
6601
- const packagedSnapshotPath = join23(shareDir, "wiki-snapshot.sh");
7246
+ const logDir = input.logDir ?? (isMac ? join26(home, "Library", "Logs") : join26(home, ".local", "state", "vault-sync", "log"));
7247
+ const shareDir = input.shareDir ?? (isMac ? join26(home, "Library", "Application Support", "vault-sync", "bin") : join26(home, ".local", "share", "vault-sync", "bin"));
7248
+ const filterPath = input.filterPath ?? join26(home, ".config", "rclone", "wiki-push-filters.txt");
7249
+ const packagedSnapshotPath = join26(shareDir, "wiki-snapshot.sh");
6602
7250
  const legacySnapshotPath = "/root/.hermes/scripts/wiki-snapshot-v3.sh";
6603
- const snapshotPath = input.snapshotScriptPath ?? (existsSync12(packagedSnapshotPath) ? packagedSnapshotPath : legacySnapshotPath);
7251
+ const snapshotPath = input.snapshotScriptPath ?? (existsSync14(packagedSnapshotPath) ? packagedSnapshotPath : legacySnapshotPath);
6604
7252
  function snapshotLastStatusCheck() {
6605
- const snapshotLog = join23(logDir, "wiki-snapshot.log");
7253
+ const snapshotLog = join26(logDir, "wiki-snapshot.log");
6606
7254
  try {
6607
- const logContent = readFileSync9(snapshotLog, "utf8");
7255
+ const logContent = readFileSync11(snapshotLog, "utf8");
6608
7256
  const lines = logContent.trim().split("\n").filter(Boolean);
6609
7257
  if (lines.length === 0) {
6610
7258
  return check(
@@ -6649,14 +7297,14 @@ function vaultSyncChecks(input) {
6649
7297
  }
6650
7298
  }
6651
7299
  if (input.vaultSyncRole === "snapshotter") {
6652
- const c12 = existsSync12(snapshotPath) ? check("pass", "vault_sync_installed", "Vault sync installed", `Found snapshot script: ${snapshotPath}`) : check("error", "vault_sync_installed", "Vault sync installed", `Snapshot script not found at ${snapshotPath}`);
7300
+ const c12 = existsSync14(snapshotPath) ? check("pass", "vault_sync_installed", "Vault sync installed", `Found snapshot script: ${snapshotPath}`) : check("error", "vault_sync_installed", "Vault sync installed", `Snapshot script not found at ${snapshotPath}`);
6653
7301
  const serviceScope = input.vaultSyncServiceScope ?? "user";
6654
- const userTimerPath = join23(home, ".config", "systemd", "user", "wiki-snapshot.timer");
7302
+ const userTimerPath = join26(home, ".config", "systemd", "user", "wiki-snapshot.timer");
6655
7303
  const systemTimerPath = "/etc/systemd/system/wiki-snapshot.timer";
6656
7304
  let c22;
6657
- if (serviceScope === "user" && existsSync12(userTimerPath)) {
7305
+ if (serviceScope === "user" && existsSync14(userTimerPath)) {
6658
7306
  c22 = check("pass", "vault_sync_jobs_enabled", "Vault sync jobs enabled", `Found: ${userTimerPath}`);
6659
- } else if (serviceScope === "system" && existsSync12(systemTimerPath)) {
7307
+ } else if (serviceScope === "system" && existsSync14(systemTimerPath)) {
6660
7308
  c22 = check("pass", "vault_sync_jobs_enabled", "Vault sync jobs enabled", `Found: ${systemTimerPath}`);
6661
7309
  } else if (os !== "linux") {
6662
7310
  c22 = check("warn", "vault_sync_jobs_enabled", "Vault sync jobs enabled", "Snapshotter scheduler is Linux-only and no wiki-snapshot.timer file was found");
@@ -6688,7 +7336,7 @@ function vaultSyncChecks(input) {
6688
7336
  );
6689
7337
  let c52;
6690
7338
  try {
6691
- if (!existsSync12(snapshotPath)) {
7339
+ if (!existsSync14(snapshotPath)) {
6692
7340
  c52 = check(
6693
7341
  "error",
6694
7342
  "vault_sync_snapshot_guard",
@@ -6696,7 +7344,7 @@ function vaultSyncChecks(input) {
6696
7344
  `Snapshot script not found at ${snapshotPath}`
6697
7345
  );
6698
7346
  } else {
6699
- const content = readFileSync9(snapshotPath, "utf8");
7347
+ const content = readFileSync11(snapshotPath, "utf8");
6700
7348
  if (!content.includes("--max-delete")) {
6701
7349
  c52 = check(
6702
7350
  "error",
@@ -6723,8 +7371,8 @@ function vaultSyncChecks(input) {
6723
7371
  }
6724
7372
  return [c12, c22, c32, cFetch2, c42, c52];
6725
7373
  }
6726
- const pushScriptPath = join23(shareDir, "wiki-push.sh");
6727
- const c1 = existsSync12(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`);
7374
+ const pushScriptPath = join26(shareDir, "wiki-push.sh");
7375
+ const c1 = existsSync14(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`);
6728
7376
  let c2;
6729
7377
  try {
6730
7378
  if (isMac) {
@@ -6775,10 +7423,10 @@ function vaultSyncChecks(input) {
6775
7423
  "Scheduler check failed \u2014 run vault-sync-install"
6776
7424
  );
6777
7425
  }
6778
- const logFile = join23(logDir, "wiki-push.log");
7426
+ const logFile = join26(logDir, "wiki-push.log");
6779
7427
  let c3;
6780
7428
  try {
6781
- const logContent = readFileSync9(logFile, "utf8");
7429
+ const logContent = readFileSync11(logFile, "utf8");
6782
7430
  const lines = logContent.trim().split("\n").filter(Boolean);
6783
7431
  if (lines.length === 0) {
6784
7432
  c3 = check(
@@ -6834,7 +7482,7 @@ function vaultSyncChecks(input) {
6834
7482
  }
6835
7483
  }
6836
7484
  } catch {
6837
- c3 = existsSync12(logDir) ? check(
7485
+ c3 = existsSync14(logDir) ? check(
6838
7486
  "warn",
6839
7487
  "vault_sync_last_push_age",
6840
7488
  "Vault sync last push recency",
@@ -6846,10 +7494,10 @@ function vaultSyncChecks(input) {
6846
7494
  `Log directory not found at ${logDir}`
6847
7495
  );
6848
7496
  }
6849
- const fetchLogFile = join23(logDir, "wiki-fetch.log");
7497
+ const fetchLogFile = join26(logDir, "wiki-fetch.log");
6850
7498
  let cFetch;
6851
7499
  try {
6852
- const logContent = readFileSync9(fetchLogFile, "utf8");
7500
+ const logContent = readFileSync11(fetchLogFile, "utf8");
6853
7501
  const lines = logContent.trim().split("\n").filter(Boolean);
6854
7502
  if (lines.length === 0) {
6855
7503
  cFetch = check(
@@ -6893,7 +7541,7 @@ function vaultSyncChecks(input) {
6893
7541
  }
6894
7542
  let c4;
6895
7543
  try {
6896
- if (!existsSync12(filterPath)) {
7544
+ if (!existsSync14(filterPath)) {
6897
7545
  c4 = check(
6898
7546
  "error",
6899
7547
  "vault_sync_filter_present",
@@ -6901,7 +7549,7 @@ function vaultSyncChecks(input) {
6901
7549
  `Filter file not found at ${filterPath}`
6902
7550
  );
6903
7551
  } else {
6904
- const content = readFileSync9(filterPath, "utf8");
7552
+ const content = readFileSync11(filterPath, "utf8");
6905
7553
  const requiredExcludes = [
6906
7554
  "remotely-save/data.json",
6907
7555
  ".skillwiki/sync.lock",
@@ -6944,7 +7592,7 @@ function vaultSyncChecks(input) {
6944
7592
  );
6945
7593
  } else {
6946
7594
  try {
6947
- if (!existsSync12(snapshotPath)) {
7595
+ if (!existsSync14(snapshotPath)) {
6948
7596
  c5 = check(
6949
7597
  "error",
6950
7598
  "vault_sync_snapshot_guard",
@@ -6952,7 +7600,7 @@ function vaultSyncChecks(input) {
6952
7600
  `Snapshot script not found at ${snapshotPath}`
6953
7601
  );
6954
7602
  } else {
6955
- const content = readFileSync9(snapshotPath, "utf8");
7603
+ const content = readFileSync11(snapshotPath, "utf8");
6956
7604
  if (!content.includes("--max-delete")) {
6957
7605
  c5 = check(
6958
7606
  "error",
@@ -6990,15 +7638,15 @@ function findSkillMd(dir) {
6990
7638
  }
6991
7639
  for (const entry of entries) {
6992
7640
  if (entry.isFile() && entry.name === "SKILL.md") {
6993
- results.push(join23(dir, entry.name));
7641
+ results.push(join26(dir, entry.name));
6994
7642
  } else if (entry.isDirectory()) {
6995
- results.push(...findSkillMd(join23(dir, entry.name)));
7643
+ results.push(...findSkillMd(join26(dir, entry.name)));
6996
7644
  }
6997
7645
  }
6998
7646
  return results;
6999
7647
  }
7000
7648
  function findInstalledSkillMd(dir) {
7001
- const directSkills = findSkillNames(dir).map((name) => join23(dir, name, "SKILL.md"));
7649
+ const directSkills = findSkillNames(dir).map((name) => join26(dir, name, "SKILL.md"));
7002
7650
  return directSkills.length > 0 ? directSkills : findSkillMd(dir);
7003
7651
  }
7004
7652
  function findSkillNames(dir) {
@@ -7010,7 +7658,7 @@ function findSkillNames(dir) {
7010
7658
  return results;
7011
7659
  }
7012
7660
  for (const entry of entries) {
7013
- if (entry.isDirectory() && existsSync12(join23(dir, entry.name, "SKILL.md"))) {
7661
+ if (entry.isDirectory() && existsSync14(join26(dir, entry.name, "SKILL.md"))) {
7014
7662
  results.push(entry.name);
7015
7663
  }
7016
7664
  }
@@ -7054,7 +7702,7 @@ async function vaultMetrics(resolvedPath) {
7054
7702
  }
7055
7703
  let logLines = 0;
7056
7704
  try {
7057
- logLines = readFileSync9(join23(resolvedPath, "log.md"), "utf8").split("\n").length;
7705
+ logLines = readFileSync11(join26(resolvedPath, "log.md"), "utf8").split("\n").length;
7058
7706
  } catch {
7059
7707
  }
7060
7708
  return [
@@ -7162,7 +7810,7 @@ async function runDoctor(input) {
7162
7810
  }
7163
7811
 
7164
7812
  // src/utils/package-info.ts
7165
- import { readFileSync as readFileSync10 } from "fs";
7813
+ import { readFileSync as readFileSync12 } from "fs";
7166
7814
  function packageJsonCandidateUrls(baseUrl = import.meta.url) {
7167
7815
  return [
7168
7816
  new URL("../package.json", baseUrl),
@@ -7172,7 +7820,7 @@ function packageJsonCandidateUrls(baseUrl = import.meta.url) {
7172
7820
  function readCliPackageJson(baseUrl = import.meta.url) {
7173
7821
  for (const url of packageJsonCandidateUrls(baseUrl)) {
7174
7822
  try {
7175
- const pkg = JSON.parse(readFileSync10(url, "utf8"));
7823
+ const pkg = JSON.parse(readFileSync12(url, "utf8"));
7176
7824
  if (typeof pkg.version === "string") {
7177
7825
  return { ...pkg, version: pkg.version };
7178
7826
  }
@@ -7183,8 +7831,8 @@ function readCliPackageJson(baseUrl = import.meta.url) {
7183
7831
  }
7184
7832
 
7185
7833
  // src/commands/project-index.ts
7186
- import { readdir as readdir4, readFile as readFile16, writeFile as writeFile6, mkdir as mkdir5 } from "fs/promises";
7187
- import { join as join24, dirname as dirname7, basename as basename2 } from "path";
7834
+ import { readdir as readdir4, readFile as readFile19, writeFile as writeFile5, mkdir as mkdir5 } from "fs/promises";
7835
+ import { join as join27, dirname as dirname8, basename as basename2 } from "path";
7188
7836
  var LAYER2_DIRS = ["entities", "concepts", "comparisons", "queries", "meta"];
7189
7837
  var PROJECT_LOCAL_DIRS = ["requirements", "work", "architecture", "history"];
7190
7838
  async function scanMarkdownTree(rootAbs, rootRel) {
@@ -7196,7 +7844,7 @@ async function scanMarkdownTree(rootAbs, rootRel) {
7196
7844
  return found;
7197
7845
  }
7198
7846
  for (const entry of entries) {
7199
- const abs = join24(rootAbs, entry.name);
7847
+ const abs = join27(rootAbs, entry.name);
7200
7848
  const rel = `${rootRel}/${entry.name}`;
7201
7849
  if (entry.isDirectory()) {
7202
7850
  found.push(...await scanMarkdownTree(abs, rel));
@@ -7226,7 +7874,7 @@ function projectLocalType(slug, page, data) {
7226
7874
  }
7227
7875
  async function runProjectIndex(input) {
7228
7876
  const slug = input.slug;
7229
- const projectDir = join24(input.vault, "projects", slug);
7877
+ const projectDir = join27(input.vault, "projects", slug);
7230
7878
  try {
7231
7879
  await readdir4(projectDir);
7232
7880
  } catch {
@@ -7237,15 +7885,15 @@ async function runProjectIndex(input) {
7237
7885
  }
7238
7886
  const wikilinkPattern = `[[${slug}]]`;
7239
7887
  const entries = [];
7240
- const compoundDir = join24(input.vault, "projects", slug, "compound");
7888
+ const compoundDir = join27(input.vault, "projects", slug, "compound");
7241
7889
  try {
7242
7890
  const compoundFiles = await readdir4(compoundDir, { withFileTypes: true });
7243
7891
  for (const entry of compoundFiles) {
7244
7892
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
7245
- const filePath = join24(compoundDir, entry.name);
7893
+ const filePath = join27(compoundDir, entry.name);
7246
7894
  let text;
7247
7895
  try {
7248
- text = await readFile16(filePath, "utf8");
7896
+ text = await readFile19(filePath, "utf8");
7249
7897
  } catch {
7250
7898
  continue;
7251
7899
  }
@@ -7262,16 +7910,16 @@ async function runProjectIndex(input) {
7262
7910
  for (const dir of LAYER2_DIRS) {
7263
7911
  let files;
7264
7912
  try {
7265
- files = await readdir4(join24(input.vault, dir), { withFileTypes: true });
7913
+ files = await readdir4(join27(input.vault, dir), { withFileTypes: true });
7266
7914
  } catch {
7267
7915
  continue;
7268
7916
  }
7269
7917
  for (const entry of files) {
7270
7918
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
7271
- const filePath = join24(input.vault, dir, entry.name);
7919
+ const filePath = join27(input.vault, dir, entry.name);
7272
7920
  let text;
7273
7921
  try {
7274
- text = await readFile16(filePath, "utf8");
7922
+ text = await readFile19(filePath, "utf8");
7275
7923
  } catch {
7276
7924
  continue;
7277
7925
  }
@@ -7287,14 +7935,14 @@ async function runProjectIndex(input) {
7287
7935
  }
7288
7936
  }
7289
7937
  for (const dir of PROJECT_LOCAL_DIRS) {
7290
- const rootAbs = join24(projectDir, dir);
7938
+ const rootAbs = join27(projectDir, dir);
7291
7939
  const rootRel = `projects/${slug}/${dir}`;
7292
7940
  const pages = await scanMarkdownTree(rootAbs, rootRel);
7293
7941
  for (const page of pages) {
7294
- const filePath = join24(input.vault, page);
7942
+ const filePath = join27(input.vault, page);
7295
7943
  let text;
7296
7944
  try {
7297
- text = await readFile16(filePath, "utf8");
7945
+ text = await readFile19(filePath, "utf8");
7298
7946
  } catch {
7299
7947
  continue;
7300
7948
  }
@@ -7313,11 +7961,11 @@ async function runProjectIndex(input) {
7313
7961
  const tb = typeOrder[b.type] ?? 99;
7314
7962
  return ta !== tb ? ta - tb : a.title.localeCompare(b.title);
7315
7963
  });
7316
- const indexPath = join24(projectDir, "knowledge.md");
7964
+ const indexPath = join27(projectDir, "knowledge.md");
7317
7965
  let existing = false;
7318
7966
  let stale = false;
7319
7967
  try {
7320
- const existingText = await readFile16(indexPath, "utf8");
7968
+ const existingText = await readFile19(indexPath, "utf8");
7321
7969
  existing = true;
7322
7970
  const existingEntries = existingText.split("\n").filter((l) => l.startsWith("- [["));
7323
7971
  const existingPages = new Set(existingEntries.map((l) => {
@@ -7357,8 +8005,8 @@ Autogenerated by \`skillwiki project-index\` on ${today}.
7357
8005
  }
7358
8006
  if (input.apply) {
7359
8007
  try {
7360
- await mkdir5(dirname7(indexPath), { recursive: true });
7361
- await writeFile6(indexPath, body, "utf8");
8008
+ await mkdir5(dirname8(indexPath), { recursive: true });
8009
+ await writeFile5(indexPath, body, "utf8");
7362
8010
  } catch (e) {
7363
8011
  return {
7364
8012
  exitCode: ExitCode.WRITE_FAILED,
@@ -7386,10 +8034,10 @@ ${entries.map((e) => ` ${e.type}: [[${e.page.replace(/\.md$/, "")}]] \u2014 ${e
7386
8034
  }
7387
8035
 
7388
8036
  // src/commands/observe.ts
7389
- import { mkdir as mkdir6, writeFile as writeFile7 } from "fs/promises";
7390
- import { existsSync as existsSync13, statSync as statSync2 } from "fs";
7391
- import { join as join25 } from "path";
7392
- import { createHash as createHash5 } from "crypto";
8037
+ import { mkdir as mkdir6, writeFile as writeFile6 } from "fs/promises";
8038
+ import { existsSync as existsSync15, statSync as statSync3 } from "fs";
8039
+ import { join as join28 } from "path";
8040
+ import { createHash as createHash7 } from "crypto";
7393
8041
  var ALLOWED_KINDS = /* @__PURE__ */ new Set(["note", "bug", "task", "idea", "session-log"]);
7394
8042
  function slugify(text) {
7395
8043
  const words = text.trim().split(/\s+/).slice(0, 6).join("-").toLowerCase().replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "");
@@ -7411,13 +8059,13 @@ async function runObserve(input) {
7411
8059
  result: err("SCHEME_REJECTED", { message: "Text must not be empty" })
7412
8060
  };
7413
8061
  }
7414
- if (!existsSync13(input.vault) || !statSync2(input.vault).isDirectory()) {
8062
+ if (!existsSync15(input.vault) || !statSync3(input.vault).isDirectory()) {
7415
8063
  return {
7416
8064
  exitCode: ExitCode.VAULT_PATH_INVALID,
7417
8065
  result: err("VAULT_PATH_INVALID", { path: input.vault })
7418
8066
  };
7419
8067
  }
7420
- const transcriptsDir = join25(input.vault, "raw", "transcripts");
8068
+ const transcriptsDir = join28(input.vault, "raw", "transcripts");
7421
8069
  try {
7422
8070
  await mkdir6(transcriptsDir, { recursive: true });
7423
8071
  } catch {
@@ -7429,11 +8077,11 @@ async function runObserve(input) {
7429
8077
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
7430
8078
  const slug = slugify(input.text);
7431
8079
  const fileName = `${today}-observation-${slug}.md`;
7432
- const filePath = join25(transcriptsDir, fileName);
8080
+ const filePath = join28(transcriptsDir, fileName);
7433
8081
  const body = `
7434
8082
  ${input.text.trim()}
7435
8083
  `;
7436
- const sha256 = createHash5("sha256").update(Buffer.from(body, "utf8")).digest("hex");
8084
+ const sha256 = createHash7("sha256").update(Buffer.from(body, "utf8")).digest("hex");
7437
8085
  const frontmatterLines = [
7438
8086
  "---",
7439
8087
  "source_url:",
@@ -7447,7 +8095,7 @@ ${input.text.trim()}
7447
8095
  frontmatterLines.push("---");
7448
8096
  const content = frontmatterLines.join("\n") + body;
7449
8097
  try {
7450
- await writeFile7(filePath, content, "utf8");
8098
+ await writeFile6(filePath, content, "utf8");
7451
8099
  } catch (e) {
7452
8100
  return {
7453
8101
  exitCode: ExitCode.WRITE_FAILED,
@@ -7469,9 +8117,9 @@ ${input.text.trim()}
7469
8117
  }
7470
8118
 
7471
8119
  // src/commands/memory.ts
7472
- import { createHash as createHash6 } from "crypto";
7473
- import { mkdir as mkdir7, readFile as readFile17, readdir as readdir5, stat as stat5, writeFile as writeFile8 } from "fs/promises";
7474
- import { basename as basename3, extname, join as join26, relative as relative4, sep as sep4 } from "path";
8120
+ import { createHash as createHash8 } from "crypto";
8121
+ import { mkdir as mkdir7, readFile as readFile20, readdir as readdir5, stat as stat6, writeFile as writeFile7 } from "fs/promises";
8122
+ import { basename as basename3, extname, join as join29, relative as relative5, sep as sep5 } from "path";
7475
8123
  async function runMemoryTopics(input) {
7476
8124
  const scan = await scanVault(input.vault);
7477
8125
  if (!scan.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scan };
@@ -7535,9 +8183,9 @@ async function runMemoryIndex(input) {
7535
8183
  }
7536
8184
  const generatedAt = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
7537
8185
  const relCachePath = memoryCacheRelPath(input.project);
7538
- const absCachePath = join26(input.vault, relCachePath);
7539
- await mkdir7(join26(input.vault, ".skillwiki", "memory", input.project), { recursive: true });
7540
- await writeFile8(absCachePath, `${JSON.stringify({
8186
+ const absCachePath = join29(input.vault, relCachePath);
8187
+ await mkdir7(join29(input.vault, ".skillwiki", "memory", input.project), { recursive: true });
8188
+ await writeFile7(absCachePath, `${JSON.stringify({
7541
8189
  generated_at: generatedAt,
7542
8190
  project: input.project,
7543
8191
  topics: state.topics,
@@ -7728,7 +8376,7 @@ async function buildMemoryIndexState(pages, project) {
7728
8376
  }
7729
8377
  async function checkMemoryIndex(vault, project, current) {
7730
8378
  const relCachePath = memoryCacheRelPath(project);
7731
- const cacheText = await readIfExists2(join26(vault, relCachePath));
8379
+ const cacheText = await readIfExists2(join29(vault, relCachePath));
7732
8380
  if (!cacheText) {
7733
8381
  return {
7734
8382
  ok: true,
@@ -7845,7 +8493,7 @@ async function readMemoryPage(page, project, warnings) {
7845
8493
  title,
7846
8494
  summary: summarize(body),
7847
8495
  updated,
7848
- hash: createHash6("sha256").update(Buffer.from(body, "utf8")).digest("hex"),
8496
+ hash: createHash8("sha256").update(Buffer.from(body, "utf8")).digest("hex"),
7849
8497
  topics,
7850
8498
  project,
7851
8499
  ...stringField(fm.data.memory_kind) ? { memory_kind: stringField(fm.data.memory_kind) } : {},
@@ -8121,13 +8769,13 @@ function renderMemoryIndexStatusHint(status) {
8121
8769
  }
8122
8770
  async function readIfExists2(path) {
8123
8771
  try {
8124
- return await readFile17(path, "utf8");
8772
+ return await readFile20(path, "utf8");
8125
8773
  } catch {
8126
8774
  return "";
8127
8775
  }
8128
8776
  }
8129
8777
  async function collectImportFiles(source) {
8130
- const st = await stat5(source);
8778
+ const st = await stat6(source);
8131
8779
  if (st.isFile()) return isImportCandidate(source) ? [source] : [];
8132
8780
  const files = [];
8133
8781
  await walkImportFiles(source, files);
@@ -8137,7 +8785,7 @@ async function walkImportFiles(dir, out) {
8137
8785
  const entries = await readdir5(dir, { withFileTypes: true });
8138
8786
  for (const entry of entries) {
8139
8787
  if (entry.name === ".git" || entry.name === "node_modules") continue;
8140
- const path = join26(dir, entry.name);
8788
+ const path = join29(dir, entry.name);
8141
8789
  if (entry.isDirectory()) {
8142
8790
  await walkImportFiles(path, out);
8143
8791
  } else if (entry.isFile() && isImportCandidate(path)) {
@@ -8150,9 +8798,9 @@ function isImportCandidate(path) {
8150
8798
  return ext === ".md" || ext === ".txt";
8151
8799
  }
8152
8800
  async function buildImportEntry(file, sourceRoot, project, today, maxBytes) {
8153
- const st = await stat5(file);
8801
+ const st = await stat6(file);
8154
8802
  const sourceKind = classifyImportSource(file);
8155
- const hash = createHash6("sha256").update(await readFile17(file)).digest("hex");
8803
+ const hash = createHash8("sha256").update(await readFile20(file)).digest("hex");
8156
8804
  const baseEntry = {
8157
8805
  source_path: file,
8158
8806
  source_kind: sourceKind,
@@ -8175,7 +8823,7 @@ async function buildImportEntry(file, sourceRoot, project, today, maxBytes) {
8175
8823
  reason: "policy_source_not_imported"
8176
8824
  };
8177
8825
  }
8178
- const text = await readFile17(file, "utf8");
8826
+ const text = await readFile20(file, "utf8");
8179
8827
  const extracted = extractImportText(text, sourceKind);
8180
8828
  if (!extracted) {
8181
8829
  return {
@@ -8187,7 +8835,7 @@ async function buildImportEntry(file, sourceRoot, project, today, maxBytes) {
8187
8835
  const redacted = redactSensitiveContent(extracted, { file });
8188
8836
  const privacy = redacted.findings.length > 0 ? "sensitive" : "local";
8189
8837
  const sourceSlug = slugify2(basename3(file, extname(file)));
8190
- const relSource = relative4(sourceRoot, file).split(sep4).join("/");
8838
+ const relSource = relative5(sourceRoot, file).split(sep5).join("/");
8191
8839
  const entry = {
8192
8840
  ...baseEntry,
8193
8841
  status: "ready",
@@ -8204,9 +8852,9 @@ async function writeImportCapture(vault, entry, today) {
8204
8852
  const content = hiddenString(entry, "__content");
8205
8853
  const project = hiddenString(entry, "__project");
8206
8854
  const relPath = await availableImportPath(vault, entry.proposed_path);
8207
- const absPath = join26(vault, relPath);
8208
- await mkdir7(join26(vault, "raw", "transcripts"), { recursive: true });
8209
- await writeFile8(absPath, renderImportCapture(entry, content, project, today), "utf8");
8855
+ const absPath = join29(vault, relPath);
8856
+ await mkdir7(join29(vault, "raw", "transcripts"), { recursive: true });
8857
+ await writeFile7(absPath, renderImportCapture(entry, content, project, today), "utf8");
8210
8858
  const validation = await runValidate({ file: absPath });
8211
8859
  return {
8212
8860
  relPath,
@@ -8221,7 +8869,7 @@ async function availableImportPath(vault, proposed) {
8221
8869
  const stem = proposed.slice(0, -ext.length);
8222
8870
  let candidate = proposed;
8223
8871
  let i = 2;
8224
- while (await readIfExists2(join26(vault, candidate))) {
8872
+ while (await readIfExists2(join29(vault, candidate))) {
8225
8873
  candidate = `${stem}-${i}${ext}`;
8226
8874
  i++;
8227
8875
  }
@@ -8259,7 +8907,7 @@ function hiddenString(entry, key) {
8259
8907
  return entry[key] ?? "";
8260
8908
  }
8261
8909
  function classifyImportSource(file) {
8262
- const rel = file.split(sep4).join("/");
8910
+ const rel = file.split(sep5).join("/");
8263
8911
  const name = basename3(file);
8264
8912
  if (rel.includes("/.codex/memories/")) return "codex-memory";
8265
8913
  if (rel.includes("/.codex/rules/")) return "codex-rule";
@@ -8393,10 +9041,10 @@ function memoryCacheRelPath(project) {
8393
9041
  }
8394
9042
  async function readMemoryCache(vault, project) {
8395
9043
  if (project) {
8396
- const projectCache = await readIfExists2(join26(vault, memoryCacheRelPath(project)));
9044
+ const projectCache = await readIfExists2(join29(vault, memoryCacheRelPath(project)));
8397
9045
  if (projectCache) return projectCache;
8398
9046
  }
8399
- return readIfExists2(join26(vault, ".skillwiki", "memory-topics.json"));
9047
+ return readIfExists2(join29(vault, ".skillwiki", "memory-topics.json"));
8400
9048
  }
8401
9049
  function dedupePages(pages) {
8402
9050
  const seen = /* @__PURE__ */ new Set();
@@ -8482,8 +9130,8 @@ function slugify2(value) {
8482
9130
  }
8483
9131
 
8484
9132
  // src/commands/query.ts
8485
- import { readFile as readFile18, stat as stat6 } from "fs/promises";
8486
- import { join as join27 } from "path";
9133
+ import { readFile as readFile21, stat as stat7 } from "fs/promises";
9134
+ import { join as join30 } from "path";
8487
9135
  var W_KEYWORD = 2;
8488
9136
  var W_SOURCE_OVERLAP = 4;
8489
9137
  var W_WIKILINK = 3;
@@ -8604,10 +9252,10 @@ function computeKeywordScore(terms, title, tags, body) {
8604
9252
  return score;
8605
9253
  }
8606
9254
  async function loadOrBuildGraph(vault) {
8607
- const graphPath = join27(vault, ".skillwiki", "graph.json");
9255
+ const graphPath = join30(vault, ".skillwiki", "graph.json");
8608
9256
  let needsBuild = false;
8609
9257
  try {
8610
- const fileStat = await stat6(graphPath);
9258
+ const fileStat = await stat7(graphPath);
8611
9259
  const ageHours = (Date.now() - fileStat.mtimeMs) / (1e3 * 60 * 60);
8612
9260
  if (ageHours > 24) needsBuild = true;
8613
9261
  } catch {
@@ -8618,7 +9266,7 @@ async function loadOrBuildGraph(vault) {
8618
9266
  if (buildResult.exitCode !== 0) return null;
8619
9267
  }
8620
9268
  try {
8621
- const raw = await readFile18(graphPath, "utf8");
9269
+ const raw = await readFile21(graphPath, "utf8");
8622
9270
  return JSON.parse(raw);
8623
9271
  } catch {
8624
9272
  return null;
@@ -8633,27 +9281,27 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
8633
9281
  import { z as z2 } from "zod";
8634
9282
 
8635
9283
  // src/mcp/vault-resolve.ts
8636
- import { join as join28, resolve as resolve7 } from "path";
9284
+ import { join as join31, resolve as resolve8 } from "path";
8637
9285
 
8638
9286
  // src/mcp/allowlist.ts
8639
- import { resolve as resolve6, sep as sep5 } from "path";
8640
- import { realpathSync } from "fs";
9287
+ import { resolve as resolve7, sep as sep6 } from "path";
9288
+ import { realpathSync as realpathSync2 } from "fs";
8641
9289
  function parseVaultAllowlist(envValue) {
8642
9290
  if (envValue === void 0 || envValue.trim() === "") return null;
8643
- return envValue.split(",").map((s) => s.trim()).filter((s) => s.length > 0).map((p) => resolve6(p));
9291
+ return envValue.split(",").map((s) => s.trim()).filter((s) => s.length > 0).map((p) => resolve7(p));
8644
9292
  }
8645
9293
  function vaultAllowedByList(vaultPath, allowlist) {
8646
9294
  if (!allowlist || allowlist.length === 0) return true;
8647
9295
  let canonical = vaultPath;
8648
9296
  try {
8649
- canonical = realpathSync(vaultPath);
9297
+ canonical = realpathSync2(vaultPath);
8650
9298
  } catch {
8651
- canonical = resolve6(vaultPath);
9299
+ canonical = resolve7(vaultPath);
8652
9300
  }
8653
- const resolved = resolve6(canonical);
9301
+ const resolved = resolve7(canonical);
8654
9302
  return allowlist.some((root) => {
8655
- const r = resolve6(root);
8656
- return resolved === r || resolved.startsWith(r + sep5);
9303
+ const r = resolve7(root);
9304
+ return resolved === r || resolved.startsWith(r + sep6);
8657
9305
  });
8658
9306
  }
8659
9307
  function getVaultAllowlistFromEnv() {
@@ -8666,7 +9314,7 @@ async function resolveMcpVault(input) {
8666
9314
  let vaultPath;
8667
9315
  let source = "resolved";
8668
9316
  if (input.vault !== void 0 && input.vault.length > 0) {
8669
- vaultPath = resolve7(input.vault);
9317
+ vaultPath = resolve8(input.vault);
8670
9318
  source = "flag";
8671
9319
  } else {
8672
9320
  const r = await resolveRuntimePath({
@@ -8678,7 +9326,7 @@ async function resolveMcpVault(input) {
8678
9326
  cwd: input.cwd ?? process.cwd()
8679
9327
  });
8680
9328
  if (!r.ok) return r;
8681
- vaultPath = resolve7(r.data.path);
9329
+ vaultPath = resolve8(r.data.path);
8682
9330
  source = r.data.source;
8683
9331
  }
8684
9332
  const scan = await scanVault(vaultPath);
@@ -8695,7 +9343,7 @@ async function resolveMcpVault(input) {
8695
9343
  return ok({ vault: vaultPath, source });
8696
9344
  }
8697
9345
  function defaultGraphOut(vault) {
8698
- return join28(vault, ".skillwiki", "graph.json");
9346
+ return join31(vault, ".skillwiki", "graph.json");
8699
9347
  }
8700
9348
 
8701
9349
  // src/mcp/result-format.ts
@@ -8710,9 +9358,9 @@ function formatToolResult(payload) {
8710
9358
  }
8711
9359
 
8712
9360
  // src/mcp/audit-log.ts
8713
- import { appendFileSync, mkdirSync as mkdirSync3 } from "fs";
9361
+ import { appendFileSync, mkdirSync as mkdirSync5 } from "fs";
8714
9362
  import { homedir } from "os";
8715
- import { join as join29 } from "path";
9363
+ import { join as join32 } from "path";
8716
9364
  function auditEnabled() {
8717
9365
  const v = process.env.SKILLWIKI_MCP_AUDIT;
8718
9366
  if (v === "0" || v === "false") return false;
@@ -8724,7 +9372,7 @@ function auditSink() {
8724
9372
  function auditFilePath() {
8725
9373
  const custom = process.env.SKILLWIKI_MCP_AUDIT_FILE;
8726
9374
  if (custom && custom.length > 0) return custom;
8727
- return join29(homedir(), ".skillwiki", "mcp-audit.jsonl");
9375
+ return join32(homedir(), ".skillwiki", "mcp-audit.jsonl");
8728
9376
  }
8729
9377
  function auditMcpToolCall(entry) {
8730
9378
  if (!auditEnabled()) return;
@@ -8734,7 +9382,7 @@ function auditMcpToolCall(entry) {
8734
9382
  return;
8735
9383
  }
8736
9384
  const path = auditFilePath();
8737
- mkdirSync3(join29(path, ".."), { recursive: true });
9385
+ mkdirSync5(join32(path, ".."), { recursive: true });
8738
9386
  appendFileSync(path, line, "utf8");
8739
9387
  }
8740
9388
  async function runMcpToolHandler(tool, input, fn) {
@@ -8954,8 +9602,8 @@ function registerMcpMutatingTools(server) {
8954
9602
  }
8955
9603
 
8956
9604
  // src/mcp/resources.ts
8957
- import { readFile as readFile20 } from "fs/promises";
8958
- import { join as join31 } from "path";
9605
+ import { readFile as readFile23 } from "fs/promises";
9606
+ import { join as join34 } from "path";
8959
9607
  import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
8960
9608
 
8961
9609
  // src/mcp/lint-bucket.ts
@@ -9083,9 +9731,9 @@ async function fetchQueryPreview(input) {
9083
9731
  }
9084
9732
 
9085
9733
  // src/mcp/graph-html.ts
9086
- import { readFile as readFile19 } from "fs/promises";
9087
- import { join as join30 } from "path";
9088
- import { existsSync as existsSync14 } from "fs";
9734
+ import { readFile as readFile22 } from "fs/promises";
9735
+ import { join as join33 } from "path";
9736
+ import { existsSync as existsSync16 } from "fs";
9089
9737
  var TYPE_COLORS = {
9090
9738
  entities: "#e74c3c",
9091
9739
  concepts: "#27ae60",
@@ -9153,9 +9801,9 @@ ${nodeSvg}
9153
9801
  return { html, node_count: nodes.length, edge_count: edges.length, truncated };
9154
9802
  }
9155
9803
  async function fetchGraphHtmlReport(input) {
9156
- const graphPath = input.graphPath ?? join30(input.vault, ".skillwiki", "graph.json");
9804
+ const graphPath = input.graphPath ?? join33(input.vault, ".skillwiki", "graph.json");
9157
9805
  const maxNodes = Math.min(Math.max(10, input.maxNodes ?? 120), 500);
9158
- if (!existsSync14(graphPath)) {
9806
+ if (!existsSync16(graphPath)) {
9159
9807
  return {
9160
9808
  exitCode: ExitCode.FILE_NOT_FOUND,
9161
9809
  result: err("GRAPH_MISSING", { path: graphPath, hint: "Run skillwiki.graph_build first." })
@@ -9163,7 +9811,7 @@ async function fetchGraphHtmlReport(input) {
9163
9811
  }
9164
9812
  let raw;
9165
9813
  try {
9166
- raw = await readFile19(graphPath, "utf8");
9814
+ raw = await readFile22(graphPath, "utf8");
9167
9815
  } catch (e) {
9168
9816
  return {
9169
9817
  exitCode: ExitCode.FILE_NOT_FOUND,
@@ -9227,7 +9875,7 @@ async function fetchStaleSummary(input) {
9227
9875
 
9228
9876
  // src/mcp/resources.ts
9229
9877
  async function readVaultFile(vault, rel) {
9230
- return readFile20(join31(vault, rel), "utf8");
9878
+ return readFile23(join34(vault, rel), "utf8");
9231
9879
  }
9232
9880
  async function tailLines(text, lines) {
9233
9881
  const parts = text.split(/\r?\n/);
@@ -9313,9 +9961,9 @@ function registerMcpResources(server) {
9313
9961
  if (!v.ok) {
9314
9962
  return { contents: [{ uri: uri.href, mimeType: "text/plain", text: JSON.stringify(v) }] };
9315
9963
  }
9316
- const path = join31(v.data.vault, ".skillwiki", "graph.json");
9964
+ const path = join34(v.data.vault, ".skillwiki", "graph.json");
9317
9965
  try {
9318
- const raw = await readFile20(path, "utf8");
9966
+ const raw = await readFile23(path, "utf8");
9319
9967
  const graph = JSON.parse(raw);
9320
9968
  const adjacency = graph.adjacency ?? {};
9321
9969
  const nodes = Object.keys(adjacency);
@@ -9628,13 +10276,29 @@ export {
9628
10276
  ExitCode,
9629
10277
  ok,
9630
10278
  err,
9631
- TypedKnowledgeSchema,
10279
+ RawSourceSchema,
9632
10280
  MetaSchema,
9633
- detectSchema,
9634
10281
  isBlockedHost,
9635
10282
  splitFrontmatter,
9636
10283
  extractFrontmatter,
9637
10284
  scanSensitiveContent,
10285
+ redactSensitiveContent,
10286
+ readLastOp,
10287
+ appendLastOp,
10288
+ clearLastOp,
10289
+ atomicWriteText,
10290
+ runLogAppend,
10291
+ renderIndexUpsert,
10292
+ upsertIndexEntry,
10293
+ getSessionId,
10294
+ getCliSessionId,
10295
+ readLock,
10296
+ acquireLock,
10297
+ releaseLock,
10298
+ acquireOwnedSyncLock,
10299
+ releaseOwnedSyncLock,
10300
+ assertTargetInsideVault,
10301
+ prepareTypedPage,
9638
10302
  runValidate,
9639
10303
  scanVault,
9640
10304
  readPage,
@@ -9650,9 +10314,8 @@ export {
9650
10314
  runAudit,
9651
10315
  findPlugin,
9652
10316
  extractTaxonomy,
9653
- readLastOp,
9654
- appendLastOp,
9655
- clearLastOp,
10317
+ taxonomyCommentForPage,
10318
+ reconcileTaxonomyDocument,
9656
10319
  runLinks,
9657
10320
  runTagAudit,
9658
10321
  runIndexCheck,