skillwiki 0.4.3 → 0.4.4-beta.1

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
@@ -2402,6 +2402,46 @@ async function runDedup(input) {
2402
2402
  };
2403
2403
  }
2404
2404
 
2405
+ // src/commands/raw-body-dedup.ts
2406
+ import { createHash as createHash2 } from "crypto";
2407
+ async function runRawBodyDedup(vault) {
2408
+ const scan = await scanVault(vault);
2409
+ if (!scan.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scan };
2410
+ const bodyHashMap = /* @__PURE__ */ new Map();
2411
+ let totalFiles = 0;
2412
+ for (const raw of scan.data.raw) {
2413
+ const text = await readPage(raw);
2414
+ const split = splitFrontmatter(text);
2415
+ if (!split.ok) continue;
2416
+ totalFiles++;
2417
+ const bodyHash = createHash2("sha256").update(split.data.body).digest("hex");
2418
+ const fm = extractFrontmatter(text);
2419
+ let fmSha256 = null;
2420
+ if (fm.ok && typeof fm.data.sha256 === "string" && fm.data.sha256.length === 64) {
2421
+ fmSha256 = fm.data.sha256;
2422
+ }
2423
+ const existing = bodyHashMap.get(bodyHash);
2424
+ if (existing) {
2425
+ existing.push({ relPath: raw.relPath, sha256: fmSha256 });
2426
+ } else {
2427
+ bodyHashMap.set(bodyHash, [{ relPath: raw.relPath, sha256: fmSha256 }]);
2428
+ }
2429
+ }
2430
+ const duplicates = [];
2431
+ for (const [bodyHash, files] of bodyHashMap) {
2432
+ if (files.length < 2) continue;
2433
+ const uniqueShas = new Set(files.map((f) => f.sha256));
2434
+ const allHaveSameValidSha = uniqueShas.size === 1 && files.every((f) => f.sha256 !== null);
2435
+ if (!allHaveSameValidSha) {
2436
+ duplicates.push({ bodyHash, files });
2437
+ }
2438
+ }
2439
+ return {
2440
+ exitCode: 0,
2441
+ result: ok({ scanned: totalFiles, duplicates })
2442
+ };
2443
+ }
2444
+
2405
2445
  // src/commands/lint.ts
2406
2446
  var STRUCT_MIN_BODY_LINES = 60;
2407
2447
  var STRUCT_MIN_SECTIONS = 3;
@@ -2434,7 +2474,7 @@ function extractSourceEntries(rawFm) {
2434
2474
  return entries;
2435
2475
  }
2436
2476
  var ERROR_ORDER = ["broken_wikilinks", "invalid_frontmatter", "raw_dedup", "broken_sources", "tag_not_in_taxonomy"];
2437
- var WARNING_ORDER = ["index_incomplete", "index_link_format", "stale_page", "page_too_large", "log_rotate_needed", "orphans", "compound_refs", "legacy_citation_style", "orphaned_citations", "duplicate_frontmatter", "work_item_health", "orphaned_project_pages", "missing_overview"];
2477
+ var WARNING_ORDER = ["raw_body_duplicate", "raw_subdirectory_duplicate", "index_incomplete", "index_link_format", "stale_page", "page_too_large", "log_rotate_needed", "orphans", "compound_refs", "legacy_citation_style", "orphaned_citations", "duplicate_frontmatter", "work_item_health", "orphaned_project_pages", "missing_overview"];
2438
2478
  var INFO_ORDER = ["bridges", "page_structure", "topic_map_recommended", "frontmatter_wikilink", "wikilink_citation", "missing_tldr", "missing_diagram"];
