skillwiki 0.9.29 → 0.9.31

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.
@@ -62,7 +62,8 @@ var ExitCode = {
62
62
  SYNC_LOCK_HELD: 48,
63
63
  LOG_APPEND_LOCK_HELD: 49,
64
64
  FLEET_MANIFEST_INVALID: 50,
65
- SENSITIVE_CONTENT_DETECTED: 51
65
+ SENSITIVE_CONTENT_DETECTED: 51,
66
+ FLEET_SATELLITE_HEALTH_FAILED: 52
66
67
  };
67
68
 
68
69
  // ../shared/src/json-output.ts
@@ -1169,8 +1170,9 @@ var FRONTMATTER = /^---\n[\s\S]*?\n---\n?/;
1169
1170
  function stripFences(body) {
1170
1171
  return body.replace(FENCE2, "").replace(INLINE_CODE, "");
1171
1172
  }
1173
+ var TILDE_FENCE = /~~~[\s\S]*?~~~/g;
1172
1174
  function stripFencedBlocks(body) {
1173
- return body.replace(FENCE2, "");
1175
+ return body.replace(FENCE2, "").replace(TILDE_FENCE, "");
1174
1176
  }
1175
1177
  function extractCitationMarkers(body) {
1176
1178
  const stripped = stripFences(body);
@@ -2292,25 +2294,6 @@ async function defaultRcloneRunner(args) {
2292
2294
  }
2293
2295
  }
2294
2296
 
2295
- // src/commands/lint.ts
2296
- import { existsSync as existsSync4 } from "fs";
2297
- import { readFile as readFile12, readdir as readdir3 } from "fs/promises";
2298
- import { createHash as createHash4 } from "crypto";
2299
- import { join as join15, relative as relative3, sep as sep3 } from "path";
2300
-
2301
- // src/commands/sparse-community.ts
2302
- async function runSparseCommunity(input) {
2303
- const scan = await scanVault(input.vault);
2304
- if (!scan.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scan };
2305
- const adjacency = await buildWikilinkAdjacency(scan.data.typedKnowledge);
2306
- const communities = findSparseCommunities(adjacency, {
2307
- minSize: input.minSize,
2308
- maxCohesion: input.maxCohesion
2309
- });
2310
- const humanHint = communities.length === 0 ? "no sparse communities" : communities.map((c) => ` cohesion ${c.cohesion} (${c.size} pages): ${c.action}`).join("\n");
2311
- return { exitCode: ExitCode.OK, result: ok({ communities, humanHint }) };
2312
- }
2313
-
2314
2297
  // src/utils/safe-write.ts
2315
2298
  import { open, readFile as readFile10, rename as rename3, unlink, writeFile as writeFile5 } from "fs/promises";
2316
2299
  import { randomBytes } from "crypto";
@@ -2386,6 +2369,101 @@ async function safeWritePage(absPath, newContent, opts = {}) {
2386
2369
  }
2387
2370
  }
2388
2371
 
