skillwiki 0.9.52 → 0.9.56

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.
@@ -1,13 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- CACHE_FILENAME,
4
- CHECK_INTERVAL_MS,
5
- CLI_DISABLE_FLAG,
6
- DIST_TAG,
7
- ENV_DISABLE_KEY,
8
- normalizeDistTag,
3
+ latestFromCache,
9
4
  semverGt
10
- } from "./chunk-E6UWZ3S3.js";
5
+ } from "./chunk-7I2TPIV5.js";
11
6
 
12
7
  // ../shared/src/exit-codes.ts
13
8
  var ExitCode = {
@@ -2852,7 +2847,7 @@ function buildCliSurface() {
2852
2847
  program.command("lint").option("--days <n>").option("--lines <n>").option("--log-threshold <n>").option("--fix").option("--only <bucket>").option("--summary").option("--examples <n>").option("--wiki <name>");
2853
2848
  program.command("config");
2854
2849
  program.command("health").option("--wiki <name>").option("--sync <mode>").option("--no-fail").option("--out <path>").option("--examples <n>");
2855
- program.command("doctor");
2850
+ program.command("doctor").option("--check-snapshotter");
2856
2851
  program.command("status").option("--wiki <name>");
2857
2852
  program.command("archive").option("--wiki <name>").option("--cascade").option("--apply").option("--remote <remote>").option("--remote-delete").option("--max-remote-deletes <n>");
2858
2853
  program.command("drift").option("--apply").option("--new <date>").option("--wiki <name>");
@@ -2887,7 +2882,7 @@ function buildCliSurface() {
2887
2882
  compoundCmd.command("list").requiredOption("--project <slug>").option("--wiki <name>");
2888
2883
  compoundCmd.command("delete").requiredOption("--project <slug>").option("--wiki <name>");
2889
2884
  const syncCmd = program.commands.find((c) => c.name() === "sync");
2890
- syncCmd.command("status").option("--wiki <name>").option("--include-stashes");
2885
+ syncCmd.command("status").option("--wiki <name>").option("--include-stashes").option("--include-remote-health").option("--check-snapshotter");
2891
2886
  syncCmd.command("push").option("--wiki <name>");
2892
2887
  syncCmd.command("pull").option("--wiki <name>");
2893
2888
  syncCmd.command("lock").option("--summary <text>").option("--ttl-minutes <n>").option("--force").option("--wiki <name>");
@@ -3100,7 +3095,7 @@ function extractSourceEntries(rawFm) {
3100
3095
  }
3101
3096
  return entries;
3102
3097
  }
3103
- var ERROR_ORDER = ["sensitive_content", "broken_wikilinks", "invalid_frontmatter", "raw_source_identity_conflict", "raw_dedup", "broken_sources", "tag_not_in_taxonomy", "path_too_long"];
3098
+ var ERROR_ORDER = ["sensitive_content", "conflict_markers", "broken_wikilinks", "invalid_frontmatter", "raw_source_identity_conflict", "raw_dedup", "broken_sources", "tag_not_in_taxonomy", "path_too_long"];
3104
3099
  var WARNING_ORDER = ["raw_body_duplicate", "raw_subdirectory_duplicate", "file_source_url", "index_incomplete", "index_link_format", "stale_page", "page_too_large", "log_rotate_needed", "orphans", "compound_refs", "legacy_citation_style", "orphaned_citations", "duplicate_frontmatter", "frontmatter_yaml_invalid", "work_item_health", "orphaned_project_pages", "missing_overview", "missing_diagram"];
3105
3100
  var INFO_ORDER = ["bridges", "sparse_community", "page_structure", "topic_map_recommended", "frontmatter_wikilink", "wikilink_citation", "missing_tldr", "stale_sections", "cli_refs"];
3106
3101
  var KNOWN_BUCKETS = [...ERROR_ORDER, ...WARNING_ORDER, ...INFO_ORDER];
@@ -3448,6 +3443,38 @@ async function runFileSourceUrlOnly(input) {
3448
3443
  const match = remaining.size > 0 ? [{ kind: "file_source_url", items: [...remaining] }] : [];
3449
3444
  return outputForOnlyBucket(input, match, fixed, unresolved, readVault);
3450
3445
  }
3446
+ function scanConflictMarkerBlocks(path, text) {
3447
+ const findings = [];
3448
+ const lines = text.split(/\r?\n/);
3449
+ let inFence = false;
3450
+ let openLine = 0;
3451
+ let sawSeparator = false;
3452
+ for (let i = 0; i < lines.length; i += 1) {
3453
+ const line = lines[i];
3454
+ if (line.startsWith("```") || line.startsWith("~~~")) {
3455
+ inFence = !inFence;
3456
+ continue;
3457
+ }
3458
+ if (inFence) continue;
3459
+ if (line.startsWith("<<<<<<< ")) {
3460
+ openLine = i + 1;
3461
+ sawSeparator = false;
3462
+ continue;
3463
+ }
3464
+ if (line === "=======" && openLine > 0) {
3465
+ sawSeparator = true;
3466
+ continue;
3467
+ }
3468
+ if (line.startsWith(">>>>>>> ")) {
3469
+ if (openLine > 0 && sawSeparator) {
3470
+ findings.push({ path, line: openLine, message: "complete Git conflict-marker block" });
3471
+ }
3472
+ openLine = 0;
3473
+ sawSeparator = false;
3474
+ }
3475
+ }
3476
+ return findings;
3477
+ }
3451
3478
  async function runLint(input) {
3452
3479
  if (input.only && !KNOWN_BUCKETS.includes(input.only)) {
3453
3480
  return {
@@ -3545,10 +3572,12 @@ async function runLint(input) {
3545
3572
  {
3546
3573
  const allPageResults = await mapWithConcurrency(scan.allMarkdown, vaultIoConcurrency(), async (page) => {
3547
3574
  const sensitiveFlags2 = [];
3575
+ const conflictMarkers2 = [];
3548
3576
  let fmYamlInvalid2 = null;
3549
3577
  try {
3550
3578
  const text = await readPageCached(page, pageTextCache);
3551
3579
  sensitiveFlags2.push(...scanSensitiveContent(text, { file: page.relPath }));
3580
+ conflictMarkers2.push(...scanConflictMarkerBlocks(page.relPath, text));
3552
3581
  const fm = extractFrontmatter(text);
3553
3582
  if (!fm.ok && fm.error === "INVALID_FRONTMATTER") {
3554
3583
  const detail = fm.detail;
@@ -3557,10 +3586,12 @@ async function runLint(input) {
3557
3586
  }
3558
3587
  } catch {
3559
3588
  }
3560
- return { sensitiveFlags: sensitiveFlags2, fmYamlInvalid: fmYamlInvalid2 };
3589
+ return { sensitiveFlags: sensitiveFlags2, conflictMarkers: conflictMarkers2, fmYamlInvalid: fmYamlInvalid2 };
3561
3590
  });
3562
3591
  const sensitiveFlags = allPageResults.flatMap((result) => result.sensitiveFlags);
3563
3592
  if (sensitiveFlags.length > 0) buckets.sensitive_content = sensitiveFlags;
3593
+ const conflictMarkers = allPageResults.flatMap((result) => result.conflictMarkers);
3594
+ if (conflictMarkers.length > 0) buckets.conflict_markers = conflictMarkers;
3564
3595
  const fmYamlInvalid = allPageResults.map((result) => result.fmYamlInvalid).filter((item) => item !== null);
3565
3596
  if (fmYamlInvalid.length > 0) buckets.frontmatter_yaml_invalid = fmYamlInvalid;
3566
3597
  const subDirDupes = [];
@@ -4281,71 +4312,12 @@ async function runConfigPath(input) {
4281
4312
  return { exitCode: ExitCode.OK, result: ok({ path: filePath, exists: existsSync6(filePath), humanHint: filePath }) };
4282
4313
  }
4283
4314
 
4284
- // src/utils/auto-update.ts
4285
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync7, mkdirSync as mkdirSync3 } from "fs";
4286
- import { join as join17, dirname as dirname7 } from "path";
4287
- import { spawn } from "child_process";
4288
- function cachePath(home) {
4289
- return join17(home, ".skillwiki", CACHE_FILENAME);
4290
- }
4291
- function readCacheRaw(home) {
4292
- try {
4293
- const raw = readFileSync4(cachePath(home), "utf8");
4294
- return JSON.parse(raw);
4295
- } catch {
4296
- return null;
4297
- }
4298
- }
4299
- function readCache(home) {
4300
- const cache = readCacheRaw(home);
4301
- if (!cache) return { cache: null, hasUpdate: false, isStale: true };
4302
- const isStale = Date.now() - cache.lastCheck >= CHECK_INTERVAL_MS;
4303
- const hasUpdate = !!cache.latestVersion && semverGt(cache.latestVersion, cache.currentVersion);
4304
- return { cache, hasUpdate, isStale };
4305
- }
4306
- function writeCache(home, cache) {
4307
- const p = cachePath(home);
4308
- mkdirSync3(dirname7(p), { recursive: true });
4309
- writeFileSync3(p, JSON.stringify(cache, null, 2));
4310
- }
4311
- function latestFromCache(home, currentVersion) {
4312
- const { cache } = readCache(home);
4313
- if (!cache || !cache.latestVersion) return { hasUpdate: false, latest: null, distTag: DIST_TAG };
4314
- const distTag = normalizeDistTag(cache.distTag);
4315
- return {
4316
- hasUpdate: semverGt(cache.latestVersion, currentVersion),
4317
- latest: cache.latestVersion,
4318
- distTag
4319
- };
4320
- }
4321
- function distTagFromCache(home) {
4322
- return normalizeDistTag(readCacheRaw(home)?.distTag);
4323
- }
4324
- function isDisabled() {
4325
- return !!(process.env[ENV_DISABLE_KEY] || process.env.NODE_ENV === "test" || process.argv.includes(CLI_DISABLE_FLAG));
4326
- }
4327
- function triggerAutoUpdate(home, currentVersion) {
4328
- if (isDisabled()) return;
4329
- const { isStale } = readCache(home);
4330
- if (!isStale) return;
4331
- const distTag = distTagFromCache(home);
4332
- const bgScript = new URL("../auto-update-bg.js", import.meta.url).pathname;
4333
- if (!existsSync7(bgScript)) return;
4334
- const child = spawn(process.execPath, [bgScript, home, currentVersion, distTag], {
4335
- detached: true,
4336
- stdio: "ignore"
4337
- });
4338
- child.on("error", () => {
4339
- });
4340
- child.unref();
4341
- }
4342
-
4343
4315
  // src/commands/fleet.ts
4344
4316
  import { readFile as readFile14 } from "fs/promises";
4345
4317
  import { hostname as nodeHostname, userInfo } from "os";
4346
- import { join as join18 } from "path";
4318
+ import { join as join17 } from "path";
4347
4319
  import yaml3 from "js-yaml";
4348
- var FLEET_REL_PATH = join18("projects", "llm-wiki", "architecture", "fleet.yaml");
4320
+ var FLEET_REL_PATH = join17("projects", "llm-wiki", "architecture", "fleet.yaml");
4349
4321
  async function runFleetValidate(input) {
4350
4322
  const loaded = await loadFleetManifest(input.file);
4351
4323
  if (!loaded.ok) {
@@ -4376,7 +4348,7 @@ async function runFleetContext(input) {
4376
4348
  const osHostname = input.osHostname ?? safeEnvValue(env.HOSTNAME) ?? nodeHostname();
4377
4349
  const user = input.user ?? safeEnvValue(env.USER) ?? safeUserName();
4378
4350
  const vault = input.vault ?? safeEnvValue(env.WIKI_PATH);
4379
- const file = input.file ?? (vault ? join18(vault, FLEET_REL_PATH) : void 0);
4351
+ const file = input.file ?? (vault ? join17(vault, FLEET_REL_PATH) : void 0);
4380
4352
  const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
4381
4353
  const loaded = file ? await loadFleetManifest(file) : { ok: false, error: "FILE_NOT_FOUND" };
4382
4354
  if (!loaded.ok) {
@@ -4496,7 +4468,7 @@ function fleetContextEnv(input) {
4496
4468
  const home = input.home ?? env.HOME ?? "";
4497
4469
  const osHostname = input.osHostname ?? safeEnvValue(env.HOSTNAME) ?? nodeHostname();
4498
4470
  const vault = input.vault ?? safeEnvValue(env.WIKI_PATH);
4499
- const file = input.file ?? (vault ? join18(vault, FLEET_REL_PATH) : void 0);
4471
+ const file = input.file ?? (vault ? join17(vault, FLEET_REL_PATH) : void 0);
4500
4472
  return { env, home, osHostname, vault, file };
4501
4473
  }
4502
4474
  async function loadFleetManifestAndHost(input) {
@@ -4538,6 +4510,15 @@ async function loadFleetManifestAndHost(input) {
4538
4510
  identityStatus: "known"
4539
4511
  };
4540
4512
  }
4513
+ function snapshotterAliasForLocalHost(fleetLoad) {
4514
+ if (!fleetLoad?.manifest || !fleetLoad.hostId) return void 0;
4515
+ const snapshotterId = Object.entries(fleetLoad.manifest.hosts).find(([, h]) => h.role === "snapshotter")?.[0];
4516
+ if (!snapshotterId) return void 0;
4517
+ const profile = fleetLoad.manifest.hosts[snapshotterId]?.access?.from?.[fleetLoad.hostId];
4518
+ if (!profile || profile.status !== "configured" && profile.status !== "local") return void 0;
4519
+ const aliases = profile.ssh_aliases ?? [];
4520
+ return aliases.length > 0 ? aliases[0] : void 0;
4521
+ }
4541
4522
  function satelliteGateFromFleetLoad(load) {
4542
4523
  if (!load?.hostId) return { satelliteExpected: false };
4543
4524
  const host = load.manifest.hosts[load.hostId];
@@ -4621,7 +4602,7 @@ async function resolveFleetHostId(input) {
4621
4602
  }
4622
4603
  trace.push({ source: "AGENT_HOST_ID", status: "unset" });
4623
4604
  if (input.home) {
4624
- const dotenv = await parseDotenvFile(join18(input.home, ".skillwiki", ".env"));
4605
+ const dotenv = await parseDotenvFile(join17(input.home, ".skillwiki", ".env"));
4625
4606
  if (dotenv.SKILLWIKI_HOST_ID) {
4626
4607
  trace.push({ source: "~/.skillwiki/.env:SKILLWIKI_HOST_ID", status: "matched", value: dotenv.SKILLWIKI_HOST_ID });
4627
4608
  return { hostId: dotenv.SKILLWIKI_HOST_ID, source: "~/.skillwiki/.env:SKILLWIKI_HOST_ID", trace };
@@ -4794,20 +4775,20 @@ function safeUserName() {
4794
4775
  }
4795
4776
 
4796
4777
  // src/commands/doctor.ts
4797
- import { existsSync as existsSync11, lstatSync, readlinkSync, readdirSync as readdirSync2, statSync, readFileSync as readFileSync8 } from "fs";
4798
- import { join as join22, resolve as resolve5 } from "path";
4778
+ import { existsSync as existsSync12, lstatSync, readlinkSync, readdirSync as readdirSync3, statSync, readFileSync as readFileSync9 } from "fs";
4779
+ import { join as join23, resolve as resolve5 } from "path";
4799
4780
  import { execSync as execSync2 } from "child_process";
4800
4781
  import { platform as platform2 } from "os";
4801
4782
 
4802
4783
  // src/utils/plugin-registry.ts
4803
- import { existsSync as existsSync8, readdirSync, readFileSync as readFileSync5 } from "fs";
4804
- import { join as join19 } from "path";
4805
- var REGISTRY_PATH = join19(".claude", "plugins", "installed_plugins.json");
4806
- var CODEX_CONFIG_PATH = join19(".codex", "config.toml");
4784
+ import { existsSync as existsSync7, readdirSync, readFileSync as readFileSync4 } from "fs";
4785
+ import { join as join18 } from "path";
4786
+ var REGISTRY_PATH = join18(".claude", "plugins", "installed_plugins.json");
4787
+ var CODEX_CONFIG_PATH = join18(".codex", "config.toml");
4807
4788
  var PLUGIN_KEY = "skillwiki@llm-wiki";
4808
4789
  function readInstalledPlugins(home) {
4809
4790
  try {
4810
- const raw = readFileSync5(join19(home, REGISTRY_PATH), "utf8");
4791
+ const raw = readFileSync4(join18(home, REGISTRY_PATH), "utf8");
4811
4792
  return JSON.parse(raw);
4812
4793
  } catch {
4813
4794
  return null;
@@ -4843,8 +4824,8 @@ function findPluginInstallations(home, key = PLUGIN_KEY) {
4843
4824
  function findCodexPlugin(home, key, pluginName, marketplace) {
4844
4825
  const config = readCodexPluginConfig(home, key, marketplace);
4845
4826
  if (!config?.enabled) return null;
4846
- const cacheRoot = join19(home, ".codex", "plugins", "cache", marketplace, pluginName);
4847
- if (!existsSync8(cacheRoot)) return null;
4827
+ const cacheRoot = join18(home, ".codex", "plugins", "cache", marketplace, pluginName);
4828
+ if (!existsSync7(cacheRoot)) return null;
4848
4829
  let versions;
4849
4830
  try {
4850
4831
  versions = readdirSync(cacheRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
@@ -4859,7 +4840,7 @@ function findCodexPlugin(home, key, pluginName, marketplace) {
4859
4840
  key,
4860
4841
  pluginName,
4861
4842
  marketplace,
4862
- installPath: join19(cacheRoot, version),
4843
+ installPath: join18(cacheRoot, version),
4863
4844
  version,
4864
4845
  sourceType: config.sourceType,
4865
4846
  source: config.source
@@ -4876,7 +4857,7 @@ function parsePluginKey(key) {
4876
4857
  function readCodexPluginConfig(home, key, marketplace) {
4877
4858
  let raw;
4878
4859
  try {
4879
- raw = readFileSync5(join19(home, CODEX_CONFIG_PATH), "utf8");
4860
+ raw = readFileSync4(join18(home, CODEX_CONFIG_PATH), "utf8");
4880
4861
  } catch {
4881
4862
  return null;
4882
4863
  }
@@ -4917,12 +4898,180 @@ function parseTomlScalar(rawValue) {
4917
4898
  return value;
4918
4899
  }
4919
4900
 
4920
- // src/utils/satellite-run-health.ts
4901
+ // src/utils/conflict-markers.ts
4902
+ import { existsSync as existsSync8, readdirSync as readdirSync2, readFileSync as readFileSync5 } from "fs";
4903
+ import { join as join19 } from "path";
4904
+ function scanConflictMarkerBlocksInText(relPath, text) {
4905
+ const findings = [];
4906
+ const lines = text.split(/\r?\n/);
4907
+ let inFence = false;
4908
+ let openLine = 0;
4909
+ let sawSeparator = false;
4910
+ for (let i = 0; i < lines.length; i += 1) {
4911
+ const line = lines[i];
4912
+ if (line.startsWith("```") || line.startsWith("~~~")) {
4913
+ inFence = !inFence;
4914
+ continue;
4915
+ }
4916
+ if (inFence) continue;
4917
+ if (line.startsWith("<<<<<<< ")) {
4918
+ openLine = i + 1;
4919
+ sawSeparator = false;
4920
+ continue;
4921
+ }
4922
+ if (line === "=======" && openLine > 0) {
4923
+ sawSeparator = true;
4924
+ continue;
4925
+ }
4926
+ if (line.startsWith(">>>>>>> ")) {
4927
+ if (openLine > 0 && sawSeparator) {
4928
+ findings.push({ path: relPath, line: openLine });
4929
+ }
4930
+ openLine = 0;
4931
+ sawSeparator = false;
4932
+ }
4933
+ }
4934
+ return findings;
4935
+ }
4936
+ var PRUNE_DIRS = /* @__PURE__ */ new Set([
4937
+ ".git",
4938
+ ".obsidian",
4939
+ ".skillwiki",
4940
+ ".claude",
4941
+ ".antigravitycli",
4942
+ ".playwright-cli"
4943
+ ]);
4944
+ function walkMarkdownFiles2(root, dir, rel, out) {
4945
+ let entries;
4946
+ try {
4947
+ entries = readdirSync2(dir, { withFileTypes: true });
4948
+ } catch {
4949
+ return;
4950
+ }
4951
+ for (const entry of entries) {
4952
+ if (entry.isDirectory()) {
4953
+ if (PRUNE_DIRS.has(entry.name)) continue;
4954
+ walkMarkdownFiles2(root, join19(dir, entry.name), rel ? `${rel}/${entry.name}` : entry.name, out);
4955
+ } else if (entry.isFile() && entry.name.endsWith(".md")) {
4956
+ out.push(rel ? `${rel}/${entry.name}` : entry.name);
4957
+ }
4958
+ }
4959
+ }
4960
+ function scanVaultConflictMarkers(vaultRoot) {
4961
+ if (!existsSync8(vaultRoot)) return [];
4962
+ const relPaths = [];
4963
+ walkMarkdownFiles2(vaultRoot, vaultRoot, "", relPaths);
4964
+ const all = [];
4965
+ for (const rel of relPaths) {
4966
+ let text;
4967
+ try {
4968
+ text = readFileSync5(join19(vaultRoot, rel), "utf8");
4969
+ } catch {
4970
+ continue;
4971
+ }
4972
+ all.push(...scanConflictMarkerBlocksInText(rel, text));
4973
+ }
4974
+ return all;
4975
+ }
4976
+
4977
+ // src/utils/remote-health.ts
4921
4978
  import { existsSync as existsSync9, readFileSync as readFileSync6 } from "fs";
4922
4979
  import { join as join20 } from "path";
4980
+ import { execFileSync } from "child_process";
4981
+ var REMOTE_PROBE_TIMEOUT_MS = 3e3;
4982
+ var defaultExec = (file, args, cwd) => execFileSync(file, args, {
4983
+ cwd,
4984
+ encoding: "utf8",
4985
+ stdio: ["pipe", "pipe", "pipe"],
4986
+ timeout: REMOTE_PROBE_TIMEOUT_MS
4987
+ }).trim();
4988
+ var DEFAULT_WIKI_S3_REMOTE = "seaweed-wiki:cloud/wiki";
4989
+ function readWikiS3RemoteConfigured(home) {
4990
+ try {
4991
+ const content = readFileSync6(join20(home, ".skillwiki", ".env"), "utf8");
4992
+ for (const line of content.split(/\r?\n/)) {
4993
+ const trimmed = line.trim();
4994
+ if (!trimmed || trimmed.startsWith("#")) continue;
4995
+ const eq = trimmed.indexOf("=");
4996
+ if (eq <= 0) continue;
4997
+ const k = trimmed.slice(0, eq).trim();
4998
+ const v = trimmed.slice(eq + 1).trim();
4999
+ if (k === "WIKI_REMOTE" && v.length > 0) return v;
5000
+ }
5001
+ } catch {
5002
+ }
5003
+ return void 0;
5004
+ }
5005
+ function readWikiS3RemoteFromEnv(home) {
5006
+ return readWikiS3RemoteConfigured(home) ?? DEFAULT_WIKI_S3_REMOTE;
5007
+ }
5008
+ function probeGithubReachability(vaultPath, exec = defaultExec) {
5009
+ if (!existsSync9(join20(vaultPath, ".git"))) return "unknown";
5010
+ try {
5011
+ exec("git", ["remote", "get-url", "origin"], vaultPath);
5012
+ } catch {
5013
+ return "unknown";
5014
+ }
5015
+ try {
5016
+ const out = exec("git", ["ls-remote", "origin", "refs/heads/main"], vaultPath);
5017
+ if (out.length > 0) return "ok";
5018
+ return "unreachable";
5019
+ } catch {
5020
+ return "unreachable";
5021
+ }
5022
+ }
5023
+ function probeS3Reachability(remote, exec = defaultExec) {
5024
+ if (!remote) return "unknown";
5025
+ try {
5026
+ exec("rclone", ["lsf", remote, "--max-depth", "1", "--files-only"]);
5027
+ return "ok";
5028
+ } catch {
5029
+ return "unreachable";
5030
+ }
5031
+ }
5032
+ function probeSnapshotterSsh(sshAlias2, exec = defaultExec) {
5033
+ if (!sshAlias2) return "unknown";
5034
+ try {
5035
+ exec("ssh", [
5036
+ "-o",
5037
+ "BatchMode=yes",
5038
+ "-o",
5039
+ "ConnectTimeout=3",
5040
+ "-o",
5041
+ "StrictHostKeyChecking=accept-new",
5042
+ sshAlias2,
5043
+ "true"
5044
+ ]);
5045
+ return "ok";
5046
+ } catch {
5047
+ return "unreachable";
5048
+ }
5049
+ }
5050
+ function buildDegradedReasons(health) {
5051
+ const reasons = [];
5052
+ if (health.github === "unreachable") reasons.push("github_remote_unreachable");
5053
+ if (health.s3 === "unreachable") reasons.push("s3_remote_unreachable");
5054
+ if (health.snapshotter === "unreachable") reasons.push("snapshotter_host_unreachable");
5055
+ return reasons;
5056
+ }
5057
+ function probeRemoteHealth(input) {
5058
+ const exec = input.exec ?? defaultExec;
5059
+ const github = probeGithubReachability(input.vaultPath, exec);
5060
+ const s3Remote = input.s3Remote ?? readWikiS3RemoteFromEnv(input.home);
5061
+ const s3 = probeS3Reachability(s3Remote, exec);
5062
+ let snapshotter = "not_checked";
5063
+ if (input.checkSnapshotter && input.snapshotterAlias) {
5064
+ snapshotter = probeSnapshotterSsh(input.snapshotterAlias, exec);
5065
+ }
5066
+ return { github, s3, snapshotter };
5067
+ }
5068
+
5069
+ // src/utils/satellite-run-health.ts
5070
+ import { existsSync as existsSync10, readFileSync as readFileSync7 } from "fs";
5071
+ import { join as join21 } from "path";
4923
5072
  var SATELLITE_STALE_MS = 26 * 60 * 60 * 1e3;
4924
5073
  function satelliteLatestRunPath(vault) {
4925
- return join20(vault, ".skillwiki", "agent-memory-trends", "latest-run.json");
5074
+ return join21(vault, ".skillwiki", "agent-memory-trends", "latest-run.json");
4926
5075
  }
4927
5076
  function isFailedRunStatus(status) {
4928
5077
  return status === "fail" || status === "failure";
@@ -4944,9 +5093,9 @@ function readSatelliteLatestRunFromText(text) {
4944
5093
  }
4945
5094
  function readSatelliteLatestRun(vault) {
4946
5095
  const latestPath = satelliteLatestRunPath(vault);
4947
- if (!existsSync9(latestPath)) return null;
5096
+ if (!existsSync10(latestPath)) return null;
4948
5097
  try {
4949
- return parseLatestRunFile(readFileSync6(latestPath, "utf8"));
5098
+ return parseLatestRunFile(readFileSync7(latestPath, "utf8"));
4950
5099
  } catch {
4951
5100
  return null;
4952
5101
  }
@@ -4975,8 +5124,8 @@ function evaluateSatelliteRunHealth(vault, now) {
4975
5124
  // src/utils/s3-mount-health.ts
4976
5125
  import { execSync } from "child_process";
4977
5126
  import { platform } from "os";
4978
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, unlinkSync as unlinkSync3, readFileSync as readFile15 } from "fs";
4979
- import { join as join21 } from "path";
5127
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync3, unlinkSync as unlinkSync3, readFileSync as readFile15 } from "fs";
5128
+ import { join as join22 } from "path";
4980
5129
  var OS = platform();
4981
5130
  function findRcloneMountPid() {
4982
5131
  try {
@@ -5060,7 +5209,7 @@ function extractRcloneFs(args) {
5060
5209
  function getRcloneArgs(pid) {
5061
5210
  try {
5062
5211
  if (OS === "linux") {
5063
- const raw = readFileSync7(`/proc/${pid}/cmdline`);
5212
+ const raw = readFileSync8(`/proc/${pid}/cmdline`);
5064
5213
  return new TextDecoder().decode(raw).split("\0").filter(Boolean);
5065
5214
  } else {
5066
5215
  const out = execSync(`ps -o args= -p ${pid}`, {
@@ -5103,7 +5252,7 @@ function queryRcloneRC(rcAddr, fs) {
5103
5252
  function detectFuseMount(vaultPath) {
5104
5253
  try {
5105
5254
  if (OS === "linux") {
5106
- const mounts = readFileSync7("/proc/mounts", "utf8");
5255
+ const mounts = readFileSync8("/proc/mounts", "utf8");
5107
5256
  let best = null;
5108
5257
  for (const line of mounts.split("\n")) {
5109
5258
  const parts = line.split(" ");
@@ -5134,11 +5283,11 @@ function detectFuseMount(vaultPath) {
5134
5283
  return null;
5135
5284
  }
5136
5285
  function writeTest(dir) {
5137
- const testFile = join21(dir, `.doctor-write-test-${process.pid}.tmp`);
5286
+ const testFile = join22(dir, `.doctor-write-test-${process.pid}.tmp`);
5138
5287
  const payload = `skillwiki doctor write test \u2014 ${Date.now()} \u2014 ${Math.random().toString(36).slice(2)}`;
5139
5288
  const start = Date.now();
5140
5289
  try {
5141
- writeFileSync4(testFile, payload, "utf8");
5290
+ writeFileSync3(testFile, payload, "utf8");
5142
5291
  } catch (e) {
5143
5292
  return { success: false, writeMs: Date.now() - start, readMs: 0, size: 0, error: `write failed: ${e.message}` };
5144
5293
  }
@@ -5234,13 +5383,13 @@ function detectCliChannels(argv, home) {
5234
5383
  }
5235
5384
  const plugin = findPlugin(home);
5236
5385
  if (plugin) {
5237
- const pluginBin = join22(plugin.installPath, "bin", "skillwiki");
5238
- if (existsSync11(pluginBin)) {
5386
+ const pluginBin = join23(plugin.installPath, "bin", "skillwiki");
5387
+ if (existsSync12(pluginBin)) {
5239
5388
  channels.push({ name: "plugin", path: pluginBin, isDevLink: false });
5240
5389
  }
5241
5390
  }
5242
- const installBin = join22(home, ".claude", "skills", "bin", "skillwiki");
5243
- if (existsSync11(installBin)) {
5391
+ const installBin = join23(home, ".claude", "skills", "bin", "skillwiki");
5392
+ if (existsSync12(installBin)) {
5244
5393
  channels.push({ name: "install", path: installBin, isDevLink: false });
5245
5394
  }
5246
5395
  return channels;
@@ -5301,7 +5450,7 @@ function isDevSourceRun(argv) {
5301
5450
  }
5302
5451
  async function checkConfigFile(home) {
5303
5452
  const cfgPath = configPath(home);
5304
- if (!existsSync11(cfgPath)) {
5453
+ if (!existsSync12(cfgPath)) {
5305
5454
  return check("warn", "config_file", "Config file exists", `${cfgPath} not found`);
5306
5455
  }
5307
5456
  try {
@@ -5316,7 +5465,7 @@ function checkWikiPathExists(resolvedPath) {
5316
5465
  if (resolvedPath === void 0) {
5317
5466
  return check("error", "wiki_path_exists", "Vault directory exists", "Cannot check \u2014 WIKI_PATH not resolved");
5318
5467
  }
5319
- if (existsSync11(resolvedPath) && statSync(resolvedPath).isDirectory()) {
5468
+ if (existsSync12(resolvedPath) && statSync(resolvedPath).isDirectory()) {
5320
5469
  return check("pass", "wiki_path_exists", "Vault directory exists", resolvedPath);
5321
5470
  }
5322
5471
  return check("error", "wiki_path_exists", "Vault directory exists", `${resolvedPath} does not exist or is not a directory`);
@@ -5325,13 +5474,13 @@ function checkVaultStructure(resolvedPath) {
5325
5474
  if (resolvedPath === void 0) {
5326
5475
  return check("error", "vault_structure", "Vault structure valid", "Cannot check \u2014 WIKI_PATH not resolved");
5327
5476
  }
5328
- if (!existsSync11(resolvedPath)) {
5477
+ if (!existsSync12(resolvedPath)) {
5329
5478
  return check("error", "vault_structure", "Vault structure valid", "Cannot check \u2014 vault directory does not exist");
5330
5479
  }
5331
5480
  const missing = [];
5332
- if (!existsSync11(join22(resolvedPath, "SCHEMA.md"))) missing.push("SCHEMA.md");
5481
+ if (!existsSync12(join23(resolvedPath, "SCHEMA.md"))) missing.push("SCHEMA.md");
5333
5482
  for (const dir of ["raw", "entities", "concepts", "meta"]) {
5334
- if (!existsSync11(join22(resolvedPath, dir))) missing.push(dir + "/");
5483
+ if (!existsSync12(join23(resolvedPath, dir))) missing.push(dir + "/");
5335
5484
  }
5336
5485
  if (missing.length === 0) {
5337
5486
  return check("pass", "vault_structure", "Vault structure valid", "All required files and directories present");
@@ -5339,8 +5488,8 @@ function checkVaultStructure(resolvedPath) {
5339
5488
  return check("warn", "vault_structure", "Vault structure valid", `Missing: ${missing.join(", ")} \u2014 run \`skillwiki init\` to add CodeWiki structure`);
5340
5489
  }
5341
5490
  function checkSkillsInstalled(home, cwd) {
5342
- const srcDir = cwd ? join22(cwd, "packages", "skills") : void 0;
5343
- if (srcDir && existsSync11(srcDir)) {
5491
+ const srcDir = cwd ? join23(cwd, "packages", "skills") : void 0;
5492
+ if (srcDir && existsSync12(srcDir)) {
5344
5493
  const found = findInstalledSkillMd(srcDir);
5345
5494
  if (found.length > 0) {
5346
5495
  return check("pass", "skills_installed", "Skills installed", `${found.length} SKILL.md file(s) found (source)`);
@@ -5353,8 +5502,8 @@ function checkSkillsInstalled(home, cwd) {
5353
5502
  return check("pass", "skills_installed", "Skills installed", `${found.length} SKILL.md file(s) found (plugin v${plugin.version})`);
5354
5503
  }
5355
5504
  }
5356
- const skillsDir = join22(home, ".claude", "skills");
5357
- if (existsSync11(skillsDir)) {
5505
+ const skillsDir = join23(home, ".claude", "skills");
5506
+ if (existsSync12(skillsDir)) {
5358
5507
  const found = findInstalledSkillMd(skillsDir);
5359
5508
  if (found.length > 0) {
5360
5509
  return check("pass", "skills_installed", "Skills installed", `${found.length} SKILL.md file(s) found (CLI install)`);
@@ -5364,10 +5513,10 @@ function checkSkillsInstalled(home, cwd) {
5364
5513
  }
5365
5514
  function checkDuplicateSkills(home) {
5366
5515
  const plugin = findPlugin(home);
5367
- const skillsDir = join22(home, ".claude", "skills");
5516
+ const skillsDir = join23(home, ".claude", "skills");
5368
5517
  const agentSkillDirs = [
5369
- { label: "~/.codex/skills/", path: join22(home, ".codex", "skills") },
5370
- { label: "~/.agents/skills/", path: join22(home, ".agents", "skills") }
5518
+ { label: "~/.codex/skills/", path: join23(home, ".codex", "skills") },
5519
+ { label: "~/.agents/skills/", path: join23(home, ".agents", "skills") }
5371
5520
  ];
5372
5521
  if (!plugin) {
5373
5522
  return check("pass", "skills_duplicate", "Skills not duplicated", "Single install channel");
@@ -5470,8 +5619,8 @@ async function checkProfiles(home) {
5470
5619
  }
5471
5620
  async function checkProjectLocalOverride(cwd) {
5472
5621
  const dir = cwd ?? process.cwd();
5473
- const envPath = join22(dir, ".skillwiki", ".env");
5474
- if (existsSync11(envPath)) {
5622
+ const envPath = join23(dir, ".skillwiki", ".env");
5623
+ if (existsSync12(envPath)) {
5475
5624
  return check("pass", "project_local", "Project-local config", `Found: ${envPath}`);
5476
5625
  }
5477
5626
  return check("pass", "project_local", "Project-local config", "None");
@@ -5480,7 +5629,7 @@ function checkVaultGitRemote(resolvedPath) {
5480
5629
  if (resolvedPath === void 0) {
5481
5630
  return check("error", "vault_git_remote", "Vault git remote", "Cannot check \u2014 WIKI_PATH not resolved");
5482
5631
  }
5483
- if (!existsSync11(join22(resolvedPath, ".git"))) {
5632
+ if (!existsSync12(join23(resolvedPath, ".git"))) {
5484
5633
  return check("warn", "vault_git_remote", "Vault git remote", "Vault is not a git repository \u2014 sync features unavailable");
5485
5634
  }
5486
5635
  try {
@@ -5503,9 +5652,9 @@ function checkObsidianTemplates(resolvedPath) {
5503
5652
  return check("error", "obsidian_templates", "Obsidian templates", "Cannot check \u2014 WIKI_PATH not resolved");
5504
5653
  }
5505
5654
  const missing = [];
5506
- if (!existsSync11(join22(resolvedPath, "_Templates"))) missing.push("_Templates/");
5507
- if (!existsSync11(join22(resolvedPath, ".obsidian", "templates.json"))) missing.push(".obsidian/templates.json");
5508
- if (!existsSync11(join22(resolvedPath, ".obsidian", "app.json"))) missing.push(".obsidian/app.json");
5655
+ if (!existsSync12(join23(resolvedPath, "_Templates"))) missing.push("_Templates/");
5656
+ if (!existsSync12(join23(resolvedPath, ".obsidian", "templates.json"))) missing.push(".obsidian/templates.json");
5657
+ if (!existsSync12(join23(resolvedPath, ".obsidian", "app.json"))) missing.push(".obsidian/app.json");
5509
5658
  if (missing.length === 0) {
5510
5659
  return check("pass", "obsidian_templates", "Obsidian templates", "Template folder and config present");
5511
5660
  }
@@ -5515,15 +5664,15 @@ function checkDotStoreClean(resolvedPath) {
5515
5664
  if (resolvedPath === void 0) {
5516
5665
  return check("error", "dsstore_clean", "No .DS_Store in raw/", "Cannot check \u2014 WIKI_PATH not resolved");
5517
5666
  }
5518
- const rawDir = join22(resolvedPath, "raw");
5519
- if (!existsSync11(rawDir)) {
5667
+ const rawDir = join23(resolvedPath, "raw");
5668
+ if (!existsSync12(rawDir)) {
5520
5669
  return check("pass", "dsstore_clean", "No .DS_Store in raw/", "raw/ directory not found \u2014 check skipped");
5521
5670
  }
5522
5671
  const found = [];
5523
5672
  (function walk2(dir, rel) {
5524
5673
  let entries;
5525
5674
  try {
5526
- entries = readdirSync2(dir, { withFileTypes: true });
5675
+ entries = readdirSync3(dir, { withFileTypes: true });
5527
5676
  } catch {
5528
5677
  return;
5529
5678
  }
@@ -5531,7 +5680,7 @@ function checkDotStoreClean(resolvedPath) {
5531
5680
  if (entry.name === ".DS_Store") {
5532
5681
  found.push(rel ? `${rel}/.DS_Store` : ".DS_Store");
5533
5682
  } else if (entry.isDirectory()) {
5534
- walk2(join22(dir, entry.name), rel ? `${rel}/${entry.name}` : entry.name);
5683
+ walk2(join23(dir, entry.name), rel ? `${rel}/${entry.name}` : entry.name);
5535
5684
  }
5536
5685
  }
5537
5686
  })(rawDir, "");
@@ -5540,11 +5689,29 @@ function checkDotStoreClean(resolvedPath) {
5540
5689
  }
5541
5690
  return check("info", "dsstore_clean", "No .DS_Store in raw/", `${found.length} .DS_Store file(s) found \u2014 remove with: find ${rawDir} -name .DS_Store -delete`);
5542
5691
  }
5692
+ function checkVaultConflictMarkers(resolvedPath) {
5693
+ if (resolvedPath === void 0) {
5694
+ return check("pass", "vault_conflict_markers", "Vault conflict markers", "No vault path \u2014 check skipped");
5695
+ }
5696
+ const findings = scanVaultConflictMarkers(resolvedPath);
5697
+ if (findings.length === 0) {
5698
+ return check("pass", "vault_conflict_markers", "Vault conflict markers", "No complete conflict-marker blocks");
5699
+ }
5700
+ const first = findings[0];
5701
+ const n = findings.length;
5702
+ const fileWord = n === 1 ? "file" : "files";
5703
+ return check(
5704
+ "error",
5705
+ "vault_conflict_markers",
5706
+ "Vault conflict markers",
5707
+ `${n} ${fileWord}, first: ${first.path}:${first.line}`
5708
+ );
5709
+ }
5543
5710
  function checkSyncLastPush(resolvedPath) {
5544
5711
  if (resolvedPath === void 0) {
5545
5712
  return check("error", "sync_last_push", "Vault sync recency", "Cannot check \u2014 WIKI_PATH not resolved");
5546
5713
  }
5547
- if (!existsSync11(join22(resolvedPath, ".git"))) {
5714
+ if (!existsSync12(join23(resolvedPath, ".git"))) {
5548
5715
  return check("pass", "sync_last_push", "Vault sync recency", "No git repo \u2014 sync check skipped");
5549
5716
  }
5550
5717
  let timestamp;
@@ -5592,7 +5759,7 @@ function checkVaultGitDirty(resolvedPath) {
5592
5759
  if (resolvedPath === void 0) {
5593
5760
  return check("pass", "vault_git_dirty", "Vault git dirty state", "No vault path \u2014 check skipped");
5594
5761
  }
5595
- if (!existsSync11(join22(resolvedPath, ".git"))) {
5762
+ if (!existsSync12(join23(resolvedPath, ".git"))) {
5596
5763
  return check("pass", "vault_git_dirty", "Vault git dirty state", "No git repo \u2014 check skipped");
5597
5764
  }
5598
5765
  try {
@@ -5660,7 +5827,7 @@ function remoteMainHash(resolvedPath) {
5660
5827
  }
5661
5828
  function checkStaleRemoteMain(resolvedPath) {
5662
5829
  if (resolvedPath === void 0) return void 0;
5663
- if (!existsSync11(join22(resolvedPath, ".git"))) return void 0;
5830
+ if (!existsSync12(join23(resolvedPath, ".git"))) return void 0;
5664
5831
  const localOrigin = gitRefHash(resolvedPath, "origin/main");
5665
5832
  if (!localOrigin) return void 0;
5666
5833
  const remoteMain = remoteMainHash(resolvedPath);
@@ -5672,11 +5839,101 @@ function checkStaleRemoteMain(resolvedPath) {
5672
5839
  `Remote main differs from local origin/main (${remoteMain.slice(0, 8)} != ${localOrigin.slice(0, 8)}) \u2014 run git fetch before trusting behind count`
5673
5840
  );
5674
5841
  }
5842
+ function checkVaultLocalGit(resolvedPath) {
5843
+ if (resolvedPath === void 0) {
5844
+ return check("warn", "vault_local_git", "Vault local git", "Cannot check \u2014 WIKI_PATH not resolved");
5845
+ }
5846
+ if (!existsSync12(join23(resolvedPath, ".git"))) {
5847
+ return check("warn", "vault_local_git", "Vault local git", "Not a git repository - sync features unavailable");
5848
+ }
5849
+ try {
5850
+ execSync2("git rev-parse --git-dir", {
5851
+ cwd: resolvedPath,
5852
+ encoding: "utf8",
5853
+ stdio: ["pipe", "pipe", "pipe"],
5854
+ timeout: 2e3
5855
+ });
5856
+ return check("pass", "vault_local_git", "Vault local git", "Git metadata readable");
5857
+ } catch {
5858
+ return check("error", "vault_local_git", "Vault local git", "Git metadata unreadable \u2014 local vault may be corrupt");
5859
+ }
5860
+ }
5861
+ function checkVaultGithubRemote(resolvedPath, exec) {
5862
+ if (resolvedPath === void 0) {
5863
+ return check("pass", "vault_github_remote", "Vault GitHub remote", "No vault path \u2014 check skipped");
5864
+ }
5865
+ if (!existsSync12(join23(resolvedPath, ".git"))) {
5866
+ return check("pass", "vault_github_remote", "Vault GitHub remote", "No git repo \u2014 check skipped");
5867
+ }
5868
+ const state = probeGithubReachability(resolvedPath, exec);
5869
+ if (state === "ok") {
5870
+ return check("pass", "vault_github_remote", "Vault GitHub remote", "git ls-remote origin main succeeded");
5871
+ }
5872
+ if (state === "unreachable") {
5873
+ return check("warn", "vault_github_remote", "Vault GitHub remote", "GitHub unreachable (ls-remote failed) \u2014 local vault still usable");
5874
+ }
5875
+ return check("pass", "vault_github_remote", "Vault GitHub remote", "No origin remote \u2014 network probe skipped");
5876
+ }
5877
+ function checkVaultS3Remote(home, exec) {
5878
+ const remote = readWikiS3RemoteConfigured(home);
5879
+ if (!remote) {
5880
+ return check("pass", "vault_s3_remote", "Vault S3 remote", "S3 remote not configured \u2014 check skipped");
5881
+ }
5882
+ const state = probeS3Reachability(remote, exec);
5883
+ if (state === "ok") {
5884
+ return check("pass", "vault_s3_remote", "Vault S3 remote", `rclone lsf ${remote} succeeded`);
5885
+ }
5886
+ if (state === "unreachable") {
5887
+ return check("warn", "vault_s3_remote", "Vault S3 remote", `S3 remote unreachable (${remote}) \u2014 local/GitHub work may continue`);
5888
+ }
5889
+ return check("pass", "vault_s3_remote", "Vault S3 remote", "S3 remote not configured \u2014 check skipped");
5890
+ }
5891
+ function checkVaultSnapshotterReachable(fleetLoad, checkSnapshotter, exec) {
5892
+ if (!checkSnapshotter) {
5893
+ return check("pass", "vault_snapshotter_reachable", "Vault snapshotter host", "Snapshotter SSH probe not requested \u2014 check skipped");
5894
+ }
5895
+ const alias = snapshotterAliasForLocalHost(fleetLoad);
5896
+ if (!alias) {
5897
+ return check("pass", "vault_snapshotter_reachable", "Vault snapshotter host", "No declared SSH alias from this host \u2014 check skipped");
5898
+ }
5899
+ const state = probeSnapshotterSsh(alias, exec);
5900
+ if (state === "ok") {
5901
+ return check("pass", "vault_snapshotter_reachable", "Vault snapshotter host", `SSH reachable via ${alias}`);
5902
+ }
5903
+ return check("warn", "vault_snapshotter_reachable", "Vault snapshotter host", `Snapshotter unreachable via ${alias} \u2014 not a local vault corruption signal`);
5904
+ }
5905
+ function checkVaultPromotionLag(resolvedPath) {
5906
+ if (resolvedPath === void 0) {
5907
+ return check("pass", "vault_promotion_lag", "Vault promotion lag", "No vault path \u2014 check skipped");
5908
+ }
5909
+ if (!existsSync12(join23(resolvedPath, ".git"))) {
5910
+ return check("pass", "vault_promotion_lag", "Vault promotion lag", "No git repo \u2014 check skipped");
5911
+ }
5912
+ try {
5913
+ const out = execSync2("git log -1 --format=%ct origin/main", {
5914
+ cwd: resolvedPath,
5915
+ encoding: "utf8",
5916
+ stdio: ["pipe", "pipe", "pipe"],
5917
+ timeout: 2e3
5918
+ }).trim();
5919
+ const ts = parseInt(out, 10);
5920
+ if (!Number.isFinite(ts) || ts <= 0) {
5921
+ return check("pass", "vault_promotion_lag", "Vault promotion lag", "origin/main timestamp unavailable \u2014 check skipped");
5922
+ }
5923
+ const ageHours = Math.floor((Date.now() / 1e3 - ts) / 3600);
5924
+ if (ageHours > 48) {
5925
+ return check("warn", "vault_promotion_lag", "Vault promotion lag", `Local origin/main snapshot is ${ageHours}h old \u2014 verify snapshotter/GitHub when online`);
5926
+ }
5927
+ return check("pass", "vault_promotion_lag", "Vault promotion lag", `origin/main age ${ageHours}h`);
5928
+ } catch {
5929
+ return check("pass", "vault_promotion_lag", "Vault promotion lag", "Could not read origin/main \u2014 check skipped");
5930
+ }
5931
+ }
5675
5932
  function checkVaultGitComparison(resolvedPath, id, label, range, nonZeroSuffix, zeroDetail) {
5676
5933
  if (resolvedPath === void 0) {
5677
5934
  return check("pass", id, label, "No vault path \u2014 check skipped");
5678
5935
  }
5679
- if (!existsSync11(join22(resolvedPath, ".git"))) {
5936
+ if (!existsSync12(join23(resolvedPath, ".git"))) {
5680
5937
  return check("pass", id, label, "No git repo \u2014 check skipped");
5681
5938
  }
5682
5939
  if (!hasOriginMain(resolvedPath)) {
@@ -5704,7 +5961,7 @@ function checkSatelliteLastRun(vaultPath, satelliteExpected) {
5704
5961
  return check("pass", "satellite_job_last_run", "Satellite job last run", "No vault path \u2014 check skipped");
5705
5962
  }
5706
5963
  const latestPath = satelliteLatestRunPath(vaultPath);
5707
- if (!existsSync11(latestPath)) {
5964
+ if (!existsSync12(latestPath)) {
5708
5965
  return check("pass", "satellite_job_last_run", "Satellite job last run", "No latest-run.json \u2014 satellite has not run yet");
5709
5966
  }
5710
5967
  try {
@@ -5792,11 +6049,11 @@ async function checkFleetIdentity(input) {
5792
6049
  }
5793
6050
  function pullLogPaths(home) {
5794
6051
  const paths = platform2() === "darwin" ? [
5795
- join22(home, "Library", "Logs", "wiki-pull.log"),
5796
- join22(home, ".local", "state", "vault-sync", "log", "wiki-pull.log")
6052
+ join23(home, "Library", "Logs", "wiki-pull.log"),
6053
+ join23(home, ".local", "state", "vault-sync", "log", "wiki-pull.log")
5797
6054
  ] : [
5798
- join22(home, ".local", "state", "vault-sync", "log", "wiki-pull.log"),
5799
- join22(home, "Library", "Logs", "wiki-pull.log")
6055
+ join23(home, ".local", "state", "vault-sync", "log", "wiki-pull.log"),
6056
+ join23(home, "Library", "Logs", "wiki-pull.log")
5800
6057
  ];
5801
6058
  return [...new Set(paths)];
5802
6059
  }
@@ -5808,12 +6065,12 @@ function isRecentLogLine(line, nowMs) {
5808
6065
  return nowMs - ts <= 24 * 60 * 60 * 1e3;
5809
6066
  }
5810
6067
  function checkVaultGitPullFailures(home) {
5811
- const path = pullLogPaths(home).find((p) => existsSync11(p));
6068
+ const path = pullLogPaths(home).find((p) => existsSync12(p));
5812
6069
  if (!path) {
5813
6070
  return check("pass", "vault_git_pull_failures", "Vault pull failures", "No wiki-pull.log found \u2014 check skipped");
5814
6071
  }
5815
6072
  try {
5816
- const lines = readFileSync8(path, "utf8").split(/\r?\n/).filter(Boolean);
6073
+ const lines = readFileSync9(path, "utf8").split(/\r?\n/).filter(Boolean);
5817
6074
  const now = Date.now();
5818
6075
  const failures = lines.filter(
5819
6076
  (line) => isRecentLogLine(line, now) && /(pre-push pull failed|FAIL .*pull|FAIL .*rebase|cannot pull with rebase|unstaged changes)/i.test(line)
@@ -5836,8 +6093,8 @@ function checkS3MountPerf(resolvedPath) {
5836
6093
  return check("pass", "s3_mount_perf", "S3 mount performance", "local disk");
5837
6094
  }
5838
6095
  const mountPoint = fuse.mountPoint;
5839
- const conceptsDir = join22(resolvedPath, "concepts");
5840
- if (!existsSync11(conceptsDir)) {
6096
+ const conceptsDir = join23(resolvedPath, "concepts");
6097
+ if (!existsSync12(conceptsDir)) {
5841
6098
  return check("pass", "s3_mount_perf", "S3 mount performance", `S3 FUSE mount (${mountPoint}), no concepts/ to benchmark`);
5842
6099
  }
5843
6100
  const start = Date.now();
@@ -6019,8 +6276,8 @@ function checkWriteTest(resolvedPath) {
6019
6276
  if (!fuse) {
6020
6277
  return check("pass", "s3_write_test", "S3 write test", "local disk \u2014 check skipped");
6021
6278
  }
6022
- const conceptsDir = join22(resolvedPath, "concepts");
6023
- if (!existsSync11(conceptsDir)) {
6279
+ const conceptsDir = join23(resolvedPath, "concepts");
6280
+ if (!existsSync12(conceptsDir)) {
6024
6281
  return check("pass", "s3_write_test", "S3 write test", "no concepts/ dir to test \u2014 check skipped");
6025
6282
  }
6026
6283
  const result = writeTest(conceptsDir);
@@ -6106,7 +6363,7 @@ function checkVfsCacheHealth(resolvedPath) {
6106
6363
  }
6107
6364
  function readVaultSyncConfig(home) {
6108
6365
  try {
6109
- const content = readFileSync8(join22(home, ".skillwiki", ".env"), "utf8");
6366
+ const content = readFileSync9(join23(home, ".skillwiki", ".env"), "utf8");
6110
6367
  let installed = false;
6111
6368
  let role;
6112
6369
  let serviceScope;
@@ -6135,7 +6392,7 @@ function readVaultSyncConfig(home) {
6135
6392
  }
6136
6393
  function readKeyFromEnvFile(path, keys) {
6137
6394
  try {
6138
- const content = readFileSync8(path, "utf8");
6395
+ const content = readFileSync9(path, "utf8");
6139
6396
  for (const line of content.split(/\r?\n/)) {
6140
6397
  const trimmed = line.trim();
6141
6398
  if (trimmed.length === 0 || trimmed.startsWith("#")) continue;
@@ -6157,7 +6414,7 @@ function resolveSnapshotGitWorktree(config) {
6157
6414
  if (fromProfile) return fromProfile;
6158
6415
  }
6159
6416
  const defaultPath = "/root/wiki-git";
6160
- return existsSync11(defaultPath) ? defaultPath : void 0;
6417
+ return existsSync12(defaultPath) ? defaultPath : void 0;
6161
6418
  }
6162
6419
  function vaultSyncChecks(input) {
6163
6420
  const os = input.os ?? platform2();
@@ -6174,16 +6431,16 @@ function vaultSyncChecks(input) {
6174
6431
  ];
6175
6432
  }
6176
6433
  const isMac = os === "darwin";
6177
- const logDir = input.logDir ?? (isMac ? join22(home, "Library", "Logs") : join22(home, ".local", "state", "vault-sync", "log"));
6178
- const shareDir = input.shareDir ?? (isMac ? join22(home, "Library", "Application Support", "vault-sync", "bin") : join22(home, ".local", "share", "vault-sync", "bin"));
6179
- const filterPath = input.filterPath ?? join22(home, ".config", "rclone", "wiki-push-filters.txt");
6180
- const packagedSnapshotPath = join22(shareDir, "wiki-snapshot.sh");
6434
+ const logDir = input.logDir ?? (isMac ? join23(home, "Library", "Logs") : join23(home, ".local", "state", "vault-sync", "log"));
6435
+ const shareDir = input.shareDir ?? (isMac ? join23(home, "Library", "Application Support", "vault-sync", "bin") : join23(home, ".local", "share", "vault-sync", "bin"));
6436
+ const filterPath = input.filterPath ?? join23(home, ".config", "rclone", "wiki-push-filters.txt");
6437
+ const packagedSnapshotPath = join23(shareDir, "wiki-snapshot.sh");
6181
6438
  const legacySnapshotPath = "/root/.hermes/scripts/wiki-snapshot-v3.sh";
6182
- const snapshotPath = input.snapshotScriptPath ?? (existsSync11(packagedSnapshotPath) ? packagedSnapshotPath : legacySnapshotPath);
6439
+ const snapshotPath = input.snapshotScriptPath ?? (existsSync12(packagedSnapshotPath) ? packagedSnapshotPath : legacySnapshotPath);
6183
6440
  function snapshotLastStatusCheck() {
6184
- const snapshotLog = join22(logDir, "wiki-snapshot.log");
6441
+ const snapshotLog = join23(logDir, "wiki-snapshot.log");
6185
6442
  try {
6186
- const logContent = readFileSync8(snapshotLog, "utf8");
6443
+ const logContent = readFileSync9(snapshotLog, "utf8");
6187
6444
  const lines = logContent.trim().split("\n").filter(Boolean);
6188
6445
  if (lines.length === 0) {
6189
6446
  return check(
@@ -6228,14 +6485,14 @@ function vaultSyncChecks(input) {
6228
6485
  }
6229
6486
  }
6230
6487
  if (input.vaultSyncRole === "snapshotter") {
6231
- const c12 = existsSync11(snapshotPath) ? check("pass", "vault_sync_installed", "Vault sync installed", `Found snapshot script: ${snapshotPath}`) : check("error", "vault_sync_installed", "Vault sync installed", `Snapshot script not found at ${snapshotPath}`);
6488
+ const c12 = existsSync12(snapshotPath) ? check("pass", "vault_sync_installed", "Vault sync installed", `Found snapshot script: ${snapshotPath}`) : check("error", "vault_sync_installed", "Vault sync installed", `Snapshot script not found at ${snapshotPath}`);
6232
6489
  const serviceScope = input.vaultSyncServiceScope ?? "user";
6233
- const userTimerPath = join22(home, ".config", "systemd", "user", "wiki-snapshot.timer");
6490
+ const userTimerPath = join23(home, ".config", "systemd", "user", "wiki-snapshot.timer");
6234
6491
  const systemTimerPath = "/etc/systemd/system/wiki-snapshot.timer";
6235
6492
  let c22;
6236
- if (serviceScope === "user" && existsSync11(userTimerPath)) {
6493
+ if (serviceScope === "user" && existsSync12(userTimerPath)) {
6237
6494
  c22 = check("pass", "vault_sync_jobs_enabled", "Vault sync jobs enabled", `Found: ${userTimerPath}`);
6238
- } else if (serviceScope === "system" && existsSync11(systemTimerPath)) {
6495
+ } else if (serviceScope === "system" && existsSync12(systemTimerPath)) {
6239
6496
  c22 = check("pass", "vault_sync_jobs_enabled", "Vault sync jobs enabled", `Found: ${systemTimerPath}`);
6240
6497
  } else if (os !== "linux") {
6241
6498
  c22 = check("warn", "vault_sync_jobs_enabled", "Vault sync jobs enabled", "Snapshotter scheduler is Linux-only and no wiki-snapshot.timer file was found");
@@ -6267,7 +6524,7 @@ function vaultSyncChecks(input) {
6267
6524
  );
6268
6525
  let c52;
6269
6526
  try {
6270
- if (!existsSync11(snapshotPath)) {
6527
+ if (!existsSync12(snapshotPath)) {
6271
6528
  c52 = check(
6272
6529
  "error",
6273
6530
  "vault_sync_snapshot_guard",
@@ -6275,7 +6532,7 @@ function vaultSyncChecks(input) {
6275
6532
  `Snapshot script not found at ${snapshotPath}`
6276
6533
  );
6277
6534
  } else {
6278
- const content = readFileSync8(snapshotPath, "utf8");
6535
+ const content = readFileSync9(snapshotPath, "utf8");
6279
6536
  if (!content.includes("--max-delete")) {
6280
6537
  c52 = check(
6281
6538
  "error",
@@ -6302,8 +6559,8 @@ function vaultSyncChecks(input) {
6302
6559
  }
6303
6560
  return [c12, c22, c32, cFetch2, c42, c52];
6304
6561
  }
6305
- const pushScriptPath = join22(shareDir, "wiki-push.sh");
6306
- const c1 = existsSync11(pushScriptPath) ? check("pass", "vault_sync_installed", "Vault sync installed", `Found: ${pushScriptPath}`) : check("error", "vault_sync_installed", "Vault sync installed", `Script not found at ${pushScriptPath} \u2014 run vault-sync-install`);
6562
+ const pushScriptPath = join23(shareDir, "wiki-push.sh");
6563
+ const c1 = existsSync12(pushScriptPath) ? check("pass", "vault_sync_installed", "Vault sync installed", `Found: ${pushScriptPath}`) : check("error", "vault_sync_installed", "Vault sync installed", `Script not found at ${pushScriptPath} \u2014 run vault-sync-install`);
6307
6564
  let c2;
6308
6565
  try {
6309
6566
  if (isMac) {
@@ -6354,10 +6611,10 @@ function vaultSyncChecks(input) {
6354
6611
  "Scheduler check failed \u2014 run vault-sync-install"
6355
6612
  );
6356
6613
  }
6357
- const logFile = join22(logDir, "wiki-push.log");
6614
+ const logFile = join23(logDir, "wiki-push.log");
6358
6615
  let c3;
6359
6616
  try {
6360
- const logContent = readFileSync8(logFile, "utf8");
6617
+ const logContent = readFileSync9(logFile, "utf8");
6361
6618
  const lines = logContent.trim().split("\n").filter(Boolean);
6362
6619
  if (lines.length === 0) {
6363
6620
  c3 = check(
@@ -6413,7 +6670,7 @@ function vaultSyncChecks(input) {
6413
6670
  }
6414
6671
  }
6415
6672
  } catch {
6416
- c3 = existsSync11(logDir) ? check(
6673
+ c3 = existsSync12(logDir) ? check(
6417
6674
  "warn",
6418
6675
  "vault_sync_last_push_age",
6419
6676
  "Vault sync last push recency",
@@ -6425,10 +6682,10 @@ function vaultSyncChecks(input) {
6425
6682
  `Log directory not found at ${logDir}`
6426
6683
  );
6427
6684
  }
6428
- const fetchLogFile = join22(logDir, "wiki-fetch.log");
6685
+ const fetchLogFile = join23(logDir, "wiki-fetch.log");
6429
6686
  let cFetch;
6430
6687
  try {
6431
- const logContent = readFileSync8(fetchLogFile, "utf8");
6688
+ const logContent = readFileSync9(fetchLogFile, "utf8");
6432
6689
  const lines = logContent.trim().split("\n").filter(Boolean);
6433
6690
  if (lines.length === 0) {
6434
6691
  cFetch = check(
@@ -6472,7 +6729,7 @@ function vaultSyncChecks(input) {
6472
6729
  }
6473
6730
  let c4;
6474
6731
  try {
6475
- if (!existsSync11(filterPath)) {
6732
+ if (!existsSync12(filterPath)) {
6476
6733
  c4 = check(
6477
6734
  "error",
6478
6735
  "vault_sync_filter_present",
@@ -6480,7 +6737,7 @@ function vaultSyncChecks(input) {
6480
6737
  `Filter file not found at ${filterPath}`
6481
6738
  );
6482
6739
  } else {
6483
- const content = readFileSync8(filterPath, "utf8");
6740
+ const content = readFileSync9(filterPath, "utf8");
6484
6741
  const requiredExcludes = [
6485
6742
  "remotely-save/data.json",
6486
6743
  ".skillwiki/sync.lock",
@@ -6523,7 +6780,7 @@ function vaultSyncChecks(input) {
6523
6780
  );
6524
6781
  } else {
6525
6782
  try {
6526
- if (!existsSync11(snapshotPath)) {
6783
+ if (!existsSync12(snapshotPath)) {
6527
6784
  c5 = check(
6528
6785
  "error",
6529
6786
  "vault_sync_snapshot_guard",
@@ -6531,7 +6788,7 @@ function vaultSyncChecks(input) {
6531
6788
  `Snapshot script not found at ${snapshotPath}`
6532
6789
  );
6533
6790
  } else {
6534
- const content = readFileSync8(snapshotPath, "utf8");
6791
+ const content = readFileSync9(snapshotPath, "utf8");
6535
6792
  if (!content.includes("--max-delete")) {
6536
6793
  c5 = check(
6537
6794
  "error",
@@ -6563,33 +6820,33 @@ function findSkillMd(dir) {
6563
6820
  const results = [];
6564
6821
  let entries;
6565
6822
  try {
6566
- entries = readdirSync2(dir, { withFileTypes: true });
6823
+ entries = readdirSync3(dir, { withFileTypes: true });
6567
6824
  } catch {
6568
6825
  return results;
6569
6826
  }
6570
6827
  for (const entry of entries) {
6571
6828
  if (entry.isFile() && entry.name === "SKILL.md") {
6572
- results.push(join22(dir, entry.name));
6829
+ results.push(join23(dir, entry.name));
6573
6830
  } else if (entry.isDirectory()) {
6574
- results.push(...findSkillMd(join22(dir, entry.name)));
6831
+ results.push(...findSkillMd(join23(dir, entry.name)));
6575
6832
  }
6576
6833
  }
6577
6834
  return results;
6578
6835
  }
6579
6836
  function findInstalledSkillMd(dir) {
6580
- const directSkills = findSkillNames(dir).map((name) => join22(dir, name, "SKILL.md"));
6837
+ const directSkills = findSkillNames(dir).map((name) => join23(dir, name, "SKILL.md"));
6581
6838
  return directSkills.length > 0 ? directSkills : findSkillMd(dir);
6582
6839
  }
6583
6840
  function findSkillNames(dir) {
6584
6841
  const results = [];
6585
6842
  let entries;
6586
6843
  try {
6587
- entries = readdirSync2(dir, { withFileTypes: true });
6844
+ entries = readdirSync3(dir, { withFileTypes: true });
6588
6845
  } catch {
6589
6846
  return results;
6590
6847
  }
6591
6848
  for (const entry of entries) {
6592
- if (entry.isDirectory() && existsSync11(join22(dir, entry.name, "SKILL.md"))) {
6849
+ if (entry.isDirectory() && existsSync12(join23(dir, entry.name, "SKILL.md"))) {
6593
6850
  results.push(entry.name);
6594
6851
  }
6595
6852
  }
@@ -6633,7 +6890,7 @@ async function vaultMetrics(resolvedPath) {
6633
6890
  }
6634
6891
  let logLines = 0;
6635
6892
  try {
6636
- logLines = readFileSync8(join22(resolvedPath, "log.md"), "utf8").split("\n").length;
6893
+ logLines = readFileSync9(join23(resolvedPath, "log.md"), "utf8").split("\n").length;
6637
6894
  } catch {
6638
6895
  }
6639
6896
  return [
@@ -6690,7 +6947,13 @@ async function runDoctor(input) {
6690
6947
  checks.push(checkVaultGitAhead(gitCheckPath));
6691
6948
  checks.push(checkVaultGitBehind(gitCheckPath));
6692
6949
  checks.push(checkVaultGitPullFailures(input.home));
6950
+ checks.push(checkVaultLocalGit(gitCheckPath));
6951
+ checks.push(checkVaultGithubRemote(gitCheckPath, input.execProbe));
6952
+ checks.push(checkVaultS3Remote(input.home, input.execProbe));
6953
+ checks.push(checkVaultSnapshotterReachable(fleetLoad, input.checkSnapshotter, input.execProbe));
6954
+ checks.push(checkVaultPromotionLag(gitCheckPath));
6693
6955
  checks.push(checkDotStoreClean(resolvedPath));
6956
+ checks.push(checkVaultConflictMarkers(resolvedPath));
6694
6957
  checks.push(checkS3MountPerf(resolvedPath));
6695
6958
  checks.push(checkS3MountFreshness(resolvedPath));
6696
6959
  checks.push(checkRcloneFlagAudit(resolvedPath));
@@ -6735,7 +6998,7 @@ async function runDoctor(input) {
6735
6998
  }
6736
6999
 
6737
7000
  // src/utils/package-info.ts
6738
- import { readFileSync as readFileSync9 } from "fs";
7001
+ import { readFileSync as readFileSync10 } from "fs";
6739
7002
  function packageJsonCandidateUrls(baseUrl = import.meta.url) {
6740
7003
  return [
6741
7004
  new URL("../package.json", baseUrl),
@@ -6745,7 +7008,7 @@ function packageJsonCandidateUrls(baseUrl = import.meta.url) {
6745
7008
  function readCliPackageJson(baseUrl = import.meta.url) {
6746
7009
  for (const url of packageJsonCandidateUrls(baseUrl)) {
6747
7010
  try {
6748
- const pkg = JSON.parse(readFileSync9(url, "utf8"));
7011
+ const pkg = JSON.parse(readFileSync10(url, "utf8"));
6749
7012
  if (typeof pkg.version === "string") {
6750
7013
  return { ...pkg, version: pkg.version };
6751
7014
  }
@@ -6757,7 +7020,7 @@ function readCliPackageJson(baseUrl = import.meta.url) {
6757
7020
 
6758
7021
  // src/commands/project-index.ts
6759
7022
  import { readdir as readdir4, readFile as readFile16, writeFile as writeFile6, mkdir as mkdir5 } from "fs/promises";
6760
- import { join as join23, dirname as dirname8, basename as basename2 } from "path";
7023
+ import { join as join24, dirname as dirname7, basename as basename2 } from "path";
6761
7024
  var LAYER2_DIRS = ["entities", "concepts", "comparisons", "queries", "meta"];
6762
7025
  var PROJECT_LOCAL_DIRS = ["requirements", "work", "architecture", "history"];
6763
7026
  async function scanMarkdownTree(rootAbs, rootRel) {
@@ -6769,7 +7032,7 @@ async function scanMarkdownTree(rootAbs, rootRel) {
6769
7032
  return found;
6770
7033
  }
6771
7034
  for (const entry of entries) {
6772
- const abs = join23(rootAbs, entry.name);
7035
+ const abs = join24(rootAbs, entry.name);
6773
7036
  const rel = `${rootRel}/${entry.name}`;
6774
7037
  if (entry.isDirectory()) {
6775
7038
  found.push(...await scanMarkdownTree(abs, rel));
@@ -6799,7 +7062,7 @@ function projectLocalType(slug, page, data) {
6799
7062
  }
6800
7063
  async function runProjectIndex(input) {
6801
7064
  const slug = input.slug;
6802
- const projectDir = join23(input.vault, "projects", slug);
7065
+ const projectDir = join24(input.vault, "projects", slug);
6803
7066
  try {
6804
7067
  await readdir4(projectDir);
6805
7068
  } catch {
@@ -6810,12 +7073,12 @@ async function runProjectIndex(input) {
6810
7073
  }
6811
7074
  const wikilinkPattern = `[[${slug}]]`;
6812
7075
  const entries = [];
6813
- const compoundDir = join23(input.vault, "projects", slug, "compound");
7076
+ const compoundDir = join24(input.vault, "projects", slug, "compound");
6814
7077
  try {
6815
7078
  const compoundFiles = await readdir4(compoundDir, { withFileTypes: true });
6816
7079
  for (const entry of compoundFiles) {
6817
7080
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
6818
- const filePath = join23(compoundDir, entry.name);
7081
+ const filePath = join24(compoundDir, entry.name);
6819
7082
  let text;
6820
7083
  try {
6821
7084
  text = await readFile16(filePath, "utf8");
@@ -6835,13 +7098,13 @@ async function runProjectIndex(input) {
6835
7098
  for (const dir of LAYER2_DIRS) {
6836
7099
  let files;
6837
7100
  try {
6838
- files = await readdir4(join23(input.vault, dir), { withFileTypes: true });
7101
+ files = await readdir4(join24(input.vault, dir), { withFileTypes: true });
6839
7102
  } catch {
6840
7103
  continue;
6841
7104
  }
6842
7105
  for (const entry of files) {
6843
7106
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
6844
- const filePath = join23(input.vault, dir, entry.name);
7107
+ const filePath = join24(input.vault, dir, entry.name);
6845
7108
  let text;
6846
7109
  try {
6847
7110
  text = await readFile16(filePath, "utf8");
@@ -6860,11 +7123,11 @@ async function runProjectIndex(input) {
6860
7123
  }
6861
7124
  }
6862
7125
  for (const dir of PROJECT_LOCAL_DIRS) {
6863
- const rootAbs = join23(projectDir, dir);
7126
+ const rootAbs = join24(projectDir, dir);
6864
7127
  const rootRel = `projects/${slug}/${dir}`;
6865
7128
  const pages = await scanMarkdownTree(rootAbs, rootRel);
6866
7129
  for (const page of pages) {
6867
- const filePath = join23(input.vault, page);
7130
+ const filePath = join24(input.vault, page);
6868
7131
  let text;
6869
7132
  try {
6870
7133
  text = await readFile16(filePath, "utf8");
@@ -6886,7 +7149,7 @@ async function runProjectIndex(input) {
6886
7149
  const tb = typeOrder[b.type] ?? 99;
6887
7150
  return ta !== tb ? ta - tb : a.title.localeCompare(b.title);
6888
7151
  });
6889
- const indexPath = join23(projectDir, "knowledge.md");
7152
+ const indexPath = join24(projectDir, "knowledge.md");
6890
7153
  let existing = false;
6891
7154
  let stale = false;
6892
7155
  try {
@@ -6930,7 +7193,7 @@ Autogenerated by \`skillwiki project-index\` on ${today}.
6930
7193
  }
6931
7194
  if (input.apply) {
6932
7195
  try {
6933
- await mkdir5(dirname8(indexPath), { recursive: true });
7196
+ await mkdir5(dirname7(indexPath), { recursive: true });
6934
7197
  await writeFile6(indexPath, body, "utf8");
6935
7198
  } catch (e) {
6936
7199
  return {
@@ -6960,8 +7223,8 @@ ${entries.map((e) => ` ${e.type}: [[${e.page.replace(/\.md$/, "")}]] \u2014 ${e
6960
7223
 
6961
7224
  // src/commands/observe.ts
6962
7225
  import { mkdir as mkdir6, writeFile as writeFile7 } from "fs/promises";
6963
- import { existsSync as existsSync12, statSync as statSync2 } from "fs";
6964
- import { join as join24 } from "path";
7226
+ import { existsSync as existsSync13, statSync as statSync2 } from "fs";
7227
+ import { join as join25 } from "path";
6965
7228
  import { createHash as createHash5 } from "crypto";
6966
7229
  var ALLOWED_KINDS = /* @__PURE__ */ new Set(["note", "bug", "task", "idea", "session-log"]);
6967
7230
  function slugify(text) {
@@ -6984,13 +7247,13 @@ async function runObserve(input) {
6984
7247
  result: err("SCHEME_REJECTED", { message: "Text must not be empty" })
6985
7248
  };
6986
7249
  }
6987
- if (!existsSync12(input.vault) || !statSync2(input.vault).isDirectory()) {
7250
+ if (!existsSync13(input.vault) || !statSync2(input.vault).isDirectory()) {
6988
7251
  return {
6989
7252
  exitCode: ExitCode.VAULT_PATH_INVALID,
6990
7253
  result: err("VAULT_PATH_INVALID", { path: input.vault })
6991
7254
  };
6992
7255
  }
6993
- const transcriptsDir = join24(input.vault, "raw", "transcripts");
7256
+ const transcriptsDir = join25(input.vault, "raw", "transcripts");
6994
7257
  try {
6995
7258
  await mkdir6(transcriptsDir, { recursive: true });
6996
7259
  } catch {
@@ -7002,7 +7265,7 @@ async function runObserve(input) {
7002
7265
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
7003
7266
  const slug = slugify(input.text);
7004
7267
  const fileName = `${today}-observation-${slug}.md`;
7005
- const filePath = join24(transcriptsDir, fileName);
7268
+ const filePath = join25(transcriptsDir, fileName);
7006
7269
  const body = `
7007
7270
  ${input.text.trim()}
7008
7271
  `;
@@ -7044,7 +7307,7 @@ ${input.text.trim()}
7044
7307
  // src/commands/memory.ts
7045
7308
  import { createHash as createHash6 } from "crypto";
7046
7309
  import { mkdir as mkdir7, readFile as readFile17, readdir as readdir5, stat as stat5, writeFile as writeFile8 } from "fs/promises";
7047
- import { basename as basename3, extname, join as join25, relative as relative4, sep as sep4 } from "path";
7310
+ import { basename as basename3, extname, join as join26, relative as relative4, sep as sep4 } from "path";
7048
7311
  async function runMemoryTopics(input) {
7049
7312
  const scan = await scanVault(input.vault);
7050
7313
  if (!scan.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scan };
@@ -7108,8 +7371,8 @@ async function runMemoryIndex(input) {
7108
7371
  }
7109
7372
  const generatedAt = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
7110
7373
  const relCachePath = memoryCacheRelPath(input.project);
7111
- const absCachePath = join25(input.vault, relCachePath);
7112
- await mkdir7(join25(input.vault, ".skillwiki", "memory", input.project), { recursive: true });
7374
+ const absCachePath = join26(input.vault, relCachePath);
7375
+ await mkdir7(join26(input.vault, ".skillwiki", "memory", input.project), { recursive: true });
7113
7376
  await writeFile8(absCachePath, `${JSON.stringify({
7114
7377
  generated_at: generatedAt,
7115
7378
  project: input.project,
@@ -7301,7 +7564,7 @@ async function buildMemoryIndexState(pages, project) {
7301
7564
  }
7302
7565
  async function checkMemoryIndex(vault, project, current) {
7303
7566
  const relCachePath = memoryCacheRelPath(project);
7304
- const cacheText = await readIfExists2(join25(vault, relCachePath));
7567
+ const cacheText = await readIfExists2(join26(vault, relCachePath));
7305
7568
  if (!cacheText) {
7306
7569
  return {
7307
7570
  ok: true,
@@ -7710,7 +7973,7 @@ async function walkImportFiles(dir, out) {
7710
7973
  const entries = await readdir5(dir, { withFileTypes: true });
7711
7974
  for (const entry of entries) {
7712
7975
  if (entry.name === ".git" || entry.name === "node_modules") continue;
7713
- const path = join25(dir, entry.name);
7976
+ const path = join26(dir, entry.name);
7714
7977
  if (entry.isDirectory()) {
7715
7978
  await walkImportFiles(path, out);
7716
7979
  } else if (entry.isFile() && isImportCandidate(path)) {
@@ -7777,8 +8040,8 @@ async function writeImportCapture(vault, entry, today) {
7777
8040
  const content = hiddenString(entry, "__content");
7778
8041
  const project = hiddenString(entry, "__project");
7779
8042
  const relPath = await availableImportPath(vault, entry.proposed_path);
7780
- const absPath = join25(vault, relPath);
7781
- await mkdir7(join25(vault, "raw", "transcripts"), { recursive: true });
8043
+ const absPath = join26(vault, relPath);
8044
+ await mkdir7(join26(vault, "raw", "transcripts"), { recursive: true });
7782
8045
  await writeFile8(absPath, renderImportCapture(entry, content, project, today), "utf8");
7783
8046
  const validation = await runValidate({ file: absPath });
7784
8047
  return {
@@ -7794,7 +8057,7 @@ async function availableImportPath(vault, proposed) {
7794
8057
  const stem = proposed.slice(0, -ext.length);
7795
8058
  let candidate = proposed;
7796
8059
  let i = 2;
7797
- while (await readIfExists2(join25(vault, candidate))) {
8060
+ while (await readIfExists2(join26(vault, candidate))) {
7798
8061
  candidate = `${stem}-${i}${ext}`;
7799
8062
  i++;
7800
8063
  }
@@ -7966,10 +8229,10 @@ function memoryCacheRelPath(project) {
7966
8229
  }
7967
8230
  async function readMemoryCache(vault, project) {
7968
8231
  if (project) {
7969
- const projectCache = await readIfExists2(join25(vault, memoryCacheRelPath(project)));
8232
+ const projectCache = await readIfExists2(join26(vault, memoryCacheRelPath(project)));
7970
8233
  if (projectCache) return projectCache;
7971
8234
  }
7972
- return readIfExists2(join25(vault, ".skillwiki", "memory-topics.json"));
8235
+ return readIfExists2(join26(vault, ".skillwiki", "memory-topics.json"));
7973
8236
  }
7974
8237
  function dedupePages(pages) {
7975
8238
  const seen = /* @__PURE__ */ new Set();
@@ -8056,7 +8319,7 @@ function slugify2(value) {
8056
8319
 
8057
8320
  // src/commands/query.ts
8058
8321
  import { readFile as readFile18, stat as stat6 } from "fs/promises";
8059
- import { join as join26 } from "path";
8322
+ import { join as join27 } from "path";
8060
8323
  var W_KEYWORD = 2;
8061
8324
  var W_SOURCE_OVERLAP = 4;
8062
8325
  var W_WIKILINK = 3;
@@ -8177,7 +8440,7 @@ function computeKeywordScore(terms, title, tags, body) {
8177
8440
  return score;
8178
8441
  }
8179
8442
  async function loadOrBuildGraph(vault) {
8180
- const graphPath = join26(vault, ".skillwiki", "graph.json");
8443
+ const graphPath = join27(vault, ".skillwiki", "graph.json");
8181
8444
  let needsBuild = false;
8182
8445
  try {
8183
8446
  const fileStat = await stat6(graphPath);
@@ -8206,7 +8469,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
8206
8469
  import { z as z2 } from "zod";
8207
8470
 
8208
8471
  // src/mcp/vault-resolve.ts
8209
- import { join as join27, resolve as resolve7 } from "path";
8472
+ import { join as join28, resolve as resolve7 } from "path";
8210
8473
 
8211
8474
  // src/mcp/allowlist.ts
8212
8475
  import { resolve as resolve6, sep as sep5 } from "path";
@@ -8268,7 +8531,7 @@ async function resolveMcpVault(input) {
8268
8531
  return ok({ vault: vaultPath, source });
8269
8532
  }
8270
8533
  function defaultGraphOut(vault) {
8271
- return join27(vault, ".skillwiki", "graph.json");
8534
+ return join28(vault, ".skillwiki", "graph.json");
8272
8535
  }
8273
8536
 
8274
8537
  // src/mcp/result-format.ts
@@ -8283,9 +8546,9 @@ function formatToolResult(payload) {
8283
8546
  }
8284
8547
 
8285
8548
  // src/mcp/audit-log.ts
8286
- import { appendFileSync, mkdirSync as mkdirSync4 } from "fs";
8549
+ import { appendFileSync, mkdirSync as mkdirSync3 } from "fs";
8287
8550
  import { homedir } from "os";
8288
- import { join as join28 } from "path";
8551
+ import { join as join29 } from "path";
8289
8552
  function auditEnabled() {
8290
8553
  const v = process.env.SKILLWIKI_MCP_AUDIT;
8291
8554
  if (v === "0" || v === "false") return false;
@@ -8297,7 +8560,7 @@ function auditSink() {
8297
8560
  function auditFilePath() {
8298
8561
  const custom = process.env.SKILLWIKI_MCP_AUDIT_FILE;
8299
8562
  if (custom && custom.length > 0) return custom;
8300
- return join28(homedir(), ".skillwiki", "mcp-audit.jsonl");
8563
+ return join29(homedir(), ".skillwiki", "mcp-audit.jsonl");
8301
8564
  }
8302
8565
  function auditMcpToolCall(entry) {
8303
8566
  if (!auditEnabled()) return;
@@ -8307,7 +8570,7 @@ function auditMcpToolCall(entry) {
8307
8570
  return;
8308
8571
  }
8309
8572
  const path = auditFilePath();
8310
- mkdirSync4(join28(path, ".."), { recursive: true });
8573
+ mkdirSync3(join29(path, ".."), { recursive: true });
8311
8574
  appendFileSync(path, line, "utf8");
8312
8575
  }
8313
8576
  async function runMcpToolHandler(tool, input, fn) {
@@ -8528,7 +8791,7 @@ function registerMcpMutatingTools(server) {
8528
8791
 
8529
8792
  // src/mcp/resources.ts
8530
8793
  import { readFile as readFile20 } from "fs/promises";
8531
- import { join as join30 } from "path";
8794
+ import { join as join31 } from "path";
8532
8795
  import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
8533
8796
 
8534
8797
  // src/mcp/lint-bucket.ts
@@ -8657,8 +8920,8 @@ async function fetchQueryPreview(input) {
8657
8920
 
8658
8921
  // src/mcp/graph-html.ts
8659
8922
  import { readFile as readFile19 } from "fs/promises";
8660
- import { join as join29 } from "path";
8661
- import { existsSync as existsSync13 } from "fs";
8923
+ import { join as join30 } from "path";
8924
+ import { existsSync as existsSync14 } from "fs";
8662
8925
  var TYPE_COLORS = {
8663
8926
  entities: "#e74c3c",
8664
8927
  concepts: "#27ae60",
@@ -8726,9 +8989,9 @@ ${nodeSvg}
8726
8989
  return { html, node_count: nodes.length, edge_count: edges.length, truncated };
8727
8990
  }
8728
8991
  async function fetchGraphHtmlReport(input) {
8729
- const graphPath = input.graphPath ?? join29(input.vault, ".skillwiki", "graph.json");
8992
+ const graphPath = input.graphPath ?? join30(input.vault, ".skillwiki", "graph.json");
8730
8993
  const maxNodes = Math.min(Math.max(10, input.maxNodes ?? 120), 500);
8731
- if (!existsSync13(graphPath)) {
8994
+ if (!existsSync14(graphPath)) {
8732
8995
  return {
8733
8996
  exitCode: ExitCode.FILE_NOT_FOUND,
8734
8997
  result: err("GRAPH_MISSING", { path: graphPath, hint: "Run skillwiki.graph_build first." })
@@ -8800,7 +9063,7 @@ async function fetchStaleSummary(input) {
8800
9063
 
8801
9064
  // src/mcp/resources.ts
8802
9065
  async function readVaultFile(vault, rel) {
8803
- return readFile20(join30(vault, rel), "utf8");
9066
+ return readFile20(join31(vault, rel), "utf8");
8804
9067
  }
8805
9068
  async function tailLines(text, lines) {
8806
9069
  const parts = text.split(/\r?\n/);
@@ -8886,7 +9149,7 @@ function registerMcpResources(server) {
8886
9149
  if (!v.ok) {
8887
9150
  return { contents: [{ uri: uri.href, mimeType: "text/plain", text: JSON.stringify(v) }] };
8888
9151
  }
8889
- const path = join30(v.data.vault, ".skillwiki", "graph.json");
9152
+ const path = join31(v.data.vault, ".skillwiki", "graph.json");
8890
9153
  try {
8891
9154
  const raw = await readFile20(path, "utf8");
8892
9155
  const graph = JSON.parse(raw);
@@ -9249,12 +9512,13 @@ export {
9249
9512
  runConfigSet,
9250
9513
  runConfigList,
9251
9514
  runConfigPath,
9252
- writeCache,
9253
- triggerAutoUpdate,
9515
+ buildDegradedReasons,
9516
+ probeRemoteHealth,
9254
9517
  FLEET_REL_PATH,
9255
9518
  runFleetValidate,
9256
9519
  runFleetContext,
9257
9520
  loadFleetManifestAndHost,
9521
+ snapshotterAliasForLocalHost,
9258
9522
  loadFleetManifest,
9259
9523
  resolveFleetHostId,
9260
9524
  SATELLITE_STALE_MS,