skillwiki 0.10.100 → 0.10.103

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,
@@ -12,17 +16,21 @@ import {
12
16
  countEventLedgerFiles,
13
17
  detectFuseMount,
14
18
  evaluateDirtyVolumeGate,
15
- evaluateSatelliteRunHealth,
19
+ evaluateSatelliteRunHealthState,
16
20
  findPlugin,
17
21
  guardProtectedVaultWrite,
18
22
  isEventLedgerPath,
19
23
  isFailedRunStatus,
20
24
  measureDirtyVolume,
21
25
  memoryAuthorityTiersRank,
26
+ mergeMcpAuthIntoEnv,
27
+ nonEmptyEnvValue,
22
28
  pruneVectorIndex,
23
29
  readCliPackageJson,
30
+ readSatelliteLatestRunAt,
24
31
  readSatelliteLatestRunFromText,
25
32
  readVaultSyncConfig,
33
+ redactMcpSecret,
26
34
  reindexPageInVectorIndex,
27
35
  renderIndexUpsert,
28
36
  renderProjectIndex,
@@ -55,7 +63,7 @@ import {
55
63
  snapshotterHealthChecks,
56
64
  upsertIndexEntry,
57
65
  vectorIndexStatus
58
- } from "./chunk-3QT3OFAL.js";
66
+ } from "./chunk-2BPOEEMW.js";
59
67
  import {
60
68
  normalizeDistTag,
61
69
  readCache,
@@ -160,7 +168,7 @@ import {
160
168
  supersedeStaleReviewRequiredJournals,
161
169
  taxonomyCommentForPage,
162
170
  writeDotenv
163
- } from "./chunk-B4QTKCEK.js";
171
+ } from "./chunk-PVIOU7BN.js";
164
172
  import {
165
173
  assertTargetInsideVault,
166
174
  atomicWriteText,
@@ -214,7 +222,7 @@ import {
214
222
  } from "./chunk-QM64LUEY.js";
215
223
 
216
224
  // src/cli.ts
217
- import { join as join44 } from "path";
225
+ import { join as join45 } from "path";
218
226
  import { Command } from "commander";
219
227
 
220
228
  // src/utils/output.ts
@@ -3576,13 +3584,334 @@ async function runHealth(input) {
3576
3584
  return { exitCode, result: ok(report) };
3577
3585
  }
3578
3586
 
3587
+ // src/commands/connect.ts
3588
+ import { chmod, mkdir as mkdir5, readFile as readFile6, stat as stat3 } from "fs/promises";
3589
+ import { join as join18 } from "path";
3590
+
3591
+ // src/utils/mcp-token-map.ts
3592
+ import { createHash as createHash2, randomBytes } from "crypto";
3593
+ import yaml from "js-yaml";
3594
+ var HOST_ID_RE = /^[a-z][a-z0-9-]{1,62}$/;
3595
+ function writerIdFromValue(value) {
3596
+ if (typeof value === "string" && value.length > 0) return value;
3597
+ if (value && typeof value === "object" && !Array.isArray(value)) {
3598
+ const rec = value;
3599
+ if (typeof rec.writer_id === "string" && rec.writer_id.length > 0) return rec.writer_id;
3600
+ if (typeof rec.host_id === "string" && rec.host_id.length > 0) return rec.host_id;
3601
+ }
3602
+ return void 0;
3603
+ }
3604
+ function parseAllowedVaultIds(raw) {
3605
+ if (!raw || raw.length === 0) return { vaults: [] };
3606
+ const vaults = [];
3607
+ const seen = /* @__PURE__ */ new Set();
3608
+ for (const item of raw) {
3609
+ const id = item.trim();
3610
+ if (!id) continue;
3611
+ if (id.includes("*") || id.includes("/") || !HOST_ID_RE.test(id)) {
3612
+ return { error: "INVALID_ALLOWED_VAULTS" };
3613
+ }
3614
+ if (seen.has(id)) continue;
3615
+ seen.add(id);
3616
+ vaults.push(id);
3617
+ }
3618
+ return { vaults };
3619
+ }
3620
+ function parseMcpTokenMap(yamlText) {
3621
+ const parsed = yaml.load(yamlText);
3622
+ const map = /* @__PURE__ */ new Map();
3623
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
3624
+ return map;
3625
+ }
3626
+ const records = parsed;
3627
+ const nested = records.tokens;
3628
+ const source = nested && typeof nested === "object" && !Array.isArray(nested) ? nested : records;
3629
+ for (const [hash, value] of Object.entries(source)) {
3630
+ if (hash === "tokens") continue;
3631
+ const writerId = writerIdFromValue(value);
3632
+ if (!writerId) continue;
3633
+ const normalized = hash.trim().toLowerCase();
3634
+ if (!/^[0-9a-f]{64}$/.test(normalized)) continue;
3635
+ map.set(normalized, writerId);
3636
+ }
3637
+ return map;
3638
+ }
3639
+ function generateHostBearer(rng) {
3640
+ const bytes = (rng ?? (() => randomBytes(32)))();
3641
+ const raw = bytes.toString("base64url");
3642
+ const hashHex = createHash2("sha256").update(raw, "utf8").digest("hex");
3643
+ return { raw, hashHex };
3644
+ }
3645
+ function formatHostRow(hashHex, hostId, allowedVaults) {
3646
+ if (allowedVaults.length === 0) {
3647
+ return `${hashHex}: ${hostId}
3648
+ `;
3649
+ }
3650
+ return `${hashHex}:
3651
+ writer_id: ${hostId}
3652
+ allowed_vaults: [${allowedVaults.join(", ")}]
3653
+ `;
3654
+ }
3655
+ function appendHostHash(yamlText, hashHex, hostId, allowedVaults) {
3656
+ if (!HOST_ID_RE.test(hostId)) {
3657
+ return { error: "INVALID_HOST_ID" };
3658
+ }
3659
+ const granted = parseAllowedVaultIds(allowedVaults);
3660
+ if ("error" in granted) return granted;
3661
+ const map = parseMcpTokenMap(yamlText);
3662
+ const normalizedHash = hashHex.trim().toLowerCase();
3663
+ if ([...map.values()].includes(hostId)) {
3664
+ return { error: "DUPLICATE_HOST_ID" };
3665
+ }
3666
+ if (map.has(normalizedHash)) {
3667
+ return { error: "DUPLICATE_HASH" };
3668
+ }
3669
+ return { yaml: yamlText.replace(/\s*$/, "") + `
3670
+ ${formatHostRow(normalizedHash, hostId, granted.vaults)}` };
3671
+ }
3672
+
3673
+ // src/commands/connect.ts
3674
+ 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.";
3675
+ var RESERVED_HOST_IDS = ["msi", "doubao-linux-agent", "macos-dev"];
3676
+ var DEST_MODE = 384;
3677
+ function isUnsupportedSecretPath(filePath) {
3678
+ const normalized = filePath.replace(/\\/g, "/");
3679
+ return /(?:^|\/)(?:Google Drive|OneDrive|iCloud Drive|CloudStorage|云盘|百度网盘|坚果云|Dropbox)(?:\/|$)/i.test(normalized) || /clouddrive|cloud-disk/i.test(normalized);
3680
+ }
3681
+ function fail(code, error, detail) {
3682
+ return { exitCode: code, result: err(error, detail) };
3683
+ }
3684
+ async function defaultReadStdin() {
3685
+ const chunks = [];
3686
+ for await (const chunk of process.stdin) {
3687
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
3688
+ }
3689
+ return Buffer.concat(chunks).toString("utf8");
3690
+ }
3691
+ function looksLikeVaultInit(text) {
3692
+ return /^#\s*Vault Schema/im.test(text) || /^\s*---\s*$/m.test(text) && /taxonomy:/i.test(text);
3693
+ }
3694
+ function tokenPresence(token) {
3695
+ return token ? "TOKEN_SET" : "EMPTY";
3696
+ }
3697
+ function summaryHint(data) {
3698
+ return [
3699
+ data.token,
3700
+ `auth=${data.auth}`,
3701
+ `writer_id=${data.writer_id ?? "n/a"}`,
3702
+ `ok=${data.ok ?? "n/a"}`,
3703
+ `reconcile_ready=${data.reconcile_ready ?? "n/a"}`,
3704
+ data.dry_run ? "dry-run" : data.written ? "wrote ~/.skillwiki/.env 0600" : "not written"
3705
+ ].join(" ");
3706
+ }
3707
+ function redactConnectPayload(data, secret) {
3708
+ return {
3709
+ ...data,
3710
+ handshake: data.handshake ? redactMcpSecret(data.handshake, secret) : data.handshake,
3711
+ humanHint: redactMcpSecret(data.humanHint, secret)
3712
+ };
3713
+ }
3714
+ async function readWikiStatus(fetchFn, url, token) {
3715
+ const headers = {
3716
+ Authorization: `Bearer ${token}`,
3717
+ "Content-Type": "application/json",
3718
+ Accept: "application/json, text/event-stream"
3719
+ };
3720
+ try {
3721
+ const initRes = await fetchFn(url, {
3722
+ method: "POST",
3723
+ headers,
3724
+ body: JSON.stringify({
3725
+ jsonrpc: "2.0",
3726
+ id: 1,
3727
+ method: "initialize",
3728
+ params: {
3729
+ protocolVersion: "2025-11-25",
3730
+ capabilities: {},
3731
+ clientInfo: { name: "skillwiki-connect", version: "0" }
3732
+ }
3733
+ })
3734
+ });
3735
+ const session = initRes.headers.get("mcp-session-id");
3736
+ if (session) headers["mcp-session-id"] = session;
3737
+ const statusRes = await fetchFn(url, {
3738
+ method: "POST",
3739
+ headers,
3740
+ body: JSON.stringify({
3741
+ jsonrpc: "2.0",
3742
+ id: 2,
3743
+ method: "tools/call",
3744
+ params: { name: "wiki_status", arguments: {} }
3745
+ })
3746
+ });
3747
+ const raw = await statusRes.text();
3748
+ const parsed = JSON.parse(raw.startsWith("data:") ? raw.replace(/^data:\s*/m, "") : raw);
3749
+ const structured = parsed.result?.structuredContent;
3750
+ let body = structured;
3751
+ if (!body) {
3752
+ const text = parsed.result?.content?.find((c) => typeof c.text === "string")?.text;
3753
+ if (text) {
3754
+ try {
3755
+ body = JSON.parse(text);
3756
+ } catch {
3757
+ body = void 0;
3758
+ }
3759
+ }
3760
+ }
3761
+ if (!body) return { writer_id: null, ok: null, reconcile_ready: null };
3762
+ return {
3763
+ writer_id: typeof body.writer_id === "string" ? body.writer_id : null,
3764
+ ok: typeof body.ok === "boolean" ? body.ok : null,
3765
+ reconcile_ready: typeof body.reconcile_ready === "boolean" ? body.reconcile_ready : null
3766
+ };
3767
+ } catch {
3768
+ return { writer_id: null, ok: null, reconcile_ready: null };
3769
+ }
3770
+ }
3771
+ async function runConnect(input) {
3772
+ const dest = join18(input.home, ".skillwiki", ".env");
3773
+ const fromFile = input.fromFile?.trim();
3774
+ const fromStdin = Boolean(input.fromStdin);
3775
+ if (fromFile && fromStdin) {
3776
+ return fail(ExitCode.USAGE, "USAGE", { reason: "pass --from-file or --from-stdin, not both" });
3777
+ }
3778
+ if (!fromFile && !fromStdin) {
3779
+ return fail(ExitCode.USAGE, "USAGE", {
3780
+ reason: "pass --from-file <chat-attachment> or --from-stdin (Cloud Drive / \u4E91\u76D8 unsupported)"
3781
+ });
3782
+ }
3783
+ if (fromFile && isUnsupportedSecretPath(fromFile)) {
3784
+ return fail(ExitCode.PREFLIGHT_FAILED, "DRIVE_UNSUPPORTED", {
3785
+ reason: "Cloud Drive / \u4E91\u76D8 is not a secret path. Attach the env file in chat, then skillwiki connect --from-file <attachment>."
3786
+ });
3787
+ }
3788
+ let raw;
3789
+ try {
3790
+ raw = fromStdin ? await (input.readStdin ?? defaultReadStdin)() : await readFile6(fromFile, "utf8");
3791
+ } catch (e) {
3792
+ const code = e.code;
3793
+ if (code === "ENOENT" || code === "ENOTDIR") {
3794
+ return fail(ExitCode.FILE_NOT_FOUND, "FILE_NOT_FOUND", { path: fromFile });
3795
+ }
3796
+ if (code === "EISDIR") {
3797
+ return fail(ExitCode.PREFLIGHT_FAILED, "NOT_ENV_FILE", { reason: "source is a directory, not an env file" });
3798
+ }
3799
+ return fail(ExitCode.PREFLIGHT_FAILED, "READ_FAILED", { reason: "could not read env source" });
3800
+ }
3801
+ if (looksLikeVaultInit(raw)) {
3802
+ return fail(ExitCode.PREFLIGHT_FAILED, "REFUSE_INIT", {
3803
+ reason: "source looks like a vault init; skillwiki connect never runs skillwiki init"
3804
+ });
3805
+ }
3806
+ const parsed = parseDotenvText(raw);
3807
+ const incomingToken = nonEmptyEnvValue(parsed[MCP_TOKEN_ENV]);
3808
+ const incomingHost = nonEmptyEnvValue(parsed[MCP_HOST_ID_ENV]);
3809
+ const incomingUrl = nonEmptyEnvValue(parsed[MCP_URL_ENV]);
3810
+ if (!incomingToken) {
3811
+ return fail(ExitCode.INVALID_CONFIG_VALUE, "TOKEN_EMPTY", {
3812
+ reason: "SKILLWIKI_MCP_TOKEN missing or empty (value not shown)"
3813
+ });
3814
+ }
3815
+ if (!incomingHost) {
3816
+ return fail(ExitCode.INVALID_CONFIG_VALUE, "HOST_ID_REQUIRED", {
3817
+ reason: "SKILLWIKI_HOST_ID missing; each machine needs a unique host-id"
3818
+ });
3819
+ }
3820
+ if (!HOST_ID_RE.test(incomingHost)) {
3821
+ return fail(ExitCode.PREFLIGHT_FAILED, "INVALID_HOST_ID", { reason: "host-id must match [a-z][a-z0-9-]{1,62}" });
3822
+ }
3823
+ if (RESERVED_HOST_IDS.includes(incomingHost) && !input.force) {
3824
+ return fail(ExitCode.PREFLIGHT_FAILED, "RESERVED_HOST_ID", {
3825
+ reason: `refuse copying reserved host-id ${incomingHost}; issue a unique id on metal or pass --force`
3826
+ });
3827
+ }
3828
+ const existing = await parseDotenvFile(dest);
3829
+ const existingToken = nonEmptyEnvValue(existing[MCP_TOKEN_ENV]);
3830
+ if (existingToken && existingToken !== incomingToken && !input.force) {
3831
+ return fail(ExitCode.ENV_WRITE_CONFLICT, "ENV_WRITE_CONFLICT", {
3832
+ reason: "existing ~/.skillwiki/.env has a different token; pass --force to overwrite (value not shown)"
3833
+ });
3834
+ }
3835
+ const entries = {
3836
+ [MCP_TOKEN_ENV]: incomingToken,
3837
+ [MCP_HOST_ID_ENV]: incomingHost
3838
+ };
3839
+ if (incomingUrl) entries[MCP_URL_ENV] = incomingUrl;
3840
+ const dryRun = Boolean(input.dryRun);
3841
+ if (!dryRun) {
3842
+ await mkdir5(join18(input.home, ".skillwiki"), { recursive: true });
3843
+ let original;
3844
+ try {
3845
+ original = await readFile6(dest, "utf8");
3846
+ } catch {
3847
+ original = void 0;
3848
+ }
3849
+ await writeDotenv(dest, entries, original);
3850
+ await chmod(dest, DEST_MODE);
3851
+ try {
3852
+ const st = await stat3(dest);
3853
+ if ((st.mode & 511) !== DEST_MODE) await chmod(dest, DEST_MODE);
3854
+ } catch {
3855
+ }
3856
+ }
3857
+ let writerId = null;
3858
+ let statusOk = null;
3859
+ let reconcileReady = null;
3860
+ let handshake = null;
3861
+ let doctorExit = ExitCode.OK;
3862
+ const checkMcp = input.checkMcp ?? !dryRun;
3863
+ if (checkMcp) {
3864
+ const env = await mergeMcpAuthIntoEnv(input.home, input.env ?? {});
3865
+ if (!nonEmptyEnvValue(env[MCP_TOKEN_ENV])) env[MCP_TOKEN_ENV] = incomingToken;
3866
+ if (!nonEmptyEnvValue(env[MCP_HOST_ID_ENV])) env[MCP_HOST_ID_ENV] = incomingHost;
3867
+ if (incomingUrl && !nonEmptyEnvValue(env[MCP_URL_ENV])) env[MCP_URL_ENV] = incomingUrl;
3868
+ const doctor = await runDoctor({
3869
+ home: input.home,
3870
+ envValue: void 0,
3871
+ argv: ["node", "skillwiki", "connect"],
3872
+ currentVersion: input.currentVersion,
3873
+ checkMcp: true,
3874
+ env,
3875
+ mcpFetch: input.mcpFetch
3876
+ });
3877
+ doctorExit = doctor.exitCode;
3878
+ if (doctor.result.ok) {
3879
+ handshake = doctor.result.data.checks.find((c) => c.id === "mcp_handshake")?.detail ?? null;
3880
+ }
3881
+ const url = incomingUrl ?? DEFAULT_MCP_URL;
3882
+ const status = await readWikiStatus(input.mcpFetch ?? globalThis.fetch, url, incomingToken);
3883
+ writerId = status.writer_id;
3884
+ statusOk = status.ok;
3885
+ reconcileReady = status.reconcile_ready;
3886
+ }
3887
+ const payload = {
3888
+ dest,
3889
+ written: !dryRun,
3890
+ dry_run: dryRun,
3891
+ mode: "0600",
3892
+ token: tokenPresence(incomingToken),
3893
+ auth: incomingToken ? "present" : "not set",
3894
+ writer_id: writerId,
3895
+ ok: statusOk,
3896
+ reconcile_ready: reconcileReady,
3897
+ handshake,
3898
+ humanHint: ""
3899
+ };
3900
+ payload.humanHint = summaryHint(payload);
3901
+ const safe = redactConnectPayload(payload, incomingToken);
3902
+ if (!dryRun && checkMcp && doctorExit !== ExitCode.OK && doctorExit !== ExitCode.DOCTOR_HAS_WARNINGS) {
3903
+ return { exitCode: doctorExit, result: ok(safe) };
3904
+ }
3905
+ return { exitCode: ExitCode.OK, result: ok(safe) };
3906
+ }
3907
+
3579
3908
  // 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";