2372
+ // src/commands/frontmatter-fix.ts
2373
+ function isoToday() {
2374
+ return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
2375
+ }
2376
+ function fixFrontmatter(rawFm) {
2377
+ const additions = [];
2378
+ if (!/^created:/m.test(rawFm)) additions.push(`created: ${isoToday()}`);
2379
+ if (!/^updated:/m.test(rawFm)) additions.push(`updated: ${isoToday()}`);
2380
+ if (!/^tags:/m.test(rawFm)) additions.push("tags: []");
2381
+ if (!/^sources:/m.test(rawFm)) additions.push("sources: []");
2382
+ if (!/^provenance:/m.test(rawFm)) additions.push("provenance: research");
2383
+ if (additions.length === 0) return rawFm;
2384
+ return rawFm.trimEnd() + "\n" + additions.join("\n") + "\n";
2385
+ }
2386
+ function removeOrphanTagsLines(body) {
2387
+ return body.split("\n").filter((line) => !/^tags:\s*\[/.test(line.trim())).join("\n");
2388
+ }
2389
+ async function runFrontmatterFix(input) {
2390
+ const scan = await scanVault(input.vault);
2391
+ if (!scan.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scan };
2392
+ const fixed = [];
2393
+ const skipped = [];
2394
+ let unchanged = 0;
2395
+ for (const page of scan.data.typedKnowledge) {
2396
+ const text = await readPage(page);
2397
+ const split = splitFrontmatter(text);
2398
+ if (!split.ok) {
2399
+ skipped.push(page.relPath);
2400
+ continue;
2401
+ }
2402
+ const { rawFrontmatter, body } = split.data;
2403
+ const newFm = fixFrontmatter(rawFrontmatter);
2404
+ const newBody = removeOrphanTagsLines(body);
2405
+ const newText = `---
2406
+ ${newFm}
2407
+ ---
2408
+ ${newBody}`;
2409
+ if (newText === text) {
2410
+ unchanged++;
2411
+ continue;
2412
+ }
2413
+ if (!input.dryRun) {
2414
+ const w = await safeWritePage(page.absPath, newText);
2415
+ if (!w.ok) {
2416
+ skipped.push(page.relPath);
2417
+ continue;
2418
+ }
2419
+ }
2420
+ fixed.push(page.relPath);
2421
+ }
2422
+ const exitCode = fixed.length > 0 ? ExitCode.MIGRATION_APPLIED : ExitCode.OK;
2423
+ const hintLines = [`scanned: ${fixed.length + skipped.length + unchanged}`];
2424
+ if (fixed.length > 0) hintLines.push(`fixed: ${fixed.length}`);
2425
+ if (skipped.length > 0) hintLines.push(`skipped (parse error): ${skipped.length}`);
2426
+ if (unchanged > 0) hintLines.push(`unchanged: ${unchanged}`);
2427
+ if (input.dryRun && fixed.length > 0) hintLines.push("(dry run \u2014 no files written)");
2428
+ if (!input.dryRun && fixed.length > 0) {
2429
+ appendLastOp(input.vault, {
2430
+ operation: "frontmatter-fix",
2431
+ summary: `normalized frontmatter on ${fixed.length} page(s)`,
2432
+ files: fixed,
2433
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2434
+ });
2435
+ }
2436
+ return {
2437
+ exitCode,
2438
+ result: ok({
2439
+ scanned: fixed.length + skipped.length + unchanged,
2440
+ fixed,
2441
+ skipped,
2442
+ unchanged,
2443
+ humanHint: hintLines.join("\n")
2444
+ })
2445
+ };
2446
+ }
2447
+
2448
+ // src/commands/lint.ts
2449
+ import { existsSync as existsSync4 } from "fs";
2450
+ import { readFile as readFile12, readdir as readdir3 } from "fs/promises";
2451
+ import { createHash as createHash4 } from "crypto";
2452
+ import { join as join15, relative as relative3, sep as sep3 } from "path";
2453
+
2454
+ // src/commands/sparse-community.ts
2455
+ async function runSparseCommunity(input) {
2456
+ const scan = await scanVault(input.vault);
2457
+ if (!scan.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scan };
2458
+ const adjacency = await buildWikilinkAdjacency(scan.data.typedKnowledge);
2459
+ const communities = findSparseCommunities(adjacency, {
2460
+ minSize: input.minSize,
2461
+ maxCohesion: input.maxCohesion
2462
+ });
2463
+ const humanHint = communities.length === 0 ? "no sparse communities" : communities.map((c) => ` cohesion ${c.cohesion} (${c.size} pages): ${c.action}`).join("\n");
2464
+ return { exitCode: ExitCode.OK, result: ok({ communities, humanHint }) };
2465
+ }
2466
+
2389
2467
  // src/commands/raw-body-dedup.ts
2390
2468
  import { createHash as createHash3 } from "crypto";
2391
2469
  async function runRawBodyDedup(vault) {
@@ -2684,6 +2762,7 @@ function buildCliSurface() {
2684
2762
  const fleetCmd = program.commands.find((c) => c.name() === "fleet");
2685
2763
  fleetCmd.command("validate");
2686
2764
  fleetCmd.command("context").option("--file <path>").option("--host-id <id>");
2765
+ fleetCmd.command("health").option("--file <path>").option("--host-id <id>").option("--json");
2687
2766
  const surface = /* @__PURE__ */ new Map();
2688
2767
  const rootFlags = new Set(program.options.map((o) => o.long ?? o.short).filter((f) => f != null));
2689
2768
  function walk2(cmd, prefix, parentFlags) {
@@ -2844,6 +2923,23 @@ function hasDuplicateFrontmatter(body) {
2844
2923
  }
2845
2924
  return false;
2846
2925
  }
2926
+ var CANONICAL_LOCAL_SOURCE_LABEL = /^\s*(?:>\s*)?(?:[-*+]\s*)?Source (?:file|inspected):/i;
2927
+ var LOCAL_ABSOLUTE_SOURCE_REF = /(?:file:\/\/(?:\/)?(?:Users|home)\/|\/(?:Users|home)\/)/;
2928
+ function hasCanonicalLocalSourceAssertion(body) {
2929
+ const visibleBody = stripFencedBlocks(body);
2930
+ return visibleBody.split(/\r?\n/).some(
2931
+ (line) => CANONICAL_LOCAL_SOURCE_LABEL.test(line) && LOCAL_ABSOLUTE_SOURCE_REF.test(line)
2932
+ );
2933
+ }
2934
+ function shouldCheckCanonicalLocalSourceAssertion(page) {
2935
+ if (page.relPath.startsWith("raw/transcripts/")) return false;
2936
+ if (/^projects\/[^/]+\/work\/[^/]+\/log\.md$/.test(page.relPath)) return false;
2937
+ if (page.relPath.startsWith("raw/")) return true;
2938
+ if (/^(entities|concepts|comparisons|queries|meta)\//.test(page.relPath)) return true;
2939
+ if (/^projects\/[^/]+\/compound\//.test(page.relPath)) return true;
2940
+ if (/^projects\/[^/]+\/work\/[^/]+\/(spec|plan)\.md$/.test(page.relPath)) return true;
2941
+ return false;
2942
+ }
2847
2943
  function extractSourceEntries(rawFm) {
2848
2944
  const lines = rawFm.split(/\r?\n/);
2849
2945
  const sourcesLineIdx = lines.findIndex((l) => /^sources:/.test(l));
@@ -2862,7 +2958,7 @@ function extractSourceEntries(rawFm) {
2862
2958
  return entries;
2863
2959
  }
2864
2960
  var ERROR_ORDER = ["sensitive_content", "broken_wikilinks", "invalid_frontmatter", "raw_source_identity_conflict", "raw_dedup", "broken_sources", "tag_not_in_taxonomy", "path_too_long"];
2865
- 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", "work_item_health", "orphaned_project_pages", "missing_overview", "missing_diagram"];
2961
+ 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"];
2866
2962
  var INFO_ORDER = ["bridges", "sparse_community", "page_structure", "topic_map_recommended", "frontmatter_wikilink", "wikilink_citation", "missing_tldr", "stale_sections", "cli_refs"];
2867
2963
  var KNOWN_BUCKETS = [...ERROR_ORDER, ...WARNING_ORDER, ...INFO_ORDER];
2868
2964
  var CLI_REFS_TYPED_DIRS = ["entities", "concepts", "comparisons", "queries", "meta"];
@@ -3082,6 +3178,20 @@ async function runLint(input) {
3082
3178
  }
3083
3179
  }
3084
3180
  if (sensitiveFlags.length > 0) buckets.sensitive_content = sensitiveFlags;
3181
+ const fmYamlInvalid = [];
3182
+ for (const page of scan.data.allMarkdown) {
3183
+ try {
3184
+ const text = await readPage(page);
3185
+ const fm = extractFrontmatter(text);
3186
+ if (!fm.ok && fm.error === "INVALID_FRONTMATTER") {
3187
+ const detail = fm.detail;
3188
+ const message = detail?.message ?? "invalid YAML";
3189
+ fmYamlInvalid.push({ path: page.relPath, message });
3190
+ }
3191
+ } catch {
3192
+ }
3193
+ }
3194
+ if (fmYamlInvalid.length > 0) buckets.frontmatter_yaml_invalid = fmYamlInvalid;
3085
3195
  const subDirDupes = [];
3086
3196
  const flatStems = /* @__PURE__ */ new Map();
3087
3197
  const deepFiles = [];
@@ -3104,14 +3214,18 @@ async function runLint(input) {
3104
3214
  if (subDirDupes.length > 0) {
3105
3215
  buckets.raw_subdirectory_duplicate = subDirDupes;
3106
3216
  }
3107
- const fileSourceUrlFlags = [];
3217
+ const fileSourceUrlFlags = /* @__PURE__ */ new Set();
3218
+ const fileSourceUrlFrontmatterFlags = /* @__PURE__ */ new Set();
3108
3219
  const rawIdentityConflicts = [];
3220
+ const rawPageBodyByPath = /* @__PURE__ */ new Map();
3109
3221
  for (const raw of scan.data.raw) {
3110
3222
  const text = await readPage(raw);
3111
3223
  const split = splitFrontmatter(text);
3112
3224
  if (!split.ok) continue;
3225
+ rawPageBodyByPath.set(raw.relPath, split.data.body);
3113
3226
  if (/^source_url:\s*file:\/\//m.test(split.data.rawFrontmatter)) {
3114
- fileSourceUrlFlags.push(raw.relPath);
3227
+ fileSourceUrlFlags.add(raw.relPath);
3228
+ fileSourceUrlFrontmatterFlags.add(raw.relPath);
3115
3229
  }
3116
3230
  const sourceUrl = split.data.rawFrontmatter.match(/^source_url:\s*(.+)$/m)?.[1]?.trim().replace(/^["']|["']$/g, "") ?? "";
3117
3231
  const assessment = assessSourceIdentity({
@@ -3130,7 +3244,26 @@ async function runLint(input) {
3130
3244
  });
3131
3245
  }
3132
3246
  }
3133
- if (fileSourceUrlFlags.length > 0) buckets.file_source_url = fileSourceUrlFlags;
3247
+ const canonicalSourcePages = [
3248
+ ...scan.data.raw,
3249
+ ...scan.data.typedKnowledge,
3250
+ ...scan.data.compound,
3251
+ ...scan.data.workItems
3252
+ ];
3253
+ for (const page of canonicalSourcePages) {
3254
+ if (!shouldCheckCanonicalLocalSourceAssertion(page)) continue;
3255
+ let body = rawPageBodyByPath.get(page.relPath);
3256
+ if (body === void 0) {
3257
+ const text = await readPage(page);
3258
+ const split = splitFrontmatter(text);
3259
+ if (!split.ok) continue;
3260
+ body = split.data.body;
3261
+ }
3262
+ if (hasCanonicalLocalSourceAssertion(body)) {
3263
+ fileSourceUrlFlags.add(page.relPath);
3264
+ }
3265
+ }
3266
+ if (fileSourceUrlFlags.size > 0) buckets.file_source_url = [...fileSourceUrlFlags];
3134
3267
  if (rawIdentityConflicts.length > 0) buckets.raw_source_identity_conflict = rawIdentityConflicts;
3135
3268
  const legacyPages = [];
3136
3269
  const orphanedPages = [];
@@ -3605,9 +3738,9 @@ ${newBody}`;
3605
3738
  else delete buckets.wikilink_citation;
3606
3739
  }
3607
3740
  }
3608
- if (shouldFix("file_source_url") && fileSourceUrlFlags.length > 0) {
3741
+ if (shouldFix("file_source_url") && fileSourceUrlFrontmatterFlags.size > 0) {
3609
3742
  const FILE_FIXED = [];
3610
- for (const relPath of fileSourceUrlFlags) {
3743
+ for (const relPath of fileSourceUrlFrontmatterFlags) {
3611
3744
  try {
3612
3745
  const absPath = `${input.vault}/${relPath}`;
3613
3746
  const raw = await readFile12(absPath, "utf8");
@@ -3638,9 +3771,26 @@ ${newBody}`;
3638
3771
  }
3639
3772
  fixed.push(...FILE_FIXED);
3640
3773
  if (FILE_FIXED.length > 0) {
3641
- const fixedSet = new Set(FILE_FIXED);
3642
- const remaining = fileSourceUrlFlags.filter((p) => !fixedSet.has(p));
3643
- if (remaining.length > 0) buckets.file_source_url = remaining;
3774
+ const remaining = new Set(fileSourceUrlFlags);
3775
+ for (const relPath of FILE_FIXED) {
3776
+ try {
3777
+ const page = scan.data.allMarkdown.find((p) => p.relPath === relPath);
3778
+ if (!page) {
3779
+ remaining.delete(relPath);
3780
+ continue;
3781
+ }
3782
+ const text = await readPage(page);
3783
+ const split = splitFrontmatter(text);
3784
+ if (!split.ok) continue;
3785
+ const stillHasFileSourceUrl = /^source_url:\s*file:\/\//m.test(split.data.rawFrontmatter);
3786
+ const stillHasCanonicalBodyAssertion = shouldCheckCanonicalLocalSourceAssertion(page) && hasCanonicalLocalSourceAssertion(split.data.body);
3787
+ if (!stillHasFileSourceUrl && !stillHasCanonicalBodyAssertion) {
3788
+ remaining.delete(relPath);
3789
+ }
3790
+ } catch {
3791
+ }
3792
+ }
3793
+ if (remaining.size > 0) buckets.file_source_url = [...remaining];
3644
3794
  else delete buckets.file_source_url;
3645
3795
  }
3646
3796
  }
@@ -3658,6 +3808,50 @@ ${newBody}`;
3658
3808
  else delete buckets.path_too_long;
3659
3809
  }
3660
3810
  }
3811
+ if (shouldFix("frontmatter_yaml_invalid") && buckets.frontmatter_yaml_invalid) {
3812
+ const invalidItems = buckets.frontmatter_yaml_invalid;
3813
+ const remaining = [];
3814
+ for (const item of invalidItems) {
3815
+ const page = scan.data.allMarkdown.find((p) => p.relPath === item.path);
3816
+ if (!page) {
3817
+ unresolved.push(item.path);
3818
+ remaining.push(item);
3819
+ continue;
3820
+ }
3821
+ try {
3822
+ const text = await readPage(page);
3823
+ const split = splitFrontmatter(text);
3824
+ if (!split.ok) {
3825
+ unresolved.push(item.path);
3826
+ remaining.push(item);
3827
+ continue;
3828
+ }
3829
+ const newFm = fixFrontmatter(split.data.rawFrontmatter);
3830
+ const newText = `---
3831
+ ${newFm}
3832
+ ---
3833
+ ${split.data.body}`;
3834
+ const recheck = extractFrontmatter(newText);
3835
+ if (!recheck.ok) {
3836
+ unresolved.push(item.path);
3837
+ remaining.push(item);
3838
+ continue;
3839
+ }
3840
+ const w = await safeWritePage(page.absPath, newText, { minBodyRatio: null });
3841
+ if (!w.ok) {
3842
+ unresolved.push(item.path);
3843
+ remaining.push(item);
3844
+ continue;
3845
+ }
3846
+ fixed.push(item.path);
3847
+ } catch {
3848
+ unresolved.push(item.path);
3849
+ remaining.push(item);
3850
+ }
3851
+ }
3852
+ if (remaining.length > 0) buckets.frontmatter_yaml_invalid = remaining;
3853
+ else delete buckets.frontmatter_yaml_invalid;
3854
+ }
3661
3855
  }
3662
3856
  const errorOut = ERROR_ORDER.flatMap((k) => buckets[k] ? [{ kind: k, items: buckets[k] }] : []);
3663
3857
  const warningOut = WARNING_ORDER.flatMap((k) => buckets[k] ? [{ kind: k, items: buckets[k] }] : []);
@@ -3905,7 +4099,7 @@ async function runFleetContext(input) {
3905
4099
  })
3906
4100
  };
3907
4101
  }
3908
- const resolved = await resolveHostId({
4102
+ const resolved = await resolveFleetHostId({
3909
4103
  manifest: loaded.manifest,
3910
4104
  hostId: input.hostId,
3911
4105
  env,
@@ -3992,6 +4186,59 @@ async function runFleetContext(input) {
3992
4186
  })
3993
4187
  };
3994
4188
  }
4189
+ function fleetContextEnv(input) {
4190
+ const env = input.env ?? process.env;
4191
+ const home = input.home ?? env.HOME ?? "";
4192
+ const osHostname = input.osHostname ?? safeEnvValue(env.HOSTNAME) ?? nodeHostname();
4193
+ const vault = input.vault ?? safeEnvValue(env.WIKI_PATH);
4194
+ const file = input.file ?? (vault ? join18(vault, FLEET_REL_PATH) : void 0);
4195
+ return { env, home, osHostname, vault, file };
4196
+ }
4197
+ async function loadFleetManifestAndHost(input) {
4198
+ const { env, home, osHostname, file } = fleetContextEnv(input);
4199
+ if (!file) return null;
4200
+ const loaded = await loadFleetManifest(file);
4201
+ if (!loaded.ok) return null;
4202
+ const resolved = await resolveFleetHostId({
4203
+ manifest: loaded.manifest,
4204
+ hostId: input.hostId,
4205
+ env,
4206
+ home,
4207
+ osHostname
4208
+ });
4209
+ if (!resolved.hostId) {
4210
+ return {
4211
+ manifest: loaded.manifest,
4212
+ hostId: void 0,
4213
+ source: resolved.source,
4214
+ warnings: ["host identity is unresolved"],
4215
+ identityStatus: "unknown"
4216
+ };
4217
+ }
4218
+ if (!loaded.manifest.hosts[resolved.hostId]) {
4219
+ const source = resolved.source ?? "unknown";
4220
+ return {
4221
+ manifest: loaded.manifest,
4222
+ hostId: resolved.hostId,
4223
+ source: resolved.source,
4224
+ warnings: [`resolved host id \`${resolved.hostId}\` from ${source} is not in fleet.yaml`],
4225
+ identityStatus: "invalid"
4226
+ };
4227
+ }
4228
+ return {
4229
+ manifest: loaded.manifest,
4230
+ hostId: resolved.hostId,
4231
+ source: resolved.source,
4232
+ warnings: [],
4233
+ identityStatus: "known"
4234
+ };
4235
+ }
4236
+ function satelliteGateFromFleetLoad(load) {
4237
+ if (!load?.hostId) return { satelliteExpected: false };
4238
+ const host = load.manifest.hosts[load.hostId];
4239
+ if (!host) return { satelliteExpected: false };
4240
+ return { satelliteExpected: host.maintenance?.skillwiki_satellite?.enabled === true };
4241
+ }
3995
4242
  async function loadFleetManifest(file) {
3996
4243
  let text;
3997
4244
  try {
@@ -4051,7 +4298,7 @@ function fleetWarnings(manifest) {
4051
4298
  function findSnapshotter(manifest) {
4052
4299
  return Object.entries(manifest.hosts).find(([, host]) => host.role === "snapshotter")?.[0];
4053
4300
  }
4054
- async function resolveHostId(input) {
4301
+ async function resolveFleetHostId(input) {
4055
4302
  const trace = [];
4056
4303
  if (input.hostId) {
4057
4304
  trace.push({ source: "--host-id", status: "matched", value: input.hostId });
@@ -4242,8 +4489,8 @@ function safeUserName() {
4242
4489
  }
4243
4490
 
4244
4491
  // src/commands/doctor.ts
4245
- import { existsSync as existsSync9, lstatSync, readlinkSync, readdirSync as readdirSync2, statSync, readFileSync as readFileSync6 } from "fs";
4246
- import { join as join21, resolve as resolve5 } from "path";
4492
+ import { existsSync as existsSync10, lstatSync, readlinkSync, readdirSync as readdirSync2, statSync, readFileSync as readFileSync7 } from "fs";
4493
+ import { join as join22, resolve as resolve5 } from "path";
4247
4494
  import { execSync as execSync2 } from "child_process";
4248
4495
  import { platform as platform2 } from "os";
4249
4496
 
@@ -4365,11 +4612,66 @@ function parseTomlScalar(rawValue) {
4365
4612
  return value;
4366
4613
  }
4367
4614
 
4615
+ // src/utils/satellite-run-health.ts
4616
+ import { existsSync as existsSync8, readFileSync as readFileSync5 } from "fs";
4617
+ import { join as join20 } from "path";
4618
+ var SATELLITE_STALE_MS = 26 * 60 * 60 * 1e3;
4619
+ function satelliteLatestRunPath(vault) {
4620
+ return join20(vault, ".skillwiki", "agent-memory-trends", "latest-run.json");
4621
+ }
4622
+ function isFailedRunStatus(status) {
4623
+ return status === "fail" || status === "failure";
4624
+ }
4625
+ function parseLatestRunFile(text) {
4626
+ try {
4627
+ const parsed = JSON.parse(text);
4628
+ const status = typeof parsed.status === "string" ? parsed.status : "";
4629
+ if (!status) return null;
4630
+ const finishedAt = typeof parsed.finished_at === "string" && parsed.finished_at.length > 0 ? parsed.finished_at : void 0;
4631
+ const failureClass = parsed.failure_class != null && String(parsed.failure_class).length > 0 ? String(parsed.failure_class) : void 0;
4632
+ return { status, finishedAt, failureClass };
4633
+ } catch {
4634
+ return null;
4635
+ }
4636
+ }
4637
+ function readSatelliteLatestRunFromText(text) {
4638
+ return parseLatestRunFile(text);
4639
+ }
4640
+ function readSatelliteLatestRun(vault) {
4641
+ const latestPath = satelliteLatestRunPath(vault);
4642
+ if (!existsSync8(latestPath)) return null;
4643
+ try {
4644
+ return parseLatestRunFile(readFileSync5(latestPath, "utf8"));
4645
+ } catch {
4646
+ return null;
4647
+ }
4648
+ }
4649
+ function evaluateSatelliteRunHealth(vault, now) {
4650
+ const run = readSatelliteLatestRun(vault);
4651
+ if (!run) {
4652
+ return { failed: false, stale: false };
4653
+ }
4654
+ const failed = isFailedRunStatus(run.status);
4655
+ let stale = false;
4656
+ if (!failed && run.finishedAt) {
4657
+ const ts = Date.parse(run.finishedAt);
4658
+ if (Number.isFinite(ts) && now.getTime() - ts > SATELLITE_STALE_MS) {
4659
+ stale = true;
4660
+ }
4661
+ }
4662
+ return {
4663
+ failed,
4664
+ stale,
4665
+ failureClass: run.failureClass,
4666
+ finishedAt: run.finishedAt
4667
+ };
4668
+ }
4669
+
4368
4670
  // src/utils/s3-mount-health.ts
4369
4671
  import { execSync } from "child_process";
4370
4672
  import { platform } from "os";
4371
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, unlinkSync as unlinkSync3, readFileSync as readFile15 } from "fs";
4372
- import { join as join20 } from "path";
4673
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, unlinkSync as unlinkSync3, readFileSync as readFile15 } from "fs";
4674
+ import { join as join21 } from "path";
4373
4675
  var OS = platform();
4374
4676
  function findRcloneMountPid() {
4375
4677
  try {
@@ -4453,7 +4755,7 @@ function extractRcloneFs(args) {
4453
4755
  function getRcloneArgs(pid) {
4454
4756
  try {
4455
4757
  if (OS === "linux") {
4456
- const raw = readFileSync5(`/proc/${pid}/cmdline`);
4758
+ const raw = readFileSync6(`/proc/${pid}/cmdline`);
4457
4759
  return new TextDecoder().decode(raw).split("\0").filter(Boolean);
4458
4760
  } else {
4459
4761
  const out = execSync(`ps -o args= -p ${pid}`, {
@@ -4496,7 +4798,7 @@ function queryRcloneRC(rcAddr, fs) {
4496
4798
  function detectFuseMount(vaultPath) {
4497
4799
  try {
4498
4800
  if (OS === "linux") {
4499
- const mounts = readFileSync5("/proc/mounts", "utf8");
4801
+ const mounts = readFileSync6("/proc/mounts", "utf8");
4500
4802
  let best = null;
4501
4803
  for (const line of mounts.split("\n")) {
4502
4804
  const parts = line.split(" ");
@@ -4527,7 +4829,7 @@ function detectFuseMount(vaultPath) {
4527
4829
  return null;
4528
4830
  }
4529
4831
  function writeTest(dir) {
4530
- const testFile = join20(dir, `.doctor-write-test-${process.pid}.tmp`);
4832
+ const testFile = join21(dir, `.doctor-write-test-${process.pid}.tmp`);
4531
4833
  const payload = `skillwiki doctor write test \u2014 ${Date.now()} \u2014 ${Math.random().toString(36).slice(2)}`;
4532
4834
  const start = Date.now();
4533
4835
  try {
@@ -4627,13 +4929,13 @@ function detectCliChannels(argv, home) {
4627
4929
  }
4628
4930
  const plugin = findPlugin(home);
4629
4931
  if (plugin) {
4630
- const pluginBin = join21(plugin.installPath, "bin", "skillwiki");
4631
- if (existsSync9(pluginBin)) {
4932
+ const pluginBin = join22(plugin.installPath, "bin", "skillwiki");
4933
+ if (existsSync10(pluginBin)) {
4632
4934
  channels.push({ name: "plugin", path: pluginBin, isDevLink: false });
4633
4935
  }
4634
4936
  }
4635
- const installBin = join21(home, ".claude", "skills", "bin", "skillwiki");
4636
- if (existsSync9(installBin)) {
4937
+ const installBin = join22(home, ".claude", "skills", "bin", "skillwiki");
4938
+ if (existsSync10(installBin)) {
4637
4939
  channels.push({ name: "install", path: installBin, isDevLink: false });
4638
4940
  }
4639
4941
  return channels;
@@ -4694,7 +4996,7 @@ function isDevSourceRun(argv) {
4694
4996
  }
4695
4997
  async function checkConfigFile(home) {
4696
4998
  const cfgPath = configPath(home);
4697
- if (!existsSync9(cfgPath)) {
4999
+ if (!existsSync10(cfgPath)) {
4698
5000
  return check("warn", "config_file", "Config file exists", `${cfgPath} not found`);
4699
5001
  }
4700
5002
  try {
@@ -4709,7 +5011,7 @@ function checkWikiPathExists(resolvedPath) {
4709
5011
  if (resolvedPath === void 0) {
4710
5012
  return check("error", "wiki_path_exists", "Vault directory exists", "Cannot check \u2014 WIKI_PATH not resolved");
4711
5013
  }
4712
- if (existsSync9(resolvedPath) && statSync(resolvedPath).isDirectory()) {
5014
+ if (existsSync10(resolvedPath) && statSync(resolvedPath).isDirectory()) {
4713
5015
  return check("pass", "wiki_path_exists", "Vault directory exists", resolvedPath);
4714
5016
  }
4715
5017
  return check("error", "wiki_path_exists", "Vault directory exists", `${resolvedPath} does not exist or is not a directory`);
@@ -4718,13 +5020,13 @@ function checkVaultStructure(resolvedPath) {
4718
5020
  if (resolvedPath === void 0) {
4719
5021
  return check("error", "vault_structure", "Vault structure valid", "Cannot check \u2014 WIKI_PATH not resolved");
4720
5022
  }
4721
- if (!existsSync9(resolvedPath)) {
5023
+ if (!existsSync10(resolvedPath)) {
4722
5024
  return check("error", "vault_structure", "Vault structure valid", "Cannot check \u2014 vault directory does not exist");
4723
5025
  }
4724
5026
  const missing = [];
4725
- if (!existsSync9(join21(resolvedPath, "SCHEMA.md"))) missing.push("SCHEMA.md");
5027
+ if (!existsSync10(join22(resolvedPath, "SCHEMA.md"))) missing.push("SCHEMA.md");
4726
5028
  for (const dir of ["raw", "entities", "concepts", "meta"]) {
4727
- if (!existsSync9(join21(resolvedPath, dir))) missing.push(dir + "/");
5029
+ if (!existsSync10(join22(resolvedPath, dir))) missing.push(dir + "/");
4728
5030
  }
4729
5031
  if (missing.length === 0) {
4730
5032
  return check("pass", "vault_structure", "Vault structure valid", "All required files and directories present");
@@ -4732,8 +5034,8 @@ function checkVaultStructure(resolvedPath) {
4732
5034
  return check("warn", "vault_structure", "Vault structure valid", `Missing: ${missing.join(", ")} \u2014 run \`skillwiki init\` to add CodeWiki structure`);
4733
5035
  }
4734
5036
  function checkSkillsInstalled(home, cwd) {
4735
- const srcDir = cwd ? join21(cwd, "packages", "skills") : void 0;
4736
- if (srcDir && existsSync9(srcDir)) {
5037
+ const srcDir = cwd ? join22(cwd, "packages", "skills") : void 0;
5038
+ if (srcDir && existsSync10(srcDir)) {
4737
5039
  const found = findInstalledSkillMd(srcDir);
4738
5040
  if (found.length > 0) {
4739
5041
  return check("pass", "skills_installed", "Skills installed", `${found.length} SKILL.md file(s) found (source)`);
@@ -4746,8 +5048,8 @@ function checkSkillsInstalled(home, cwd) {
4746
5048
  return check("pass", "skills_installed", "Skills installed", `${found.length} SKILL.md file(s) found (plugin v${plugin.version})`);
4747
5049
  }
4748
5050
  }
4749
- const skillsDir = join21(home, ".claude", "skills");
4750
- if (existsSync9(skillsDir)) {
5051
+ const skillsDir = join22(home, ".claude", "skills");
5052
+ if (existsSync10(skillsDir)) {
4751
5053
  const found = findInstalledSkillMd(skillsDir);
4752
5054
  if (found.length > 0) {
4753
5055
  return check("pass", "skills_installed", "Skills installed", `${found.length} SKILL.md file(s) found (CLI install)`);
@@ -4757,10 +5059,10 @@ function checkSkillsInstalled(home, cwd) {
4757
5059
  }
4758
5060
  function checkDuplicateSkills(home) {
4759
5061
  const plugin = findPlugin(home);
4760
- const skillsDir = join21(home, ".claude", "skills");
5062
+ const skillsDir = join22(home, ".claude", "skills");
4761
5063
  const agentSkillDirs = [
4762
- { label: "~/.codex/skills/", path: join21(home, ".codex", "skills") },
4763
- { label: "~/.agents/skills/", path: join21(home, ".agents", "skills") }
5064
+ { label: "~/.codex/skills/", path: join22(home, ".codex", "skills") },
5065
+ { label: "~/.agents/skills/", path: join22(home, ".agents", "skills") }
4764
5066
  ];
4765
5067
  if (!plugin) {
4766
5068
  return check("pass", "skills_duplicate", "Skills not duplicated", "Single install channel");
@@ -4863,8 +5165,8 @@ async function checkProfiles(home) {
4863
5165
  }
4864
5166
  async function checkProjectLocalOverride(cwd) {
4865
5167
  const dir = cwd ?? process.cwd();
4866
- const envPath = join21(dir, ".skillwiki", ".env");
4867
- if (existsSync9(envPath)) {
5168
+ const envPath = join22(dir, ".skillwiki", ".env");
5169
+ if (existsSync10(envPath)) {
4868
5170
  return check("pass", "project_local", "Project-local config", `Found: ${envPath}`);
4869
5171
  }
4870
5172
  return check("pass", "project_local", "Project-local config", "None");
@@ -4873,7 +5175,7 @@ function checkVaultGitRemote(resolvedPath) {
4873
5175
  if (resolvedPath === void 0) {
4874
5176
  return check("error", "vault_git_remote", "Vault git remote", "Cannot check \u2014 WIKI_PATH not resolved");
4875
5177
  }
4876
- if (!existsSync9(join21(resolvedPath, ".git"))) {
5178
+ if (!existsSync10(join22(resolvedPath, ".git"))) {
4877
5179
  return check("warn", "vault_git_remote", "Vault git remote", "Vault is not a git repository \u2014 sync features unavailable");
4878
5180
  }
4879
5181
  try {
@@ -4896,9 +5198,9 @@ function checkObsidianTemplates(resolvedPath) {
4896
5198
  return check("error", "obsidian_templates", "Obsidian templates", "Cannot check \u2014 WIKI_PATH not resolved");
4897
5199
  }
4898
5200
  const missing = [];
4899
- if (!existsSync9(join21(resolvedPath, "_Templates"))) missing.push("_Templates/");
4900
- if (!existsSync9(join21(resolvedPath, ".obsidian", "templates.json"))) missing.push(".obsidian/templates.json");
4901
- if (!existsSync9(join21(resolvedPath, ".obsidian", "app.json"))) missing.push(".obsidian/app.json");
5201
+ if (!existsSync10(join22(resolvedPath, "_Templates"))) missing.push("_Templates/");
5202
+ if (!existsSync10(join22(resolvedPath, ".obsidian", "templates.json"))) missing.push(".obsidian/templates.json");
5203
+ if (!existsSync10(join22(resolvedPath, ".obsidian", "app.json"))) missing.push(".obsidian/app.json");
4902
5204
  if (missing.length === 0) {
4903
5205
  return check("pass", "obsidian_templates", "Obsidian templates", "Template folder and config present");
4904
5206
  }
@@ -4908,8 +5210,8 @@ function checkDotStoreClean(resolvedPath) {
4908
5210
  if (resolvedPath === void 0) {
4909
5211
  return check("error", "dsstore_clean", "No .DS_Store in raw/", "Cannot check \u2014 WIKI_PATH not resolved");
4910
5212
  }
4911
- const rawDir = join21(resolvedPath, "raw");
4912
- if (!existsSync9(rawDir)) {
5213
+ const rawDir = join22(resolvedPath, "raw");
5214
+ if (!existsSync10(rawDir)) {
4913
5215
  return check("pass", "dsstore_clean", "No .DS_Store in raw/", "raw/ directory not found \u2014 check skipped");
4914
5216
  }
4915
5217
  const found = [];
@@ -4924,7 +5226,7 @@ function checkDotStoreClean(resolvedPath) {
4924
5226
  if (entry.name === ".DS_Store") {
4925
5227
  found.push(rel ? `${rel}/.DS_Store` : ".DS_Store");
4926
5228
  } else if (entry.isDirectory()) {
4927
- walk2(join21(dir, entry.name), rel ? `${rel}/${entry.name}` : entry.name);
5229
+ walk2(join22(dir, entry.name), rel ? `${rel}/${entry.name}` : entry.name);
4928
5230
  }
4929
5231
  }
4930
5232
  })(rawDir, "");
@@ -4937,7 +5239,7 @@ function checkSyncLastPush(resolvedPath) {
4937
5239
  if (resolvedPath === void 0) {
4938
5240
  return check("error", "sync_last_push", "Vault sync recency", "Cannot check \u2014 WIKI_PATH not resolved");
4939
5241
  }
4940
- if (!existsSync9(join21(resolvedPath, ".git"))) {
5242
+ if (!existsSync10(join22(resolvedPath, ".git"))) {
4941
5243
  return check("pass", "sync_last_push", "Vault sync recency", "No git repo \u2014 sync check skipped");
4942
5244
  }
4943
5245
  let timestamp;
@@ -4985,7 +5287,7 @@ function checkVaultGitDirty(resolvedPath) {
4985
5287
  if (resolvedPath === void 0) {
4986
5288
  return check("pass", "vault_git_dirty", "Vault git dirty state", "No vault path \u2014 check skipped");
4987
5289
  }
4988
- if (!existsSync9(join21(resolvedPath, ".git"))) {
5290
+ if (!existsSync10(join22(resolvedPath, ".git"))) {
4989
5291
  return check("pass", "vault_git_dirty", "Vault git dirty state", "No git repo \u2014 check skipped");
4990
5292
  }
4991
5293
  try {
@@ -5053,7 +5355,7 @@ function remoteMainHash(resolvedPath) {
5053
5355
  }
5054
5356
  function checkStaleRemoteMain(resolvedPath) {
5055
5357
  if (resolvedPath === void 0) return void 0;
5056
- if (!existsSync9(join21(resolvedPath, ".git"))) return void 0;
5358
+ if (!existsSync10(join22(resolvedPath, ".git"))) return void 0;
5057
5359
  const localOrigin = gitRefHash(resolvedPath, "origin/main");
5058
5360
  if (!localOrigin) return void 0;
5059
5361
  const remoteMain = remoteMainHash(resolvedPath);
@@ -5069,7 +5371,7 @@ function checkVaultGitComparison(resolvedPath, id, label, range, nonZeroSuffix,
5069
5371
  if (resolvedPath === void 0) {
5070
5372
  return check("pass", id, label, "No vault path \u2014 check skipped");
5071
5373
  }
5072
- if (!existsSync9(join21(resolvedPath, ".git"))) {
5374
+ if (!existsSync10(join22(resolvedPath, ".git"))) {
5073
5375
  return check("pass", id, label, "No git repo \u2014 check skipped");
5074
5376
  }
5075
5377
  if (!hasOriginMain(resolvedPath)) {
@@ -5089,11 +5391,84 @@ function checkVaultGitComparison(resolvedPath, id, label, range, nonZeroSuffix,
5089
5391
  return check("warn", id, label, "Could not compare HEAD with origin/main");
5090
5392
  }
5091
5393
  }
5394
+ function checkSatelliteLastRun(vaultPath, satelliteExpected) {
5395
+ if (!satelliteExpected) {
5396
+ return check("pass", "satellite_job_last_run", "Satellite job last run", "Satellite job not expected on this host");
5397
+ }
5398
+ if (vaultPath === void 0) {
5399
+ return check("pass", "satellite_job_last_run", "Satellite job last run", "No vault path \u2014 check skipped");
5400
+ }
5401
+ const latestPath = satelliteLatestRunPath(vaultPath);
5402
+ if (!existsSync10(latestPath)) {
5403
+ return check("pass", "satellite_job_last_run", "Satellite job last run", "No latest-run.json \u2014 satellite has not run yet");
5404
+ }
5405
+ try {
5406
+ const health = evaluateSatelliteRunHealth(vaultPath, /* @__PURE__ */ new Date());
5407
+ if (health.failed) {
5408
+ const fc = health.failureClass;
5409
+ const detail = fc ? `Last satellite run failed (failure_class: ${fc})` : "Last satellite run failed";
5410
+ return check("error", "satellite_job_last_run", "Satellite job last run", detail);
5411
+ }
5412
+ if (health.stale && health.finishedAt) {
5413
+ return check(
5414
+ "warn",
5415
+ "satellite_job_last_run",
5416
+ "Satellite job last run",
5417
+ `Last run finished_at is older than 26h (${health.finishedAt})`
5418
+ );
5419
+ }
5420
+ return check(
5421
+ "pass",
5422
+ "satellite_job_last_run",
5423
+ "Satellite job last run",
5424
+ health.finishedAt ? `Last run ok (finished_at ${health.finishedAt})` : "Last run ok"
5425
+ );
5426
+ } catch {
5427
+ return check("warn", "satellite_job_last_run", "Satellite job last run", `Could not read ${latestPath}`);
5428
+ }
5429
+ }
5430
+ function defaultSatelliteTimerDeps() {
5431
+ return {
5432
+ platform: () => platform2(),
5433
+ systemctlIsActive: (unit) => {
5434
+ try {
5435
+ return execSync2(`systemctl is-active ${unit}`, {
5436
+ encoding: "utf8",
5437
+ timeout: 2e3,
5438
+ stdio: ["pipe", "pipe", "pipe"]
5439
+ }).trim();
5440
+ } catch {
5441
+ return void 0;
5442
+ }
5443
+ }
5444
+ };
5445
+ }
5446
+ function checkSatelliteTimer(satelliteExpected, deps = defaultSatelliteTimerDeps()) {
5447
+ if (!satelliteExpected) {
5448
+ return check("pass", "satellite_job_timer", "Satellite job timer", "Satellite job not expected on this host");
5449
+ }
5450
+ if (deps.platform() !== "linux") {
5451
+ return check("pass", "satellite_job_timer", "Satellite job timer", "Timer check skipped \u2014 Linux only");
5452
+ }
5453
+ const out = deps.systemctlIsActive("agent-memory-trends.timer");
5454
+ if (out === void 0) {
5455
+ return check("pass", "satellite_job_timer", "Satellite job timer", "systemctl unavailable");
5456
+ }
5457
+ if (out === "active") {
5458
+ return check("pass", "satellite_job_timer", "Satellite job timer", "systemd: agent-memory-trends.timer active");
5459
+ }
5460
+ return check(
5461
+ "error",
5462
+ "satellite_job_timer",
5463
+ "Satellite job timer",
5464
+ `systemd: agent-memory-trends.timer is ${out || "not active"}`
5465
+ );
5466
+ }
5092
5467
  async function checkFleetIdentity(input) {
5093
5468
  if (!input.vaultPath) {
5094
5469
  return check("pass", "fleet_identity", "Fleet identity", "No vault path \u2014 check skipped");
5095
5470
  }
5096
- const r = await runFleetContext({
5471
+ const load = input.fleetLoad !== void 0 ? input.fleetLoad : await loadFleetManifestAndHost({
5097
5472
  vault: input.vaultPath,
5098
5473
  env: { ...process.env, WIKI_PATH: input.envValue ?? input.vaultPath },
5099
5474
  home: input.home,
@@ -5101,26 +5476,22 @@ async function checkFleetIdentity(input) {
5101
5476
  osHostname: process.env.HOSTNAME,
5102
5477
  user: process.env.USER
5103
5478
  });
5104
- if (!r.result.ok) {
5105
- return check("warn", "fleet_identity", "Fleet identity", "Could not evaluate fleet identity");
5106
- }
5107
- const data = r.result.data;
5108
- if (!data.manifest_loaded) {
5479
+ if (!load) {
5109
5480
  return check("pass", "fleet_identity", "Fleet identity", "Fleet manifest unavailable \u2014 check skipped");
5110
5481
  }
5111
- if (data.identity_status === "known") {
5112
- return check("pass", "fleet_identity", "Fleet identity", `Resolved ${data.host_id ?? "unknown"} via ${data.source ?? "unknown"}`);
5482
+ if (load.identityStatus === "known") {
5483
+ return check("pass", "fleet_identity", "Fleet identity", `Resolved ${load.hostId ?? "unknown"} via ${load.source ?? "unknown"}`);
5113
5484
  }
5114
- const detail = data.warnings.length > 0 ? data.warnings.join("; ") : "Fleet identity is unresolved";
5485
+ const detail = load.warnings.length > 0 ? load.warnings.join("; ") : "Fleet identity is unresolved";
5115
5486
  return check("warn", "fleet_identity", "Fleet identity", detail);
5116
5487
  }
5117
5488
  function pullLogPaths(home) {
5118
5489
  const paths = platform2() === "darwin" ? [
5119
- join21(home, "Library", "Logs", "wiki-pull.log"),
5120
- join21(home, ".local", "state", "vault-sync", "log", "wiki-pull.log")
5490
+ join22(home, "Library", "Logs", "wiki-pull.log"),
5491
+ join22(home, ".local", "state", "vault-sync", "log", "wiki-pull.log")
5121
5492
  ] : [
5122
- join21(home, ".local", "state", "vault-sync", "log", "wiki-pull.log"),
5123
- join21(home, "Library", "Logs", "wiki-pull.log")
5493
+ join22(home, ".local", "state", "vault-sync", "log", "wiki-pull.log"),
5494
+ join22(home, "Library", "Logs", "wiki-pull.log")
5124
5495
  ];
5125
5496
  return [...new Set(paths)];
5126
5497
  }
@@ -5132,12 +5503,12 @@ function isRecentLogLine(line, nowMs) {
5132
5503
  return nowMs - ts <= 24 * 60 * 60 * 1e3;
5133
5504
  }
5134
5505
  function checkVaultGitPullFailures(home) {
5135
- const path = pullLogPaths(home).find((p) => existsSync9(p));
5506
+ const path = pullLogPaths(home).find((p) => existsSync10(p));
5136
5507
  if (!path) {
5137
5508
  return check("pass", "vault_git_pull_failures", "Vault pull failures", "No wiki-pull.log found \u2014 check skipped");
5138
5509
  }
5139
5510
  try {
5140
- const lines = readFileSync6(path, "utf8").split(/\r?\n/).filter(Boolean);
5511
+ const lines = readFileSync7(path, "utf8").split(/\r?\n/).filter(Boolean);
5141
5512
  const now = Date.now();
5142
5513
  const failures = lines.filter(
5143
5514
  (line) => isRecentLogLine(line, now) && /(pre-push pull failed|FAIL .*pull|FAIL .*rebase|cannot pull with rebase|unstaged changes)/i.test(line)
@@ -5160,8 +5531,8 @@ function checkS3MountPerf(resolvedPath) {
5160
5531
  return check("pass", "s3_mount_perf", "S3 mount performance", "local disk");
5161
5532
  }
5162
5533
  const mountPoint = fuse.mountPoint;
5163
- const conceptsDir = join21(resolvedPath, "concepts");
5164
- if (!existsSync9(conceptsDir)) {
5534
+ const conceptsDir = join22(resolvedPath, "concepts");
5535
+ if (!existsSync10(conceptsDir)) {
5165
5536
  return check("pass", "s3_mount_perf", "S3 mount performance", `S3 FUSE mount (${mountPoint}), no concepts/ to benchmark`);
5166
5537
  }
5167
5538
  const start = Date.now();
@@ -5343,8 +5714,8 @@ function checkWriteTest(resolvedPath) {
5343
5714
  if (!fuse) {
5344
5715
  return check("pass", "s3_write_test", "S3 write test", "local disk \u2014 check skipped");
5345
5716
  }
5346
- const conceptsDir = join21(resolvedPath, "concepts");
5347
- if (!existsSync9(conceptsDir)) {
5717
+ const conceptsDir = join22(resolvedPath, "concepts");
5718
+ if (!existsSync10(conceptsDir)) {
5348
5719
  return check("pass", "s3_write_test", "S3 write test", "no concepts/ dir to test \u2014 check skipped");
5349
5720
  }
5350
5721
  const result = writeTest(conceptsDir);
@@ -5430,7 +5801,7 @@ function checkVfsCacheHealth(resolvedPath) {
5430
5801
  }
5431
5802
  function readVaultSyncConfig(home) {
5432
5803
  try {
5433
- const content = readFileSync6(join21(home, ".skillwiki", ".env"), "utf8");
5804
+ const content = readFileSync7(join22(home, ".skillwiki", ".env"), "utf8");
5434
5805
  let installed = false;
5435
5806
  let role;
5436
5807
  let serviceScope;
@@ -5459,7 +5830,7 @@ function readVaultSyncConfig(home) {
5459
5830
  }
5460
5831
  function readKeyFromEnvFile(path, keys) {
5461
5832
  try {
5462
- const content = readFileSync6(path, "utf8");
5833
+ const content = readFileSync7(path, "utf8");
5463
5834
  for (const line of content.split(/\r?\n/)) {
5464
5835
  const trimmed = line.trim();
5465
5836
  if (trimmed.length === 0 || trimmed.startsWith("#")) continue;
@@ -5481,7 +5852,7 @@ function resolveSnapshotGitWorktree(config) {
5481
5852
  if (fromProfile) return fromProfile;
5482
5853
  }
5483
5854
  const defaultPath = "/root/wiki-git";
5484
- return existsSync9(defaultPath) ? defaultPath : void 0;
5855
+ return existsSync10(defaultPath) ? defaultPath : void 0;
5485
5856
  }
5486
5857
  function vaultSyncChecks(input) {
5487
5858
  const os = input.os ?? platform2();
@@ -5498,16 +5869,16 @@ function vaultSyncChecks(input) {
5498
5869
  ];
5499
5870
  }
5500
5871
  const isMac = os === "darwin";
5501
- const logDir = input.logDir ?? (isMac ? join21(home, "Library", "Logs") : join21(home, ".local", "state", "vault-sync", "log"));
5502
- const shareDir = input.shareDir ?? (isMac ? join21(home, "Library", "Application Support", "vault-sync", "bin") : join21(home, ".local", "share", "vault-sync", "bin"));
5503
- const filterPath = input.filterPath ?? join21(home, ".config", "rclone", "wiki-push-filters.txt");
5504
- const packagedSnapshotPath = join21(shareDir, "wiki-snapshot.sh");
5872
+ const logDir = input.logDir ?? (isMac ? join22(home, "Library", "Logs") : join22(home, ".local", "state", "vault-sync", "log"));
5873
+ const shareDir = input.shareDir ?? (isMac ? join22(home, "Library", "Application Support", "vault-sync", "bin") : join22(home, ".local", "share", "vault-sync", "bin"));
5874
+ const filterPath = input.filterPath ?? join22(home, ".config", "rclone", "wiki-push-filters.txt");
5875
+ const packagedSnapshotPath = join22(shareDir, "wiki-snapshot.sh");
5505
5876
  const legacySnapshotPath = "/root/.hermes/scripts/wiki-snapshot-v3.sh";
5506
- const snapshotPath = input.snapshotScriptPath ?? (existsSync9(packagedSnapshotPath) ? packagedSnapshotPath : legacySnapshotPath);
5877
+ const snapshotPath = input.snapshotScriptPath ?? (existsSync10(packagedSnapshotPath) ? packagedSnapshotPath : legacySnapshotPath);
5507
5878
  function snapshotLastStatusCheck() {
5508
- const snapshotLog = join21(logDir, "wiki-snapshot.log");
5879
+ const snapshotLog = join22(logDir, "wiki-snapshot.log");
5509
5880
  try {
5510
- const logContent = readFileSync6(snapshotLog, "utf8");
5881
+ const logContent = readFileSync7(snapshotLog, "utf8");
5511
5882
  const lines = logContent.trim().split("\n").filter(Boolean);
5512
5883
  if (lines.length === 0) {
5513
5884
  return check(
@@ -5552,14 +5923,14 @@ function vaultSyncChecks(input) {
5552
5923
  }
5553
5924
  }
5554
5925
  if (input.vaultSyncRole === "snapshotter") {
5555
- const c12 = existsSync9(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}`);
5926
+ const c12 = existsSync10(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}`);
5556
5927
  const serviceScope = input.vaultSyncServiceScope ?? "user";
5557
- const userTimerPath = join21(home, ".config", "systemd", "user", "wiki-snapshot.timer");
5928
+ const userTimerPath = join22(home, ".config", "systemd", "user", "wiki-snapshot.timer");
5558
5929
  const systemTimerPath = "/etc/systemd/system/wiki-snapshot.timer";
5559
5930
  let c22;
5560
- if (serviceScope === "user" && existsSync9(userTimerPath)) {
5931
+ if (serviceScope === "user" && existsSync10(userTimerPath)) {
5561
5932
  c22 = check("pass", "vault_sync_jobs_enabled", "Vault sync jobs enabled", `Found: ${userTimerPath}`);
5562
- } else if (serviceScope === "system" && existsSync9(systemTimerPath)) {
5933
+ } else if (serviceScope === "system" && existsSync10(systemTimerPath)) {
5563
5934
  c22 = check("pass", "vault_sync_jobs_enabled", "Vault sync jobs enabled", `Found: ${systemTimerPath}`);
5564
5935
  } else if (os !== "linux") {
5565
5936
  c22 = check("warn", "vault_sync_jobs_enabled", "Vault sync jobs enabled", "Snapshotter scheduler is Linux-only and no wiki-snapshot.timer file was found");
@@ -5591,7 +5962,7 @@ function vaultSyncChecks(input) {
5591
5962
  );
5592
5963
  let c52;
5593
5964
  try {
5594
- if (!existsSync9(snapshotPath)) {
5965
+ if (!existsSync10(snapshotPath)) {
5595
5966
  c52 = check(
5596
5967
  "error",
5597
5968
  "vault_sync_snapshot_guard",
@@ -5599,7 +5970,7 @@ function vaultSyncChecks(input) {
5599
5970
  `Snapshot script not found at ${snapshotPath}`
5600
5971
  );
5601
5972
  } else {
5602
- const content = readFileSync6(snapshotPath, "utf8");
5973
+ const content = readFileSync7(snapshotPath, "utf8");
5603
5974
  if (!content.includes("--max-delete")) {
5604
5975
  c52 = check(
5605
5976
  "error",
@@ -5626,8 +5997,8 @@ function vaultSyncChecks(input) {
5626
5997
  }
5627
5998
  return [c12, c22, c32, cFetch2, c42, c52];
5628
5999
  }
5629
- const pushScriptPath = join21(shareDir, "wiki-push.sh");
5630
- const c1 = existsSync9(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`);
6000
+ const pushScriptPath = join22(shareDir, "wiki-push.sh");
6001
+ const c1 = existsSync10(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`);
5631
6002
  let c2;
5632
6003
  try {
5633
6004
  if (isMac) {
@@ -5678,10 +6049,10 @@ function vaultSyncChecks(input) {
5678
6049
  "Scheduler check failed \u2014 run vault-sync-install"
5679
6050
  );
5680
6051
  }
5681
- const logFile = join21(logDir, "wiki-push.log");
6052
+ const logFile = join22(logDir, "wiki-push.log");
5682
6053
  let c3;
5683
6054
  try {
5684
- const logContent = readFileSync6(logFile, "utf8");
6055
+ const logContent = readFileSync7(logFile, "utf8");
5685
6056
  const lines = logContent.trim().split("\n").filter(Boolean);
5686
6057
  if (lines.length === 0) {
5687
6058
  c3 = check(
@@ -5737,7 +6108,7 @@ function vaultSyncChecks(input) {
5737
6108
  }
5738
6109
  }
5739
6110
  } catch {
5740
- c3 = existsSync9(logDir) ? check(
6111
+ c3 = existsSync10(logDir) ? check(
5741
6112
  "warn",
5742
6113
  "vault_sync_last_push_age",
5743
6114
  "Vault sync last push recency",
@@ -5749,10 +6120,10 @@ function vaultSyncChecks(input) {
5749
6120
  `Log directory not found at ${logDir}`
5750
6121
  );
5751
6122
  }
5752
- const fetchLogFile = join21(logDir, "wiki-fetch.log");
6123
+ const fetchLogFile = join22(logDir, "wiki-fetch.log");
5753
6124
  let cFetch;
5754
6125
  try {
5755
- const logContent = readFileSync6(fetchLogFile, "utf8");
6126
+ const logContent = readFileSync7(fetchLogFile, "utf8");
5756
6127
  const lines = logContent.trim().split("\n").filter(Boolean);
5757
6128
  if (lines.length === 0) {
5758
6129
  cFetch = check(
@@ -5796,7 +6167,7 @@ function vaultSyncChecks(input) {
5796
6167
  }
5797
6168
  let c4;
5798
6169
  try {
5799
- if (!existsSync9(filterPath)) {
6170
+ if (!existsSync10(filterPath)) {
5800
6171
  c4 = check(
5801
6172
  "error",
5802
6173
  "vault_sync_filter_present",
@@ -5804,7 +6175,7 @@ function vaultSyncChecks(input) {
5804
6175
  `Filter file not found at ${filterPath}`
5805
6176
  );
5806
6177
  } else {
5807
- const content = readFileSync6(filterPath, "utf8");
6178
+ const content = readFileSync7(filterPath, "utf8");
5808
6179
  const requiredExcludes = [
5809
6180
  "remotely-save/data.json",
5810
6181
  ".skillwiki/sync.lock",
@@ -5847,7 +6218,7 @@ function vaultSyncChecks(input) {
5847
6218
  );
5848
6219
  } else {
5849
6220
  try {
5850
- if (!existsSync9(snapshotPath)) {
6221
+ if (!existsSync10(snapshotPath)) {
5851
6222
  c5 = check(
5852
6223
  "error",
5853
6224
  "vault_sync_snapshot_guard",
@@ -5855,7 +6226,7 @@ function vaultSyncChecks(input) {
5855
6226
  `Snapshot script not found at ${snapshotPath}`
5856
6227
  );
5857
6228
  } else {
5858
- const content = readFileSync6(snapshotPath, "utf8");
6229
+ const content = readFileSync7(snapshotPath, "utf8");
5859
6230
  if (!content.includes("--max-delete")) {
5860
6231
  c5 = check(
5861
6232
  "error",
@@ -5893,15 +6264,15 @@ function findSkillMd(dir) {
5893
6264
  }
5894
6265
  for (const entry of entries) {
5895
6266
  if (entry.isFile() && entry.name === "SKILL.md") {
5896
- results.push(join21(dir, entry.name));
6267
+ results.push(join22(dir, entry.name));
5897
6268
  } else if (entry.isDirectory()) {
5898
- results.push(...findSkillMd(join21(dir, entry.name)));
6269
+ results.push(...findSkillMd(join22(dir, entry.name)));
5899
6270
  }
5900
6271
  }
5901
6272
  return results;
5902
6273
  }
5903
6274
  function findInstalledSkillMd(dir) {
5904
- const directSkills = findSkillNames(dir).map((name) => join21(dir, name, "SKILL.md"));
6275
+ const directSkills = findSkillNames(dir).map((name) => join22(dir, name, "SKILL.md"));
5905
6276
  return directSkills.length > 0 ? directSkills : findSkillMd(dir);
5906
6277
  }
5907
6278
  function findSkillNames(dir) {
@@ -5913,7 +6284,7 @@ function findSkillNames(dir) {
5913
6284
  return results;
5914
6285
  }
5915
6286
  for (const entry of entries) {
5916
- if (entry.isDirectory() && existsSync9(join21(dir, entry.name, "SKILL.md"))) {
6287
+ if (entry.isDirectory() && existsSync10(join22(dir, entry.name, "SKILL.md"))) {
5917
6288
  results.push(entry.name);
5918
6289
  }
5919
6290
  }
@@ -5957,7 +6328,7 @@ async function vaultMetrics(resolvedPath) {
5957
6328
  }
5958
6329
  let logLines = 0;
5959
6330
  try {
5960
- logLines = readFileSync6(join21(resolvedPath, "log.md"), "utf8").split("\n").length;
6331
+ logLines = readFileSync7(join22(resolvedPath, "log.md"), "utf8").split("\n").length;
5961
6332
  } catch {
5962
6333
  }
5963
6334
  return [
@@ -5989,11 +6360,20 @@ async function runDoctor(input) {
5989
6360
  checks.push(checkVaultStructure(resolvedPath));
5990
6361
  checks.push(checkObsidianTemplates(resolvedPath));
5991
6362
  checks.push(checkVaultGitRemote(gitCheckPath));
6363
+ const fleetLoad = resolvedPath ? await loadFleetManifestAndHost({
6364
+ vault: resolvedPath,
6365
+ env: { ...process.env, WIKI_PATH: input.envValue ?? resolvedPath },
6366
+ home: input.home,
6367
+ cwd: input.cwd,
6368
+ osHostname: process.env.HOSTNAME,
6369
+ user: process.env.USER
6370
+ }) : null;
5992
6371
  checks.push(await checkFleetIdentity({
5993
6372
  vaultPath: resolvedPath,
5994
6373
  home: input.home,
5995
6374
  cwd: input.cwd,
5996
- envValue: input.envValue
6375
+ envValue: input.envValue,
6376
+ fleetLoad
5997
6377
  }));
5998
6378
  checks.push(checkSyncLastPush(gitCheckPath));
5999
6379
  checks.push(checkVaultGitDirty(gitCheckPath));
@@ -6018,6 +6398,9 @@ async function runDoctor(input) {
6018
6398
  vaultSyncServiceScope: vsConfig.serviceScope,
6019
6399
  snapshotScriptPath: vsConfig.snapshotScript
6020
6400
  }));
6401
+ const satelliteGate = satelliteGateFromFleetLoad(fleetLoad);
6402
+ checks.push(checkSatelliteLastRun(resolvedPath, satelliteGate.satelliteExpected));
6403
+ checks.push(checkSatelliteTimer(satelliteGate.satelliteExpected));
6021
6404
  checks.push(...await vaultMetrics(resolvedPath));
6022
6405
  const summary = {
6023
6406
  pass: checks.filter((c) => c.status === "pass").length,
@@ -6042,7 +6425,7 @@ async function runDoctor(input) {
6042
6425
  }
6043
6426
 
6044
6427
  // src/utils/package-info.ts
6045
- import { readFileSync as readFileSync7 } from "fs";
6428
+ import { readFileSync as readFileSync8 } from "fs";
6046
6429
  function packageJsonCandidateUrls(baseUrl = import.meta.url) {
6047
6430
  return [
6048
6431
  new URL("../package.json", baseUrl),
@@ -6052,7 +6435,7 @@ function packageJsonCandidateUrls(baseUrl = import.meta.url) {
6052
6435
  function readCliPackageJson(baseUrl = import.meta.url) {
6053
6436
  for (const url of packageJsonCandidateUrls(baseUrl)) {
6054
6437
  try {
6055
- const pkg = JSON.parse(readFileSync7(url, "utf8"));
6438
+ const pkg = JSON.parse(readFileSync8(url, "utf8"));
6056
6439
  if (typeof pkg.version === "string") {
6057
6440
  return { ...pkg, version: pkg.version };
6058
6441
  }
@@ -6064,11 +6447,11 @@ function readCliPackageJson(baseUrl = import.meta.url) {
6064
6447
 
6065
6448
  // src/commands/project-index.ts
6066
6449
  import { readdir as readdir4, readFile as readFile16, writeFile as writeFile6, mkdir as mkdir5 } from "fs/promises";
6067
- import { join as join22, dirname as dirname8 } from "path";
6450
+ import { join as join23, dirname as dirname8 } from "path";
6068
6451
  var LAYER2_DIRS = ["entities", "concepts", "comparisons", "queries", "meta"];
6069
6452
  async function runProjectIndex(input) {
6070
6453
  const slug = input.slug;
6071
- const projectDir = join22(input.vault, "projects", slug);
6454
+ const projectDir = join23(input.vault, "projects", slug);
6072
6455
  try {
6073
6456
  await readdir4(projectDir);
6074
6457
  } catch {
@@ -6079,12 +6462,12 @@ async function runProjectIndex(input) {
6079
6462
  }
6080
6463
  const wikilinkPattern = `[[${slug}]]`;
6081
6464
  const entries = [];
6082
- const compoundDir = join22(input.vault, "projects", slug, "compound");
6465
+ const compoundDir = join23(input.vault, "projects", slug, "compound");
6083
6466
  try {
6084
6467
  const compoundFiles = await readdir4(compoundDir, { withFileTypes: true });
6085
6468
  for (const entry of compoundFiles) {
6086
6469
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
6087
- const filePath = join22(compoundDir, entry.name);
6470
+ const filePath = join23(compoundDir, entry.name);
6088
6471
  let text;
6089
6472
  try {
6090
6473
  text = await readFile16(filePath, "utf8");
@@ -6104,13 +6487,13 @@ async function runProjectIndex(input) {
6104
6487
  for (const dir of LAYER2_DIRS) {
6105
6488
  let files;
6106
6489
  try {
6107
- files = await readdir4(join22(input.vault, dir), { withFileTypes: true });
6490
+ files = await readdir4(join23(input.vault, dir), { withFileTypes: true });
6108
6491
  } catch {
6109
6492
  continue;
6110
6493
  }
6111
6494
  for (const entry of files) {
6112
6495
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
6113
- const filePath = join22(input.vault, dir, entry.name);
6496
+ const filePath = join23(input.vault, dir, entry.name);
6114
6497
  let text;
6115
6498
  try {
6116
6499
  text = await readFile16(filePath, "utf8");
@@ -6134,7 +6517,7 @@ async function runProjectIndex(input) {
6134
6517
  const tb = typeOrder[b.type] ?? 99;
6135
6518
  return ta !== tb ? ta - tb : a.title.localeCompare(b.title);
6136
6519
  });
6137
- const indexPath = join22(projectDir, "knowledge.md");
6520
+ const indexPath = join23(projectDir, "knowledge.md");
6138
6521
  let existing = false;
6139
6522
  let stale = false;
6140
6523
  try {
@@ -6208,8 +6591,8 @@ ${entries.map((e) => ` ${e.type}: [[${e.page.replace(/\.md$/, "")}]] \u2014 ${e
6208
6591
 
6209
6592
  // src/commands/observe.ts
6210
6593
  import { mkdir as mkdir6, writeFile as writeFile7 } from "fs/promises";
6211
- import { existsSync as existsSync10, statSync as statSync2 } from "fs";
6212
- import { join as join23 } from "path";
6594
+ import { existsSync as existsSync11, statSync as statSync2 } from "fs";
6595
+ import { join as join24 } from "path";
6213
6596
  import { createHash as createHash5 } from "crypto";
6214
6597
  var ALLOWED_KINDS = /* @__PURE__ */ new Set(["note", "bug", "task", "idea", "session-log"]);
6215
6598
  function slugify(text) {
@@ -6232,13 +6615,13 @@ async function runObserve(input) {
6232
6615
  result: err("SCHEME_REJECTED", { message: "Text must not be empty" })
6233
6616
  };
6234
6617
  }
6235
- if (!existsSync10(input.vault) || !statSync2(input.vault).isDirectory()) {
6618
+ if (!existsSync11(input.vault) || !statSync2(input.vault).isDirectory()) {
6236
6619
  return {
6237
6620
  exitCode: ExitCode.VAULT_PATH_INVALID,
6238
6621
  result: err("VAULT_PATH_INVALID", { path: input.vault })
6239
6622
  };
6240
6623
  }
6241
- const transcriptsDir = join23(input.vault, "raw", "transcripts");
6624
+ const transcriptsDir = join24(input.vault, "raw", "transcripts");
6242
6625
  try {
6243
6626
  await mkdir6(transcriptsDir, { recursive: true });
6244
6627
  } catch {
@@ -6250,7 +6633,7 @@ async function runObserve(input) {
6250
6633
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
6251
6634
  const slug = slugify(input.text);
6252
6635
  const fileName = `${today}-observation-${slug}.md`;
6253
- const filePath = join23(transcriptsDir, fileName);
6636
+ const filePath = join24(transcriptsDir, fileName);
6254
6637
  const body = `
6255
6638
  ${input.text.trim()}
6256
6639
  `;
@@ -6292,7 +6675,7 @@ ${input.text.trim()}
6292
6675
  // src/commands/memory.ts
6293
6676
  import { createHash as createHash6 } from "crypto";
6294
6677
  import { mkdir as mkdir7, readFile as readFile17, readdir as readdir5, stat as stat5, writeFile as writeFile8 } from "fs/promises";
6295
- import { basename as basename2, extname, join as join24, relative as relative4, sep as sep4 } from "path";
6678
+ import { basename as basename2, extname, join as join25, relative as relative4, sep as sep4 } from "path";
6296
6679
  async function runMemoryTopics(input) {
6297
6680
  const scan = await scanVault(input.vault);
6298
6681
  if (!scan.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scan };
@@ -6356,8 +6739,8 @@ async function runMemoryIndex(input) {
6356
6739
  }
6357
6740
  const generatedAt = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
6358
6741
  const relCachePath = memoryCacheRelPath(input.project);
6359
- const absCachePath = join24(input.vault, relCachePath);
6360
- await mkdir7(join24(input.vault, ".skillwiki", "memory", input.project), { recursive: true });
6742
+ const absCachePath = join25(input.vault, relCachePath);
6743
+ await mkdir7(join25(input.vault, ".skillwiki", "memory", input.project), { recursive: true });
6361
6744
  await writeFile8(absCachePath, `${JSON.stringify({
6362
6745
  generated_at: generatedAt,
6363
6746
  project: input.project,
@@ -6549,7 +6932,7 @@ async function buildMemoryIndexState(pages, project) {
6549
6932
  }
6550
6933
  async function checkMemoryIndex(vault, project, current) {
6551
6934
  const relCachePath = memoryCacheRelPath(project);
6552
- const cacheText = await readIfExists2(join24(vault, relCachePath));
6935
+ const cacheText = await readIfExists2(join25(vault, relCachePath));
6553
6936
  if (!cacheText) {
6554
6937
  return {
6555
6938
  ok: true,
@@ -6958,7 +7341,7 @@ async function walkImportFiles(dir, out) {
6958
7341
  const entries = await readdir5(dir, { withFileTypes: true });
6959
7342
  for (const entry of entries) {
6960
7343
  if (entry.name === ".git" || entry.name === "node_modules") continue;
6961
- const path = join24(dir, entry.name);
7344
+ const path = join25(dir, entry.name);
6962
7345
  if (entry.isDirectory()) {
6963
7346
  await walkImportFiles(path, out);
6964
7347
  } else if (entry.isFile() && isImportCandidate(path)) {
@@ -7025,8 +7408,8 @@ async function writeImportCapture(vault, entry, today) {
7025
7408
  const content = hiddenString(entry, "__content");
7026
7409
  const project = hiddenString(entry, "__project");
7027
7410
  const relPath = await availableImportPath(vault, entry.proposed_path);
7028
- const absPath = join24(vault, relPath);
7029
- await mkdir7(join24(vault, "raw", "transcripts"), { recursive: true });
7411
+ const absPath = join25(vault, relPath);
7412
+ await mkdir7(join25(vault, "raw", "transcripts"), { recursive: true });
7030
7413
  await writeFile8(absPath, renderImportCapture(entry, content, project, today), "utf8");
7031
7414
  const validation = await runValidate({ file: absPath });
7032
7415
  return {
@@ -7042,7 +7425,7 @@ async function availableImportPath(vault, proposed) {
7042
7425
  const stem = proposed.slice(0, -ext.length);
7043
7426
  let candidate = proposed;
7044
7427
  let i = 2;
7045
- while (await readIfExists2(join24(vault, candidate))) {
7428
+ while (await readIfExists2(join25(vault, candidate))) {
7046
7429
  candidate = `${stem}-${i}${ext}`;
7047
7430
  i++;
7048
7431
  }
@@ -7214,10 +7597,10 @@ function memoryCacheRelPath(project) {
7214
7597
  }
7215
7598
  async function readMemoryCache(vault, project) {
7216
7599
  if (project) {
7217
- const projectCache = await readIfExists2(join24(vault, memoryCacheRelPath(project)));
7600
+ const projectCache = await readIfExists2(join25(vault, memoryCacheRelPath(project)));
7218
7601
  if (projectCache) return projectCache;
7219
7602
  }
7220
- return readIfExists2(join24(vault, ".skillwiki", "memory-topics.json"));
7603
+ return readIfExists2(join25(vault, ".skillwiki", "memory-topics.json"));
7221
7604
  }
7222
7605
  function dedupePages(pages) {
7223
7606
  const seen = /* @__PURE__ */ new Set();
@@ -7304,7 +7687,7 @@ function slugify2(value) {
7304
7687
 
7305
7688
  // src/commands/query.ts
7306
7689
  import { readFile as readFile18, stat as stat6 } from "fs/promises";
7307
- import { join as join25 } from "path";
7690
+ import { join as join26 } from "path";
7308
7691
  var W_KEYWORD = 2;
7309
7692
  var W_SOURCE_OVERLAP = 4;
7310
7693
  var W_WIKILINK = 3;
@@ -7425,7 +7808,7 @@ function computeKeywordScore(terms, title, tags, body) {
7425
7808
  return score;
7426
7809
  }
7427
7810
  async function loadOrBuildGraph(vault) {
7428
- const graphPath = join25(vault, ".skillwiki", "graph.json");
7811
+ const graphPath = join26(vault, ".skillwiki", "graph.json");
7429
7812
  let needsBuild = false;
7430
7813
  try {
7431
7814
  const fileStat = await stat6(graphPath);
@@ -7454,7 +7837,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
7454
7837
  import { z as z2 } from "zod";
7455
7838
 
7456
7839
  // src/mcp/vault-resolve.ts
7457
- import { join as join26, resolve as resolve7 } from "path";
7840
+ import { join as join27, resolve as resolve7 } from "path";
7458
7841
 
7459
7842
  // src/mcp/allowlist.ts
7460
7843
  import { resolve as resolve6, sep as sep5 } from "path";
@@ -7515,7 +7898,7 @@ async function resolveMcpVault(input) {
7515
7898
  return ok({ vault: vaultPath, source });
7516
7899
  }
7517
7900
  function defaultGraphOut(vault) {
7518
- return join26(vault, ".skillwiki", "graph.json");
7901
+ return join27(vault, ".skillwiki", "graph.json");
7519
7902
  }
7520
7903
 
7521
7904
  // src/mcp/result-format.ts
@@ -7532,7 +7915,7 @@ function formatToolResult(payload) {
7532
7915
  // src/mcp/audit-log.ts
7533
7916
  import { appendFileSync, mkdirSync as mkdirSync4 } from "fs";
7534
7917
  import { homedir } from "os";
7535
- import { join as join27 } from "path";
7918
+ import { join as join28 } from "path";
7536
7919
  function auditEnabled() {
7537
7920
  const v = process.env.SKILLWIKI_MCP_AUDIT;
7538
7921
  if (v === "0" || v === "false") return false;
@@ -7544,7 +7927,7 @@ function auditSink() {
7544
7927
  function auditFilePath() {
7545
7928
  const custom = process.env.SKILLWIKI_MCP_AUDIT_FILE;
7546
7929
  if (custom && custom.length > 0) return custom;
7547
- return join27(homedir(), ".skillwiki", "mcp-audit.jsonl");
7930
+ return join28(homedir(), ".skillwiki", "mcp-audit.jsonl");
7548
7931
  }
7549
7932
  function auditMcpToolCall(entry) {
7550
7933
  if (!auditEnabled()) return;
@@ -7554,7 +7937,7 @@ function auditMcpToolCall(entry) {
7554
7937
  return;
7555
7938
  }
7556
7939
  const path = auditFilePath();
7557
- mkdirSync4(join27(path, ".."), { recursive: true });
7940
+ mkdirSync4(join28(path, ".."), { recursive: true });
7558
7941
  appendFileSync(path, line, "utf8");
7559
7942
  }
7560
7943
  async function runMcpToolHandler(tool, input, fn) {
@@ -7775,7 +8158,7 @@ function registerMcpMutatingTools(server) {
7775
8158
 
7776
8159
  // src/mcp/resources.ts
7777
8160
  import { readFile as readFile20 } from "fs/promises";
7778
- import { join as join29 } from "path";
8161
+ import { join as join30 } from "path";
7779
8162
  import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
7780
8163
 
7781
8164
  // src/mcp/lint-bucket.ts
@@ -7904,8 +8287,8 @@ async function fetchQueryPreview(input) {
7904
8287
 
7905
8288
  // src/mcp/graph-html.ts
7906
8289
  import { readFile as readFile19 } from "fs/promises";
7907
- import { join as join28 } from "path";
7908
- import { existsSync as existsSync11 } from "fs";
8290
+ import { join as join29 } from "path";
8291
+ import { existsSync as existsSync12 } from "fs";
7909
8292
  var TYPE_COLORS = {
7910
8293
  entities: "#e74c3c",
7911
8294
  concepts: "#27ae60",
@@ -7973,9 +8356,9 @@ ${nodeSvg}
7973
8356
  return { html, node_count: nodes.length, edge_count: edges.length, truncated };
7974
8357
  }
7975
8358
  async function fetchGraphHtmlReport(input) {
7976
- const graphPath = input.graphPath ?? join28(input.vault, ".skillwiki", "graph.json");
8359
+ const graphPath = input.graphPath ?? join29(input.vault, ".skillwiki", "graph.json");
7977
8360
  const maxNodes = Math.min(Math.max(10, input.maxNodes ?? 120), 500);
7978
- if (!existsSync11(graphPath)) {
8361
+ if (!existsSync12(graphPath)) {
7979
8362
  return {
7980
8363
  exitCode: ExitCode.FILE_NOT_FOUND,
7981
8364
  result: err("GRAPH_MISSING", { path: graphPath, hint: "Run skillwiki.graph_build first." })
@@ -8047,7 +8430,7 @@ async function fetchStaleSummary(input) {
8047
8430
 
8048
8431
  // src/mcp/resources.ts
8049
8432
  async function readVaultFile(vault, rel) {
8050
- return readFile20(join29(vault, rel), "utf8");
8433
+ return readFile20(join30(vault, rel), "utf8");
8051
8434
  }
8052
8435
  async function tailLines(text, lines) {
8053
8436
  const parts = text.split(/\r?\n/);
@@ -8133,7 +8516,7 @@ function registerMcpResources(server) {
8133
8516
  if (!v.ok) {
8134
8517
  return { contents: [{ uri: uri.href, mimeType: "text/plain", text: JSON.stringify(v) }] };
8135
8518
  }
8136
- const path = join29(v.data.vault, ".skillwiki", "graph.json");
8519
+ const path = join30(v.data.vault, ".skillwiki", "graph.json");
8137
8520
  try {
8138
8521
  const raw = await readFile20(path, "utf8");
8139
8522
  const graph = JSON.parse(raw);
@@ -8482,6 +8865,7 @@ export {
8482
8865
  runIndexLinkFormat,
8483
8866
  runDedup,
8484
8867
  safeWritePage,
8868
+ runFrontmatterFix,
8485
8869
  fixPathTooLong,
8486
8870
  assessSourceIdentity,
8487
8871
  runLint,
@@ -8492,8 +8876,16 @@ export {
8492
8876
  runConfigPath,
8493
8877
  writeCache,
8494
8878
  triggerAutoUpdate,
8879
+ FLEET_REL_PATH,
8495
8880
  runFleetValidate,
8496
8881
  runFleetContext,
8882
+ loadFleetManifest,
8883
+ resolveFleetHostId,
8884
+ SATELLITE_STALE_MS,
8885
+ satelliteLatestRunPath,
8886
+ isFailedRunStatus,
8887
+ readSatelliteLatestRunFromText,
8888
+ evaluateSatelliteRunHealth,
8497
8889
  runDoctor,
8498
8890
  readCliPackageJson,
8499
8891
  runProjectIndex,