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.
- package/dist/{chunk-T7XG3WFK.js → chunk-6G7RAAOS.js} +550 -158
- package/dist/cli.js +375 -83
- package/dist/skillwiki-mcp.js +1 -1
- package/package.json +1 -1
- package/skills/.claude-plugin/plugin.json +1 -1
- package/skills/.codex-plugin/plugin.json +1 -1
- package/skills/package.json +1 -1
- package/skills/proj-distill/SKILL.md +1 -0
- package/skills/proj-work/SKILL.md +1 -0
- package/skills/skills/proj-distill/SKILL.md +1 -0
- package/skills/skills/proj-work/SKILL.md +1 -0
- package/skills/skills/using-skillwiki/SKILL.md +9 -0
- package/skills/skills/wiki-crystallize/SKILL.md +2 -0
- package/skills/skills/wiki-ingest/SKILL.md +2 -0
- package/skills/using-skillwiki/SKILL.md +9 -0
- package/skills/wiki-crystallize/SKILL.md +2 -0
- package/skills/wiki-ingest/SKILL.md +2 -0
|
@@ -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.
|
|
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
|
-
|
|
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") &&
|
|
3741
|
+
if (shouldFix("file_source_url") && fileSourceUrlFrontmatterFlags.size > 0) {
|
|
3609
3742
|
const FILE_FIXED = [];
|
|
3610
|
-
for (const relPath of
|
|
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
|
|
3642
|
-
const
|
|
3643
|
-
|
|
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
|
|
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
|
|
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
|
|
4246
|
-
import { join as
|
|
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
|
|
4372
|
-
import { join as
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
4631
|
-
if (
|
|
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 =
|
|
4636
|
-
if (
|
|
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 (!
|
|
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 (
|
|
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 (!
|
|
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 (!
|
|
5027
|
+
if (!existsSync10(join22(resolvedPath, "SCHEMA.md"))) missing.push("SCHEMA.md");
|
|
4726
5028
|
for (const dir of ["raw", "entities", "concepts", "meta"]) {
|
|
4727
|
-
if (!
|
|
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 ?
|
|
4736
|
-
if (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 =
|
|
4750
|
-
if (
|
|
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 =
|
|
5062
|
+
const skillsDir = join22(home, ".claude", "skills");
|
|
4761
5063
|
const agentSkillDirs = [
|
|
4762
|
-
{ label: "~/.codex/skills/", path:
|
|
4763
|
-
{ label: "~/.agents/skills/", path:
|
|
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 =
|
|
4867
|
-
if (
|
|
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 (!
|
|
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 (!
|
|
4900
|
-
if (!
|
|
4901
|
-
if (!
|
|
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 =
|
|
4912
|
-
if (!
|
|
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(
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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 (
|
|
5112
|
-
return check("pass", "fleet_identity", "Fleet identity", `Resolved ${
|
|
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 =
|
|
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
|
-
|
|
5120
|
-
|
|
5490
|
+
join22(home, "Library", "Logs", "wiki-pull.log"),
|
|
5491
|
+
join22(home, ".local", "state", "vault-sync", "log", "wiki-pull.log")
|
|
5121
5492
|
] : [
|
|
5122
|
-
|
|
5123
|
-
|
|
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) =>
|
|
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 =
|
|
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 =
|
|
5164
|
-
if (!
|
|
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 =
|
|
5347
|
-
if (!
|
|
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 =
|
|
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 =
|
|
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
|
|
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 ?
|
|
5502
|
-
const shareDir = input.shareDir ?? (isMac ?
|
|
5503
|
-
const filterPath = input.filterPath ??
|
|
5504
|
-
const packagedSnapshotPath =
|
|
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 ?? (
|
|
5877
|
+
const snapshotPath = input.snapshotScriptPath ?? (existsSync10(packagedSnapshotPath) ? packagedSnapshotPath : legacySnapshotPath);
|
|
5507
5878
|
function snapshotLastStatusCheck() {
|
|
5508
|
-
const snapshotLog =
|
|
5879
|
+
const snapshotLog = join22(logDir, "wiki-snapshot.log");
|
|
5509
5880
|
try {
|
|
5510
|
-
const logContent =
|
|
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 =
|
|
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 =
|
|
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" &&
|
|
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" &&
|
|
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 (!
|
|
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 =
|
|
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 =
|
|
5630
|
-
const c1 =
|
|
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 =
|
|
6052
|
+
const logFile = join22(logDir, "wiki-push.log");
|
|
5682
6053
|
let c3;
|
|
5683
6054
|
try {
|
|
5684
|
-
const logContent =
|
|
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 =
|
|
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 =
|
|
6123
|
+
const fetchLogFile = join22(logDir, "wiki-fetch.log");
|
|
5753
6124
|
let cFetch;
|
|
5754
6125
|
try {
|
|
5755
|
-
const logContent =
|
|
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 (!
|
|
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 =
|
|
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 (!
|
|
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 =
|
|
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(
|
|
6267
|
+
results.push(join22(dir, entry.name));
|
|
5897
6268
|
} else if (entry.isDirectory()) {
|
|
5898
|
-
results.push(...findSkillMd(
|
|
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) =>
|
|
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() &&
|
|
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 =
|
|
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
|
|
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(
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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(
|
|
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 =
|
|
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 =
|
|
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
|
|
6212
|
-
import { join as
|
|
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 (!
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
6360
|
-
await mkdir7(
|
|
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(
|
|
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 =
|
|
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 =
|
|
7029
|
-
await mkdir7(
|
|
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(
|
|
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(
|
|
7600
|
+
const projectCache = await readIfExists2(join25(vault, memoryCacheRelPath(project)));
|
|
7218
7601
|
if (projectCache) return projectCache;
|
|
7219
7602
|
}
|
|
7220
|
-
return readIfExists2(
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
7908
|
-
import { existsSync as
|
|
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 ??
|
|
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 (!
|
|
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(
|
|
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 =
|
|
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,
|