skillwiki 0.10.111 → 0.10.113

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.
Files changed (44) hide show
  1. package/dist/{chunk-I6IRJCUJ.js → chunk-3TKNAOJH.js} +2 -2
  2. package/dist/chunk-5D5HFQ76.js +111 -0
  3. package/dist/{chunk-OJJBZDPR.js → chunk-5RHR4FEK.js} +183 -211
  4. package/dist/{chunk-GQWCTJTU.js → chunk-6VUK2FTX.js} +1 -1
  5. package/dist/{chunk-JQV5KNPT.js → chunk-CBT35FAW.js} +3 -3
  6. package/dist/{chunk-HBJLFPQP.js → chunk-FQERKMD7.js} +458 -18
  7. package/dist/{chunk-PYPPG7RF.js → chunk-IJKJGQKL.js} +1 -1
  8. package/dist/{chunk-MRPJ7X7W.js → chunk-IJWZDQDH.js} +3 -3
  9. package/dist/{chunk-X5GL2FCU.js → chunk-JL2SCLXT.js} +1 -1
  10. package/dist/chunk-K3M6OSUW.js +77 -0
  11. package/dist/{chunk-5IBLXGLY.js → chunk-K3YZESND.js} +2 -2
  12. package/dist/chunk-OGEAFXON.js +60 -0
  13. package/dist/{chunk-6OQLBNKR.js → chunk-OWWWO2XS.js} +29 -4
  14. package/dist/{chunk-KWEYA3OE.js → chunk-SEY4N4CT.js} +1 -1
  15. package/dist/{chunk-UIEQIJXP.js → chunk-VNSZCK5V.js} +47 -44
  16. package/dist/{chunk-TDBUJUYH.js → chunk-XJ5VEIH7.js} +42 -18
  17. package/dist/cli.js +586 -122
  18. package/dist/{index-projection-2K672CBO.js → index-projection-RQSOEDXM.js} +5 -5
  19. package/dist/{journal-QU5IDXQF.js → journal-V5XHBEI5.js} +3 -3
  20. package/dist/{managed-write-preflight-GRSHQYKC.js → managed-write-preflight-2BMHZWVJ.js} +7 -7
  21. package/dist/mcp-auth-env-QONKW4EO.js +25 -0
  22. package/dist/{migrate-PIB4QA54.js → migrate-SRRCXI2K.js} +6 -5
  23. package/dist/mirror-H5BTBAON.js +151 -0
  24. package/dist/runtime-LQZIG3XN.js +20 -0
  25. package/dist/skillwiki-mcp.js +14 -12
  26. package/dist/sources-E6KHDRDE.js +10 -0
  27. package/dist/{watcher-MHG46J3R.js → watcher-VJ3TOU6J.js} +20 -110
  28. package/package.json +1 -1
  29. package/skills/.claude-plugin/plugin.json +1 -1
  30. package/skills/.codex-plugin/plugin.json +1 -1
  31. package/skills/.cursor-plugin/plugin.json +1 -1
  32. package/skills/package.json +1 -1
  33. package/skills/skills/using-skillwiki/SKILL.md +1 -1
  34. package/skills/skills/wiki-ingest/SKILL.md +47 -0
  35. package/skills/skills/wiki-init/SKILL.md +3 -0
  36. package/skills/skills/wiki-lint/SKILL.md +7 -0
  37. package/skills/using-skillwiki/SKILL.md +1 -1
  38. package/skills/wiki-ingest/SKILL.md +47 -0
  39. package/skills/wiki-init/SKILL.md +3 -0
  40. package/skills/wiki-lint/SKILL.md +7 -0
  41. package/templates/launchd/com.karlchow.skillwiki-mirror-sse.plist +20 -0
  42. package/dist/chunk-I2J3IJB7.js +0 -54
  43. package/dist/runtime-GLMI5OCT.js +0 -15
  44. package/dist/sources-D2MGP246.js +0 -10
package/dist/cli.js CHANGED
@@ -1,8 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- runFetchGuard,
4
- runFetchGuardSync
5
- } from "./chunk-I2J3IJB7.js";
6
2
  import {
7
3
  buildDeleteIntent,
8
4
  normalizeVaultRelPath,
@@ -16,9 +12,6 @@ import {
16
12
  DEFAULT_MCP_URL,
17
13
  DEFAULT_NO_DECISION_STREAK,
18
14
  GateError,
19
- MCP_HOST_ID_ENV,
20
- MCP_TOKEN_ENV,
21
- MCP_URL_ENV,
22
15
  PUSH_NOT_IN_PROFILE,
23
16
  SATELLITE_STALE_MS,
24
17
  buildVectorIndex,
@@ -31,17 +24,13 @@ import {
31
24
  guardProtectedVaultWrite,
32
25
  isEventLedgerPath,
33
26
  isFailedRunStatus,
34
- mcpAuthFromEnv,
35
27
  measureDirtyVolume,
36
28
  memoryAuthorityTiersRank,
37
- mergeMcpAuthIntoEnv,
38
- nonEmptyEnvValue,
39
29
  pruneVectorIndex,
40
30
  readCliPackageJson,
41
31
  readSatelliteLatestRunAt,
42
32
  readSatelliteLatestRunFromText,
43
33
  readVaultSyncConfig,
44
- redactMcpSecret,
45
34
  reindexPageInVectorIndex,
46
35
  renderIndexUpsert,
47
36
  renderProjectIndex,
@@ -75,7 +64,7 @@ import {
75
64
  upsertIndexEntry,
76
65
  vectorIndexStatus,
77
66
  withMcpClient
78
- } from "./chunk-OJJBZDPR.js";
67
+ } from "./chunk-5RHR4FEK.js";
79
68
  import {
80
69
  applySourceDisposition,
81
70
  decodeSourceActionApproval,
@@ -83,7 +72,7 @@ import {
83
72
  inventorySources,
84
73
  planSourceDisposition,
85
74
  runSourcesPending
86
- } from "./chunk-JQV5KNPT.js";
75
+ } from "./chunk-CBT35FAW.js";
87
76
  import {
88
77
  normalizeDistTag,
89
78
  readCache,
@@ -96,7 +85,7 @@ import {
96
85
  UNMANAGED_START,
97
86
  renderRootIndex,
98
87
  writeRootIndexProjection
99
- } from "./chunk-MRPJ7X7W.js";
88
+ } from "./chunk-IJWZDQDH.js";
100
89
  import {
101
90
  FLEET_REL_PATH,
102
91
  REDACTED_MALFORMED_REFERENCE,
@@ -124,6 +113,7 @@ import {
124
113
  gitStrict,
125
114
  hasActiveGitSequencer,
126
115
  hasUnmergedPaths,
116
+ isAdvisoryLintWarning,
127
117
  isValidRemoteDeleteCap,
128
118
  isWorktreeClean,
129
119
  listJournalOpIds,
@@ -184,7 +174,7 @@ import {
184
174
  stageVaultContentChanges,
185
175
  supersedeStaleReviewRequiredJournals,
186
176
  taxonomyCommentForPage
187
- } from "./chunk-HBJLFPQP.js";
177
+ } from "./chunk-FQERKMD7.js";
188
178
  import {
189
179
  authorizeRawOperation,
190
180
  buildSourceReferenceIndex,
@@ -201,11 +191,11 @@ import {
201
191
  resolveExistingRegularFileInsideVault,
202
192
  validateLogEvent,
203
193
  writeLogEvent
204
- } from "./chunk-5IBLXGLY.js";
194
+ } from "./chunk-K3YZESND.js";
205
195
  import {
206
196
  assertTargetInsideVault,
207
197
  prepareTypedPage
208
- } from "./chunk-I6IRJCUJ.js";
198
+ } from "./chunk-3TKNAOJH.js";
209
199
  import {
210
200
  extractFrontmatter,
211
201
  mapWithConcurrency,
@@ -217,25 +207,38 @@ import {
217
207
  scanVault,
218
208
  splitFrontmatter,
219
209
  vaultIoConcurrency
220
- } from "./chunk-X5GL2FCU.js";
210
+ } from "./chunk-JL2SCLXT.js";
211
+ import {
212
+ MCP_HOST_ID_ENV,
213
+ MCP_TOKEN_ENV,
214
+ MCP_URL_ENV,
215
+ mcpAuthFromEnv,
216
+ mergeMcpAuthIntoEnv,
217
+ nonEmptyEnvValue,
218
+ redactMcpSecret
219
+ } from "./chunk-OGEAFXON.js";
221
220
  import {
222
221
  CLIP_MIGRATION_HINT,
223
222
  refreshInstalledClipPlist
224
- } from "./chunk-TDBUJUYH.js";
223
+ } from "./chunk-XJ5VEIH7.js";
224
+ import {
225
+ atomicWriteText
226
+ } from "./chunk-6VUK2FTX.js";
225
227
  import {
226
228
  CLIP_NOT_CONFIGURED,
227
229
  loadClipOutboxConfig
228
- } from "./chunk-6OQLBNKR.js";
230
+ } from "./chunk-OWWWO2XS.js";
231
+ import {
232
+ runFetchGuard,
233
+ runFetchGuardSync
234
+ } from "./chunk-5D5HFQ76.js";
229
235
  import {
230
236
  parseDotenvFile,
231
237
  parseDotenvText,
232
238
  profileKey,
233
239
  resolveSkillwikiHome,
234
240
  writeDotenv
235
- } from "./chunk-PYPPG7RF.js";
236
- import {
237
- atomicWriteText
238
- } from "./chunk-GQWCTJTU.js";
241
+ } from "./chunk-IJKJGQKL.js";
239
242
  import {
240
243
  ExitCode,
241
244
  MetaSchema,
@@ -245,10 +248,10 @@ import {
245
248
  err,
246
249
  ok,
247
250
  resolveSessionKind
248
- } from "./chunk-UIEQIJXP.js";
251
+ } from "./chunk-VNSZCK5V.js";
249
252
 
250
253
  // src/cli.ts
251
- import { join as join44 } from "path";
254
+ import { join as join45 } from "path";
252
255
  import { Command } from "commander";
253
256
 
254
257
  // src/utils/output.ts