2439
2479
  async function runLint(input) {
2440
2480
  const buckets = {};
@@ -2484,12 +2524,41 @@ async function runLint(input) {
2484
2524
  }
2485
2525
  const dedup = await runDedup({ vault: input.vault });
2486
2526
  if (dedup.result.ok && dedup.result.data.duplicates.length > 0) buckets.raw_dedup = dedup.result.data.duplicates;
2527
+ const bodyDedup = await runRawBodyDedup(input.vault);
2528
+ if (bodyDedup.result.ok && bodyDedup.result.data.duplicates.length > 0) {
2529
+ buckets.raw_body_duplicate = bodyDedup.result.data.duplicates.map((d) => ({
2530
+ body_hash: d.bodyHash.slice(0, 12),
2531
+ files: d.files.map((f) => `${f.relPath} (sha256: ${f.sha256 ?? "none"})`)
2532
+ }));
2533
+ }
2487
2534
  const compoundRefs = await validateCompoundReferences(input.vault);
2488
2535
  if (compoundRefs.ok && compoundRefs.data.length > 0) buckets.compound_refs = compoundRefs.data;
2489
2536
  const scan = await scanVault(input.vault);
2490
2537
  const allPages = scan.ok ? [...scan.data.typedKnowledge, ...scan.data.raw, ...scan.data.workItems, ...scan.data.compound] : [];
2491
2538
  const slugs = scan.ok ? buildSlugMap(allPages) : /* @__PURE__ */ new Map();
2492
2539
  if (scan.ok) {
2540
+ const subDirDupes = [];
2541
+ const flatStems = /* @__PURE__ */ new Map();
2542
+ const deepFiles = [];
2543
+ for (const raw of scan.data.raw) {
2544
+ const parts = raw.relPath.split("/");
2545
+ if (parts.length === 3) {
2546
+ const stem = parts[2].replace(/\.md$/, "");
2547
+ flatStems.set(`${parts[1]}/${stem}`, raw.relPath);
2548
+ } else if (parts.length > 3) {
2549
+ const stem = parts[parts.length - 1].replace(/\.md$/, "");
2550
+ deepFiles.push({ relPath: raw.relPath, stem, parentType: parts[1] });
2551
+ }
2552
+ }
2553
+ for (const df of deepFiles) {
2554
+ const flatPath = flatStems.get(`${df.parentType}/${df.stem}`);
2555
+ if (flatPath) {
2556
+ subDirDupes.push(`${df.relPath} -> duplicate of ${flatPath}`);
2557
+ }
2558
+ }
2559
+ if (subDirDupes.length > 0) {
2560
+ buckets.raw_subdirectory_duplicate = subDirDupes;
2561
+ }
2493
2562
  const legacyPages = [];
2494
2563
  const orphanedPages = [];
2495
2564
  const structFlags = [];
@@ -3444,18 +3513,6 @@ function checkS3MountPerf(resolvedPath) {
3444
3513
  if (!existsSync6(conceptsDir)) {
3445
3514
  return check("pass", "s3_mount_perf", "S3 mount performance", `S3 FUSE mount (${mountPoint}), no concepts/ to benchmark`);
3446
3515
  }
3447
- let rgAvailable = false;
3448
- try {
3449
- execSync("which rg", { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
3450
- rgAvailable = true;
3451
- } catch {
3452
- return check(
3453
- "info",
3454
- "s3_mount_perf",
3455
- "S3 mount performance",
3456
- `S3 FUSE mount (${mountPoint}) \u2014 ripgrep not found, benchmark skipped`
3457
- );
3458
- }
3459
3516
  const start = Date.now();
3460
3517
  let timedOut = false;
3461
3518
  try {
@@ -3618,7 +3675,7 @@ async function runArchive(input) {
3618
3675
  }
3619
3676
 
3620
3677
  // src/commands/drift.ts
3621
- import { createHash as createHash2 } from "crypto";
3678
+ import { createHash as createHash3 } from "crypto";
3622
3679
  import { writeFile as writeFile10 } from "fs/promises";
3623
3680
 
3624
3681
  // src/utils/fetch.ts
@@ -3698,7 +3755,7 @@ async function runDrift(input) {
3698
3755
  });
3699
3756
  continue;
3700
3757
  }
3701
- const currentHash = createHash2("sha256").update(Buffer.from(resp.data.body, "utf8")).digest("hex");
3758
+ const currentHash = createHash3("sha256").update(Buffer.from(resp.data.body, "utf8")).digest("hex");
3702
3759
  const drifted2 = currentHash !== storedHash;
3703
3760
  if (drifted2 && input.apply) {
3704
3761
  const newFm = rawFrontmatter.replace(/^sha256:\s*[a-f0-9]+$/m, `sha256: ${currentHash}`);
@@ -4683,7 +4740,7 @@ no compound entries found`;
4683
4740
  import { mkdir as mkdir11, writeFile as writeFile15 } from "fs/promises";
4684
4741
  import { existsSync as existsSync9, statSync as statSync3 } from "fs";
4685
4742
  import { join as join29 } from "path";
4686
- import { createHash as createHash3 } from "crypto";
4743
+ import { createHash as createHash4 } from "crypto";
4687
4744
  var ALLOWED_KINDS = /* @__PURE__ */ new Set(["note", "bug", "task", "idea", "session-log"]);
4688
4745
  function slugify2(text) {
4689
4746
  const words = text.trim().split(/\s+/).slice(0, 6).join("-").toLowerCase().replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "");
@@ -4727,7 +4784,7 @@ async function runObserve(input) {
4727
4784
  const body = `
4728
4785
  ${input.text.trim()}
4729
4786
  `;
4730
- const sha256 = createHash3("sha256").update(Buffer.from(body, "utf8")).digest("hex");
4787
+ const sha256 = createHash4("sha256").update(Buffer.from(body, "utf8")).digest("hex");
4731
4788
  const frontmatterLines = [
4732
4789
  "---",
4733
4790
  "source_url:",
@@ -4765,7 +4822,7 @@ ${input.text.trim()}
4765
4822
  // src/commands/ingest.ts
4766
4823
  import { readFile as readFile20, writeFile as writeFile16, mkdir as mkdir12 } from "fs/promises";
4767
4824
  import { join as join30 } from "path";
4768
- import { createHash as createHash4 } from "crypto";
4825
+ import { createHash as createHash5 } from "crypto";
4769
4826
  var ALLOWED_TYPES = /* @__PURE__ */ new Set(["entity", "concept", "comparison", "query"]);
4770
4827
  var TYPE_DIR = {
4771
4828
  entity: "entities",
@@ -4931,7 +4988,7 @@ async function runIngest(input) {
4931
4988
  };
4932
4989
  }
4933
4990
  }
4934
- const sha256 = createHash4("sha256").update(Buffer.from(sourceContent, "utf8")).digest("hex");
4991
+ const sha256 = createHash5("sha256").update(Buffer.from(sourceContent, "utf8")).digest("hex");
4935
4992
  const today = todayIso();
4936
4993
  const slug = slugify3(input.title);
4937
4994
  const tags = input.tags && input.tags.length > 0 ? input.tags : [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillwiki",
3
- "version": "0.4.3",
3
+ "version": "0.4.4-beta.1",
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.4.3",
3
+ "version": "0.4.4-beta.1",
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.4.3",
3
+ "version": "0.4.4-beta.1",
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.4.3",
3
+ "version": "0.4.4-beta.1",
4
4
  "private": true,
5
5
  "files": [
6
6
  "wiki-*",