skillwiki 0.8.1-beta.3 → 0.8.1-beta.5
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/cli.js
CHANGED
|
@@ -4103,6 +4103,37 @@ function writeTest(dir) {
|
|
|
4103
4103
|
}
|
|
4104
4104
|
return { success: true, writeMs, readMs: Date.now() - readStart, size: Buffer.byteLength(payload, "utf8") };
|
|
4105
4105
|
}
|
|
4106
|
+
var DURATION_UNIT_SECONDS = {
|
|
4107
|
+
ms: 1 / 1e3,
|
|
4108
|
+
s: 1,
|
|
4109
|
+
m: 60,
|
|
4110
|
+
h: 3600,
|
|
4111
|
+
d: 86400,
|
|
4112
|
+
w: 604800
|
|
4113
|
+
};
|
|
4114
|
+
function parseDurationSeconds(raw) {
|
|
4115
|
+
const input = raw.trim().toLowerCase();
|
|
4116
|
+
if (!input) return null;
|
|
4117
|
+
if (/^\d+(?:\.\d+)?$/.test(input)) {
|
|
4118
|
+
const num = parseFloat(input);
|
|
4119
|
+
return Number.isFinite(num) ? num : null;
|
|
4120
|
+
}
|
|
4121
|
+
const re = /(\d+(?:\.\d+)?)(ms|s|m|h|d|w)/g;
|
|
4122
|
+
let total = 0;
|
|
4123
|
+
let consumed = 0;
|
|
4124
|
+
for (const match of input.matchAll(re)) {
|
|
4125
|
+
const full = match[0];
|
|
4126
|
+
const value = parseFloat(match[1]);
|
|
4127
|
+
const unit = match[2];
|
|
4128
|
+
if (!Number.isFinite(value)) return null;
|
|
4129
|
+
const factor = DURATION_UNIT_SECONDS[unit];
|
|
4130
|
+
if (factor === void 0) return null;
|
|
4131
|
+
total += value * factor;
|
|
4132
|
+
consumed += full.length;
|
|
4133
|
+
}
|
|
4134
|
+
if (consumed !== input.length) return null;
|
|
4135
|
+
return total;
|
|
4136
|
+
}
|
|
4106
4137
|
var FLAG_THRESHOLDS = {
|
|
4107
4138
|
"--vfs-write-back": { min: 15, unit: "s", label: "VFS write-back window" },
|
|
4108
4139
|
"--vfs-write-wait": { min: 10, unit: "s", label: "VFS write-wait" },
|
|
@@ -4495,6 +4526,73 @@ function checkS3MountPerf(resolvedPath) {
|
|
|
4495
4526
|
`S3 FUSE mount, cache warm (rg scan: ${elapsed.toFixed(3)}s)`
|
|
4496
4527
|
);
|
|
4497
4528
|
}
|
|
4529
|
+
var MAX_DIR_CACHE_TIME_SECONDS = 15 * 60;
|
|
4530
|
+
function formatDurationForHumans(seconds) {
|
|
4531
|
+
if (!Number.isFinite(seconds)) return `${seconds}s`;
|
|
4532
|
+
if (seconds >= 3600) return `${(seconds / 3600).toFixed(1)}h`;
|
|
4533
|
+
if (seconds >= 60) return `${(seconds / 60).toFixed(1)}m`;
|
|
4534
|
+
if (seconds >= 1) return `${seconds.toFixed(1)}s`;
|
|
4535
|
+
return `${Math.round(seconds * 1e3)}ms`;
|
|
4536
|
+
}
|
|
4537
|
+
function checkS3MountFreshness(resolvedPath) {
|
|
4538
|
+
if (!resolvedPath) {
|
|
4539
|
+
return check("pass", "s3_mount_freshness", "S3 visibility freshness", "No vault path \u2014 check skipped");
|
|
4540
|
+
}
|
|
4541
|
+
const fuse = detectFuseMount(resolvedPath);
|
|
4542
|
+
if (!fuse) {
|
|
4543
|
+
return check("pass", "s3_mount_freshness", "S3 visibility freshness", "local disk \u2014 check skipped");
|
|
4544
|
+
}
|
|
4545
|
+
const pid = findRcloneMountPid();
|
|
4546
|
+
if (pid === null) {
|
|
4547
|
+
return check(
|
|
4548
|
+
"warn",
|
|
4549
|
+
"s3_mount_freshness",
|
|
4550
|
+
"S3 visibility freshness",
|
|
4551
|
+
`S3 FUSE mount (${fuse.mountPoint}) but no rclone process found \u2014 cannot audit --dir-cache-time`
|
|
4552
|
+
);
|
|
4553
|
+
}
|
|
4554
|
+
const flags = parseRcloneFlags(pid);
|
|
4555
|
+
if (flags.size === 0) {
|
|
4556
|
+
return check(
|
|
4557
|
+
"warn",
|
|
4558
|
+
"s3_mount_freshness",
|
|
4559
|
+
"S3 visibility freshness",
|
|
4560
|
+
`rclone PID ${pid} found but could not parse flags`
|
|
4561
|
+
);
|
|
4562
|
+
}
|
|
4563
|
+
const raw = flags.get("--dir-cache-time");
|
|
4564
|
+
if (!raw) {
|
|
4565
|
+
return check(
|
|
4566
|
+
"pass",
|
|
4567
|
+
"s3_mount_freshness",
|
|
4568
|
+
"S3 visibility freshness",
|
|
4569
|
+
"PID " + pid + ": --dir-cache-time not set (rclone default 5m, within <=15m SLA)"
|
|
4570
|
+
);
|
|
4571
|
+
}
|
|
4572
|
+
const seconds = parseDurationSeconds(raw);
|
|
4573
|
+
if (seconds === null) {
|
|
4574
|
+
return check(
|
|
4575
|
+
"warn",
|
|
4576
|
+
"s3_mount_freshness",
|
|
4577
|
+
"S3 visibility freshness",
|
|
4578
|
+
`PID ${pid}: could not parse --dir-cache-time=${raw}`
|
|
4579
|
+
);
|
|
4580
|
+
}
|
|
4581
|
+
if (seconds > MAX_DIR_CACHE_TIME_SECONDS) {
|
|
4582
|
+
return check(
|
|
4583
|
+
"warn",
|
|
4584
|
+
"s3_mount_freshness",
|
|
4585
|
+
"S3 visibility freshness",
|
|
4586
|
+
`PID ${pid}: --dir-cache-time=${raw} (${formatDurationForHumans(seconds)}) exceeds 15m SLA \u2014 external S3 changes may remain invisible`
|
|
4587
|
+
);
|
|
4588
|
+
}
|
|
4589
|
+
return check(
|
|
4590
|
+
"pass",
|
|
4591
|
+
"s3_mount_freshness",
|
|
4592
|
+
"S3 visibility freshness",
|
|
4593
|
+
`PID ${pid}: --dir-cache-time=${raw} (${formatDurationForHumans(seconds)}), within <=15m SLA`
|
|
4594
|
+
);
|
|
4595
|
+
}
|
|
4498
4596
|
function checkRcloneFlagAudit(resolvedPath) {
|
|
4499
4597
|
if (!resolvedPath) {
|
|
4500
4598
|
return check("pass", "rclone_flags", "rclone VFS flags", "No vault path \u2014 check skipped");
|
|
@@ -4518,11 +4616,8 @@ function checkRcloneFlagAudit(resolvedPath) {
|
|
|
4518
4616
|
warnings.push(`${flag} not set (default may be unsafe)`);
|
|
4519
4617
|
continue;
|
|
4520
4618
|
}
|
|
4521
|
-
const
|
|
4522
|
-
if (
|
|
4523
|
-
let inSeconds = value;
|
|
4524
|
-
if (raw.endsWith("h")) inSeconds = value * 3600;
|
|
4525
|
-
else if (raw.endsWith("m")) inSeconds = value * 60;
|
|
4619
|
+
const inSeconds = parseDurationSeconds(raw);
|
|
4620
|
+
if (inSeconds === null) continue;
|
|
4526
4621
|
const thresholdSec = threshold.unit === "h" ? threshold.min * 3600 : threshold.unit === "m" ? threshold.min * 60 : threshold.min;
|
|
4527
4622
|
if (inSeconds < thresholdSec) {
|
|
4528
4623
|
warnings.push(`${flag}=${raw} (recommended \u2265${threshold.min}${threshold.unit})`);
|
|
@@ -4986,6 +5081,55 @@ function findSkillNames(dir) {
|
|
|
4986
5081
|
}
|
|
4987
5082
|
return results;
|
|
4988
5083
|
}
|
|
5084
|
+
var METRIC_TYPES = ["entities", "concepts", "comparisons", "queries", "meta"];
|
|
5085
|
+
async function vaultMetrics(resolvedPath) {
|
|
5086
|
+
const ids = [
|
|
5087
|
+
["vault_metric_pages", "Vault pages by type"],
|
|
5088
|
+
["vault_metric_orphans", "Vault orphan rate"],
|
|
5089
|
+
["vault_metric_bridges", "Vault bridge count"],
|
|
5090
|
+
["vault_metric_cohesion", "Mean community cohesion"],
|
|
5091
|
+
["vault_metric_log_size", "Vault log size"]
|
|
5092
|
+
];
|
|
5093
|
+
const noVault = () => ids.map(([id, label]) => check("info", id, label, "no vault configured"));
|
|
5094
|
+
if (!resolvedPath) return noVault();
|
|
5095
|
+
const scan = await scanVault(resolvedPath);
|
|
5096
|
+
if (!scan.ok) return noVault();
|
|
5097
|
+
const tk = scan.data.typedKnowledge;
|
|
5098
|
+
const perType = METRIC_TYPES.map((d) => `${d} ${tk.filter((p) => p.relPath.startsWith(d + "/")).length}`).join(", ");
|
|
5099
|
+
const adj = await buildWikilinkAdjacency(tk);
|
|
5100
|
+
const g = toUndirectedWeighted(adj);
|
|
5101
|
+
const nodes = [...g.keys()];
|
|
5102
|
+
const total = nodes.length;
|
|
5103
|
+
const orphanCount = nodes.filter((n) => g.get(n).size === 0).length;
|
|
5104
|
+
const orphanRate = total > 0 ? Math.round(orphanCount / total * 1e3) / 10 : 0;
|
|
5105
|
+
const comm = louvain(g);
|
|
5106
|
+
const groups = /* @__PURE__ */ new Map();
|
|
5107
|
+
for (const [node, c] of comm) {
|
|
5108
|
+
const arr = groups.get(c);
|
|
5109
|
+
if (arr) arr.push(node);
|
|
5110
|
+
else groups.set(c, [node]);
|
|
5111
|
+
}
|
|
5112
|
+
const cohesions = [...groups.values()].filter((m) => m.length >= 2).map((m) => communityCohesion(m, g));
|
|
5113
|
+
const meanCohesion = cohesions.length > 0 ? Math.round(cohesions.reduce((a, b) => a + b, 0) / cohesions.length * 1e3) / 1e3 : 0;
|
|
5114
|
+
let bridges = 0;
|
|
5115
|
+
for (const n of nodes) {
|
|
5116
|
+
const nbrComms = /* @__PURE__ */ new Set();
|
|
5117
|
+
for (const nb of g.get(n).keys()) nbrComms.add(comm.get(nb));
|
|
5118
|
+
if (nbrComms.size >= 3) bridges++;
|
|
5119
|
+
}
|
|
5120
|
+
let logLines = 0;
|
|
5121
|
+
try {
|
|
5122
|
+
logLines = readFileSync7(join26(resolvedPath, "log.md"), "utf8").split("\n").length;
|
|
5123
|
+
} catch {
|
|
5124
|
+
}
|
|
5125
|
+
return [
|
|
5126
|
+
check("info", "vault_metric_pages", "Vault pages by type", `${total} typed (${perType})`),
|
|
5127
|
+
check("info", "vault_metric_orphans", "Vault orphan rate", `${orphanRate}% (${orphanCount}/${total} degree-0)`),
|
|
5128
|
+
check("info", "vault_metric_bridges", "Vault bridge count", `${bridges} page(s) link >= 3 communities`),
|
|
5129
|
+
check("info", "vault_metric_cohesion", "Mean community cohesion", `${meanCohesion} across ${cohesions.length} communities (size >= 2)`),
|
|
5130
|
+
check("info", "vault_metric_log_size", "Vault log size", `${logLines} lines`)
|
|
5131
|
+
];
|
|
5132
|
+
}
|
|
4989
5133
|
async function runDoctor(input) {
|
|
4990
5134
|
const checks = [];
|
|
4991
5135
|
const vsConfig = readVaultSyncConfig(input.home);
|
|
@@ -5008,6 +5152,7 @@ async function runDoctor(input) {
|
|
|
5008
5152
|
checks.push(checkSyncLastPush(resolvedPath));
|
|
5009
5153
|
checks.push(checkDotStoreClean(resolvedPath));
|
|
5010
5154
|
checks.push(checkS3MountPerf(resolvedPath));
|
|
5155
|
+
checks.push(checkS3MountFreshness(resolvedPath));
|
|
5011
5156
|
checks.push(checkRcloneFlagAudit(resolvedPath));
|
|
5012
5157
|
checks.push(checkRcloneVersion(resolvedPath, vsConfig.installed));
|
|
5013
5158
|
checks.push(checkWriteTest(resolvedPath));
|
|
@@ -5021,6 +5166,7 @@ async function runDoctor(input) {
|
|
|
5021
5166
|
vaultSyncInstalled: vsConfig.installed,
|
|
5022
5167
|
vaultSyncRole: vsConfig.role
|
|
5023
5168
|
}));
|
|
5169
|
+
checks.push(...await vaultMetrics(resolvedPath));
|
|
5024
5170
|
const summary = {
|
|
5025
5171
|
pass: checks.filter((c) => c.status === "pass").length,
|
|
5026
5172
|
info: checks.filter((c) => c.status === "info").length,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skillwiki",
|
|
3
|
-
"version": "0.8.1-beta.
|
|
3
|
+
"version": "0.8.1-beta.5",
|
|
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": {
|