skillwiki 0.10.109 → 0.10.110

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.
@@ -6,7 +6,7 @@ import {
6
6
  import {
7
7
  renderRootIndex,
8
8
  writeRootIndexProjection
9
- } from "./chunk-DTTKCCQ4.js";
9
+ } from "./chunk-KX5Y3GSX.js";
10
10
  import {
11
11
  CONFIG_KEYS,
12
12
  VAULT_HYGIENE_GENERATED_COMMIT_PATHS,
@@ -41,11 +41,11 @@ import {
41
41
  snapshotterAliasForLocalHost,
42
42
  toUndirectedWeighted,
43
43
  writeDotenv
44
- } from "./chunk-FYAKMULW.js";
44
+ } from "./chunk-QU2CKXM4.js";
45
45
  import {
46
46
  atomicWriteText,
47
47
  prepareTypedPage
48
- } from "./chunk-HMHMCPCE.js";
48
+ } from "./chunk-WPJHDRTP.js";
49
49
  import {
50
50
  applySourceCompileClaim,
51
51
  applySourceCompilePublished,
@@ -58,12 +58,13 @@ import {
58
58
  planSourceCompileRelease,
59
59
  planSourceReview,
60
60
  runSourcesPending
61
- } from "./chunk-QU2IXZRN.js";
61
+ } from "./chunk-NA3TZ4JN.js";
62
62
  import {
63
63
  eventPathFor,
64
64
  writeLogEvent
65
- } from "./chunk-JCDAEZVD.js";
65
+ } from "./chunk-5MV5SSRM.js";
66
66
  import {
67
+ CLIP_OUTBOX_DEFAULTS,
67
68
  CompoundSchema,
68
69
  ExitCode,
69
70
  MetaSchema,
@@ -73,8 +74,10 @@ import {
73
74
  detectSchema,
74
75
  err,
75
76
  extractFrontmatter,
77
+ isClipOutboxKey,
76
78
  isVaultSyncKey,
77
79
  ok,
80
+ parseClipOutboxKeyValue,
78
81
  parseVaultSyncKeyValue,
79
82
  readPage,
80
83
  redactSensitiveContent,
@@ -83,7 +86,7 @@ import {
83
86
  scanVault,
84
87
  splitFrontmatter,
85
88
  systemdPropertyFor
86
- } from "./chunk-GJC2CI3W.js";
89
+ } from "./chunk-R2DJ7IJ5.js";
87
90
 
88
91
  // src/commands/log-append.ts
89
92
  import { readFile, stat } from "fs/promises";
@@ -1017,7 +1020,7 @@ import { readFile as readFile5 } from "fs/promises";
1017
1020
  import { existsSync as existsSync2 } from "fs";
1018
1021
  import { join as join5 } from "path";
1019
1022
  function validateKey(key) {
1020
- return CONFIG_KEYS.includes(key) || isValidWikiProfileKey(key) || isVaultSyncKey(key);
1023
+ return CONFIG_KEYS.includes(key) || isValidWikiProfileKey(key) || isVaultSyncKey(key) || isClipOutboxKey(key);
1021
1024
  }
1022
1025
  function displayConfigValue(key, value) {
1023
1026
  if (key === "SKILLWIKI_MCP_TOKEN") return value.length > 0 ? "TOKEN_SET" : "";
@@ -1044,6 +1047,12 @@ async function runConfigSet(input) {
1044
1047
  return { exitCode: ExitCode.INVALID_CONFIG_VALUE, result: err("INVALID_CONFIG_VALUE", { key: input.key, value: input.value, message: parsed.message }) };
1045
1048
  }
1046
1049
  }
1050
+ if (isClipOutboxKey(input.key)) {
1051
+ const parsed = parseClipOutboxKeyValue(input.key, input.value);
1052
+ if (!parsed.ok) {
1053
+ return { exitCode: ExitCode.INVALID_CONFIG_VALUE, result: err("INVALID_CONFIG_VALUE", { key: input.key, value: input.value, message: parsed.message }) };
1054
+ }
1055
+ }
1047
1056
  const filePath = configPath(input.home);
1048
1057
  try {
1049
1058
  let originalContent;
@@ -1130,7 +1139,7 @@ function isMcpOnlyLeaf(input) {
1130
1139
  }
1131
1140
 
1132
1141
  // src/doctor/runner.ts
1133
- import { existsSync as existsSync18 } from "fs";
1142
+ import { existsSync as existsSync20 } from "fs";
1134
1143
 
1135
1144
  // src/doctor/probes/environment.ts
1136
1145
  import { existsSync as existsSync4, lstatSync, readlinkSync } from "fs";
@@ -2102,7 +2111,9 @@ import { execSync as execSync4 } from "child_process";
2102
2111
  // src/utils/s3-mount-health.ts
2103
2112
  import { execSync as execSync3 } from "child_process";
2104
2113
  import { platform as platform2 } from "os";
2105
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync2, unlinkSync as unlinkSync2, readFileSync as readFile6 } from "fs";
2114
+ import { readFileSync as readFileSync6 } from "fs";
2115
+ import { readFile as readFile6, writeFile as writeFile2, unlink } from "fs/promises";
2116
+ import { randomUUID } from "crypto";
2106
2117
  import { join as join13 } from "path";
2107
2118
  var OS = platform2();
2108
2119
  function findRcloneMountPid() {
@@ -2260,36 +2271,45 @@ function detectFuseMount(vaultPath) {
2260
2271
  }
2261
2272
  return null;
2262
2273
  }
2263
- function writeTest(dir) {
2264
- const testFile = join13(dir, `.doctor-write-test-${process.pid}.tmp`);
2265
- const payload = `skillwiki doctor write test \u2014 ${Date.now()} \u2014 ${Math.random().toString(36).slice(2)}`;
2274
+ async function writeTest(dir, options = {}) {
2275
+ const testFile = join13(dir, options.fileName ?? `.doctor-write-test-${process.pid}.tmp`);
2276
+ const io = options.io ?? {
2277
+ write: (path, payload2) => writeFile2(path, payload2, "utf8"),
2278
+ read: (path) => readFile6(path, "utf8"),
2279
+ remove: (path) => unlink(path)
2280
+ };
2281
+ const payload = `skillwiki write test \u2014 ${(/* @__PURE__ */ new Date()).toISOString()} \u2014 ${randomUUID()}`;
2266
2282
  const start = Date.now();
2267
2283
  try {
2268
- writeFileSync2(testFile, payload, "utf8");
2284
+ await io.write(testFile, payload);
2269
2285
  } catch (e) {
2286
+ try {
2287
+ await io.remove(testFile);
2288
+ } catch {
2289
+ }
2270
2290
  return { success: false, writeMs: Date.now() - start, readMs: 0, size: 0, error: `write failed: ${e.message}` };
2271
2291
  }
2272
2292
  const writeMs = Date.now() - start;
2273
2293
  const readStart = Date.now();
2274
2294
  try {
2275
- const back = readFile6(testFile, "utf8");
2295
+ const back = await io.read(testFile);
2276
2296
  const readMs = Date.now() - readStart;
2277
2297
  if (back !== payload) {
2278
2298
  try {
2279
- unlinkSync2(testFile);
2299
+ await io.remove(testFile);
2280
2300
  } catch {
2281
2301
  }
2282
2302
  return { success: false, writeMs, readMs, size: Buffer.byteLength(payload, "utf8"), error: "content mismatch \u2014 wrote and read-back differ" };
2283
2303
  }
2284
2304
  } catch (e) {
2285
2305
  try {
2286
- unlinkSync2(testFile);
2306
+ await io.remove(testFile);
2287
2307
  } catch {
2288
2308
  }
2289
2309
  return { success: false, writeMs, readMs: Date.now() - readStart, size: 0, error: `read failed: ${e.message}` };
2290
2310
  }
2291
2311
  try {
2292
- unlinkSync2(testFile);
2312
+ await io.remove(testFile);
2293
2313
  } catch {
2294
2314
  }
2295
2315
  return { success: true, writeMs, readMs: Date.now() - readStart, size: Buffer.byteLength(payload, "utf8") };
@@ -2523,7 +2543,7 @@ function checkRcloneVersion(resolvedPath, vaultSyncInstalled, pushEnabled = true
2523
2543
  }
2524
2544
  return check("pass", "rclone_version", "rclone version", ver.raw);
2525
2545
  }
2526
- function checkWriteTest(resolvedPath) {
2546
+ async function checkWriteTest(resolvedPath) {
2527
2547
  if (!resolvedPath) {
2528
2548
  return check("pass", "s3_write_test", "S3 write test", "No vault path \u2014 check skipped");
2529
2549
  }
@@ -2535,7 +2555,7 @@ function checkWriteTest(resolvedPath) {
2535
2555
  if (!existsSync10(conceptsDir)) {
2536
2556
  return check("pass", "s3_write_test", "S3 write test", "no concepts/ dir to test \u2014 check skipped");
2537
2557
  }
2538
- const result = writeTest(conceptsDir);
2558
+ const result = await writeTest(conceptsDir);
2539
2559
  if (result.success) {
2540
2560
  const totalMs = result.writeMs + result.readMs;
2541
2561
  if (totalMs > 3e3) {
@@ -2618,13 +2638,13 @@ function checkVfsCacheHealth(resolvedPath) {
2618
2638
  }
2619
2639
  var s3MountHealthProbe = {
2620
2640
  id: "s3_mount_health",
2621
- run(ctx) {
2641
+ async run(ctx) {
2622
2642
  return [
2623
2643
  checkS3MountPerf(ctx.resolvedPath),
2624
2644
  checkS3MountFreshness(ctx.resolvedPath),
2625
2645
  checkRcloneFlagAudit(ctx.resolvedPath),
2626
2646
  checkRcloneVersion(ctx.resolvedPath, ctx.vsConfig.installed, ctx.vsConfig.pushEnabled !== false, ctx.mcpOnlyLeaf),
2627
- checkWriteTest(ctx.resolvedPath),
2647
+ await checkWriteTest(ctx.resolvedPath),
2628
2648
  checkVfsCacheHealth(ctx.resolvedPath)
2629
2649
  ];
2630
2650
  }
@@ -4422,6 +4442,307 @@ var mcpHandshakeProbe = {
4422
4442
  }
4423
4443
  };
4424
4444
 
4445
+ // src/doctor/probes/clip-outbox.ts
4446
+ import { existsSync as existsSync19 } from "fs";
4447
+ import { execSync as execSync7 } from "child_process";
4448
+ import { platform as platform6 } from "os";
4449
+ import { join as join23 } from "path";
4450
+
4451
+ // src/utils/clip-outbox-config.ts
4452
+ import { realpathSync, statSync as statSync3, existsSync as existsSync18, accessSync, constants } from "fs";
4453
+ import { homedir } from "os";
4454
+ import { isAbsolute, join as join22, resolve as resolve3, sep as sep2 } from "path";
4455
+ var CLIP_NOT_CONFIGURED = "clip channel not configured";
4456
+ var PRODUCTION_WATCHER_LABEL = CLIP_OUTBOX_DEFAULTS.watcher_label;
4457
+ var RETIRED_WATCHER_LABELS = [
4458
+ "com.karlchow.wiki-dev-outbox",
4459
+ "com.karlchow.wiki-dev-sse",
4460
+ "com.karlchow.wiki-dev-hourly",
4461
+ "com.karlchow.skillwiki-note-outbox"
4462
+ ];
4463
+ function expandUserPath(raw, home) {
4464
+ if (raw === "~") return home;
4465
+ if (raw.startsWith("~/")) return resolve3(home, raw.slice(2));
4466
+ return raw;
4467
+ }
4468
+ function canonicalPath(raw, home) {
4469
+ const expanded = expandUserPath(raw.trim(), home);
4470
+ if (expanded.includes("\0")) return { ok: false, message: "path contains NUL" };
4471
+ if (expanded.includes("://") || expanded.includes("@")) {
4472
+ return { ok: false, message: "path must not include URL credentials" };
4473
+ }
4474
+ if (!isAbsolute(expanded)) return { ok: false, message: "must be an absolute path" };
4475
+ try {
4476
+ const real = existsSync18(expanded) ? realpathSync(expanded) : resolve3(expanded);
4477
+ if (real.split(sep2).some((seg) => seg === "..")) {
4478
+ return { ok: false, message: "path must not contain traversal" };
4479
+ }
4480
+ return { ok: true, path: real };
4481
+ } catch (error) {
4482
+ return { ok: false, message: error instanceof Error ? error.message : "invalid path" };
4483
+ }
4484
+ }
4485
+ function nestedOrEqual(a, b) {
4486
+ if (a === b) return true;
4487
+ const prefixA = a.endsWith(sep2) ? a : a + sep2;
4488
+ const prefixB = b.endsWith(sep2) ? b : b + sep2;
4489
+ return prefixA.startsWith(prefixB) || prefixB.startsWith(prefixA);
4490
+ }
4491
+ function intField(value, fallback) {
4492
+ return Number(value && value.length > 0 ? value : fallback);
4493
+ }
4494
+ function requireWritableDir(path, label) {
4495
+ try {
4496
+ const st = statSync3(path);
4497
+ if (!st.isDirectory()) return `${label} is not a directory`;
4498
+ accessSync(path, constants.W_OK);
4499
+ return void 0;
4500
+ } catch {
4501
+ return `${label} is missing or not writable`;
4502
+ }
4503
+ }
4504
+ function resolveClipOutboxConfig(map, home) {
4505
+ const present = Object.keys(map).filter(isClipOutboxKey);
4506
+ for (const key of present) {
4507
+ const parsed = parseClipOutboxKeyValue(key, map[key] ?? "");
4508
+ if (!parsed.ok) return { ok: false, message: `${key}: ${parsed.message}` };
4509
+ }
4510
+ const enabled = (map["clip_outbox.enabled"] ?? CLIP_OUTBOX_DEFAULTS.enabled) === "true";
4511
+ if (!enabled) {
4512
+ return { ok: true, enabled: false };
4513
+ }
4514
+ const required = [
4515
+ "clip_outbox.root",
4516
+ "clip_outbox.state_root",
4517
+ "clip_outbox.mirror_root",
4518
+ "clip_outbox.ready_url",
4519
+ "clip_outbox.s3_credential_ref",
4520
+ "clip_outbox.ready_credential_ref"
4521
+ ];
4522
+ for (const key of required) {
4523
+ if (!map[key]) return { ok: false, message: `${key} is required when clip_outbox.enabled=true` };
4524
+ }
4525
+ const d8Absent = map["clip_outbox.d8_note_absent"] === "true";
4526
+ const d8RootRaw = map["clip_outbox.d8_note_root"];
4527
+ if (d8Absent && d8RootRaw) {
4528
+ return { ok: false, message: "clip_outbox.d8_note_root and clip_outbox.d8_note_absent cannot both be set" };
4529
+ }
4530
+ if (!d8Absent && !d8RootRaw) {
4531
+ return { ok: false, message: "clip_outbox.d8_note_root or clip_outbox.d8_note_absent=true is required when enabled" };
4532
+ }
4533
+ const root = canonicalPath(map["clip_outbox.root"], home);
4534
+ const stateRoot = canonicalPath(map["clip_outbox.state_root"], home);
4535
+ const mirrorRoot = canonicalPath(map["clip_outbox.mirror_root"], home);
4536
+ if (!root.ok) return { ok: false, message: `clip_outbox.root: ${root.message}` };
4537
+ if (!stateRoot.ok) return { ok: false, message: `clip_outbox.state_root: ${stateRoot.message}` };
4538
+ if (!mirrorRoot.ok) return { ok: false, message: `clip_outbox.mirror_root: ${mirrorRoot.message}` };
4539
+ let d8NoteRoot = null;
4540
+ if (d8RootRaw) {
4541
+ const d8 = canonicalPath(d8RootRaw, home);
4542
+ if (!d8.ok) return { ok: false, message: `clip_outbox.d8_note_root: ${d8.message}` };
4543
+ d8NoteRoot = d8.path;
4544
+ }
4545
+ if (nestedOrEqual(root.path, mirrorRoot.path)) {
4546
+ return { ok: false, message: "clip_outbox.root must be outside the frozen mirror" };
4547
+ }
4548
+ if (nestedOrEqual(root.path, stateRoot.path) || nestedOrEqual(stateRoot.path, mirrorRoot.path)) {
4549
+ return { ok: false, message: "clip_outbox.state_root must be disjoint from outbox and mirror" };
4550
+ }
4551
+ if (d8NoteRoot && (nestedOrEqual(root.path, d8NoteRoot) || nestedOrEqual(stateRoot.path, d8NoteRoot))) {
4552
+ return { ok: false, message: "clip outbox must not equal or nest the D8 note outbox" };
4553
+ }
4554
+ const rootErr = requireWritableDir(root.path, "clip_outbox.root");
4555
+ if (rootErr) return { ok: false, message: rootErr };
4556
+ const stateErr = requireWritableDir(stateRoot.path, "clip_outbox.state_root");
4557
+ if (stateErr) {
4558
+ if (existsSync18(stateRoot.path)) return { ok: false, message: stateErr };
4559
+ const parentErr = requireWritableDir(resolve3(stateRoot.path, ".."), "clip_outbox.state_root parent");
4560
+ if (parentErr) return { ok: false, message: stateErr };
4561
+ }
4562
+ if (!existsSync18(mirrorRoot.path)) {
4563
+ return { ok: false, message: "clip_outbox.mirror_root does not exist" };
4564
+ }
4565
+ const vaultId = map["clip_outbox.vault_id"] ?? CLIP_OUTBOX_DEFAULTS.vault_id;
4566
+ if (vaultId !== "central") return { ok: false, message: "clip_outbox.vault_id must be central" };
4567
+ const destination = map["clip_outbox.destination"] ?? CLIP_OUTBOX_DEFAULTS.destination;
4568
+ if (destination !== CLIP_OUTBOX_DEFAULTS.destination) {
4569
+ return { ok: false, message: "clip_outbox.destination must be seaweed-wiki:cloud/wiki" };
4570
+ }
4571
+ const mode = map["clip_outbox.mode"] ?? CLIP_OUTBOX_DEFAULTS.mode;
4572
+ if (mode !== "event-watch") return { ok: false, message: "clip_outbox.mode must be event-watch" };
4573
+ const watcherLabel = map["clip_outbox.watcher_label"] ?? CLIP_OUTBOX_DEFAULTS.watcher_label;
4574
+ if (watcherLabel !== PRODUCTION_WATCHER_LABEL) {
4575
+ return { ok: false, message: "clip_outbox.watcher_label must be the production clip watcher" };
4576
+ }
4577
+ const imageTypes = (map["clip_outbox.image_types"] ?? CLIP_OUTBOX_DEFAULTS.image_types).split(",").map((p) => p.trim()).filter(Boolean);
4578
+ return {
4579
+ ok: true,
4580
+ enabled: true,
4581
+ config: {
4582
+ enabled: true,
4583
+ root: root.path,
4584
+ stateRoot: stateRoot.path,
4585
+ mirrorRoot: mirrorRoot.path,
4586
+ d8NoteRoot,
4587
+ d8NoteAbsent: d8Absent,
4588
+ vaultId: "central",
4589
+ allowedPrefixes: ["raw/articles", "raw/assets"],
4590
+ destination: "seaweed-wiki:cloud/wiki",
4591
+ mode: "event-watch",
4592
+ pollFallbackEnabled: (map["clip_outbox.poll_fallback_enabled"] ?? CLIP_OUTBOX_DEFAULTS.poll_fallback_enabled) === "true",
4593
+ reconcileSeconds: intField(map["clip_outbox.reconcile_seconds"], CLIP_OUTBOX_DEFAULTS.reconcile_seconds),
4594
+ debounceMs: intField(map["clip_outbox.debounce_ms"], CLIP_OUTBOX_DEFAULTS.debounce_ms),
4595
+ stableHashMs: intField(map["clip_outbox.stable_hash_ms"], CLIP_OUTBOX_DEFAULTS.stable_hash_ms),
4596
+ maxArticleBytes: intField(map["clip_outbox.max_article_bytes"], CLIP_OUTBOX_DEFAULTS.max_article_bytes),
4597
+ maxImages: intField(map["clip_outbox.max_images"], CLIP_OUTBOX_DEFAULTS.max_images),
4598
+ maxImageBytes: intField(map["clip_outbox.max_image_bytes"], CLIP_OUTBOX_DEFAULTS.max_image_bytes),
4599
+ maxBundleBytes: intField(map["clip_outbox.max_bundle_bytes"], CLIP_OUTBOX_DEFAULTS.max_bundle_bytes),
4600
+ maxPixels: intField(map["clip_outbox.max_pixels"], CLIP_OUTBOX_DEFAULTS.max_pixels),
4601
+ downloadConcurrency: intField(map["clip_outbox.download_concurrency"], CLIP_OUTBOX_DEFAULTS.download_concurrency),
4602
+ imageTypes,
4603
+ maxRedirects: intField(map["clip_outbox.max_redirects"], CLIP_OUTBOX_DEFAULTS.max_redirects),
4604
+ requestTimeoutMs: intField(map["clip_outbox.request_timeout_ms"], CLIP_OUTBOX_DEFAULTS.request_timeout_ms),
4605
+ localizeTimeoutMs: intField(map["clip_outbox.localize_timeout_ms"], CLIP_OUTBOX_DEFAULTS.localize_timeout_ms),
4606
+ readyUrl: map["clip_outbox.ready_url"],
4607
+ s3CredentialRef: map["clip_outbox.s3_credential_ref"],
4608
+ readyCredentialRef: map["clip_outbox.ready_credential_ref"],
4609
+ watcherLabel: PRODUCTION_WATCHER_LABEL
4610
+ }
4611
+ };
4612
+ }
4613
+ function clipOutboxConfigPath(home) {
4614
+ return join22(resolveSkillwikiHome(home), ".skillwiki", ".env");
4615
+ }
4616
+ async function loadClipOutboxConfig(home) {
4617
+ const map = await parseDotenvFile(clipOutboxConfigPath(home));
4618
+ return resolveClipOutboxConfig(map, home || homedir());
4619
+ }
4620
+
4621
+ // src/doctor/probes/clip-outbox.ts
4622
+ var CLIP_CHECK_IDS = [
4623
+ "clip_config",
4624
+ "clip_frozen_mirror",
4625
+ "clip_push_isolation",
4626
+ "clip_outbox_root",
4627
+ "clip_watcher",
4628
+ "clip_storage_credentials",
4629
+ "clip_ready_channel",
4630
+ "clip_mirror_listener"
4631
+ ];
4632
+ function notConfigured() {
4633
+ return CLIP_CHECK_IDS.map(
4634
+ (id) => check("info", id, clipLabel(id), CLIP_NOT_CONFIGURED)
4635
+ );
4636
+ }
4637
+ function clipLabel(id) {
4638
+ switch (id) {
4639
+ case "clip_config":
4640
+ return "Clip outbox configuration";
4641
+ case "clip_frozen_mirror":
4642
+ return "Clip frozen mirror";
4643
+ case "clip_push_isolation":
4644
+ return "Clip push isolation";
4645
+ case "clip_outbox_root":
4646
+ return "Clip outbox root";
4647
+ case "clip_watcher":
4648
+ return "Clip outbox watcher";
4649
+ case "clip_storage_credentials":
4650
+ return "Clip storage credentials";
4651
+ case "clip_ready_channel":
4652
+ return "Clip ready channel";
4653
+ case "clip_mirror_listener":
4654
+ return "Clip mirror listener";
4655
+ default:
4656
+ return id;
4657
+ }
4658
+ }
4659
+ function launchdLoaded(label) {
4660
+ if (platform6() !== "darwin") return false;
4661
+ try {
4662
+ execSync7(`launchctl list ${label}`, {
4663
+ encoding: "utf8",
4664
+ timeout: 2e3,
4665
+ stdio: ["pipe", "ignore", "ignore"]
4666
+ });
4667
+ return true;
4668
+ } catch {
4669
+ return false;
4670
+ }
4671
+ }
4672
+ function watcherStatus(env, label) {
4673
+ const fixture = env.CLIP_WATCHER_FIXTURE;
4674
+ if (fixture === "loaded-enabled") return { loaded: true, enabled: true, kind: "production" };
4675
+ if (fixture === "stopped") return { loaded: false, enabled: false, kind: "production" };
4676
+ if (fixture === "retired-wiki-dev") return { loaded: true, enabled: true, kind: "retired" };
4677
+ if (fixture === "missing") return { loaded: false, enabled: false, kind: "unknown" };
4678
+ if (RETIRED_WATCHER_LABELS.includes(label)) {
4679
+ return { loaded: false, enabled: false, kind: "retired" };
4680
+ }
4681
+ if (!launchdLoaded(label)) {
4682
+ return { loaded: false, enabled: false, kind: "unknown" };
4683
+ }
4684
+ const plist = join23(process.env.HOME ?? "", "Library", "LaunchAgents", `${label}.plist`);
4685
+ return { loaded: true, enabled: existsSync19(plist), kind: "production" };
4686
+ }
4687
+ function pushLoaded(env) {
4688
+ if (env.CLIP_PUSH_FIXTURE === "loaded") return true;
4689
+ if (env.CLIP_PUSH_FIXTURE === "unloaded") return false;
4690
+ return launchdLoaded("com.karlchow.wiki-push");
4691
+ }
4692
+ function credentialPresent(env, ref) {
4693
+ if (env.CLIP_CREDENTIAL_FIXTURE === "present") return true;
4694
+ if (env.CLIP_CREDENTIAL_FIXTURE === "absent") return false;
4695
+ const value = env[ref];
4696
+ return typeof value === "string" && value.length > 0;
4697
+ }
4698
+ function listenerOk(env) {
4699
+ if (env.CLIP_LISTENER_FIXTURE === "ok") return true;
4700
+ return false;
4701
+ }
4702
+ function conditionalCreateEvidence(env) {
4703
+ return env.CLIP_CONDITIONAL_CREATE_FIXTURE === "ok";
4704
+ }
4705
+ var clipOutboxProbe = {
4706
+ id: "clip_outbox",
4707
+ async run(ctx) {
4708
+ if (ctx.mcpOnlyLeaf) {
4709
+ return CLIP_CHECK_IDS.map(
4710
+ (id) => check("info", id, clipLabel(id), MCP_ONLY_LEAF_SKIP)
4711
+ );
4712
+ }
4713
+ const env = ctx.input.env ?? process.env;
4714
+ const resolved = await loadClipOutboxConfig(ctx.input.home);
4715
+ if (!resolved.ok) {
4716
+ return [
4717
+ check("error", "clip_config", clipLabel("clip_config"), resolved.message),
4718
+ ...CLIP_CHECK_IDS.slice(1).map((id) => check("error", id, clipLabel(id), "clip configuration invalid"))
4719
+ ];
4720
+ }
4721
+ if (!resolved.enabled) {
4722
+ return notConfigured();
4723
+ }
4724
+ const cfg = resolved.config;
4725
+ const frozen = existsSync19(join23(cfg.mirrorRoot, ".WIKI_GIT_FROZEN"));
4726
+ const pushOn = ctx.vsConfig.pushEnabled !== false;
4727
+ const pushIsLoaded = pushLoaded(env);
4728
+ const watch = watcherStatus(env, cfg.watcherLabel);
4729
+ const s3Creds = credentialPresent(env, cfg.s3CredentialRef);
4730
+ const readyCreds = credentialPresent(env, cfg.readyCredentialRef);
4731
+ const condCreate = conditionalCreateEvidence(env);
4732
+ const listener = listenerOk(env);
4733
+ return [
4734
+ check("pass", "clip_config", clipLabel("clip_config"), "clip_outbox.enabled=true; schema valid"),
4735
+ frozen ? check("pass", "clip_frozen_mirror", clipLabel("clip_frozen_mirror"), "frozen marker present") : check("error", "clip_frozen_mirror", clipLabel("clip_frozen_mirror"), "frozen marker missing; do not unfreeze"),
4736
+ !pushOn && !pushIsLoaded ? check("pass", "clip_push_isolation", clipLabel("clip_push_isolation"), "wiki-push disabled and unloaded") : check("error", "clip_push_isolation", clipLabel("clip_push_isolation"), "wiki-push must stay disabled and unloaded"),
4737
+ check("pass", "clip_outbox_root", clipLabel("clip_outbox_root"), "outbox and state roots are canonical and disjoint"),
4738
+ watch.kind === "retired" ? check("error", "clip_watcher", clipLabel("clip_watcher"), "retired wiki-dev or D8 job does not satisfy the clip watcher") : watch.loaded && watch.enabled && watch.kind === "production" ? check("pass", "clip_watcher", clipLabel("clip_watcher"), `loaded and enabled: ${PRODUCTION_WATCHER_LABEL}`) : check("error", "clip_watcher", clipLabel("clip_watcher"), "production clip watcher not loaded and enabled"),
4739
+ s3Creds && readyCreds && condCreate ? check("pass", "clip_storage_credentials", clipLabel("clip_storage_credentials"), "credential references present; conditional-create evidence available") : check("error", "clip_storage_credentials", clipLabel("clip_storage_credentials"), "missing credential reference or conditional-create evidence"),
4740
+ readyCreds ? check("pass", "clip_ready_channel", clipLabel("clip_ready_channel"), "ready URL and credential reference present") : check("error", "clip_ready_channel", clipLabel("clip_ready_channel"), "ready credential missing"),
4741
+ listener ? check("pass", "clip_mirror_listener", clipLabel("clip_mirror_listener"), "inbound production SSE listener configured") : check("error", "clip_mirror_listener", clipLabel("clip_mirror_listener"), "inbound SSE listener disabled or missing")
4742
+ ];
4743
+ }
4744
+ };
4745
+
4425
4746
  // src/doctor/probes/index.ts
4426
4747
  var DOCTOR_PROBES = [
4427
4748
  environmentProbe,
@@ -4436,7 +4757,8 @@ var DOCTOR_PROBES = [
4436
4757
  fuseStalenessProbe,
4437
4758
  activationMarkerProbe,
4438
4759
  dsStoreNoiseProbe,
4439
- mcpHandshakeProbe
4760
+ mcpHandshakeProbe,
4761
+ clipOutboxProbe
4440
4762
  ];
4441
4763
 
4442
4764
  // src/doctor/runner.ts
@@ -4447,7 +4769,7 @@ function resolveSnapshotGitWorktree(home) {
4447
4769
  const configured = resolveConfiguredSnapshotWorktree(home);
4448
4770
  if (configured) return configured;
4449
4771
  const defaultPath = "/root/wiki-git";
4450
- return existsSync18(defaultPath) ? defaultPath : void 0;
4772
+ return existsSync20(defaultPath) ? defaultPath : void 0;
4451
4773
  }
4452
4774
  var DoctorRunner = class {
4453
4775
  probes;
@@ -4558,8 +4880,8 @@ function readCliPackageJson(baseUrl = import.meta.url) {
4558
4880
 
4559
4881
  // src/utils/vault-write-gates.ts
4560
4882
  import { execFileSync } from "child_process";
4561
- import { existsSync as existsSync19, readdirSync as readdirSync7, readFileSync as readFileSync13, statSync as statSync3 } from "fs";
4562
- import { join as join22, relative as relative2 } from "path";
4883
+ import { existsSync as existsSync21, readdirSync as readdirSync7, readFileSync as readFileSync13, statSync as statSync4 } from "fs";
4884
+ import { join as join24, relative as relative2 } from "path";
4563
4885
  var DEFAULT_DIRTY_VOLUME_THRESHOLD = 50;
4564
4886
  var DEFAULT_CAPTURE_BUDGET = 20;
4565
4887
  var DEFAULT_NO_DECISION_STREAK = 3;
@@ -4615,7 +4937,7 @@ function isEventLedgerPath(rel) {
4615
4937
  return n === "meta/log-events" || n.startsWith("meta/log-events/");
4616
4938
  }
4617
4939
  function countEventLedgerFiles(vault) {
4618
- return listFilesRecursive(join22(vault, "meta", "log-events")).length;
4940
+ return listFilesRecursive(join24(vault, "meta", "log-events")).length;
4619
4941
  }
4620
4942
  function measureDirtyVolume(vault) {
4621
4943
  const empty = (extra = {}) => ({
@@ -4631,7 +4953,7 @@ function measureDirtyVolume(vault) {
4631
4953
  is_git_repo: false,
4632
4954
  ...extra
4633
4955
  });
4634
- if (!existsSync19(vault) || !statSync3(vault).isDirectory()) {
4956
+ if (!existsSync21(vault) || !statSync4(vault).isDirectory()) {
4635
4957
  return empty();
4636
4958
  }
4637
4959
  const gitDir = git(vault, ["rev-parse", "--absolute-git-dir"]);
@@ -4675,8 +4997,8 @@ function measureDirtyVolume(vault) {
4675
4997
  if (!rel) continue;
4676
4998
  if (code === "??") {
4677
4999
  untracked += 1;
4678
- const abs = join22(vault, rel);
4679
- if (existsSync19(abs) && statSync3(abs).isDirectory()) {
5000
+ const abs = join24(vault, rel);
5001
+ if (existsSync21(abs) && statSync4(abs).isDirectory()) {
4680
5002
  const files = listFilesRecursive(abs);
4681
5003
  expanded += files.length;
4682
5004
  addBucket(rel, files.length);
@@ -4719,9 +5041,9 @@ function listFilesRecursive(dir) {
4719
5041
  }
4720
5042
  for (const name of entries) {
4721
5043
  if (name === ".git") continue;
4722
- const p = join22(dir, name);
5044
+ const p = join24(dir, name);
4723
5045
  try {
4724
- const st = statSync3(p);
5046
+ const st = statSync4(p);
4725
5047
  if (st.isDirectory()) out.push(...listFilesRecursive(p));
4726
5048
  else if (st.isFile()) out.push(p);
4727
5049
  } catch {
@@ -4814,7 +5136,7 @@ var CAPTURE_HYGIENE_CONTRACT = `After each productive cycle (or when daily captu
4814
5136
  function listProjectDayCaptures(vault, project, day) {
4815
5137
  const found = [];
4816
5138
  const slug = project.replace(/^\[\[/, "").replace(/\]\]$/, "").trim();
4817
- if (!slug || !existsSync19(vault)) return found;
5139
+ if (!slug || !existsSync21(vault)) return found;
4818
5140
  const consider = (abs, rel) => {
4819
5141
  if (!rel.endsWith(".md")) return;
4820
5142
  const base = rel.split(/[/\\]/).pop() ?? "";
@@ -4842,7 +5164,7 @@ function listProjectDayCaptures(vault, project, day) {
4842
5164
  }
4843
5165
  };
4844
5166
  const walk = (dir, relBase) => {
4845
- if (!existsSync19(dir)) return;
5167
+ if (!existsSync21(dir)) return;
4846
5168
  let entries;
4847
5169
  try {
4848
5170
  entries = readdirSync7(dir);
@@ -4850,27 +5172,27 @@ function listProjectDayCaptures(vault, project, day) {
4850
5172
  return;
4851
5173
  }
4852
5174
  for (const name of entries) {
4853
- const abs = join22(dir, name);
5175
+ const abs = join24(dir, name);
4854
5176
  const rel = relBase ? `${relBase}/${name}` : name;
4855
5177
  try {
4856
- const st = statSync3(abs);
5178
+ const st = statSync4(abs);
4857
5179
  if (st.isDirectory()) walk(abs, rel);
4858
5180
  else if (st.isFile()) consider(abs, rel);
4859
5181
  } catch {
4860
5182
  }
4861
5183
  }
4862
5184
  };
4863
- walk(join22(vault, "raw", "transcripts"), "raw/transcripts");
4864
- walk(join22(vault, "projects", slug, "raw", "transcripts"), `projects/${slug}/raw/transcripts`);
4865
- walk(join22(vault, "projects", slug, "requirements"), `projects/${slug}/requirements`);
4866
- const workRoot = join22(vault, "projects", slug, "work");
4867
- if (existsSync19(workRoot)) {
5185
+ walk(join24(vault, "raw", "transcripts"), "raw/transcripts");
5186
+ walk(join24(vault, "projects", slug, "raw", "transcripts"), `projects/${slug}/raw/transcripts`);
5187
+ walk(join24(vault, "projects", slug, "requirements"), `projects/${slug}/requirements`);
5188
+ const workRoot = join24(vault, "projects", slug, "work");
5189
+ if (existsSync21(workRoot)) {
4868
5190
  try {
4869
5191
  for (const name of readdirSync7(workRoot)) {
4870
5192
  if (!name.startsWith(day)) continue;
4871
5193
  if (!/investigate|pilot-q|research|cycle|dev-loop/.test(name)) continue;
4872
- const abs = join22(workRoot, name);
4873
- if (statSync3(abs).isDirectory()) {
5194
+ const abs = join24(workRoot, name);
5195
+ if (statSync4(abs).isDirectory()) {
4874
5196
  for (const f of listFilesRecursive(abs)) {
4875
5197
  const rel = relative2(vault, f).replace(/\\/g, "/");
4876
5198
  if (rel.endsWith(".md")) found.push(rel);
@@ -4914,7 +5236,7 @@ function evaluateCaptureBudget(input) {
4914
5236
  return { allowed: true, reason: "under_budget", report };
4915
5237
  }
4916
5238
  function runWritePreflight(input) {
4917
- if (!input.vault || !existsSync19(input.vault)) {
5239
+ if (!input.vault || !existsSync21(input.vault)) {
4918
5240
  return err(GateError.VAULT_PATH_INVALID, { path: input.vault });
4919
5241
  }
4920
5242
  const want = new Set(input.checks ?? ["all"]);
@@ -4973,9 +5295,9 @@ function runWritePreflight(input) {
4973
5295
  }
4974
5296
 
4975
5297
  // src/commands/observe.ts
4976
- import { mkdir as mkdir3, writeFile as writeFile2 } from "fs/promises";
4977
- import { existsSync as existsSync20, statSync as statSync4 } from "fs";
4978
- import { join as join23 } from "path";
5298
+ import { mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
5299
+ import { existsSync as existsSync22, statSync as statSync5 } from "fs";
5300
+ import { join as join25 } from "path";
4979
5301
  import { createHash as createHash2 } from "crypto";
4980
5302
  var ALLOWED_KINDS = /* @__PURE__ */ new Set(["note", "bug", "task", "idea", "session-log"]);
4981
5303
  function slugify(text) {
@@ -4998,7 +5320,7 @@ async function runObserve(input) {
4998
5320
  result: err("SCHEME_REJECTED", { message: "Text must not be empty" })
4999
5321
  };
5000
5322
  }
5001
- if (!existsSync20(input.vault) || !statSync4(input.vault).isDirectory()) {
5323
+ if (!existsSync22(input.vault) || !statSync5(input.vault).isDirectory()) {
5002
5324
  return {
5003
5325
  exitCode: ExitCode.VAULT_PATH_INVALID,
5004
5326
  result: err("VAULT_PATH_INVALID", { path: input.vault })
@@ -5024,7 +5346,7 @@ async function runObserve(input) {
5024
5346
  };
5025
5347
  }
5026
5348
  }
5027
- const transcriptsDir = join23(input.vault, "raw", "transcripts");
5349
+ const transcriptsDir = join25(input.vault, "raw", "transcripts");
5028
5350
  try {
5029
5351
  await mkdir3(transcriptsDir, { recursive: true });
5030
5352
  } catch {
@@ -5036,7 +5358,7 @@ async function runObserve(input) {
5036
5358
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
5037
5359
  const slug = slugify(input.text);
5038
5360
  const fileName = `${today}-observation-${slug}.md`;
5039
- const filePath = join23(transcriptsDir, fileName);
5361
+ const filePath = join25(transcriptsDir, fileName);
5040
5362
  const body = `
5041
5363
  ${input.text.trim()}
5042
5364
  `;
@@ -5054,7 +5376,7 @@ ${input.text.trim()}
5054
5376
  frontmatterLines.push("---");
5055
5377
  const content = frontmatterLines.join("\n") + body;
5056
5378
  try {
5057
- await writeFile2(filePath, content, "utf8");
5379
+ await writeFile3(filePath, content, "utf8");
5058
5380
  } catch (e) {
5059
5381
  return {
5060
5382
  exitCode: ExitCode.WRITE_FAILED,
@@ -5077,8 +5399,8 @@ ${input.text.trim()}
5077
5399
 
5078
5400
  // src/commands/memory.ts
5079
5401
  import { createHash as createHash3 } from "crypto";
5080
- import { mkdir as mkdir4, readFile as readFile7, readdir as readdir2, stat as stat2, writeFile as writeFile3 } from "fs/promises";
5081
- import { basename as basename2, extname, join as join24, relative as relative3, sep as sep2 } from "path";
5402
+ import { mkdir as mkdir4, readFile as readFile7, readdir as readdir2, stat as stat2, writeFile as writeFile4 } from "fs/promises";
5403
+ import { basename as basename2, extname, join as join26, relative as relative3, sep as sep3 } from "path";
5082
5404
 
5083
5405
  // src/utils/memory-authority.ts
5084
5406
  var TIER_RANK = {
@@ -5197,9 +5519,9 @@ async function runMemoryIndex(input) {
5197
5519
  }
5198
5520
  const generatedAt = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
5199
5521
  const relCachePath = memoryCacheRelPath(input.project);
5200
- const absCachePath = join24(input.vault, relCachePath);
5201
- await mkdir4(join24(input.vault, ".skillwiki", "memory", input.project), { recursive: true });
5202
- await writeFile3(absCachePath, `${JSON.stringify({
5522
+ const absCachePath = join26(input.vault, relCachePath);
5523
+ await mkdir4(join26(input.vault, ".skillwiki", "memory", input.project), { recursive: true });
5524
+ await writeFile4(absCachePath, `${JSON.stringify({
5203
5525
  generated_at: generatedAt,
5204
5526
  project: input.project,
5205
5527
  topics: state.topics,
@@ -5390,7 +5712,7 @@ async function buildMemoryIndexState(pages, project) {
5390
5712
  }
5391
5713
  async function checkMemoryIndex(vault, project, current) {
5392
5714
  const relCachePath = memoryCacheRelPath(project);
5393
- const cacheText = await readIfExists(join24(vault, relCachePath));
5715
+ const cacheText = await readIfExists(join26(vault, relCachePath));
5394
5716
  if (!cacheText) {
5395
5717
  return {
5396
5718
  ok: true,
@@ -5799,7 +6121,7 @@ async function walkImportFiles(dir, out) {
5799
6121
  const entries = await readdir2(dir, { withFileTypes: true });
5800
6122
  for (const entry of entries) {
5801
6123
  if (entry.name === ".git" || entry.name === "node_modules") continue;
5802
- const path = join24(dir, entry.name);
6124
+ const path = join26(dir, entry.name);
5803
6125
  if (entry.isDirectory()) {
5804
6126
  await walkImportFiles(path, out);
5805
6127
  } else if (entry.isFile() && isImportCandidate(path)) {
@@ -5849,7 +6171,7 @@ async function buildImportEntry(file, sourceRoot, project, today, maxBytes) {
5849
6171
  const redacted = redactSensitiveContent(extracted, { file });
5850
6172
  const privacy = redacted.findings.length > 0 ? "sensitive" : "local";
5851
6173
  const sourceSlug = slugify2(basename2(file, extname(file)));
5852
- const relSource = relative3(sourceRoot, file).split(sep2).join("/");
6174
+ const relSource = relative3(sourceRoot, file).split(sep3).join("/");
5853
6175
  const entry = {
5854
6176
  ...baseEntry,
5855
6177
  status: "ready",
@@ -5866,9 +6188,9 @@ async function writeImportCapture(vault, entry, today) {
5866
6188
  const content = hiddenString(entry, "__content");
5867
6189
  const project = hiddenString(entry, "__project");
5868
6190
  const relPath = await availableImportPath(vault, entry.proposed_path);
5869
- const absPath = join24(vault, relPath);
5870
- await mkdir4(join24(vault, "raw", "transcripts"), { recursive: true });
5871
- await writeFile3(absPath, renderImportCapture(entry, content, project, today), "utf8");
6191
+ const absPath = join26(vault, relPath);
6192
+ await mkdir4(join26(vault, "raw", "transcripts"), { recursive: true });
6193
+ await writeFile4(absPath, renderImportCapture(entry, content, project, today), "utf8");
5872
6194
  const validation = await runValidate({ file: absPath });
5873
6195
  return {
5874
6196
  relPath,
@@ -5883,7 +6205,7 @@ async function availableImportPath(vault, proposed) {
5883
6205
  const stem = proposed.slice(0, -ext.length);
5884
6206
  let candidate = proposed;
5885
6207
  let i = 2;
5886
- while (await readIfExists(join24(vault, candidate))) {
6208
+ while (await readIfExists(join26(vault, candidate))) {
5887
6209
  candidate = `${stem}-${i}${ext}`;
5888
6210
  i++;
5889
6211
  }
@@ -5921,7 +6243,7 @@ function hiddenString(entry, key) {
5921
6243
  return entry[key] ?? "";
5922
6244
  }
5923
6245
  function classifyImportSource(file) {
5924
- const rel = file.split(sep2).join("/");
6246
+ const rel = file.split(sep3).join("/");
5925
6247
  const name = basename2(file);
5926
6248
  if (rel.includes("/.codex/memories/")) return "codex-memory";
5927
6249
  if (rel.includes("/.codex/rules/")) return "codex-rule";
@@ -6068,10 +6390,10 @@ function memoryCacheRelPath(project) {
6068
6390
  }
6069
6391
  async function readMemoryCache(vault, project) {
6070
6392
  if (project) {
6071
- const projectCache = await readIfExists(join24(vault, memoryCacheRelPath(project)));
6393
+ const projectCache = await readIfExists(join26(vault, memoryCacheRelPath(project)));
6072
6394
  if (projectCache) return projectCache;
6073
6395
  }
6074
- return readIfExists(join24(vault, ".skillwiki", "memory-topics.json"));
6396
+ return readIfExists(join26(vault, ".skillwiki", "memory-topics.json"));
6075
6397
  }
6076
6398
  function dedupePages(pages) {
6077
6399
  const seen = /* @__PURE__ */ new Set();
@@ -6163,7 +6485,7 @@ function slugify2(value) {
6163
6485
 
6164
6486
  // src/commands/query.ts
6165
6487
  import { readFile as readFile9, stat as stat4 } from "fs/promises";
6166
- import { join as join26 } from "path";
6488
+ import { join as join28 } from "path";
6167
6489
 
6168
6490
  // src/utils/rrf.ts
6169
6491
  var RRF_K = 60;
@@ -6179,8 +6501,8 @@ function fuseRankings(lists, k = RRF_K) {
6179
6501
 
6180
6502
  // src/utils/vector-index.ts
6181
6503
  import { mkdir as mkdir5, readFile as readFile8, stat as stat3 } from "fs/promises";
6182
- import { join as join25 } from "path";
6183
- import { existsSync as existsSync21 } from "fs";
6504
+ import { join as join27 } from "path";
6505
+ import { existsSync as existsSync23 } from "fs";
6184
6506
  var VECTOR_INDEX_REL = ".skillwiki/vectors/index.json";
6185
6507
  var VECTOR_INDEX_SCHEMA = "skillwiki-tfidf-index/v1";
6186
6508
  function tokenize(text) {
@@ -6194,7 +6516,7 @@ function extractPageTokens(text) {
6194
6516
  return tokenize(`${title} ${body}`);
6195
6517
  }
6196
6518
  function vectorIndexPath(vault) {
6197
- return join25(vault, ...VECTOR_INDEX_REL.split("/"));
6519
+ return join27(vault, ...VECTOR_INDEX_REL.split("/"));
6198
6520
  }
6199
6521
  function termFreq(tokens) {
6200
6522
  const tf = /* @__PURE__ */ new Map();
@@ -6249,7 +6571,7 @@ async function buildVectorIndex(vault, now = (/* @__PURE__ */ new Date()).toISOS
6249
6571
  docs
6250
6572
  };
6251
6573
  const dest = vectorIndexPath(vault);
6252
- await mkdir5(join25(vault, ".skillwiki", "vectors"), { recursive: true });
6574
+ await mkdir5(join27(vault, ".skillwiki", "vectors"), { recursive: true });
6253
6575
  await atomicWriteText(dest, `${JSON.stringify(index)}
6254
6576
  `);
6255
6577
  return ok(index);
@@ -6292,7 +6614,7 @@ async function reindexPageInVectorIndex(vault, pageRelPath, now = (/* @__PURE__
6292
6614
  if (!normalizedPage.endsWith(".md")) {
6293
6615
  return err("USAGE", { message: `Page must be a .md file: ${pageRelPath}` });
6294
6616
  }
6295
- const absPath = join25(vault, ...normalizedPage.split("/"));
6617
+ const absPath = join27(vault, ...normalizedPage.split("/"));
6296
6618
  let text;
6297
6619
  try {
6298
6620
  text = await readFile8(absPath, "utf8");
@@ -6377,7 +6699,7 @@ async function reindexPageInVectorIndex(vault, pageRelPath, now = (/* @__PURE__
6377
6699
  }
6378
6700
  }
6379
6701
  const dest = vectorIndexPath(vault);
6380
- await mkdir5(join25(vault, ".skillwiki", "vectors"), { recursive: true });
6702
+ await mkdir5(join27(vault, ".skillwiki", "vectors"), { recursive: true });
6381
6703
  await atomicWriteText(dest, `${JSON.stringify(index)}
6382
6704
  `);
6383
6705
  return ok({
@@ -6395,8 +6717,8 @@ async function pruneVectorIndex(vault, opts) {
6395
6717
  const orphans = [];
6396
6718
  for (const docKey of Object.keys(index.docs)) {
6397
6719
  const normalizedKey = docKey.split(/\\|\//).join("/");
6398
- const absPath = join25(vault, ...normalizedKey.split("/"));
6399
- if (!existsSync21(absPath)) {
6720
+ const absPath = join27(vault, ...normalizedKey.split("/"));
6721
+ if (!existsSync23(absPath)) {
6400
6722
  orphans.push(normalizedKey);
6401
6723
  }
6402
6724
  }
@@ -6442,7 +6764,7 @@ async function pruneVectorIndex(vault, opts) {
6442
6764
  index.docs[docKey] = toTfIdf(termFreq(tokens), index.df, newPageCount);
6443
6765
  } else {
6444
6766
  try {
6445
- const text = await readFile8(join25(vault, ...docKey.split("/")), "utf8");
6767
+ const text = await readFile8(join27(vault, ...docKey.split("/")), "utf8");
6446
6768
  const tokens = extractPageTokens(text);
6447
6769
  index.docs[docKey] = toTfIdf(termFreq(tokens), index.df, newPageCount);
6448
6770
  } catch {
@@ -6452,7 +6774,7 @@ async function pruneVectorIndex(vault, opts) {
6452
6774
  }
6453
6775
  if (!opts?.dryRun) {
6454
6776
  const dest = vectorIndexPath(vault);
6455
- await mkdir5(join25(vault, ".skillwiki", "vectors"), { recursive: true });
6777
+ await mkdir5(join27(vault, ".skillwiki", "vectors"), { recursive: true });
6456
6778
  await atomicWriteText(dest, `${JSON.stringify(index)}
6457
6779
  `);
6458
6780
  }
@@ -6583,7 +6905,7 @@ async function runQuery(input) {
6583
6905
  }
6584
6906
  let pendingSources;
6585
6907
  if (input.includePending) {
6586
- const { runSourcesPending: runSourcesPending2 } = await import("./sources-QTQSZMST.js");
6908
+ const { runSourcesPending: runSourcesPending2 } = await import("./sources-CLOF5VAT.js");
6587
6909
  const pending = await runSourcesPending2({
6588
6910
  vault: input.vault,
6589
6911
  match: input.text,
@@ -6753,7 +7075,7 @@ function computeKeywordScore(terms, title, tags, body, relPath) {
6753
7075
  return score;
6754
7076
  }
6755
7077
  async function loadOrBuildGraph(vault) {
6756
- const graphPath = join26(vault, ".skillwiki", "graph.json");
7078
+ const graphPath = join28(vault, ".skillwiki", "graph.json");
6757
7079
  let needsBuild = false;
6758
7080
  try {
6759
7081
  const fileStat = await stat4(graphPath);
@@ -6842,27 +7164,27 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
6842
7164
  import { z } from "zod";
6843
7165
 
6844
7166
  // src/mcp/vault-resolve.ts
6845
- import { join as join27, resolve as resolve4 } from "path";
7167
+ import { join as join29, resolve as resolve5 } from "path";
6846
7168
 
6847
7169
  // src/mcp/allowlist.ts
6848
- import { resolve as resolve3, sep as sep3 } from "path";
6849
- import { realpathSync } from "fs";
7170
+ import { resolve as resolve4, sep as sep4 } from "path";
7171
+ import { realpathSync as realpathSync2 } from "fs";
6850
7172
  function parseVaultAllowlist(envValue) {
6851
7173
  if (envValue === void 0 || envValue.trim() === "") return null;
6852
- return envValue.split(",").map((s) => s.trim()).filter((s) => s.length > 0).map((p) => resolve3(p));
7174
+ return envValue.split(",").map((s) => s.trim()).filter((s) => s.length > 0).map((p) => resolve4(p));
6853
7175
  }
6854
7176
  function vaultAllowedByList(vaultPath, allowlist) {
6855
7177
  if (!allowlist || allowlist.length === 0) return true;
6856
7178
  let canonical = vaultPath;
6857
7179
  try {
6858
- canonical = realpathSync(vaultPath);
7180
+ canonical = realpathSync2(vaultPath);
6859
7181
  } catch {
6860
- canonical = resolve3(vaultPath);
7182
+ canonical = resolve4(vaultPath);
6861
7183
  }
6862
- const resolved = resolve3(canonical);
7184
+ const resolved = resolve4(canonical);
6863
7185
  return allowlist.some((root) => {
6864
- const r = resolve3(root);
6865
- return resolved === r || resolved.startsWith(r + sep3);
7186
+ const r = resolve4(root);
7187
+ return resolved === r || resolved.startsWith(r + sep4);
6866
7188
  });
6867
7189
  }
6868
7190
  function getVaultAllowlistFromEnv() {
@@ -6875,7 +7197,7 @@ async function resolveMcpVault(input) {
6875
7197
  let vaultPath;
6876
7198
  let source = "resolved";
6877
7199
  if (input.vault !== void 0 && input.vault.length > 0) {
6878
- vaultPath = resolve4(input.vault);
7200
+ vaultPath = resolve5(input.vault);
6879
7201
  source = "flag";
6880
7202
  } else {
6881
7203
  const r = await resolveRuntimePath({
@@ -6887,7 +7209,7 @@ async function resolveMcpVault(input) {
6887
7209
  cwd: input.cwd ?? process.cwd()
6888
7210
  });
6889
7211
  if (!r.ok) return r;
6890
- vaultPath = resolve4(r.data.path);
7212
+ vaultPath = resolve5(r.data.path);
6891
7213
  source = r.data.source;
6892
7214
  }
6893
7215
  const scan = await scanVault(vaultPath);
@@ -6904,7 +7226,7 @@ async function resolveMcpVault(input) {
6904
7226
  return ok({ vault: vaultPath, source });
6905
7227
  }
6906
7228
  function defaultGraphOut(vault) {
6907
- return join27(vault, ".skillwiki", "graph.json");
7229
+ return join29(vault, ".skillwiki", "graph.json");
6908
7230
  }
6909
7231
 
6910
7232
  // src/mcp/result-format.ts
@@ -6920,7 +7242,7 @@ function formatToolResult(payload) {
6920
7242
 
6921
7243
  // src/mcp/audit-log.ts
6922
7244
  import { appendFileSync, mkdirSync as mkdirSync2 } from "fs";
6923
- import { join as join28 } from "path";
7245
+ import { join as join30 } from "path";
6924
7246
  function auditEnabled() {
6925
7247
  const v = process.env.SKILLWIKI_MCP_AUDIT;
6926
7248
  if (v === "0" || v === "false") return false;
@@ -6932,7 +7254,7 @@ function auditSink() {
6932
7254
  function auditFilePath() {
6933
7255
  const custom = process.env.SKILLWIKI_MCP_AUDIT_FILE;
6934
7256
  if (custom && custom.length > 0) return custom;
6935
- return join28(resolveSkillwikiHome(), ".skillwiki", "mcp-audit.jsonl");
7257
+ return join30(resolveSkillwikiHome(), ".skillwiki", "mcp-audit.jsonl");
6936
7258
  }
6937
7259
  function auditMcpToolCall(entry) {
6938
7260
  if (!auditEnabled()) return;
@@ -6942,7 +7264,7 @@ function auditMcpToolCall(entry) {
6942
7264
  return;
6943
7265
  }
6944
7266
  const path = auditFilePath();
6945
- mkdirSync2(join28(path, ".."), { recursive: true });
7267
+ mkdirSync2(join30(path, ".."), { recursive: true });
6946
7268
  appendFileSync(path, line, "utf8");
6947
7269
  }
6948
7270
  async function runMcpToolHandler(tool, input, fn) {
@@ -7217,7 +7539,7 @@ function registerMcpMutatingTools(server) {
7217
7539
 
7218
7540
  // src/mcp/resources.ts
7219
7541
  import { readFile as readFile11 } from "fs/promises";
7220
- import { join as join30 } from "path";
7542
+ import { join as join32 } from "path";
7221
7543
  import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
7222
7544
 
7223
7545
  // src/mcp/lint-bucket.ts
@@ -7347,8 +7669,8 @@ async function fetchQueryPreview(input) {
7347
7669
 
7348
7670
  // src/mcp/graph-html.ts
7349
7671
  import { readFile as readFile10 } from "fs/promises";
7350
- import { join as join29 } from "path";
7351
- import { existsSync as existsSync22 } from "fs";
7672
+ import { join as join31 } from "path";
7673
+ import { existsSync as existsSync24 } from "fs";
7352
7674
  var TYPE_COLORS = {
7353
7675
  entities: "#e74c3c",
7354
7676
  concepts: "#27ae60",
@@ -7416,9 +7738,9 @@ ${nodeSvg}
7416
7738
  return { html, node_count: nodes.length, edge_count: edges.length, truncated };
7417
7739
  }
7418
7740
  async function fetchGraphHtmlReport(input) {
7419
- const graphPath = input.graphPath ?? join29(input.vault, ".skillwiki", "graph.json");
7741
+ const graphPath = input.graphPath ?? join31(input.vault, ".skillwiki", "graph.json");
7420
7742
  const maxNodes = Math.min(Math.max(10, input.maxNodes ?? 120), 500);
7421
- if (!existsSync22(graphPath)) {
7743
+ if (!existsSync24(graphPath)) {
7422
7744
  return {
7423
7745
  exitCode: ExitCode.FILE_NOT_FOUND,
7424
7746
  result: err("GRAPH_MISSING", { path: graphPath, hint: "Run skillwiki.graph_build first." })
@@ -7490,7 +7812,7 @@ async function fetchStaleSummary(input) {
7490
7812
 
7491
7813
  // src/mcp/resources.ts
7492
7814
  async function readVaultFile(vault, rel) {
7493
- return readFile11(join30(vault, rel), "utf8");
7815
+ return readFile11(join32(vault, rel), "utf8");
7494
7816
  }
7495
7817
  async function tailLines(text, lines) {
7496
7818
  const parts = text.split(/\r?\n/);
@@ -7576,7 +7898,7 @@ function registerMcpResources(server) {
7576
7898
  if (!v.ok) {
7577
7899
  return { contents: [{ uri: uri.href, mimeType: "text/plain", text: JSON.stringify(v) }] };
7578
7900
  }
7579
- const path = join30(v.data.vault, ".skillwiki", "graph.json");
7901
+ const path = join32(v.data.vault, ".skillwiki", "graph.json");
7580
7902
  try {
7581
7903
  const raw = await readFile11(path, "utf8");
7582
7904
  const graph = JSON.parse(raw);
@@ -7951,6 +8273,8 @@ export {
7951
8273
  evaluateSatelliteRunHealthState,
7952
8274
  withMcpClient,
7953
8275
  DEFAULT_MCP_URL,
8276
+ CLIP_NOT_CONFIGURED,
8277
+ loadClipOutboxConfig,
7954
8278
  runDoctor,
7955
8279
  readCliPackageJson,
7956
8280
  DEFAULT_DIRTY_VOLUME_THRESHOLD,