skillwiki 0.7.0 → 0.8.0

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
@@ -2364,7 +2364,7 @@ Chronological action log. Newest entries last. Skill writes append entries; lint
2364
2364
 
2365
2365
  // src/commands/lint.ts
2366
2366
  import { existsSync as existsSync3 } from "fs";
2367
- import { readFile as readFile15 } from "fs/promises";
2367
+ import { readFile as readFile15, rename as rename5 } from "fs/promises";
2368
2368
  import { join as join19 } from "path";
2369
2369
 
2370
2370
  // src/commands/topic-map-check.ts
@@ -2607,6 +2607,60 @@ async function runRawBodyDedup(vault) {
2607
2607
  };
2608
2608
  }
2609
2609
 
2610
+ // src/commands/path-too-long.ts
2611
+ var MAX_PATH_LENGTH = 240;
2612
+ async function runPathTooLong(input) {
2613
+ const scan = await scanVault(input.vault);
2614
+ if (!scan.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scan };
2615
+ const allPages = [...scan.data.typedKnowledge, ...scan.data.raw, ...scan.data.workItems, ...scan.data.compound];
2616
+ const violations = [];
2617
+ for (const page of allPages) {
2618
+ if (page.relPath.length > MAX_PATH_LENGTH) {
2619
+ violations.push({ relPath: page.relPath, length: page.relPath.length });
2620
+ }
2621
+ }
2622
+ if (violations.length > 0) {
2623
+ return {
2624
+ exitCode: ExitCode.LINT_HAS_ERRORS,
2625
+ result: ok({
2626
+ violations,
2627
+ humanHint: violations.map((v) => `${v.relPath}: ${v.length} chars (max ${MAX_PATH_LENGTH})`).join("\n")
2628
+ })
2629
+ };
2630
+ }
2631
+ return { exitCode: ExitCode.OK, result: ok({ violations, humanHint: "all paths within length limit" }) };
2632
+ }
2633
+ function truncateFilename(relPath, maxLength = MAX_PATH_LENGTH) {
2634
+ if (relPath.length <= maxLength) return relPath;
2635
+ const lastSlash = relPath.lastIndexOf("/");
2636
+ const dir = lastSlash >= 0 ? relPath.slice(0, lastSlash) : "";
2637
+ const filename = lastSlash >= 0 ? relPath.slice(lastSlash + 1) : relPath;
2638
+ const hash = computeShortHash(relPath);
2639
+ const ext = filename.endsWith(".md") ? ".md" : "";
2640
+ const base = filename.endsWith(".md") ? filename.slice(0, -3) : filename;
2641
+ const suffix = `-${hash}${ext}`;
2642
+ const dirPrefix = dir ? dir + "/" : "";
2643
+ const maxPrefixLen = maxLength - dirPrefix.length - suffix.length;
2644
+ if (maxPrefixLen <= 0) {
2645
+ const fallback = dirPrefix + hash + ext;
2646
+ if (fallback.length > maxLength) {
2647
+ const dirBudget = maxLength - suffix.length;
2648
+ return dirPrefix.slice(0, Math.max(0, dirBudget)) + suffix;
2649
+ }
2650
+ return fallback;
2651
+ }
2652
+ const prefix = base.slice(0, maxPrefixLen).replace(/[-_\s]+$/, "");
2653
+ return dirPrefix + prefix + suffix;
2654
+ }
2655
+ function computeShortHash(input) {
2656
+ let hash = 2166136261;
2657
+ for (let i = 0; i < input.length; i++) {
2658
+ hash ^= input.charCodeAt(i);
2659
+ hash = Math.imul(hash, 16777619);
2660
+ }
2661
+ return (hash >>> 0).toString(16).padStart(8, "0").slice(0, 8);
2662
+ }
2663
+
2610
2664
  // src/utils/cli-surface.ts
2611
2665
  import { Command } from "commander";
2612
2666
  function buildCliSurface() {
@@ -2779,7 +2833,7 @@ function extractSourceEntries(rawFm) {
2779
2833
  }
2780
2834
  return entries;
2781
2835
  }
