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 value = parseFloat(raw);
4522
- if (isNaN(value)) continue;
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",
3
+ "version": "0.8.1-beta.5",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "skillwiki": "dist/cli.js"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillwiki",
3
- "version": "0.8.1-beta.3",
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": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillwiki",
3
- "version": "0.8.1-beta.3",
3
+ "version": "0.8.1-beta.5",
4
4
  "description": "Project-aware Karpathy-style knowledge base for Codex with 18 prompt-only skills backed by the deterministic skillwiki CLI.",
5
5
  "author": {
6
6
  "name": "karlorz",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skillwiki/skills",
3
- "version": "0.8.1-beta.3",
3
+ "version": "0.8.1-beta.5",
4
4
  "private": true,
5
5
  "files": [
6
6
  "wiki-*",