3909
+ import { rename as rename2, mkdir as mkdir7, readFile as readFile8 } from "fs/promises";
3910
+ import { join as join20, dirname as dirname5 } from "path";
3582
3911
 
3583
3912
  // 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";
3913
+ import { mkdir as mkdir6, writeFile as writeFile4, readdir as readdir4, readFile as readFile7 } from "fs/promises";
3914
+ import { join as join19 } from "path";
3586
3915
  var DELETE_INTENT_SCHEMA = "vault-delete-intent/v1";
3587
3916
  var DELETE_INTENT_DIR = "meta/delete-intents";
3588
3917
  function normalizeVaultRelPath(path) {
@@ -3613,10 +3942,10 @@ function buildDeleteIntent(input) {
3613
3942
  };
3614
3943
  }
3615
3944
  async function writeDeleteIntent(vault, intent) {
3616
- const dir = join18(vault, DELETE_INTENT_DIR);
3617
- await mkdir5(dir, { recursive: true });
3945
+ const dir = join19(vault, DELETE_INTENT_DIR);
3946
+ await mkdir6(dir, { recursive: true });
3618
3947
  const rel = `${DELETE_INTENT_DIR}/${pathToIntentFilename(intent.path)}`;
3619
- await writeFile4(join18(vault, rel), JSON.stringify(intent, null, 2) + "\n", "utf8");
3948
+ await writeFile4(join19(vault, rel), JSON.stringify(intent, null, 2) + "\n", "utf8");
3620
3949
  return rel;
3621
3950
  }
3622
3951
 
