skillwiki 0.9.51 → 0.9.55
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-O3WK7GBV.js → chunk-4BT3HY4K.js} +525 -136
- package/dist/cli.js +46 -5
- 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/templates/project-README.md +2 -2
|
@@ -1823,9 +1823,9 @@ async function runStale(input) {
|
|
|
1823
1823
|
if (input.project && !project.includes(input.project)) return null;
|
|
1824
1824
|
let inferred = false;
|
|
1825
1825
|
if (input.forceScan && !kind) {
|
|
1826
|
-
const
|
|
1827
|
-
if (!LOOP_CYCLE_PATTERN.test(
|
|
1828
|
-
const m =
|
|
1826
|
+
const basename4 = t.relPath.split("/").pop();
|
|
1827
|
+
if (!LOOP_CYCLE_PATTERN.test(basename4)) {
|
|
1828
|
+
const m = basename4.match(KIND_FROM_FILENAME);
|
|
1829
1829
|
if (m) {
|
|
1830
1830
|
kind = m[1];
|
|
1831
1831
|
inferred = true;
|
|
@@ -2852,7 +2852,7 @@ function buildCliSurface() {
|
|
|
2852
2852
|
program.command("lint").option("--days <n>").option("--lines <n>").option("--log-threshold <n>").option("--fix").option("--only <bucket>").option("--summary").option("--examples <n>").option("--wiki <name>");
|
|
2853
2853
|
program.command("config");
|
|
2854
2854
|
program.command("health").option("--wiki <name>").option("--sync <mode>").option("--no-fail").option("--out <path>").option("--examples <n>");
|
|
2855
|
-
program.command("doctor");
|
|
2855
|
+
program.command("doctor").option("--check-snapshotter");
|
|
2856
2856
|
program.command("status").option("--wiki <name>");
|
|
2857
2857
|
program.command("archive").option("--wiki <name>").option("--cascade").option("--apply").option("--remote <remote>").option("--remote-delete").option("--max-remote-deletes <n>");
|
|
2858
2858
|
program.command("drift").option("--apply").option("--new <date>").option("--wiki <name>");
|
|
@@ -2887,7 +2887,7 @@ function buildCliSurface() {
|
|
|
2887
2887
|
compoundCmd.command("list").requiredOption("--project <slug>").option("--wiki <name>");
|
|
2888
2888
|
compoundCmd.command("delete").requiredOption("--project <slug>").option("--wiki <name>");
|
|
2889
2889
|
const syncCmd = program.commands.find((c) => c.name() === "sync");
|
|
2890
|
-
syncCmd.command("status").option("--wiki <name>").option("--include-stashes");
|
|
2890
|
+
syncCmd.command("status").option("--wiki <name>").option("--include-stashes").option("--include-remote-health").option("--check-snapshotter");
|
|
2891
2891
|
syncCmd.command("push").option("--wiki <name>");
|
|
2892
2892
|
syncCmd.command("pull").option("--wiki <name>");
|
|
2893
2893
|
syncCmd.command("lock").option("--summary <text>").option("--ttl-minutes <n>").option("--force").option("--wiki <name>");
|
|
@@ -3100,7 +3100,7 @@ function extractSourceEntries(rawFm) {
|
|
|
3100
3100
|
}
|
|
3101
3101
|
return entries;
|
|
3102
3102
|
}
|
|
3103
|
-
var ERROR_ORDER = ["sensitive_content", "broken_wikilinks", "invalid_frontmatter", "raw_source_identity_conflict", "raw_dedup", "broken_sources", "tag_not_in_taxonomy", "path_too_long"];
|
|
3103
|
+
var ERROR_ORDER = ["sensitive_content", "conflict_markers", "broken_wikilinks", "invalid_frontmatter", "raw_source_identity_conflict", "raw_dedup", "broken_sources", "tag_not_in_taxonomy", "path_too_long"];
|
|
3104
3104
|
var WARNING_ORDER = ["raw_body_duplicate", "raw_subdirectory_duplicate", "file_source_url", "index_incomplete", "index_link_format", "stale_page", "page_too_large", "log_rotate_needed", "orphans", "compound_refs", "legacy_citation_style", "orphaned_citations", "duplicate_frontmatter", "frontmatter_yaml_invalid", "work_item_health", "orphaned_project_pages", "missing_overview", "missing_diagram"];
|
|
3105
3105
|
var INFO_ORDER = ["bridges", "sparse_community", "page_structure", "topic_map_recommended", "frontmatter_wikilink", "wikilink_citation", "missing_tldr", "stale_sections", "cli_refs"];
|
|
3106
3106
|
var KNOWN_BUCKETS = [...ERROR_ORDER, ...WARNING_ORDER, ...INFO_ORDER];
|
|
@@ -3448,6 +3448,38 @@ async function runFileSourceUrlOnly(input) {
|
|
|
3448
3448
|
const match = remaining.size > 0 ? [{ kind: "file_source_url", items: [...remaining] }] : [];
|
|
3449
3449
|
return outputForOnlyBucket(input, match, fixed, unresolved, readVault);
|
|
3450
3450
|
}
|
|
3451
|
+
function scanConflictMarkerBlocks(path, text) {
|
|
3452
|
+
const findings = [];
|
|
3453
|
+
const lines = text.split(/\r?\n/);
|
|
3454
|
+
let inFence = false;
|
|
3455
|
+
let openLine = 0;
|
|
3456
|
+
let sawSeparator = false;
|
|
3457
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
3458
|
+
const line = lines[i];
|
|
3459
|
+
if (line.startsWith("```") || line.startsWith("~~~")) {
|
|
3460
|
+
inFence = !inFence;
|
|
3461
|
+
continue;
|
|
3462
|
+
}
|
|
3463
|
+
if (inFence) continue;
|
|
3464
|
+
if (line.startsWith("<<<<<<< ")) {
|
|
3465
|
+
openLine = i + 1;
|
|
3466
|
+
sawSeparator = false;
|
|
3467
|
+
continue;
|
|
3468
|
+
}
|
|
3469
|
+
if (line === "=======" && openLine > 0) {
|
|
3470
|
+
sawSeparator = true;
|
|
3471
|
+
continue;
|
|
3472
|
+
}
|
|
3473
|
+
if (line.startsWith(">>>>>>> ")) {
|
|
3474
|
+
if (openLine > 0 && sawSeparator) {
|
|
3475
|
+
findings.push({ path, line: openLine, message: "complete Git conflict-marker block" });
|
|
3476
|
+
}
|
|
3477
|
+
openLine = 0;
|
|
3478
|
+
sawSeparator = false;
|
|
3479
|
+
}
|
|
3480
|
+
}
|
|
3481
|
+
return findings;
|
|
3482
|
+
}
|
|
3451
3483
|
async function runLint(input) {
|
|
3452
3484
|
if (input.only && !KNOWN_BUCKETS.includes(input.only)) {
|
|
3453
3485
|
return {
|
|
@@ -3545,10 +3577,12 @@ async function runLint(input) {
|
|
|
3545
3577
|
{
|
|
3546
3578
|
const allPageResults = await mapWithConcurrency(scan.allMarkdown, vaultIoConcurrency(), async (page) => {
|
|
3547
3579
|
const sensitiveFlags2 = [];
|
|
3580
|
+
const conflictMarkers2 = [];
|
|
3548
3581
|
let fmYamlInvalid2 = null;
|
|
3549
3582
|
try {
|
|
3550
3583
|
const text = await readPageCached(page, pageTextCache);
|
|
3551
3584
|
sensitiveFlags2.push(...scanSensitiveContent(text, { file: page.relPath }));
|
|
3585
|
+
conflictMarkers2.push(...scanConflictMarkerBlocks(page.relPath, text));
|
|
3552
3586
|
const fm = extractFrontmatter(text);
|
|
3553
3587
|
if (!fm.ok && fm.error === "INVALID_FRONTMATTER") {
|
|
3554
3588
|
const detail = fm.detail;
|
|
@@ -3557,10 +3591,12 @@ async function runLint(input) {
|
|
|
3557
3591
|
}
|
|
3558
3592
|
} catch {
|
|
3559
3593
|
}
|
|
3560
|
-
return { sensitiveFlags: sensitiveFlags2, fmYamlInvalid: fmYamlInvalid2 };
|
|
3594
|
+
return { sensitiveFlags: sensitiveFlags2, conflictMarkers: conflictMarkers2, fmYamlInvalid: fmYamlInvalid2 };
|
|
3561
3595
|
});
|
|
3562
3596
|
const sensitiveFlags = allPageResults.flatMap((result) => result.sensitiveFlags);
|
|
3563
3597
|
if (sensitiveFlags.length > 0) buckets.sensitive_content = sensitiveFlags;
|
|
3598
|
+
const conflictMarkers = allPageResults.flatMap((result) => result.conflictMarkers);
|
|
3599
|
+
if (conflictMarkers.length > 0) buckets.conflict_markers = conflictMarkers;
|
|
3564
3600
|
const fmYamlInvalid = allPageResults.map((result) => result.fmYamlInvalid).filter((item) => item !== null);
|
|
3565
3601
|
if (fmYamlInvalid.length > 0) buckets.frontmatter_yaml_invalid = fmYamlInvalid;
|
|
3566
3602
|
const subDirDupes = [];
|
|
@@ -4538,6 +4574,15 @@ async function loadFleetManifestAndHost(input) {
|
|
|
4538
4574
|
identityStatus: "known"
|
|
4539
4575
|
};
|
|
4540
4576
|
}
|
|
4577
|
+
function snapshotterAliasForLocalHost(fleetLoad) {
|
|
4578
|
+
if (!fleetLoad?.manifest || !fleetLoad.hostId) return void 0;
|
|
4579
|
+
const snapshotterId = Object.entries(fleetLoad.manifest.hosts).find(([, h]) => h.role === "snapshotter")?.[0];
|
|
4580
|
+
if (!snapshotterId) return void 0;
|
|
4581
|
+
const profile = fleetLoad.manifest.hosts[snapshotterId]?.access?.from?.[fleetLoad.hostId];
|
|
4582
|
+
if (!profile || profile.status !== "configured" && profile.status !== "local") return void 0;
|
|
4583
|
+
const aliases = profile.ssh_aliases ?? [];
|
|
4584
|
+
return aliases.length > 0 ? aliases[0] : void 0;
|
|
4585
|
+
}
|
|
4541
4586
|
function satelliteGateFromFleetLoad(load) {
|
|
4542
4587
|
if (!load?.hostId) return { satelliteExpected: false };
|
|
4543
4588
|
const host = load.manifest.hosts[load.hostId];
|
|
@@ -4794,8 +4839,8 @@ function safeUserName() {
|
|
|
4794
4839
|
}
|
|
4795
4840
|
|
|
4796
4841
|
// src/commands/doctor.ts
|
|
4797
|
-
import { existsSync as
|
|
4798
|
-
import { join as
|
|
4842
|
+
import { existsSync as existsSync13, lstatSync, readlinkSync, readdirSync as readdirSync3, statSync, readFileSync as readFileSync10 } from "fs";
|
|
4843
|
+
import { join as join24, resolve as resolve5 } from "path";
|
|
4799
4844
|
import { execSync as execSync2 } from "child_process";
|
|
4800
4845
|
import { platform as platform2 } from "os";
|
|
4801
4846
|
|
|
@@ -4917,12 +4962,180 @@ function parseTomlScalar(rawValue) {
|
|
|
4917
4962
|
return value;
|
|
4918
4963
|
}
|
|
4919
4964
|
|
|
4920
|
-
// src/utils/
|
|
4921
|
-
import { existsSync as existsSync9, readFileSync as readFileSync6 } from "fs";
|
|
4965
|
+
// src/utils/conflict-markers.ts
|
|
4966
|
+
import { existsSync as existsSync9, readdirSync as readdirSync2, readFileSync as readFileSync6 } from "fs";
|
|
4922
4967
|
import { join as join20 } from "path";
|
|
4968
|
+
function scanConflictMarkerBlocksInText(relPath, text) {
|
|
4969
|
+
const findings = [];
|
|
4970
|
+
const lines = text.split(/\r?\n/);
|
|
4971
|
+
let inFence = false;
|
|
4972
|
+
let openLine = 0;
|
|
4973
|
+
let sawSeparator = false;
|
|
4974
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
4975
|
+
const line = lines[i];
|
|
4976
|
+
if (line.startsWith("```") || line.startsWith("~~~")) {
|
|
4977
|
+
inFence = !inFence;
|
|
4978
|
+
continue;
|
|
4979
|
+
}
|
|
4980
|
+
if (inFence) continue;
|
|
4981
|
+
if (line.startsWith("<<<<<<< ")) {
|
|
4982
|
+
openLine = i + 1;
|
|
4983
|
+
sawSeparator = false;
|
|
4984
|
+
continue;
|
|
4985
|
+
}
|
|
4986
|
+
if (line === "=======" && openLine > 0) {
|
|
4987
|
+
sawSeparator = true;
|
|
4988
|
+
continue;
|
|
4989
|
+
}
|
|
4990
|
+
if (line.startsWith(">>>>>>> ")) {
|
|
4991
|
+
if (openLine > 0 && sawSeparator) {
|
|
4992
|
+
findings.push({ path: relPath, line: openLine });
|
|
4993
|
+
}
|
|
4994
|
+
openLine = 0;
|
|
4995
|
+
sawSeparator = false;
|
|
4996
|
+
}
|
|
4997
|
+
}
|
|
4998
|
+
return findings;
|
|
4999
|
+
}
|
|
5000
|
+
var PRUNE_DIRS = /* @__PURE__ */ new Set([
|
|
5001
|
+
".git",
|
|
5002
|
+
".obsidian",
|
|
5003
|
+
".skillwiki",
|
|
5004
|
+
".claude",
|
|
5005
|
+
".antigravitycli",
|
|
5006
|
+
".playwright-cli"
|
|
5007
|
+
]);
|
|
5008
|
+
function walkMarkdownFiles2(root, dir, rel, out) {
|
|
5009
|
+
let entries;
|
|
5010
|
+
try {
|
|
5011
|
+
entries = readdirSync2(dir, { withFileTypes: true });
|
|
5012
|
+
} catch {
|
|
5013
|
+
return;
|
|
5014
|
+
}
|
|
5015
|
+
for (const entry of entries) {
|
|
5016
|
+
if (entry.isDirectory()) {
|
|
5017
|
+
if (PRUNE_DIRS.has(entry.name)) continue;
|
|
5018
|
+
walkMarkdownFiles2(root, join20(dir, entry.name), rel ? `${rel}/${entry.name}` : entry.name, out);
|
|
5019
|
+
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
5020
|
+
out.push(rel ? `${rel}/${entry.name}` : entry.name);
|
|
5021
|
+
}
|
|
5022
|
+
}
|
|
5023
|
+
}
|
|
5024
|
+
function scanVaultConflictMarkers(vaultRoot) {
|
|
5025
|
+
if (!existsSync9(vaultRoot)) return [];
|
|
5026
|
+
const relPaths = [];
|
|
5027
|
+
walkMarkdownFiles2(vaultRoot, vaultRoot, "", relPaths);
|
|
5028
|
+
const all = [];
|
|
5029
|
+
for (const rel of relPaths) {
|
|
5030
|
+
let text;
|
|
5031
|
+
try {
|
|
5032
|
+
text = readFileSync6(join20(vaultRoot, rel), "utf8");
|
|
5033
|
+
} catch {
|
|
5034
|
+
continue;
|
|
5035
|
+
}
|
|
5036
|
+
all.push(...scanConflictMarkerBlocksInText(rel, text));
|
|
5037
|
+
}
|
|
5038
|
+
return all;
|
|
5039
|
+
}
|
|
5040
|
+
|
|
5041
|
+
// src/utils/remote-health.ts
|
|
5042
|
+
import { existsSync as existsSync10, readFileSync as readFileSync7 } from "fs";
|
|
5043
|
+
import { join as join21 } from "path";
|
|
5044
|
+
import { execFileSync } from "child_process";
|
|
5045
|
+
var REMOTE_PROBE_TIMEOUT_MS = 3e3;
|
|
5046
|
+
var defaultExec = (file, args, cwd) => execFileSync(file, args, {
|
|
5047
|
+
cwd,
|
|
5048
|
+
encoding: "utf8",
|
|
5049
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
5050
|
+
timeout: REMOTE_PROBE_TIMEOUT_MS
|
|
5051
|
+
}).trim();
|
|
5052
|
+
var DEFAULT_WIKI_S3_REMOTE = "seaweed-wiki:cloud/wiki";
|
|
5053
|
+
function readWikiS3RemoteConfigured(home) {
|
|
5054
|
+
try {
|
|
5055
|
+
const content = readFileSync7(join21(home, ".skillwiki", ".env"), "utf8");
|
|
5056
|
+
for (const line of content.split(/\r?\n/)) {
|
|
5057
|
+
const trimmed = line.trim();
|
|
5058
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
5059
|
+
const eq = trimmed.indexOf("=");
|
|
5060
|
+
if (eq <= 0) continue;
|
|
5061
|
+
const k = trimmed.slice(0, eq).trim();
|
|
5062
|
+
const v = trimmed.slice(eq + 1).trim();
|
|
5063
|
+
if (k === "WIKI_REMOTE" && v.length > 0) return v;
|
|
5064
|
+
}
|
|
5065
|
+
} catch {
|
|
5066
|
+
}
|
|
5067
|
+
return void 0;
|
|
5068
|
+
}
|
|
5069
|
+
function readWikiS3RemoteFromEnv(home) {
|
|
5070
|
+
return readWikiS3RemoteConfigured(home) ?? DEFAULT_WIKI_S3_REMOTE;
|
|
5071
|
+
}
|
|
5072
|
+
function probeGithubReachability(vaultPath, exec = defaultExec) {
|
|
5073
|
+
if (!existsSync10(join21(vaultPath, ".git"))) return "unknown";
|
|
5074
|
+
try {
|
|
5075
|
+
exec("git", ["remote", "get-url", "origin"], vaultPath);
|
|
5076
|
+
} catch {
|
|
5077
|
+
return "unknown";
|
|
5078
|
+
}
|
|
5079
|
+
try {
|
|
5080
|
+
const out = exec("git", ["ls-remote", "origin", "refs/heads/main"], vaultPath);
|
|
5081
|
+
if (out.length > 0) return "ok";
|
|
5082
|
+
return "unreachable";
|
|
5083
|
+
} catch {
|
|
5084
|
+
return "unreachable";
|
|
5085
|
+
}
|
|
5086
|
+
}
|
|
5087
|
+
function probeS3Reachability(remote, exec = defaultExec) {
|
|
5088
|
+
if (!remote) return "unknown";
|
|
5089
|
+
try {
|
|
5090
|
+
exec("rclone", ["lsf", remote, "--max-depth", "1", "--files-only"]);
|
|
5091
|
+
return "ok";
|
|
5092
|
+
} catch {
|
|
5093
|
+
return "unreachable";
|
|
5094
|
+
}
|
|
5095
|
+
}
|
|
5096
|
+
function probeSnapshotterSsh(sshAlias2, exec = defaultExec) {
|
|
5097
|
+
if (!sshAlias2) return "unknown";
|
|
5098
|
+
try {
|
|
5099
|
+
exec("ssh", [
|
|
5100
|
+
"-o",
|
|
5101
|
+
"BatchMode=yes",
|
|
5102
|
+
"-o",
|
|
5103
|
+
"ConnectTimeout=3",
|
|
5104
|
+
"-o",
|
|
5105
|
+
"StrictHostKeyChecking=accept-new",
|
|
5106
|
+
sshAlias2,
|
|
5107
|
+
"true"
|
|
5108
|
+
]);
|
|
5109
|
+
return "ok";
|
|
5110
|
+
} catch {
|
|
5111
|
+
return "unreachable";
|
|
5112
|
+
}
|
|
5113
|
+
}
|
|
5114
|
+
function buildDegradedReasons(health) {
|
|
5115
|
+
const reasons = [];
|
|
5116
|
+
if (health.github === "unreachable") reasons.push("github_remote_unreachable");
|
|
5117
|
+
if (health.s3 === "unreachable") reasons.push("s3_remote_unreachable");
|
|
5118
|
+
if (health.snapshotter === "unreachable") reasons.push("snapshotter_host_unreachable");
|
|
5119
|
+
return reasons;
|
|
5120
|
+
}
|
|
5121
|
+
function probeRemoteHealth(input) {
|
|
5122
|
+
const exec = input.exec ?? defaultExec;
|
|
5123
|
+
const github = probeGithubReachability(input.vaultPath, exec);
|
|
5124
|
+
const s3Remote = input.s3Remote ?? readWikiS3RemoteFromEnv(input.home);
|
|
5125
|
+
const s3 = probeS3Reachability(s3Remote, exec);
|
|
5126
|
+
let snapshotter = "not_checked";
|
|
5127
|
+
if (input.checkSnapshotter && input.snapshotterAlias) {
|
|
5128
|
+
snapshotter = probeSnapshotterSsh(input.snapshotterAlias, exec);
|
|
5129
|
+
}
|
|
5130
|
+
return { github, s3, snapshotter };
|
|
5131
|
+
}
|
|
5132
|
+
|
|
5133
|
+
// src/utils/satellite-run-health.ts
|
|
5134
|
+
import { existsSync as existsSync11, readFileSync as readFileSync8 } from "fs";
|
|
5135
|
+
import { join as join22 } from "path";
|
|
4923
5136
|
var SATELLITE_STALE_MS = 26 * 60 * 60 * 1e3;
|
|
4924
5137
|
function satelliteLatestRunPath(vault) {
|
|
4925
|
-
return
|
|
5138
|
+
return join22(vault, ".skillwiki", "agent-memory-trends", "latest-run.json");
|
|
4926
5139
|
}
|
|
4927
5140
|
function isFailedRunStatus(status) {
|
|
4928
5141
|
return status === "fail" || status === "failure";
|
|
@@ -4944,9 +5157,9 @@ function readSatelliteLatestRunFromText(text) {
|
|
|
4944
5157
|
}
|
|
4945
5158
|
function readSatelliteLatestRun(vault) {
|
|
4946
5159
|
const latestPath = satelliteLatestRunPath(vault);
|
|
4947
|
-
if (!
|
|
5160
|
+
if (!existsSync11(latestPath)) return null;
|
|
4948
5161
|
try {
|
|
4949
|
-
return parseLatestRunFile(
|
|
5162
|
+
return parseLatestRunFile(readFileSync8(latestPath, "utf8"));
|
|
4950
5163
|
} catch {
|
|
4951
5164
|
return null;
|
|
4952
5165
|
}
|
|
@@ -4975,8 +5188,8 @@ function evaluateSatelliteRunHealth(vault, now) {
|
|
|
4975
5188
|
// src/utils/s3-mount-health.ts
|
|
4976
5189
|
import { execSync } from "child_process";
|
|
4977
5190
|
import { platform } from "os";
|
|
4978
|
-
import { readFileSync as
|
|
4979
|
-
import { join as
|
|
5191
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync4, unlinkSync as unlinkSync3, readFileSync as readFile15 } from "fs";
|
|
5192
|
+
import { join as join23 } from "path";
|
|
4980
5193
|
var OS = platform();
|
|
4981
5194
|
function findRcloneMountPid() {
|
|
4982
5195
|
try {
|
|
@@ -5060,7 +5273,7 @@ function extractRcloneFs(args) {
|
|
|
5060
5273
|
function getRcloneArgs(pid) {
|
|
5061
5274
|
try {
|
|
5062
5275
|
if (OS === "linux") {
|
|
5063
|
-
const raw =
|
|
5276
|
+
const raw = readFileSync9(`/proc/${pid}/cmdline`);
|
|
5064
5277
|
return new TextDecoder().decode(raw).split("\0").filter(Boolean);
|
|
5065
5278
|
} else {
|
|
5066
5279
|
const out = execSync(`ps -o args= -p ${pid}`, {
|
|
@@ -5103,7 +5316,7 @@ function queryRcloneRC(rcAddr, fs) {
|
|
|
5103
5316
|
function detectFuseMount(vaultPath) {
|
|
5104
5317
|
try {
|
|
5105
5318
|
if (OS === "linux") {
|
|
5106
|
-
const mounts =
|
|
5319
|
+
const mounts = readFileSync9("/proc/mounts", "utf8");
|
|
5107
5320
|
let best = null;
|
|
5108
5321
|
for (const line of mounts.split("\n")) {
|
|
5109
5322
|
const parts = line.split(" ");
|
|
@@ -5134,7 +5347,7 @@ function detectFuseMount(vaultPath) {
|
|
|
5134
5347
|
return null;
|
|
5135
5348
|
}
|
|
5136
5349
|
function writeTest(dir) {
|
|
5137
|
-
const testFile =
|
|
5350
|
+
const testFile = join23(dir, `.doctor-write-test-${process.pid}.tmp`);
|
|
5138
5351
|
const payload = `skillwiki doctor write test \u2014 ${Date.now()} \u2014 ${Math.random().toString(36).slice(2)}`;
|
|
5139
5352
|
const start = Date.now();
|
|
5140
5353
|
try {
|
|
@@ -5234,13 +5447,13 @@ function detectCliChannels(argv, home) {
|
|
|
5234
5447
|
}
|
|
5235
5448
|
const plugin = findPlugin(home);
|
|
5236
5449
|
if (plugin) {
|
|
5237
|
-
const pluginBin =
|
|
5238
|
-
if (
|
|
5450
|
+
const pluginBin = join24(plugin.installPath, "bin", "skillwiki");
|
|
5451
|
+
if (existsSync13(pluginBin)) {
|
|
5239
5452
|
channels.push({ name: "plugin", path: pluginBin, isDevLink: false });
|
|
5240
5453
|
}
|
|
5241
5454
|
}
|
|
5242
|
-
const installBin =
|
|
5243
|
-
if (
|
|
5455
|
+
const installBin = join24(home, ".claude", "skills", "bin", "skillwiki");
|
|
5456
|
+
if (existsSync13(installBin)) {
|
|
5244
5457
|
channels.push({ name: "install", path: installBin, isDevLink: false });
|
|
5245
5458
|
}
|
|
5246
5459
|
return channels;
|
|
@@ -5301,7 +5514,7 @@ function isDevSourceRun(argv) {
|
|
|
5301
5514
|
}
|
|
5302
5515
|
async function checkConfigFile(home) {
|
|
5303
5516
|
const cfgPath = configPath(home);
|
|
5304
|
-
if (!
|
|
5517
|
+
if (!existsSync13(cfgPath)) {
|
|
5305
5518
|
return check("warn", "config_file", "Config file exists", `${cfgPath} not found`);
|
|
5306
5519
|
}
|
|
5307
5520
|
try {
|
|
@@ -5316,7 +5529,7 @@ function checkWikiPathExists(resolvedPath) {
|
|
|
5316
5529
|
if (resolvedPath === void 0) {
|
|
5317
5530
|
return check("error", "wiki_path_exists", "Vault directory exists", "Cannot check \u2014 WIKI_PATH not resolved");
|
|
5318
5531
|
}
|
|
5319
|
-
if (
|
|
5532
|
+
if (existsSync13(resolvedPath) && statSync(resolvedPath).isDirectory()) {
|
|
5320
5533
|
return check("pass", "wiki_path_exists", "Vault directory exists", resolvedPath);
|
|
5321
5534
|
}
|
|
5322
5535
|
return check("error", "wiki_path_exists", "Vault directory exists", `${resolvedPath} does not exist or is not a directory`);
|
|
@@ -5325,13 +5538,13 @@ function checkVaultStructure(resolvedPath) {
|
|
|
5325
5538
|
if (resolvedPath === void 0) {
|
|
5326
5539
|
return check("error", "vault_structure", "Vault structure valid", "Cannot check \u2014 WIKI_PATH not resolved");
|
|
5327
5540
|
}
|
|
5328
|
-
if (!
|
|
5541
|
+
if (!existsSync13(resolvedPath)) {
|
|
5329
5542
|
return check("error", "vault_structure", "Vault structure valid", "Cannot check \u2014 vault directory does not exist");
|
|
5330
5543
|
}
|
|
5331
5544
|
const missing = [];
|
|
5332
|
-
if (!
|
|
5545
|
+
if (!existsSync13(join24(resolvedPath, "SCHEMA.md"))) missing.push("SCHEMA.md");
|
|
5333
5546
|
for (const dir of ["raw", "entities", "concepts", "meta"]) {
|
|
5334
|
-
if (!
|
|
5547
|
+
if (!existsSync13(join24(resolvedPath, dir))) missing.push(dir + "/");
|
|
5335
5548
|
}
|
|
5336
5549
|
if (missing.length === 0) {
|
|
5337
5550
|
return check("pass", "vault_structure", "Vault structure valid", "All required files and directories present");
|
|
@@ -5339,8 +5552,8 @@ function checkVaultStructure(resolvedPath) {
|
|
|
5339
5552
|
return check("warn", "vault_structure", "Vault structure valid", `Missing: ${missing.join(", ")} \u2014 run \`skillwiki init\` to add CodeWiki structure`);
|
|
5340
5553
|
}
|
|
5341
5554
|
function checkSkillsInstalled(home, cwd) {
|
|
5342
|
-
const srcDir = cwd ?
|
|
5343
|
-
if (srcDir &&
|
|
5555
|
+
const srcDir = cwd ? join24(cwd, "packages", "skills") : void 0;
|
|
5556
|
+
if (srcDir && existsSync13(srcDir)) {
|
|
5344
5557
|
const found = findInstalledSkillMd(srcDir);
|
|
5345
5558
|
if (found.length > 0) {
|
|
5346
5559
|
return check("pass", "skills_installed", "Skills installed", `${found.length} SKILL.md file(s) found (source)`);
|
|
@@ -5353,8 +5566,8 @@ function checkSkillsInstalled(home, cwd) {
|
|
|
5353
5566
|
return check("pass", "skills_installed", "Skills installed", `${found.length} SKILL.md file(s) found (plugin v${plugin.version})`);
|
|
5354
5567
|
}
|
|
5355
5568
|
}
|
|
5356
|
-
const skillsDir =
|
|
5357
|
-
if (
|
|
5569
|
+
const skillsDir = join24(home, ".claude", "skills");
|
|
5570
|
+
if (existsSync13(skillsDir)) {
|
|
5358
5571
|
const found = findInstalledSkillMd(skillsDir);
|
|
5359
5572
|
if (found.length > 0) {
|
|
5360
5573
|
return check("pass", "skills_installed", "Skills installed", `${found.length} SKILL.md file(s) found (CLI install)`);
|
|
@@ -5364,10 +5577,10 @@ function checkSkillsInstalled(home, cwd) {
|
|
|
5364
5577
|
}
|
|
5365
5578
|
function checkDuplicateSkills(home) {
|
|
5366
5579
|
const plugin = findPlugin(home);
|
|
5367
|
-
const skillsDir =
|
|
5580
|
+
const skillsDir = join24(home, ".claude", "skills");
|
|
5368
5581
|
const agentSkillDirs = [
|
|
5369
|
-
{ label: "~/.codex/skills/", path:
|
|
5370
|
-
{ label: "~/.agents/skills/", path:
|
|
5582
|
+
{ label: "~/.codex/skills/", path: join24(home, ".codex", "skills") },
|
|
5583
|
+
{ label: "~/.agents/skills/", path: join24(home, ".agents", "skills") }
|
|
5371
5584
|
];
|
|
5372
5585
|
if (!plugin) {
|
|
5373
5586
|
return check("pass", "skills_duplicate", "Skills not duplicated", "Single install channel");
|
|
@@ -5470,8 +5683,8 @@ async function checkProfiles(home) {
|
|
|
5470
5683
|
}
|
|
5471
5684
|
async function checkProjectLocalOverride(cwd) {
|
|
5472
5685
|
const dir = cwd ?? process.cwd();
|
|
5473
|
-
const envPath =
|
|
5474
|
-
if (
|
|
5686
|
+
const envPath = join24(dir, ".skillwiki", ".env");
|
|
5687
|
+
if (existsSync13(envPath)) {
|
|
5475
5688
|
return check("pass", "project_local", "Project-local config", `Found: ${envPath}`);
|
|
5476
5689
|
}
|
|
5477
5690
|
return check("pass", "project_local", "Project-local config", "None");
|
|
@@ -5480,7 +5693,7 @@ function checkVaultGitRemote(resolvedPath) {
|
|
|
5480
5693
|
if (resolvedPath === void 0) {
|
|
5481
5694
|
return check("error", "vault_git_remote", "Vault git remote", "Cannot check \u2014 WIKI_PATH not resolved");
|
|
5482
5695
|
}
|
|
5483
|
-
if (!
|
|
5696
|
+
if (!existsSync13(join24(resolvedPath, ".git"))) {
|
|
5484
5697
|
return check("warn", "vault_git_remote", "Vault git remote", "Vault is not a git repository \u2014 sync features unavailable");
|
|
5485
5698
|
}
|
|
5486
5699
|
try {
|
|
@@ -5503,9 +5716,9 @@ function checkObsidianTemplates(resolvedPath) {
|
|
|
5503
5716
|
return check("error", "obsidian_templates", "Obsidian templates", "Cannot check \u2014 WIKI_PATH not resolved");
|
|
5504
5717
|
}
|
|
5505
5718
|
const missing = [];
|
|
5506
|
-
if (!
|
|
5507
|
-
if (!
|
|
5508
|
-
if (!
|
|
5719
|
+
if (!existsSync13(join24(resolvedPath, "_Templates"))) missing.push("_Templates/");
|
|
5720
|
+
if (!existsSync13(join24(resolvedPath, ".obsidian", "templates.json"))) missing.push(".obsidian/templates.json");
|
|
5721
|
+
if (!existsSync13(join24(resolvedPath, ".obsidian", "app.json"))) missing.push(".obsidian/app.json");
|
|
5509
5722
|
if (missing.length === 0) {
|
|
5510
5723
|
return check("pass", "obsidian_templates", "Obsidian templates", "Template folder and config present");
|
|
5511
5724
|
}
|
|
@@ -5515,15 +5728,15 @@ function checkDotStoreClean(resolvedPath) {
|
|
|
5515
5728
|
if (resolvedPath === void 0) {
|
|
5516
5729
|
return check("error", "dsstore_clean", "No .DS_Store in raw/", "Cannot check \u2014 WIKI_PATH not resolved");
|
|
5517
5730
|
}
|
|
5518
|
-
const rawDir =
|
|
5519
|
-
if (!
|
|
5731
|
+
const rawDir = join24(resolvedPath, "raw");
|
|
5732
|
+
if (!existsSync13(rawDir)) {
|
|
5520
5733
|
return check("pass", "dsstore_clean", "No .DS_Store in raw/", "raw/ directory not found \u2014 check skipped");
|
|
5521
5734
|
}
|
|
5522
5735
|
const found = [];
|
|
5523
5736
|
(function walk2(dir, rel) {
|
|
5524
5737
|
let entries;
|
|
5525
5738
|
try {
|
|
5526
|
-
entries =
|
|
5739
|
+
entries = readdirSync3(dir, { withFileTypes: true });
|
|
5527
5740
|
} catch {
|
|
5528
5741
|
return;
|
|
5529
5742
|
}
|
|
@@ -5531,7 +5744,7 @@ function checkDotStoreClean(resolvedPath) {
|
|
|
5531
5744
|
if (entry.name === ".DS_Store") {
|
|
5532
5745
|
found.push(rel ? `${rel}/.DS_Store` : ".DS_Store");
|
|
5533
5746
|
} else if (entry.isDirectory()) {
|
|
5534
|
-
walk2(
|
|
5747
|
+
walk2(join24(dir, entry.name), rel ? `${rel}/${entry.name}` : entry.name);
|
|
5535
5748
|
}
|
|
5536
5749
|
}
|
|
5537
5750
|
})(rawDir, "");
|
|
@@ -5540,11 +5753,29 @@ function checkDotStoreClean(resolvedPath) {
|
|
|
5540
5753
|
}
|
|
5541
5754
|
return check("info", "dsstore_clean", "No .DS_Store in raw/", `${found.length} .DS_Store file(s) found \u2014 remove with: find ${rawDir} -name .DS_Store -delete`);
|
|
5542
5755
|
}
|
|
5756
|
+
function checkVaultConflictMarkers(resolvedPath) {
|
|
5757
|
+
if (resolvedPath === void 0) {
|
|
5758
|
+
return check("pass", "vault_conflict_markers", "Vault conflict markers", "No vault path \u2014 check skipped");
|
|
5759
|
+
}
|
|
5760
|
+
const findings = scanVaultConflictMarkers(resolvedPath);
|
|
5761
|
+
if (findings.length === 0) {
|
|
5762
|
+
return check("pass", "vault_conflict_markers", "Vault conflict markers", "No complete conflict-marker blocks");
|
|
5763
|
+
}
|
|
5764
|
+
const first = findings[0];
|
|
5765
|
+
const n = findings.length;
|
|
5766
|
+
const fileWord = n === 1 ? "file" : "files";
|
|
5767
|
+
return check(
|
|
5768
|
+
"error",
|
|
5769
|
+
"vault_conflict_markers",
|
|
5770
|
+
"Vault conflict markers",
|
|
5771
|
+
`${n} ${fileWord}, first: ${first.path}:${first.line}`
|
|
5772
|
+
);
|
|
5773
|
+
}
|
|
5543
5774
|
function checkSyncLastPush(resolvedPath) {
|
|
5544
5775
|
if (resolvedPath === void 0) {
|
|
5545
5776
|
return check("error", "sync_last_push", "Vault sync recency", "Cannot check \u2014 WIKI_PATH not resolved");
|
|
5546
5777
|
}
|
|
5547
|
-
if (!
|
|
5778
|
+
if (!existsSync13(join24(resolvedPath, ".git"))) {
|
|
5548
5779
|
return check("pass", "sync_last_push", "Vault sync recency", "No git repo \u2014 sync check skipped");
|
|
5549
5780
|
}
|
|
5550
5781
|
let timestamp;
|
|
@@ -5592,7 +5823,7 @@ function checkVaultGitDirty(resolvedPath) {
|
|
|
5592
5823
|
if (resolvedPath === void 0) {
|
|
5593
5824
|
return check("pass", "vault_git_dirty", "Vault git dirty state", "No vault path \u2014 check skipped");
|
|
5594
5825
|
}
|
|
5595
|
-
if (!
|
|
5826
|
+
if (!existsSync13(join24(resolvedPath, ".git"))) {
|
|
5596
5827
|
return check("pass", "vault_git_dirty", "Vault git dirty state", "No git repo \u2014 check skipped");
|
|
5597
5828
|
}
|
|
5598
5829
|
try {
|
|
@@ -5660,7 +5891,7 @@ function remoteMainHash(resolvedPath) {
|
|
|
5660
5891
|
}
|
|
5661
5892
|
function checkStaleRemoteMain(resolvedPath) {
|
|
5662
5893
|
if (resolvedPath === void 0) return void 0;
|
|
5663
|
-
if (!
|
|
5894
|
+
if (!existsSync13(join24(resolvedPath, ".git"))) return void 0;
|
|
5664
5895
|
const localOrigin = gitRefHash(resolvedPath, "origin/main");
|
|
5665
5896
|
if (!localOrigin) return void 0;
|
|
5666
5897
|
const remoteMain = remoteMainHash(resolvedPath);
|
|
@@ -5672,11 +5903,101 @@ function checkStaleRemoteMain(resolvedPath) {
|
|
|
5672
5903
|
`Remote main differs from local origin/main (${remoteMain.slice(0, 8)} != ${localOrigin.slice(0, 8)}) \u2014 run git fetch before trusting behind count`
|
|
5673
5904
|
);
|
|
5674
5905
|
}
|
|
5906
|
+
function checkVaultLocalGit(resolvedPath) {
|
|
5907
|
+
if (resolvedPath === void 0) {
|
|
5908
|
+
return check("warn", "vault_local_git", "Vault local git", "Cannot check \u2014 WIKI_PATH not resolved");
|
|
5909
|
+
}
|
|
5910
|
+
if (!existsSync13(join24(resolvedPath, ".git"))) {
|
|
5911
|
+
return check("warn", "vault_local_git", "Vault local git", "Not a git repository - sync features unavailable");
|
|
5912
|
+
}
|
|
5913
|
+
try {
|
|
5914
|
+
execSync2("git rev-parse --git-dir", {
|
|
5915
|
+
cwd: resolvedPath,
|
|
5916
|
+
encoding: "utf8",
|
|
5917
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
5918
|
+
timeout: 2e3
|
|
5919
|
+
});
|
|
5920
|
+
return check("pass", "vault_local_git", "Vault local git", "Git metadata readable");
|
|
5921
|
+
} catch {
|
|
5922
|
+
return check("error", "vault_local_git", "Vault local git", "Git metadata unreadable \u2014 local vault may be corrupt");
|
|
5923
|
+
}
|
|
5924
|
+
}
|
|
5925
|
+
function checkVaultGithubRemote(resolvedPath, exec) {
|
|
5926
|
+
if (resolvedPath === void 0) {
|
|
5927
|
+
return check("pass", "vault_github_remote", "Vault GitHub remote", "No vault path \u2014 check skipped");
|
|
5928
|
+
}
|
|
5929
|
+
if (!existsSync13(join24(resolvedPath, ".git"))) {
|
|
5930
|
+
return check("pass", "vault_github_remote", "Vault GitHub remote", "No git repo \u2014 check skipped");
|
|
5931
|
+
}
|
|
5932
|
+
const state = probeGithubReachability(resolvedPath, exec);
|
|
5933
|
+
if (state === "ok") {
|
|
5934
|
+
return check("pass", "vault_github_remote", "Vault GitHub remote", "git ls-remote origin main succeeded");
|
|
5935
|
+
}
|
|
5936
|
+
if (state === "unreachable") {
|
|
5937
|
+
return check("warn", "vault_github_remote", "Vault GitHub remote", "GitHub unreachable (ls-remote failed) \u2014 local vault still usable");
|
|
5938
|
+
}
|
|
5939
|
+
return check("pass", "vault_github_remote", "Vault GitHub remote", "No origin remote \u2014 network probe skipped");
|
|
5940
|
+
}
|
|
5941
|
+
function checkVaultS3Remote(home, exec) {
|
|
5942
|
+
const remote = readWikiS3RemoteConfigured(home);
|
|
5943
|
+
if (!remote) {
|
|
5944
|
+
return check("pass", "vault_s3_remote", "Vault S3 remote", "S3 remote not configured \u2014 check skipped");
|
|
5945
|
+
}
|
|
5946
|
+
const state = probeS3Reachability(remote, exec);
|
|
5947
|
+
if (state === "ok") {
|
|
5948
|
+
return check("pass", "vault_s3_remote", "Vault S3 remote", `rclone lsf ${remote} succeeded`);
|
|
5949
|
+
}
|
|
5950
|
+
if (state === "unreachable") {
|
|
5951
|
+
return check("warn", "vault_s3_remote", "Vault S3 remote", `S3 remote unreachable (${remote}) \u2014 local/GitHub work may continue`);
|
|
5952
|
+
}
|
|
5953
|
+
return check("pass", "vault_s3_remote", "Vault S3 remote", "S3 remote not configured \u2014 check skipped");
|
|
5954
|
+
}
|
|
5955
|
+
function checkVaultSnapshotterReachable(fleetLoad, checkSnapshotter, exec) {
|
|
5956
|
+
if (!checkSnapshotter) {
|
|
5957
|
+
return check("pass", "vault_snapshotter_reachable", "Vault snapshotter host", "Snapshotter SSH probe not requested \u2014 check skipped");
|
|
5958
|
+
}
|
|
5959
|
+
const alias = snapshotterAliasForLocalHost(fleetLoad);
|
|
5960
|
+
if (!alias) {
|
|
5961
|
+
return check("pass", "vault_snapshotter_reachable", "Vault snapshotter host", "No declared SSH alias from this host \u2014 check skipped");
|
|
5962
|
+
}
|
|
5963
|
+
const state = probeSnapshotterSsh(alias, exec);
|
|
5964
|
+
if (state === "ok") {
|
|
5965
|
+
return check("pass", "vault_snapshotter_reachable", "Vault snapshotter host", `SSH reachable via ${alias}`);
|
|
5966
|
+
}
|
|
5967
|
+
return check("warn", "vault_snapshotter_reachable", "Vault snapshotter host", `Snapshotter unreachable via ${alias} \u2014 not a local vault corruption signal`);
|
|
5968
|
+
}
|
|
5969
|
+
function checkVaultPromotionLag(resolvedPath) {
|
|
5970
|
+
if (resolvedPath === void 0) {
|
|
5971
|
+
return check("pass", "vault_promotion_lag", "Vault promotion lag", "No vault path \u2014 check skipped");
|
|
5972
|
+
}
|
|
5973
|
+
if (!existsSync13(join24(resolvedPath, ".git"))) {
|
|
5974
|
+
return check("pass", "vault_promotion_lag", "Vault promotion lag", "No git repo \u2014 check skipped");
|
|
5975
|
+
}
|
|
5976
|
+
try {
|
|
5977
|
+
const out = execSync2("git log -1 --format=%ct origin/main", {
|
|
5978
|
+
cwd: resolvedPath,
|
|
5979
|
+
encoding: "utf8",
|
|
5980
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
5981
|
+
timeout: 2e3
|
|
5982
|
+
}).trim();
|
|
5983
|
+
const ts = parseInt(out, 10);
|
|
5984
|
+
if (!Number.isFinite(ts) || ts <= 0) {
|
|
5985
|
+
return check("pass", "vault_promotion_lag", "Vault promotion lag", "origin/main timestamp unavailable \u2014 check skipped");
|
|
5986
|
+
}
|
|
5987
|
+
const ageHours = Math.floor((Date.now() / 1e3 - ts) / 3600);
|
|
5988
|
+
if (ageHours > 48) {
|
|
5989
|
+
return check("warn", "vault_promotion_lag", "Vault promotion lag", `Local origin/main snapshot is ${ageHours}h old \u2014 verify snapshotter/GitHub when online`);
|
|
5990
|
+
}
|
|
5991
|
+
return check("pass", "vault_promotion_lag", "Vault promotion lag", `origin/main age ${ageHours}h`);
|
|
5992
|
+
} catch {
|
|
5993
|
+
return check("pass", "vault_promotion_lag", "Vault promotion lag", "Could not read origin/main \u2014 check skipped");
|
|
5994
|
+
}
|
|
5995
|
+
}
|
|
5675
5996
|
function checkVaultGitComparison(resolvedPath, id, label, range, nonZeroSuffix, zeroDetail) {
|
|
5676
5997
|
if (resolvedPath === void 0) {
|
|
5677
5998
|
return check("pass", id, label, "No vault path \u2014 check skipped");
|
|
5678
5999
|
}
|
|
5679
|
-
if (!
|
|
6000
|
+
if (!existsSync13(join24(resolvedPath, ".git"))) {
|
|
5680
6001
|
return check("pass", id, label, "No git repo \u2014 check skipped");
|
|
5681
6002
|
}
|
|
5682
6003
|
if (!hasOriginMain(resolvedPath)) {
|
|
@@ -5704,7 +6025,7 @@ function checkSatelliteLastRun(vaultPath, satelliteExpected) {
|
|
|
5704
6025
|
return check("pass", "satellite_job_last_run", "Satellite job last run", "No vault path \u2014 check skipped");
|
|
5705
6026
|
}
|
|
5706
6027
|
const latestPath = satelliteLatestRunPath(vaultPath);
|
|
5707
|
-
if (!
|
|
6028
|
+
if (!existsSync13(latestPath)) {
|
|
5708
6029
|
return check("pass", "satellite_job_last_run", "Satellite job last run", "No latest-run.json \u2014 satellite has not run yet");
|
|
5709
6030
|
}
|
|
5710
6031
|
try {
|
|
@@ -5792,11 +6113,11 @@ async function checkFleetIdentity(input) {
|
|
|
5792
6113
|
}
|
|
5793
6114
|
function pullLogPaths(home) {
|
|
5794
6115
|
const paths = platform2() === "darwin" ? [
|
|
5795
|
-
|
|
5796
|
-
|
|
6116
|
+
join24(home, "Library", "Logs", "wiki-pull.log"),
|
|
6117
|
+
join24(home, ".local", "state", "vault-sync", "log", "wiki-pull.log")
|
|
5797
6118
|
] : [
|
|
5798
|
-
|
|
5799
|
-
|
|
6119
|
+
join24(home, ".local", "state", "vault-sync", "log", "wiki-pull.log"),
|
|
6120
|
+
join24(home, "Library", "Logs", "wiki-pull.log")
|
|
5800
6121
|
];
|
|
5801
6122
|
return [...new Set(paths)];
|
|
5802
6123
|
}
|
|
@@ -5808,12 +6129,12 @@ function isRecentLogLine(line, nowMs) {
|
|
|
5808
6129
|
return nowMs - ts <= 24 * 60 * 60 * 1e3;
|
|
5809
6130
|
}
|
|
5810
6131
|
function checkVaultGitPullFailures(home) {
|
|
5811
|
-
const path = pullLogPaths(home).find((p) =>
|
|
6132
|
+
const path = pullLogPaths(home).find((p) => existsSync13(p));
|
|
5812
6133
|
if (!path) {
|
|
5813
6134
|
return check("pass", "vault_git_pull_failures", "Vault pull failures", "No wiki-pull.log found \u2014 check skipped");
|
|
5814
6135
|
}
|
|
5815
6136
|
try {
|
|
5816
|
-
const lines =
|
|
6137
|
+
const lines = readFileSync10(path, "utf8").split(/\r?\n/).filter(Boolean);
|
|
5817
6138
|
const now = Date.now();
|
|
5818
6139
|
const failures = lines.filter(
|
|
5819
6140
|
(line) => isRecentLogLine(line, now) && /(pre-push pull failed|FAIL .*pull|FAIL .*rebase|cannot pull with rebase|unstaged changes)/i.test(line)
|
|
@@ -5836,8 +6157,8 @@ function checkS3MountPerf(resolvedPath) {
|
|
|
5836
6157
|
return check("pass", "s3_mount_perf", "S3 mount performance", "local disk");
|
|
5837
6158
|
}
|
|
5838
6159
|
const mountPoint = fuse.mountPoint;
|
|
5839
|
-
const conceptsDir =
|
|
5840
|
-
if (!
|
|
6160
|
+
const conceptsDir = join24(resolvedPath, "concepts");
|
|
6161
|
+
if (!existsSync13(conceptsDir)) {
|
|
5841
6162
|
return check("pass", "s3_mount_perf", "S3 mount performance", `S3 FUSE mount (${mountPoint}), no concepts/ to benchmark`);
|
|
5842
6163
|
}
|
|
5843
6164
|
const start = Date.now();
|
|
@@ -6019,8 +6340,8 @@ function checkWriteTest(resolvedPath) {
|
|
|
6019
6340
|
if (!fuse) {
|
|
6020
6341
|
return check("pass", "s3_write_test", "S3 write test", "local disk \u2014 check skipped");
|
|
6021
6342
|
}
|
|
6022
|
-
const conceptsDir =
|
|
6023
|
-
if (!
|
|
6343
|
+
const conceptsDir = join24(resolvedPath, "concepts");
|
|
6344
|
+
if (!existsSync13(conceptsDir)) {
|
|
6024
6345
|
return check("pass", "s3_write_test", "S3 write test", "no concepts/ dir to test \u2014 check skipped");
|
|
6025
6346
|
}
|
|
6026
6347
|
const result = writeTest(conceptsDir);
|
|
@@ -6106,7 +6427,7 @@ function checkVfsCacheHealth(resolvedPath) {
|
|
|
6106
6427
|
}
|
|
6107
6428
|
function readVaultSyncConfig(home) {
|
|
6108
6429
|
try {
|
|
6109
|
-
const content =
|
|
6430
|
+
const content = readFileSync10(join24(home, ".skillwiki", ".env"), "utf8");
|
|
6110
6431
|
let installed = false;
|
|
6111
6432
|
let role;
|
|
6112
6433
|
let serviceScope;
|
|
@@ -6135,7 +6456,7 @@ function readVaultSyncConfig(home) {
|
|
|
6135
6456
|
}
|
|
6136
6457
|
function readKeyFromEnvFile(path, keys) {
|
|
6137
6458
|
try {
|
|
6138
|
-
const content =
|
|
6459
|
+
const content = readFileSync10(path, "utf8");
|
|
6139
6460
|
for (const line of content.split(/\r?\n/)) {
|
|
6140
6461
|
const trimmed = line.trim();
|
|
6141
6462
|
if (trimmed.length === 0 || trimmed.startsWith("#")) continue;
|
|
@@ -6157,7 +6478,7 @@ function resolveSnapshotGitWorktree(config) {
|
|
|
6157
6478
|
if (fromProfile) return fromProfile;
|
|
6158
6479
|
}
|
|
6159
6480
|
const defaultPath = "/root/wiki-git";
|
|
6160
|
-
return
|
|
6481
|
+
return existsSync13(defaultPath) ? defaultPath : void 0;
|
|
6161
6482
|
}
|
|
6162
6483
|
function vaultSyncChecks(input) {
|
|
6163
6484
|
const os = input.os ?? platform2();
|
|
@@ -6174,16 +6495,16 @@ function vaultSyncChecks(input) {
|
|
|
6174
6495
|
];
|
|
6175
6496
|
}
|
|
6176
6497
|
const isMac = os === "darwin";
|
|
6177
|
-
const logDir = input.logDir ?? (isMac ?
|
|
6178
|
-
const shareDir = input.shareDir ?? (isMac ?
|
|
6179
|
-
const filterPath = input.filterPath ??
|
|
6180
|
-
const packagedSnapshotPath =
|
|
6498
|
+
const logDir = input.logDir ?? (isMac ? join24(home, "Library", "Logs") : join24(home, ".local", "state", "vault-sync", "log"));
|
|
6499
|
+
const shareDir = input.shareDir ?? (isMac ? join24(home, "Library", "Application Support", "vault-sync", "bin") : join24(home, ".local", "share", "vault-sync", "bin"));
|
|
6500
|
+
const filterPath = input.filterPath ?? join24(home, ".config", "rclone", "wiki-push-filters.txt");
|
|
6501
|
+
const packagedSnapshotPath = join24(shareDir, "wiki-snapshot.sh");
|
|
6181
6502
|
const legacySnapshotPath = "/root/.hermes/scripts/wiki-snapshot-v3.sh";
|
|
6182
|
-
const snapshotPath = input.snapshotScriptPath ?? (
|
|
6503
|
+
const snapshotPath = input.snapshotScriptPath ?? (existsSync13(packagedSnapshotPath) ? packagedSnapshotPath : legacySnapshotPath);
|
|
6183
6504
|
function snapshotLastStatusCheck() {
|
|
6184
|
-
const snapshotLog =
|
|
6505
|
+
const snapshotLog = join24(logDir, "wiki-snapshot.log");
|
|
6185
6506
|
try {
|
|
6186
|
-
const logContent =
|
|
6507
|
+
const logContent = readFileSync10(snapshotLog, "utf8");
|
|
6187
6508
|
const lines = logContent.trim().split("\n").filter(Boolean);
|
|
6188
6509
|
if (lines.length === 0) {
|
|
6189
6510
|
return check(
|
|
@@ -6228,14 +6549,14 @@ function vaultSyncChecks(input) {
|
|
|
6228
6549
|
}
|
|
6229
6550
|
}
|
|
6230
6551
|
if (input.vaultSyncRole === "snapshotter") {
|
|
6231
|
-
const c12 =
|
|
6552
|
+
const c12 = existsSync13(snapshotPath) ? check("pass", "vault_sync_installed", "Vault sync installed", `Found snapshot script: ${snapshotPath}`) : check("error", "vault_sync_installed", "Vault sync installed", `Snapshot script not found at ${snapshotPath}`);
|
|
6232
6553
|
const serviceScope = input.vaultSyncServiceScope ?? "user";
|
|
6233
|
-
const userTimerPath =
|
|
6554
|
+
const userTimerPath = join24(home, ".config", "systemd", "user", "wiki-snapshot.timer");
|
|
6234
6555
|
const systemTimerPath = "/etc/systemd/system/wiki-snapshot.timer";
|
|
6235
6556
|
let c22;
|
|
6236
|
-
if (serviceScope === "user" &&
|
|
6557
|
+
if (serviceScope === "user" && existsSync13(userTimerPath)) {
|
|
6237
6558
|
c22 = check("pass", "vault_sync_jobs_enabled", "Vault sync jobs enabled", `Found: ${userTimerPath}`);
|
|
6238
|
-
} else if (serviceScope === "system" &&
|
|
6559
|
+
} else if (serviceScope === "system" && existsSync13(systemTimerPath)) {
|
|
6239
6560
|
c22 = check("pass", "vault_sync_jobs_enabled", "Vault sync jobs enabled", `Found: ${systemTimerPath}`);
|
|
6240
6561
|
} else if (os !== "linux") {
|
|
6241
6562
|
c22 = check("warn", "vault_sync_jobs_enabled", "Vault sync jobs enabled", "Snapshotter scheduler is Linux-only and no wiki-snapshot.timer file was found");
|
|
@@ -6267,7 +6588,7 @@ function vaultSyncChecks(input) {
|
|
|
6267
6588
|
);
|
|
6268
6589
|
let c52;
|
|
6269
6590
|
try {
|
|
6270
|
-
if (!
|
|
6591
|
+
if (!existsSync13(snapshotPath)) {
|
|
6271
6592
|
c52 = check(
|
|
6272
6593
|
"error",
|
|
6273
6594
|
"vault_sync_snapshot_guard",
|
|
@@ -6275,7 +6596,7 @@ function vaultSyncChecks(input) {
|
|
|
6275
6596
|
`Snapshot script not found at ${snapshotPath}`
|
|
6276
6597
|
);
|
|
6277
6598
|
} else {
|
|
6278
|
-
const content =
|
|
6599
|
+
const content = readFileSync10(snapshotPath, "utf8");
|
|
6279
6600
|
if (!content.includes("--max-delete")) {
|
|
6280
6601
|
c52 = check(
|
|
6281
6602
|
"error",
|
|
@@ -6302,8 +6623,8 @@ function vaultSyncChecks(input) {
|
|
|
6302
6623
|
}
|
|
6303
6624
|
return [c12, c22, c32, cFetch2, c42, c52];
|
|
6304
6625
|
}
|
|
6305
|
-
const pushScriptPath =
|
|
6306
|
-
const c1 =
|
|
6626
|
+
const pushScriptPath = join24(shareDir, "wiki-push.sh");
|
|
6627
|
+
const c1 = existsSync13(pushScriptPath) ? check("pass", "vault_sync_installed", "Vault sync installed", `Found: ${pushScriptPath}`) : check("error", "vault_sync_installed", "Vault sync installed", `Script not found at ${pushScriptPath} \u2014 run vault-sync-install`);
|
|
6307
6628
|
let c2;
|
|
6308
6629
|
try {
|
|
6309
6630
|
if (isMac) {
|
|
@@ -6354,10 +6675,10 @@ function vaultSyncChecks(input) {
|
|
|
6354
6675
|
"Scheduler check failed \u2014 run vault-sync-install"
|
|
6355
6676
|
);
|
|
6356
6677
|
}
|
|
6357
|
-
const logFile =
|
|
6678
|
+
const logFile = join24(logDir, "wiki-push.log");
|
|
6358
6679
|
let c3;
|
|
6359
6680
|
try {
|
|
6360
|
-
const logContent =
|
|
6681
|
+
const logContent = readFileSync10(logFile, "utf8");
|
|
6361
6682
|
const lines = logContent.trim().split("\n").filter(Boolean);
|
|
6362
6683
|
if (lines.length === 0) {
|
|
6363
6684
|
c3 = check(
|
|
@@ -6413,7 +6734,7 @@ function vaultSyncChecks(input) {
|
|
|
6413
6734
|
}
|
|
6414
6735
|
}
|
|
6415
6736
|
} catch {
|
|
6416
|
-
c3 =
|
|
6737
|
+
c3 = existsSync13(logDir) ? check(
|
|
6417
6738
|
"warn",
|
|
6418
6739
|
"vault_sync_last_push_age",
|
|
6419
6740
|
"Vault sync last push recency",
|
|
@@ -6425,10 +6746,10 @@ function vaultSyncChecks(input) {
|
|
|
6425
6746
|
`Log directory not found at ${logDir}`
|
|
6426
6747
|
);
|
|
6427
6748
|
}
|
|
6428
|
-
const fetchLogFile =
|
|
6749
|
+
const fetchLogFile = join24(logDir, "wiki-fetch.log");
|
|
6429
6750
|
let cFetch;
|
|
6430
6751
|
try {
|
|
6431
|
-
const logContent =
|
|
6752
|
+
const logContent = readFileSync10(fetchLogFile, "utf8");
|
|
6432
6753
|
const lines = logContent.trim().split("\n").filter(Boolean);
|
|
6433
6754
|
if (lines.length === 0) {
|
|
6434
6755
|
cFetch = check(
|
|
@@ -6472,7 +6793,7 @@ function vaultSyncChecks(input) {
|
|
|
6472
6793
|
}
|
|
6473
6794
|
let c4;
|
|
6474
6795
|
try {
|
|
6475
|
-
if (!
|
|
6796
|
+
if (!existsSync13(filterPath)) {
|
|
6476
6797
|
c4 = check(
|
|
6477
6798
|
"error",
|
|
6478
6799
|
"vault_sync_filter_present",
|
|
@@ -6480,7 +6801,7 @@ function vaultSyncChecks(input) {
|
|
|
6480
6801
|
`Filter file not found at ${filterPath}`
|
|
6481
6802
|
);
|
|
6482
6803
|
} else {
|
|
6483
|
-
const content =
|
|
6804
|
+
const content = readFileSync10(filterPath, "utf8");
|
|
6484
6805
|
const requiredExcludes = [
|
|
6485
6806
|
"remotely-save/data.json",
|
|
6486
6807
|
".skillwiki/sync.lock",
|
|
@@ -6523,7 +6844,7 @@ function vaultSyncChecks(input) {
|
|
|
6523
6844
|
);
|
|
6524
6845
|
} else {
|
|
6525
6846
|
try {
|
|
6526
|
-
if (!
|
|
6847
|
+
if (!existsSync13(snapshotPath)) {
|
|
6527
6848
|
c5 = check(
|
|
6528
6849
|
"error",
|
|
6529
6850
|
"vault_sync_snapshot_guard",
|
|
@@ -6531,7 +6852,7 @@ function vaultSyncChecks(input) {
|
|
|
6531
6852
|
`Snapshot script not found at ${snapshotPath}`
|
|
6532
6853
|
);
|
|
6533
6854
|
} else {
|
|
6534
|
-
const content =
|
|
6855
|
+
const content = readFileSync10(snapshotPath, "utf8");
|
|
6535
6856
|
if (!content.includes("--max-delete")) {
|
|
6536
6857
|
c5 = check(
|
|
6537
6858
|
"error",
|
|
@@ -6563,33 +6884,33 @@ function findSkillMd(dir) {
|
|
|
6563
6884
|
const results = [];
|
|
6564
6885
|
let entries;
|
|
6565
6886
|
try {
|
|
6566
|
-
entries =
|
|
6887
|
+
entries = readdirSync3(dir, { withFileTypes: true });
|
|
6567
6888
|
} catch {
|
|
6568
6889
|
return results;
|
|
6569
6890
|
}
|
|
6570
6891
|
for (const entry of entries) {
|
|
6571
6892
|
if (entry.isFile() && entry.name === "SKILL.md") {
|
|
6572
|
-
results.push(
|
|
6893
|
+
results.push(join24(dir, entry.name));
|
|
6573
6894
|
} else if (entry.isDirectory()) {
|
|
6574
|
-
results.push(...findSkillMd(
|
|
6895
|
+
results.push(...findSkillMd(join24(dir, entry.name)));
|
|
6575
6896
|
}
|
|
6576
6897
|
}
|
|
6577
6898
|
return results;
|
|
6578
6899
|
}
|
|
6579
6900
|
function findInstalledSkillMd(dir) {
|
|
6580
|
-
const directSkills = findSkillNames(dir).map((name) =>
|
|
6901
|
+
const directSkills = findSkillNames(dir).map((name) => join24(dir, name, "SKILL.md"));
|
|
6581
6902
|
return directSkills.length > 0 ? directSkills : findSkillMd(dir);
|
|
6582
6903
|
}
|
|
6583
6904
|
function findSkillNames(dir) {
|
|
6584
6905
|
const results = [];
|
|
6585
6906
|
let entries;
|
|
6586
6907
|
try {
|
|
6587
|
-
entries =
|
|
6908
|
+
entries = readdirSync3(dir, { withFileTypes: true });
|
|
6588
6909
|
} catch {
|
|
6589
6910
|
return results;
|
|
6590
6911
|
}
|
|
6591
6912
|
for (const entry of entries) {
|
|
6592
|
-
if (entry.isDirectory() &&
|
|
6913
|
+
if (entry.isDirectory() && existsSync13(join24(dir, entry.name, "SKILL.md"))) {
|
|
6593
6914
|
results.push(entry.name);
|
|
6594
6915
|
}
|
|
6595
6916
|
}
|
|
@@ -6633,7 +6954,7 @@ async function vaultMetrics(resolvedPath) {
|
|
|
6633
6954
|
}
|
|
6634
6955
|
let logLines = 0;
|
|
6635
6956
|
try {
|
|
6636
|
-
logLines =
|
|
6957
|
+
logLines = readFileSync10(join24(resolvedPath, "log.md"), "utf8").split("\n").length;
|
|
6637
6958
|
} catch {
|
|
6638
6959
|
}
|
|
6639
6960
|
return [
|
|
@@ -6690,7 +7011,13 @@ async function runDoctor(input) {
|
|
|
6690
7011
|
checks.push(checkVaultGitAhead(gitCheckPath));
|
|
6691
7012
|
checks.push(checkVaultGitBehind(gitCheckPath));
|
|
6692
7013
|
checks.push(checkVaultGitPullFailures(input.home));
|
|
7014
|
+
checks.push(checkVaultLocalGit(gitCheckPath));
|
|
7015
|
+
checks.push(checkVaultGithubRemote(gitCheckPath, input.execProbe));
|
|
7016
|
+
checks.push(checkVaultS3Remote(input.home, input.execProbe));
|
|
7017
|
+
checks.push(checkVaultSnapshotterReachable(fleetLoad, input.checkSnapshotter, input.execProbe));
|
|
7018
|
+
checks.push(checkVaultPromotionLag(gitCheckPath));
|
|
6693
7019
|
checks.push(checkDotStoreClean(resolvedPath));
|
|
7020
|
+
checks.push(checkVaultConflictMarkers(resolvedPath));
|
|
6694
7021
|
checks.push(checkS3MountPerf(resolvedPath));
|
|
6695
7022
|
checks.push(checkS3MountFreshness(resolvedPath));
|
|
6696
7023
|
checks.push(checkRcloneFlagAudit(resolvedPath));
|
|
@@ -6735,7 +7062,7 @@ async function runDoctor(input) {
|
|
|
6735
7062
|
}
|
|
6736
7063
|
|
|
6737
7064
|
// src/utils/package-info.ts
|
|
6738
|
-
import { readFileSync as
|
|
7065
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
6739
7066
|
function packageJsonCandidateUrls(baseUrl = import.meta.url) {
|
|
6740
7067
|
return [
|
|
6741
7068
|
new URL("../package.json", baseUrl),
|
|
@@ -6745,7 +7072,7 @@ function packageJsonCandidateUrls(baseUrl = import.meta.url) {
|
|
|
6745
7072
|
function readCliPackageJson(baseUrl = import.meta.url) {
|
|
6746
7073
|
for (const url of packageJsonCandidateUrls(baseUrl)) {
|
|
6747
7074
|
try {
|
|
6748
|
-
const pkg = JSON.parse(
|
|
7075
|
+
const pkg = JSON.parse(readFileSync11(url, "utf8"));
|
|
6749
7076
|
if (typeof pkg.version === "string") {
|
|
6750
7077
|
return { ...pkg, version: pkg.version };
|
|
6751
7078
|
}
|
|
@@ -6757,11 +7084,49 @@ function readCliPackageJson(baseUrl = import.meta.url) {
|
|
|
6757
7084
|
|
|
6758
7085
|
// src/commands/project-index.ts
|
|
6759
7086
|
import { readdir as readdir4, readFile as readFile16, writeFile as writeFile6, mkdir as mkdir5 } from "fs/promises";
|
|
6760
|
-
import { join as
|
|
7087
|
+
import { join as join25, dirname as dirname8, basename as basename2 } from "path";
|
|
6761
7088
|
var LAYER2_DIRS = ["entities", "concepts", "comparisons", "queries", "meta"];
|
|
7089
|
+
var PROJECT_LOCAL_DIRS = ["requirements", "work", "architecture", "history"];
|
|
7090
|
+
async function scanMarkdownTree(rootAbs, rootRel) {
|
|
7091
|
+
const found = [];
|
|
7092
|
+
let entries;
|
|
7093
|
+
try {
|
|
7094
|
+
entries = await readdir4(rootAbs, { withFileTypes: true });
|
|
7095
|
+
} catch {
|
|
7096
|
+
return found;
|
|
7097
|
+
}
|
|
7098
|
+
for (const entry of entries) {
|
|
7099
|
+
const abs = join25(rootAbs, entry.name);
|
|
7100
|
+
const rel = `${rootRel}/${entry.name}`;
|
|
7101
|
+
if (entry.isDirectory()) {
|
|
7102
|
+
found.push(...await scanMarkdownTree(abs, rel));
|
|
7103
|
+
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
7104
|
+
found.push(rel);
|
|
7105
|
+
}
|
|
7106
|
+
}
|
|
7107
|
+
return found;
|
|
7108
|
+
}
|
|
7109
|
+
function projectLocalType(slug, page, data) {
|
|
7110
|
+
if (page.startsWith(`projects/${slug}/requirements/`)) return "requirement";
|
|
7111
|
+
if (page.startsWith(`projects/${slug}/work/`)) {
|
|
7112
|
+
if (typeof data.kind === "string") return data.kind;
|
|
7113
|
+
const name = basename2(page, ".md");
|
|
7114
|
+
if (name === "spec" || name === "plan" || name === "retro") return name;
|
|
7115
|
+
return "work";
|
|
7116
|
+
}
|
|
7117
|
+
if (page.startsWith(`projects/${slug}/architecture/`)) {
|
|
7118
|
+
return typeof data.type === "string" ? data.type : "architecture";
|
|
7119
|
+
}
|
|
7120
|
+
if (page.startsWith(`projects/${slug}/history/`)) {
|
|
7121
|
+
if (typeof data.kind === "string") return data.kind;
|
|
7122
|
+
if (typeof data.type === "string") return data.type;
|
|
7123
|
+
return "history";
|
|
7124
|
+
}
|
|
7125
|
+
return typeof data.type === "string" ? data.type : "project";
|
|
7126
|
+
}
|
|
6762
7127
|
async function runProjectIndex(input) {
|
|
6763
7128
|
const slug = input.slug;
|
|
6764
|
-
const projectDir =
|
|
7129
|
+
const projectDir = join25(input.vault, "projects", slug);
|
|
6765
7130
|
try {
|
|
6766
7131
|
await readdir4(projectDir);
|
|
6767
7132
|
} catch {
|
|
@@ -6772,12 +7137,12 @@ async function runProjectIndex(input) {
|
|
|
6772
7137
|
}
|
|
6773
7138
|
const wikilinkPattern = `[[${slug}]]`;
|
|
6774
7139
|
const entries = [];
|
|
6775
|
-
const compoundDir =
|
|
7140
|
+
const compoundDir = join25(input.vault, "projects", slug, "compound");
|
|
6776
7141
|
try {
|
|
6777
7142
|
const compoundFiles = await readdir4(compoundDir, { withFileTypes: true });
|
|
6778
7143
|
for (const entry of compoundFiles) {
|
|
6779
7144
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
6780
|
-
const filePath =
|
|
7145
|
+
const filePath = join25(compoundDir, entry.name);
|
|
6781
7146
|
let text;
|
|
6782
7147
|
try {
|
|
6783
7148
|
text = await readFile16(filePath, "utf8");
|
|
@@ -6797,13 +7162,13 @@ async function runProjectIndex(input) {
|
|
|
6797
7162
|
for (const dir of LAYER2_DIRS) {
|
|
6798
7163
|
let files;
|
|
6799
7164
|
try {
|
|
6800
|
-
files = await readdir4(
|
|
7165
|
+
files = await readdir4(join25(input.vault, dir), { withFileTypes: true });
|
|
6801
7166
|
} catch {
|
|
6802
7167
|
continue;
|
|
6803
7168
|
}
|
|
6804
7169
|
for (const entry of files) {
|
|
6805
7170
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
6806
|
-
const filePath =
|
|
7171
|
+
const filePath = join25(input.vault, dir, entry.name);
|
|
6807
7172
|
let text;
|
|
6808
7173
|
try {
|
|
6809
7174
|
text = await readFile16(filePath, "utf8");
|
|
@@ -6821,13 +7186,34 @@ async function runProjectIndex(input) {
|
|
|
6821
7186
|
});
|
|
6822
7187
|
}
|
|
6823
7188
|
}
|
|
6824
|
-
const
|
|
7189
|
+
for (const dir of PROJECT_LOCAL_DIRS) {
|
|
7190
|
+
const rootAbs = join25(projectDir, dir);
|
|
7191
|
+
const rootRel = `projects/${slug}/${dir}`;
|
|
7192
|
+
const pages = await scanMarkdownTree(rootAbs, rootRel);
|
|
7193
|
+
for (const page of pages) {
|
|
7194
|
+
const filePath = join25(input.vault, page);
|
|
7195
|
+
let text;
|
|
7196
|
+
try {
|
|
7197
|
+
text = await readFile16(filePath, "utf8");
|
|
7198
|
+
} catch {
|
|
7199
|
+
continue;
|
|
7200
|
+
}
|
|
7201
|
+
const fm = extractFrontmatter(text);
|
|
7202
|
+
if (!fm.ok) continue;
|
|
7203
|
+
entries.push({
|
|
7204
|
+
page,
|
|
7205
|
+
type: projectLocalType(slug, page, fm.data),
|
|
7206
|
+
title: typeof fm.data.title === "string" ? fm.data.title : basename2(page, ".md")
|
|
7207
|
+
});
|
|
7208
|
+
}
|
|
7209
|
+
}
|
|
7210
|
+
const typeOrder = { entity: 0, concept: 1, comparison: 2, query: 3, summary: 4, meta: 5, requirement: 6, spec: 7, plan: 8, retro: 9, architecture: 10, pattern: 11, gotcha: 12, lesson: 13, antipattern: 14, compound: 15, work: 16, history: 17 };
|
|
6825
7211
|
entries.sort((a, b) => {
|
|
6826
7212
|
const ta = typeOrder[a.type] ?? 99;
|
|
6827
7213
|
const tb = typeOrder[b.type] ?? 99;
|
|
6828
7214
|
return ta !== tb ? ta - tb : a.title.localeCompare(b.title);
|
|
6829
7215
|
});
|
|
6830
|
-
const indexPath =
|
|
7216
|
+
const indexPath = join25(projectDir, "knowledge.md");
|
|
6831
7217
|
let existing = false;
|
|
6832
7218
|
let stale = false;
|
|
6833
7219
|
try {
|
|
@@ -6901,8 +7287,8 @@ ${entries.map((e) => ` ${e.type}: [[${e.page.replace(/\.md$/, "")}]] \u2014 ${e
|
|
|
6901
7287
|
|
|
6902
7288
|
// src/commands/observe.ts
|
|
6903
7289
|
import { mkdir as mkdir6, writeFile as writeFile7 } from "fs/promises";
|
|
6904
|
-
import { existsSync as
|
|
6905
|
-
import { join as
|
|
7290
|
+
import { existsSync as existsSync14, statSync as statSync2 } from "fs";
|
|
7291
|
+
import { join as join26 } from "path";
|
|
6906
7292
|
import { createHash as createHash5 } from "crypto";
|
|
6907
7293
|
var ALLOWED_KINDS = /* @__PURE__ */ new Set(["note", "bug", "task", "idea", "session-log"]);
|
|
6908
7294
|
function slugify(text) {
|
|
@@ -6925,13 +7311,13 @@ async function runObserve(input) {
|
|
|
6925
7311
|
result: err("SCHEME_REJECTED", { message: "Text must not be empty" })
|
|
6926
7312
|
};
|
|
6927
7313
|
}
|
|
6928
|
-
if (!
|
|
7314
|
+
if (!existsSync14(input.vault) || !statSync2(input.vault).isDirectory()) {
|
|
6929
7315
|
return {
|
|
6930
7316
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
6931
7317
|
result: err("VAULT_PATH_INVALID", { path: input.vault })
|
|
6932
7318
|
};
|
|
6933
7319
|
}
|
|
6934
|
-
const transcriptsDir =
|
|
7320
|
+
const transcriptsDir = join26(input.vault, "raw", "transcripts");
|
|
6935
7321
|
try {
|
|
6936
7322
|
await mkdir6(transcriptsDir, { recursive: true });
|
|
6937
7323
|
} catch {
|
|
@@ -6943,7 +7329,7 @@ async function runObserve(input) {
|
|
|
6943
7329
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
6944
7330
|
const slug = slugify(input.text);
|
|
6945
7331
|
const fileName = `${today}-observation-${slug}.md`;
|
|
6946
|
-
const filePath =
|
|
7332
|
+
const filePath = join26(transcriptsDir, fileName);
|
|
6947
7333
|
const body = `
|
|
6948
7334
|
${input.text.trim()}
|
|
6949
7335
|
`;
|
|
@@ -6985,7 +7371,7 @@ ${input.text.trim()}
|
|
|
6985
7371
|
// src/commands/memory.ts
|
|
6986
7372
|
import { createHash as createHash6 } from "crypto";
|
|
6987
7373
|
import { mkdir as mkdir7, readFile as readFile17, readdir as readdir5, stat as stat5, writeFile as writeFile8 } from "fs/promises";
|
|
6988
|
-
import { basename as
|
|
7374
|
+
import { basename as basename3, extname, join as join27, relative as relative4, sep as sep4 } from "path";
|
|
6989
7375
|
async function runMemoryTopics(input) {
|
|
6990
7376
|
const scan = await scanVault(input.vault);
|
|
6991
7377
|
if (!scan.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scan };
|
|
@@ -7049,8 +7435,8 @@ async function runMemoryIndex(input) {
|
|
|
7049
7435
|
}
|
|
7050
7436
|
const generatedAt = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
7051
7437
|
const relCachePath = memoryCacheRelPath(input.project);
|
|
7052
|
-
const absCachePath =
|
|
7053
|
-
await mkdir7(
|
|
7438
|
+
const absCachePath = join27(input.vault, relCachePath);
|
|
7439
|
+
await mkdir7(join27(input.vault, ".skillwiki", "memory", input.project), { recursive: true });
|
|
7054
7440
|
await writeFile8(absCachePath, `${JSON.stringify({
|
|
7055
7441
|
generated_at: generatedAt,
|
|
7056
7442
|
project: input.project,
|
|
@@ -7242,7 +7628,7 @@ async function buildMemoryIndexState(pages, project) {
|
|
|
7242
7628
|
}
|
|
7243
7629
|
async function checkMemoryIndex(vault, project, current) {
|
|
7244
7630
|
const relCachePath = memoryCacheRelPath(project);
|
|
7245
|
-
const cacheText = await readIfExists2(
|
|
7631
|
+
const cacheText = await readIfExists2(join27(vault, relCachePath));
|
|
7246
7632
|
if (!cacheText) {
|
|
7247
7633
|
return {
|
|
7248
7634
|
ok: true,
|
|
@@ -7651,7 +8037,7 @@ async function walkImportFiles(dir, out) {
|
|
|
7651
8037
|
const entries = await readdir5(dir, { withFileTypes: true });
|
|
7652
8038
|
for (const entry of entries) {
|
|
7653
8039
|
if (entry.name === ".git" || entry.name === "node_modules") continue;
|
|
7654
|
-
const path =
|
|
8040
|
+
const path = join27(dir, entry.name);
|
|
7655
8041
|
if (entry.isDirectory()) {
|
|
7656
8042
|
await walkImportFiles(path, out);
|
|
7657
8043
|
} else if (entry.isFile() && isImportCandidate(path)) {
|
|
@@ -7700,7 +8086,7 @@ async function buildImportEntry(file, sourceRoot, project, today, maxBytes) {
|
|
|
7700
8086
|
}
|
|
7701
8087
|
const redacted = redactSensitiveContent(extracted, { file });
|
|
7702
8088
|
const privacy = redacted.findings.length > 0 ? "sensitive" : "local";
|
|
7703
|
-
const sourceSlug = slugify2(
|
|
8089
|
+
const sourceSlug = slugify2(basename3(file, extname(file)));
|
|
7704
8090
|
const relSource = relative4(sourceRoot, file).split(sep4).join("/");
|
|
7705
8091
|
const entry = {
|
|
7706
8092
|
...baseEntry,
|
|
@@ -7718,8 +8104,8 @@ async function writeImportCapture(vault, entry, today) {
|
|
|
7718
8104
|
const content = hiddenString(entry, "__content");
|
|
7719
8105
|
const project = hiddenString(entry, "__project");
|
|
7720
8106
|
const relPath = await availableImportPath(vault, entry.proposed_path);
|
|
7721
|
-
const absPath =
|
|
7722
|
-
await mkdir7(
|
|
8107
|
+
const absPath = join27(vault, relPath);
|
|
8108
|
+
await mkdir7(join27(vault, "raw", "transcripts"), { recursive: true });
|
|
7723
8109
|
await writeFile8(absPath, renderImportCapture(entry, content, project, today), "utf8");
|
|
7724
8110
|
const validation = await runValidate({ file: absPath });
|
|
7725
8111
|
return {
|
|
@@ -7735,7 +8121,7 @@ async function availableImportPath(vault, proposed) {
|
|
|
7735
8121
|
const stem = proposed.slice(0, -ext.length);
|
|
7736
8122
|
let candidate = proposed;
|
|
7737
8123
|
let i = 2;
|
|
7738
|
-
while (await readIfExists2(
|
|
8124
|
+
while (await readIfExists2(join27(vault, candidate))) {
|
|
7739
8125
|
candidate = `${stem}-${i}${ext}`;
|
|
7740
8126
|
i++;
|
|
7741
8127
|
}
|
|
@@ -7759,7 +8145,7 @@ function renderImportCapture(entry, content, project, today) {
|
|
|
7759
8145
|
`source_paths: ["${entry.source_path.replaceAll('"', '\\"')}"]`,
|
|
7760
8146
|
"---",
|
|
7761
8147
|
"",
|
|
7762
|
-
`# Imported Memory: ${
|
|
8148
|
+
`# Imported Memory: ${basename3(entry.source_path, extname(entry.source_path))}`,
|
|
7763
8149
|
"",
|
|
7764
8150
|
`Source kind: ${entry.source_kind}`,
|
|
7765
8151
|
"",
|
|
@@ -7774,7 +8160,7 @@ function hiddenString(entry, key) {
|
|
|
7774
8160
|
}
|
|
7775
8161
|
function classifyImportSource(file) {
|
|
7776
8162
|
const rel = file.split(sep4).join("/");
|
|
7777
|
-
const name =
|
|
8163
|
+
const name = basename3(file);
|
|
7778
8164
|
if (rel.includes("/.codex/memories/")) return "codex-memory";
|
|
7779
8165
|
if (rel.includes("/.codex/rules/")) return "codex-rule";
|
|
7780
8166
|
if (rel.includes("/.claude/") && name === "napkin.md") return "napkin";
|
|
@@ -7907,10 +8293,10 @@ function memoryCacheRelPath(project) {
|
|
|
7907
8293
|
}
|
|
7908
8294
|
async function readMemoryCache(vault, project) {
|
|
7909
8295
|
if (project) {
|
|
7910
|
-
const projectCache = await readIfExists2(
|
|
8296
|
+
const projectCache = await readIfExists2(join27(vault, memoryCacheRelPath(project)));
|
|
7911
8297
|
if (projectCache) return projectCache;
|
|
7912
8298
|
}
|
|
7913
|
-
return readIfExists2(
|
|
8299
|
+
return readIfExists2(join27(vault, ".skillwiki", "memory-topics.json"));
|
|
7914
8300
|
}
|
|
7915
8301
|
function dedupePages(pages) {
|
|
7916
8302
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -7997,7 +8383,7 @@ function slugify2(value) {
|
|
|
7997
8383
|
|
|
7998
8384
|
// src/commands/query.ts
|
|
7999
8385
|
import { readFile as readFile18, stat as stat6 } from "fs/promises";
|
|
8000
|
-
import { join as
|
|
8386
|
+
import { join as join28 } from "path";
|
|
8001
8387
|
var W_KEYWORD = 2;
|
|
8002
8388
|
var W_SOURCE_OVERLAP = 4;
|
|
8003
8389
|
var W_WIKILINK = 3;
|
|
@@ -8118,7 +8504,7 @@ function computeKeywordScore(terms, title, tags, body) {
|
|
|
8118
8504
|
return score;
|
|
8119
8505
|
}
|
|
8120
8506
|
async function loadOrBuildGraph(vault) {
|
|
8121
|
-
const graphPath =
|
|
8507
|
+
const graphPath = join28(vault, ".skillwiki", "graph.json");
|
|
8122
8508
|
let needsBuild = false;
|
|
8123
8509
|
try {
|
|
8124
8510
|
const fileStat = await stat6(graphPath);
|
|
@@ -8147,7 +8533,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
8147
8533
|
import { z as z2 } from "zod";
|
|
8148
8534
|
|
|
8149
8535
|
// src/mcp/vault-resolve.ts
|
|
8150
|
-
import { join as
|
|
8536
|
+
import { join as join29, resolve as resolve7 } from "path";
|
|
8151
8537
|
|
|
8152
8538
|
// src/mcp/allowlist.ts
|
|
8153
8539
|
import { resolve as resolve6, sep as sep5 } from "path";
|
|
@@ -8209,7 +8595,7 @@ async function resolveMcpVault(input) {
|
|
|
8209
8595
|
return ok({ vault: vaultPath, source });
|
|
8210
8596
|
}
|
|
8211
8597
|
function defaultGraphOut(vault) {
|
|
8212
|
-
return
|
|
8598
|
+
return join29(vault, ".skillwiki", "graph.json");
|
|
8213
8599
|
}
|
|
8214
8600
|
|
|
8215
8601
|
// src/mcp/result-format.ts
|
|
@@ -8226,7 +8612,7 @@ function formatToolResult(payload) {
|
|
|
8226
8612
|
// src/mcp/audit-log.ts
|
|
8227
8613
|
import { appendFileSync, mkdirSync as mkdirSync4 } from "fs";
|
|
8228
8614
|
import { homedir } from "os";
|
|
8229
|
-
import { join as
|
|
8615
|
+
import { join as join30 } from "path";
|
|
8230
8616
|
function auditEnabled() {
|
|
8231
8617
|
const v = process.env.SKILLWIKI_MCP_AUDIT;
|
|
8232
8618
|
if (v === "0" || v === "false") return false;
|
|
@@ -8238,7 +8624,7 @@ function auditSink() {
|
|
|
8238
8624
|
function auditFilePath() {
|
|
8239
8625
|
const custom = process.env.SKILLWIKI_MCP_AUDIT_FILE;
|
|
8240
8626
|
if (custom && custom.length > 0) return custom;
|
|
8241
|
-
return
|
|
8627
|
+
return join30(homedir(), ".skillwiki", "mcp-audit.jsonl");
|
|
8242
8628
|
}
|
|
8243
8629
|
function auditMcpToolCall(entry) {
|
|
8244
8630
|
if (!auditEnabled()) return;
|
|
@@ -8248,7 +8634,7 @@ function auditMcpToolCall(entry) {
|
|
|
8248
8634
|
return;
|
|
8249
8635
|
}
|
|
8250
8636
|
const path = auditFilePath();
|
|
8251
|
-
mkdirSync4(
|
|
8637
|
+
mkdirSync4(join30(path, ".."), { recursive: true });
|
|
8252
8638
|
appendFileSync(path, line, "utf8");
|
|
8253
8639
|
}
|
|
8254
8640
|
async function runMcpToolHandler(tool, input, fn) {
|
|
@@ -8469,7 +8855,7 @@ function registerMcpMutatingTools(server) {
|
|
|
8469
8855
|
|
|
8470
8856
|
// src/mcp/resources.ts
|
|
8471
8857
|
import { readFile as readFile20 } from "fs/promises";
|
|
8472
|
-
import { join as
|
|
8858
|
+
import { join as join32 } from "path";
|
|
8473
8859
|
import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
8474
8860
|
|
|
8475
8861
|
// src/mcp/lint-bucket.ts
|
|
@@ -8598,8 +8984,8 @@ async function fetchQueryPreview(input) {
|
|
|
8598
8984
|
|
|
8599
8985
|
// src/mcp/graph-html.ts
|
|
8600
8986
|
import { readFile as readFile19 } from "fs/promises";
|
|
8601
|
-
import { join as
|
|
8602
|
-
import { existsSync as
|
|
8987
|
+
import { join as join31 } from "path";
|
|
8988
|
+
import { existsSync as existsSync15 } from "fs";
|
|
8603
8989
|
var TYPE_COLORS = {
|
|
8604
8990
|
entities: "#e74c3c",
|
|
8605
8991
|
concepts: "#27ae60",
|
|
@@ -8667,9 +9053,9 @@ ${nodeSvg}
|
|
|
8667
9053
|
return { html, node_count: nodes.length, edge_count: edges.length, truncated };
|
|
8668
9054
|
}
|
|
8669
9055
|
async function fetchGraphHtmlReport(input) {
|
|
8670
|
-
const graphPath = input.graphPath ??
|
|
9056
|
+
const graphPath = input.graphPath ?? join31(input.vault, ".skillwiki", "graph.json");
|
|
8671
9057
|
const maxNodes = Math.min(Math.max(10, input.maxNodes ?? 120), 500);
|
|
8672
|
-
if (!
|
|
9058
|
+
if (!existsSync15(graphPath)) {
|
|
8673
9059
|
return {
|
|
8674
9060
|
exitCode: ExitCode.FILE_NOT_FOUND,
|
|
8675
9061
|
result: err("GRAPH_MISSING", { path: graphPath, hint: "Run skillwiki.graph_build first." })
|
|
@@ -8741,7 +9127,7 @@ async function fetchStaleSummary(input) {
|
|
|
8741
9127
|
|
|
8742
9128
|
// src/mcp/resources.ts
|
|
8743
9129
|
async function readVaultFile(vault, rel) {
|
|
8744
|
-
return readFile20(
|
|
9130
|
+
return readFile20(join32(vault, rel), "utf8");
|
|
8745
9131
|
}
|
|
8746
9132
|
async function tailLines(text, lines) {
|
|
8747
9133
|
const parts = text.split(/\r?\n/);
|
|
@@ -8827,7 +9213,7 @@ function registerMcpResources(server) {
|
|
|
8827
9213
|
if (!v.ok) {
|
|
8828
9214
|
return { contents: [{ uri: uri.href, mimeType: "text/plain", text: JSON.stringify(v) }] };
|
|
8829
9215
|
}
|
|
8830
|
-
const path =
|
|
9216
|
+
const path = join32(v.data.vault, ".skillwiki", "graph.json");
|
|
8831
9217
|
try {
|
|
8832
9218
|
const raw = await readFile20(path, "utf8");
|
|
8833
9219
|
const graph = JSON.parse(raw);
|
|
@@ -9192,10 +9578,13 @@ export {
|
|
|
9192
9578
|
runConfigPath,
|
|
9193
9579
|
writeCache,
|
|
9194
9580
|
triggerAutoUpdate,
|
|
9581
|
+
buildDegradedReasons,
|
|
9582
|
+
probeRemoteHealth,
|
|
9195
9583
|
FLEET_REL_PATH,
|
|
9196
9584
|
runFleetValidate,
|
|
9197
9585
|
runFleetContext,
|
|
9198
9586
|
loadFleetManifestAndHost,
|
|
9587
|
+
snapshotterAliasForLocalHost,
|
|
9199
9588
|
loadFleetManifest,
|
|
9200
9589
|
resolveFleetHostId,
|
|
9201
9590
|
SATELLITE_STALE_MS,
|