skillwiki 0.10.98 → 0.10.102

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -3,8 +3,12 @@ import {
3
3
  CAPTURE_HYGIENE_CONTRACT,
4
4
  DEFAULT_CAPTURE_BUDGET,
5
5
  DEFAULT_DIRTY_VOLUME_THRESHOLD,
6
+ DEFAULT_MCP_URL,
6
7
  DEFAULT_NO_DECISION_STREAK,
7
8
  GateError,
9
+ MCP_HOST_ID_ENV,
10
+ MCP_TOKEN_ENV,
11
+ MCP_URL_ENV,
8
12
  PUSH_NOT_IN_PROFILE,
9
13
  SATELLITE_STALE_MS,
10
14
  buildVectorIndex,
@@ -19,10 +23,13 @@ import {
19
23
  isFailedRunStatus,
20
24
  measureDirtyVolume,
21
25
  memoryAuthorityTiersRank,
26
+ mergeMcpAuthIntoEnv,
27
+ nonEmptyEnvValue,
22
28
  pruneVectorIndex,
23
29
  readCliPackageJson,
24
30
  readSatelliteLatestRunFromText,
25
31
  readVaultSyncConfig,
32
+ redactMcpSecret,
26
33
  reindexPageInVectorIndex,
27
34
  renderIndexUpsert,
28
35
  renderProjectIndex,
@@ -55,7 +62,7 @@ import {
55
62
  snapshotterHealthChecks,
56
63
  upsertIndexEntry,
57
64
  vectorIndexStatus
58
- } from "./chunk-EEO2YDPX.js";
65
+ } from "./chunk-PIXBNB74.js";
59
66
  import {
60
67
  normalizeDistTag,
61
68
  readCache,
@@ -160,7 +167,7 @@ import {
160
167
  supersedeStaleReviewRequiredJournals,
161
168
  taxonomyCommentForPage,
162
169
  writeDotenv
163
- } from "./chunk-MRYWP5HY.js";
170
+ } from "./chunk-7UK67FAZ.js";
164
171
  import {
165
172
  assertTargetInsideVault,
166
173
  atomicWriteText,
@@ -214,7 +221,7 @@ import {
214
221
  } from "./chunk-QM64LUEY.js";
215
222
 
216
223
  // src/cli.ts
217
- import { join as join44 } from "path";
224
+ import { join as join45 } from "path";
218
225
  import { Command } from "commander";
219
226
 
220
227
  // src/utils/output.ts
@@ -3576,13 +3583,334 @@ async function runHealth(input) {
3576
3583
  return { exitCode, result: ok(report) };
3577
3584
  }
3578
3585
 
3586
+ // src/commands/connect.ts
3587
+ import { chmod, mkdir as mkdir5, readFile as readFile6, stat as stat3 } from "fs/promises";
3588
+ import { join as join18 } from "path";
3589
+
3590
+ // src/utils/mcp-token-map.ts
3591
+ import { createHash as createHash2, randomBytes } from "crypto";
3592
+ import yaml from "js-yaml";
3593
+ var HOST_ID_RE = /^[a-z][a-z0-9-]{1,62}$/;
3594
+ function writerIdFromValue(value) {
3595
+ if (typeof value === "string" && value.length > 0) return value;
3596
+ if (value && typeof value === "object" && !Array.isArray(value)) {
3597
+ const rec = value;
3598
+ if (typeof rec.writer_id === "string" && rec.writer_id.length > 0) return rec.writer_id;
3599
+ if (typeof rec.host_id === "string" && rec.host_id.length > 0) return rec.host_id;
3600
+ }
3601
+ return void 0;
3602
+ }
3603
+ function parseAllowedVaultIds(raw) {
3604
+ if (!raw || raw.length === 0) return { vaults: [] };
3605
+ const vaults = [];
3606
+ const seen = /* @__PURE__ */ new Set();
3607
+ for (const item of raw) {
3608
+ const id = item.trim();
3609
+ if (!id) continue;
3610
+ if (id.includes("*") || id.includes("/") || !HOST_ID_RE.test(id)) {
3611
+ return { error: "INVALID_ALLOWED_VAULTS" };
3612
+ }
3613
+ if (seen.has(id)) continue;
3614
+ seen.add(id);
3615
+ vaults.push(id);
3616
+ }
3617
+ return { vaults };
3618
+ }
3619
+ function parseMcpTokenMap(yamlText) {
3620
+ const parsed = yaml.load(yamlText);
3621
+ const map = /* @__PURE__ */ new Map();
3622
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
3623
+ return map;
3624
+ }
3625
+ const records = parsed;
3626
+ const nested = records.tokens;
3627
+ const source = nested && typeof nested === "object" && !Array.isArray(nested) ? nested : records;
3628
+ for (const [hash, value] of Object.entries(source)) {
3629
+ if (hash === "tokens") continue;
3630
+ const writerId = writerIdFromValue(value);
3631
+ if (!writerId) continue;
3632
+ const normalized = hash.trim().toLowerCase();
3633
+ if (!/^[0-9a-f]{64}$/.test(normalized)) continue;
3634
+ map.set(normalized, writerId);
3635
+ }
3636
+ return map;
3637
+ }
3638
+ function generateHostBearer(rng) {
3639
+ const bytes = (rng ?? (() => randomBytes(32)))();
3640
+ const raw = bytes.toString("base64url");
3641
+ const hashHex = createHash2("sha256").update(raw, "utf8").digest("hex");
3642
+ return { raw, hashHex };
3643
+ }
3644
+ function formatHostRow(hashHex, hostId, allowedVaults) {
3645
+ if (allowedVaults.length === 0) {
3646
+ return `${hashHex}: ${hostId}
3647
+ `;
3648
+ }
3649
+ return `${hashHex}:
3650
+ writer_id: ${hostId}
3651
+ allowed_vaults: [${allowedVaults.join(", ")}]
3652
+ `;
3653
+ }
3654
+ function appendHostHash(yamlText, hashHex, hostId, allowedVaults) {
3655
+ if (!HOST_ID_RE.test(hostId)) {
3656
+ return { error: "INVALID_HOST_ID" };
3657
+ }
3658
+ const granted = parseAllowedVaultIds(allowedVaults);
3659
+ if ("error" in granted) return granted;
3660
+ const map = parseMcpTokenMap(yamlText);
3661
+ const normalizedHash = hashHex.trim().toLowerCase();
3662
+ if ([...map.values()].includes(hostId)) {
3663
+ return { error: "DUPLICATE_HOST_ID" };
3664
+ }
3665
+ if (map.has(normalizedHash)) {
3666
+ return { error: "DUPLICATE_HASH" };
3667
+ }
3668
+ return { yaml: yamlText.replace(/\s*$/, "") + `
3669
+ ${formatHostRow(normalizedHash, hostId, granted.vaults)}` };
3670
+ }
3671
+
3672
+ // src/commands/connect.ts
3673
+ var CONNECT_DESCRIPTION = "HTTP MCP leaf connect: ingest a chat-attached env file into ~/.skillwiki/.env (mode 0600). Never runs skillwiki init. Cloud Drive / \u4E91\u76D8 is unsupported.";
3674
+ var RESERVED_HOST_IDS = ["msi", "doubao-linux-agent", "macos-dev"];
3675
+ var DEST_MODE = 384;
3676
+ function isUnsupportedSecretPath(filePath) {
3677
+ const normalized = filePath.replace(/\\/g, "/");
3678
+ return /(?:^|\/)(?:Google Drive|OneDrive|iCloud Drive|CloudStorage|云盘|百度网盘|坚果云|Dropbox)(?:\/|$)/i.test(normalized) || /clouddrive|cloud-disk/i.test(normalized);
3679
+ }
3680
+ function fail(code, error, detail) {
3681
+ return { exitCode: code, result: err(error, detail) };
3682
+ }
3683
+ async function defaultReadStdin() {
3684
+ const chunks = [];
3685
+ for await (const chunk of process.stdin) {
3686
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
3687
+ }
3688
+ return Buffer.concat(chunks).toString("utf8");
3689
+ }
3690
+ function looksLikeVaultInit(text) {
3691
+ return /^#\s*Vault Schema/im.test(text) || /^\s*---\s*$/m.test(text) && /taxonomy:/i.test(text);
3692
+ }
3693
+ function tokenPresence(token) {
3694
+ return token ? "TOKEN_SET" : "EMPTY";
3695
+ }
3696
+ function summaryHint(data) {
3697
+ return [
3698
+ data.token,
3699
+ `auth=${data.auth}`,
3700
+ `writer_id=${data.writer_id ?? "n/a"}`,
3701
+ `ok=${data.ok ?? "n/a"}`,
3702
+ `reconcile_ready=${data.reconcile_ready ?? "n/a"}`,
3703
+ data.dry_run ? "dry-run" : data.written ? "wrote ~/.skillwiki/.env 0600" : "not written"
3704
+ ].join(" ");
3705
+ }
3706
+ function redactConnectPayload(data, secret) {
3707
+ return {
3708
+ ...data,
3709
+ handshake: data.handshake ? redactMcpSecret(data.handshake, secret) : data.handshake,
3710
+ humanHint: redactMcpSecret(data.humanHint, secret)
3711
+ };
3712
+ }
3713
+ async function readWikiStatus(fetchFn, url, token) {
3714
+ const headers = {
3715
+ Authorization: `Bearer ${token}`,
3716
+ "Content-Type": "application/json",
3717
+ Accept: "application/json, text/event-stream"
3718
+ };
3719
+ try {
3720
+ const initRes = await fetchFn(url, {
3721
+ method: "POST",
3722
+ headers,
3723
+ body: JSON.stringify({
3724
+ jsonrpc: "2.0",
3725
+ id: 1,
3726
+ method: "initialize",
3727
+ params: {
3728
+ protocolVersion: "2025-11-25",
3729
+ capabilities: {},
3730
+ clientInfo: { name: "skillwiki-connect", version: "0" }
3731
+ }
3732
+ })
3733
+ });
3734
+ const session = initRes.headers.get("mcp-session-id");
3735
+ if (session) headers["mcp-session-id"] = session;
3736
+ const statusRes = await fetchFn(url, {
3737
+ method: "POST",
3738
+ headers,
3739
+ body: JSON.stringify({
3740
+ jsonrpc: "2.0",
3741
+ id: 2,
3742
+ method: "tools/call",
3743
+ params: { name: "wiki_status", arguments: {} }
3744
+ })
3745
+ });
3746
+ const raw = await statusRes.text();
3747
+ const parsed = JSON.parse(raw.startsWith("data:") ? raw.replace(/^data:\s*/m, "") : raw);
3748
+ const structured = parsed.result?.structuredContent;
3749
+ let body = structured;
3750
+ if (!body) {
3751
+ const text = parsed.result?.content?.find((c) => typeof c.text === "string")?.text;
3752
+ if (text) {
3753
+ try {
3754
+ body = JSON.parse(text);
3755
+ } catch {
3756
+ body = void 0;
3757
+ }
3758
+ }
3759
+ }
3760
+ if (!body) return { writer_id: null, ok: null, reconcile_ready: null };
3761
+ return {
3762
+ writer_id: typeof body.writer_id === "string" ? body.writer_id : null,
3763
+ ok: typeof body.ok === "boolean" ? body.ok : null,
3764
+ reconcile_ready: typeof body.reconcile_ready === "boolean" ? body.reconcile_ready : null
3765
+ };
3766
+ } catch {
3767
+ return { writer_id: null, ok: null, reconcile_ready: null };
3768
+ }
3769
+ }
3770
+ async function runConnect(input) {
3771
+ const dest = join18(input.home, ".skillwiki", ".env");
3772
+ const fromFile = input.fromFile?.trim();
3773
+ const fromStdin = Boolean(input.fromStdin);
3774
+ if (fromFile && fromStdin) {
3775
+ return fail(ExitCode.USAGE, "USAGE", { reason: "pass --from-file or --from-stdin, not both" });
3776
+ }
3777
+ if (!fromFile && !fromStdin) {
3778
+ return fail(ExitCode.USAGE, "USAGE", {
3779
+ reason: "pass --from-file <chat-attachment> or --from-stdin (Cloud Drive / \u4E91\u76D8 unsupported)"
3780
+ });
3781
+ }
3782
+ if (fromFile && isUnsupportedSecretPath(fromFile)) {
3783
+ return fail(ExitCode.PREFLIGHT_FAILED, "DRIVE_UNSUPPORTED", {
3784
+ reason: "Cloud Drive / \u4E91\u76D8 is not a secret path. Attach the env file in chat, then skillwiki connect --from-file <attachment>."
3785
+ });
3786
+ }
3787
+ let raw;
3788
+ try {
3789
+ raw = fromStdin ? await (input.readStdin ?? defaultReadStdin)() : await readFile6(fromFile, "utf8");
3790
+ } catch (e) {
3791
+ const code = e.code;
3792
+ if (code === "ENOENT" || code === "ENOTDIR") {
3793
+ return fail(ExitCode.FILE_NOT_FOUND, "FILE_NOT_FOUND", { path: fromFile });
3794
+ }
3795
+ if (code === "EISDIR") {
3796
+ return fail(ExitCode.PREFLIGHT_FAILED, "NOT_ENV_FILE", { reason: "source is a directory, not an env file" });
3797
+ }
3798
+ return fail(ExitCode.PREFLIGHT_FAILED, "READ_FAILED", { reason: "could not read env source" });
3799
+ }
3800
+ if (looksLikeVaultInit(raw)) {
3801
+ return fail(ExitCode.PREFLIGHT_FAILED, "REFUSE_INIT", {
3802
+ reason: "source looks like a vault init; skillwiki connect never runs skillwiki init"
3803
+ });
3804
+ }
3805
+ const parsed = parseDotenvText(raw);
3806
+ const incomingToken = nonEmptyEnvValue(parsed[MCP_TOKEN_ENV]);
3807
+ const incomingHost = nonEmptyEnvValue(parsed[MCP_HOST_ID_ENV]);
3808
+ const incomingUrl = nonEmptyEnvValue(parsed[MCP_URL_ENV]);
3809
+ if (!incomingToken) {
3810
+ return fail(ExitCode.INVALID_CONFIG_VALUE, "TOKEN_EMPTY", {
3811
+ reason: "SKILLWIKI_MCP_TOKEN missing or empty (value not shown)"
3812
+ });
3813
+ }
3814
+ if (!incomingHost) {
3815
+ return fail(ExitCode.INVALID_CONFIG_VALUE, "HOST_ID_REQUIRED", {
3816
+ reason: "SKILLWIKI_HOST_ID missing; each machine needs a unique host-id"
3817
+ });
3818
+ }
3819
+ if (!HOST_ID_RE.test(incomingHost)) {
3820
+ return fail(ExitCode.PREFLIGHT_FAILED, "INVALID_HOST_ID", { reason: "host-id must match [a-z][a-z0-9-]{1,62}" });
3821
+ }
3822
+ if (RESERVED_HOST_IDS.includes(incomingHost) && !input.force) {
3823
+ return fail(ExitCode.PREFLIGHT_FAILED, "RESERVED_HOST_ID", {
3824
+ reason: `refuse copying reserved host-id ${incomingHost}; issue a unique id on metal or pass --force`
3825
+ });
3826
+ }
3827
+ const existing = await parseDotenvFile(dest);
3828
+ const existingToken = nonEmptyEnvValue(existing[MCP_TOKEN_ENV]);
3829
+ if (existingToken && existingToken !== incomingToken && !input.force) {
3830
+ return fail(ExitCode.ENV_WRITE_CONFLICT, "ENV_WRITE_CONFLICT", {
3831
+ reason: "existing ~/.skillwiki/.env has a different token; pass --force to overwrite (value not shown)"
3832
+ });
3833
+ }
3834
+ const entries = {
3835
+ [MCP_TOKEN_ENV]: incomingToken,
3836
+ [MCP_HOST_ID_ENV]: incomingHost
3837
+ };
3838
+ if (incomingUrl) entries[MCP_URL_ENV] = incomingUrl;
3839
+ const dryRun = Boolean(input.dryRun);
3840
+ if (!dryRun) {
3841
+ await mkdir5(join18(input.home, ".skillwiki"), { recursive: true });
3842
+ let original;
3843
+ try {
3844
+ original = await readFile6(dest, "utf8");
3845
+ } catch {
3846
+ original = void 0;
3847
+ }
3848
+ await writeDotenv(dest, entries, original);
3849
+ await chmod(dest, DEST_MODE);
3850
+ try {
3851
+ const st = await stat3(dest);
3852
+ if ((st.mode & 511) !== DEST_MODE) await chmod(dest, DEST_MODE);
3853
+ } catch {
3854
+ }
3855
+ }
3856
+ let writerId = null;
3857
+ let statusOk = null;
3858
+ let reconcileReady = null;
3859
+ let handshake = null;
3860
+ let doctorExit = ExitCode.OK;
3861
+ const checkMcp = input.checkMcp ?? !dryRun;
3862
+ if (checkMcp) {
3863
+ const env = await mergeMcpAuthIntoEnv(input.home, input.env ?? {});
3864
+ if (!nonEmptyEnvValue(env[MCP_TOKEN_ENV])) env[MCP_TOKEN_ENV] = incomingToken;
3865
+ if (!nonEmptyEnvValue(env[MCP_HOST_ID_ENV])) env[MCP_HOST_ID_ENV] = incomingHost;
3866
+ if (incomingUrl && !nonEmptyEnvValue(env[MCP_URL_ENV])) env[MCP_URL_ENV] = incomingUrl;
3867
+ const doctor = await runDoctor({
3868
+ home: input.home,
3869
+ envValue: void 0,
3870
+ argv: ["node", "skillwiki", "connect"],
3871
+ currentVersion: input.currentVersion,
3872
+ checkMcp: true,
3873
+ env,
3874
+ mcpFetch: input.mcpFetch
3875
+ });
3876
+ doctorExit = doctor.exitCode;
3877
+ if (doctor.result.ok) {
3878
+ handshake = doctor.result.data.checks.find((c) => c.id === "mcp_handshake")?.detail ?? null;
3879
+ }
3880
+ const url = incomingUrl ?? DEFAULT_MCP_URL;
3881
+ const status = await readWikiStatus(input.mcpFetch ?? globalThis.fetch, url, incomingToken);
3882
+ writerId = status.writer_id;
3883
+ statusOk = status.ok;
3884
+ reconcileReady = status.reconcile_ready;
3885
+ }
3886
+ const payload = {
3887
+ dest,
3888
+ written: !dryRun,
3889
+ dry_run: dryRun,
3890
+ mode: "0600",
3891
+ token: tokenPresence(incomingToken),
3892
+ auth: incomingToken ? "present" : "not set",
3893
+ writer_id: writerId,
3894
+ ok: statusOk,
3895
+ reconcile_ready: reconcileReady,
3896
+ handshake,
3897
+ humanHint: ""
3898
+ };
3899
+ payload.humanHint = summaryHint(payload);
3900
+ const safe = redactConnectPayload(payload, incomingToken);
3901
+ if (!dryRun && checkMcp && doctorExit !== ExitCode.OK && doctorExit !== ExitCode.DOCTOR_HAS_WARNINGS) {
3902
+ return { exitCode: doctorExit, result: ok(safe) };
3903
+ }
3904
+ return { exitCode: ExitCode.OK, result: ok(safe) };
3905
+ }
3906
+
3579
3907
  // src/commands/archive.ts
3580
- import { rename as rename2, mkdir as mkdir6, readFile as readFile7 } from "fs/promises";
3581
- import { join as join19, dirname as dirname5 } from "path";
3908
+ import { rename as rename2, mkdir as mkdir7, readFile as readFile8 } from "fs/promises";
3909
+ import { join as join20, dirname as dirname5 } from "path";
3582
3910
 
3583
3911
  // src/utils/delete-intent.ts
3584
- import { mkdir as mkdir5, writeFile as writeFile4, readdir as readdir4, readFile as readFile6 } from "fs/promises";
3585
- import { join as join18 } from "path";
3912
+ import { mkdir as mkdir6, writeFile as writeFile4, readdir as readdir4, readFile as readFile7 } from "fs/promises";
3913
+ import { join as join19 } from "path";
3586
3914
  var DELETE_INTENT_SCHEMA = "vault-delete-intent/v1";
3587
3915
  var DELETE_INTENT_DIR = "meta/delete-intents";
3588
3916
  function normalizeVaultRelPath(path) {
@@ -3613,10 +3941,10 @@ function buildDeleteIntent(input) {
3613
3941
  };
3614
3942
  }
3615
3943
  async function writeDeleteIntent(vault, intent) {
3616
- const dir = join18(vault, DELETE_INTENT_DIR);
3617
- await mkdir5(dir, { recursive: true });
3944
+ const dir = join19(vault, DELETE_INTENT_DIR);
3945
+ await mkdir6(dir, { recursive: true });
3618
3946
  const rel = `${DELETE_INTENT_DIR}/${pathToIntentFilename(intent.path)}`;
3619
- await writeFile4(join18(vault, rel), JSON.stringify(intent, null, 2) + "\n", "utf8");
3947
+ await writeFile4(join19(vault, rel), JSON.stringify(intent, null, 2) + "\n", "utf8");
3620
3948
  return rel;
3621
3949
  }
3622
3950
 
@@ -3656,7 +3984,7 @@ async function runArchive(input) {
3656
3984
  const slug = relPath.replace(/\.md$/, "").split("/").pop();
3657
3985
  const rawArchive = isRaw ? lifecycleDestination(relPath, "archive") : void 0;
3658
3986
  if (rawArchive && !rawArchive.ok) return { exitCode: ExitCode.USAGE, result: rawArchive };
3659
- const archivePath = isRaw ? rawArchive.data : join19("_archive", relPath).replace(/\\/g, "/");
3987
+ const archivePath = isRaw ? rawArchive.data : join20("_archive", relPath).replace(/\\/g, "/");
3660
3988
  const remoteRoot = normalizeRemoteRoot(input.remote);
3661
3989
  const remoteObjectPath = buildRemoteObjectPath(remoteRoot, relPath);
3662
3990
  let cascade;
@@ -3686,7 +4014,7 @@ async function runArchive(input) {
3686
4014
  const indexRefs = [];
3687
4015
  if (!isRaw) {
3688
4016
  try {
3689
- const idx = await readFile7(join19(input.vault, "index.md"), "utf8");
4017
+ const idx = await readFile8(join20(input.vault, "index.md"), "utf8");
3690
4018
  idx.split("\n").forEach((line, i) => {
3691
4019
  if (line.includes(`[[${slug}]]`)) indexRefs.push({ line: i + 1, text: line });
3692
4020
  });
@@ -3744,8 +4072,8 @@ async function runArchive(input) {
3744
4072
  const rewrittenCascadePages = [];
3745
4073
  if ((input.cascade || isRaw) && input.apply && cascade) {
3746
4074
  for (const ref of cascade.source_array_refs) {
3747
- const absPath = join19(input.vault, ref.page);
3748
- const text = await readFile7(absPath, "utf8");
4075
+ const absPath = join20(input.vault, ref.page);
4076
+ const text = await readFile8(absPath, "utf8");
3749
4077
  if (!arraysEqual(ref.sources_after, ref.sources_before)) {
3750
4078
  let updated;
3751
4079
  if (isRaw) {
@@ -3771,8 +4099,8 @@ ${fmRewritten}
3771
4099
  if (isRaw) {
3772
4100
  for (const ref of cascade.citation_refs) {
3773
4101
  if (cascade.source_array_refs.some((sourceRef) => sourceRef.page === ref.page)) continue;
3774
- const absPath = join19(input.vault, ref.page);
3775
- const original = await readFile7(absPath, "utf8");
4102
+ const absPath = join20(input.vault, ref.page);
4103
+ const original = await readFile8(absPath, "utf8");
3776
4104
  const updated = rewriteRawSourceReferences(original, relPath, archivePath).text;
3777
4105
  if (updated !== original) {
3778
4106
  const write = await safeWritePage(absPath, updated);
@@ -3783,13 +4111,13 @@ ${fmRewritten}
3783
4111
  }
3784
4112
  }
3785
4113
  if (!isRaw) {
3786
- await mkdir6(dirname5(join19(input.vault, archivePath)), { recursive: true });
3787
- await rename2(join19(input.vault, relPath), join19(input.vault, archivePath));
4114
+ await mkdir7(dirname5(join20(input.vault, archivePath)), { recursive: true });
4115
+ await rename2(join20(input.vault, relPath), join20(input.vault, archivePath));
3788
4116
  }
3789
4117
  let indexUpdated = false;
3790
4118
  if (!isRaw) {
3791
4119
  const { renderRootIndex: renderRootIndex2, writeRootIndexProjection: writeRootIndexProjection2 } = await import("./index-projection-6OHDINHT.js");
3792
- const before = await readFile7(join19(input.vault, "index.md"), "utf8").catch(() => "");
4120
+ const before = await readFile8(join20(input.vault, "index.md"), "utf8").catch(() => "");
3793
4121
  const fullTarget = relPath.replace(/\.md$/, "");
3794
4122
  const bare = fullTarget.split("/").pop() ?? fullTarget;
3795
4123
  const hadIndexEntry = before.includes(`[[${fullTarget}]]`) || before.includes(`[[${bare}]]`);
@@ -3843,7 +4171,7 @@ ${fmRewritten}
3843
4171
 
3844
4172
  // src/commands/remove.ts
3845
4173
  import { unlink as unlink2, access } from "fs/promises";
3846
- import { join as join20 } from "path";
4174
+ import { join as join21 } from "path";
3847
4175
  async function pathExists(abs) {
3848
4176
  try {
3849
4177
  await access(abs);
@@ -3869,7 +4197,7 @@ async function runRemove(input) {
3869
4197
  if (!relPath) {
3870
4198
  try {
3871
4199
  const candidate = normalizeVaultRelPath(input.page);
3872
- if (await pathExists(join20(input.vault, candidate))) {
4200
+ if (await pathExists(join21(input.vault, candidate))) {
3873
4201
  relPath = candidate;
3874
4202
  }
3875
4203
  } catch {
@@ -3907,12 +4235,12 @@ async function runRemove(input) {
3907
4235
  reason: input.reason
3908
4236
  });
3909
4237
  const tombstonePath = await writeDeleteIntent(input.vault, intent);
3910
- await unlink2(join20(input.vault, relPath));
4238
+ await unlink2(join21(input.vault, relPath));
3911
4239
  if (relPath.endsWith(".md") && !relPath.startsWith("raw/")) {
3912
- const { readFile: readFile23 } = await import("fs/promises");
4240
+ const { readFile: readFile24 } = await import("fs/promises");
3913
4241
  const { join: pathJoin } = await import("path");
3914
4242
  const { renderRootIndex: renderRootIndex2, writeRootIndexProjection: writeRootIndexProjection2 } = await import("./index-projection-6OHDINHT.js");
3915
- const before = await readFile23(pathJoin(input.vault, "index.md"), "utf8").catch(() => "");
4243
+ const before = await readFile24(pathJoin(input.vault, "index.md"), "utf8").catch(() => "");
3916
4244
  const fullTarget = relPath.replace(/\.md$/, "");
3917
4245
  const bare = fullTarget.split("/").pop() ?? fullTarget;
3918
4246
  const hadIndexEntry = before.includes(`[[${fullTarget}]]`) || before.includes(`[[${bare}]]`);
@@ -3950,7 +4278,7 @@ async function runRemove(input) {
3950
4278
  }
3951
4279
 
3952
4280
  // src/commands/drift.ts
3953
- import { createHash as createHash2 } from "crypto";
4281
+ import { createHash as createHash3 } from "crypto";
3954
4282
 
3955
4283
  // src/utils/fetch.ts
3956
4284
  async function controlledFetch(url, opts) {
@@ -4029,7 +4357,7 @@ async function runDrift(input) {
4029
4357
  });
4030
4358
  continue;
4031
4359
  }
4032
- const currentHash = createHash2("sha256").update(Buffer.from(resp.data.body, "utf8")).digest("hex");
4360
+ const currentHash = createHash3("sha256").update(Buffer.from(resp.data.body, "utf8")).digest("hex");
4033
4361
  const identity = assessSourceIdentity({
4034
4362
  rawPath: raw.relPath,
4035
4363
  sourceUrl,
@@ -4261,7 +4589,7 @@ ${migratedBody}${newFooter}`;
4261
4589
 
4262
4590
  // src/commands/update.ts
4263
4591
  import { execSync } from "child_process";
4264
- import { join as join21 } from "path";
4592
+ import { join as join22 } from "path";
4265
4593
  function parseCoreSemver(version) {
4266
4594
  const m = version.match(/^(\d+)\.(\d+)\.(\d+)/);
4267
4595
  if (!m) return null;
@@ -4313,7 +4641,7 @@ function resolveGlobalSkillsRoot() {
4313
4641
  encoding: "utf8",
4314
4642
  timeout: 5e3
4315
4643
  }).trim();
4316
- return join21(globalRoot, "skillwiki", "skills");
4644
+ return join22(globalRoot, "skillwiki", "skills");
4317
4645
  } catch {
4318
4646
  return null;
4319
4647
  }
@@ -4341,7 +4669,7 @@ async function runUpdate(input) {
4341
4669
  const pkg2 = readCliPackageJson();
4342
4670
  const currentVersion = pkg2.version;
4343
4671
  const tag = normalizeDistTag(input.distTag);
4344
- const target = join21(input.home, ".claude", "skills");
4672
+ const target = join22(input.home, ".claude", "skills");
4345
4673
  let latest;
4346
4674
  try {
4347
4675
  latest = execSync(`npm view skillwiki@${tag} version`, {
@@ -4422,13 +4750,13 @@ async function runUpdate(input) {
4422
4750
  // src/commands/self-update.ts
4423
4751
  import { execSync as execSync2 } from "child_process";
4424
4752
  import { existsSync as existsSync8, readFileSync as readFileSync11 } from "fs";
4425
- import { join as join22 } from "path";
4753
+ import { join as join23 } from "path";
4426
4754
  var DEFAULT_SOURCE_ROOT_SUFFIX = "/Desktop/code/llm-wiki";
4427
4755
  async function runSelfUpdate(input) {
4428
4756
  const currentVersion = readCliPackageJson().version;
4429
4757
  const sourceRoot = input.sourceRoot ?? `${input.home}${DEFAULT_SOURCE_ROOT_SUFFIX}`;
4430
4758
  const distTag = normalizeDistTag(input.distTag);
4431
- const localPkgPath = join22(sourceRoot, "packages", "cli", "package.json");
4759
+ const localPkgPath = join23(sourceRoot, "packages", "cli", "package.json");
4432
4760
  const hasLocalSource = existsSync8(localPkgPath);
4433
4761
  if (input.check) {
4434
4762
  let availableVersion = null;
@@ -4560,10 +4888,10 @@ async function runSelfUpdate(input) {
4560
4888
  }
4561
4889
 
4562
4890
  // src/commands/transcripts.ts
4563
- import { readdir as readdir5, stat as stat3, readFile as readFile9 } from "fs/promises";
4564
- import { join as join23 } from "path";
4891
+ import { readdir as readdir5, stat as stat4, readFile as readFile10 } from "fs/promises";
4892
+ import { join as join24 } from "path";
4565
4893
  async function runTranscripts(input) {
4566
- const dir = join23(input.vault, "raw", "transcripts");
4894
+ const dir = join24(input.vault, "raw", "transcripts");
4567
4895
  let entries;
4568
4896
  try {
4569
4897
  entries = await readdir5(dir, { withFileTypes: true });
@@ -4573,13 +4901,13 @@ async function runTranscripts(input) {
4573
4901
  const transcripts = [];
4574
4902
  for (const entry of entries) {
4575
4903
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
4576
- const filePath = join23(dir, entry.name);
4577
- const content = await readFile9(filePath, "utf8");
4904
+ const filePath = join24(dir, entry.name);
4905
+ const content = await readFile10(filePath, "utf8");
4578
4906
  const fm = extractFrontmatter(content);
4579
4907
  if (!fm.ok) continue;
4580
4908
  const ingested = typeof fm.data.ingested === "string" ? fm.data.ingested : "";
4581
4909
  if (input.since && ingested && ingested < input.since) continue;
4582
- const s = await stat3(filePath);
4910
+ const s = await stat4(filePath);
4583
4911
  transcripts.push({
4584
4912
  file: `raw/transcripts/${entry.name}`,
4585
4913
  ingested,
@@ -4591,8 +4919,8 @@ async function runTranscripts(input) {
4591
4919
  }
4592
4920
 
4593
4921
  // src/commands/claims-audit.ts
4594
- import { readFile as readFile10 } from "fs/promises";
4595
- import { join as join24 } from "path";
4922
+ import { readFile as readFile11 } from "fs/promises";
4923
+ import { join as join25 } from "path";
4596
4924
  function safeProjectPresentation(raw) {
4597
4925
  if (safeDiagnosticValue(raw) === REDACTED_MALFORMED_REFERENCE) {
4598
4926
  return REDACTED_MALFORMED_REFERENCE;
@@ -4632,9 +4960,9 @@ async function runClaimsAudit(input) {
4632
4960
  return !input.project || slugByDir.get(relDir) === input.project;
4633
4961
  });
4634
4962
  const claimSourceResults = await mapWithConcurrency(scopedSpecDirs, vaultIoConcurrency(), async (relDir) => {
4635
- const specPath = join24(input.vault, relDir, "spec.md");
4963
+ const specPath = join25(input.vault, relDir, "spec.md");
4636
4964
  try {
4637
- const specText = await readFile10(specPath, "utf8");
4965
+ const specText = await readFile11(specPath, "utf8");
4638
4966
  const fm = extractFrontmatter(specText);
4639
4967
  if (!fm.ok) return null;
4640
4968
  return {
@@ -4728,10 +5056,10 @@ async function runClaimsAudit(input) {
4728
5056
  }
4729
5057
 
4730
5058
  // src/commands/compound.ts
4731
- import { writeFile as writeFile6, mkdir as mkdir7, readdir as readdir6, unlink as unlink3 } from "fs/promises";
4732
- import { join as join25 } from "path";
5059
+ import { writeFile as writeFile6, mkdir as mkdir8, readdir as readdir6, unlink as unlink3 } from "fs/promises";
5060
+ import { join as join26 } from "path";
4733
5061
  import { existsSync as existsSync9 } from "fs";
4734
- import { readFile as readFile11 } from "fs/promises";
5062
+ import { readFile as readFile12 } from "fs/promises";
4735
5063
  var RETRO_HEADING_RE = /^## \[(\d{4}-\d{2}-\d{2})(?:\s+[^\]]+)?\] retro \| loop cycle(?: (\d+))?: (.+)$/;
4736
5064
  var FIELD_RE = {
4737
5065
  improve: /^-\s+\*?\*?Improve:?\*?\*?\s*(.+)$/m,
@@ -4829,17 +5157,17 @@ function extractRetroFields(date, cycleName, block) {
4829
5157
  };
4830
5158
  }
4831
5159
  async function runCompound(input) {
4832
- const logPath = join25(input.vault, "log.md");
5160
+ const logPath = join26(input.vault, "log.md");
4833
5161
  let logText;
4834
5162
  try {
4835
- logText = await readFile11(logPath, "utf8");
5163
+ logText = await readFile12(logPath, "utf8");
4836
5164
  } catch {
4837
5165
  return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path: logPath }) };
4838
5166
  }
4839
5167
  const entries = parseRetroEntries(logText);
4840
5168
  const promoted = [];
4841
5169
  const skipped = [];
4842
- const compoundDir = join25(input.vault, "projects", input.project, "compound");
5170
+ const compoundDir = join26(input.vault, "projects", input.project, "compound");
4843
5171
  for (const entry of entries) {
4844
5172
  const generalizeValue = entry.generalize.trim();
4845
5173
  if (!/^yes/i.test(generalizeValue)) {
@@ -4847,7 +5175,7 @@ async function runCompound(input) {
4847
5175
  continue;
4848
5176
  }
4849
5177
  const slug = slugify(entry.cycleName);
4850
- const compoundPath = join25(compoundDir, `${slug}.md`);
5178
+ const compoundPath = join26(compoundDir, `${slug}.md`);
4851
5179
  if (existsSync9(compoundPath)) {
4852
5180
  skipped.push(entry.date);
4853
5181
  continue;
@@ -4887,7 +5215,7 @@ async function runCompound(input) {
4887
5215
  const content = frontmatter + "\n" + body;
4888
5216
  if (!input.dryRun) {
4889
5217
  if (!existsSync9(compoundDir)) {
4890
- await mkdir7(compoundDir, { recursive: true });
5218
+ await mkdir8(compoundDir, { recursive: true });
4891
5219
  }
4892
5220
  await writeFile6(compoundPath, content, "utf8");
4893
5221
  }
@@ -4908,7 +5236,7 @@ async function runCompound(input) {
4908
5236
  };
4909
5237
  }
4910
5238
  async function runCompoundDelete(input) {
4911
- const projectDir = join25(input.vault, "projects", input.project);
5239
+ const projectDir = join26(input.vault, "projects", input.project);
4912
5240
  if (!existsSync9(projectDir)) {
4913
5241
  return {
4914
5242
  exitCode: ExitCode.PROJECT_NOT_FOUND,
@@ -4916,7 +5244,7 @@ async function runCompoundDelete(input) {
4916
5244
  };
4917
5245
  }
4918
5246
  const entryName = input.entry.replace(/\.md$/, "");
4919
- const compoundPath = join25(projectDir, "compound", `${entryName}.md`);
5247
+ const compoundPath = join26(projectDir, "compound", `${entryName}.md`);
4920
5248
  if (!existsSync9(compoundPath)) {
4921
5249
  return {
4922
5250
  exitCode: ExitCode.FILE_NOT_FOUND,
@@ -4950,7 +5278,7 @@ knowledge.md regenerated`
4950
5278
  };
4951
5279
  }
4952
5280
  async function runCompoundList(input) {
4953
- const compoundDir = join25(input.vault, "projects", input.project, "compound");
5281
+ const compoundDir = join26(input.vault, "projects", input.project, "compound");
4954
5282
  if (!existsSync9(compoundDir)) {
4955
5283
  return {
4956
5284
  exitCode: ExitCode.OK,
@@ -4981,10 +5309,10 @@ could not read compound directory`
4981
5309
  const entries = [];
4982
5310
  for (const dirent of dirents) {
4983
5311
  if (!dirent.isFile() || !dirent.name.endsWith(".md")) continue;
4984
- const filePath = join25(compoundDir, dirent.name);
5312
+ const filePath = join26(compoundDir, dirent.name);
4985
5313
  let text;
4986
5314
  try {
4987
- text = await readFile11(filePath, "utf8");
5315
+ text = await readFile12(filePath, "utf8");
4988
5316
  } catch {
4989
5317
  continue;
4990
5318
  }
@@ -5013,8 +5341,8 @@ no compound entries found`;
5013
5341
  }
5014
5342
 
5015
5343
  // src/commands/session-brief.ts
5016
- import { mkdir as mkdir8, readFile as readFile12, writeFile as writeFile7 } from "fs/promises";
5017
- import { join as join26, relative, sep } from "path";
5344
+ import { mkdir as mkdir9, readFile as readFile13, writeFile as writeFile7 } from "fs/promises";
5345
+ import { join as join27, relative, sep } from "path";
5018
5346
  var MAX_WORDS = 900;
5019
5347
  async function runSessionBrief(input) {
5020
5348
  const { root: scanRoot } = resolveReadOnlyVaultRoot(input.vault);
@@ -5123,7 +5451,7 @@ async function resolveProject(input) {
5123
5451
  const envProject = input.env?.SKILLWIKI_PROJECT;
5124
5452
  if (envProject) return envProject;
5125
5453
  const cwd = input.cwd ?? process.cwd();
5126
- const projectDotenv = await readProjectSlug(join26(cwd, ".skillwiki", ".env"));
5454
+ const projectDotenv = await readProjectSlug(join27(cwd, ".skillwiki", ".env"));
5127
5455
  if (projectDotenv) return projectDotenv;
5128
5456
  const inferred = inferProjectFromPath(input.vault, cwd);
5129
5457
  if (inferred) return inferred;
@@ -5132,7 +5460,7 @@ async function resolveProject(input) {
5132
5460
  async function readProjectSlug(file) {
5133
5461
  let text;
5134
5462
  try {
5135
- text = await readFile12(file, "utf8");
5463
+ text = await readFile13(file, "utf8");
5136
5464
  } catch {
5137
5465
  return void 0;
5138
5466
  }
@@ -5223,7 +5551,7 @@ async function loadTrendDigests(typedPages) {
5223
5551
  return out;
5224
5552
  }
5225
5553
  async function loadSessionPins(vault, project) {
5226
- const text = await readIfExists(join26(vault, "meta", "session-pins.md"));
5554
+ const text = await readIfExists(join27(vault, "meta", "session-pins.md"));
5227
5555
  if (!text) return [];
5228
5556
  const fm = extractFrontmatter(text);
5229
5557
  if (!fm.ok) return [];
@@ -5342,7 +5670,7 @@ function satelliteHealthWarnings(warning) {
5342
5670
  return warning ? [warning] : [];
5343
5671
  }
5344
5672
  async function loadHealthWarnings(vault) {
5345
- const text = await readIfExists(join26(vault, ".skillwiki", "health.json"));
5673
+ const text = await readIfExists(join27(vault, ".skillwiki", "health.json"));
5346
5674
  if (!text) return [];
5347
5675
  try {
5348
5676
  const parsed = JSON.parse(text);
@@ -5355,7 +5683,7 @@ async function loadHealthWarnings(vault) {
5355
5683
  }
5356
5684
  }
5357
5685
  async function loadMemoryTopics(vault, project) {
5358
- const text = await readIfExists(join26(vault, ".skillwiki", "memory", project, "topics.json"));
5686
+ const text = await readIfExists(join27(vault, ".skillwiki", "memory", project, "topics.json"));
5359
5687
  if (!text) return [];
5360
5688
  try {
5361
5689
  const parsed = JSON.parse(text);
@@ -5380,11 +5708,11 @@ async function loadMemoryTopics(vault, project) {
5380
5708
  }
5381
5709
  }
5382
5710
  async function writeBriefArtifacts(vault, input) {
5383
- const metaPath = join26(vault, "meta", "latest-session-brief.md");
5384
- const cacheMdPath = join26(vault, ".skillwiki", "session-brief.md");
5385
- const cacheJsonPath = join26(vault, ".skillwiki", "session-brief.json");
5386
- await mkdir8(join26(vault, "meta"), { recursive: true });
5387
- await mkdir8(join26(vault, ".skillwiki"), { recursive: true });
5711
+ const metaPath = join27(vault, "meta", "latest-session-brief.md");
5712
+ const cacheMdPath = join27(vault, ".skillwiki", "session-brief.md");
5713
+ const cacheJsonPath = join27(vault, ".skillwiki", "session-brief.json");
5714
+ await mkdir9(join27(vault, "meta"), { recursive: true });
5715
+ await mkdir9(join27(vault, ".skillwiki"), { recursive: true });
5388
5716
  const committed = renderCommittedBrief(input);
5389
5717
  const previousComparable = comparableBrief(await readIfExists(metaPath));
5390
5718
  const nextComparable = comparableBrief(committed);
@@ -5457,7 +5785,7 @@ function renderCommittedBrief(input) {
5457
5785
  ].filter((line) => line !== "").join("\n");
5458
5786
  }
5459
5787
  async function rebuildRootIndexProjection(vault) {
5460
- const before = await readIfExists(join26(vault, "index.md"));
5788
+ const before = await readIfExists(join27(vault, "index.md"));
5461
5789
  if (!before) return false;
5462
5790
  const projection = await renderRootIndex({ vault, currentText: before });
5463
5791
  if (!projection.ok) return false;
@@ -5467,7 +5795,7 @@ async function rebuildRootIndexProjection(vault) {
5467
5795
  }
5468
5796
  async function readIfExists(path) {
5469
5797
  try {
5470
- return await readFile12(path, "utf8");
5798
+ return await readFile13(path, "utf8");
5471
5799
  } catch {
5472
5800
  return "";
5473
5801
  }
@@ -5509,23 +5837,23 @@ function dateFromPath(path) {
5509
5837
  }
5510
5838
 
5511
5839
  // src/commands/ingest.ts
5512
- import { readFile as readFile15, open, unlink as unlink4, mkdir as mkdir10 } from "fs/promises";
5513
- import { join as join31 } from "path";
5514
- import { createHash as createHash4 } from "crypto";
5840
+ import { readFile as readFile16, open, unlink as unlink4, mkdir as mkdir11 } from "fs/promises";
5841
+ import { join as join32 } from "path";
5842
+ import { createHash as createHash5 } from "crypto";
5515
5843
 
5516
5844
  // src/commands/page-publish.ts
5517
5845
  import { readFileSync as readFileSync13, realpathSync as realpathSync2 } from "fs";
5518
- import { mkdir as mkdir9, readFile as readFile14 } from "fs/promises";
5519
- import { dirname as dirname8, join as join30, resolve as resolve4 } from "path";
5846
+ import { mkdir as mkdir10, readFile as readFile15 } from "fs/promises";
5847
+ import { dirname as dirname8, join as join31, resolve as resolve4 } from "path";
5520
5848
 
5521
5849
  // src/utils/publication-approval.ts
5522
- import { createHash as createHash3 } from "crypto";
5850
+ import { createHash as createHash4 } from "crypto";
5523
5851
  var APPROVAL_CONTRACT = "skillwiki-publication-approval-v1";
5524
5852
  var APPROVAL_TOKEN_VERSION = "swpub1";
5525
5853
  var SHA256_HEX = /^[0-9a-f]{64}$/;
5526
5854
  var PUBLISHERS = /* @__PURE__ */ new Set(["page", "project-page"]);
5527
5855
  function sha256Hex(bytes) {
5528
- return createHash3("sha256").update(bytes).digest("hex");
5856
+ return createHash4("sha256").update(bytes).digest("hex");
5529
5857
  }
5530
5858
  function normalizeLogNote(note) {
5531
5859
  if (note === void 0) return "";
@@ -5744,8 +6072,8 @@ function redactApprovalTokens(text) {
5744
6072
  }
5745
6073
 
5746
6074
  // src/publish/pipeline.ts
5747
- import { readFile as readFile13 } from "fs/promises";
5748
- import { dirname as dirname7, join as join29, resolve as resolve3 } from "path";
6075
+ import { readFile as readFile14 } from "fs/promises";
6076
+ import { dirname as dirname7, join as join30, resolve as resolve3 } from "path";
5749
6077
 
5750
6078
  // src/utils/publication-operation-journal.ts
5751
6079
  import {
@@ -5759,7 +6087,7 @@ import {
5759
6087
  writeFileSync as writeFileSync5
5760
6088
  } from "fs";
5761
6089
  import { homedir } from "os";
5762
- import { dirname as dirname6, join as join27 } from "path";
6090
+ import { dirname as dirname6, join as join28 } from "path";
5763
6091
  var PUBLICATION_JOURNAL_SCHEMA = "skillwiki-publication-operation-v1";
5764
6092
  var PHASE_ORDER2 = [
5765
6093
  "locked",
@@ -5788,16 +6116,16 @@ function vaultIdentity(vaultPath) {
5788
6116
  return sha256Hex(real).slice(0, 16);
5789
6117
  }
5790
6118
  function publicationJournalRoot(home = process.env.HOME || homedir()) {
5791
- return join27(home, ".skillwiki", "publication-operations");
6119
+ return join28(home, ".skillwiki", "publication-operations");
5792
6120
  }
5793
6121
  function resolveJournalPaths(vaultPath, operationId2, home = process.env.HOME || homedir()) {
5794
6122
  const identity = vaultIdentity(vaultPath);
5795
6123
  const rootDir = publicationJournalRoot(home);
5796
- const vaultDir = join27(rootDir, identity);
6124
+ const vaultDir = join28(rootDir, identity);
5797
6125
  return {
5798
6126
  rootDir,
5799
6127
  vaultDir,
5800
- journalPath: join27(vaultDir, `${operationId2}.json`)
6128
+ journalPath: join28(vaultDir, `${operationId2}.json`)
5801
6129
  };
5802
6130
  }
5803
6131
  function ensurePrivateDir(dir) {
@@ -6018,7 +6346,7 @@ function completePublicationJournal(input) {
6018
6346
  }
6019
6347
 
6020
6348
  // src/publish/hold-gates.ts
6021
- import { join as join28 } from "path";
6349
+ import { join as join29 } from "path";
6022
6350
  function evaluateSchemaGate(content, target, publisherKind) {
6023
6351
  const frontmatter = extractFrontmatter(content);
6024
6352
  if (!frontmatter.ok) return false;
@@ -6054,7 +6382,7 @@ function evaluateSchemaGate(content, target, publisherKind) {
6054
6382
  return false;
6055
6383
  }
6056
6384
  function evaluateBrokenWikilinkGate(body, scan, ownTarget) {
6057
- const draftPage = { absPath: join28(scan.root, ownTarget), relPath: ownTarget };
6385
+ const draftPage = { absPath: join29(scan.root, ownTarget), relPath: ownTarget };
6058
6386
  const resolver = buildWikilinkResolver([...scan.allMarkdown, draftPage]);
6059
6387
  const wikilinks = extractBodyWikilinks(body);
6060
6388
  for (const link of wikilinks) {
@@ -6241,7 +6569,7 @@ function phaseAtLeast(current, needed) {
6241
6569
  }
6242
6570
  async function readPageChanged(targetPath, content) {
6243
6571
  try {
6244
- return ok(await readFile13(targetPath, "utf8") !== content);
6572
+ return ok(await readFile14(targetPath, "utf8") !== content);
6245
6573
  } catch (error) {
6246
6574
  if (error.code === "ENOENT") return ok(true);
6247
6575
  return err("WRITE_FAILED", { path: targetPath, message: String(error) });
@@ -6249,7 +6577,7 @@ async function readPageChanged(targetPath, content) {
6249
6577
  }
6250
6578
  async function readPriorTargetSha(targetPath) {
6251
6579
  try {
6252
- const text = await readFile13(targetPath, "utf8");
6580
+ const text = await readFile14(targetPath, "utf8");
6253
6581
  return ok(sha256Hex(text));
6254
6582
  } catch (error) {
6255
6583
  if (error.code === "ENOENT") return ok("absent");
@@ -6282,10 +6610,10 @@ function successReceipt(strategy, prepared, details) {
6282
6610
  };
6283
6611
  }
6284
6612
  async function runPipelinePreview(strategy, prepared, vault) {
6285
- const schemaPath = join29(vault, "SCHEMA.md");
6613
+ const schemaPath = join30(vault, "SCHEMA.md");
6286
6614
  let schemaText;
6287
6615
  try {
6288
- schemaText = await readFile13(schemaPath, "utf8");
6616
+ schemaText = await readFile14(schemaPath, "utf8");
6289
6617
  } catch (error) {
6290
6618
  const result = err("FILE_NOT_FOUND", { path: schemaPath, message: String(error) });
6291
6619
  return { exitCode: ExitCode.FILE_NOT_FOUND, result };
@@ -6309,10 +6637,10 @@ async function runPipelinePreview(strategy, prepared, vault) {
6309
6637
  if (!indices.ok) {
6310
6638
  return { exitCode: indices.exitCode, result: indices.result };
6311
6639
  }
6312
- const logPath = join29(vault, "log.md");
6640
+ const logPath = join30(vault, "log.md");
6313
6641
  let logAppended;
6314
6642
  try {
6315
- const logText = await readFile13(logPath, "utf8");
6643
+ const logText = await readFile14(logPath, "utf8");
6316
6644
  const marker = strategy.publisherKind === "page" ? `<!-- skillwiki-page-publish:${prepared.operationId} -->` : `<!-- skillwiki-project-page-publish:${prepared.operationId} -->`;
6317
6645
  logAppended = !logText.includes(marker);
6318
6646
  } catch (error) {
@@ -6403,10 +6731,10 @@ async function runPipelineLockedPrimaryStages(strategy, prepared, vault, deps, j
6403
6731
  return { ok: false, exitCode: ExitCode.WRITE_FAILED, stage: "journal", state, cause: journalHook };
6404
6732
  }
6405
6733
  }
6406
- const schemaPath = join29(vault, "SCHEMA.md");
6734
+ const schemaPath = join30(vault, "SCHEMA.md");
6407
6735
  let schemaText;
6408
6736
  try {
6409
- schemaText = await readFile13(schemaPath, "utf8");
6737
+ schemaText = await readFile14(schemaPath, "utf8");
6410
6738
  } catch (error) {
6411
6739
  return {
6412
6740
  ok: false,
@@ -6455,8 +6783,8 @@ async function runPipelineLockedPrimaryStages(strategy, prepared, vault, deps, j
6455
6783
  if (!strategy.usesJournal || !phaseAtLeast(currentPhase, "page")) {
6456
6784
  if (strategy.publisherKind === "project-page") {
6457
6785
  try {
6458
- const { mkdir: mkdir13 } = await import("fs/promises");
6459
- await mkdir13(dirname7(prepared.targetPath), { recursive: true });
6786
+ const { mkdir: mkdir14 } = await import("fs/promises");
6787
+ await mkdir14(dirname7(prepared.targetPath), { recursive: true });
6460
6788
  } catch (error) {
6461
6789
  return {
6462
6790
  ok: false,
@@ -6504,8 +6832,8 @@ async function runPipelineLockedPrimaryStages(strategy, prepared, vault, deps, j
6504
6832
  let visibleSchema;
6505
6833
  try {
6506
6834
  [visible, visibleSchema] = await Promise.all([
6507
- readFile13(prepared.targetPath, "utf8"),
6508
- readFile13(schemaPath, "utf8")
6835
+ readFile14(prepared.targetPath, "utf8"),
6836
+ readFile14(schemaPath, "utf8")
6509
6837
  ]);
6510
6838
  } catch (error) {
6511
6839
  return {
@@ -6897,16 +7225,16 @@ async function refreshProjectIndexForTarget(vault, projectSlugs, today) {
6897
7225
  for (const slug of projectSlugs) {
6898
7226
  const rendered = await renderProjectIndex(vault, slug, { today });
6899
7227
  if (!rendered.ok) return rendered;
6900
- const indexPath = join30(vault, rendered.data.index_path);
7228
+ const indexPath = join31(vault, rendered.data.index_path);
6901
7229
  try {
6902
- await mkdir9(dirname8(indexPath), { recursive: true });
7230
+ await mkdir10(dirname8(indexPath), { recursive: true });
6903
7231
  } catch (error) {
6904
7232
  return err("WRITE_FAILED", { path: indexPath, message: String(error) });
6905
7233
  }
6906
7234
  const written = await atomicWriteText(indexPath, rendered.data.text);
6907
7235
  if (!written.ok) return written;
6908
7236
  try {
6909
- const visible = await readFile14(indexPath, "utf8");
7237
+ const visible = await readFile15(indexPath, "utf8");
6910
7238
  if (visible !== rendered.data.text) {
6911
7239
  return err("WRITE_FAILED", { path: indexPath, message: "project index verification failed" });
6912
7240
  }
@@ -7007,7 +7335,7 @@ function preparePagePublicationFromContent(input) {
7007
7335
  async function preparePagePublication(input) {
7008
7336
  let content;
7009
7337
  try {
7010
- content = await readFile14(input.draftPath, "utf8");
7338
+ content = await readFile15(input.draftPath, "utf8");
7011
7339
  } catch (error) {
7012
7340
  return err("FILE_NOT_FOUND", { path: input.draftPath, message: String(error) });
7013
7341
  }
@@ -7095,10 +7423,10 @@ var PAGE_PUBLISH_STRATEGY = {
7095
7423
  return void 0;
7096
7424
  },
7097
7425
  async previewIndices(prepared, vault, pageChanged) {
7098
- const indexPath = join30(vault, "index.md");
7426
+ const indexPath = join31(vault, "index.md");
7099
7427
  let indexText;
7100
7428
  try {
7101
- indexText = await readFile14(indexPath, "utf8");
7429
+ indexText = await readFile15(indexPath, "utf8");
7102
7430
  } catch (error) {
7103
7431
  const result = err("FILE_NOT_FOUND", { path: indexPath, message: String(error) });
7104
7432
  return { ok: false, exitCode: ExitCode.FILE_NOT_FOUND, result };
@@ -7116,7 +7444,7 @@ var PAGE_PUBLISH_STRATEGY = {
7116
7444
  if (!renderedProject.ok) {
7117
7445
  return { ok: false, exitCode: errorExitCode(renderedProject.error), result: renderedProject };
7118
7446
  }
7119
- const projectIndexPath = join30(vault, renderedProject.data.index_path);
7447
+ const projectIndexPath = join31(vault, renderedProject.data.index_path);
7120
7448
  const projectIndexChanged = await readPageChanged(projectIndexPath, renderedProject.data.text);
7121
7449
  if (!projectIndexChanged.ok) {
7122
7450
  return { ok: false, exitCode: errorExitCode(projectIndexChanged.error), result: projectIndexChanged };
@@ -7304,7 +7632,7 @@ function buildTypedContent(title, ingested, type, tags, rawRelPath, provenance)
7304
7632
  }
7305
7633
  async function resolveRawCapture(input) {
7306
7634
  try {
7307
- const existing = await readFile15(input.path, "utf8");
7635
+ const existing = await readFile16(input.path, "utf8");
7308
7636
  const frontmatter = extractFrontmatter(existing);
7309
7637
  if (!frontmatter.ok) {
7310
7638
  return err("INGEST_VALIDATION_FAILED", {
@@ -7443,7 +7771,7 @@ async function runIngest(input, pagePublishDeps = defaultPagePublishDeps()) {
7443
7771
  sourceContent = fetchResult.data.body;
7444
7772
  } else {
7445
7773
  try {
7446
- sourceContent = await readFile15(input.source, "utf8");
7774
+ sourceContent = await readFile16(input.source, "utf8");
7447
7775
  } catch {
7448
7776
  return {
7449
7777
  exitCode: ExitCode.FILE_NOT_FOUND,
@@ -7461,14 +7789,14 @@ async function runIngest(input, pagePublishDeps = defaultPagePublishDeps()) {
7461
7789
  })
7462
7790
  };
7463
7791
  }
7464
- const sha2562 = createHash4("sha256").update(Buffer.from(sourceContent, "utf8")).digest("hex");
7792
+ const sha2562 = createHash5("sha256").update(Buffer.from(sourceContent, "utf8")).digest("hex");
7465
7793
  const today = todayIso();
7466
7794
  const slug = slugify2(input.title);
7467
7795
  const tags = input.tags && input.tags.length > 0 ? input.tags : [];
7468
7796
  const rawRelPath = `raw/articles/${slug}.md`;
7469
7797
  const typedDir = TYPE_DIR[input.type] ?? `${input.type}s`;
7470
7798
  const typedRelPath = `${typedDir}/${slug}.md`;
7471
- const rawAbsPath = join31(input.vault, rawRelPath);
7799
+ const rawAbsPath = join32(input.vault, rawRelPath);
7472
7800
  const identity = assessSourceIdentity({
7473
7801
  rawPath: rawRelPath,
7474
7802
  sourceUrl: sourceUrl ?? void 0,
@@ -7512,11 +7840,11 @@ async function runIngest(input, pagePublishDeps = defaultPagePublishDeps()) {
7512
7840
  );
7513
7841
  if (!input.dryRun) {
7514
7842
  try {
7515
- await mkdir10(join31(input.vault, typedDir), { recursive: true });
7843
+ await mkdir11(join32(input.vault, typedDir), { recursive: true });
7516
7844
  } catch (error) {
7517
7845
  return {
7518
7846
  exitCode: ExitCode.WRITE_FAILED,
7519
- result: err("WRITE_FAILED", { path: join31(input.vault, typedDir), message: String(error) })
7847
+ result: err("WRITE_FAILED", { path: join32(input.vault, typedDir), message: String(error) })
7520
7848
  };
7521
7849
  }
7522
7850
  }
@@ -7570,11 +7898,11 @@ async function runIngest(input, pagePublishDeps = defaultPagePublishDeps()) {
7570
7898
  preflight: pagePublishDeps.preflight,
7571
7899
  mutate: async (receipt) => {
7572
7900
  try {
7573
- await mkdir10(join31(input.vault, "raw", "articles"), { recursive: true });
7901
+ await mkdir11(join32(input.vault, "raw", "articles"), { recursive: true });
7574
7902
  } catch (error) {
7575
7903
  return {
7576
7904
  exitCode: ExitCode.WRITE_FAILED,
7577
- result: err("WRITE_FAILED", { path: join31(input.vault, "raw", "articles"), message: String(error) })
7905
+ result: err("WRITE_FAILED", { path: join32(input.vault, "raw", "articles"), message: String(error) })
7578
7906
  };
7579
7907
  }
7580
7908
  const rawWrite = await writeResolvedRaw({
@@ -7810,8 +8138,8 @@ ${body}`;
7810
8138
  }
7811
8139
 
7812
8140
  // src/commands/tag-reconcile.ts
7813
- import { readFile as readFile16 } from "fs/promises";
7814
- import { join as join32, posix } from "path";
8141
+ import { readFile as readFile17 } from "fs/promises";
8142
+ import { join as join33, posix } from "path";
7815
8143
  var TYPED_TARGET_RE = /^(entities|concepts|comparisons|queries|meta)\/[a-z0-9][a-z0-9./_-]*\.md$/;
7816
8144
  function errorExitCode2(error) {
7817
8145
  switch (error) {
@@ -7854,7 +8182,7 @@ function asTagArray(frontmatter, path) {
7854
8182
  async function readTagsFromFile(path) {
7855
8183
  let text;
7856
8184
  try {
7857
- text = await readFile16(path, "utf8");
8185
+ text = await readFile17(path, "utf8");
7858
8186
  } catch (error) {
7859
8187
  if (error.code === "ENOENT") {
7860
8188
  return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path }) };
@@ -7875,7 +8203,7 @@ async function resolveRequestedTags(input, page) {
7875
8203
  result: err("INVALID_FRONTMATTER", { message: "explicit tags must be an array of strings" })
7876
8204
  };
7877
8205
  }
7878
- const source = input.from ?? (explicit.length === 0 ? join32(input.vault, page) : void 0);
8206
+ const source = input.from ?? (explicit.length === 0 ? join33(input.vault, page) : void 0);
7879
8207
  if (!source) return { exitCode: ExitCode.OK, result: ok({ tags: [...new Set(explicit)].sort() }) };
7880
8208
  const sourced = await readTagsFromFile(source);
7881
8209
  if (!sourced.result.ok) return { exitCode: sourced.exitCode, result: sourced.result };
@@ -7886,7 +8214,7 @@ async function resolveRequestedTags(input, page) {
7886
8214
  }
7887
8215
  async function readSchema(schemaPath) {
7888
8216
  try {
7889
- return { exitCode: ExitCode.OK, result: ok(await readFile16(schemaPath, "utf8")) };
8217
+ return { exitCode: ExitCode.OK, result: ok(await readFile17(schemaPath, "utf8")) };
7890
8218
  } catch (error) {
7891
8219
  if (error.code === "ENOENT") {
7892
8220
  return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path: schemaPath }) };
@@ -7914,7 +8242,7 @@ function previewResult(page, tags, reconciled, dryRun, filesChanged) {
7914
8242
  };
7915
8243
  }
7916
8244
  async function reconcileTagsWhileLocked(input, page, tags, comment) {
7917
- const schemaPath = join32(input.vault, "SCHEMA.md");
8245
+ const schemaPath = join33(input.vault, "SCHEMA.md");
7918
8246
  const current = await readSchema(schemaPath);
7919
8247
  if (!current.result.ok) return { exitCode: current.exitCode, result: current.result };
7920
8248
  const next = reconcileTaxonomyDocument(current.result.data, { tags, comment });
@@ -7925,7 +8253,7 @@ async function reconcileTagsWhileLocked(input, page, tags, comment) {
7925
8253
  }
7926
8254
  let verifiedText;
7927
8255
  try {
7928
- verifiedText = await readFile16(schemaPath, "utf8");
8256
+ verifiedText = await readFile17(schemaPath, "utf8");
7929
8257
  } catch (error) {
7930
8258
  return {
7931
8259
  exitCode: ExitCode.WRITE_FAILED,
@@ -7951,7 +8279,7 @@ async function runTagReconcile(input) {
7951
8279
  const comment = taxonomyCommentForPage(page.data, date, input.reason);
7952
8280
  if (!comment.ok) return { exitCode: ExitCode.SCHEME_REJECTED, result: comment };
7953
8281
  if (!input.write) {
7954
- const schema = await readSchema(join32(input.vault, "SCHEMA.md"));
8282
+ const schema = await readSchema(join33(input.vault, "SCHEMA.md"));
7955
8283
  if (!schema.result.ok) return { exitCode: schema.exitCode, result: schema.result };
7956
8284
  const preview = reconcileTaxonomyDocument(schema.result.data, { tags, comment: comment.data });
7957
8285
  return previewResult(page.data, tags, preview, true, []);
@@ -8004,8 +8332,8 @@ async function runTagReconcile(input) {
8004
8332
 
8005
8333
  // src/commands/project-page-publish.ts
8006
8334
  import { realpathSync as realpathSync4 } from "fs";
8007
- import { mkdir as mkdir11, readFile as readFile17 } from "fs/promises";
8008
- import { dirname as dirname10, join as join33, resolve as resolve6 } from "path";
8335
+ import { mkdir as mkdir12, readFile as readFile18 } from "fs/promises";
8336
+ import { dirname as dirname10, join as join34, resolve as resolve6 } from "path";
8009
8337
 
8010
8338
  // src/utils/architecture-page.ts
8011
8339
  import { lstatSync, realpathSync as realpathSync3 } from "fs";
@@ -8177,7 +8505,7 @@ function validateLogNote(logNote) {
8177
8505
  async function prepareProjectPagePublication(input) {
8178
8506
  let content;
8179
8507
  try {
8180
- content = await readFile17(input.draftPath, "utf8");
8508
+ content = await readFile18(input.draftPath, "utf8");
8181
8509
  } catch (error) {
8182
8510
  return err("FILE_NOT_FOUND", { path: input.draftPath, message: String(error) });
8183
8511
  }
@@ -8310,9 +8638,9 @@ var PROJECT_PAGE_PUBLISH_STRATEGY = {
8310
8638
  async updateIndicesLocked(prepared, vault, state, observe) {
8311
8639
  const rendered = await renderProjectIndex(vault, prepared.page.project, { today: prepared.date });
8312
8640
  if (!rendered.ok) return rendered;
8313
- const knowledgePath = join33(vault, rendered.data.index_path);
8641
+ const knowledgePath = join34(vault, rendered.data.index_path);
8314
8642
  try {
8315
- await mkdir11(dirname10(knowledgePath), { recursive: true });
8643
+ await mkdir12(dirname10(knowledgePath), { recursive: true });
8316
8644
  } catch (error) {
8317
8645
  return err("WRITE_FAILED", { message: String(error) });
8318
8646
  }
@@ -8321,7 +8649,7 @@ var PROJECT_PAGE_PUBLISH_STRATEGY = {
8321
8649
  state.projectIndexUpdated = knowledgeWrite.data.changed;
8322
8650
  if (state.projectIndexUpdated) state.changed.add(rendered.data.index_path);
8323
8651
  try {
8324
- const visibleIndex = await readFile17(knowledgePath, "utf8");
8652
+ const visibleIndex = await readFile18(knowledgePath, "utf8");
8325
8653
  if (visibleIndex !== rendered.data.text) {
8326
8654
  return err("WRITE_FAILED", { message: "project knowledge.md verification failed" });
8327
8655
  }
@@ -8341,9 +8669,9 @@ var PROJECT_PAGE_PUBLISH_STRATEGY = {
8341
8669
  }
8342
8670
  let projectIndexChanged = true;
8343
8671
  if (projectIndex.ok) {
8344
- const indexPath = join33(vault, projectIndex.data.index_path);
8672
+ const indexPath = join34(vault, projectIndex.data.index_path);
8345
8673
  try {
8346
- const existing = await readFile17(indexPath, "utf8");
8674
+ const existing = await readFile18(indexPath, "utf8");
8347
8675
  projectIndexChanged = existing !== projectIndex.data.text || pageChanged;
8348
8676
  } catch {
8349
8677
  projectIndexChanged = true;
@@ -8500,10 +8828,10 @@ function runSyncJournalClearStale(input) {
8500
8828
 
8501
8829
  // src/commands/snapshot-maintenance.ts
8502
8830
  import { existsSync as existsSync11, writeFileSync as writeFileSync6, mkdirSync as mkdirSync4, appendFileSync } from "fs";
8503
- import { createHash as createHash5 } from "crypto";
8831
+ import { createHash as createHash6 } from "crypto";
8504
8832
  import { execSync as execSync3, spawn } from "child_process";
8505
8833
  import { platform as platform2 } from "os";
8506
- import { join as join34, resolve as resolvePath } from "path";
8834
+ import { join as join35, resolve as resolvePath } from "path";
8507
8835
  var MAINTENANCE_SCHEMA_VERSION = 1;
8508
8836
  var MAINTENANCE_COMMAND = "snapshot-maintenance journal clear-stale";
8509
8837
  var REPAIR_COMMAND = "snapshot-maintenance projection-conflict repair";
@@ -8527,7 +8855,7 @@ function computeApprovalId(plan) {
8527
8855
  targets,
8528
8856
  normalizeReason(plan.operator_reason)
8529
8857
  ].join("|");
8530
- return "smap1-" + createHash5("sha256").update(payload).digest("hex").slice(0, 32);
8858
+ return "smap1-" + createHash6("sha256").update(payload).digest("hex").slice(0, 32);
8531
8859
  }
8532
8860
  function normalizeReason(reason) {
8533
8861
  return reason.trim().replace(/\s+/g, " ");
@@ -8568,7 +8896,7 @@ async function runSnapshotMaintenanceDryRun(input) {
8568
8896
  audit(makeAuditEvent(input, fleetLoad.hostId, now, "refusal", "MAINTENANCE_WRONG_WORKTREE"));
8569
8897
  return { exitCode: ExitCode.PROTECTED_SNAPSHOTTER_WRITE_BLOCKED, result: refusalErr("MAINTENANCE_WRONG_WORKTREE", `requested path '${requested}' is not the configured snapshot worktree '${canonicalize(configuredWorktree)}'`) };
8570
8898
  }
8571
- if (!existsSync11(requested) || !existsSync11(join34(requested, ".git"))) {
8899
+ if (!existsSync11(requested) || !existsSync11(join35(requested, ".git"))) {
8572
8900
  audit(makeAuditEvent(input, fleetLoad.hostId, now, "refusal", "MAINTENANCE_MISSING_GIT_REPO"));
8573
8901
  return { exitCode: ExitCode.PROTECTED_SNAPSHOTTER_WRITE_BLOCKED, result: refusalErr("MAINTENANCE_MISSING_GIT_REPO", `snapshot worktree is not a git repository: ${requested}`) };
8574
8902
  }
@@ -8648,14 +8976,14 @@ async function resolveLiveVault(input) {
8648
8976
  return resolved.ok ? canonicalize(resolved.data.path) : void 0;
8649
8977
  }
8650
8978
  function snapshotMaintenanceAuditLogPath(home) {
8651
- const stateDir = platform2() === "darwin" ? join34(home, "Library", "Application Support", "vault-sync") : join34(home, ".local", "state", "vault-sync");
8652
- return join34(stateDir, "snapshot-maintenance-audit.jsonl");
8979
+ const stateDir = platform2() === "darwin" ? join35(home, "Library", "Application Support", "vault-sync") : join35(home, ".local", "state", "vault-sync");
8980
+ return join35(stateDir, "snapshot-maintenance-audit.jsonl");
8653
8981
  }
8654
8982
  function defaultAuditSink(home) {
8655
8983
  return (event) => {
8656
8984
  try {
8657
8985
  const logPath = snapshotMaintenanceAuditLogPath(home);
8658
- mkdirSync4(join34(logPath, ".."), { recursive: true });
8986
+ mkdirSync4(join35(logPath, ".."), { recursive: true });
8659
8987
  appendFileSync(logPath, JSON.stringify(event) + "\n", { encoding: "utf8" });
8660
8988
  } catch {
8661
8989
  }
@@ -8797,15 +9125,15 @@ function computeRepairApprovalId(plan) {
8797
9125
  paths,
8798
9126
  normalizeReason(plan.operator_reason)
8799
9127
  ].join("|");
8800
- return "smap1-" + createHash5("sha256").update(payload).digest("hex").slice(0, 32);
9128
+ return "smap1-" + createHash6("sha256").update(payload).digest("hex").slice(0, 32);
8801
9129
  }
8802
9130
  function nonMergeSequencerActive(repo) {
8803
9131
  const gitDir = git(repo, ["rev-parse", "--absolute-git-dir"]);
8804
9132
  if (!gitDir) return false;
8805
9133
  for (const m of ["CHERRY_PICK_HEAD", "REVERT_HEAD"]) {
8806
- if (existsSync11(join34(gitDir, m))) return true;
9134
+ if (existsSync11(join35(gitDir, m))) return true;
8807
9135
  }
8808
- return existsSync11(join34(gitDir, "rebase-merge")) || existsSync11(join34(gitDir, "rebase-apply"));
9136
+ return existsSync11(join35(gitDir, "rebase-merge")) || existsSync11(join35(gitDir, "rebase-apply"));
8809
9137
  }
8810
9138
  function extraUnmergedPaths(unmerged) {
8811
9139
  const allow = new Set(PROJECTION_CONFLICT_ALLOWLIST);
@@ -8844,7 +9172,7 @@ async function authorizeRepairContext(input) {
8844
9172
  audit(makeAuditEvent(input, fleetLoad.hostId, now, "refusal", "MAINTENANCE_WRONG_WORKTREE", void 0, REPAIR_COMMAND));
8845
9173
  return { exitCode: ExitCode.PROTECTED_SNAPSHOTTER_WRITE_BLOCKED, result: refusalErr("MAINTENANCE_WRONG_WORKTREE", `requested path '${requested}' is not the configured snapshot worktree '${canonicalize(configuredWorktree)}'`) };
8846
9174
  }
8847
- if (!existsSync11(requested) || !existsSync11(join34(requested, ".git"))) {
9175
+ if (!existsSync11(requested) || !existsSync11(join35(requested, ".git"))) {
8848
9176
  audit(makeAuditEvent(input, fleetLoad.hostId, now, "refusal", "MAINTENANCE_MISSING_GIT_REPO", void 0, REPAIR_COMMAND));
8849
9177
  return { exitCode: ExitCode.PROTECTED_SNAPSHOTTER_WRITE_BLOCKED, result: refusalErr("MAINTENANCE_MISSING_GIT_REPO", `snapshot worktree is not a git repository: ${requested}`) };
8850
9178
  }
@@ -8945,8 +9273,8 @@ async function performProjectionRepair(input, plan, audit, now) {
8945
9273
  return { exitCode: ExitCode.WRITE_FAILED, result: rendered };
8946
9274
  }
8947
9275
  try {
8948
- writeFileSync6(join34(plan.snapshot_worktree, "index.md"), rendered.data.index);
8949
- writeFileSync6(join34(plan.snapshot_worktree, "log.md"), rendered.data.log);
9276
+ writeFileSync6(join35(plan.snapshot_worktree, "index.md"), rendered.data.index);
9277
+ writeFileSync6(join35(plan.snapshot_worktree, "log.md"), rendered.data.log);
8950
9278
  gitStrict(plan.snapshot_worktree, ["add", "--", "index.md", "log.md"]);
8951
9279
  gitStrict(plan.snapshot_worktree, [
8952
9280
  "commit",
@@ -9054,7 +9382,7 @@ function releaseSnapshotFlock(handle) {
9054
9382
 
9055
9383
  // src/commands/backup.ts
9056
9384
  import { statSync as statSync3, readdirSync as readdirSync2, readFileSync as readFileSync15, mkdirSync as mkdirSync5, writeFileSync as writeFileSync7 } from "fs";
9057
- import { join as join35, relative as relative3, dirname as dirname11 } from "path";
9385
+ import { join as join36, relative as relative3, dirname as dirname11 } from "path";
9058
9386
  import { PutObjectCommand, HeadObjectCommand, ListObjectsV2Command, GetObjectCommand, DeleteObjectsCommand } from "@aws-sdk/client-s3";
9059
9387
 
9060
9388
  // src/utils/s3-client.ts
@@ -9078,7 +9406,7 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([".git", ".obsidian", "_archive", "node_
9078
9406
  function* walkMarkdown(dir, base) {
9079
9407
  for (const entry of readdirSync2(dir, { withFileTypes: true })) {
9080
9408
  if (SKIP_DIRS.has(entry.name)) continue;
9081
- const full = join35(dir, entry.name);
9409
+ const full = join36(dir, entry.name);
9082
9410
  if (entry.isDirectory()) {
9083
9411
  yield* walkMarkdown(full, base);
9084
9412
  } else if (entry.name.endsWith(".md")) {
@@ -9101,7 +9429,7 @@ async function runBackupSync(input) {
9101
9429
  let failed = 0;
9102
9430
  const files = [...walkMarkdown(input.vault, input.vault)];
9103
9431
  for (const relPath of files) {
9104
- const absPath = join35(input.vault, relPath);
9432
+ const absPath = join36(input.vault, relPath);
9105
9433
  const localStat = statSync3(absPath);
9106
9434
  let needsUpload = true;
9107
9435
  try {
@@ -9177,7 +9505,7 @@ async function runBackupRestore(input) {
9177
9505
  const objects = list.Contents ?? [];
9178
9506
  for (const obj of objects) {
9179
9507
  if (!obj.Key) continue;
9180
- const localPath = join35(target, obj.Key);
9508
+ const localPath = join36(target, obj.Key);
9181
9509
  try {
9182
9510
  const localStat = statSync3(localPath);
9183
9511
  if (obj.LastModified && localStat.mtime > obj.LastModified) {
@@ -9224,8 +9552,8 @@ async function runBackupRestore(input) {
9224
9552
 
9225
9553
  // src/commands/status.ts
9226
9554
  import { existsSync as existsSync12, statSync as statSync4 } from "fs";
9227
- import { readFile as readFile18 } from "fs/promises";
9228
- import { join as join36 } from "path";
9555
+ import { readFile as readFile19 } from "fs/promises";
9556
+ import { join as join37 } from "path";
9229
9557
  var FUSE_NO_MIRROR_NOTE = "FUSE vault with no read mirror - status scan may be slow";
9230
9558
  async function runStatus(input) {
9231
9559
  if (!existsSync12(input.vault)) {
@@ -9264,7 +9592,7 @@ async function runStatus(input) {
9264
9592
  const compound = scan.data.compound.length;
9265
9593
  let schemaVersion = "v1";
9266
9594
  try {
9267
- const schemaContent = await readFile18(join36(scanRoot, "SCHEMA.md"), "utf8");
9595
+ const schemaContent = await readFile19(join37(scanRoot, "SCHEMA.md"), "utf8");
9268
9596
  const versionMatch = schemaContent.match(/version:\s*["']?([^"'\s\n]+)/i);
9269
9597
  if (versionMatch) schemaVersion = versionMatch[1];
9270
9598
  } catch {
@@ -9332,13 +9660,13 @@ async function runStatus(input) {
9332
9660
 
9333
9661
  // src/commands/copy-status.ts
9334
9662
  import { existsSync as existsSync13, realpathSync as realpathSync5 } from "fs";
9335
- import { isAbsolute as isAbsolute2, join as join38, relative as relative5, resolve as resolve8 } from "path";
9663
+ import { isAbsolute as isAbsolute2, join as join39, relative as relative5, resolve as resolve8 } from "path";
9336
9664
 
9337
9665
  // src/utils/fetch-projection.ts
9338
- import { isAbsolute, join as join37, resolve as resolve7 } from "path";
9666
+ import { isAbsolute, join as join38, resolve as resolve7 } from "path";
9339
9667
  function inspectConfiguredFetchProjection(home) {
9340
9668
  if (!home) return { configured: false };
9341
- const config = readSkillWikiConfig(join37(home, ".skillwiki", ".env"));
9669
+ const config = readSkillWikiConfig(join38(home, ".skillwiki", ".env"));
9342
9670
  const configured = config["vault_sync.fetch_projection"];
9343
9671
  if (!configured || configured === "none") return { configured: false };
9344
9672
  if (!isAbsolute(configured)) {
@@ -9348,8 +9676,8 @@ function inspectConfiguredFetchProjection(home) {
9348
9676
  }
9349
9677
 
9350
9678
  // src/utils/live-drift.ts
9351
- import { createHash as createHash6 } from "crypto";
9352
- import { readFile as readFile19 } from "fs/promises";
9679
+ import { createHash as createHash7 } from "crypto";
9680
+ import { readFile as readFile20 } from "fs/promises";
9353
9681
  import { relative as relative4, sep as sep3 } from "path";
9354
9682
 
9355
9683
  // src/utils/git-promotion.ts
@@ -9370,7 +9698,7 @@ function isGitPromotablePath(path) {
9370
9698
  // src/utils/live-drift.ts
9371
9699
  function gitBlobOid(bytes) {
9372
9700
  const header = Buffer.from(`blob ${bytes.length}\0`);
9373
- return createHash6("sha1").update(header).update(bytes).digest("hex");
9701
+ return createHash7("sha1").update(header).update(bytes).digest("hex");
9374
9702
  }
9375
9703
  function headBlobOids(projection) {
9376
9704
  const output = git(projection, ["ls-tree", "-rz", "-r", "HEAD"]);
@@ -9394,7 +9722,7 @@ async function measureAuthoritativeLiveDrift(liveVault, projection) {
9394
9722
  const rel = relative4(liveVault, page.absPath).split(sep3).join("/");
9395
9723
  if (!isGitPromotablePath(rel)) continue;
9396
9724
  livePromotable.add(rel);
9397
- const bytes = await readFile19(page.absPath);
9725
+ const bytes = await readFile20(page.absPath);
9398
9726
  if (projectionBlobs.get(rel) !== gitBlobOid(bytes)) content += 1;
9399
9727
  }
9400
9728
  for (const rel of projectionBlobs.keys()) {
@@ -9572,7 +9900,7 @@ function defaultCopyStatusDeps(input) {
9572
9900
  const projectionWarning = selection.configured ? `configured fetch projection ignored for Git status; using live vault${projectionProblem ? ` (${projectionProblem})` : ""}` : void 0;
9573
9901
  const invalidProjectionDetail = projectionProblem ?? "configured fetch projection is not a git repository";
9574
9902
  const gitRootProblem = () => {
9575
- if (!existsSync13(join38(gitVault, ".git"))) {
9903
+ if (!existsSync13(join39(gitVault, ".git"))) {
9576
9904
  return "vault is not a git repository";
9577
9905
  }
9578
9906
  return void 0;
@@ -9640,7 +9968,7 @@ function defaultCopyStatusDeps(input) {
9640
9968
  if (!configuredProjection) {
9641
9969
  return { unknown: true, detail: projectionProblem ?? "live drift requires a configured fetch projection" };
9642
9970
  }
9643
- if (!existsSync13(join38(configuredProjection, ".git"))) {
9971
+ if (!existsSync13(join39(configuredProjection, ".git"))) {
9644
9972
  return { unknown: true, detail: invalidProjectionDetail };
9645
9973
  }
9646
9974
  return measureAuthoritativeLiveDrift(input.vault, configuredProjection);
@@ -9656,8 +9984,8 @@ async function runCopyStatusCommand(input) {
9656
9984
  }
9657
9985
 
9658
9986
  // src/commands/seed.ts
9659
- import { mkdir as mkdir12, writeFile as writeFile8, stat as stat4 } from "fs/promises";
9660
- import { join as join39 } from "path";
9987
+ import { mkdir as mkdir13, writeFile as writeFile8, stat as stat5 } from "fs/promises";
9988
+ import { join as join40 } from "path";
9661
9989
  var TODAY = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
9662
9990
  var EXAMPLE_PAGES = {
9663
9991
  "entities/example-project.md": `---
@@ -9726,29 +10054,29 @@ Real sources are immutable after ingestion \u2014 never edit them.
9726
10054
  `;
9727
10055
  async function runSeed(input) {
9728
10056
  try {
9729
- await stat4(join39(input.vault, "SCHEMA.md"));
10057
+ await stat5(join40(input.vault, "SCHEMA.md"));
9730
10058
  } catch {
9731
10059
  return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { root: input.vault, reason: "SCHEMA.md missing \u2014 run `skillwiki init` first" }) };
9732
10060
  }
9733
10061
  const created = [];
9734
10062
  const skipped = [];
9735
10063
  for (const [relPath, content] of Object.entries(EXAMPLE_PAGES)) {
9736
- const absPath = join39(input.vault, relPath);
10064
+ const absPath = join40(input.vault, relPath);
9737
10065
  try {
9738
- await stat4(absPath);
10066
+ await stat5(absPath);
9739
10067
  skipped.push(relPath);
9740
10068
  } catch {
9741
- await mkdir12(join39(absPath, ".."), { recursive: true });
10069
+ await mkdir13(join40(absPath, ".."), { recursive: true });
9742
10070
  await writeFile8(absPath, content, "utf8");
9743
10071
  created.push(relPath);
9744
10072
  }
9745
10073
  }
9746
- const rawPath = join39(input.vault, "raw", "articles", "example-source.md");
10074
+ const rawPath = join40(input.vault, "raw", "articles", "example-source.md");
9747
10075
  try {
9748
- await stat4(rawPath);
10076
+ await stat5(rawPath);
9749
10077
  skipped.push("raw/articles/example-source.md");
9750
10078
  } catch {
9751
- await mkdir12(join39(rawPath, ".."), { recursive: true });
10079
+ await mkdir13(join40(rawPath, ".."), { recursive: true });
9752
10080
  await writeFile8(rawPath, EXAMPLE_RAW, "utf8");
9753
10081
  created.push("raw/articles/example-source.md");
9754
10082
  }
@@ -9771,9 +10099,9 @@ async function runSeed(input) {
9771
10099
  }
9772
10100
 
9773
10101
  // src/commands/canvas.ts
9774
- import { readFile as readFile20, writeFile as writeFile9 } from "fs/promises";
10102
+ import { readFile as readFile21, writeFile as writeFile9 } from "fs/promises";
9775
10103
  import { existsSync as existsSync14 } from "fs";
9776
- import { join as join40 } from "path";
10104
+ import { join as join41 } from "path";
9777
10105
  var NODE_WIDTH = 240;
9778
10106
  var NODE_HEIGHT = 60;
9779
10107
  var COLUMN_SPACING = 400;
@@ -9851,7 +10179,7 @@ function buildCanvasEdges(adjacency) {
9851
10179
  return edges;
9852
10180
  }
9853
10181
  async function runCanvasGenerate(input) {
9854
- const graphPath = input.graphPath ?? join40(input.vault, ".skillwiki", "graph.json");
10182
+ const graphPath = input.graphPath ?? join41(input.vault, ".skillwiki", "graph.json");
9855
10183
  if (!existsSync14(graphPath)) {
9856
10184
  return {
9857
10185
  exitCode: ExitCode.FILE_NOT_FOUND,
@@ -9863,7 +10191,7 @@ async function runCanvasGenerate(input) {
9863
10191
  }
9864
10192
  let raw;
9865
10193
  try {
9866
- raw = await readFile20(graphPath, "utf8");
10194
+ raw = await readFile21(graphPath, "utf8");
9867
10195
  } catch (e) {
9868
10196
  return {
9869
10197
  exitCode: ExitCode.FILE_NOT_FOUND,
@@ -9889,7 +10217,7 @@ async function runCanvasGenerate(input) {
9889
10217
  const nodes = buildCanvasNodes(paths);
9890
10218
  const edges = buildCanvasEdges(graph.adjacency);
9891
10219
  const canvas = { nodes, edges };
9892
- const outPath = join40(input.vault, "vault-graph.canvas");
10220
+ const outPath = join41(input.vault, "vault-graph.canvas");
9893
10221
  try {
9894
10222
  await writeFile9(outPath, JSON.stringify(canvas, null, 2));
9895
10223
  } catch (e) {
@@ -10033,12 +10361,12 @@ async function runSourceDisposition(input) {
10033
10361
  }
10034
10362
 
10035
10363
  // src/utils/source-disposal.ts
10036
- import { createHash as createHash7 } from "crypto";
10037
- import { readFile as readFile22, unlink as unlink5 } from "fs/promises";
10364
+ import { createHash as createHash8 } from "crypto";
10365
+ import { readFile as readFile23, unlink as unlink5 } from "fs/promises";
10038
10366
 
10039
10367
  // src/utils/raw-asset-reference-index.ts
10040
- import { readdir as readdir7, readFile as readFile21 } from "fs/promises";
10041
- import { join as join41, relative as relative6, sep as sep4 } from "path";
10368
+ import { readdir as readdir7, readFile as readFile22 } from "fs/promises";
10369
+ import { join as join42, relative as relative6, sep as sep4 } from "path";
10042
10370
  var PREVIEW_EXTENSIONS = /* @__PURE__ */ new Set(["png", "jpg", "jpeg", "gif", "webp", "svg", "bmp", "avif", "pdf", "mp3", "wav", "m4a", "mp4", "webm"]);
10043
10371
  async function walkFiles(root, dir = root) {
10044
10372
  let entries;
@@ -10049,7 +10377,7 @@ async function walkFiles(root, dir = root) {
10049
10377
  }
10050
10378
  const out = [];
10051
10379
  for (const entry of entries) {
10052
- const path = join41(dir, entry.name);
10380
+ const path = join42(dir, entry.name);
10053
10381
  if (entry.isDirectory()) out.push(...await walkFiles(root, path));
10054
10382
  else if (entry.isFile()) out.push(relative6(root, path).split(sep4).join("/"));
10055
10383
  }
@@ -10065,7 +10393,7 @@ function addInbound(map, target, source) {
10065
10393
  map.set(target, refs);
10066
10394
  }
10067
10395
  async function buildRawAssetReferenceIndex(vault) {
10068
- const assetRoot = join41(vault, "raw", "assets");
10396
+ const assetRoot = join42(vault, "raw", "assets");
10069
10397
  const relativeAssets = await walkFiles(assetRoot);
10070
10398
  const assets = relativeAssets.map((path) => `raw/assets/${path}`).sort((a, b) => a.localeCompare(b));
10071
10399
  const assetSet = new Set(assets);
@@ -10081,7 +10409,7 @@ async function buildRawAssetReferenceIndex(vault) {
10081
10409
  for (const page of scan.data.allMarkdown) {
10082
10410
  let text;
10083
10411
  try {
10084
- text = await readFile21(page.absPath, "utf8");
10412
+ text = await readFile22(page.absPath, "utf8");
10085
10413
  } catch {
10086
10414
  continue;
10087
10415
  }
@@ -10117,7 +10445,7 @@ async function buildRawAssetReferenceIndex(vault) {
10117
10445
 
10118
10446
  // src/utils/source-disposal.ts
10119
10447
  function sha256(bytes) {
10120
- return createHash7("sha256").update(bytes).digest("hex");
10448
+ return createHash8("sha256").update(bytes).digest("hex");
10121
10449
  }
10122
10450
  function exactTarget(path) {
10123
10451
  if (/[?*\[\]{}]/.test(path) || !path.includes("/") || path.endsWith("/")) return err("RAW_DISPOSAL_TARGET_INVALID", { path, message: "exact file path required; globs, basenames, and directories are refused" });
@@ -10141,7 +10469,7 @@ async function planSourceDisposal(input) {
10141
10469
  if (sensitive.length > 0) return err("SENSITIVE_CONTENT_DETECTED", { findings: sensitive });
10142
10470
  const target = await resolveExistingRegularFileInsideVault(input.vault, input.rawPath);
10143
10471
  if (!target.ok) return err("RAW_DISPOSAL_TARGET_INVALID", { path: input.rawPath, cause: target });
10144
- const bytes = await readFile22(target.data);
10472
+ const bytes = await readFile23(target.data);
10145
10473
  const size = bytes.byteLength;
10146
10474
  let typedReferences = [];
10147
10475
  let otherReferences = [];
@@ -10265,7 +10593,7 @@ async function runSourceDisposal(input) {
10265
10593
  import { existsSync as existsSync15, readFileSync as readFileSync16 } from "fs";
10266
10594
  import { execSync as nodeExecSync } from "child_process";
10267
10595
  import { hostname as nodeHostname, platform as nodePlatform } from "os";
10268
- import { join as join42 } from "path";
10596
+ import { join as join43 } from "path";
10269
10597
  var SSH_TIMEOUT_MS = 15e3;
10270
10598
  var TIMER_UNIT = "agent-memory-trends.timer";
10271
10599
  var SERVICE_UNIT = "agent-memory-trends.service";
@@ -10495,7 +10823,7 @@ async function runFleetHealth(input) {
10495
10823
  const home = input.home ?? env.HOME ?? "";
10496
10824
  const osHostname = input.osHostname ?? env.HOSTNAME ?? nodeHostname();
10497
10825
  const vault = input.vault ?? env.WIKI_PATH;
10498
- const file = input.file ?? (vault ? join42(vault, FLEET_REL_PATH) : void 0);
10826
+ const file = input.file ?? (vault ? join43(vault, FLEET_REL_PATH) : void 0);
10499
10827
  if (!file) {
10500
10828
  return {
10501
10829
  exitCode: ExitCode.NO_VAULT_CONFIGURED,
@@ -10580,7 +10908,7 @@ async function runFleetHealth(input) {
10580
10908
 
10581
10909
  // src/utils/auto-commit.ts
10582
10910
  import { existsSync as existsSync16 } from "fs";
10583
- import { isAbsolute as isAbsolute3, join as join43, normalize } from "path";
10911
+ import { isAbsolute as isAbsolute3, join as join44, normalize } from "path";
10584
10912
  function isValidLastOpPath(file) {
10585
10913
  if (!file || typeof file !== "string") return false;
10586
10914
  const trimmed = file.trim();
@@ -10601,7 +10929,7 @@ async function postCommit(vault, exitCode) {
10601
10929
  const dotenv = await parseDotenvFile(configPath(home));
10602
10930
  const autoCommit = process.env.AUTO_COMMIT ?? dotenv["AUTO_COMMIT"];
10603
10931
  if (autoCommit === "false") return;
10604
- if (!existsSync16(join43(vault, ".git"))) return;
10932
+ if (!existsSync16(join44(vault, ".git"))) return;
10605
10933
  const lastOps = readLastOp(vault);
10606
10934
  if (lastOps.length === 0) return;
10607
10935
  const porcelain = git(vault, ["status", "--porcelain", "--", ...VAULT_COMMIT_PATHSPEC]);
@@ -10641,53 +10969,6 @@ async function postCommit(vault, exitCode) {
10641
10969
 
10642
10970
  // src/commands/mcp-auth.ts
10643
10971
  import { readFileSync as readFileSync17, writeFileSync as writeFileSync8 } from "fs";
10644
-
10645
- // src/utils/mcp-token-map.ts
10646
- import { createHash as createHash8, randomBytes } from "crypto";
10647
- import yaml from "js-yaml";
10648
- var HOST_ID_RE = /^[a-z][a-z0-9-]{1,62}$/;
10649
- function parseMcpTokenMap(yamlText) {
10650
- const parsed = yaml.load(yamlText);
10651
- const map = /* @__PURE__ */ new Map();
10652
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
10653
- return map;
10654
- }
10655
- const records = parsed;
10656
- const nested = records.tokens;
10657
- const source = nested && typeof nested === "object" && !Array.isArray(nested) ? nested : records;
10658
- for (const [hash, hostId] of Object.entries(source)) {
10659
- if (hash === "tokens") continue;
10660
- if (typeof hostId !== "string" || hostId.length === 0) continue;
10661
- const normalized = hash.trim().toLowerCase();
10662
- if (!/^[0-9a-f]{64}$/.test(normalized)) continue;
10663
- map.set(normalized, hostId);
10664
- }
10665
- return map;
10666
- }
10667
- function generateHostBearer(rng) {
10668
- const bytes = (rng ?? (() => randomBytes(32)))();
10669
- const raw = bytes.toString("base64url");
10670
- const hashHex = createHash8("sha256").update(raw, "utf8").digest("hex");
10671
- return { raw, hashHex };
10672
- }
10673
- function appendHostHash(yamlText, hashHex, hostId) {
10674
- if (!HOST_ID_RE.test(hostId)) {
10675
- return { error: "INVALID_HOST_ID" };
10676
- }
10677
- const map = parseMcpTokenMap(yamlText);
10678
- const normalizedHash = hashHex.trim().toLowerCase();
10679
- if ([...map.values()].includes(hostId)) {
10680
- return { error: "DUPLICATE_HOST_ID" };
10681
- }
10682
- if (map.has(normalizedHash)) {
10683
- return { error: "DUPLICATE_HASH" };
10684
- }
10685
- return { yaml: yamlText.replace(/\s*$/, "") + `
10686
- ${normalizedHash}: ${hostId}
10687
- ` };
10688
- }
10689
-
10690
- // src/commands/mcp-auth.ts
10691
10972
  function isEnoent(error) {
10692
10973
  return error.code === "ENOENT";
10693
10974
  }
@@ -10696,7 +10977,7 @@ function preflight(error) {
10696
10977
  }
10697
10978
  async function runMcpAuthIssueHost(input) {
10698
10979
  const isTty = input.isTty ?? Boolean(process.stdin.isTTY && process.stdout.isTTY);
10699
- const readFile23 = input.readFile ?? readFileSync17;
10980
+ const readFile24 = input.readFile ?? readFileSync17;
10700
10981
  const writeFile10 = input.writeFile ?? writeFileSync8;
10701
10982
  const stderrWrite = input.stderrWrite ?? ((s) => {
10702
10983
  process.stderr.write(s);
@@ -10706,10 +10987,12 @@ async function runMcpAuthIssueHost(input) {
10706
10987
  const hostId = input.hostId;
10707
10988
  if (!mapPath) return preflight("MAP_PATH_REQUIRED");
10708
10989
  if (!HOST_ID_RE.test(hostId)) return preflight("INVALID_HOST_ID");
10990
+ const granted = parseAllowedVaultIds(input.allowedVaults);
10991
+ if ("error" in granted) return preflight(granted.error);
10709
10992
  if (write && !isTty) return preflight("NO_TTY");
10710
10993
  let yamlText = "";
10711
10994
  try {
10712
- yamlText = readFile23(mapPath, "utf8");
10995
+ yamlText = readFile24(mapPath, "utf8");
10713
10996
  } catch (error) {
10714
10997
  if (!isEnoent(error)) throw error;
10715
10998
  }
@@ -10722,12 +11005,13 @@ async function runMcpAuthIssueHost(input) {
10722
11005
  host_id: hostId,
10723
11006
  hash_prefix: "",
10724
11007
  map_path: mapPath,
10725
- wrote: false
11008
+ wrote: false,
11009
+ allowed_vaults: granted.vaults
10726
11010
  })
10727
11011
  };
10728
11012
  }
10729
11013
  const { raw, hashHex } = generateHostBearer(input.rng);
10730
- const appended = appendHostHash(yamlText, hashHex, hostId);
11014
+ const appended = appendHostHash(yamlText, hashHex, hostId, granted.vaults);
10731
11015
  if ("error" in appended) return preflight(appended.error);
10732
11016
  try {
10733
11017
  writeFile10(mapPath, appended.yaml, { encoding: "utf8", mode: 416 });
@@ -10737,7 +11021,8 @@ async function runMcpAuthIssueHost(input) {
10737
11021
  }
10738
11022
  throw error;
10739
11023
  }
10740
- stderrWrite(`issued host_id=${hostId} copy once: ${raw}
11024
+ const grantNote = granted.vaults.length > 0 ? ` allowed_vaults=${granted.vaults.join(",")}` : "";
11025
+ stderrWrite(`issued host_id=${hostId}${grantNote} copy once: ${raw}
10741
11026
  `);
10742
11027
  return {
10743
11028
  exitCode: ExitCode.OK,
@@ -10745,7 +11030,8 @@ async function runMcpAuthIssueHost(input) {
10745
11030
  host_id: hostId,
10746
11031
  hash_prefix: hashHex.slice(0, 8),
10747
11032
  map_path: mapPath,
10748
- wrote: true
11033
+ wrote: true,
11034
+ allowed_vaults: granted.vaults
10749
11035
  })
10750
11036
  };
10751
11037
  }
@@ -10888,7 +11174,7 @@ async function emitManagedVaultWrite(vault, command, mutate, opts) {
10888
11174
  if (dirty) {
10889
11175
  return emit(dirty, void 0, { postCommit: false });
10890
11176
  }
10891
- const { runManagedWriteTransaction: runManagedWriteTransaction2 } = await import("./managed-write-preflight-THGIM2U7.js");
11177
+ const { runManagedWriteTransaction: runManagedWriteTransaction2 } = await import("./managed-write-preflight-NS7OQ2Y7.js");
10892
11178
  const run = await runManagedWriteTransaction2({
10893
11179
  vault,
10894
11180
  command,
@@ -10918,7 +11204,7 @@ program.command("validate <file>").description("validate vault page frontmatter
10918
11204
  emit(await runValidate({ file, apply: !!opts.apply, vault }), vault);
10919
11205
  });
10920
11206
  program.command("graph").description("graph subcommands").command("build <vault>").option("--out <path>", "graph output path (default: <vault>/.skillwiki/graph.json)").option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
10921
- const out = opts.out ?? join44(vault, ".skillwiki", "graph.json");
11207
+ const out = opts.out ?? join45(vault, ".skillwiki", "graph.json");
10922
11208
  return emitGuardedVaultWrite(vault, "graph build", () => runGraphBuild({ vault, out }));
10923
11209
  });
10924
11210
  var canvasCmd = program.command("canvas").description("manage Obsidian canvas files");
@@ -11529,7 +11815,18 @@ program.command("doctor").description("diagnose skillwiki setup issues").option(
11529
11815
  currentVersion: pkg.version,
11530
11816
  cwd: process.cwd(),
11531
11817
  checkSnapshotter: !!opts.checkSnapshotter,
11532
- checkMcp: !!opts.checkMcp
11818
+ checkMcp: !!opts.checkMcp,
11819
+ env: process.env
11820
+ })));
11821
+ program.command("connect").description(CONNECT_DESCRIPTION).option("--from-file <path>", "chat-attached env file (not Drive / \u4E91\u76D8)").option("--from-stdin", "read env file from stdin (no echo)").option("--dry-run", "validate without writing ~/.skillwiki/.env").option("--force", "overwrite a different existing token").action(async (opts) => emit(await runConnect({
11822
+ home: process.env.HOME ?? "",
11823
+ fromFile: opts.fromFile,
11824
+ fromStdin: !!opts.fromStdin,
11825
+ dryRun: !!opts.dryRun,
11826
+ force: !!opts.force,
11827
+ checkMcp: !opts.dryRun,
11828
+ currentVersion: pkg.version,
11829
+ env: process.env
11533
11830
  })));
11534
11831
  program.command("status [vault]").description("output vault diagnostics").option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
11535
11832
  const v = await resolveVaultArg(vault, opts.wiki);
@@ -12123,12 +12420,14 @@ program.command("mcp").description("start stdio Model Context Protocol server (r
12123
12420
  await runSkillwikiMcpStdio();
12124
12421
  });
12125
12422
  var mcpAuthCmd = program.command("mcp-auth").description("attended HTTP MCP host-id bearer issuance (metal)");
12126
- mcpAuthCmd.command("issue-host").description("append a host-id hash to the metal map; print raw once on a TTY").requiredOption("--host-id <id>", "host identity to bind").option("--map <path>", "hash map file (defaults to SKILLWIKI_MCP_TOKEN_MAP)").option("--write", "append the hash (default is dry-run)", false).action(async (opts) => {
12423
+ mcpAuthCmd.command("issue-host").description("append a host-id hash to the metal map; print raw once on a TTY").requiredOption("--host-id <id>", "host identity to bind").option("--map <path>", "hash map file (defaults to SKILLWIKI_MCP_TOKEN_MAP)").option("--allowed-vaults <ids>", "comma-separated exact vault ids; omit for default vault only").option("--write", "append the hash (default is dry-run)", false).action(async (opts) => {
12127
12424
  const mapPath = opts.map || process.env.SKILLWIKI_MCP_TOKEN_MAP || "";
12425
+ const allowedVaults = String(opts.allowedVaults ?? "").split(/[,\s]+/).map((id) => id.trim()).filter(Boolean);
12128
12426
  emit(await runMcpAuthIssueHost({
12129
12427
  hostId: String(opts.hostId),
12130
12428
  mapPath,
12131
- write: !!opts.write
12429
+ write: !!opts.write,
12430
+ allowedVaults
12132
12431
  }));
12133
12432
  });
12134
12433
  for (const w of getDeprecatedWarnings(process.env.HOME ?? "")) {