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
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
ExitCode,
|
|
4
|
+
FLEET_REL_PATH,
|
|
5
|
+
SATELLITE_STALE_MS,
|
|
4
6
|
TypedKnowledgeSchema,
|
|
5
7
|
appendLastOp,
|
|
6
8
|
assessSourceIdentity,
|
|
@@ -8,12 +10,15 @@ import {
|
|
|
8
10
|
configPath,
|
|
9
11
|
detectSchema,
|
|
10
12
|
err,
|
|
13
|
+
evaluateSatelliteRunHealth,
|
|
11
14
|
extractCitationMarkers,
|
|
12
15
|
extractFrontmatter,
|
|
13
16
|
extractTaxonomy,
|
|
14
17
|
findPlugin,
|
|
15
18
|
fixPathTooLong,
|
|
16
19
|
isBlockedHost,
|
|
20
|
+
isFailedRunStatus,
|
|
21
|
+
loadFleetManifest,
|
|
17
22
|
ok,
|
|
18
23
|
parseDotenvFile,
|
|
19
24
|
parseDotenvText,
|
|
@@ -21,6 +26,8 @@ import {
|
|
|
21
26
|
readCliPackageJson,
|
|
22
27
|
readLastOp,
|
|
23
28
|
readPage,
|
|
29
|
+
readSatelliteLatestRunFromText,
|
|
30
|
+
resolveFleetHostId,
|
|
24
31
|
resolveInitTimePath,
|
|
25
32
|
resolveRuntimePath,
|
|
26
33
|
runAudit,
|
|
@@ -32,6 +39,7 @@ import {
|
|
|
32
39
|
runDoctor,
|
|
33
40
|
runFleetContext,
|
|
34
41
|
runFleetValidate,
|
|
42
|
+
runFrontmatterFix,
|
|
35
43
|
runGraphBuild,
|
|
36
44
|
runIndexCheck,
|
|
37
45
|
runIndexLinkFormat,
|
|
@@ -54,19 +62,20 @@ import {
|
|
|
54
62
|
runTopicMapCheck,
|
|
55
63
|
runValidate,
|
|
56
64
|
safeWritePage,
|
|
65
|
+
satelliteLatestRunPath,
|
|
57
66
|
scanSensitiveContent,
|
|
58
67
|
scanVault,
|
|
59
68
|
splitFrontmatter,
|
|
60
69
|
triggerAutoUpdate,
|
|
61
70
|
writeCache,
|
|
62
71
|
writeDotenv
|
|
63
|
-
} from "./chunk-
|
|
72
|
+
} from "./chunk-6G7RAAOS.js";
|
|
64
73
|
import {
|
|
65
74
|
normalizeDistTag
|
|
66
75
|
} from "./chunk-E6UWZ3S3.js";
|
|
67
76
|
|
|
68
77
|
// src/cli.ts
|
|
69
|
-
import { join as
|
|
78
|
+
import { join as join25 } from "path";
|
|
70
79
|
import { Command } from "commander";
|
|
71
80
|
|
|
72
81
|
// src/utils/output.ts
|
|
@@ -1760,82 +1769,6 @@ ${migratedBody}${newFooter}`;
|
|
|
1760
1769
|
};
|
|
1761
1770
|
}
|
|
1762
1771
|
|
|
1763
|
-
// src/commands/frontmatter-fix.ts
|
|
1764
|
-
function isoToday() {
|
|
1765
|
-
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
1766
|
-
}
|
|
1767
|
-
function fixFrontmatter(rawFm) {
|
|
1768
|
-
const additions = [];
|
|
1769
|
-
if (!/^created:/m.test(rawFm)) additions.push(`created: ${isoToday()}`);
|
|
1770
|
-
if (!/^updated:/m.test(rawFm)) additions.push(`updated: ${isoToday()}`);
|
|
1771
|
-
if (!/^tags:/m.test(rawFm)) additions.push("tags: []");
|
|
1772
|
-
if (!/^sources:/m.test(rawFm)) additions.push("sources: []");
|
|
1773
|
-
if (!/^provenance:/m.test(rawFm)) additions.push("provenance: research");
|
|
1774
|
-
if (additions.length === 0) return rawFm;
|
|
1775
|
-
return rawFm.trimEnd() + "\n" + additions.join("\n") + "\n";
|
|
1776
|
-
}
|
|
1777
|
-
function removeOrphanTagsLines(body) {
|
|
1778
|
-
return body.split("\n").filter((line) => !/^tags:\s*\[/.test(line.trim())).join("\n");
|
|
1779
|
-
}
|
|
1780
|
-
async function runFrontmatterFix(input) {
|
|
1781
|
-
const scan = await scanVault(input.vault);
|
|
1782
|
-
if (!scan.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scan };
|
|
1783
|
-
const fixed = [];
|
|
1784
|
-
const skipped = [];
|
|
1785
|
-
let unchanged = 0;
|
|
1786
|
-
for (const page of scan.data.typedKnowledge) {
|
|
1787
|
-
const text = await readPage(page);
|
|
1788
|
-
const split = splitFrontmatter(text);
|
|
1789
|
-
if (!split.ok) {
|
|
1790
|
-
skipped.push(page.relPath);
|
|
1791
|
-
continue;
|
|
1792
|
-
}
|
|
1793
|
-
const { rawFrontmatter, body } = split.data;
|
|
1794
|
-
const newFm = fixFrontmatter(rawFrontmatter);
|
|
1795
|
-
const newBody = removeOrphanTagsLines(body);
|
|
1796
|
-
const newText = `---
|
|
1797
|
-
${newFm}
|
|
1798
|
-
---
|
|
1799
|
-
${newBody}`;
|
|
1800
|
-
if (newText === text) {
|
|
1801
|
-
unchanged++;
|
|
1802
|
-
continue;
|
|
1803
|
-
}
|
|
1804
|
-
if (!input.dryRun) {
|
|
1805
|
-
const w = await safeWritePage(page.absPath, newText);
|
|
1806
|
-
if (!w.ok) {
|
|
1807
|
-
skipped.push(page.relPath);
|
|
1808
|
-
continue;
|
|
1809
|
-
}
|
|
1810
|
-
}
|
|
1811
|
-
fixed.push(page.relPath);
|
|
1812
|
-
}
|
|
1813
|
-
const exitCode = fixed.length > 0 ? ExitCode.MIGRATION_APPLIED : ExitCode.OK;
|
|
1814
|
-
const hintLines = [`scanned: ${fixed.length + skipped.length + unchanged}`];
|
|
1815
|
-
if (fixed.length > 0) hintLines.push(`fixed: ${fixed.length}`);
|
|
1816
|
-
if (skipped.length > 0) hintLines.push(`skipped (parse error): ${skipped.length}`);
|
|
1817
|
-
if (unchanged > 0) hintLines.push(`unchanged: ${unchanged}`);
|
|
1818
|
-
if (input.dryRun && fixed.length > 0) hintLines.push("(dry run \u2014 no files written)");
|
|
1819
|
-
if (!input.dryRun && fixed.length > 0) {
|
|
1820
|
-
appendLastOp(input.vault, {
|
|
1821
|
-
operation: "frontmatter-fix",
|
|
1822
|
-
summary: `normalized frontmatter on ${fixed.length} page(s)`,
|
|
1823
|
-
files: fixed,
|
|
1824
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1825
|
-
});
|
|
1826
|
-
}
|
|
1827
|
-
return {
|
|
1828
|
-
exitCode,
|
|
1829
|
-
result: ok({
|
|
1830
|
-
scanned: fixed.length + skipped.length + unchanged,
|
|
1831
|
-
fixed,
|
|
1832
|
-
skipped,
|
|
1833
|
-
unchanged,
|
|
1834
|
-
humanHint: hintLines.join("\n")
|
|
1835
|
-
})
|
|
1836
|
-
};
|
|
1837
|
-
}
|
|
1838
|
-
|
|
1839
1772
|
// src/commands/update.ts
|
|
1840
1773
|
import { execSync } from "child_process";
|
|
1841
1774
|
import { join as join11 } from "path";
|
|
@@ -2418,7 +2351,10 @@ async function runSessionBrief(input) {
|
|
|
2418
2351
|
const transcripts = await loadTranscriptInfo(scan.data.raw);
|
|
2419
2352
|
const workItems = await loadWorkItems(scan.data.workItems);
|
|
2420
2353
|
const digests = await loadTrendDigests(scan.data.typedKnowledge);
|
|
2421
|
-
const healthWarnings =
|
|
2354
|
+
const healthWarnings = [
|
|
2355
|
+
...await loadHealthWarnings(input.vault),
|
|
2356
|
+
...satelliteHealthWarnings(await loadSatelliteHealth(input.vault))
|
|
2357
|
+
];
|
|
2422
2358
|
const memoryTopics = project ? await loadMemoryTopics(input.vault, project) : [];
|
|
2423
2359
|
const latestLogs = newest(transcripts.filter((t) => t.kind === "session-log"), 3);
|
|
2424
2360
|
const unclaimedCaptures = newest(transcripts.filter((t) => {
|
|
@@ -2628,6 +2564,31 @@ function appendTextSection(lines, title, items, empty) {
|
|
|
2628
2564
|
}
|
|
2629
2565
|
lines.push("");
|
|
2630
2566
|
}
|
|
2567
|
+
async function loadSatelliteHealth(vaultPath) {
|
|
2568
|
+
const health = evaluateSatelliteRunHealth(vaultPath, /* @__PURE__ */ new Date());
|
|
2569
|
+
if (!health.failed && !health.stale) {
|
|
2570
|
+
const runPath2 = satelliteLatestRunPath(vaultPath);
|
|
2571
|
+
const hasRun = await readIfExists(runPath2);
|
|
2572
|
+
if (!hasRun) return null;
|
|
2573
|
+
}
|
|
2574
|
+
if (health.failed) {
|
|
2575
|
+
const fc = health.failureClass && health.failureClass.length > 0 ? health.failureClass : "unknown";
|
|
2576
|
+
return `agent-memory-trends: last run failed (${fc})`;
|
|
2577
|
+
}
|
|
2578
|
+
if (health.stale && health.finishedAt) {
|
|
2579
|
+
const finishedMs = Date.parse(health.finishedAt);
|
|
2580
|
+
if (!Number.isNaN(finishedMs)) {
|
|
2581
|
+
const ageHours = (Date.now() - finishedMs) / (60 * 60 * 1e3);
|
|
2582
|
+
const hoursRounded = Math.floor(ageHours);
|
|
2583
|
+
const datePart = health.finishedAt.slice(0, 10);
|
|
2584
|
+
return `agent-memory-trends: no run in ${hoursRounded}h (last: ${datePart})`;
|
|
2585
|
+
}
|
|
2586
|
+
}
|
|
2587
|
+
return null;
|
|
2588
|
+
}
|
|
2589
|
+
function satelliteHealthWarnings(warning) {
|
|
2590
|
+
return warning ? [warning] : [];
|
|
2591
|
+
}
|
|
2631
2592
|
async function loadHealthWarnings(vault) {
|
|
2632
2593
|
const text = await readIfExists(join15(vault, ".skillwiki", "health.json"));
|
|
2633
2594
|
if (!text) return [];
|
|
@@ -4356,16 +4317,333 @@ written: ${outPath}`
|
|
|
4356
4317
|
};
|
|
4357
4318
|
}
|
|
4358
4319
|
|
|
4359
|
-
// src/
|
|
4360
|
-
import { existsSync as existsSync10 } from "fs";
|
|
4320
|
+
// src/commands/fleet-health.ts
|
|
4321
|
+
import { existsSync as existsSync10, readFileSync as readFileSync6 } from "fs";
|
|
4322
|
+
import { execSync as nodeExecSync } from "child_process";
|
|
4323
|
+
import { hostname as nodeHostname, platform as nodePlatform } from "os";
|
|
4361
4324
|
import { join as join23 } from "path";
|
|
4325
|
+
var SSH_TIMEOUT_MS = 15e3;
|
|
4326
|
+
var TIMER_UNIT = "agent-memory-trends.timer";
|
|
4327
|
+
var SERVICE_UNIT = "agent-memory-trends.service";
|
|
4328
|
+
var SYSTEMD_SERVICE_FAILED_CLASS = "SYSTEMD_SERVICE_FAILED";
|
|
4329
|
+
function defaultDeps() {
|
|
4330
|
+
return {
|
|
4331
|
+
platform: () => nodePlatform(),
|
|
4332
|
+
execSync: nodeExecSync
|
|
4333
|
+
};
|
|
4334
|
+
}
|
|
4335
|
+
function formatAgeHours(finishedAt) {
|
|
4336
|
+
if (!finishedAt) return "never";
|
|
4337
|
+
const ts = Date.parse(finishedAt);
|
|
4338
|
+
if (!Number.isFinite(ts)) return "unknown";
|
|
4339
|
+
const hours = Math.floor((Date.now() - ts) / (60 * 60 * 1e3));
|
|
4340
|
+
if (hours < 1) return "<1h";
|
|
4341
|
+
return `${hours}h`;
|
|
4342
|
+
}
|
|
4343
|
+
function deriveRunFields(parsed) {
|
|
4344
|
+
if (!parsed) {
|
|
4345
|
+
return {
|
|
4346
|
+
last_run_status: "unknown",
|
|
4347
|
+
last_run_age: "never",
|
|
4348
|
+
failure_class: "-",
|
|
4349
|
+
runUnhealthy: false
|
|
4350
|
+
};
|
|
4351
|
+
}
|
|
4352
|
+
const finishedAt = parsed.finished_at;
|
|
4353
|
+
const age = formatAgeHours(finishedAt);
|
|
4354
|
+
const fc = parsed.failure_class != null && String(parsed.failure_class).length > 0 ? String(parsed.failure_class) : "-";
|
|
4355
|
+
if (isFailedRunStatus(parsed.status ?? "")) {
|
|
4356
|
+
return {
|
|
4357
|
+
last_run_status: "fail",
|
|
4358
|
+
last_run_age: age,
|
|
4359
|
+
failure_class: fc === "-" ? "fail" : fc,
|
|
4360
|
+
runUnhealthy: true
|
|
4361
|
+
};
|
|
4362
|
+
}
|
|
4363
|
+
if (finishedAt) {
|
|
4364
|
+
const ts = Date.parse(finishedAt);
|
|
4365
|
+
if (Number.isFinite(ts) && Date.now() - ts > SATELLITE_STALE_MS) {
|
|
4366
|
+
return {
|
|
4367
|
+
last_run_status: "success",
|
|
4368
|
+
last_run_age: age,
|
|
4369
|
+
failure_class: "-",
|
|
4370
|
+
runUnhealthy: true
|
|
4371
|
+
};
|
|
4372
|
+
}
|
|
4373
|
+
}
|
|
4374
|
+
if (parsed.status === "success" || parsed.status === "ok") {
|
|
4375
|
+
return {
|
|
4376
|
+
last_run_status: "success",
|
|
4377
|
+
last_run_age: age,
|
|
4378
|
+
failure_class: "-",
|
|
4379
|
+
runUnhealthy: false
|
|
4380
|
+
};
|
|
4381
|
+
}
|
|
4382
|
+
return {
|
|
4383
|
+
last_run_status: parsed.status ? "unknown" : "none",
|
|
4384
|
+
last_run_age: age,
|
|
4385
|
+
failure_class: "-",
|
|
4386
|
+
runUnhealthy: false
|
|
4387
|
+
};
|
|
4388
|
+
}
|
|
4389
|
+
function systemctlPropLocal(deps, unit, prop) {
|
|
4390
|
+
if (deps.platform() !== "linux") return null;
|
|
4391
|
+
try {
|
|
4392
|
+
return deps.execSync(`systemctl ${prop} ${unit}`, {
|
|
4393
|
+
encoding: "utf8",
|
|
4394
|
+
timeout: 2e3,
|
|
4395
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4396
|
+
}).trim();
|
|
4397
|
+
} catch {
|
|
4398
|
+
return null;
|
|
4399
|
+
}
|
|
4400
|
+
}
|
|
4401
|
+
function systemctlIsActiveLocal(deps, unit) {
|
|
4402
|
+
const out = systemctlPropLocal(deps, unit, "is-active");
|
|
4403
|
+
if (out === null) return "unknown";
|
|
4404
|
+
return out === "active" ? "active" : "inactive";
|
|
4405
|
+
}
|
|
4406
|
+
function systemctlIsFailedLocal(deps, unit) {
|
|
4407
|
+
return systemctlPropLocal(deps, unit, "is-failed") === "failed";
|
|
4408
|
+
}
|
|
4409
|
+
function applyServiceFailedOverlay(run, serviceFailed) {
|
|
4410
|
+
if (!serviceFailed) return run;
|
|
4411
|
+
return {
|
|
4412
|
+
...run,
|
|
4413
|
+
last_run_status: "fail",
|
|
4414
|
+
failure_class: run.failure_class === "-" ? SYSTEMD_SERVICE_FAILED_CLASS : run.failure_class,
|
|
4415
|
+
runUnhealthy: true
|
|
4416
|
+
};
|
|
4417
|
+
}
|
|
4418
|
+
function probeLocal(vaultPath, deps) {
|
|
4419
|
+
const latestPath = satelliteLatestRunPath(vaultPath);
|
|
4420
|
+
let parsed = null;
|
|
4421
|
+
if (existsSync10(latestPath)) {
|
|
4422
|
+
try {
|
|
4423
|
+
const wire = readSatelliteLatestRunFromText(readFileSync6(latestPath, "utf8"));
|
|
4424
|
+
if (wire) {
|
|
4425
|
+
parsed = {
|
|
4426
|
+
status: wire.status,
|
|
4427
|
+
finished_at: wire.finishedAt,
|
|
4428
|
+
failure_class: wire.failureClass ?? null
|
|
4429
|
+
};
|
|
4430
|
+
}
|
|
4431
|
+
} catch {
|
|
4432
|
+
parsed = null;
|
|
4433
|
+
}
|
|
4434
|
+
}
|
|
4435
|
+
const timer = systemctlIsActiveLocal(deps, TIMER_UNIT);
|
|
4436
|
+
const serviceFailed = systemctlIsFailedLocal(deps, SERVICE_UNIT);
|
|
4437
|
+
const run = applyServiceFailedOverlay(deriveRunFields(parsed), serviceFailed);
|
|
4438
|
+
const timerBad = deps.platform() === "linux" && timer === "inactive";
|
|
4439
|
+
return {
|
|
4440
|
+
timer,
|
|
4441
|
+
last_run_status: run.last_run_status,
|
|
4442
|
+
last_run_age: run.last_run_age,
|
|
4443
|
+
failure_class: run.failure_class,
|
|
4444
|
+
reachable: "yes",
|
|
4445
|
+
runUnhealthy: run.runUnhealthy,
|
|
4446
|
+
timerBad
|
|
4447
|
+
};
|
|
4448
|
+
}
|
|
4449
|
+
function probeRemote(sshAlias, vaultPath, deps) {
|
|
4450
|
+
const latest = satelliteLatestRunPath(vaultPath);
|
|
4451
|
+
const remoteCmd = [
|
|
4452
|
+
`cat ${shellQuote2(latest)} 2>/dev/null || true`,
|
|
4453
|
+
"echo __SW_TIMER__",
|
|
4454
|
+
`systemctl is-active ${TIMER_UNIT} 2>/dev/null || echo inactive`,
|
|
4455
|
+
"echo __SW_FAILED__",
|
|
4456
|
+
`systemctl is-failed ${SERVICE_UNIT} 2>/dev/null || echo unknown`
|
|
4457
|
+
].join("\n");
|
|
4458
|
+
const sshCmd = `ssh -o ConnectTimeout=10 ${sshAlias} ${shellQuote2(remoteCmd)}`;
|
|
4459
|
+
try {
|
|
4460
|
+
const out = deps.execSync(sshCmd, {
|
|
4461
|
+
encoding: "utf8",
|
|
4462
|
+
timeout: SSH_TIMEOUT_MS,
|
|
4463
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4464
|
+
});
|
|
4465
|
+
const timerMarker = "__SW_TIMER__";
|
|
4466
|
+
const failedMarker = "__SW_FAILED__";
|
|
4467
|
+
const timerIdx = out.indexOf(timerMarker);
|
|
4468
|
+
const jsonPart = timerIdx >= 0 ? out.slice(0, timerIdx) : out;
|
|
4469
|
+
const afterTimer = timerIdx >= 0 ? out.slice(timerIdx + timerMarker.length) : "";
|
|
4470
|
+
const failedIdx = afterTimer.indexOf(failedMarker);
|
|
4471
|
+
const timerRaw = failedIdx >= 0 ? afterTimer.slice(0, failedIdx) : afterTimer;
|
|
4472
|
+
const failedRaw = failedIdx >= 0 ? afterTimer.slice(failedIdx + failedMarker.length) : "";
|
|
4473
|
+
const jsonText = jsonPart.trim();
|
|
4474
|
+
const parsed = jsonText.startsWith("{") ? (() => {
|
|
4475
|
+
const wire = readSatelliteLatestRunFromText(jsonText);
|
|
4476
|
+
if (!wire) return null;
|
|
4477
|
+
return {
|
|
4478
|
+
status: wire.status,
|
|
4479
|
+
finished_at: wire.finishedAt,
|
|
4480
|
+
failure_class: wire.failureClass ?? null
|
|
4481
|
+
};
|
|
4482
|
+
})() : null;
|
|
4483
|
+
const timerLine = timerRaw.trim().split("\n")[0]?.trim() ?? "unknown";
|
|
4484
|
+
let timer = "unknown";
|
|
4485
|
+
if (timerLine === "active") timer = "active";
|
|
4486
|
+
else if (timerLine === "inactive") timer = "inactive";
|
|
4487
|
+
const failedLine = failedRaw.trim().split("\n")[0]?.trim() ?? "unknown";
|
|
4488
|
+
const serviceFailed = failedLine === "failed";
|
|
4489
|
+
const run = applyServiceFailedOverlay(deriveRunFields(parsed), serviceFailed);
|
|
4490
|
+
const timerBad = timer === "inactive";
|
|
4491
|
+
return {
|
|
4492
|
+
timer,
|
|
4493
|
+
last_run_status: run.last_run_status,
|
|
4494
|
+
last_run_age: run.last_run_age,
|
|
4495
|
+
failure_class: run.failure_class,
|
|
4496
|
+
reachable: "yes",
|
|
4497
|
+
runUnhealthy: run.runUnhealthy,
|
|
4498
|
+
timerBad
|
|
4499
|
+
};
|
|
4500
|
+
} catch {
|
|
4501
|
+
return {
|
|
4502
|
+
timer: "unknown",
|
|
4503
|
+
last_run_status: "unknown",
|
|
4504
|
+
last_run_age: "never",
|
|
4505
|
+
failure_class: "-",
|
|
4506
|
+
reachable: "no",
|
|
4507
|
+
runUnhealthy: true,
|
|
4508
|
+
timerBad: true
|
|
4509
|
+
};
|
|
4510
|
+
}
|
|
4511
|
+
}
|
|
4512
|
+
function shellQuote2(s) {
|
|
4513
|
+
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
4514
|
+
}
|
|
4515
|
+
function satelliteHosts(manifest) {
|
|
4516
|
+
const out = [];
|
|
4517
|
+
for (const [id, host] of Object.entries(manifest.hosts)) {
|
|
4518
|
+
const sat = host.maintenance?.skillwiki_satellite;
|
|
4519
|
+
if (sat?.enabled === true) {
|
|
4520
|
+
out.push({ id, vaultPath: sat.vault_path, sshAlias: sat.ssh_alias, host });
|
|
4521
|
+
}
|
|
4522
|
+
}
|
|
4523
|
+
return out.sort((a, b) => a.id.localeCompare(b.id));
|
|
4524
|
+
}
|
|
4525
|
+
function hasDeclaredSshAccess(host, localHostId, satelliteAlias) {
|
|
4526
|
+
const profile = host.access?.from?.[localHostId];
|
|
4527
|
+
if (!profile) return false;
|
|
4528
|
+
if (profile.status !== "configured" && profile.status !== "local") return false;
|
|
4529
|
+
const aliases = profile.ssh_aliases ?? [];
|
|
4530
|
+
return aliases.includes(satelliteAlias);
|
|
4531
|
+
}
|
|
4532
|
+
function formatTable(rows) {
|
|
4533
|
+
if (rows.length === 0) return "no satellite hosts configured";
|
|
4534
|
+
const header = "host | timer | last_run_status | last_run_age | failure_class | reachable";
|
|
4535
|
+
const lines = rows.map(
|
|
4536
|
+
(r) => `${r.host} | ${r.timer} | ${r.last_run_status} | ${r.last_run_age} | ${r.failure_class} | ${r.reachable}`
|
|
4537
|
+
);
|
|
4538
|
+
return [header, ...lines].join("\n");
|
|
4539
|
+
}
|
|
4540
|
+
function rowHealthy(reachable, timer, runUnhealthy, timerBad, platform2) {
|
|
4541
|
+
if (reachable === "no-access") return true;
|
|
4542
|
+
if (reachable === "no") return false;
|
|
4543
|
+
if (runUnhealthy) return false;
|
|
4544
|
+
if (platform2 === "linux" && timerBad) return false;
|
|
4545
|
+
if (platform2 === "linux" && timer === "inactive") return false;
|
|
4546
|
+
return true;
|
|
4547
|
+
}
|
|
4548
|
+
async function runFleetHealth(input) {
|
|
4549
|
+
const deps = input.deps ?? defaultDeps();
|
|
4550
|
+
const env = input.env ?? process.env;
|
|
4551
|
+
const home = input.home ?? env.HOME ?? "";
|
|
4552
|
+
const osHostname = input.osHostname ?? env.HOSTNAME ?? nodeHostname();
|
|
4553
|
+
const vault = input.vault ?? env.WIKI_PATH;
|
|
4554
|
+
const file = input.file ?? (vault ? join23(vault, FLEET_REL_PATH) : void 0);
|
|
4555
|
+
if (!file) {
|
|
4556
|
+
return {
|
|
4557
|
+
exitCode: ExitCode.NO_VAULT_CONFIGURED,
|
|
4558
|
+
result: err("NO_VAULT_CONFIGURED", { message: "vault path or --file required" })
|
|
4559
|
+
};
|
|
4560
|
+
}
|
|
4561
|
+
const loaded = await loadFleetManifest(file);
|
|
4562
|
+
if (!loaded.ok) {
|
|
4563
|
+
if (loaded.error === "FILE_NOT_FOUND") {
|
|
4564
|
+
return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path: file }) };
|
|
4565
|
+
}
|
|
4566
|
+
return {
|
|
4567
|
+
exitCode: ExitCode.FLEET_MANIFEST_INVALID,
|
|
4568
|
+
result: err("INVALID_FLEET_MANIFEST", { path: file })
|
|
4569
|
+
};
|
|
4570
|
+
}
|
|
4571
|
+
const resolved = await resolveFleetHostId({
|
|
4572
|
+
manifest: loaded.manifest,
|
|
4573
|
+
hostId: input.hostId,
|
|
4574
|
+
env,
|
|
4575
|
+
home,
|
|
4576
|
+
osHostname
|
|
4577
|
+
});
|
|
4578
|
+
const localHostId = resolved.hostId;
|
|
4579
|
+
const targets = satelliteHosts(loaded.manifest);
|
|
4580
|
+
if (targets.length === 0) {
|
|
4581
|
+
const humanHint2 = "no satellite hosts configured";
|
|
4582
|
+
return {
|
|
4583
|
+
exitCode: ExitCode.OK,
|
|
4584
|
+
result: ok({ hosts: [], humanHint: humanHint2 })
|
|
4585
|
+
};
|
|
4586
|
+
}
|
|
4587
|
+
const rows = [];
|
|
4588
|
+
for (const t of targets) {
|
|
4589
|
+
const isLocal = localHostId === t.id;
|
|
4590
|
+
if (isLocal) {
|
|
4591
|
+
const p = probeLocal(t.vaultPath, deps);
|
|
4592
|
+
const healthy = rowHealthy(p.reachable, p.timer, p.runUnhealthy, p.timerBad, deps.platform());
|
|
4593
|
+
rows.push({
|
|
4594
|
+
host: t.id,
|
|
4595
|
+
timer: p.timer,
|
|
4596
|
+
last_run_status: p.last_run_status,
|
|
4597
|
+
last_run_age: p.last_run_age,
|
|
4598
|
+
failure_class: p.failure_class,
|
|
4599
|
+
reachable: p.reachable,
|
|
4600
|
+
healthy
|
|
4601
|
+
});
|
|
4602
|
+
} else {
|
|
4603
|
+
if (!hasDeclaredSshAccess(t.host, localHostId, t.sshAlias)) {
|
|
4604
|
+
rows.push({
|
|
4605
|
+
host: t.id,
|
|
4606
|
+
timer: "unknown",
|
|
4607
|
+
last_run_status: "unknown",
|
|
4608
|
+
last_run_age: "never",
|
|
4609
|
+
failure_class: "-",
|
|
4610
|
+
reachable: "no-access",
|
|
4611
|
+
healthy: true
|
|
4612
|
+
});
|
|
4613
|
+
continue;
|
|
4614
|
+
}
|
|
4615
|
+
const p = probeRemote(t.sshAlias, t.vaultPath, deps);
|
|
4616
|
+
const healthy = rowHealthy(p.reachable, p.timer, p.runUnhealthy, p.timerBad, "linux");
|
|
4617
|
+
rows.push({
|
|
4618
|
+
host: t.id,
|
|
4619
|
+
timer: p.timer,
|
|
4620
|
+
last_run_status: p.last_run_status,
|
|
4621
|
+
last_run_age: p.last_run_age,
|
|
4622
|
+
failure_class: p.failure_class,
|
|
4623
|
+
reachable: p.reachable,
|
|
4624
|
+
healthy
|
|
4625
|
+
});
|
|
4626
|
+
}
|
|
4627
|
+
}
|
|
4628
|
+
const allHealthy = rows.every((r) => r.healthy);
|
|
4629
|
+
const humanHint = formatTable(rows);
|
|
4630
|
+
const exitCode = allHealthy ? ExitCode.OK : ExitCode.FLEET_SATELLITE_HEALTH_FAILED;
|
|
4631
|
+
return {
|
|
4632
|
+
exitCode,
|
|
4633
|
+
result: ok({ hosts: rows, humanHint })
|
|
4634
|
+
};
|
|
4635
|
+
}
|
|
4636
|
+
|
|
4637
|
+
// src/utils/auto-commit.ts
|
|
4638
|
+
import { existsSync as existsSync11 } from "fs";
|
|
4639
|
+
import { join as join24 } from "path";
|
|
4362
4640
|
async function postCommit(vault, exitCode) {
|
|
4363
4641
|
if (exitCode !== 0) return;
|
|
4364
4642
|
const home = process.env.HOME ?? "";
|
|
4365
4643
|
const dotenv = await parseDotenvFile(configPath(home));
|
|
4366
4644
|
const autoCommit = process.env.AUTO_COMMIT ?? dotenv["AUTO_COMMIT"];
|
|
4367
4645
|
if (autoCommit === "false") return;
|
|
4368
|
-
if (!
|
|
4646
|
+
if (!existsSync11(join24(vault, ".git"))) return;
|
|
4369
4647
|
const lastOps = readLastOp(vault);
|
|
4370
4648
|
if (lastOps.length === 0) return;
|
|
4371
4649
|
const porcelain = git(vault, ["status", "--porcelain", "--", ...VAULT_COMMIT_PATHSPEC]);
|
|
@@ -4411,7 +4689,7 @@ program.command("validate <file>").description("validate vault page frontmatter
|
|
|
4411
4689
|
emit(await runValidate({ file, apply: !!opts.apply, vault }), vault);
|
|
4412
4690
|
});
|
|
4413
4691
|
program.command("graph").description("graph subcommands").command("build <vault>").option("--out <path>", "graph output path (default: <vault>/.skillwiki/graph.json)").option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
|
|
4414
|
-
const out = opts.out ??
|
|
4692
|
+
const out = opts.out ?? join25(vault, ".skillwiki", "graph.json");
|
|
4415
4693
|
emit(await runGraphBuild({ vault, out }), vault);
|
|
4416
4694
|
});
|
|
4417
4695
|
var canvasCmd = program.command("canvas").description("manage Obsidian canvas files");
|
|
@@ -4860,6 +5138,20 @@ fleetCmd.command("context [vault]").description("render compact Runtime Host Con
|
|
|
4860
5138
|
user: process.env.USER
|
|
4861
5139
|
}));
|
|
4862
5140
|
});
|
|
5141
|
+
fleetCmd.command("health [vault]").description("read-only health probe for skillwiki satellite hosts").option("--file <path>", "fleet manifest path").option("--host-id <id>", "explicit current fleet host id").option("--json", "emit JSON result").action(async (vault, opts) => {
|
|
5142
|
+
const r = await runFleetHealth({
|
|
5143
|
+
vault,
|
|
5144
|
+
file: opts.file,
|
|
5145
|
+
hostId: opts.hostId,
|
|
5146
|
+
json: !!opts.json,
|
|
5147
|
+
env: process.env,
|
|
5148
|
+
home: process.env.HOME ?? "",
|
|
5149
|
+
cwd: process.cwd(),
|
|
5150
|
+
osHostname: process.env.HOSTNAME,
|
|
5151
|
+
user: process.env.USER
|
|
5152
|
+
});
|
|
5153
|
+
emit(r, vault);
|
|
5154
|
+
});
|
|
4863
5155
|
program.command("mcp").description("start stdio Model Context Protocol server (read-only vault tools)").action(async () => {
|
|
4864
5156
|
await runSkillwikiMcpStdio();
|
|
4865
5157
|
});
|
package/dist/skillwiki-mcp.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skillwiki",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.31",
|
|
4
4
|
"skills": "./",
|
|
5
5
|
"description": "Project-aware Karpathy-style knowledge base for Claude Code: 18 prompt-only skills (wiki-*, proj-*, using-skillwiki) backed by the deterministic `skillwiki` CLI.",
|
|
6
6
|
"author": {
|
package/skills/package.json
CHANGED
|
@@ -32,6 +32,7 @@ When reading retros as source material:
|
|
|
32
32
|
`provenance: project` and
|
|
33
33
|
`provenance_projects: ["[[slug]]"]`. Validate with
|
|
34
34
|
`skillwiki validate`.
|
|
35
|
+
- **Portable source references:** follow `using-skillwiki` → Portable Source References (commit-pinned GitHub URLs or repo-relative paths; not canonical `Source file:` / `Source inspected:` host paths).
|
|
35
36
|
- **Tag hygiene:** `tags:` must only contain entries from
|
|
36
37
|
`{vault}/SCHEMA.md` taxonomy. Never derive tags from prose text
|
|
37
38
|
(lesson/evidence sections) — use only established taxonomy tags
|
|
@@ -53,6 +53,7 @@ Rules:
|
|
|
53
53
|
- Resolve `<vault-root>` via `skillwiki path` (never hardcode).
|
|
54
54
|
- proj-work does NOT invoke any PRD skill — it provides paths only.
|
|
55
55
|
- If the PRD skill cannot accept custom save paths, fall back to manual `wiki-ingest`.
|
|
56
|
+
- When `spec.md` or `plan.md` mentions repo files, follow `using-skillwiki` → Portable Source References.
|
|
56
57
|
|
|
57
58
|
## Pitfalls
|
|
58
59
|
- **Wiki-as-truth fallacy**: tasks.md status markers are aspirational claims by previous sessions. They are often wrong. Always audit the actual file system before accepting a "DONE" label.
|
|
@@ -32,6 +32,7 @@ When reading retros as source material:
|
|
|
32
32
|
`provenance: project` and
|
|
33
33
|
`provenance_projects: ["[[slug]]"]`. Validate with
|
|
34
34
|
`skillwiki validate`.
|
|
35
|
+
- **Portable source references:** follow `using-skillwiki` → Portable Source References (commit-pinned GitHub URLs or repo-relative paths; not canonical `Source file:` / `Source inspected:` host paths).
|
|
35
36
|
- **Tag hygiene:** `tags:` must only contain entries from
|
|
36
37
|
`{vault}/SCHEMA.md` taxonomy. Never derive tags from prose text
|
|
37
38
|
(lesson/evidence sections) — use only established taxonomy tags
|
|
@@ -53,6 +53,7 @@ Rules:
|
|
|
53
53
|
- Resolve `<vault-root>` via `skillwiki path` (never hardcode).
|
|
54
54
|
- proj-work does NOT invoke any PRD skill — it provides paths only.
|
|
55
55
|
- If the PRD skill cannot accept custom save paths, fall back to manual `wiki-ingest`.
|
|
56
|
+
- When `spec.md` or `plan.md` mentions repo files, follow `using-skillwiki` → Portable Source References.
|
|
56
57
|
|
|
57
58
|
## Pitfalls
|
|
58
59
|
- **Wiki-as-truth fallacy**: tasks.md status markers are aspirational claims by previous sessions. They are often wrong. Always audit the actual file system before accepting a "DONE" label.
|
|
@@ -59,6 +59,15 @@ sha256: # computed by skillwiki hash over body bytes after closing ---
|
|
|
59
59
|
## Sensitive Content Policy
|
|
60
60
|
Vault content must not contain live credentials, access keys, tokens, passwords, cookies, bearer headers, private keys, or other authenticating secrets. This includes development-only and local-only credentials. Redact values before filing using `[REDACTED:<kind>]` or `[REDACTED:<kind>:<fingerprint>]`. If a source contains live secrets, stop and ask for a redacted source or explicit rotation/remediation direction; do not preserve the secret in `raw/`.
|
|
61
61
|
|
|
62
|
+
## Portable Source References
|
|
63
|
+
The vault is shared across hosts, so host-local absolute paths are not durable source identity.
|
|
64
|
+
|
|
65
|
+
- Prefer commit-pinned GitHub URLs when the source file is in a pushed repository and the commit is known.
|
|
66
|
+
- Otherwise prefer repo-relative identity in prose, such as repo slug + relative path.
|
|
67
|
+
- Use vault-relative references or `[[wikilinks]]` for pages already inside the wiki.
|
|
68
|
+
- Keep host-local absolute paths (`/Users/...`, `/home/...`, `file:///...`) only as clearly labeled observations such as `Observed on host: ...`, not as canonical `Source file:` or `Source inspected:` lines.
|
|
69
|
+
- Do not use markdown links to local vault files when a `[[wikilink]]` should be used instead.
|
|
70
|
+
|
|
62
71
|
### Ad-hoc capture: three entry points
|
|
63
72
|
| Entry | When | What happens |
|
|
64
73
|
|-------|------|-------------|
|
|
@@ -17,6 +17,7 @@ Standard four reads. If cwd is inside `projects/{slug}/`, also read project READ
|
|
|
17
17
|
3. Compose the page with citations pre-attached. Reuse existing `raw/` sources where possible. Every page MUST include:
|
|
18
18
|
- `> **TL;DR:**` blockquote as the first content after the title heading — a one-sentence summary of the page's key takeaway (under 200 chars). See SCHEMA.md `## TL;DR Convention`.
|
|
19
19
|
- For pages tagged `architecture` or explaining workflows/systems: include a Mermaid diagram (`graph TB` or `sequenceDiagram`) in the body. Follow Obsidian-compatible Mermaid rules (see SCHEMA.md `## Mermaid Diagrams`).
|
|
20
|
+
- When referring to local repo files in narrative prose, follow `using-skillwiki` → Portable Source References.
|
|
20
21
|
For `comparison`, evaluation-style `query`, or research-summary pages, end the body with:
|
|
21
22
|
```markdown
|
|
22
23
|
## Decision Closeout
|
|
@@ -36,5 +37,6 @@ Use exactly one disposition. Keep this as a prompt/template convention; do not a
|
|
|
36
37
|
## Forbidden
|
|
37
38
|
- Filing without explicit `provenance:`.
|
|
38
39
|
- Updating `index.md` before `validate` passes.
|
|
40
|
+
- Writing host-local absolute paths as canonical durable source references when a portable reference is available (see `using-skillwiki` → Portable Source References).
|
|
39
41
|
- Writing `[[wikilinks]]` to pages that don't exist in the vault. Before linking, verify the target exists: check `index.md` or `ls` the target directory. If the target doesn't exist yet, use plain text instead of a wikilink.
|
|
40
42
|
- Writing live credentials, access keys, tokens, passwords, cookies, bearer headers, private keys, or other authenticating secrets to the vault.
|