2782
- var ERROR_ORDER = ["broken_wikilinks", "invalid_frontmatter", "raw_dedup", "broken_sources", "tag_not_in_taxonomy"];
2836
+ var ERROR_ORDER = ["broken_wikilinks", "invalid_frontmatter", "raw_dedup", "broken_sources", "tag_not_in_taxonomy", "path_too_long"];
2783
2837
  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", "work_item_health", "orphaned_project_pages", "missing_overview", "missing_diagram"];
2784
2838
  var INFO_ORDER = ["bridges", "page_structure", "topic_map_recommended", "frontmatter_wikilink", "wikilink_citation", "missing_tldr", "stale_sections", "cli_refs"];
2785
2839
  async function runLint(input) {
@@ -2839,6 +2893,8 @@ async function runLint(input) {
2839
2893
  }
2840
2894
  const compoundRefs = await validateCompoundReferences(input.vault);
2841
2895
  if (compoundRefs.ok && compoundRefs.data.length > 0) buckets.compound_refs = compoundRefs.data;
2896
+ const pathCheck = await runPathTooLong({ vault: input.vault });
2897
+ if (pathCheck.result.ok && pathCheck.result.data.violations.length > 0) buckets.path_too_long = pathCheck.result.data.violations;
2842
2898
  const scan = await scanVault(input.vault);
2843
2899
  const allPages = scan.ok ? [...scan.data.typedKnowledge, ...scan.data.raw, ...scan.data.workItems, ...scan.data.compound] : [];
2844
2900
  const slugs = scan.ok ? buildSlugMap(allPages) : /* @__PURE__ */ new Map();
@@ -3353,6 +3409,45 @@ ${newBody}`;
3353
3409
  else delete buckets.file_source_url;
3354
3410
  }
3355
3411
  }
3412
+ const pathViolations = buckets.path_too_long;
3413
+ if (input.fix && pathViolations && pathViolations.length > 0) {
3414
+ const pathFixed = [];
3415
+ for (const v of pathViolations) {
3416
+ try {
3417
+ const absPath = `${input.vault}/${v.relPath}`;
3418
+ const newRelPath = truncateFilename(v.relPath);
3419
+ const newAbsPath = `${input.vault}/${newRelPath}`;
3420
+ await rename5(absPath, newAbsPath);
3421
+ const oldPathEscaped = v.relPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3422
+ for (const page of allPages) {
3423
+ if (page.relPath === v.relPath) continue;
3424
+ const content = await readFile15(page.absPath, "utf8");
3425
+ if (!content.includes(v.relPath)) continue;
3426
+ let updated = content;
3427
+ const citationRe = new RegExp(`\\^\\[${oldPathEscaped}\\]`, "g");
3428
+ updated = updated.replace(citationRe, `^[${newRelPath}]`);
3429
+ const wikilinkRe = new RegExp(`\\[\\[${oldPathEscaped}(\\|[^\\]]*)?\\]\\]`, "g");
3430
+ updated = updated.replace(wikilinkRe, (_m, alias) => `[[${newRelPath}${alias ?? ""}]]`);
3431
+ if (updated !== content) {
3432
+ const w = await safeWritePage(page.absPath, updated);
3433
+ if (!w.ok) {
3434
+ unresolved.push(`${page.relPath} (rewire)`);
3435
+ }
3436
+ }
3437
+ }
3438
+ pathFixed.push(v.relPath);
3439
+ } catch {
3440
+ unresolved.push(v.relPath);
3441
+ }
3442
+ }
3443
+ fixed.push(...pathFixed);
3444
+ if (pathFixed.length > 0) {
3445
+ const fixedSet = new Set(pathFixed);
3446
+ const remaining = pathViolations.filter((v) => !fixedSet.has(v.relPath));
3447
+ if (remaining.length > 0) buckets.path_too_long = remaining;
3448
+ else delete buckets.path_too_long;
3449
+ }
3450
+ }
3356
3451
  }
3357
3452
  const errorOut = ERROR_ORDER.flatMap((k) => buckets[k] ? [{ kind: k, items: buckets[k] }] : []);
3358
3453
  const warningOut = WARNING_ORDER.flatMap((k) => buckets[k] ? [{ kind: k, items: buckets[k] }] : []);
@@ -4712,7 +4807,7 @@ async function runDoctor(input) {
4712
4807
  }
4713
4808
 
4714
4809
  // src/commands/archive.ts
4715
- import { rename as rename5, mkdir as mkdir8, readFile as readFile18, writeFile as writeFile9 } from "fs/promises";
4810
+ import { rename as rename6, mkdir as mkdir8, readFile as readFile18, writeFile as writeFile9 } from "fs/promises";
4716
4811
  import { join as join25, dirname as dirname9 } from "path";
4717
4812
  function countWikilinks(body, slug) {
4718
4813
  const escaped = slug.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -4825,7 +4920,7 @@ ${fmRewritten}
4825
4920
  if (e instanceof Error && "code" in e && e.code !== "ENOENT") throw e;
4826
4921
  }
4827
4922
  }
4828
- await rename5(join25(input.vault, relPath), join25(input.vault, archivePath));
4923
+ await rename6(join25(input.vault, relPath), join25(input.vault, archivePath));
4829
4924
  appendLastOp(input.vault, {
4830
4925
  operation: input.cascade ? "archive-cascade" : "archive",
4831
4926
  summary: `moved ${relPath} to ${archivePath}${input.cascade ? ` (cascade: ${cascade?.source_array_refs.length ?? 0} source arrays updated)` : ""}`,
@@ -7791,7 +7886,7 @@ program.command("log-rotate [vault]").description("rotate or trim the vault log
7791
7886
  if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
7792
7887
  else emit(await runLogRotate({ vault: v.vault, threshold: opts.threshold, apply: !!opts.apply }), v.vault);
7793
7888
  });
7794
- program.command("lint [vault]").description("run all vault health checks").option("--days <n>", "stale threshold", (s) => parseInt(s, 10), 90).option("--lines <n>", "pagesize threshold", (s) => parseInt(s, 10), 200).option("--log-threshold <n>", "log rotation threshold", (s) => parseInt(s, 10), 500).option("--fix", "auto-fix legacy_citation_style violations").option("--only <bucket>", "run only the specified lint bucket").option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
7889
+ program.command("lint [vault]").description("run all vault health checks").option("--days <n>", "stale threshold", (s) => parseInt(s, 10), 90).option("--lines <n>", "pagesize threshold", (s) => parseInt(s, 10), 200).option("--log-threshold <n>", "log rotation threshold", (s) => parseInt(s, 10), 500).option("--fix", "auto-fix supported lint violations").option("--only <bucket>", "run only the specified lint bucket").option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
7795
7890
  const v = await resolveVaultArg(vault, opts.wiki);
7796
7891
  if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
7797
7892
  else emit(await runLint({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillwiki",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
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.7.0",
3
+ "version": "0.8.0",
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.7.0",
3
+ "version": "0.8.0",
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.7.0",
3
+ "version": "0.8.0",
4
4
  "private": true,
5
5
  "files": [
6
6
  "wiki-*",
@@ -8,7 +8,7 @@ Capture ad-hoc ideas, bugs, tasks, and notes into the vault. Three entry points
8
8
  | Entry | When | What happens |
9
9
  |-------|------|-------------|
10
10
  | `/wiki-add-task <text>` | You're in a Claude Code session (NOT Hermes compact) | Creates `raw/transcripts/YYYY-MM-DD-{type}-{slug}.md` with ad-hoc capture frontmatter |
11
- | `skillwiki add-task <text>` | Hermes Agent compact mode | Same as above — compact-compatible CLI trigger |
11
+ | Filesystem drop | Hermes Agent compact mode (no slash commands available) | Same as above — create `.md` in `raw/transcripts/`, dev-loop discovers it |
12
12
  | Filesystem drop | You're NOT in a Claude session (Obsidian, editor, sync) | Create any `.md` file in `raw/transcripts/` using the vault template — dev-loop discovers it on next cycle |
13
13
  | Dev-loop discovery | Automatic, next cycle | Scans `raw/transcripts/` for new files since last cycle, surfaces as claimable work |
14
14
  **Path Rule:** Captures ALWAYS go to `$(skillwiki path)/raw/transcripts/` (Layer 1). Never under `projects/{slug}/raw/` — that violates SCHEMA.md Layer 1 immutability.