@@ -3656,7 +3985,7 @@ async function runArchive(input) {
3656
3985
  const slug = relPath.replace(/\.md$/, "").split("/").pop();
3657
3986
  const rawArchive = isRaw ? lifecycleDestination(relPath, "archive") : void 0;
3658
3987
  if (rawArchive && !rawArchive.ok) return { exitCode: ExitCode.USAGE, result: rawArchive };
3659
- const archivePath = isRaw ? rawArchive.data : join19("_archive", relPath).replace(/\\/g, "/");
3988
+ const archivePath = isRaw ? rawArchive.data : join20("_archive", relPath).replace(/\\/g, "/");
3660
3989
  const remoteRoot = normalizeRemoteRoot(input.remote);
3661
3990
  const remoteObjectPath = buildRemoteObjectPath(remoteRoot, relPath);
3662
3991
  let cascade;
@@ -3686,7 +4015,7 @@ async function runArchive(input) {
3686
4015
  const indexRefs = [];
3687
4016
  if (!isRaw) {
3688
4017
  try {
3689
- const idx = await readFile7(join19(input.vault, "index.md"), "utf8");
4018
+ const idx = await readFile8(join20(input.vault, "index.md"), "utf8");
3690
4019
  idx.split("\n").forEach((line, i) => {
3691
4020
  if (line.includes(`[[${slug}]]`)) indexRefs.push({ line: i + 1, text: line });
3692
4021
  });
@@ -3744,8 +4073,8 @@ async function runArchive(input) {
3744
4073
  const rewrittenCascadePages = [];
3745
4074
  if ((input.cascade || isRaw) && input.apply && cascade) {
3746
4075
  for (const ref of cascade.source_array_refs) {
3747
- const absPath = join19(input.vault, ref.page);
3748
- const text = await readFile7(absPath, "utf8");
4076
+ const absPath = join20(input.vault, ref.page);
4077
+ const text = await readFile8(absPath, "utf8");
3749
4078
  if (!arraysEqual(ref.sources_after, ref.sources_before)) {
3750
4079
  let updated;
3751
4080
  if (isRaw) {
@@ -3771,8 +4100,8 @@ ${fmRewritten}
3771
4100
  if (isRaw) {
3772
4101
  for (const ref of cascade.citation_refs) {
3773
4102
  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");
4103
+ const absPath = join20(input.vault, ref.page);
4104
+ const original = await readFile8(absPath, "utf8");
3776
4105
  const updated = rewriteRawSourceReferences(original, relPath, archivePath).text;
3777
4106
  if (updated !== original) {
3778
4107
  const write = await safeWritePage(absPath, updated);
@@ -3783,13 +4112,13 @@ ${fmRewritten}
3783
4112
  }
3784
4113
  }
3785
4114
  if (!isRaw) {
3786
- await mkdir6(dirname5(join19(input.vault, archivePath)), { recursive: true });
3787
- await rename2(join19(input.vault, relPath), join19(input.vault, archivePath));
4115
+ await mkdir7(dirname5(join20(input.vault, archivePath)), { recursive: true });
4116
+ await rename2(join20(input.vault, relPath), join20(input.vault, archivePath));
3788
4117
  }
3789
4118
  let indexUpdated = false;
3790
4119
  if (!isRaw) {
3791
4120
  const { renderRootIndex: renderRootIndex2, writeRootIndexProjection: writeRootIndexProjection2 } = await import("./index-projection-6OHDINHT.js");
3792
- const before = await readFile7(join19(input.vault, "index.md"), "utf8").catch(() => "");
4121
+ const before = await readFile8(join20(input.vault, "index.md"), "utf8").catch(() => "");
3793
4122
  const fullTarget = relPath.replace(/\.md$/, "");
3794
4123
  const bare = fullTarget.split("/").pop() ?? fullTarget;
3795
4124
  const hadIndexEntry = before.includes(`[[${fullTarget}]]`) || before.includes(`[[${bare}]]`);
@@ -3843,7 +4172,7 @@ ${fmRewritten}
3843
4172
 
3844
4173
  // src/commands/remove.ts
3845
4174
  import { unlink as unlink2, access } from "fs/promises";
3846
- import { join as join20 } from "path";
4175
+ import { join as join21 } from "path";
3847
4176
  async function pathExists(abs) {
3848
4177
  try {
3849
4178
  await access(abs);
@@ -3869,7 +4198,7 @@ async function runRemove(input) {
3869
4198
  if (!relPath) {
3870
4199
  try {
3871
4200
  const candidate = normalizeVaultRelPath(input.page);
3872
- if (await pathExists(join20(input.vault, candidate))) {
4201
+ if (await pathExists(join21(input.vault, candidate))) {
3873
4202
  relPath = candidate;
3874
4203
  }
3875
4204
  } catch {
@@ -3907,12 +4236,12 @@ async function runRemove(input) {
3907
4236
  reason: input.reason
3908
4237
  });
3909
4238
  const tombstonePath = await writeDeleteIntent(input.vault, intent);
3910
- await unlink2(join20(input.vault, relPath));
4239
+ await unlink2(join21(input.vault, relPath));
3911
4240
  if (relPath.endsWith(".md") && !relPath.startsWith("raw/")) {
3912
- const { readFile: readFile23 } = await import("fs/promises");
4241
+ const { readFile: readFile24 } = await import("fs/promises");
3913
4242
  const { join: pathJoin } = await import("path");
3914
4243
  const { renderRootIndex: renderRootIndex2, writeRootIndexProjection: writeRootIndexProjection2 } = await import("./index-projection-6OHDINHT.js");
3915
- const before = await readFile23(pathJoin(input.vault, "index.md"), "utf8").catch(() => "");
4244
+ const before = await readFile24(pathJoin(input.vault, "index.md"), "utf8").catch(() => "");
3916
4245
  const fullTarget = relPath.replace(/\.md$/, "");
3917
4246
  const bare = fullTarget.split("/").pop() ?? fullTarget;
3918
4247
  const hadIndexEntry = before.includes(`[[${fullTarget}]]`) || before.includes(`[[${bare}]]`);
@@ -3950,7 +4279,7 @@ async function runRemove(input) {
3950
4279
  }
3951
4280
 
3952
4281
  // src/commands/drift.ts
3953
- import { createHash as createHash2 } from "crypto";
4282
+ import { createHash as createHash3 } from "crypto";
3954
4283
 
3955
4284
  // src/utils/fetch.ts
3956
4285
  async function controlledFetch(url, opts) {
@@ -4029,7 +4358,7 @@ async function runDrift(input) {
4029
4358
  });
4030
4359
  continue;
4031
4360
  }
4032
- const currentHash = createHash2("sha256").update(Buffer.from(resp.data.body, "utf8")).digest("hex");
4361
+ const currentHash = createHash3("sha256").update(Buffer.from(resp.data.body, "utf8")).digest("hex");
4033
4362
  const identity = assessSourceIdentity({
4034
4363
  rawPath: raw.relPath,
4035
4364
  sourceUrl,
@@ -4261,7 +4590,7 @@ ${migratedBody}${newFooter}`;
4261
4590
 
4262
4591
  // src/commands/update.ts
4263
4592
  import { execSync } from "child_process";
4264
- import { join as join21 } from "path";
4593
+ import { join as join22 } from "path";
4265
4594
  function parseCoreSemver(version) {
4266
4595
  const m = version.match(/^(\d+)\.(\d+)\.(\d+)/);
4267
4596
  if (!m) return null;
@@ -4313,7 +4642,7 @@ function resolveGlobalSkillsRoot() {
4313
4642
  encoding: "utf8",
4314
4643
  timeout: 5e3
4315
4644
  }).trim();
4316
- return join21(globalRoot, "skillwiki", "skills");
4645
+ return join22(globalRoot, "skillwiki", "skills");
4317
4646
  } catch {
4318
4647
  return null;
4319
4648
  }
@@ -4341,7 +4670,7 @@ async function runUpdate(input) {
4341
4670
  const pkg2 = readCliPackageJson();
4342
4671
  const currentVersion = pkg2.version;
4343
4672
  const tag = normalizeDistTag(input.distTag);
4344
- const target = join21(input.home, ".claude", "skills");
4673
+ const target = join22(input.home, ".claude", "skills");
4345
4674
  let latest;
4346
4675
  try {
4347
4676
  latest = execSync(`npm view skillwiki@${tag} version`, {
@@ -4422,13 +4751,13 @@ async function runUpdate(input) {
4422
4751
  // src/commands/self-update.ts
4423
4752
  import { execSync as execSync2 } from "child_process";
4424
4753
  import { existsSync as existsSync8, readFileSync as readFileSync11 } from "fs";
4425
- import { join as join22 } from "path";
4754
+ import { join as join23 } from "path";
4426
4755
  var DEFAULT_SOURCE_ROOT_SUFFIX = "/Desktop/code/llm-wiki";
4427
4756
  async function runSelfUpdate(input) {
4428
4757
  const currentVersion = readCliPackageJson().version;
4429
4758
  const sourceRoot = input.sourceRoot ?? `${input.home}${DEFAULT_SOURCE_ROOT_SUFFIX}`;
4430
4759
  const distTag = normalizeDistTag(input.distTag);
4431
- const localPkgPath = join22(sourceRoot, "packages", "cli", "package.json");
4760
+ const localPkgPath = join23(sourceRoot, "packages", "cli", "package.json");
4432
4761
  const hasLocalSource = existsSync8(localPkgPath);
4433
4762
  if (input.check) {
4434
4763
  let availableVersion = null;
@@ -4560,10 +4889,10 @@ async function runSelfUpdate(input) {
4560
4889
  }
4561
4890
 
4562
4891
  // 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";
4892
+ import { readdir as readdir5, stat as stat4, readFile as readFile10 } from "fs/promises";
4893
+ import { join as join24 } from "path";
4565
4894
  async function runTranscripts(input) {
4566
- const dir = join23(input.vault, "raw", "transcripts");
4895
+ const dir = join24(input.vault, "raw", "transcripts");
4567
4896
  let entries;
4568
4897
  try {
4569
4898
  entries = await readdir5(dir, { withFileTypes: true });
@@ -4573,13 +4902,13 @@ async function runTranscripts(input) {
4573
4902
  const transcripts = [];
4574
4903
  for (const entry of entries) {
4575
4904
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
4576
- const filePath = join23(dir, entry.name);
4577
- const content = await readFile9(filePath, "utf8");
4905
+ const filePath = join24(dir, entry.name);
4906
+ const content = await readFile10(filePath, "utf8");
4578
4907
  const fm = extractFrontmatter(content);
4579
4908
  if (!fm.ok) continue;
4580
4909
  const ingested = typeof fm.data.ingested === "string" ? fm.data.ingested : "";
4581
4910
  if (input.since && ingested && ingested < input.since) continue;
4582
- const s = await stat3(filePath);
4911
+ const s = await stat4(filePath);
4583
4912
  transcripts.push({
4584
4913
  file: `raw/transcripts/${entry.name}`,
4585
4914
  ingested,
@@ -4591,8 +4920,8 @@ async function runTranscripts(input) {
4591
4920
  }
4592
4921
 
4593
4922
  // src/commands/claims-audit.ts
4594
- import { readFile as readFile10 } from "fs/promises";
4595
- import { join as join24 } from "path";
4923
+ import { readFile as readFile11 } from "fs/promises";
4924
+ import { join as join25 } from "path";
4596
4925
  function safeProjectPresentation(raw) {
4597
4926
  if (safeDiagnosticValue(raw) === REDACTED_MALFORMED_REFERENCE) {
4598
4927
  return REDACTED_MALFORMED_REFERENCE;
@@ -4632,9 +4961,9 @@ async function runClaimsAudit(input) {
4632
4961
  return !input.project || slugByDir.get(relDir) === input.project;
4633
4962
  });
4634
4963
  const claimSourceResults = await mapWithConcurrency(scopedSpecDirs, vaultIoConcurrency(), async (relDir) => {
4635
- const specPath = join24(input.vault, relDir, "spec.md");
4964
+ const specPath = join25(input.vault, relDir, "spec.md");
4636
4965
  try {
4637
- const specText = await readFile10(specPath, "utf8");
4966
+ const specText = await readFile11(specPath, "utf8");
4638
4967
  const fm = extractFrontmatter(specText);
4639
4968
  if (!fm.ok) return null;
4640
4969
  return {
@@ -4728,10 +5057,10 @@ async function runClaimsAudit(input) {
4728
5057
  }
4729
5058
 
4730
5059
  // 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";
5060
+ import { writeFile as writeFile6, mkdir as mkdir8, readdir as readdir6, unlink as unlink3 } from "fs/promises";
5061
+ import { join as join26 } from "path";
4733
5062
  import { existsSync as existsSync9 } from "fs";
4734
- import { readFile as readFile11 } from "fs/promises";
5063
+ import { readFile as readFile12 } from "fs/promises";
4735
5064
  var RETRO_HEADING_RE = /^## \[(\d{4}-\d{2}-\d{2})(?:\s+[^\]]+)?\] retro \| loop cycle(?: (\d+))?: (.+)$/;
4736
5065
  var FIELD_RE = {
4737
5066
  improve: /^-\s+\*?\*?Improve:?\*?\*?\s*(.+)$/m,
@@ -4829,17 +5158,17 @@ function extractRetroFields(date, cycleName, block) {
4829
5158
  };
4830
5159
  }
4831
5160
  async function runCompound(input) {
4832
- const logPath = join25(input.vault, "log.md");
5161
+ const logPath = join26(input.vault, "log.md");
4833
5162
  let logText;
4834
5163
  try {
4835
- logText = await readFile11(logPath, "utf8");
5164
+ logText = await readFile12(logPath, "utf8");
4836
5165
  } catch {
4837
5166
  return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path: logPath }) };
4838
5167
  }
4839
5168
  const entries = parseRetroEntries(logText);
4840
5169
  const promoted = [];
4841
5170
  const skipped = [];
4842
- const compoundDir = join25(input.vault, "projects", input.project, "compound");
5171
+ const compoundDir = join26(input.vault, "projects", input.project, "compound");
4843
5172
  for (const entry of entries) {
4844
5173
  const generalizeValue = entry.generalize.trim();
4845
5174
  if (!/^yes/i.test(generalizeValue)) {
@@ -4847,7 +5176,7 @@ async function runCompound(input) {
4847
5176
  continue;
4848
5177
  }
4849
5178
  const slug = slugify(entry.cycleName);
4850
- const compoundPath = join25(compoundDir, `${slug}.md`);
5179
+ const compoundPath = join26(compoundDir, `${slug}.md`);
4851
5180
  if (existsSync9(compoundPath)) {
4852
5181
  skipped.push(entry.date);
4853
5182
  continue;
@@ -4887,7 +5216,7 @@ async function runCompound(input) {
4887
5216
  const content = frontmatter + "\n" + body;
4888
5217
  if (!input.dryRun) {
4889
5218
  if (!existsSync9(compoundDir)) {
4890
- await mkdir7(compoundDir, { recursive: true });
5219
+ await mkdir8(compoundDir, { recursive: true });
4891
5220
  }
4892
5221
  await writeFile6(compoundPath, content, "utf8");
4893
5222
  }
@@ -4908,7 +5237,7 @@ async function runCompound(input) {
4908
5237
  };
4909
5238
  }
4910
5239
  async function runCompoundDelete(input) {
4911
- const projectDir = join25(input.vault, "projects", input.project);
5240
+ const projectDir = join26(input.vault, "projects", input.project);
4912
5241
  if (!existsSync9(projectDir)) {
4913
5242
  return {
4914
5243
  exitCode: ExitCode.PROJECT_NOT_FOUND,
@@ -4916,7 +5245,7 @@ async function runCompoundDelete(input) {
4916
5245
  };
4917
5246
  }
4918
5247
  const entryName = input.entry.replace(/\.md$/, "");
4919
- const compoundPath = join25(projectDir, "compound", `${entryName}.md`);
5248
+ const compoundPath = join26(projectDir, "compound", `${entryName}.md`);
4920
5249
  if (!existsSync9(compoundPath)) {
4921
5250
  return {
4922
5251
  exitCode: ExitCode.FILE_NOT_FOUND,
@@ -4950,7 +5279,7 @@ knowledge.md regenerated`
4950
5279
  };
4951
5280
  }
4952
5281
  async function runCompoundList(input) {
4953
- const compoundDir = join25(input.vault, "projects", input.project, "compound");
5282
+ const compoundDir = join26(input.vault, "projects", input.project, "compound");
4954
5283
  if (!existsSync9(compoundDir)) {
4955
5284
  return {
4956
5285
  exitCode: ExitCode.OK,
@@ -4981,10 +5310,10 @@ could not read compound directory`
4981
5310
  const entries = [];
4982
5311
  for (const dirent of dirents) {
4983
5312
  if (!dirent.isFile() || !dirent.name.endsWith(".md")) continue;
4984
- const filePath = join25(compoundDir, dirent.name);
5313
+ const filePath = join26(compoundDir, dirent.name);
4985
5314
  let text;
4986
5315
  try {
4987
- text = await readFile11(filePath, "utf8");
5316
+ text = await readFile12(filePath, "utf8");
4988
5317
  } catch {
4989
5318
  continue;
4990
5319
  }
@@ -5013,8 +5342,8 @@ no compound entries found`;
5013
5342
  }
5014
5343
 
5015
5344
  // 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";
5345
+ import { mkdir as mkdir9, readFile as readFile13, writeFile as writeFile7 } from "fs/promises";
5346
+ import { join as join27, relative, sep } from "path";
5018
5347
  var MAX_WORDS = 900;
5019
5348
  async function runSessionBrief(input) {
5020
5349
  const { root: scanRoot } = resolveReadOnlyVaultRoot(input.vault);
@@ -5026,29 +5355,33 @@ async function runSessionBrief(input) {
5026
5355
  const generatedAt = now.toISOString().replace(/\.\d{3}Z$/, "Z");
5027
5356
  const today = generatedAt.slice(0, 10);
5028
5357
  const project = await resolveProject(input);
5358
+ const collectorRunPath = input.agentMemoryRunState ?? satelliteLatestRunPath(input.vault);
5359
+ const collectorRun = readSatelliteLatestRunAt(collectorRunPath);
5360
+ const collectorHealth = evaluateSatelliteRunHealthState(collectorRun, now);
5029
5361
  try {
5362
+ const expectedPacketPath = collectorRun?.runDate != null ? `queries/${collectorRun.runDate}-agent-memory-trends-packet.md` : void 0;
5363
+ const packetExists = expectedPacketPath != null && scan.data.typedKnowledge.some((page) => page.relPath === expectedPacketPath);
5030
5364
  const [
5031
5365
  transcripts,
5032
5366
  workItems,
5033
5367
  digests,
5034
5368
  baseHealthWarnings,
5035
- satelliteHealth,
5036
5369
  sessionPins,
5037
5370
  memoryTopics,
5038
5371
  pendingSources
5039
5372
  ] = await Promise.all([
5040
5373
  loadTranscriptInfo(scan.data.raw),
5041
5374
  loadWorkItems(scan.data.workItems),
5042
- loadTrendDigests(scan.data.typedKnowledge),
5375
+ loadTrendPages(scan.data.typedKnowledge, /^queries\/\d{4}-\d{2}-\d{2}-agent-memory-trends-digest\.md$/),
5043
5376
  loadHealthWarnings(input.vault),
5044
- loadSatelliteHealth(input.vault),
5045
5377
  loadSessionPins(input.vault, project),
5046
5378
  project ? loadMemoryTopics(input.vault, project) : Promise.resolve([]),
5047
5379
  loadPendingSources(scanRoot, today)
5048
5380
  ]);
5381
+ const collectorReceipt = buildCollectorReceipt(collectorRun, collectorHealth, packetExists);
5049
5382
  const healthWarnings = [
5050
5383
  ...baseHealthWarnings,
5051
- ...satelliteHealthWarnings(satelliteHealth)
5384
+ ...collectorReceipt.healthWarnings
5052
5385
  ];
5053
5386
  const claimed = collectClaimedTranscripts(
5054
5387
  workItems.map((w) => ({ relDir: w.path, source: w.source, sources: w.sources, closes: w.closes }))
@@ -5073,6 +5406,7 @@ async function runSessionBrief(input) {
5073
5406
  unclaimedCaptures,
5074
5407
  pendingSources,
5075
5408
  activeWork,
5409
+ collectorReceipt: collectorReceipt.lines,
5076
5410
  latestDigest,
5077
5411
  projectLogs,
5078
5412
  memoryTopics,
@@ -5123,7 +5457,7 @@ async function resolveProject(input) {
5123
5457
  const envProject = input.env?.SKILLWIKI_PROJECT;
5124
5458
  if (envProject) return envProject;
5125
5459
  const cwd = input.cwd ?? process.cwd();
5126
- const projectDotenv = await readProjectSlug(join26(cwd, ".skillwiki", ".env"));
5460
+ const projectDotenv = await readProjectSlug(join27(cwd, ".skillwiki", ".env"));
5127
5461
  if (projectDotenv) return projectDotenv;
5128
5462
  const inferred = inferProjectFromPath(input.vault, cwd);
5129
5463
  if (inferred) return inferred;
@@ -5132,7 +5466,7 @@ async function resolveProject(input) {
5132
5466
  async function readProjectSlug(file) {
5133
5467
  let text;
5134
5468
  try {
5135
- text = await readFile12(file, "utf8");
5469
+ text = await readFile13(file, "utf8");
5136
5470
  } catch {
5137
5471
  return void 0;
5138
5472
  }
@@ -5205,9 +5539,9 @@ async function loadWorkItems(workItemPages) {
5205
5539
  }
5206
5540
  return out;
5207
5541
  }
5208
- async function loadTrendDigests(typedPages) {
5542
+ async function loadTrendPages(typedPages, pathPattern) {
5209
5543
  const out = [];
5210
- for (const page of typedPages.filter((p) => p.relPath.startsWith("queries/") && p.relPath.includes("agent-memory-trends"))) {
5544
+ for (const page of typedPages.filter((p) => pathPattern.test(p.relPath))) {
5211
5545
  const text = await readPage(page);
5212
5546
  const fm = extractFrontmatter(text);
5213
5547
  if (!fm.ok) continue;
@@ -5223,7 +5557,7 @@ async function loadTrendDigests(typedPages) {
5223
5557
  return out;
5224
5558
  }
5225
5559
  async function loadSessionPins(vault, project) {
5226
- const text = await readIfExists(join26(vault, "meta", "session-pins.md"));
5560
+ const text = await readIfExists(join27(vault, "meta", "session-pins.md"));
5227
5561
  if (!text) return [];
5228
5562
  const fm = extractFrontmatter(text);
5229
5563
  if (!fm.ok) return [];
@@ -5258,7 +5592,8 @@ function renderBrief(input) {
5258
5592
  appendSection(lines, "Unclaimed Captures", input.unclaimedCaptures, "No unclaimed task or bug captures found.");
5259
5593
  appendSection(lines, "Recent Pending Sources", input.pendingSources, "No pending articles or papers found.");
5260
5594
  appendSection(lines, "Recent Session Logs", input.project ? input.projectLogs : input.latestLogs, "No recent session logs found.");
5261
- appendSection(lines, "Latest Agent Memory Trends", input.latestDigest, "No agent memory trends digest found.");
5595
+ appendTextSection(lines, "Latest Collector Run", input.collectorReceipt, "No collector receipt is available.");
5596
+ appendSection(lines, "Latest Judged Agent Memory Trends", input.latestDigest, "No judged agent memory trends digest found.");
5262
5597
  appendMemoryTopicsSection(lines, input.project, input.memoryTopics);
5263
5598
  appendTextSection(lines, "Health Warnings", input.healthWarnings, "No high-level health warnings found.");
5264
5599
  lines.push(
@@ -5316,13 +5651,42 @@ function appendTextSection(lines, title, items, empty) {
5316
5651
  }
5317
5652
  lines.push("");
5318
5653
  }
5319
- async function loadSatelliteHealth(vaultPath) {
5320
- const health = evaluateSatelliteRunHealth(vaultPath, /* @__PURE__ */ new Date());
5321
- if (!health.failed && !health.stale) {
5322
- const runPath2 = satelliteLatestRunPath(vaultPath);
5323
- const hasRun = await readIfExists(runPath2);
5324
- if (!hasRun) return null;
5654
+ function buildCollectorReceipt(run, health, packetExists) {
5655
+ const healthWarning = satelliteHealthWarning(health);
5656
+ const healthWarnings = healthWarning ? [healthWarning] : [];
5657
+ if (!run) return { lines: [], healthWarnings };
5658
+ const finished = run.finishedAt ? ` at ${run.finishedAt}` : "";
5659
+ if (health.failed) {
5660
+ return { lines: [`Collector run failed${finished}.`], healthWarnings };
5661
+ }
5662
+ if (health.stale) {
5663
+ return { lines: [`Collector receipt is stale${finished}.`], healthWarnings };
5325
5664
  }
5665
+ if (run.status !== "success" || run.selectedCandidateCount === void 0 || !run.runDate) {
5666
+ return { lines: [], healthWarnings };
5667
+ }
5668
+ if (run.selectedCandidateCount === 0) {
5669
+ return {
5670
+ lines: [`${run.runDate} Quiet night \u2014 collector succeeded, 0 selected; no packet published${finished}.`],
5671
+ healthWarnings
5672
+ };
5673
+ }
5674
+ const packetPath = `queries/${run.runDate}-agent-memory-trends-packet.md`;
5675
+ if (packetExists) {
5676
+ return {
5677
+ lines: [`${run.runDate} Collector succeeded; ${run.selectedCandidateCount} selected; packet: [[${packetPath.replace(/\.md$/, "")}]]${finished}.`],
5678
+ healthWarnings
5679
+ };
5680
+ }
5681
+ return {
5682
+ lines: [`${run.runDate} Collector succeeded with ${run.selectedCandidateCount} selected, but the packet is missing${finished}.`],
5683
+ healthWarnings: [
5684
+ ...healthWarnings,
5685
+ `agent-memory-trends: collector selected ${run.selectedCandidateCount} candidate(s) but ${packetPath} is missing`
5686
+ ]
5687
+ };
5688
+ }
5689
+ function satelliteHealthWarning(health) {
5326
5690
  if (health.failed) {
5327
5691
  const fc = health.failureClass && health.failureClass.length > 0 ? health.failureClass : "unknown";
5328
5692
  return `agent-memory-trends: last run failed (${fc})`;
@@ -5338,11 +5702,8 @@ async function loadSatelliteHealth(vaultPath) {
5338
5702
  }
5339
5703
  return null;
5340
5704
  }
5341
- function satelliteHealthWarnings(warning) {
5342
- return warning ? [warning] : [];
5343
- }
5344
5705
  async function loadHealthWarnings(vault) {
5345
- const text = await readIfExists(join26(vault, ".skillwiki", "health.json"));
5706
+ const text = await readIfExists(join27(vault, ".skillwiki", "health.json"));
5346
5707
  if (!text) return [];
5347
5708
  try {
5348
5709
  const parsed = JSON.parse(text);
@@ -5355,7 +5716,7 @@ async function loadHealthWarnings(vault) {
5355
5716
  }
5356
5717
  }
5357
5718
  async function loadMemoryTopics(vault, project) {
5358
- const text = await readIfExists(join26(vault, ".skillwiki", "memory", project, "topics.json"));
5719
+ const text = await readIfExists(join27(vault, ".skillwiki", "memory", project, "topics.json"));
5359
5720
  if (!text) return [];
5360
5721
  try {
5361
5722
  const parsed = JSON.parse(text);
@@ -5380,11 +5741,11 @@ async function loadMemoryTopics(vault, project) {
5380
5741
  }
5381
5742
  }
5382
5743
  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 });
5744
+ const metaPath = join27(vault, "meta", "latest-session-brief.md");
5745
+ const cacheMdPath = join27(vault, ".skillwiki", "session-brief.md");
5746
+ const cacheJsonPath = join27(vault, ".skillwiki", "session-brief.json");
5747
+ await mkdir9(join27(vault, "meta"), { recursive: true });
5748
+ await mkdir9(join27(vault, ".skillwiki"), { recursive: true });
5388
5749
  const committed = renderCommittedBrief(input);
5389
5750
  const previousComparable = comparableBrief(await readIfExists(metaPath));
5390
5751
  const nextComparable = comparableBrief(committed);
@@ -5457,7 +5818,7 @@ function renderCommittedBrief(input) {
5457
5818
  ].filter((line) => line !== "").join("\n");
5458
5819
  }
5459
5820
  async function rebuildRootIndexProjection(vault) {
5460
- const before = await readIfExists(join26(vault, "index.md"));
5821
+ const before = await readIfExists(join27(vault, "index.md"));
5461
5822
  if (!before) return false;
5462
5823
  const projection = await renderRootIndex({ vault, currentText: before });
5463
5824
  if (!projection.ok) return false;
@@ -5467,7 +5828,7 @@ async function rebuildRootIndexProjection(vault) {
5467
5828
  }
5468
5829
  async function readIfExists(path) {
5469
5830
  try {
5470
- return await readFile12(path, "utf8");
5831
+ return await readFile13(path, "utf8");
5471
5832
  } catch {
5472
5833
  return "";
5473
5834
  }
@@ -5509,23 +5870,23 @@ function dateFromPath(path) {
5509
5870
  }
5510
5871
 
5511
5872
  // 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";
5873
+ import { readFile as readFile16, open, unlink as unlink4, mkdir as mkdir11 } from "fs/promises";
5874
+ import { join as join32 } from "path";
5875
+ import { createHash as createHash5 } from "crypto";
5515
5876
 
5516
5877
  // src/commands/page-publish.ts
5517
5878
  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";
5879
+ import { mkdir as mkdir10, readFile as readFile15 } from "fs/promises";
5880
+ import { dirname as dirname8, join as join31, resolve as resolve4 } from "path";
5520
5881
 
5521
5882
  // src/utils/publication-approval.ts
5522
- import { createHash as createHash3 } from "crypto";
5883
+ import { createHash as createHash4 } from "crypto";
5523
5884
  var APPROVAL_CONTRACT = "skillwiki-publication-approval-v1";
5524
5885
  var APPROVAL_TOKEN_VERSION = "swpub1";
5525
5886
  var SHA256_HEX = /^[0-9a-f]{64}$/;
5526
5887
  var PUBLISHERS = /* @__PURE__ */ new Set(["page", "project-page"]);
5527
5888
  function sha256Hex(bytes) {
5528
- return createHash3("sha256").update(bytes).digest("hex");
5889
+ return createHash4("sha256").update(bytes).digest("hex");
5529
5890
  }
5530
5891
  function normalizeLogNote(note) {
5531
5892
  if (note === void 0) return "";
@@ -5744,8 +6105,8 @@ function redactApprovalTokens(text) {
5744
6105
  }
5745
6106
 
5746
6107
  // 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";
6108
+ import { readFile as readFile14 } from "fs/promises";
6109
+ import { dirname as dirname7, join as join30, resolve as resolve3 } from "path";
5749
6110
 
5750
6111
  // src/utils/publication-operation-journal.ts
5751
6112
  import {
@@ -5759,7 +6120,7 @@ import {
5759
6120
  writeFileSync as writeFileSync5
5760
6121
  } from "fs";
5761
6122
  import { homedir } from "os";
5762
- import { dirname as dirname6, join as join27 } from "path";
6123
+ import { dirname as dirname6, join as join28 } from "path";
5763
6124
  var PUBLICATION_JOURNAL_SCHEMA = "skillwiki-publication-operation-v1";
5764
6125
  var PHASE_ORDER2 = [
5765
6126
  "locked",
@@ -5788,16 +6149,16 @@ function vaultIdentity(vaultPath) {
5788
6149
  return sha256Hex(real).slice(0, 16);
5789
6150
  }
5790
6151
  function publicationJournalRoot(home = process.env.HOME || homedir()) {
5791
- return join27(home, ".skillwiki", "publication-operations");
6152
+ return join28(home, ".skillwiki", "publication-operations");
5792
6153
  }
5793
6154
  function resolveJournalPaths(vaultPath, operationId2, home = process.env.HOME || homedir()) {
5794
6155
  const identity = vaultIdentity(vaultPath);
5795
6156
  const rootDir = publicationJournalRoot(home);
5796
- const vaultDir = join27(rootDir, identity);
6157
+ const vaultDir = join28(rootDir, identity);
5797
6158
  return {
5798
6159
  rootDir,
5799
6160
  vaultDir,
5800
- journalPath: join27(vaultDir, `${operationId2}.json`)
6161
+ journalPath: join28(vaultDir, `${operationId2}.json`)
5801
6162
  };
5802
6163
  }
5803
6164
  function ensurePrivateDir(dir) {
@@ -6018,7 +6379,7 @@ function completePublicationJournal(input) {
6018
6379
  }
6019
6380
 
6020
6381
  // src/publish/hold-gates.ts
6021
- import { join as join28 } from "path";
6382
+ import { join as join29 } from "path";
6022
6383
  function evaluateSchemaGate(content, target, publisherKind) {
6023
6384
  const frontmatter = extractFrontmatter(content);
6024
6385
  if (!frontmatter.ok) return false;
@@ -6054,7 +6415,7 @@ function evaluateSchemaGate(content, target, publisherKind) {
6054
6415
  return false;
6055
6416
  }
6056
6417
  function evaluateBrokenWikilinkGate(body, scan, ownTarget) {
6057
- const draftPage = { absPath: join28(scan.root, ownTarget), relPath: ownTarget };
6418
+ const draftPage = { absPath: join29(scan.root, ownTarget), relPath: ownTarget };
6058
6419
  const resolver = buildWikilinkResolver([...scan.allMarkdown, draftPage]);
6059
6420
  const wikilinks = extractBodyWikilinks(body);
6060
6421
  for (const link of wikilinks) {
@@ -6241,7 +6602,7 @@ function phaseAtLeast(current, needed) {
6241
6602
  }
6242
6603
  async function readPageChanged(targetPath, content) {
6243
6604
  try {
6244
- return ok(await readFile13(targetPath, "utf8") !== content);
6605
+ return ok(await readFile14(targetPath, "utf8") !== content);
6245
6606
  } catch (error) {
6246
6607
  if (error.code === "ENOENT") return ok(true);
6247
6608
  return err("WRITE_FAILED", { path: targetPath, message: String(error) });
@@ -6249,7 +6610,7 @@ async function readPageChanged(targetPath, content) {
6249
6610
  }
6250
6611
  async function readPriorTargetSha(targetPath) {
6251
6612
  try {
6252
- const text = await readFile13(targetPath, "utf8");
6613
+ const text = await readFile14(targetPath, "utf8");
6253
6614
  return ok(sha256Hex(text));
6254
6615
  } catch (error) {
6255
6616
  if (error.code === "ENOENT") return ok("absent");
@@ -6282,10 +6643,10 @@ function successReceipt(strategy, prepared, details) {
6282
6643
  };
6283
6644
  }
6284
6645
  async function runPipelinePreview(strategy, prepared, vault) {
6285
- const schemaPath = join29(vault, "SCHEMA.md");
6646
+ const schemaPath = join30(vault, "SCHEMA.md");
6286
6647
  let schemaText;
6287
6648
  try {
6288
- schemaText = await readFile13(schemaPath, "utf8");
6649
+ schemaText = await readFile14(schemaPath, "utf8");
6289
6650
  } catch (error) {
6290
6651
  const result = err("FILE_NOT_FOUND", { path: schemaPath, message: String(error) });
6291
6652
  return { exitCode: ExitCode.FILE_NOT_FOUND, result };
@@ -6309,10 +6670,10 @@ async function runPipelinePreview(strategy, prepared, vault) {
6309
6670
  if (!indices.ok) {
6310
6671
  return { exitCode: indices.exitCode, result: indices.result };
6311
6672
  }
6312
- const logPath = join29(vault, "log.md");
6673
+ const logPath = join30(vault, "log.md");
6313
6674
  let logAppended;
6314
6675
  try {
6315
- const logText = await readFile13(logPath, "utf8");
6676
+ const logText = await readFile14(logPath, "utf8");
6316
6677
  const marker = strategy.publisherKind === "page" ? `<!-- skillwiki-page-publish:${prepared.operationId} -->` : `<!-- skillwiki-project-page-publish:${prepared.operationId} -->`;
6317
6678
  logAppended = !logText.includes(marker);
6318
6679
  } catch (error) {
@@ -6403,10 +6764,10 @@ async function runPipelineLockedPrimaryStages(strategy, prepared, vault, deps, j
6403
6764
  return { ok: false, exitCode: ExitCode.WRITE_FAILED, stage: "journal", state, cause: journalHook };
6404
6765
  }
6405
6766
  }
6406
- const schemaPath = join29(vault, "SCHEMA.md");
6767
+ const schemaPath = join30(vault, "SCHEMA.md");
6407
6768
  let schemaText;
6408
6769
  try {
6409
- schemaText = await readFile13(schemaPath, "utf8");
6770
+ schemaText = await readFile14(schemaPath, "utf8");
6410
6771
  } catch (error) {
6411
6772
  return {
6412
6773
  ok: false,
@@ -6455,8 +6816,8 @@ async function runPipelineLockedPrimaryStages(strategy, prepared, vault, deps, j
6455
6816
  if (!strategy.usesJournal || !phaseAtLeast(currentPhase, "page")) {
6456
6817
  if (strategy.publisherKind === "project-page") {
6457
6818
  try {
6458
- const { mkdir: mkdir13 } = await import("fs/promises");
6459
- await mkdir13(dirname7(prepared.targetPath), { recursive: true });
6819
+ const { mkdir: mkdir14 } = await import("fs/promises");
6820
+ await mkdir14(dirname7(prepared.targetPath), { recursive: true });
6460
6821
  } catch (error) {
6461
6822
  return {
6462
6823
  ok: false,
@@ -6504,8 +6865,8 @@ async function runPipelineLockedPrimaryStages(strategy, prepared, vault, deps, j
6504
6865
  let visibleSchema;
6505
6866
  try {
6506
6867
  [visible, visibleSchema] = await Promise.all([
6507
- readFile13(prepared.targetPath, "utf8"),
6508
- readFile13(schemaPath, "utf8")
6868
+ readFile14(prepared.targetPath, "utf8"),
6869
+ readFile14(schemaPath, "utf8")
6509
6870
  ]);
6510
6871
  } catch (error) {
6511
6872
  return {
@@ -6897,16 +7258,16 @@ async function refreshProjectIndexForTarget(vault, projectSlugs, today) {
6897
7258
  for (const slug of projectSlugs) {
6898
7259
  const rendered = await renderProjectIndex(vault, slug, { today });
6899
7260
  if (!rendered.ok) return rendered;
6900
- const indexPath = join30(vault, rendered.data.index_path);
7261
+ const indexPath = join31(vault, rendered.data.index_path);
6901
7262
  try {
6902
- await mkdir9(dirname8(indexPath), { recursive: true });
7263
+ await mkdir10(dirname8(indexPath), { recursive: true });
6903
7264
  } catch (error) {
6904
7265
  return err("WRITE_FAILED", { path: indexPath, message: String(error) });
6905
7266
  }
6906
7267
  const written = await atomicWriteText(indexPath, rendered.data.text);
6907
7268
  if (!written.ok) return written;
6908
7269
  try {
6909
- const visible = await readFile14(indexPath, "utf8");
7270
+ const visible = await readFile15(indexPath, "utf8");
6910
7271
  if (visible !== rendered.data.text) {
6911
7272
  return err("WRITE_FAILED", { path: indexPath, message: "project index verification failed" });
6912
7273
  }
@@ -7007,7 +7368,7 @@ function preparePagePublicationFromContent(input) {
7007
7368
  async function preparePagePublication(input) {
7008
7369
  let content;
7009
7370
  try {
7010
- content = await readFile14(input.draftPath, "utf8");
7371
+ content = await readFile15(input.draftPath, "utf8");
7011
7372
  } catch (error) {
7012
7373
  return err("FILE_NOT_FOUND", { path: input.draftPath, message: String(error) });
7013
7374
  }
@@ -7095,10 +7456,10 @@ var PAGE_PUBLISH_STRATEGY = {
7095
7456
  return void 0;
7096
7457
  },
7097
7458
  async previewIndices(prepared, vault, pageChanged) {
7098
- const indexPath = join30(vault, "index.md");
7459
+ const indexPath = join31(vault, "index.md");
7099
7460
  let indexText;
7100
7461
  try {
7101
- indexText = await readFile14(indexPath, "utf8");
7462
+ indexText = await readFile15(indexPath, "utf8");
7102
7463
  } catch (error) {
7103
7464
  const result = err("FILE_NOT_FOUND", { path: indexPath, message: String(error) });
7104
7465
  return { ok: false, exitCode: ExitCode.FILE_NOT_FOUND, result };
@@ -7116,7 +7477,7 @@ var PAGE_PUBLISH_STRATEGY = {
7116
7477
  if (!renderedProject.ok) {
7117
7478
  return { ok: false, exitCode: errorExitCode(renderedProject.error), result: renderedProject };
7118
7479
  }
7119
- const projectIndexPath = join30(vault, renderedProject.data.index_path);
7480
+ const projectIndexPath = join31(vault, renderedProject.data.index_path);
7120
7481
  const projectIndexChanged = await readPageChanged(projectIndexPath, renderedProject.data.text);
7121
7482
  if (!projectIndexChanged.ok) {
7122
7483
  return { ok: false, exitCode: errorExitCode(projectIndexChanged.error), result: projectIndexChanged };
@@ -7304,7 +7665,7 @@ function buildTypedContent(title, ingested, type, tags, rawRelPath, provenance)
7304
7665
  }
7305
7666
  async function resolveRawCapture(input) {
7306
7667
  try {
7307
- const existing = await readFile15(input.path, "utf8");
7668
+ const existing = await readFile16(input.path, "utf8");
7308
7669
  const frontmatter = extractFrontmatter(existing);
7309
7670
  if (!frontmatter.ok) {
7310
7671
  return err("INGEST_VALIDATION_FAILED", {
@@ -7443,7 +7804,7 @@ async function runIngest(input, pagePublishDeps = defaultPagePublishDeps()) {
7443
7804
  sourceContent = fetchResult.data.body;
7444
7805
  } else {
7445
7806
  try {
7446
- sourceContent = await readFile15(input.source, "utf8");
7807
+ sourceContent = await readFile16(input.source, "utf8");
7447
7808
  } catch {
7448
7809
  return {
7449
7810
  exitCode: ExitCode.FILE_NOT_FOUND,
@@ -7461,14 +7822,14 @@ async function runIngest(input, pagePublishDeps = defaultPagePublishDeps()) {
7461
7822
  })
7462
7823
  };
7463
7824
  }
7464
- const sha2562 = createHash4("sha256").update(Buffer.from(sourceContent, "utf8")).digest("hex");
7825
+ const sha2562 = createHash5("sha256").update(Buffer.from(sourceContent, "utf8")).digest("hex");
7465
7826
  const today = todayIso();
7466
7827
  const slug = slugify2(input.title);
7467
7828
  const tags = input.tags && input.tags.length > 0 ? input.tags : [];
7468
7829
  const rawRelPath = `raw/articles/${slug}.md`;
7469
7830
  const typedDir = TYPE_DIR[input.type] ?? `${input.type}s`;
7470
7831
  const typedRelPath = `${typedDir}/${slug}.md`;
7471
- const rawAbsPath = join31(input.vault, rawRelPath);
7832
+ const rawAbsPath = join32(input.vault, rawRelPath);
7472
7833
  const identity = assessSourceIdentity({
7473
7834
  rawPath: rawRelPath,
7474
7835
  sourceUrl: sourceUrl ?? void 0,
@@ -7512,11 +7873,11 @@ async function runIngest(input, pagePublishDeps = defaultPagePublishDeps()) {
7512
7873
  );
7513
7874
  if (!input.dryRun) {
7514
7875
  try {
7515
- await mkdir10(join31(input.vault, typedDir), { recursive: true });
7876
+ await mkdir11(join32(input.vault, typedDir), { recursive: true });
7516
7877
  } catch (error) {
7517
7878
  return {
7518
7879
  exitCode: ExitCode.WRITE_FAILED,
7519
- result: err("WRITE_FAILED", { path: join31(input.vault, typedDir), message: String(error) })
7880
+ result: err("WRITE_FAILED", { path: join32(input.vault, typedDir), message: String(error) })
7520
7881
  };
7521
7882
  }
7522
7883
  }
@@ -7570,11 +7931,11 @@ async function runIngest(input, pagePublishDeps = defaultPagePublishDeps()) {
7570
7931
  preflight: pagePublishDeps.preflight,
7571
7932
  mutate: async (receipt) => {
7572
7933
  try {
7573
- await mkdir10(join31(input.vault, "raw", "articles"), { recursive: true });
7934
+ await mkdir11(join32(input.vault, "raw", "articles"), { recursive: true });
7574
7935
  } catch (error) {
7575
7936
  return {
7576
7937
  exitCode: ExitCode.WRITE_FAILED,
7577
- result: err("WRITE_FAILED", { path: join31(input.vault, "raw", "articles"), message: String(error) })
7938
+ result: err("WRITE_FAILED", { path: join32(input.vault, "raw", "articles"), message: String(error) })
7578
7939
  };
7579
7940
  }
7580
7941
  const rawWrite = await writeResolvedRaw({
@@ -7810,8 +8171,8 @@ ${body}`;
7810
8171
  }
7811
8172
 
7812
8173
  // src/commands/tag-reconcile.ts
7813
- import { readFile as readFile16 } from "fs/promises";
7814
- import { join as join32, posix } from "path";
8174
+ import { readFile as readFile17 } from "fs/promises";
8175
+ import { join as join33, posix } from "path";
7815
8176
  var TYPED_TARGET_RE = /^(entities|concepts|comparisons|queries|meta)\/[a-z0-9][a-z0-9./_-]*\.md$/;
7816
8177
  function errorExitCode2(error) {
7817
8178
  switch (error) {
@@ -7854,7 +8215,7 @@ function asTagArray(frontmatter, path) {
7854
8215
  async function readTagsFromFile(path) {
7855
8216
  let text;
7856
8217
  try {
7857
- text = await readFile16(path, "utf8");
8218
+ text = await readFile17(path, "utf8");
7858
8219
  } catch (error) {
7859
8220
  if (error.code === "ENOENT") {
7860
8221
  return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path }) };
@@ -7875,7 +8236,7 @@ async function resolveRequestedTags(input, page) {
7875
8236
  result: err("INVALID_FRONTMATTER", { message: "explicit tags must be an array of strings" })
7876
8237
  };
7877
8238
  }
7878
- const source = input.from ?? (explicit.length === 0 ? join32(input.vault, page) : void 0);
8239
+ const source = input.from ?? (explicit.length === 0 ? join33(input.vault, page) : void 0);
7879
8240
  if (!source) return { exitCode: ExitCode.OK, result: ok({ tags: [...new Set(explicit)].sort() }) };
7880
8241
  const sourced = await readTagsFromFile(source);
7881
8242
  if (!sourced.result.ok) return { exitCode: sourced.exitCode, result: sourced.result };
@@ -7886,7 +8247,7 @@ async function resolveRequestedTags(input, page) {
7886
8247
  }
7887
8248
  async function readSchema(schemaPath) {
7888
8249
  try {
7889
- return { exitCode: ExitCode.OK, result: ok(await readFile16(schemaPath, "utf8")) };
8250
+ return { exitCode: ExitCode.OK, result: ok(await readFile17(schemaPath, "utf8")) };
7890
8251
  } catch (error) {
7891
8252
  if (error.code === "ENOENT") {
7892
8253
  return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path: schemaPath }) };
@@ -7914,7 +8275,7 @@ function previewResult(page, tags, reconciled, dryRun, filesChanged) {
7914
8275
  };
7915
8276
  }
7916
8277
  async function reconcileTagsWhileLocked(input, page, tags, comment) {
7917
- const schemaPath = join32(input.vault, "SCHEMA.md");
8278
+ const schemaPath = join33(input.vault, "SCHEMA.md");
7918
8279
  const current = await readSchema(schemaPath);
7919
8280
  if (!current.result.ok) return { exitCode: current.exitCode, result: current.result };
7920
8281
  const next = reconcileTaxonomyDocument(current.result.data, { tags, comment });
@@ -7925,7 +8286,7 @@ async function reconcileTagsWhileLocked(input, page, tags, comment) {
7925
8286
  }
7926
8287
  let verifiedText;
7927
8288
  try {
7928
- verifiedText = await readFile16(schemaPath, "utf8");
8289
+ verifiedText = await readFile17(schemaPath, "utf8");
7929
8290
  } catch (error) {
7930
8291
  return {
7931
8292
  exitCode: ExitCode.WRITE_FAILED,
@@ -7951,7 +8312,7 @@ async function runTagReconcile(input) {
7951
8312
  const comment = taxonomyCommentForPage(page.data, date, input.reason);
7952
8313
  if (!comment.ok) return { exitCode: ExitCode.SCHEME_REJECTED, result: comment };
7953
8314
  if (!input.write) {
7954
- const schema = await readSchema(join32(input.vault, "SCHEMA.md"));
8315
+ const schema = await readSchema(join33(input.vault, "SCHEMA.md"));
7955
8316
  if (!schema.result.ok) return { exitCode: schema.exitCode, result: schema.result };
7956
8317
  const preview = reconcileTaxonomyDocument(schema.result.data, { tags, comment: comment.data });
7957
8318
  return previewResult(page.data, tags, preview, true, []);
@@ -8004,8 +8365,8 @@ async function runTagReconcile(input) {
8004
8365
 
8005
8366
  // src/commands/project-page-publish.ts
8006
8367
  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";
8368
+ import { mkdir as mkdir12, readFile as readFile18 } from "fs/promises";
8369
+ import { dirname as dirname10, join as join34, resolve as resolve6 } from "path";
8009
8370
 
8010
8371
  // src/utils/architecture-page.ts
8011
8372
  import { lstatSync, realpathSync as realpathSync3 } from "fs";
@@ -8177,7 +8538,7 @@ function validateLogNote(logNote) {
8177
8538
  async function prepareProjectPagePublication(input) {
8178
8539
  let content;
8179
8540
  try {
8180
- content = await readFile17(input.draftPath, "utf8");
8541
+ content = await readFile18(input.draftPath, "utf8");
8181
8542
  } catch (error) {
8182
8543
  return err("FILE_NOT_FOUND", { path: input.draftPath, message: String(error) });
8183
8544
  }
@@ -8310,9 +8671,9 @@ var PROJECT_PAGE_PUBLISH_STRATEGY = {
8310
8671
  async updateIndicesLocked(prepared, vault, state, observe) {
8311
8672
  const rendered = await renderProjectIndex(vault, prepared.page.project, { today: prepared.date });
8312
8673
  if (!rendered.ok) return rendered;
8313
- const knowledgePath = join33(vault, rendered.data.index_path);
8674
+ const knowledgePath = join34(vault, rendered.data.index_path);
8314
8675
  try {
8315
- await mkdir11(dirname10(knowledgePath), { recursive: true });
8676
+ await mkdir12(dirname10(knowledgePath), { recursive: true });
8316
8677
  } catch (error) {
8317
8678
  return err("WRITE_FAILED", { message: String(error) });
8318
8679
  }
@@ -8321,7 +8682,7 @@ var PROJECT_PAGE_PUBLISH_STRATEGY = {
8321
8682
  state.projectIndexUpdated = knowledgeWrite.data.changed;
8322
8683
  if (state.projectIndexUpdated) state.changed.add(rendered.data.index_path);
8323
8684
  try {
8324
- const visibleIndex = await readFile17(knowledgePath, "utf8");
8685
+ const visibleIndex = await readFile18(knowledgePath, "utf8");
8325
8686
  if (visibleIndex !== rendered.data.text) {
8326
8687
  return err("WRITE_FAILED", { message: "project knowledge.md verification failed" });
8327
8688
  }
@@ -8341,9 +8702,9 @@ var PROJECT_PAGE_PUBLISH_STRATEGY = {
8341
8702
  }
8342
8703
  let projectIndexChanged = true;
8343
8704
  if (projectIndex.ok) {
8344
- const indexPath = join33(vault, projectIndex.data.index_path);
8705
+ const indexPath = join34(vault, projectIndex.data.index_path);
8345
8706
  try {
8346
- const existing = await readFile17(indexPath, "utf8");
8707
+ const existing = await readFile18(indexPath, "utf8");
8347
8708
  projectIndexChanged = existing !== projectIndex.data.text || pageChanged;
8348
8709
  } catch {
8349
8710
  projectIndexChanged = true;
@@ -8500,10 +8861,10 @@ function runSyncJournalClearStale(input) {
8500
8861
 
8501
8862
  // src/commands/snapshot-maintenance.ts
8502
8863
  import { existsSync as existsSync11, writeFileSync as writeFileSync6, mkdirSync as mkdirSync4, appendFileSync } from "fs";
8503
- import { createHash as createHash5 } from "crypto";
8864
+ import { createHash as createHash6 } from "crypto";
8504
8865
  import { execSync as execSync3, spawn } from "child_process";
8505
8866
  import { platform as platform2 } from "os";
8506
- import { join as join34, resolve as resolvePath } from "path";
8867
+ import { join as join35, resolve as resolvePath } from "path";
8507
8868
  var MAINTENANCE_SCHEMA_VERSION = 1;
8508
8869
  var MAINTENANCE_COMMAND = "snapshot-maintenance journal clear-stale";
8509
8870
  var REPAIR_COMMAND = "snapshot-maintenance projection-conflict repair";
@@ -8527,7 +8888,7 @@ function computeApprovalId(plan) {
8527
8888
  targets,
8528
8889
  normalizeReason(plan.operator_reason)
8529
8890
  ].join("|");
8530
- return "smap1-" + createHash5("sha256").update(payload).digest("hex").slice(0, 32);
8891
+ return "smap1-" + createHash6("sha256").update(payload).digest("hex").slice(0, 32);
8531
8892
  }
8532
8893
  function normalizeReason(reason) {
8533
8894
  return reason.trim().replace(/\s+/g, " ");
@@ -8568,7 +8929,7 @@ async function runSnapshotMaintenanceDryRun(input) {
8568
8929
  audit(makeAuditEvent(input, fleetLoad.hostId, now, "refusal", "MAINTENANCE_WRONG_WORKTREE"));
8569
8930
  return { exitCode: ExitCode.PROTECTED_SNAPSHOTTER_WRITE_BLOCKED, result: refusalErr("MAINTENANCE_WRONG_WORKTREE", `requested path '${requested}' is not the configured snapshot worktree '${canonicalize(configuredWorktree)}'`) };
8570
8931
  }
8571
- if (!existsSync11(requested) || !existsSync11(join34(requested, ".git"))) {
8932
+ if (!existsSync11(requested) || !existsSync11(join35(requested, ".git"))) {
8572
8933
  audit(makeAuditEvent(input, fleetLoad.hostId, now, "refusal", "MAINTENANCE_MISSING_GIT_REPO"));
8573
8934
  return { exitCode: ExitCode.PROTECTED_SNAPSHOTTER_WRITE_BLOCKED, result: refusalErr("MAINTENANCE_MISSING_GIT_REPO", `snapshot worktree is not a git repository: ${requested}`) };
8574
8935
  }
@@ -8648,14 +9009,14 @@ async function resolveLiveVault(input) {
8648
9009
  return resolved.ok ? canonicalize(resolved.data.path) : void 0;
8649
9010
  }
8650
9011
  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");
9012
+ const stateDir = platform2() === "darwin" ? join35(home, "Library", "Application Support", "vault-sync") : join35(home, ".local", "state", "vault-sync");
9013
+ return join35(stateDir, "snapshot-maintenance-audit.jsonl");
8653
9014
  }
8654
9015
  function defaultAuditSink(home) {
8655
9016
  return (event) => {
8656
9017
  try {
8657
9018
  const logPath = snapshotMaintenanceAuditLogPath(home);
8658
- mkdirSync4(join34(logPath, ".."), { recursive: true });
9019
+ mkdirSync4(join35(logPath, ".."), { recursive: true });
8659
9020
  appendFileSync(logPath, JSON.stringify(event) + "\n", { encoding: "utf8" });
8660
9021
  } catch {
8661
9022
  }
@@ -8797,15 +9158,15 @@ function computeRepairApprovalId(plan) {
8797
9158
  paths,
8798
9159
  normalizeReason(plan.operator_reason)
8799
9160
  ].join("|");
8800
- return "smap1-" + createHash5("sha256").update(payload).digest("hex").slice(0, 32);
9161
+ return "smap1-" + createHash6("sha256").update(payload).digest("hex").slice(0, 32);
8801
9162
  }
8802
9163
  function nonMergeSequencerActive(repo) {
8803
9164
  const gitDir = git(repo, ["rev-parse", "--absolute-git-dir"]);
8804
9165
  if (!gitDir) return false;
8805
9166
  for (const m of ["CHERRY_PICK_HEAD", "REVERT_HEAD"]) {
8806
- if (existsSync11(join34(gitDir, m))) return true;
9167
+ if (existsSync11(join35(gitDir, m))) return true;
8807
9168
  }
8808
- return existsSync11(join34(gitDir, "rebase-merge")) || existsSync11(join34(gitDir, "rebase-apply"));
9169
+ return existsSync11(join35(gitDir, "rebase-merge")) || existsSync11(join35(gitDir, "rebase-apply"));
8809
9170
  }
8810
9171
  function extraUnmergedPaths(unmerged) {
8811
9172
  const allow = new Set(PROJECTION_CONFLICT_ALLOWLIST);
@@ -8844,7 +9205,7 @@ async function authorizeRepairContext(input) {
8844
9205
  audit(makeAuditEvent(input, fleetLoad.hostId, now, "refusal", "MAINTENANCE_WRONG_WORKTREE", void 0, REPAIR_COMMAND));
8845
9206
  return { exitCode: ExitCode.PROTECTED_SNAPSHOTTER_WRITE_BLOCKED, result: refusalErr("MAINTENANCE_WRONG_WORKTREE", `requested path '${requested}' is not the configured snapshot worktree '${canonicalize(configuredWorktree)}'`) };
8846
9207
  }
8847
- if (!existsSync11(requested) || !existsSync11(join34(requested, ".git"))) {
9208
+ if (!existsSync11(requested) || !existsSync11(join35(requested, ".git"))) {
8848
9209
  audit(makeAuditEvent(input, fleetLoad.hostId, now, "refusal", "MAINTENANCE_MISSING_GIT_REPO", void 0, REPAIR_COMMAND));
8849
9210
  return { exitCode: ExitCode.PROTECTED_SNAPSHOTTER_WRITE_BLOCKED, result: refusalErr("MAINTENANCE_MISSING_GIT_REPO", `snapshot worktree is not a git repository: ${requested}`) };
8850
9211
  }
@@ -8945,8 +9306,8 @@ async function performProjectionRepair(input, plan, audit, now) {
8945
9306
  return { exitCode: ExitCode.WRITE_FAILED, result: rendered };
8946
9307
  }
8947
9308
  try {
8948
- writeFileSync6(join34(plan.snapshot_worktree, "index.md"), rendered.data.index);
8949
- writeFileSync6(join34(plan.snapshot_worktree, "log.md"), rendered.data.log);
9309
+ writeFileSync6(join35(plan.snapshot_worktree, "index.md"), rendered.data.index);
9310
+ writeFileSync6(join35(plan.snapshot_worktree, "log.md"), rendered.data.log);
8950
9311
  gitStrict(plan.snapshot_worktree, ["add", "--", "index.md", "log.md"]);
8951
9312
  gitStrict(plan.snapshot_worktree, [
8952
9313
  "commit",
@@ -9054,7 +9415,7 @@ function releaseSnapshotFlock(handle) {
9054
9415
 
9055
9416
  // src/commands/backup.ts
9056
9417
  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";
9418
+ import { join as join36, relative as relative3, dirname as dirname11 } from "path";
9058
9419
  import { PutObjectCommand, HeadObjectCommand, ListObjectsV2Command, GetObjectCommand, DeleteObjectsCommand } from "@aws-sdk/client-s3";
9059
9420
 
9060
9421
  // src/utils/s3-client.ts
@@ -9078,7 +9439,7 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([".git", ".obsidian", "_archive", "node_
9078
9439
  function* walkMarkdown(dir, base) {
9079
9440
  for (const entry of readdirSync2(dir, { withFileTypes: true })) {
9080
9441
  if (SKIP_DIRS.has(entry.name)) continue;
9081
- const full = join35(dir, entry.name);
9442
+ const full = join36(dir, entry.name);
9082
9443
  if (entry.isDirectory()) {
9083
9444
  yield* walkMarkdown(full, base);
9084
9445
  } else if (entry.name.endsWith(".md")) {
@@ -9101,7 +9462,7 @@ async function runBackupSync(input) {
9101
9462
  let failed = 0;
9102
9463
  const files = [...walkMarkdown(input.vault, input.vault)];
9103
9464
  for (const relPath of files) {
9104
- const absPath = join35(input.vault, relPath);
9465
+ const absPath = join36(input.vault, relPath);
9105
9466
  const localStat = statSync3(absPath);
9106
9467
  let needsUpload = true;
9107
9468
  try {
@@ -9177,7 +9538,7 @@ async function runBackupRestore(input) {
9177
9538
  const objects = list.Contents ?? [];
9178
9539
  for (const obj of objects) {
9179
9540
  if (!obj.Key) continue;
9180
- const localPath = join35(target, obj.Key);
9541
+ const localPath = join36(target, obj.Key);
9181
9542
  try {
9182
9543
  const localStat = statSync3(localPath);
9183
9544
  if (obj.LastModified && localStat.mtime > obj.LastModified) {
@@ -9224,8 +9585,8 @@ async function runBackupRestore(input) {
9224
9585
 
9225
9586
  // src/commands/status.ts
9226
9587
  import { existsSync as existsSync12, statSync as statSync4 } from "fs";
9227
- import { readFile as readFile18 } from "fs/promises";
9228
- import { join as join36 } from "path";
9588
+ import { readFile as readFile19 } from "fs/promises";
9589
+ import { join as join37 } from "path";
9229
9590
  var FUSE_NO_MIRROR_NOTE = "FUSE vault with no read mirror - status scan may be slow";
9230
9591
  async function runStatus(input) {
9231
9592
  if (!existsSync12(input.vault)) {
@@ -9264,7 +9625,7 @@ async function runStatus(input) {
9264
9625
  const compound = scan.data.compound.length;
9265
9626
  let schemaVersion = "v1";
9266
9627
  try {
9267
- const schemaContent = await readFile18(join36(scanRoot, "SCHEMA.md"), "utf8");
9628
+ const schemaContent = await readFile19(join37(scanRoot, "SCHEMA.md"), "utf8");
9268
9629
  const versionMatch = schemaContent.match(/version:\s*["']?([^"'\s\n]+)/i);
9269
9630
  if (versionMatch) schemaVersion = versionMatch[1];
9270
9631
  } catch {
@@ -9332,13 +9693,13 @@ async function runStatus(input) {
9332
9693
 
9333
9694
  // src/commands/copy-status.ts
9334
9695
  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";
9696
+ import { isAbsolute as isAbsolute2, join as join39, relative as relative5, resolve as resolve8 } from "path";
9336
9697
 
9337
9698
  // src/utils/fetch-projection.ts
9338
- import { isAbsolute, join as join37, resolve as resolve7 } from "path";
9699
+ import { isAbsolute, join as join38, resolve as resolve7 } from "path";
9339
9700
  function inspectConfiguredFetchProjection(home) {
9340
9701
  if (!home) return { configured: false };
9341
- const config = readSkillWikiConfig(join37(home, ".skillwiki", ".env"));
9702
+ const config = readSkillWikiConfig(join38(home, ".skillwiki", ".env"));
9342
9703
  const configured = config["vault_sync.fetch_projection"];
9343
9704
  if (!configured || configured === "none") return { configured: false };
9344
9705
  if (!isAbsolute(configured)) {
@@ -9348,8 +9709,8 @@ function inspectConfiguredFetchProjection(home) {
9348
9709
  }
9349
9710
 
9350
9711
  // src/utils/live-drift.ts
9351
- import { createHash as createHash6 } from "crypto";
9352
- import { readFile as readFile19 } from "fs/promises";
9712
+ import { createHash as createHash7 } from "crypto";
9713
+ import { readFile as readFile20 } from "fs/promises";
9353
9714
  import { relative as relative4, sep as sep3 } from "path";
9354
9715
 
9355
9716
  // src/utils/git-promotion.ts
@@ -9370,7 +9731,7 @@ function isGitPromotablePath(path) {
9370
9731
  // src/utils/live-drift.ts
9371
9732
  function gitBlobOid(bytes) {
9372
9733
  const header = Buffer.from(`blob ${bytes.length}\0`);
9373
- return createHash6("sha1").update(header).update(bytes).digest("hex");
9734
+ return createHash7("sha1").update(header).update(bytes).digest("hex");
9374
9735
  }
9375
9736
  function headBlobOids(projection) {
9376
9737
  const output = git(projection, ["ls-tree", "-rz", "-r", "HEAD"]);
@@ -9394,7 +9755,7 @@ async function measureAuthoritativeLiveDrift(liveVault, projection) {
9394
9755
  const rel = relative4(liveVault, page.absPath).split(sep3).join("/");
9395
9756
  if (!isGitPromotablePath(rel)) continue;
9396
9757
  livePromotable.add(rel);
9397
- const bytes = await readFile19(page.absPath);
9758
+ const bytes = await readFile20(page.absPath);
9398
9759
  if (projectionBlobs.get(rel) !== gitBlobOid(bytes)) content += 1;
9399
9760
  }
9400
9761
  for (const rel of projectionBlobs.keys()) {
@@ -9572,7 +9933,7 @@ function defaultCopyStatusDeps(input) {
9572
9933
  const projectionWarning = selection.configured ? `configured fetch projection ignored for Git status; using live vault${projectionProblem ? ` (${projectionProblem})` : ""}` : void 0;
9573
9934
  const invalidProjectionDetail = projectionProblem ?? "configured fetch projection is not a git repository";
9574
9935
  const gitRootProblem = () => {
9575
- if (!existsSync13(join38(gitVault, ".git"))) {
9936
+ if (!existsSync13(join39(gitVault, ".git"))) {
9576
9937
  return "vault is not a git repository";
9577
9938
  }
9578
9939
  return void 0;
@@ -9640,7 +10001,7 @@ function defaultCopyStatusDeps(input) {
9640
10001
  if (!configuredProjection) {
9641
10002
  return { unknown: true, detail: projectionProblem ?? "live drift requires a configured fetch projection" };
9642
10003
  }
9643
- if (!existsSync13(join38(configuredProjection, ".git"))) {
10004
+ if (!existsSync13(join39(configuredProjection, ".git"))) {
9644
10005
  return { unknown: true, detail: invalidProjectionDetail };
9645
10006
  }
9646
10007
  return measureAuthoritativeLiveDrift(input.vault, configuredProjection);
@@ -9656,8 +10017,8 @@ async function runCopyStatusCommand(input) {
9656
10017
  }
9657
10018
 
9658
10019
  // 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";
10020
+ import { mkdir as mkdir13, writeFile as writeFile8, stat as stat5 } from "fs/promises";
10021
+ import { join as join40 } from "path";
9661
10022
  var TODAY = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
9662
10023
  var EXAMPLE_PAGES = {
9663
10024
  "entities/example-project.md": `---
@@ -9726,29 +10087,29 @@ Real sources are immutable after ingestion \u2014 never edit them.
9726
10087
  `;
9727
10088
  async function runSeed(input) {
9728
10089
  try {
9729
- await stat4(join39(input.vault, "SCHEMA.md"));
10090
+ await stat5(join40(input.vault, "SCHEMA.md"));
9730
10091
  } catch {
9731
10092
  return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { root: input.vault, reason: "SCHEMA.md missing \u2014 run `skillwiki init` first" }) };
9732
10093
  }
9733
10094
  const created = [];
9734
10095
  const skipped = [];
9735
10096
  for (const [relPath, content] of Object.entries(EXAMPLE_PAGES)) {
9736
- const absPath = join39(input.vault, relPath);
10097
+ const absPath = join40(input.vault, relPath);
9737
10098
  try {
9738
- await stat4(absPath);
10099
+ await stat5(absPath);
9739
10100
  skipped.push(relPath);
9740
10101
  } catch {
9741
- await mkdir12(join39(absPath, ".."), { recursive: true });
10102
+ await mkdir13(join40(absPath, ".."), { recursive: true });
9742
10103
  await writeFile8(absPath, content, "utf8");
9743
10104
  created.push(relPath);
9744
10105
  }
9745
10106
  }
9746
- const rawPath = join39(input.vault, "raw", "articles", "example-source.md");
10107
+ const rawPath = join40(input.vault, "raw", "articles", "example-source.md");
9747
10108
  try {
9748
- await stat4(rawPath);
10109
+ await stat5(rawPath);
9749
10110
  skipped.push("raw/articles/example-source.md");
9750
10111
  } catch {
9751
- await mkdir12(join39(rawPath, ".."), { recursive: true });
10112
+ await mkdir13(join40(rawPath, ".."), { recursive: true });
9752
10113
  await writeFile8(rawPath, EXAMPLE_RAW, "utf8");
9753
10114
  created.push("raw/articles/example-source.md");
9754
10115
  }
@@ -9771,9 +10132,9 @@ async function runSeed(input) {
9771
10132
  }
9772
10133
 
9773
10134
  // src/commands/canvas.ts
9774
- import { readFile as readFile20, writeFile as writeFile9 } from "fs/promises";
10135
+ import { readFile as readFile21, writeFile as writeFile9 } from "fs/promises";
9775
10136
  import { existsSync as existsSync14 } from "fs";
9776
- import { join as join40 } from "path";
10137
+ import { join as join41 } from "path";
9777
10138
  var NODE_WIDTH = 240;
9778
10139
  var NODE_HEIGHT = 60;
9779
10140
  var COLUMN_SPACING = 400;
@@ -9851,7 +10212,7 @@ function buildCanvasEdges(adjacency) {
9851
10212
  return edges;
9852
10213
  }
9853
10214
  async function runCanvasGenerate(input) {
9854
- const graphPath = input.graphPath ?? join40(input.vault, ".skillwiki", "graph.json");
10215
+ const graphPath = input.graphPath ?? join41(input.vault, ".skillwiki", "graph.json");
9855
10216
  if (!existsSync14(graphPath)) {
9856
10217
  return {
9857
10218
  exitCode: ExitCode.FILE_NOT_FOUND,
@@ -9863,7 +10224,7 @@ async function runCanvasGenerate(input) {
9863
10224
  }
9864
10225
  let raw;
9865
10226
  try {
9866
- raw = await readFile20(graphPath, "utf8");
10227
+ raw = await readFile21(graphPath, "utf8");
9867
10228
  } catch (e) {
9868
10229
  return {
9869
10230
  exitCode: ExitCode.FILE_NOT_FOUND,
@@ -9889,7 +10250,7 @@ async function runCanvasGenerate(input) {
9889
10250
  const nodes = buildCanvasNodes(paths);
9890
10251
  const edges = buildCanvasEdges(graph.adjacency);
9891
10252
  const canvas = { nodes, edges };
9892
- const outPath = join40(input.vault, "vault-graph.canvas");
10253
+ const outPath = join41(input.vault, "vault-graph.canvas");
9893
10254
  try {
9894
10255
  await writeFile9(outPath, JSON.stringify(canvas, null, 2));
9895
10256
  } catch (e) {
@@ -10033,12 +10394,12 @@ async function runSourceDisposition(input) {
10033
10394
  }
10034
10395
 
10035
10396
  // src/utils/source-disposal.ts
10036
- import { createHash as createHash7 } from "crypto";
10037
- import { readFile as readFile22, unlink as unlink5 } from "fs/promises";
10397
+ import { createHash as createHash8 } from "crypto";
10398
+ import { readFile as readFile23, unlink as unlink5 } from "fs/promises";
10038
10399
 
10039
10400
  // 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";
10401
+ import { readdir as readdir7, readFile as readFile22 } from "fs/promises";
10402
+ import { join as join42, relative as relative6, sep as sep4 } from "path";
10042
10403
  var PREVIEW_EXTENSIONS = /* @__PURE__ */ new Set(["png", "jpg", "jpeg", "gif", "webp", "svg", "bmp", "avif", "pdf", "mp3", "wav", "m4a", "mp4", "webm"]);
10043
10404
  async function walkFiles(root, dir = root) {
10044
10405
  let entries;
@@ -10049,7 +10410,7 @@ async function walkFiles(root, dir = root) {
10049
10410
  }
10050
10411
  const out = [];
10051
10412
  for (const entry of entries) {
10052
- const path = join41(dir, entry.name);
10413
+ const path = join42(dir, entry.name);
10053
10414
  if (entry.isDirectory()) out.push(...await walkFiles(root, path));
10054
10415
  else if (entry.isFile()) out.push(relative6(root, path).split(sep4).join("/"));
10055
10416
  }
@@ -10065,7 +10426,7 @@ function addInbound(map, target, source) {
10065
10426
  map.set(target, refs);
10066
10427
  }
10067
10428
  async function buildRawAssetReferenceIndex(vault) {
10068
- const assetRoot = join41(vault, "raw", "assets");
10429
+ const assetRoot = join42(vault, "raw", "assets");
10069
10430
  const relativeAssets = await walkFiles(assetRoot);
10070
10431
  const assets = relativeAssets.map((path) => `raw/assets/${path}`).sort((a, b) => a.localeCompare(b));
10071
10432
  const assetSet = new Set(assets);
@@ -10081,7 +10442,7 @@ async function buildRawAssetReferenceIndex(vault) {
10081
10442
  for (const page of scan.data.allMarkdown) {
10082
10443
  let text;
10083
10444
  try {
10084
- text = await readFile21(page.absPath, "utf8");
10445
+ text = await readFile22(page.absPath, "utf8");
10085
10446
  } catch {
10086
10447
  continue;
10087
10448
  }
@@ -10117,7 +10478,7 @@ async function buildRawAssetReferenceIndex(vault) {
10117
10478
 
10118
10479
  // src/utils/source-disposal.ts
10119
10480
  function sha256(bytes) {
10120
- return createHash7("sha256").update(bytes).digest("hex");
10481
+ return createHash8("sha256").update(bytes).digest("hex");
10121
10482
  }
10122
10483
  function exactTarget(path) {
10123
10484
  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 +10502,7 @@ async function planSourceDisposal(input) {
10141
10502
  if (sensitive.length > 0) return err("SENSITIVE_CONTENT_DETECTED", { findings: sensitive });
10142
10503
  const target = await resolveExistingRegularFileInsideVault(input.vault, input.rawPath);
10143
10504
  if (!target.ok) return err("RAW_DISPOSAL_TARGET_INVALID", { path: input.rawPath, cause: target });
10144
- const bytes = await readFile22(target.data);
10505
+ const bytes = await readFile23(target.data);
10145
10506
  const size = bytes.byteLength;
10146
10507
  let typedReferences = [];
10147
10508
  let otherReferences = [];
@@ -10265,7 +10626,7 @@ async function runSourceDisposal(input) {
10265
10626
  import { existsSync as existsSync15, readFileSync as readFileSync16 } from "fs";
10266
10627
  import { execSync as nodeExecSync } from "child_process";
10267
10628
  import { hostname as nodeHostname, platform as nodePlatform } from "os";
10268
- import { join as join42 } from "path";
10629
+ import { join as join43 } from "path";
10269
10630
  var SSH_TIMEOUT_MS = 15e3;
10270
10631
  var TIMER_UNIT = "agent-memory-trends.timer";
10271
10632
  var SERVICE_UNIT = "agent-memory-trends.service";
@@ -10495,7 +10856,7 @@ async function runFleetHealth(input) {
10495
10856
  const home = input.home ?? env.HOME ?? "";
10496
10857
  const osHostname = input.osHostname ?? env.HOSTNAME ?? nodeHostname();
10497
10858
  const vault = input.vault ?? env.WIKI_PATH;
10498
- const file = input.file ?? (vault ? join42(vault, FLEET_REL_PATH) : void 0);
10859
+ const file = input.file ?? (vault ? join43(vault, FLEET_REL_PATH) : void 0);
10499
10860
  if (!file) {
10500
10861
  return {
10501
10862
  exitCode: ExitCode.NO_VAULT_CONFIGURED,
@@ -10580,7 +10941,7 @@ async function runFleetHealth(input) {
10580
10941
 
10581
10942
  // src/utils/auto-commit.ts
10582
10943
  import { existsSync as existsSync16 } from "fs";
10583
- import { isAbsolute as isAbsolute3, join as join43, normalize } from "path";
10944
+ import { isAbsolute as isAbsolute3, join as join44, normalize } from "path";
10584
10945
  function isValidLastOpPath(file) {
10585
10946
  if (!file || typeof file !== "string") return false;
10586
10947
  const trimmed = file.trim();
@@ -10601,7 +10962,7 @@ async function postCommit(vault, exitCode) {
10601
10962
  const dotenv = await parseDotenvFile(configPath(home));
10602
10963
  const autoCommit = process.env.AUTO_COMMIT ?? dotenv["AUTO_COMMIT"];
10603
10964
  if (autoCommit === "false") return;
10604
- if (!existsSync16(join43(vault, ".git"))) return;
10965
+ if (!existsSync16(join44(vault, ".git"))) return;
10605
10966
  const lastOps = readLastOp(vault);
10606
10967
  if (lastOps.length === 0) return;
10607
10968
  const porcelain = git(vault, ["status", "--porcelain", "--", ...VAULT_COMMIT_PATHSPEC]);
@@ -10641,90 +11002,6 @@ async function postCommit(vault, exitCode) {
10641
11002
 
10642
11003
  // src/commands/mcp-auth.ts
10643
11004
  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 writerIdFromValue(value) {
10650
- if (typeof value === "string" && value.length > 0) return value;
10651
- if (value && typeof value === "object" && !Array.isArray(value)) {
10652
- const rec = value;
10653
- if (typeof rec.writer_id === "string" && rec.writer_id.length > 0) return rec.writer_id;
10654
- if (typeof rec.host_id === "string" && rec.host_id.length > 0) return rec.host_id;
10655
- }
10656
- return void 0;
10657
- }
10658
- function parseAllowedVaultIds(raw) {
10659
- if (!raw || raw.length === 0) return { vaults: [] };
10660
- const vaults = [];
10661
- const seen = /* @__PURE__ */ new Set();
10662
- for (const item of raw) {
10663
- const id = item.trim();
10664
- if (!id) continue;
10665
- if (id.includes("*") || id.includes("/") || !HOST_ID_RE.test(id)) {
10666
- return { error: "INVALID_ALLOWED_VAULTS" };
10667
- }
10668
- if (seen.has(id)) continue;
10669
- seen.add(id);
10670
- vaults.push(id);
10671
- }
10672
- return { vaults };
10673
- }
10674
- function parseMcpTokenMap(yamlText) {
10675
- const parsed = yaml.load(yamlText);
10676
- const map = /* @__PURE__ */ new Map();
10677
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
10678
- return map;
10679
- }
10680
- const records = parsed;
10681
- const nested = records.tokens;
10682
- const source = nested && typeof nested === "object" && !Array.isArray(nested) ? nested : records;
10683
- for (const [hash, value] of Object.entries(source)) {
10684
- if (hash === "tokens") continue;
10685
- const writerId = writerIdFromValue(value);
10686
- if (!writerId) continue;
10687
- const normalized = hash.trim().toLowerCase();
10688
- if (!/^[0-9a-f]{64}$/.test(normalized)) continue;
10689
- map.set(normalized, writerId);
10690
- }
10691
- return map;
10692
- }
10693
- function generateHostBearer(rng) {
10694
- const bytes = (rng ?? (() => randomBytes(32)))();
10695
- const raw = bytes.toString("base64url");
10696
- const hashHex = createHash8("sha256").update(raw, "utf8").digest("hex");
10697
- return { raw, hashHex };
10698
- }
10699
- function formatHostRow(hashHex, hostId, allowedVaults) {
10700
- if (allowedVaults.length === 0) {
10701
- return `${hashHex}: ${hostId}
10702
- `;
10703
- }
10704
- return `${hashHex}:
10705
- writer_id: ${hostId}
10706
- allowed_vaults: [${allowedVaults.join(", ")}]
10707
- `;
10708
- }
10709
- function appendHostHash(yamlText, hashHex, hostId, allowedVaults) {
10710
- if (!HOST_ID_RE.test(hostId)) {
10711
- return { error: "INVALID_HOST_ID" };
10712
- }
10713
- const granted = parseAllowedVaultIds(allowedVaults);
10714
- if ("error" in granted) return granted;
10715
- const map = parseMcpTokenMap(yamlText);
10716
- const normalizedHash = hashHex.trim().toLowerCase();
10717
- if ([...map.values()].includes(hostId)) {
10718
- return { error: "DUPLICATE_HOST_ID" };
10719
- }
10720
- if (map.has(normalizedHash)) {
10721
- return { error: "DUPLICATE_HASH" };
10722
- }
10723
- return { yaml: yamlText.replace(/\s*$/, "") + `
10724
- ${formatHostRow(normalizedHash, hostId, granted.vaults)}` };
10725
- }
10726
-
10727
- // src/commands/mcp-auth.ts
10728
11005
  function isEnoent(error) {
10729
11006
  return error.code === "ENOENT";
10730
11007
  }
@@ -10733,7 +11010,7 @@ function preflight(error) {
10733
11010
  }
10734
11011
  async function runMcpAuthIssueHost(input) {
10735
11012
  const isTty = input.isTty ?? Boolean(process.stdin.isTTY && process.stdout.isTTY);
10736
- const readFile23 = input.readFile ?? readFileSync17;
11013
+ const readFile24 = input.readFile ?? readFileSync17;
10737
11014
  const writeFile10 = input.writeFile ?? writeFileSync8;
10738
11015
  const stderrWrite = input.stderrWrite ?? ((s) => {
10739
11016
  process.stderr.write(s);
@@ -10748,7 +11025,7 @@ async function runMcpAuthIssueHost(input) {
10748
11025
  if (write && !isTty) return preflight("NO_TTY");
10749
11026
  let yamlText = "";
10750
11027
  try {
10751
- yamlText = readFile23(mapPath, "utf8");
11028
+ yamlText = readFile24(mapPath, "utf8");
10752
11029
  } catch (error) {
10753
11030
  if (!isEnoent(error)) throw error;
10754
11031
  }
@@ -10930,7 +11207,7 @@ async function emitManagedVaultWrite(vault, command, mutate, opts) {
10930
11207
  if (dirty) {
10931
11208
  return emit(dirty, void 0, { postCommit: false });
10932
11209
  }
10933
- const { runManagedWriteTransaction: runManagedWriteTransaction2 } = await import("./managed-write-preflight-32523BPV.js");
11210
+ const { runManagedWriteTransaction: runManagedWriteTransaction2 } = await import("./managed-write-preflight-ATDFVLJV.js");
10934
11211
  const run = await runManagedWriteTransaction2({
10935
11212
  vault,
10936
11213
  command,
@@ -10960,7 +11237,7 @@ program.command("validate <file>").description("validate vault page frontmatter
10960
11237
  emit(await runValidate({ file, apply: !!opts.apply, vault }), vault);
10961
11238
  });
10962
11239
  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) => {
10963
- const out = opts.out ?? join44(vault, ".skillwiki", "graph.json");
11240
+ const out = opts.out ?? join45(vault, ".skillwiki", "graph.json");
10964
11241
  return emitGuardedVaultWrite(vault, "graph build", () => runGraphBuild({ vault, out }));
10965
11242
  });
10966
11243
  var canvasCmd = program.command("canvas").description("manage Obsidian canvas files");
@@ -11571,7 +11848,18 @@ program.command("doctor").description("diagnose skillwiki setup issues").option(
11571
11848
  currentVersion: pkg.version,
11572
11849
  cwd: process.cwd(),
11573
11850
  checkSnapshotter: !!opts.checkSnapshotter,
11574
- checkMcp: !!opts.checkMcp
11851
+ checkMcp: !!opts.checkMcp,
11852
+ env: process.env
11853
+ })));
11854
+ 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({
11855
+ home: process.env.HOME ?? "",
11856
+ fromFile: opts.fromFile,
11857
+ fromStdin: !!opts.fromStdin,
11858
+ dryRun: !!opts.dryRun,
11859
+ force: !!opts.force,
11860
+ checkMcp: !opts.dryRun,
11861
+ currentVersion: pkg.version,
11862
+ env: process.env
11575
11863
  })));
11576
11864
  program.command("status [vault]").description("output vault diagnostics").option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
11577
11865
  const v = await resolveVaultArg(vault, opts.wiki);
@@ -11961,7 +12249,7 @@ program.command("observe [vault]").description("create a raw transcript observat
11961
12249
  })
11962
12250
  );
11963
12251
  });
11964
- program.command("session-brief [vault]").description("render or refresh the bounded startup session brief").option("--project <slug>", "project slug, or auto for deterministic detection", "auto").option("--write", "write meta/latest-session-brief.md and local cache files", false).option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
12252
+ program.command("session-brief [vault]").description("render or refresh the bounded startup session brief").option("--project <slug>", "project slug, or auto for deterministic detection", "auto").option("--write", "write meta/latest-session-brief.md and local cache files", false).option("--agent-memory-run-state <path>", "explicit agent-memory latest-run JSON path").option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
11965
12253
  const v = await resolveVaultArg(vault, opts.wiki);
11966
12254
  if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
11967
12255
  else if (opts.write) return emitManagedVaultWrite(
@@ -11971,6 +12259,7 @@ program.command("session-brief [vault]").description("render or refresh the boun
11971
12259
  vault: v.vault,
11972
12260
  project: opts.project,
11973
12261
  write: true,
12262
+ agentMemoryRunState: opts.agentMemoryRunState,
11974
12263
  cwd: process.cwd(),
11975
12264
  env: { SKILLWIKI_PROJECT: process.env.SKILLWIKI_PROJECT }
11976
12265
  }),
@@ -11980,6 +12269,7 @@ program.command("session-brief [vault]").description("render or refresh the boun
11980
12269
  vault: v.vault,
11981
12270
  project: opts.project,
11982
12271
  write: false,
12272
+ agentMemoryRunState: opts.agentMemoryRunState,
11983
12273
  cwd: process.cwd(),
11984
12274
  env: { SKILLWIKI_PROJECT: process.env.SKILLWIKI_PROJECT }
11985
12275
  }), v.vault, { postCommit: false });