@@ -1599,21 +1602,21 @@ function rollbackPath(vault, path, stage2, stage3) {
1599
1602
  function hasMarkers(path, text) {
1600
1603
  const lines = text.split(/\r?\n/);
1601
1604
  let open2 = false;
1602
- let sep5 = false;
1605
+ let sep6 = false;
1603
1606
  for (const line of lines) {
1604
1607
  if (line.startsWith("<<<<<<< ")) {
1605
1608
  open2 = true;
1606
- sep5 = false;
1609
+ sep6 = false;
1607
1610
  continue;
1608
1611
  }
1609
1612
  if (line === "=======" && open2) {
1610
- sep5 = true;
1613
+ sep6 = true;
1611
1614
  continue;
1612
1615
  }
1613
- if (line.startsWith(">>>>>>> ") && open2 && sep5) return true;
1616
+ if (line.startsWith(">>>>>>> ") && open2 && sep6) return true;
1614
1617
  if (line.startsWith(">>>>>>> ")) {
1615
1618
  open2 = false;
1616
- sep5 = false;
1619
+ sep6 = false;
1617
1620
  }
1618
1621
  }
1619
1622
  return false;
@@ -3476,7 +3479,11 @@ async function runHealth(input) {
3476
3479
  checks: doctor.result.data.checks.map((check) => ({ id: check.id, status: check.status, detail: check.detail }))
3477
3480
  };
3478
3481
  const lintComponent = {
3479
- status: statusFromCounts({ error: lint.result.data.summary.errors, warnings: lint.result.data.summary.warnings, info: lint.result.data.summary.info }),
3482
+ status: statusFromCounts({
3483
+ error: lint.result.data.summary.errors,
3484
+ warnings: lint.result.data.buckets.filter((bucket) => bucket.severity === "warning" && !isAdvisoryLintWarning(bucket.kind)).reduce((n, bucket) => n + bucket.count, 0),
3485
+ info: lint.result.data.summary.info
3486
+ }),
3480
3487
  blocking: true,
3481
3488
  summary: lint.result.data.summary,
3482
3489
  buckets: lint.result.data.buckets,
@@ -3583,7 +3590,7 @@ async function runClipOutboxStatus(home) {
3583
3590
  result: ok({ enabled: false, humanHint: CLIP_NOT_CONFIGURED })
3584
3591
  };
3585
3592
  }
3586
- const { readJournal: readJournal2 } = await import("./journal-QU5IDXQF.js");
3593
+ const { readJournal: readJournal2 } = await import("./journal-V5XHBEI5.js");
3587
3594
  let queue;
3588
3595
  try {
3589
3596
  queue = (await readJournal2(resolved.config.stateRoot)).map(({ captureId, state, reason, attempts }) => ({ captureId, state, reason, attempts }));
@@ -3601,7 +3608,7 @@ async function runClipOutboxStatus(home) {
3601
3608
  }
3602
3609
  async function runClipOutboxInstall(input) {
3603
3610
  try {
3604
- const { migrateClipOutbox } = await import("./migrate-PIB4QA54.js");
3611
+ const { migrateClipOutbox } = await import("./migrate-SRRCXI2K.js");
3605
3612
  return { exitCode: ExitCode.OK, result: ok(await migrateClipOutbox(input)) };
3606
3613
  } catch (e) {
3607
3614
  return { exitCode: ExitCode.PREFLIGHT_FAILED, result: err("PREFLIGHT_FAILED", { message: e instanceof Error ? e.message : "clip migration failed" }) };
@@ -3614,10 +3621,10 @@ async function runClipOutboxWatch(home) {
3614
3621
  let client;
3615
3622
  try {
3616
3623
  const { S3Client: S3Client3 } = await import("@aws-sdk/client-s3");
3617
- const { loadClipCredentials, validateRuntime } = await import("./runtime-GLMI5OCT.js");
3624
+ const { loadClipCredentials, validateRuntime } = await import("./runtime-LQZIG3XN.js");
3618
3625
  const { createS3CreateOnlyPut, parseDestination } = await import("./upload-HTE62TOV.js");
3619
3626
  const { postReadyHint } = await import("./ready-S7F53Q2D.js");
3620
- const { startClipWorker } = await import("./watcher-MHG46J3R.js");
3627
+ const { startClipWorker } = await import("./watcher-VJ3TOU6J.js");
3621
3628
  const cfg = resolved.config;
3622
3629
  const credentials = await loadClipCredentials(home, cfg);
3623
3630
  await validateRuntime(home, cfg, credentials.s3.endpoint);
@@ -3653,6 +3660,47 @@ async function runClipOutboxWatch(home) {
3653
3660
  client?.destroy();
3654
3661
  }
3655
3662
  }
3663
+ async function runClipOutboxMirrorWatch(home) {
3664
+ const resolved = await loadClipOutboxConfig(home);
3665
+ if (!resolved.ok || !resolved.enabled) return { exitCode: ExitCode.INVALID_CONFIG_VALUE, result: err("INVALID_CONFIG_VALUE", { message: resolved.ok ? CLIP_NOT_CONFIGURED : resolved.message }) };
3666
+ let client;
3667
+ const abort = new AbortController();
3668
+ try {
3669
+ const { S3Client: S3Client3 } = await import("@aws-sdk/client-s3");
3670
+ const { loadClipCredentials, validateRuntime } = await import("./runtime-LQZIG3XN.js");
3671
+ const { createS3GetObject, eventsUrlFromMcp, parseDestinationForMirror, startMirrorListener } = await import("./mirror-H5BTBAON.js");
3672
+ const { resolveMcpAuthEnv } = await import("./mcp-auth-env-QONKW4EO.js");
3673
+ const cfg = resolved.config;
3674
+ const credentials = await loadClipCredentials(home, cfg);
3675
+ await validateRuntime(home, cfg, credentials.s3.endpoint);
3676
+ const auth = await resolveMcpAuthEnv({ home });
3677
+ if (!auth.url || !auth.token) throw new Error("MCP bearer required for inbound SSE");
3678
+ const destination = parseDestinationForMirror(cfg.destination);
3679
+ client = new S3Client3({ endpoint: credentials.s3.endpoint, region: credentials.s3.region, credentials: credentials.s3, forcePathStyle: true, maxAttempts: 1 });
3680
+ const done = new Promise((resolve10) => {
3681
+ abort.signal.addEventListener("abort", () => resolve10(), { once: true });
3682
+ });
3683
+ process.once("SIGTERM", () => abort.abort());
3684
+ process.once("SIGINT", () => abort.abort());
3685
+ await Promise.race([
3686
+ startMirrorListener({
3687
+ home,
3688
+ config: cfg,
3689
+ eventsUrl: eventsUrlFromMcp(auth.url),
3690
+ mcpToken: auth.token,
3691
+ getObject: createS3GetObject({ client, bucket: destination.bucket, prefix: destination.prefix, maxBytes: cfg.maxBundleBytes }),
3692
+ signal: abort.signal
3693
+ }),
3694
+ done
3695
+ ]);
3696
+ return { exitCode: ExitCode.OK, result: ok({ humanHint: "Inbound SSE listener stopped." }) };
3697
+ } catch {
3698
+ return { exitCode: ExitCode.PREFLIGHT_FAILED, result: err("PREFLIGHT_FAILED", { message: "inbound SSE listener startup failed; check MCP bearer, clip credentials, frozen mirror and wiki-push isolation" }) };
3699
+ } finally {
3700
+ abort.abort();
3701
+ client?.destroy();
3702
+ }
3703
+ }
3656
3704
 
3657
3705
  // src/commands/connect.ts
3658
3706
  import { chmod, mkdir as mkdir5, readFile as readFile6, stat as stat3 } from "fs/promises";
@@ -4132,7 +4180,7 @@ ${fmRewritten}
4132
4180
  }
4133
4181
  let indexUpdated = false;
4134
4182
  if (!isRaw) {
4135
- const { renderRootIndex: renderRootIndex2, writeRootIndexProjection: writeRootIndexProjection2 } = await import("./index-projection-2K672CBO.js");
4183
+ const { renderRootIndex: renderRootIndex2, writeRootIndexProjection: writeRootIndexProjection2 } = await import("./index-projection-RQSOEDXM.js");
4136
4184
  const before = await readFile7(join19(input.vault, "index.md"), "utf8").catch(() => "");
4137
4185
  const fullTarget = relPath.replace(/\.md$/, "");
4138
4186
  const bare = fullTarget.split("/").pop() ?? fullTarget;
@@ -4253,10 +4301,10 @@ async function runRemove(input) {
4253
4301
  const tombstonePath = await writeDeleteIntent(input.vault, intent);
4254
4302
  await unlink2(join20(input.vault, relPath));
4255
4303
  if (relPath.endsWith(".md") && !relPath.startsWith("raw/")) {
4256
- const { readFile: readFile23 } = await import("fs/promises");
4304
+ const { readFile: readFile24 } = await import("fs/promises");
4257
4305
  const { join: pathJoin } = await import("path");
4258
- const { renderRootIndex: renderRootIndex2, writeRootIndexProjection: writeRootIndexProjection2 } = await import("./index-projection-2K672CBO.js");
4259
- const before = await readFile23(pathJoin(input.vault, "index.md"), "utf8").catch(() => "");
4306
+ const { renderRootIndex: renderRootIndex2, writeRootIndexProjection: writeRootIndexProjection2 } = await import("./index-projection-RQSOEDXM.js");
4307
+ const before = await readFile24(pathJoin(input.vault, "index.md"), "utf8").catch(() => "");
4260
4308
  const fullTarget = relPath.replace(/\.md$/, "");
4261
4309
  const bare = fullTarget.split("/").pop() ?? fullTarget;
4262
4310
  const hadIndexEntry = before.includes(`[[${fullTarget}]]`) || before.includes(`[[${bare}]]`);
@@ -6839,8 +6887,8 @@ async function runPipelineLockedPrimaryStages(strategy, prepared, vault, deps, j
6839
6887
  if (!strategy.usesJournal || !phaseAtLeast(currentPhase, "page")) {
6840
6888
  if (strategy.publisherKind === "project-page") {
6841
6889
  try {
6842
- const { mkdir: mkdir13 } = await import("fs/promises");
6843
- await mkdir13(dirname7(prepared.targetPath), { recursive: true });
6890
+ const { mkdir: mkdir14 } = await import("fs/promises");
6891
+ await mkdir14(dirname7(prepared.targetPath), { recursive: true });
6844
6892
  } catch (error) {
6845
6893
  return {
6846
6894
  ok: false,
@@ -7647,7 +7695,7 @@ function buildRawContent(sourceUrl, ingested, sha2562, body) {
7647
7695
  ];
7648
7696
  return lines.join("\n");
7649
7697
  }
7650
- function buildTypedContent(title, ingested, type, tags, rawRelPath, provenance) {
7698
+ function buildTypedContent(title, ingested, type, tags, rawRelPath, provenance, html) {
7651
7699
  const aliases = [];
7652
7700
  const sourcesYaml = ` - ${rawRelPath}`;
7653
7701
  const tagsYaml = tags.length > 0 ? tags.map((t) => ` - ${t}`).join("\n") : " []";
@@ -7670,6 +7718,10 @@ function buildTypedContent(title, ingested, type, tags, rawRelPath, provenance)
7670
7718
  if (provenance) {
7671
7719
  fmLines.push(`provenance: ${provenance}`);
7672
7720
  }
7721
+ fmLines.push("output_standard: v2");
7722
+ if (html) {
7723
+ fmLines.push("html_render: true");
7724
+ }
7673
7725
  fmLines.push("---");
7674
7726
  fmLines.push("");
7675
7727
  const body = [
@@ -7753,7 +7805,7 @@ async function acquireRawCaptureLock(path) {
7753
7805
  if (error.code !== "EEXIST") {
7754
7806
  return err("WRITE_FAILED", { path: lockPath, phase: "raw-lock", message: String(error) });
7755
7807
  }
7756
- await new Promise((resolve9) => setTimeout(resolve9, 10));
7808
+ await new Promise((resolve10) => setTimeout(resolve10, 10));
7757
7809
  }
7758
7810
  }
7759
7811
  return err("WRITE_FAILED", { path: lockPath, phase: "raw-lock", message: "raw capture lock held" });
@@ -7892,7 +7944,8 @@ async function runIngest(input, pagePublishDeps = defaultPagePublishDeps()) {
7892
7944
  input.type,
7893
7945
  tags,
7894
7946
  rawRelPath,
7895
- input.provenance
7947
+ input.provenance,
7948
+ input.html
7896
7949
  );
7897
7950
  if (!input.dryRun) {
7898
7951
  try {
@@ -7983,7 +8036,8 @@ async function runIngest(input, pagePublishDeps = defaultPagePublishDeps()) {
7983
8036
  input.type,
7984
8037
  tags,
7985
8038
  rawRelPath,
7986
- input.provenance
8039
+ input.provenance,
8040
+ input.html
7987
8041
  );
7988
8042
  const refreshedPublication = preparePagePublicationFromContent({
7989
8043
  vault: input.vault,
@@ -8386,14 +8440,412 @@ async function runTagReconcile(input) {
8386
8440
  };
8387
8441
  }
8388
8442
 
8443
+ // src/commands/page-render-html.ts
8444
+ import { lstat, mkdir as mkdir11, readFile as readFile17, realpath } from "fs/promises";
8445
+ import { basename, dirname as dirname9, join as join33, relative as relative2, resolve as resolve5, sep as sep2 } from "path";
8446
+ function escapeHtml(text) {
8447
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
8448
+ }
8449
+ var SAFE_HREF_RE = /^(https?:\/\/|mailto:|#)/i;
8450
+ function escapeAttr(text) {
8451
+ return escapeHtml(text).replace(/'/g, "&#39;");
8452
+ }
8453
+ function slugifyHeading(text, used) {
8454
+ const base = text.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "section";
8455
+ const n = used.get(base) ?? 0;
8456
+ used.set(base, n + 1);
8457
+ return n === 0 ? base : `${base}-${n + 1}`;
8458
+ }
8459
+ function renderInlineMarkdown(raw) {
8460
+ const re = /(`[^`]+`)|(\[\[(?:[^\]]+)\]\])|(!\[[^\]]*\]\([^)]+\))|(\[[^\]]+\]\([^)]+\))|(\*\*[^*]+\*\*)|(\*[^*]+\*)/g;
8461
+ const parts = [];
8462
+ let last = 0;
8463
+ let match;
8464
+ while ((match = re.exec(raw)) !== null) {
8465
+ parts.push(escapeHtml(raw.slice(last, match.index)));
8466
+ const token = match[0];
8467
+ if (token.startsWith("`")) {
8468
+ parts.push(`<code>${escapeHtml(token.slice(1, -1))}</code>`);
8469
+ } else if (token.startsWith("[[")) {
8470
+ const inner = token.slice(2, -2);
8471
+ const pipe = inner.indexOf("|");
8472
+ const label = pipe === -1 ? inner : inner.slice(pipe + 1);
8473
+ parts.push(escapeHtml(label.trim()));
8474
+ } else if (token.startsWith("![")) {
8475
+ const alt = token.match(/^!\[([^\]]*)\]/)?.[1] ?? "";
8476
+ parts.push(escapeHtml(alt));
8477
+ } else if (token.startsWith("[")) {
8478
+ const linked = token.match(/^\[([^\]]+)\]\(([^)]+)\)$/);
8479
+ if (linked && SAFE_HREF_RE.test(linked[2])) {
8480
+ parts.push(`<a href="${escapeAttr(linked[2])}">${escapeHtml(linked[1])}</a>`);
8481
+ } else if (linked) {
8482
+ parts.push(escapeHtml(linked[1]));
8483
+ } else {
8484
+ parts.push(escapeHtml(token));
8485
+ }
8486
+ } else if (token.startsWith("**")) {
8487
+ parts.push(`<strong>${escapeHtml(token.slice(2, -2))}</strong>`);
8488
+ } else {
8489
+ parts.push(`<em>${escapeHtml(token.slice(1, -1))}</em>`);
8490
+ }
8491
+ last = match.index + token.length;
8492
+ }
8493
+ parts.push(escapeHtml(raw.slice(last)));
8494
+ return parts.join("");
8495
+ }
8496
+ function parseTableRow(line) {
8497
+ const trimmed = line.trim();
8498
+ const inner = trimmed.startsWith("|") ? trimmed.slice(1) : trimmed;
8499
+ const withoutEnd = inner.endsWith("|") ? inner.slice(0, -1) : inner;
8500
+ return withoutEnd.split("|").map((cell) => cell.trim());
8501
+ }
8502
+ function isTableSeparator(line) {
8503
+ return /^\s*\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)+\|?\s*$/.test(line);
8504
+ }
8505
+ function parseMarkdownBlocks(body) {
8506
+ const lines = body.split(/\r?\n/);
8507
+ const blocks = [];
8508
+ let i = 0;
8509
+ while (i < lines.length) {
8510
+ const line = lines[i];
8511
+ const fence = line.match(/^(\s*)(`{3,}|~{3,})(.*)$/);
8512
+ if (fence) {
8513
+ const marker = fence[2];
8514
+ const language = fence[3].trim();
8515
+ const content = [];
8516
+ i++;
8517
+ while (i < lines.length) {
8518
+ const close = lines[i].match(/^(\s*)(`{3,}|~{3,})\s*$/);
8519
+ if (close && close[2][0] === marker[0] && close[2].length >= marker.length) break;
8520
+ content.push(lines[i]);
8521
+ i++;
8522
+ }
8523
+ blocks.push({ kind: "code", language, text: content.join("\n") });
8524
+ i++;
8525
+ continue;
8526
+ }
8527
+ const heading = line.match(/^(#{1,6})\s+(\S.*)$/);
8528
+ if (heading) {
8529
+ blocks.push({ kind: "heading", level: heading[1].length, text: heading[2].trim() });
8530
+ i++;
8531
+ continue;
8532
+ }
8533
+ if (/^\s*\|.*\|\s*$/.test(line) && i + 1 < lines.length && isTableSeparator(lines[i + 1])) {
8534
+ const rows = [parseTableRow(line)];
8535
+ i += 2;
8536
+ while (i < lines.length && /^\s*\|.*\|\s*$/.test(lines[i]) && !isTableSeparator(lines[i])) {
8537
+ rows.push(parseTableRow(lines[i]));
8538
+ i++;
8539
+ }
8540
+ blocks.push({ kind: "table", rows });
8541
+ continue;
8542
+ }
8543
+ const ordered = line.match(/^\s*\d+\.\s+(.*)$/);
8544
+ const unordered = line.match(/^\s*[-*+]\s+(.*)$/);
8545
+ if (ordered || unordered) {
8546
+ const isOrdered = Boolean(ordered);
8547
+ const itemRe = isOrdered ? /^\s*\d+\.\s+(.*)$/ : /^\s*[-*+]\s+(.*)$/;
8548
+ const items = [];
8549
+ while (i < lines.length) {
8550
+ const item = lines[i].match(itemRe);
8551
+ if (!item) break;
8552
+ const chunks = [item[1]];
8553
+ i++;
8554
+ while (i < lines.length && /^\s+\S/.test(lines[i]) && !itemRe.test(lines[i]) && !/^(#{1,6})\s+/.test(lines[i])) {
8555
+ chunks.push(lines[i].trim());
8556
+ i++;
8557
+ }
8558
+ items.push(chunks.join(" "));
8559
+ }
8560
+ blocks.push({ kind: "list", ordered: isOrdered, items });
8561
+ continue;
8562
+ }
8563
+ if (!line.trim()) {
8564
+ i++;
8565
+ continue;
8566
+ }
8567
+ const para = [line.trim()];
8568
+ i++;
8569
+ while (i < lines.length) {
8570
+ const next = lines[i];
8571
+ if (!next.trim()) break;
8572
+ if (/^(#{1,6})\s+/.test(next) || /^(\s*)(`{3,}|~{3,})/.test(next) || /^\s*\|.*\|\s*$/.test(next) || /^\s*\d+\.\s+/.test(next) || /^\s*[-*+]\s+/.test(next)) {
8573
+ break;
8574
+ }
8575
+ para.push(next.trim());
8576
+ i++;
8577
+ }
8578
+ blocks.push({ kind: "paragraph", text: para.join(" ") });
8579
+ }
8580
+ return blocks;
8581
+ }
8582
+ function renderBlock(block) {
8583
+ switch (block.kind) {
8584
+ case "heading": {
8585
+ const tag = `h${Math.min(block.level, 6)}`;
8586
+ return `<${tag}>${renderInlineMarkdown(block.text)}</${tag}>`;
8587
+ }
8588
+ case "paragraph":
8589
+ return `<p>${renderInlineMarkdown(block.text)}</p>`;
8590
+ case "list": {
8591
+ const tag = block.ordered ? "ol" : "ul";
8592
+ const items = block.items.map((item) => `<li>${renderInlineMarkdown(item)}</li>`).join("");
8593
+ return `<${tag}>${items}</${tag}>`;
8594
+ }
8595
+ case "table": {
8596
+ if (block.rows.length === 0) return "";
8597
+ const [header, ...body] = block.rows;
8598
+ const thead = `<thead><tr>${header.map((cell) => `<th>${renderInlineMarkdown(cell)}</th>`).join("")}</tr></thead>`;
8599
+ const tbody = body.length > 0 ? `<tbody>${body.map((row) => `<tr>${row.map((cell) => `<td>${renderInlineMarkdown(cell)}</td>`).join("")}</tr>`).join("")}</tbody>` : "";
8600
+ return `<table>${thead}${tbody}</table>`;
8601
+ }
8602
+ case "code": {
8603
+ const cls = block.language.toLowerCase() === "mermaid" ? ' class="mermaid"' : "";
8604
+ return `<pre${cls}><code>${escapeHtml(block.text)}</code></pre>`;
8605
+ }
8606
+ }
8607
+ }
8608
+ function groupSections(blocks) {
8609
+ const used = /* @__PURE__ */ new Map();
8610
+ let title = "Page";
8611
+ const intro = [];
8612
+ const sections = [];
8613
+ let current = null;
8614
+ for (const block of blocks) {
8615
+ if (block.kind === "heading" && block.level === 1 && title === "Page") {
8616
+ title = block.text;
8617
+ continue;
8618
+ }
8619
+ if (block.kind === "heading" && block.level === 2) {
8620
+ current = { id: slugifyHeading(block.text, used), title: block.text, blocks: [] };
8621
+ sections.push(current);
8622
+ continue;
8623
+ }
8624
+ if (current) current.blocks.push(block);
8625
+ else intro.push(block);
8626
+ }
8627
+ return { title, intro, sections };
8628
+ }
8629
+ function renderPageHtml(title, body) {
8630
+ const parsed = parseMarkdownBlocks(body);
8631
+ const grouped = groupSections(parsed);
8632
+ const pageTitle = grouped.title !== "Page" ? grouped.title : title;
8633
+ const sections = grouped.sections.length > 0 ? grouped.sections : [{ id: "content", title: pageTitle, blocks: grouped.intro }];
8634
+ const introHtml = grouped.sections.length > 0 && grouped.intro.length > 0 ? grouped.intro.map(renderBlock).join("\n") : "";
8635
+ const toc = sections.map((section) => `<li><a href="#${escapeAttr(section.id)}">${escapeHtml(section.title)}</a></li>`).join("");
8636
+ const tabs = sections.map(
8637
+ (section, index) => `<button type="button" role="tab" aria-selected="${index === 0 ? "true" : "false"}" aria-controls="${escapeAttr(section.id)}" data-tab="${escapeAttr(section.id)}">${escapeHtml(section.title)}</button>`
8638
+ ).join("");
8639
+ const panels = sections.map((section, index) => {
8640
+ const inner = section.blocks.map(renderBlock).join("\n");
8641
+ return `<section id="${escapeAttr(section.id)}" class="tab-panel" role="tabpanel"${index === 0 ? "" : " hidden"}><details open><summary>${escapeHtml(section.title)}</summary>
8642
+ ${inner}
8643
+ </details></section>`;
8644
+ }).join("\n");
8645
+ return `<!DOCTYPE html>
8646
+ <html lang="en">
8647
+ <head>
8648
+ <meta charset="utf-8">
8649
+ <meta name="viewport" content="width=device-width, initial-scale=1">
8650
+ <title>${escapeHtml(pageTitle)}</title>
8651
+ <style>
8652
+ :root { color-scheme: light dark; --bg: #f7f5f0; --fg: #1b1b1b; --accent: #355c7d; --line: #cfc8bc; }
8653
+ body { margin: 0 auto; max-width: 52rem; padding: 1.5rem; font: 16px/1.5 system-ui, sans-serif; background: var(--bg); color: var(--fg); }
8654
+ nav.toc { border: 1px solid var(--line); padding: 0.75rem 1rem; margin-bottom: 1rem; }
8655
+ nav.toc ol { margin: 0; padding-left: 1.25rem; }
8656
+ .tabs { display: flex; flex-wrap: wrap; gap: 0.35rem; margin: 1rem 0; }
8657
+ .tabs button { border: 1px solid var(--line); background: transparent; color: inherit; padding: 0.35rem 0.7rem; cursor: pointer; }
8658
+ .tabs button[aria-selected="true"] { background: var(--accent); color: #fff; }
8659
+ .tab-panel { border: 1px solid var(--line); padding: 0.75rem; }
8660
+ details { margin: 0; }
8661
+ summary { cursor: pointer; font-weight: 600; }
8662
+ pre { overflow: auto; padding: 0.75rem; background: #111; color: #f5f5f5; }
8663
+ pre.mermaid { white-space: pre-wrap; }
8664
+ table { border-collapse: collapse; width: 100%; }
8665
+ th, td { border: 1px solid var(--line); padding: 0.35rem 0.5rem; text-align: left; }
8666
+ a { color: var(--accent); }
8667
+ </style>
8668
+ </head>
8669
+ <body>
8670
+ <nav class="toc" id="toc"><strong>Contents</strong><ol>${toc}</ol></nav>
8671
+ ${introHtml}
8672
+ <div class="tabs" role="tablist">${tabs}</div>
8673
+ ${panels}
8674
+ <script>
8675
+ (function () {
8676
+ var tabs = document.querySelectorAll('[role="tab"]');
8677
+ var panels = document.querySelectorAll('.tab-panel');
8678
+ function activate(id) {
8679
+ if (!id) return;
8680
+ var found = false;
8681
+ panels.forEach(function (panel) { if (panel.id === id) found = true; });
8682
+ if (!found) return;
8683
+ tabs.forEach(function (tab) {
8684
+ tab.setAttribute("aria-selected", tab.getAttribute("data-tab") === id ? "true" : "false");
8685
+ });
8686
+ panels.forEach(function (panel) { panel.hidden = panel.id !== id; });
8687
+ }
8688
+ tabs.forEach(function (tab) {
8689
+ tab.addEventListener("click", function () {
8690
+ activate(tab.getAttribute("data-tab"));
8691
+ });
8692
+ });
8693
+ document.querySelectorAll("nav.toc a[href^='#']").forEach(function (link) {
8694
+ link.addEventListener("click", function () {
8695
+ activate((link.getAttribute("href") || "").replace(/^#/, ""));
8696
+ });
8697
+ });
8698
+ function fromHash() {
8699
+ activate((location.hash || "").replace(/^#/, ""));
8700
+ }
8701
+ window.addEventListener("hashchange", fromHash);
8702
+ fromHash();
8703
+ })();
8704
+ </script>
8705
+ </body>
8706
+ </html>
8707
+ `;
8708
+ }
8709
+ function isInsideCanonical(root, candidate) {
8710
+ const rel = relative2(root, candidate).split(sep2).join("/");
8711
+ return rel === "" || rel !== ".." && !rel.startsWith("../");
8712
+ }
8713
+ function outInsideVaultError(vault, out, message) {
8714
+ return err("HTML_RENDER_OUT_INSIDE_VAULT", { vault, out, message });
8715
+ }
8716
+ async function canonicalizeWithMissingSuffix(abs) {
8717
+ try {
8718
+ await lstat(abs);
8719
+ return await realpath(abs);
8720
+ } catch (error) {
8721
+ if (error.code !== "ENOENT") throw error;
8722
+ }
8723
+ const missing = [];
8724
+ let current = abs;
8725
+ while (true) {
8726
+ missing.unshift(basename(current));
8727
+ const parent = dirname9(current);
8728
+ try {
8729
+ await lstat(parent);
8730
+ return join33(await realpath(parent), ...missing);
8731
+ } catch (error) {
8732
+ if (error.code !== "ENOENT") throw error;
8733
+ if (parent === current) return join33(parent, ...missing);
8734
+ current = parent;
8735
+ }
8736
+ }
8737
+ }
8738
+ async function resolveOutOutsideVault(vault, out) {
8739
+ let vaultReal;
8740
+ try {
8741
+ vaultReal = await realpath(vault);
8742
+ } catch (error) {
8743
+ return err("VAULT_PATH_INVALID", { message: "vault realpath failed", cause: String(error) });
8744
+ }
8745
+ const abs = resolve5(out);
8746
+ try {
8747
+ const info = await lstat(abs);
8748
+ if (info.isSymbolicLink()) {
8749
+ return outInsideVaultError(vault, out, "--out must be a regular path outside the vault");
8750
+ }
8751
+ } catch (error) {
8752
+ if (error.code !== "ENOENT") {
8753
+ return err("WRITE_FAILED", { path: abs, message: String(error) });
8754
+ }
8755
+ }
8756
+ let canonical;
8757
+ try {
8758
+ canonical = await canonicalizeWithMissingSuffix(abs);
8759
+ } catch (error) {
8760
+ return err("WRITE_FAILED", { path: abs, message: String(error) });
8761
+ }
8762
+ if (isInsideCanonical(vaultReal, canonical)) {
8763
+ return outInsideVaultError(vault, out, "--out must be outside the vault");
8764
+ }
8765
+ return ok(canonical);
8766
+ }
8767
+ async function runPageRenderHtml(input) {
8768
+ if (!input.out || input.out.trim().length === 0) {
8769
+ return {
8770
+ exitCode: ExitCode.USAGE,
8771
+ result: err("USAGE", { message: "--out is required" })
8772
+ };
8773
+ }
8774
+ const pagePath = await resolveExistingRegularFileInsideVault(input.vault, input.page);
8775
+ if (!pagePath.ok) {
8776
+ return {
8777
+ exitCode: pagePath.error === "FILE_NOT_FOUND" ? ExitCode.FILE_NOT_FOUND : ExitCode.VAULT_PATH_INVALID,
8778
+ result: pagePath
8779
+ };
8780
+ }
8781
+ const outPath = await resolveOutOutsideVault(input.vault, input.out);
8782
+ if (!outPath.ok) {
8783
+ const exitCode = outPath.error === "HTML_RENDER_OUT_INSIDE_VAULT" ? ExitCode.SCHEME_REJECTED : outPath.error === "WRITE_FAILED" ? ExitCode.WRITE_FAILED : ExitCode.VAULT_PATH_INVALID;
8784
+ return { exitCode, result: outPath };
8785
+ }
8786
+ let text;
8787
+ try {
8788
+ text = await readFile17(pagePath.data, "utf8");
8789
+ } catch {
8790
+ return {
8791
+ exitCode: ExitCode.FILE_NOT_FOUND,
8792
+ result: err("FILE_NOT_FOUND", { path: input.page })
8793
+ };
8794
+ }
8795
+ const fm = extractFrontmatter(text);
8796
+ if (!fm.ok) {
8797
+ return {
8798
+ exitCode: ExitCode.INVALID_FRONTMATTER,
8799
+ result: fm
8800
+ };
8801
+ }
8802
+ if (fm.data.html_render !== true) {
8803
+ return {
8804
+ exitCode: ExitCode.HTML_RENDER_NOT_OPTED_IN,
8805
+ result: err("HTML_RENDER_NOT_OPTED_IN", {
8806
+ page: input.page,
8807
+ message: "page frontmatter must set html_render: true"
8808
+ })
8809
+ };
8810
+ }
8811
+ const split = splitFrontmatter(text);
8812
+ if (!split.ok) {
8813
+ return { exitCode: ExitCode.MISSING_CLOSING_DELIMITER, result: split };
8814
+ }
8815
+ const title = typeof fm.data.title === "string" ? fm.data.title : input.page;
8816
+ const html = renderPageHtml(title, split.data.body);
8817
+ const outAbs = outPath.data;
8818
+ try {
8819
+ await mkdir11(dirname9(outAbs), { recursive: true });
8820
+ } catch (error) {
8821
+ return {
8822
+ exitCode: ExitCode.WRITE_FAILED,
8823
+ result: err("WRITE_FAILED", { path: outAbs, message: String(error) })
8824
+ };
8825
+ }
8826
+ const written = await atomicWriteText(outAbs, html);
8827
+ if (!written.ok) {
8828
+ return { exitCode: ExitCode.WRITE_FAILED, result: written };
8829
+ }
8830
+ return {
8831
+ exitCode: ExitCode.OK,
8832
+ result: ok({
8833
+ page: input.page,
8834
+ out: outAbs,
8835
+ bytes: Buffer.byteLength(html, "utf8"),
8836
+ humanHint: `Wrote ${outAbs} (${Buffer.byteLength(html, "utf8")} bytes)`
8837
+ })
8838
+ };
8839
+ }
8840
+
8389
8841
  // src/commands/project-page-publish.ts
8390
8842
  import { realpathSync as realpathSync4 } from "fs";
8391
- import { mkdir as mkdir11, readFile as readFile17 } from "fs/promises";
8392
- import { dirname as dirname10, join as join33, resolve as resolve6 } from "path";
8843
+ import { mkdir as mkdir12, readFile as readFile18 } from "fs/promises";
8844
+ import { dirname as dirname11, join as join34, resolve as resolve7 } from "path";
8393
8845
 
8394
8846
  // src/utils/architecture-page.ts
8395
8847
  import { lstatSync, realpathSync as realpathSync3 } from "fs";
8396
- import { dirname as dirname9, posix as posix2, relative as relative2, resolve as resolve5, sep as sep2 } from "path";
8848
+ import { dirname as dirname10, posix as posix2, relative as relative3, resolve as resolve6, sep as sep3 } from "path";
8397
8849
  var PROJECT_SLUG_RE = /^[a-z0-9][a-z0-9-]*$/;
8398
8850
  var ARCH_TARGET_RE = /^projects\/([a-z0-9][a-z0-9-]*)\/architecture\/([a-z0-9][a-z0-9._-]*\.md)$/;
8399
8851
  function invalidFrontmatter(target, issues) {
@@ -8439,15 +8891,15 @@ function assertArchitectureTargetInsideVault(vault, target, project) {
8439
8891
  } catch {
8440
8892
  return err("VAULT_PATH_INVALID", { target, message: "vault realpath failed" });
8441
8893
  }
8442
- const absolutePath = resolve5(vaultReal, target);
8443
- const parent = dirname9(absolutePath);
8894
+ const absolutePath = resolve6(vaultReal, target);
8895
+ const parent = dirname10(absolutePath);
8444
8896
  let parentReal;
8445
8897
  try {
8446
8898
  parentReal = realpathSync3(parent);
8447
8899
  } catch {
8448
8900
  return err("VAULT_PATH_INVALID", { target, message: "target parent realpath failed" });
8449
8901
  }
8450
- const parentRelative = relative2(vaultReal, parentReal).split(sep2).join("/");
8902
+ const parentRelative = relative3(vaultReal, parentReal).split(sep3).join("/");
8451
8903
  if (parentRelative === ".." || parentRelative.startsWith("../")) {
8452
8904
  return err("VAULT_PATH_INVALID", { target, message: "target parent escapes vault" });
8453
8905
  }
@@ -8561,7 +9013,7 @@ function validateLogNote(logNote) {
8561
9013
  async function prepareProjectPagePublication(input) {
8562
9014
  let content;
8563
9015
  try {
8564
- content = await readFile17(input.draftPath, "utf8");
9016
+ content = await readFile18(input.draftPath, "utf8");
8565
9017
  } catch (error) {
8566
9018
  return err("FILE_NOT_FOUND", { path: input.draftPath, message: String(error) });
8567
9019
  }
@@ -8576,7 +9028,7 @@ async function prepareProjectPagePublication(input) {
8576
9028
  }
8577
9029
  const target = assertArchitectureTargetInsideVault(input.vault, input.target, input.project);
8578
9030
  if (!target.ok) return target;
8579
- if (resolve6(input.draftPath) === target.data.absolutePath || target.data.existingRealPath !== void 0 && draftRealPath === target.data.existingRealPath) {
9031
+ if (resolve7(input.draftPath) === target.data.absolutePath || target.data.existingRealPath !== void 0 && draftRealPath === target.data.existingRealPath) {
8580
9032
  return err("VAULT_PATH_INVALID", { message: "draft must not alias the final target" });
8581
9033
  }
8582
9034
  const isNew = target.data.existingRealPath === void 0;
@@ -8694,9 +9146,9 @@ var PROJECT_PAGE_PUBLISH_STRATEGY = {
8694
9146
  async updateIndicesLocked(prepared, vault, state, observe) {
8695
9147
  const rendered = await renderProjectIndex(vault, prepared.page.project, { today: prepared.date });
8696
9148
  if (!rendered.ok) return rendered;
8697
- const knowledgePath = join33(vault, rendered.data.index_path);
9149
+ const knowledgePath = join34(vault, rendered.data.index_path);
8698
9150
  try {
8699
- await mkdir11(dirname10(knowledgePath), { recursive: true });
9151
+ await mkdir12(dirname11(knowledgePath), { recursive: true });
8700
9152
  } catch (error) {
8701
9153
  return err("WRITE_FAILED", { message: String(error) });
8702
9154
  }
@@ -8705,7 +9157,7 @@ var PROJECT_PAGE_PUBLISH_STRATEGY = {
8705
9157
  state.projectIndexUpdated = knowledgeWrite.data.changed;
8706
9158
  if (state.projectIndexUpdated) state.changed.add(rendered.data.index_path);
8707
9159
  try {
8708
- const visibleIndex = await readFile17(knowledgePath, "utf8");
9160
+ const visibleIndex = await readFile18(knowledgePath, "utf8");
8709
9161
  if (visibleIndex !== rendered.data.text) {
8710
9162
  return err("WRITE_FAILED", { message: "project knowledge.md verification failed" });
8711
9163
  }
@@ -8725,9 +9177,9 @@ var PROJECT_PAGE_PUBLISH_STRATEGY = {
8725
9177
  }
8726
9178
  let projectIndexChanged = true;
8727
9179
  if (projectIndex.ok) {
8728
- const indexPath = join33(vault, projectIndex.data.index_path);
9180
+ const indexPath = join34(vault, projectIndex.data.index_path);
8729
9181
  try {
8730
- const existing = await readFile17(indexPath, "utf8");
9182
+ const existing = await readFile18(indexPath, "utf8");
8731
9183
  projectIndexChanged = existing !== projectIndex.data.text || pageChanged;
8732
9184
  } catch {
8733
9185
  projectIndexChanged = true;
@@ -8887,7 +9339,7 @@ import { existsSync as existsSync11, writeFileSync as writeFileSync6, mkdirSync
8887
9339
  import { createHash as createHash6 } from "crypto";
8888
9340
  import { execSync as execSync3, spawn } from "child_process";
8889
9341
  import { platform as platform2 } from "os";
8890
- import { join as join34, resolve as resolvePath } from "path";
9342
+ import { join as join35, resolve as resolvePath } from "path";
8891
9343
  var MAINTENANCE_SCHEMA_VERSION = 1;
8892
9344
  var MAINTENANCE_COMMAND = "snapshot-maintenance journal clear-stale";
8893
9345
  var REPAIR_COMMAND = "snapshot-maintenance projection-conflict repair";
@@ -8952,7 +9404,7 @@ async function runSnapshotMaintenanceDryRun(input) {
8952
9404
  audit(makeAuditEvent(input, fleetLoad.hostId, now, "refusal", "MAINTENANCE_WRONG_WORKTREE"));
8953
9405
  return { exitCode: ExitCode.PROTECTED_SNAPSHOTTER_WRITE_BLOCKED, result: refusalErr("MAINTENANCE_WRONG_WORKTREE", `requested path '${requested}' is not the configured snapshot worktree '${canonicalize(configuredWorktree)}'`) };
8954
9406
  }
8955
- if (!existsSync11(requested) || !existsSync11(join34(requested, ".git"))) {
9407
+ if (!existsSync11(requested) || !existsSync11(join35(requested, ".git"))) {
8956
9408
  audit(makeAuditEvent(input, fleetLoad.hostId, now, "refusal", "MAINTENANCE_MISSING_GIT_REPO"));
8957
9409
  return { exitCode: ExitCode.PROTECTED_SNAPSHOTTER_WRITE_BLOCKED, result: refusalErr("MAINTENANCE_MISSING_GIT_REPO", `snapshot worktree is not a git repository: ${requested}`) };
8958
9410
  }
@@ -9032,14 +9484,14 @@ async function resolveLiveVault(input) {
9032
9484
  return resolved.ok ? canonicalize(resolved.data.path) : void 0;
9033
9485
  }
9034
9486
  function snapshotMaintenanceAuditLogPath(home) {
9035
- const stateDir = platform2() === "darwin" ? join34(home, "Library", "Application Support", "vault-sync") : join34(home, ".local", "state", "vault-sync");
9036
- return join34(stateDir, "snapshot-maintenance-audit.jsonl");
9487
+ const stateDir = platform2() === "darwin" ? join35(home, "Library", "Application Support", "vault-sync") : join35(home, ".local", "state", "vault-sync");
9488
+ return join35(stateDir, "snapshot-maintenance-audit.jsonl");
9037
9489
  }
9038
9490
  function defaultAuditSink(home) {
9039
9491
  return (event) => {
9040
9492
  try {
9041
9493
  const logPath = snapshotMaintenanceAuditLogPath(home);
9042
- mkdirSync4(join34(logPath, ".."), { recursive: true });
9494
+ mkdirSync4(join35(logPath, ".."), { recursive: true });
9043
9495
  appendFileSync(logPath, JSON.stringify(event) + "\n", { encoding: "utf8" });
9044
9496
  } catch {
9045
9497
  }
@@ -9187,9 +9639,9 @@ function nonMergeSequencerActive(repo) {
9187
9639
  const gitDir = git(repo, ["rev-parse", "--absolute-git-dir"]);
9188
9640
  if (!gitDir) return false;
9189
9641
  for (const m of ["CHERRY_PICK_HEAD", "REVERT_HEAD"]) {
9190
- if (existsSync11(join34(gitDir, m))) return true;
9642
+ if (existsSync11(join35(gitDir, m))) return true;
9191
9643
  }
9192
- return existsSync11(join34(gitDir, "rebase-merge")) || existsSync11(join34(gitDir, "rebase-apply"));
9644
+ return existsSync11(join35(gitDir, "rebase-merge")) || existsSync11(join35(gitDir, "rebase-apply"));
9193
9645
  }
9194
9646
  function extraUnmergedPaths(unmerged) {
9195
9647
  const allow = new Set(PROJECTION_CONFLICT_ALLOWLIST);
@@ -9228,7 +9680,7 @@ async function authorizeRepairContext(input) {
9228
9680
  audit(makeAuditEvent(input, fleetLoad.hostId, now, "refusal", "MAINTENANCE_WRONG_WORKTREE", void 0, REPAIR_COMMAND));
9229
9681
  return { exitCode: ExitCode.PROTECTED_SNAPSHOTTER_WRITE_BLOCKED, result: refusalErr("MAINTENANCE_WRONG_WORKTREE", `requested path '${requested}' is not the configured snapshot worktree '${canonicalize(configuredWorktree)}'`) };
9230
9682
  }
9231
- if (!existsSync11(requested) || !existsSync11(join34(requested, ".git"))) {
9683
+ if (!existsSync11(requested) || !existsSync11(join35(requested, ".git"))) {
9232
9684
  audit(makeAuditEvent(input, fleetLoad.hostId, now, "refusal", "MAINTENANCE_MISSING_GIT_REPO", void 0, REPAIR_COMMAND));
9233
9685
  return { exitCode: ExitCode.PROTECTED_SNAPSHOTTER_WRITE_BLOCKED, result: refusalErr("MAINTENANCE_MISSING_GIT_REPO", `snapshot worktree is not a git repository: ${requested}`) };
9234
9686
  }
@@ -9329,8 +9781,8 @@ async function performProjectionRepair(input, plan, audit, now) {
9329
9781
  return { exitCode: ExitCode.WRITE_FAILED, result: rendered };
9330
9782
  }
9331
9783
  try {
9332
- writeFileSync6(join34(plan.snapshot_worktree, "index.md"), rendered.data.index);
9333
- writeFileSync6(join34(plan.snapshot_worktree, "log.md"), rendered.data.log);
9784
+ writeFileSync6(join35(plan.snapshot_worktree, "index.md"), rendered.data.index);
9785
+ writeFileSync6(join35(plan.snapshot_worktree, "log.md"), rendered.data.log);
9334
9786
  gitStrict(plan.snapshot_worktree, ["add", "--", "index.md", "log.md"]);
9335
9787
  gitStrict(plan.snapshot_worktree, [
9336
9788
  "commit",
@@ -9438,7 +9890,7 @@ function releaseSnapshotFlock(handle) {
9438
9890
 
9439
9891
  // src/commands/backup.ts
9440
9892
  import { statSync as statSync3, readdirSync as readdirSync2, readFileSync as readFileSync15, mkdirSync as mkdirSync5, writeFileSync as writeFileSync7 } from "fs";
9441
- import { join as join35, relative as relative3, dirname as dirname11 } from "path";
9893
+ import { join as join36, relative as relative4, dirname as dirname12 } from "path";
9442
9894
  import { PutObjectCommand, HeadObjectCommand, ListObjectsV2Command, GetObjectCommand, DeleteObjectsCommand } from "@aws-sdk/client-s3";
9443
9895
 
9444
9896
  // src/utils/s3-client.ts
@@ -9462,11 +9914,11 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([".git", ".obsidian", "_archive", "node_
9462
9914
  function* walkMarkdown(dir, base) {
9463
9915
  for (const entry of readdirSync2(dir, { withFileTypes: true })) {
9464
9916
  if (SKIP_DIRS.has(entry.name)) continue;
9465
- const full = join35(dir, entry.name);
9917
+ const full = join36(dir, entry.name);
9466
9918
  if (entry.isDirectory()) {
9467
9919
  yield* walkMarkdown(full, base);
9468
9920
  } else if (entry.name.endsWith(".md")) {
9469
- yield relative3(base, full).replace(/\\/g, "/");
9921
+ yield relative4(base, full).replace(/\\/g, "/");
9470
9922
  }
9471
9923
  }
9472
9924
  }
@@ -9485,7 +9937,7 @@ async function runBackupSync(input) {
9485
9937
  let failed = 0;
9486
9938
  const files = [...walkMarkdown(input.vault, input.vault)];
9487
9939
  for (const relPath of files) {
9488
- const absPath = join35(input.vault, relPath);
9940
+ const absPath = join36(input.vault, relPath);
9489
9941
  const localStat = statSync3(absPath);
9490
9942
  let needsUpload = true;
9491
9943
  try {
@@ -9561,7 +10013,7 @@ async function runBackupRestore(input) {
9561
10013
  const objects = list.Contents ?? [];
9562
10014
  for (const obj of objects) {
9563
10015
  if (!obj.Key) continue;
9564
- const localPath = join35(target, obj.Key);
10016
+ const localPath = join36(target, obj.Key);
9565
10017
  try {
9566
10018
  const localStat = statSync3(localPath);
9567
10019
  if (obj.LastModified && localStat.mtime > obj.LastModified) {
@@ -9574,7 +10026,7 @@ async function runBackupRestore(input) {
9574
10026
  const resp = await client.send(new GetObjectCommand({ Bucket: input.bucket, Key: obj.Key }));
9575
10027
  const body = await resp.Body?.transformToByteArray();
9576
10028
  if (body) {
9577
- mkdirSync5(dirname11(localPath), { recursive: true });
10029
+ mkdirSync5(dirname12(localPath), { recursive: true });
9578
10030
  writeFileSync7(localPath, Buffer.from(body));
9579
10031
  downloaded++;
9580
10032
  }
@@ -9608,8 +10060,8 @@ async function runBackupRestore(input) {
9608
10060
 
9609
10061
  // src/commands/status.ts
9610
10062
  import { existsSync as existsSync12, statSync as statSync4 } from "fs";
9611
- import { readFile as readFile18 } from "fs/promises";
9612
- import { join as join36 } from "path";
10063
+ import { readFile as readFile19 } from "fs/promises";
10064
+ import { join as join37 } from "path";
9613
10065
  var FUSE_NO_MIRROR_NOTE = "FUSE vault with no read mirror - status scan may be slow";
9614
10066
  async function runStatus(input) {
9615
10067
  if (!existsSync12(input.vault)) {
@@ -9648,7 +10100,7 @@ async function runStatus(input) {
9648
10100
  const compound = scan.data.compound.length;
9649
10101
  let schemaVersion = "v1";
9650
10102
  try {
9651
- const schemaContent = await readFile18(join36(scanRoot, "SCHEMA.md"), "utf8");
10103
+ const schemaContent = await readFile19(join37(scanRoot, "SCHEMA.md"), "utf8");
9652
10104
  const versionMatch = schemaContent.match(/version:\s*["']?([^"'\s\n]+)/i);
9653
10105
  if (versionMatch) schemaVersion = versionMatch[1];
9654
10106
  } catch {
@@ -9716,25 +10168,25 @@ async function runStatus(input) {
9716
10168
 
9717
10169
  // src/commands/copy-status.ts
9718
10170
  import { existsSync as existsSync13, realpathSync as realpathSync5 } from "fs";
9719
- import { isAbsolute as isAbsolute2, join as join38, relative as relative5, resolve as resolve8 } from "path";
10171
+ import { isAbsolute as isAbsolute2, join as join39, relative as relative6, resolve as resolve9 } from "path";
9720
10172
 
9721
10173
  // src/utils/fetch-projection.ts
9722
- import { isAbsolute, join as join37, resolve as resolve7 } from "path";
10174
+ import { isAbsolute, join as join38, resolve as resolve8 } from "path";
9723
10175
  function inspectConfiguredFetchProjection(home) {
9724
10176
  if (!home) return { configured: false };
9725
- const config = readSkillWikiConfig(join37(home, ".skillwiki", ".env"));
10177
+ const config = readSkillWikiConfig(join38(home, ".skillwiki", ".env"));
9726
10178
  const configured = config["vault_sync.fetch_projection"];
9727
10179
  if (!configured || configured === "none") return { configured: false };
9728
10180
  if (!isAbsolute(configured)) {
9729
10181
  return { configured: true, invalidDetail: "configured fetch projection path must be absolute" };
9730
10182
  }
9731
- return { configured: true, path: resolve7(configured) };
10183
+ return { configured: true, path: resolve8(configured) };
9732
10184
  }
9733
10185
 
9734
10186
  // src/utils/live-drift.ts
9735
10187
  import { createHash as createHash7 } from "crypto";
9736
- import { readFile as readFile19 } from "fs/promises";
9737
- import { relative as relative4, sep as sep3 } from "path";
10188
+ import { readFile as readFile20 } from "fs/promises";
10189
+ import { relative as relative5, sep as sep4 } from "path";
9738
10190
 
9739
10191
  // src/utils/git-promotion.ts
9740
10192
  function normalizedRelativePath(path) {
@@ -9775,10 +10227,10 @@ async function measureAuthoritativeLiveDrift(liveVault, projection) {
9775
10227
  let content = 0;
9776
10228
  const livePromotable = /* @__PURE__ */ new Set();
9777
10229
  for (const page of scan.data.allMarkdown) {
9778
- const rel = relative4(liveVault, page.absPath).split(sep3).join("/");
10230
+ const rel = relative5(liveVault, page.absPath).split(sep4).join("/");
9779
10231
  if (!isGitPromotablePath(rel)) continue;
9780
10232
  livePromotable.add(rel);
9781
- const bytes = await readFile19(page.absPath);
10233
+ const bytes = await readFile20(page.absPath);
9782
10234
  if (projectionBlobs.get(rel) !== gitBlobOid(bytes)) content += 1;
9783
10235
  }
9784
10236
  for (const rel of projectionBlobs.keys()) {
@@ -9953,11 +10405,11 @@ function comparablePath(path) {
9953
10405
  try {
9954
10406
  return realpathSync5(path);
9955
10407
  } catch {
9956
- return resolve8(path);
10408
+ return resolve9(path);
9957
10409
  }
9958
10410
  }
9959
10411
  function pathsAreNested(left, right) {
9960
- const rel = relative5(comparablePath(left), comparablePath(right));
10412
+ const rel = relative6(comparablePath(left), comparablePath(right));
9961
10413
  return rel !== "" && !rel.startsWith("..") && !isAbsolute2(rel);
9962
10414
  }
9963
10415
  function defaultCopyStatusDeps(input) {
@@ -9973,7 +10425,7 @@ function defaultCopyStatusDeps(input) {
9973
10425
  const projectionWarning = selection.configured ? `configured fetch projection ignored for Git status; using live vault${projectionProblem ? ` (${projectionProblem})` : ""}` : void 0;
9974
10426
  const invalidProjectionDetail = projectionProblem ?? "configured fetch projection is not a git repository";
9975
10427
  const gitRootProblem = () => {
9976
- if (!existsSync13(join38(gitVault, ".git"))) {
10428
+ if (!existsSync13(join39(gitVault, ".git"))) {
9977
10429
  return "vault is not a git repository";
9978
10430
  }
9979
10431
  return void 0;
@@ -10053,7 +10505,7 @@ function defaultCopyStatusDeps(input) {
10053
10505
  detail: projectionProblem ?? "live drift requires a configured fetch projection"
10054
10506
  };
10055
10507
  }
10056
- if (!existsSync13(join38(configuredProjection, ".git"))) {
10508
+ if (!existsSync13(join39(configuredProjection, ".git"))) {
10057
10509
  return {
10058
10510
  unknown: true,
10059
10511
  reason_code: "fetch_projection_invalid",
@@ -10073,8 +10525,8 @@ async function runCopyStatusCommand(input) {
10073
10525
  }
10074
10526
 
10075
10527
  // src/commands/seed.ts
10076
- import { mkdir as mkdir12, writeFile as writeFile7, stat as stat5 } from "fs/promises";
10077
- import { join as join39 } from "path";
10528
+ import { mkdir as mkdir13, writeFile as writeFile7, stat as stat5 } from "fs/promises";
10529
+ import { join as join40 } from "path";
10078
10530
  var TODAY = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
10079
10531
  var EXAMPLE_PAGES = {
10080
10532
  "entities/example-project.md": `---
@@ -10143,29 +10595,29 @@ Real sources are immutable after ingestion \u2014 never edit them.
10143
10595
  `;
10144
10596
  async function runSeed(input) {
10145
10597
  try {
10146
- await stat5(join39(input.vault, "SCHEMA.md"));
10598
+ await stat5(join40(input.vault, "SCHEMA.md"));
10147
10599
  } catch {
10148
10600
  return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { root: input.vault, reason: "SCHEMA.md missing \u2014 run `skillwiki init` first" }) };
10149
10601
  }
10150
10602
  const created = [];
10151
10603
  const skipped = [];
10152
10604
  for (const [relPath, content] of Object.entries(EXAMPLE_PAGES)) {
10153
- const absPath = join39(input.vault, relPath);
10605
+ const absPath = join40(input.vault, relPath);
10154
10606
  try {
10155
10607
  await stat5(absPath);
10156
10608
  skipped.push(relPath);
10157
10609
  } catch {
10158
- await mkdir12(join39(absPath, ".."), { recursive: true });
10610
+ await mkdir13(join40(absPath, ".."), { recursive: true });
10159
10611
  await writeFile7(absPath, content, "utf8");
10160
10612
  created.push(relPath);
10161
10613
  }
10162
10614
  }
10163
- const rawPath = join39(input.vault, "raw", "articles", "example-source.md");
10615
+ const rawPath = join40(input.vault, "raw", "articles", "example-source.md");
10164
10616
  try {
10165
10617
  await stat5(rawPath);
10166
10618
  skipped.push("raw/articles/example-source.md");
10167
10619
  } catch {
10168
- await mkdir12(join39(rawPath, ".."), { recursive: true });
10620
+ await mkdir13(join40(rawPath, ".."), { recursive: true });
10169
10621
  await writeFile7(rawPath, EXAMPLE_RAW, "utf8");
10170
10622
  created.push("raw/articles/example-source.md");
10171
10623
  }
@@ -10188,9 +10640,9 @@ async function runSeed(input) {
10188
10640
  }
10189
10641
 
10190
10642
  // src/commands/canvas.ts
10191
- import { readFile as readFile20, writeFile as writeFile8 } from "fs/promises";
10643
+ import { readFile as readFile21, writeFile as writeFile8 } from "fs/promises";
10192
10644
  import { existsSync as existsSync14 } from "fs";
10193
- import { join as join40 } from "path";
10645
+ import { join as join41 } from "path";
10194
10646
  var NODE_WIDTH = 240;
10195
10647
  var NODE_HEIGHT = 60;
10196
10648
  var COLUMN_SPACING = 400;
@@ -10268,7 +10720,7 @@ function buildCanvasEdges(adjacency) {
10268
10720
  return edges;
10269
10721
  }
10270
10722
  async function runCanvasGenerate(input) {
10271
- const graphPath = input.graphPath ?? join40(input.vault, ".skillwiki", "graph.json");
10723
+ const graphPath = input.graphPath ?? join41(input.vault, ".skillwiki", "graph.json");
10272
10724
  if (!existsSync14(graphPath)) {
10273
10725
  return {
10274
10726
  exitCode: ExitCode.FILE_NOT_FOUND,
@@ -10280,7 +10732,7 @@ async function runCanvasGenerate(input) {
10280
10732
  }
10281
10733
  let raw;
10282
10734
  try {
10283
- raw = await readFile20(graphPath, "utf8");
10735
+ raw = await readFile21(graphPath, "utf8");
10284
10736
  } catch (e) {
10285
10737
  return {
10286
10738
  exitCode: ExitCode.FILE_NOT_FOUND,
@@ -10306,7 +10758,7 @@ async function runCanvasGenerate(input) {
10306
10758
  const nodes = buildCanvasNodes(paths);
10307
10759
  const edges = buildCanvasEdges(graph.adjacency);
10308
10760
  const canvas = { nodes, edges };
10309
- const outPath = join40(input.vault, "vault-graph.canvas");
10761
+ const outPath = join41(input.vault, "vault-graph.canvas");
10310
10762
  try {
10311
10763
  await writeFile8(outPath, JSON.stringify(canvas, null, 2));
10312
10764
  } catch (e) {
@@ -10451,11 +10903,11 @@ async function runSourceDisposition(input) {
10451
10903
 
10452
10904
  // src/utils/source-disposal.ts
10453
10905
  import { createHash as createHash8 } from "crypto";
10454
- import { readFile as readFile22, unlink as unlink5 } from "fs/promises";
10906
+ import { readFile as readFile23, unlink as unlink5 } from "fs/promises";
10455
10907
 
10456
10908
  // src/utils/raw-asset-reference-index.ts
10457
- import { readdir as readdir6, readFile as readFile21 } from "fs/promises";
10458
- import { join as join41, relative as relative6, sep as sep4 } from "path";
10909
+ import { readdir as readdir6, readFile as readFile22 } from "fs/promises";
10910
+ import { join as join42, relative as relative7, sep as sep5 } from "path";
10459
10911
  var PREVIEW_EXTENSIONS = /* @__PURE__ */ new Set(["png", "jpg", "jpeg", "gif", "webp", "svg", "bmp", "avif", "pdf", "mp3", "wav", "m4a", "mp4", "webm"]);
10460
10912
  async function walkFiles(root, dir = root) {
10461
10913
  let entries;
@@ -10466,9 +10918,9 @@ async function walkFiles(root, dir = root) {
10466
10918
  }
10467
10919
  const out = [];
10468
10920
  for (const entry of entries) {
10469
- const path = join41(dir, entry.name);
10921
+ const path = join42(dir, entry.name);
10470
10922
  if (entry.isDirectory()) out.push(...await walkFiles(root, path));
10471
- else if (entry.isFile()) out.push(relative6(root, path).split(sep4).join("/"));
10923
+ else if (entry.isFile()) out.push(relative7(root, path).split(sep5).join("/"));
10472
10924
  }
10473
10925
  return out;
10474
10926
  }
@@ -10482,7 +10934,7 @@ function addInbound(map, target, source) {
10482
10934
  map.set(target, refs);
10483
10935
  }
10484
10936
  async function buildRawAssetReferenceIndex(vault) {
10485
- const assetRoot = join41(vault, "raw", "assets");
10937
+ const assetRoot = join42(vault, "raw", "assets");
10486
10938
  const relativeAssets = await walkFiles(assetRoot);
10487
10939
  const assets = relativeAssets.map((path) => `raw/assets/${path}`).sort((a, b) => a.localeCompare(b));
10488
10940
  const assetSet = new Set(assets);
@@ -10498,7 +10950,7 @@ async function buildRawAssetReferenceIndex(vault) {
10498
10950
  for (const page of scan.data.allMarkdown) {
10499
10951
  let text;
10500
10952
  try {
10501
- text = await readFile21(page.absPath, "utf8");
10953
+ text = await readFile22(page.absPath, "utf8");
10502
10954
  } catch {
10503
10955
  continue;
10504
10956
  }
@@ -10558,7 +11010,7 @@ async function planSourceDisposal(input) {
10558
11010
  if (sensitive.length > 0) return err("SENSITIVE_CONTENT_DETECTED", { findings: sensitive });
10559
11011
  const target = await resolveExistingRegularFileInsideVault(input.vault, input.rawPath);
10560
11012
  if (!target.ok) return err("RAW_DISPOSAL_TARGET_INVALID", { path: input.rawPath, cause: target });
10561
- const bytes = await readFile22(target.data);
11013
+ const bytes = await readFile23(target.data);
10562
11014
  const size = bytes.byteLength;
10563
11015
  let typedReferences = [];
10564
11016
  let otherReferences = [];
@@ -10682,7 +11134,7 @@ async function runSourceDisposal(input) {
10682
11134
  import { existsSync as existsSync15, readFileSync as readFileSync16 } from "fs";
10683
11135
  import { execSync as nodeExecSync } from "child_process";
10684
11136
  import { hostname as nodeHostname, platform as nodePlatform } from "os";
10685
- import { join as join42 } from "path";
11137
+ import { join as join43 } from "path";
10686
11138
  var SSH_TIMEOUT_MS = 15e3;
10687
11139
  var TIMER_UNIT = "agent-memory-trends.timer";
10688
11140
  var SERVICE_UNIT = "agent-memory-trends.service";
@@ -10912,7 +11364,7 @@ async function runFleetHealth(input) {
10912
11364
  const home = resolveSkillwikiHome(input.home, env);
10913
11365
  const osHostname = input.osHostname ?? env.HOSTNAME ?? nodeHostname();
10914
11366
  const vault = input.vault ?? env.WIKI_PATH;
10915
- const file = input.file ?? (vault ? join42(vault, FLEET_REL_PATH) : void 0);
11367
+ const file = input.file ?? (vault ? join43(vault, FLEET_REL_PATH) : void 0);
10916
11368
  if (!file) {
10917
11369
  return {
10918
11370
  exitCode: ExitCode.NO_VAULT_CONFIGURED,
@@ -10997,7 +11449,7 @@ async function runFleetHealth(input) {
10997
11449
 
10998
11450
  // src/utils/auto-commit.ts
10999
11451
  import { existsSync as existsSync16 } from "fs";
11000
- import { isAbsolute as isAbsolute3, join as join43, normalize } from "path";
11452
+ import { isAbsolute as isAbsolute3, join as join44, normalize } from "path";
11001
11453
  function isValidLastOpPath(file) {
11002
11454
  if (!file || typeof file !== "string") return false;
11003
11455
  const trimmed = file.trim();
@@ -11018,7 +11470,7 @@ async function postCommit(vault, exitCode) {
11018
11470
  const dotenv = await parseDotenvFile(configPath(home));
11019
11471
  const autoCommit = process.env.AUTO_COMMIT ?? dotenv["AUTO_COMMIT"];
11020
11472
  if (autoCommit === "false") return;
11021
- if (!existsSync16(join43(vault, ".git"))) return;
11473
+ if (!existsSync16(join44(vault, ".git"))) return;
11022
11474
  const lastOps = readLastOp(vault);
11023
11475
  if (lastOps.length === 0) return;
11024
11476
  const porcelain = git(vault, ["status", "--porcelain", "--", ...VAULT_COMMIT_PATHSPEC]);
@@ -11066,7 +11518,7 @@ function preflight(error) {
11066
11518
  }
11067
11519
  async function runMcpAuthIssueHost(input) {
11068
11520
  const isTty = input.isTty ?? Boolean(process.stdin.isTTY && process.stdout.isTTY);
11069
- const readFile23 = input.readFile ?? readFileSync17;
11521
+ const readFile24 = input.readFile ?? readFileSync17;
11070
11522
  const writeFile9 = input.writeFile ?? writeFileSync8;
11071
11523
  const stderrWrite = input.stderrWrite ?? ((s) => {
11072
11524
  process.stderr.write(s);
@@ -11081,7 +11533,7 @@ async function runMcpAuthIssueHost(input) {
11081
11533
  if (write && !isTty) return preflight("NO_TTY");
11082
11534
  let yamlText = "";
11083
11535
  try {
11084
- yamlText = readFile23(mapPath, "utf8");
11536
+ yamlText = readFile24(mapPath, "utf8");
11085
11537
  } catch (error) {
11086
11538
  if (!isEnoent(error)) throw error;
11087
11539
  }
@@ -11199,8 +11651,8 @@ async function emit(r, vault, opts) {
11199
11651
  printJson(r.result);
11200
11652
  }
11201
11653
  if (vault && opts?.postCommit !== false) await postCommit(vault, r.exitCode);
11202
- await new Promise((resolve9, reject) => {
11203
- process.stdout.write("", (error) => error ? reject(error) : resolve9());
11654
+ await new Promise((resolve10, reject) => {
11655
+ process.stdout.write("", (error) => error ? reject(error) : resolve10());
11204
11656
  });
11205
11657
  process.exit(r.exitCode);
11206
11658
  }
@@ -11263,7 +11715,7 @@ async function emitManagedVaultWrite(vault, command, mutate, opts) {
11263
11715
  if (dirty) {
11264
11716
  return emit(dirty, void 0, { postCommit: false });
11265
11717
  }
11266
- const { runManagedWriteTransaction: runManagedWriteTransaction2 } = await import("./managed-write-preflight-GRSHQYKC.js");
11718
+ const { runManagedWriteTransaction: runManagedWriteTransaction2 } = await import("./managed-write-preflight-2BMHZWVJ.js");
11267
11719
  const run = await runManagedWriteTransaction2({
11268
11720
  vault,
11269
11721
  command,
@@ -11293,7 +11745,7 @@ program.command("validate <file>").description("validate vault page frontmatter
11293
11745
  emit(await runValidate({ file, apply: !!opts.apply, vault }), vault);
11294
11746
  });
11295
11747
  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) => {
11296
- const out = opts.out ?? join44(vault, ".skillwiki", "graph.json");
11748
+ const out = opts.out ?? join45(vault, ".skillwiki", "graph.json");
11297
11749
  return emitGuardedVaultWrite(vault, "graph build", () => runGraphBuild({ vault, out }));
11298
11750
  });
11299
11751
  var canvasCmd = program.command("canvas").description("manage Obsidian canvas files");
@@ -11637,6 +12089,15 @@ pageCmd.command("publish <draft> [vault]").description("preview or publish an un
11637
12089
  { postCommit: false }
11638
12090
  );
11639
12091
  });
12092
+ pageCmd.command("render-html <page> [vault]").description("render an opted-in typed page to a self-contained HTML file").requiredOption("--out <file>", "output HTML path (must be outside the vault)").option("--wiki <name>", "wiki profile name").action(async (page, vault, opts) => {
12093
+ const resolved = await resolveVaultArg(vault, opts.wiki);
12094
+ if (!resolved.ok) return emit({ exitCode: resolved.exitCode, result: resolved.payload });
12095
+ return emit(await runPageRenderHtml({
12096
+ vault: resolved.vault,
12097
+ page,
12098
+ out: opts.out
12099
+ }), resolved.vault, { postCommit: false });
12100
+ });
11640
12101
  var projectPageCmd = program.command("project-page").description("publish project-local architecture pages");
11641
12102
  projectPageCmd.command("publish <draft> [vault]").description("preview or publish a Layer-3 architecture page under projects/{slug}/architecture/").requiredOption("--project <slug>", "project slug").requiredOption("--target <path>", "vault-relative architecture target").option("--log-note <text>", "single-line publication log note").option("--write", "publish SCHEMA, page, project knowledge index, and log", false).option("--approve <token>", "required with --write; token from a prior dry-run").option("--wiki <name>", "wiki profile name").action(async (draft, vault, opts) => {
11642
12103
  const resolved = await resolveVaultArg(vault, opts.wiki);
@@ -11910,7 +12371,8 @@ program.command("doctor").description("diagnose skillwiki setup issues").option(
11910
12371
  var clipOutboxCmd = program.command("clip-outbox").description("dedicated file-level web-clip outbox");
11911
12372
  clipOutboxCmd.command("status").description("show whether the clip-outbox channel is configured").action(async () => emit(await runClipOutboxStatus(resolveSkillwikiHome())));
11912
12373
  clipOutboxCmd.command("watch").description("run the enabled dedicated outbox worker using macOS FSEvents").action(async () => emit(await runClipOutboxWatch(resolveSkillwikiHome())));
11913
- clipOutboxCmd.command("install-or-migrate").description("refresh the packaged launchd plist without enabling the channel").option("--load", "attended launchd bootstrap; requires existing enabled=true", false).option("--conditional-create-proven", "attest the deployed SeaweedFS If-None-Match:* race test passed", false).action(async (opts) => emit(await runClipOutboxInstall({ home: resolveSkillwikiHome(), load: opts.load, conditionalCreateProven: opts.conditionalCreateProven })));
12374
+ clipOutboxCmd.command("mirror-watch").description("copy admitted raw/articles and raw/assets from storage into the frozen mirror using MCP SSE").action(async () => emit(await runClipOutboxMirrorWatch(resolveSkillwikiHome())));
12375
+ clipOutboxCmd.command("install-or-migrate").description("refresh the packaged launchd plist without enabling the channel").option("--load", "attended clip watcher bootstrap; requires existing enabled=true", false).option("--load-mirror", "attended inbound SSE bootstrap; requires existing enabled=true; never auto-loaded on upgrade", false).option("--conditional-create-proven", "attest the deployed SeaweedFS If-None-Match:* race test passed", false).action(async (opts) => emit(await runClipOutboxInstall({ home: resolveSkillwikiHome(), load: opts.load, loadMirror: opts.loadMirror, conditionalCreateProven: opts.conditionalCreateProven })));
11914
12376
  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({
11915
12377
  home: resolveSkillwikiHome(),
11916
12378
  fromFile: opts.fromFile,
@@ -12409,7 +12871,7 @@ memoryCmd.command("import [vault]").description("preview or apply local memory i
12409
12871
  maxBytes: opts.maxBytes
12410
12872
  }), v.vault, { postCommit: false });
12411
12873
  });
12412
- program.command("ingest <source>").description("ingest a source URL or local file into the vault").requiredOption("--vault <path>", "vault root directory").requiredOption("--type <type>", "typed-knowledge type (entity|concept|comparison|query)").requiredOption("--title <title>", "page title").option("--tags <csv>", "comma-separated tags").option("--provenance <provenance>", "provenance (research|project)").option("--dry-run", "preview without writing files", false).action(async (source, opts) => {
12874
+ program.command("ingest <source>").description("ingest a source URL or local file into the vault").requiredOption("--vault <path>", "vault root directory").requiredOption("--type <type>", "typed-knowledge type (entity|concept|comparison|query)").requiredOption("--title <title>", "page title").option("--tags <csv>", "comma-separated tags").option("--provenance <provenance>", "provenance (research|project)").option("--html", "stamp html_render: true on the typed stub", false).option("--dry-run", "preview without writing files", false).action(async (source, opts) => {
12413
12875
  const tags = typeof opts.tags === "string" ? opts.tags.split(",").map((s) => s.trim()).filter((s) => s.length > 0) : [];
12414
12876
  if (opts.dryRun) {
12415
12877
  emit(await runIngest({
@@ -12419,6 +12881,7 @@ program.command("ingest <source>").description("ingest a source URL or local fil
12419
12881
  title: opts.title,
12420
12882
  tags,
12421
12883
  provenance: opts.provenance,
12884
+ html: !!opts.html,
12422
12885
  dryRun: true
12423
12886
  }), opts.vault);
12424
12887
  return;
@@ -12433,6 +12896,7 @@ program.command("ingest <source>").description("ingest a source URL or local fil
12433
12896
  title: opts.title,
12434
12897
  tags,
12435
12898
  provenance: opts.provenance,
12899
+ html: !!opts.html,
12436
12900
  dryRun: false
12437
12901
  })
12438
12902
  );