skillwiki 0.2.0-beta.11 → 0.2.0-beta.14
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 +216 -8
- package/package.json +1 -1
- package/skills/.claude-plugin/plugin.json +1 -1
- package/skills/package.json +1 -1
- package/templates/SCHEMA.md +2 -1
package/dist/cli.js
CHANGED
|
@@ -67,7 +67,8 @@ var ExitCode = {
|
|
|
67
67
|
ARCHIVE_TARGET_NOT_FOUND: 30,
|
|
68
68
|
ARCHIVE_ALREADY_ARCHIVED: 31,
|
|
69
69
|
DRIFT_DETECTED: 32,
|
|
70
|
-
RAW_DEDUP_DETECTED: 33
|
|
70
|
+
RAW_DEDUP_DETECTED: 33,
|
|
71
|
+
MIGRATION_APPLIED: 34
|
|
71
72
|
};
|
|
72
73
|
|
|
73
74
|
// ../shared/src/json-output.ts
|
|
@@ -704,16 +705,46 @@ import { dirname as dirname3, resolve, join as join3 } from "path";
|
|
|
704
705
|
|
|
705
706
|
// src/parsers/citations.ts
|
|
706
707
|
var FENCE2 = /```[\s\S]*?```/g;
|
|
708
|
+
var MARKER_RE = /\^\[(raw\/[^\]]+)\]/g;
|
|
709
|
+
function stripFences(body) {
|
|
710
|
+
return body.replace(FENCE2, "");
|
|
711
|
+
}
|
|
707
712
|
function extractCitationMarkers(body) {
|
|
708
|
-
const stripped = body
|
|
713
|
+
const stripped = stripFences(body);
|
|
709
714
|
const out = [];
|
|
710
|
-
const re = /\^\[(raw\/[^\]]+)\]/g;
|
|
711
715
|
let m;
|
|
712
|
-
while ((m =
|
|
716
|
+
while ((m = MARKER_RE.exec(stripped)) !== null) {
|
|
713
717
|
out.push({ marker: m[0], target: m[1] });
|
|
714
718
|
}
|
|
715
719
|
return out;
|
|
716
720
|
}
|
|
721
|
+
function hasSourcesFooter(body) {
|
|
722
|
+
return /^## Sources\s*$/m.test(stripFences(body));
|
|
723
|
+
}
|
|
724
|
+
function isLegacyCitationStyle(body) {
|
|
725
|
+
const markers = extractCitationMarkers(body);
|
|
726
|
+
if (markers.length === 0) return false;
|
|
727
|
+
if (!hasSourcesFooter(body)) return true;
|
|
728
|
+
const lines = stripFences(body).split("\n");
|
|
729
|
+
let inSources = false;
|
|
730
|
+
for (const line of lines) {
|
|
731
|
+
if (/^## Sources\b/.test(line.trim())) {
|
|
732
|
+
inSources = true;
|
|
733
|
+
continue;
|
|
734
|
+
}
|
|
735
|
+
if (inSources) continue;
|
|
736
|
+
const markerOnly = line.replace(MARKER_RE, "").trim();
|
|
737
|
+
if (markerOnly.length === 0 && /\^\[raw\//.test(line)) return true;
|
|
738
|
+
const lastMarkerIdx = line.lastIndexOf("^[raw/");
|
|
739
|
+
if (lastMarkerIdx >= 0) {
|
|
740
|
+
const afterLast = line.slice(lastMarkerIdx).replace(MARKER_RE, "").trim();
|
|
741
|
+
if (afterLast.length > 0) return true;
|
|
742
|
+
const beforeFirst = line.slice(0, line.indexOf("^[raw/")).trim();
|
|
743
|
+
if (beforeFirst.length > 0 && !/[.!?]\s*$/.test(beforeFirst)) return true;
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
return false;
|
|
747
|
+
}
|
|
717
748
|
|
|
718
749
|
// src/commands/audit.ts
|
|
719
750
|
async function runAudit(input) {
|
|
@@ -743,19 +774,35 @@ async function runAudit(input) {
|
|
|
743
774
|
const unused_sources = sources.filter((s) => !referenced.has(s));
|
|
744
775
|
const missing_from_sources = [...referenced].filter((t) => !sources.includes(t));
|
|
745
776
|
const broken = resolved.filter((m) => !m.resolved);
|
|
777
|
+
const footerMatch = body.match(/\n## Sources\n([\s\S]*)$/);
|
|
778
|
+
let footer_consistency;
|
|
779
|
+
if (footerMatch) {
|
|
780
|
+
const footerTargets = /* @__PURE__ */ new Set();
|
|
781
|
+
const footerRe = /\^\[(raw\/[^\]]+)\]/g;
|
|
782
|
+
let mm;
|
|
783
|
+
while ((mm = footerRe.exec(footerMatch[1])) !== null) footerTargets.add(mm[1]);
|
|
784
|
+
const bodyTargets = new Set(resolved.map((m) => m.target));
|
|
785
|
+
const missing_from_footer = [...bodyTargets].filter((t) => !footerTargets.has(t));
|
|
786
|
+
const extra_in_footer = [...footerTargets].filter((t) => !bodyTargets.has(t));
|
|
787
|
+
footer_consistency = { missing_from_footer, extra_in_footer };
|
|
788
|
+
}
|
|
746
789
|
const hintLines = [];
|
|
747
790
|
hintLines.push(`markers: ${resolved.length}, broken: ${broken.length}`);
|
|
748
791
|
if (unused_sources.length > 0) hintLines.push(`unused_sources: ${unused_sources.length}`);
|
|
749
792
|
if (missing_from_sources.length > 0) hintLines.push(`missing_from_sources: ${missing_from_sources.length}`);
|
|
793
|
+
if (footer_consistency) {
|
|
794
|
+
if (footer_consistency.missing_from_footer.length > 0) hintLines.push(`missing_from_footer: ${footer_consistency.missing_from_footer.length}`);
|
|
795
|
+
if (footer_consistency.extra_in_footer.length > 0) hintLines.push(`extra_in_footer: ${footer_consistency.extra_in_footer.length}`);
|
|
796
|
+
}
|
|
750
797
|
if (broken.length === 0 && unused_sources.length === 0 && missing_from_sources.length === 0) hintLines.push("OK");
|
|
751
798
|
const humanHint = hintLines.join("\n");
|
|
752
799
|
if (resolved.some((m) => !m.resolved)) {
|
|
753
|
-
return { exitCode: ExitCode.UNRESOLVED_MARKERS, result: ok({ markers: resolved, sources_consistency: { unused_sources, missing_from_sources }, humanHint }) };
|
|
800
|
+
return { exitCode: ExitCode.UNRESOLVED_MARKERS, result: ok({ markers: resolved, sources_consistency: { unused_sources, missing_from_sources }, footer_consistency, humanHint }) };
|
|
754
801
|
}
|
|
755
802
|
if (unused_sources.length > 0 || missing_from_sources.length > 0) {
|
|
756
|
-
return { exitCode: ExitCode.SOURCES_INCONSISTENT, result: ok({ markers: resolved, sources_consistency: { unused_sources, missing_from_sources }, humanHint }) };
|
|
803
|
+
return { exitCode: ExitCode.SOURCES_INCONSISTENT, result: ok({ markers: resolved, sources_consistency: { unused_sources, missing_from_sources }, footer_consistency, humanHint }) };
|
|
757
804
|
}
|
|
758
|
-
return { exitCode: ExitCode.OK, result: ok({ markers: resolved, sources_consistency: { unused_sources, missing_from_sources }, humanHint }) };
|
|
805
|
+
return { exitCode: ExitCode.OK, result: ok({ markers: resolved, sources_consistency: { unused_sources, missing_from_sources }, footer_consistency, humanHint }) };
|
|
759
806
|
}
|
|
760
807
|
async function findVaultRoot(start) {
|
|
761
808
|
let cur = start;
|
|
@@ -1429,7 +1476,7 @@ async function runDedup(input) {
|
|
|
1429
1476
|
|
|
1430
1477
|
// src/commands/lint.ts
|
|
1431
1478
|
var ERROR_ORDER = ["broken_wikilinks", "invalid_frontmatter", "raw_dedup", "tag_not_in_taxonomy"];
|
|
1432
|
-
var WARNING_ORDER = ["index_incomplete", "index_link_format", "stale_page", "page_too_large", "log_rotate_needed", "contested", "orphans"];
|
|
1479
|
+
var WARNING_ORDER = ["index_incomplete", "index_link_format", "stale_page", "page_too_large", "log_rotate_needed", "contested", "orphans", "legacy_citation_style"];
|
|
1433
1480
|
var INFO_ORDER = ["bridges", "low_confidence_single_source", "topic_map_recommended"];
|
|
1434
1481
|
async function runLint(input) {
|
|
1435
1482
|
const buckets = {};
|
|
@@ -1473,6 +1520,17 @@ async function runLint(input) {
|
|
|
1473
1520
|
}
|
|
1474
1521
|
const dedup = await runDedup({ vault: input.vault });
|
|
1475
1522
|
if (dedup.result.ok && dedup.result.data.duplicates.length > 0) buckets.raw_dedup = dedup.result.data.duplicates;
|
|
1523
|
+
const scan = await scanVault(input.vault);
|
|
1524
|
+
if (scan.ok) {
|
|
1525
|
+
const legacyPages = [];
|
|
1526
|
+
for (const page of scan.data.typedKnowledge) {
|
|
1527
|
+
const text = await readPage(page);
|
|
1528
|
+
const split = splitFrontmatter(text);
|
|
1529
|
+
if (!split.ok) continue;
|
|
1530
|
+
if (isLegacyCitationStyle(split.data.body)) legacyPages.push(page.relPath);
|
|
1531
|
+
}
|
|
1532
|
+
if (legacyPages.length > 0) buckets.legacy_citation_style = legacyPages;
|
|
1533
|
+
}
|
|
1476
1534
|
const errorOut = ERROR_ORDER.flatMap((k) => buckets[k] ? [{ kind: k, items: buckets[k] }] : []);
|
|
1477
1535
|
const warningOut = WARNING_ORDER.flatMap((k) => buckets[k] ? [{ kind: k, items: buckets[k] }] : []);
|
|
1478
1536
|
const infoOut = INFO_ORDER.flatMap((k) => buckets[k] ? [{ kind: k, items: buckets[k] }] : []);
|
|
@@ -1796,6 +1854,151 @@ async function runDrift(input) {
|
|
|
1796
1854
|
};
|
|
1797
1855
|
}
|
|
1798
1856
|
|
|
1857
|
+
// src/commands/migrate-citations.ts
|
|
1858
|
+
import { writeFile as writeFile7 } from "fs/promises";
|
|
1859
|
+
var MARKER_RE2 = /\^\[(raw\/[^\]]+)\]/g;
|
|
1860
|
+
function moveMarkersToParagraphEnd(body) {
|
|
1861
|
+
const lines = body.split("\n");
|
|
1862
|
+
const result = [];
|
|
1863
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1864
|
+
const line = lines[i];
|
|
1865
|
+
if (line.trimStart().startsWith("```")) {
|
|
1866
|
+
result.push(line);
|
|
1867
|
+
if (!line.trimEnd().endsWith("```") || line.trim() === "```") {
|
|
1868
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
1869
|
+
result.push(lines[j]);
|
|
1870
|
+
if (lines[j].trimStart().startsWith("```")) {
|
|
1871
|
+
i = j;
|
|
1872
|
+
break;
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
continue;
|
|
1877
|
+
}
|
|
1878
|
+
if (/^## Sources\b/.test(line.trim())) {
|
|
1879
|
+
result.push(line);
|
|
1880
|
+
continue;
|
|
1881
|
+
}
|
|
1882
|
+
const markers = [...line.matchAll(MARKER_RE2)];
|
|
1883
|
+
if (markers.length === 0) {
|
|
1884
|
+
result.push(line);
|
|
1885
|
+
continue;
|
|
1886
|
+
}
|
|
1887
|
+
const proseOnly = line.replace(MARKER_RE2, "").trim();
|
|
1888
|
+
if (proseOnly.length === 0) {
|
|
1889
|
+
const markerStr = " " + markers.map((m) => m[0]).join(" ");
|
|
1890
|
+
let merged = false;
|
|
1891
|
+
for (let k = result.length - 1; k >= 0; k--) {
|
|
1892
|
+
if (result[k].trim().length > 0) {
|
|
1893
|
+
result[k] = result[k].trimEnd() + markerStr;
|
|
1894
|
+
merged = true;
|
|
1895
|
+
break;
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
if (!merged) result.push(line);
|
|
1899
|
+
continue;
|
|
1900
|
+
}
|
|
1901
|
+
const lastMarkerIdx = line.lastIndexOf("^[raw/");
|
|
1902
|
+
const afterLast = line.slice(lastMarkerIdx).replace(MARKER_RE2, "").trim();
|
|
1903
|
+
const firstMarkerIdx = line.indexOf("^[raw/");
|
|
1904
|
+
const beforeFirst = line.slice(0, firstMarkerIdx).trim();
|
|
1905
|
+
const alreadyAtEnd = afterLast.length === 0 && (beforeFirst.length === 0 || /[.!?]\s*$/.test(beforeFirst));
|
|
1906
|
+
if (alreadyAtEnd) {
|
|
1907
|
+
result.push(line);
|
|
1908
|
+
continue;
|
|
1909
|
+
}
|
|
1910
|
+
let cleaned = line.replace(/\s*\^\[raw\/[^\]]+\]\s*/g, " ").trimEnd();
|
|
1911
|
+
cleaned = cleaned.replace(/ +/g, " ").trimEnd();
|
|
1912
|
+
const markerStrings = markers.map((m) => m[0]);
|
|
1913
|
+
if (cleaned.length > 0 && /[.!?]$/.test(cleaned)) {
|
|
1914
|
+
cleaned += " " + markerStrings.join(" ");
|
|
1915
|
+
} else if (cleaned.length > 0) {
|
|
1916
|
+
cleaned += ". " + markerStrings.join(" ");
|
|
1917
|
+
} else {
|
|
1918
|
+
cleaned = markerStrings.join(" ");
|
|
1919
|
+
}
|
|
1920
|
+
result.push(cleaned);
|
|
1921
|
+
}
|
|
1922
|
+
return result.join("\n");
|
|
1923
|
+
}
|
|
1924
|
+
function buildSourcesFooter(targets) {
|
|
1925
|
+
return "\n## Sources\n" + targets.map((t) => `- ^[${t}]`).join("\n") + "\n";
|
|
1926
|
+
}
|
|
1927
|
+
function reorderSourcesFm(rawFm, targets) {
|
|
1928
|
+
const sourcesLineRe = /^sources:\s*\[([^\]]*)\]\s*$/m;
|
|
1929
|
+
const match = rawFm.match(sourcesLineRe);
|
|
1930
|
+
if (!match) return rawFm;
|
|
1931
|
+
const existing = match[1].split(",").map((s) => s.trim().replace(/^["']|["']$/g, "")).filter((s) => s.length > 0);
|
|
1932
|
+
const targetSet = new Set(targets);
|
|
1933
|
+
const reordered = [
|
|
1934
|
+
...targets,
|
|
1935
|
+
...existing.filter((s) => !targetSet.has(s))
|
|
1936
|
+
];
|
|
1937
|
+
const newLine = `sources: [${reordered.join(", ")}]`;
|
|
1938
|
+
return rawFm.replace(sourcesLineRe, newLine);
|
|
1939
|
+
}
|
|
1940
|
+
function removeExistingFooter(body) {
|
|
1941
|
+
const footerRe = /\n## Sources\n[\s\S]*$/;
|
|
1942
|
+
return body.replace(footerRe, "");
|
|
1943
|
+
}
|
|
1944
|
+
async function runMigrateCitations(input) {
|
|
1945
|
+
const scan = await scanVault(input.vault);
|
|
1946
|
+
if (!scan.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scan };
|
|
1947
|
+
const migrated = [];
|
|
1948
|
+
const skipped = [];
|
|
1949
|
+
let unchanged = 0;
|
|
1950
|
+
for (const page of scan.data.typedKnowledge) {
|
|
1951
|
+
const text = await readPage(page);
|
|
1952
|
+
const split = splitFrontmatter(text);
|
|
1953
|
+
if (!split.ok) continue;
|
|
1954
|
+
const { rawFrontmatter, body } = split.data;
|
|
1955
|
+
const markers = extractCitationMarkers(body);
|
|
1956
|
+
if (markers.length === 0) {
|
|
1957
|
+
unchanged++;
|
|
1958
|
+
continue;
|
|
1959
|
+
}
|
|
1960
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1961
|
+
const uniqueTargets = [];
|
|
1962
|
+
for (const m of markers) {
|
|
1963
|
+
if (!seen.has(m.target)) {
|
|
1964
|
+
seen.add(m.target);
|
|
1965
|
+
uniqueTargets.push(m.target);
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
const bodyWithoutFooter = removeExistingFooter(body);
|
|
1969
|
+
const migratedBody = moveMarkersToParagraphEnd(bodyWithoutFooter);
|
|
1970
|
+
const newFooter = buildSourcesFooter(uniqueTargets);
|
|
1971
|
+
const newFm = reorderSourcesFm(rawFrontmatter, uniqueTargets);
|
|
1972
|
+
const newText = `---
|
|
1973
|
+
${newFm}
|
|
1974
|
+
---
|
|
1975
|
+
${migratedBody}${newFooter}`;
|
|
1976
|
+
if (newText === text) {
|
|
1977
|
+
skipped.push(page.relPath);
|
|
1978
|
+
continue;
|
|
1979
|
+
}
|
|
1980
|
+
if (!input.dryRun) {
|
|
1981
|
+
await writeFile7(page.absPath, newText, "utf8");
|
|
1982
|
+
}
|
|
1983
|
+
migrated.push(page.relPath);
|
|
1984
|
+
}
|
|
1985
|
+
const exitCode = migrated.length > 0 ? ExitCode.MIGRATION_APPLIED : ExitCode.OK;
|
|
1986
|
+
const hintLines = [`scanned: ${migrated.length + skipped.length + unchanged}`];
|
|
1987
|
+
if (migrated.length > 0) hintLines.push(`migrated: ${migrated.length}`);
|
|
1988
|
+
if (skipped.length > 0) hintLines.push(`skipped (already clean): ${skipped.length}`);
|
|
1989
|
+
if (unchanged > 0) hintLines.push(`unchanged (no markers): ${unchanged}`);
|
|
1990
|
+
return {
|
|
1991
|
+
exitCode,
|
|
1992
|
+
result: ok({
|
|
1993
|
+
scanned: migrated.length + skipped.length + unchanged,
|
|
1994
|
+
migrated,
|
|
1995
|
+
skipped,
|
|
1996
|
+
unchanged,
|
|
1997
|
+
humanHint: hintLines.join("\n")
|
|
1998
|
+
})
|
|
1999
|
+
};
|
|
2000
|
+
}
|
|
2001
|
+
|
|
1799
2002
|
// src/cli.ts
|
|
1800
2003
|
var pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
1801
2004
|
var program = new Command();
|
|
@@ -1931,6 +2134,11 @@ program.command("dedup [vault]").description("detect duplicate raw sources by sh
|
|
|
1931
2134
|
if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
|
|
1932
2135
|
else emit(await runDedup({ vault: v.vault }));
|
|
1933
2136
|
});
|
|
2137
|
+
program.command("migrate-citations [vault]").description("migrate ^[raw/...] markers to paragraph-end citations").option("--dry-run", "preview changes without writing", false).action(async (vault, opts) => {
|
|
2138
|
+
const v = await resolveVaultArg(vault);
|
|
2139
|
+
if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
|
|
2140
|
+
else emit(await runMigrateCitations({ vault: v.vault, dryRun: !!opts.dryRun }));
|
|
2141
|
+
});
|
|
1934
2142
|
program.parseAsync(process.argv).catch((e) => {
|
|
1935
2143
|
process.stdout.write(JSON.stringify({ ok: false, error: "INTERNAL", detail: { message: String(e) } }) + "\n");
|
|
1936
2144
|
process.exit(1);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skillwiki",
|
|
3
|
-
"version": "0.2.0-beta.
|
|
3
|
+
"version": "0.2.0-beta.14",
|
|
4
4
|
"skills": "./",
|
|
5
5
|
"description": "Project-aware Karpathy-style knowledge base for Claude Code: 11 prompt-only skills (wiki-*, proj-*, using-skillwiki) backed by the deterministic `skillwiki` CLI (8 subcommands, JSON-by-default).",
|
|
6
6
|
"author": {
|
package/skills/package.json
CHANGED
package/templates/SCHEMA.md
CHANGED
|
@@ -49,7 +49,8 @@ Rule: every tag on every page MUST appear in this taxonomy. Add new tags here fi
|
|
|
49
49
|
|
|
50
50
|
- File names: lowercase-hyphenated, no spaces.
|
|
51
51
|
- Wikilinks in YAML: quoted, `"[[name]]"`. Body wikilinks: unquoted `[[name]]`.
|
|
52
|
-
- Citations in body: `^[raw/...]` markers; every entry in `sources:` MUST appear in body.
|
|
52
|
+
- Citations in body: `^[raw/...]` markers at paragraph-end; every entry in `sources:` MUST appear in body and in `## Sources` footer.
|
|
53
|
+
- Legacy inline `^[raw/...]` markers remain valid; `migrate-citations` converts them.
|
|
53
54
|
- sha256 in `raw/` frontmatter is computed by `skillwiki hash` over body bytes after closing `---`.
|
|
54
55
|
|
|
55
56
|
## Obsidian Integration
|