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
|
|
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
|
|
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
|
|
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.
|
|
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": {
|
package/skills/package.json
CHANGED
|
@@ -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
|
-
|
|
|
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.
|