skillwiki 0.2.1-beta.10 → 0.2.1-beta.12

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
@@ -162,9 +162,25 @@ var CompoundSchema = z.object({
162
162
  promoted_to: wikilink.optional(),
163
163
  cssclasses: z.array(z.string()).optional()
164
164
  });
165
+ var MetaSchema = z.object({
166
+ title: z.string().min(1),
167
+ aliases: z.array(z.string()).optional(),
168
+ created: isoDate,
169
+ updated: isoDate,
170
+ type: z.literal("meta"),
171
+ tags: z.array(z.string()),
172
+ confidence: z.enum(["high", "medium", "low"]).optional(),
173
+ provenance: z.enum(["research", "project", "mixed"]).optional(),
174
+ provenance_projects: z.array(wikilink).min(2, "meta pages must reference \u22652 projects")
175
+ }).superRefine((v, ctx) => {
176
+ if (v.provenance && v.provenance !== "research" && (!v.provenance_projects || v.provenance_projects.length === 0)) {
177
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["provenance_projects"], message: "required when provenance != research" });
178
+ }
179
+ });
165
180
  function detectSchema(fm) {
166
181
  const COMPOUND_TYPES = /* @__PURE__ */ new Set(["lesson", "pattern", "antipattern", "gotcha"]);
167
182
  if (typeof fm.type === "string" && COMPOUND_TYPES.has(fm.type) && "project" in fm) return { schema: "compound" };
183
+ if (fm.type === "meta") return { schema: "meta" };
168
184
  if ("type" in fm && "sources" in fm) return { schema: "typed-knowledge" };
169
185
  if (typeof fm.sha256 === "string" && "ingested" in fm) return { schema: "raw" };
170
186
  if ("kind" in fm && "status" in fm) return { schema: "work-item" };
@@ -303,7 +319,8 @@ var SCHEMAS = {
303
319
  "typed-knowledge": TypedKnowledgeSchema,
304
320
  "raw": RawSourceSchema,
305
321
  "work-item": WorkItemSchema,
306
- "compound": CompoundSchema
322
+ "compound": CompoundSchema,
323
+ "meta": MetaSchema
307
324
  };
308
325
  async function runValidate(input) {
309
326
  let text;
@@ -342,7 +359,7 @@ import { dirname } from "path";
342
359
  // src/utils/vault.ts
343
360
  import { readFile as readFile3, readdir, stat } from "fs/promises";
344
361
  import { join, relative, sep } from "path";
345
- var TYPED_DIRS = ["entities", "concepts", "comparisons", "queries"];
362
+ var TYPED_DIRS = ["entities", "concepts", "comparisons", "queries", "meta"];
346
363
  async function scanVault(root) {
347
364
  try {
348
365
  await stat(join(root, "SCHEMA.md"));
@@ -1596,6 +1613,9 @@ Chronological action log. Newest entries last. Skill writes append entries; lint
1596
1613
  return { exitCode: ExitCode.OK, result: ok({ entries, threshold: input.threshold, rotated: true, rotated_to: rotatedName, humanHint: `rotated ${entries} entries to ${rotatedName}` }) };
1597
1614
  }
1598
1615
 
1616
+ // src/commands/lint.ts
1617
+ import { readFile as readFile12, writeFile as writeFile6 } from "fs/promises";
1618
+
1599
1619
  // src/commands/topic-map-check.ts
1600
1620
  var DEFAULT_THRESHOLD = 200;
1601
1621
  async function runTopicMapCheck(input) {
@@ -1732,6 +1752,8 @@ var WARNING_ORDER = ["index_incomplete", "index_link_format", "stale_page", "pag
1732
1752
  var INFO_ORDER = ["bridges", "page_structure", "topic_map_recommended", "frontmatter_wikilink", "wikilink_citation"];
1733
1753
  async function runLint(input) {
1734
1754
  const buckets = {};
1755
+ const fixed = [];
1756
+ const unresolved = [];
1735
1757
  const links = await runLinks({ vault: input.vault });
1736
1758
  if (links.result.ok && links.result.data.broken.length > 0) buckets.broken_wikilinks = links.result.data.broken;
1737
1759
  if (!links.result.ok && links.result.error === "INVALID_FRONTMATTER") {
@@ -1822,6 +1844,105 @@ async function runLint(input) {
1822
1844
  if (noOverview.length > 0) buckets.missing_overview = noOverview;
1823
1845
  if (fmWikilinkFlags.length > 0) buckets.frontmatter_wikilink = fmWikilinkFlags;
1824
1846
  if (wikilinkCitationFlags.length > 0) buckets.wikilink_citation = wikilinkCitationFlags;
1847
+ if (input.fix && legacyPages.length > 0) {
1848
+ const FENCE_RE2 = /```[\s\S]*?```/g;
1849
+ const INLINE_MARKER = /\^\[raw\/[^\]]+\]/g;
1850
+ for (const relPath of legacyPages) {
1851
+ try {
1852
+ const absPath = `${input.vault}/${relPath}`;
1853
+ const raw = await readFile12(absPath, "utf8");
1854
+ const split = splitFrontmatter(raw);
1855
+ if (!split.ok) {
1856
+ unresolved.push(relPath);
1857
+ continue;
1858
+ }
1859
+ const body = split.data.body;
1860
+ const rawFm = split.data.rawFrontmatter;
1861
+ const stripped = body.replace(FENCE_RE2, "");
1862
+ const lines = stripped.split("\n");
1863
+ const inlineMarkers = [];
1864
+ let inSources = false;
1865
+ for (const line of lines) {
1866
+ if (/^## Sources\b/.test(line.trim())) {
1867
+ inSources = true;
1868
+ continue;
1869
+ }
1870
+ if (inSources) continue;
1871
+ for (const m of line.matchAll(INLINE_MARKER)) {
1872
+ inlineMarkers.push(m[0]);
1873
+ }
1874
+ }
1875
+ if (inlineMarkers.length === 0) {
1876
+ unresolved.push(relPath);
1877
+ continue;
1878
+ }
1879
+ const bodyLines = body.split("\n");
1880
+ let inSrc = false;
1881
+ const newBodyLines = [];
1882
+ const seen = /* @__PURE__ */ new Set();
1883
+ for (const line of bodyLines) {
1884
+ if (/^## Sources\b/.test(line.trim())) {
1885
+ inSrc = true;
1886
+ newBodyLines.push(line);
1887
+ continue;
1888
+ }
1889
+ if (inSrc) {
1890
+ newBodyLines.push(line);
1891
+ continue;
1892
+ }
1893
+ const lineWithoutMarkers = line.replace(INLINE_MARKER, "").trim();
1894
+ if (lineWithoutMarkers.length === 0 && INLINE_MARKER.test(line)) {
1895
+ continue;
1896
+ }
1897
+ let cleaned = line;
1898
+ for (const marker of inlineMarkers) {
1899
+ if (seen.has(marker)) continue;
1900
+ const escapedMarker = marker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1901
+ const trailingRe = new RegExp(`([.!?]\\s*)${escapedMarker}`);
1902
+ if (trailingRe.test(cleaned)) {
1903
+ cleaned = cleaned.replace(trailingRe, "$1");
1904
+ seen.add(marker);
1905
+ }
1906
+ const midRe = new RegExp(`${escapedMarker}\\s*`);
1907
+ if (!seen.has(marker) && midRe.test(cleaned)) {
1908
+ cleaned = cleaned.replace(midRe, "");
1909
+ seen.add(marker);
1910
+ }
1911
+ }
1912
+ newBodyLines.push(cleaned);
1913
+ }
1914
+ let newBody = newBodyLines.join("\n");
1915
+ const dedupedMarkers = [...new Set(inlineMarkers)];
1916
+ if (inSrc) {
1917
+ const existingSources = new Set(
1918
+ body.split("\n").filter((l) => /^- \^\[raw\//.test(l.trim())).map((l) => l.trim().replace(/^- /, ""))
1919
+ );
1920
+ const newMarkers = dedupedMarkers.filter((m) => !existingSources.has(m));
1921
+ const sourceLines = newMarkers.map((m) => `- ${m}`);
1922
+ if (sourceLines.length > 0) {
1923
+ newBody = newBody.trimEnd() + "\n" + sourceLines.join("\n") + "\n";
1924
+ }
1925
+ } else {
1926
+ const sourceLines = dedupedMarkers.map((m) => `- ${m}`);
1927
+ newBody = newBody.trimEnd() + "\n\n## Sources\n\n" + sourceLines.join("\n") + "\n";
1928
+ }
1929
+ const newContent = `---
1930
+ ${rawFm}
1931
+ ---
1932
+ ${newBody}`;
1933
+ await writeFile6(absPath, newContent, "utf8");
1934
+ fixed.push(relPath);
1935
+ } catch {
1936
+ unresolved.push(relPath);
1937
+ }
1938
+ }
1939
+ if (fixed.length > 0) {
1940
+ const fixedSet = new Set(fixed);
1941
+ const remaining = legacyPages.filter((p) => !fixedSet.has(p));
1942
+ if (remaining.length > 0) buckets.legacy_citation_style = remaining;
1943
+ else delete buckets.legacy_citation_style;
1944
+ }
1945
+ }
1825
1946
  }
1826
1947
  const errorOut = ERROR_ORDER.flatMap((k) => buckets[k] ? [{ kind: k, items: buckets[k] }] : []);
1827
1948
  const warningOut = WARNING_ORDER.flatMap((k) => buckets[k] ? [{ kind: k, items: buckets[k] }] : []);
@@ -1849,13 +1970,15 @@ async function runLint(input) {
1849
1970
  vault: { path: input.vault, source: input.source ?? "resolved" },
1850
1971
  summary,
1851
1972
  by_severity: { error: errorOut, warning: warningOut, info: infoOut },
1973
+ fixed,
1974
+ unresolved,
1852
1975
  humanHint: hintLines.join("\n")
1853
1976
  })
1854
1977
  };
1855
1978
  }
1856
1979
 
1857
1980
  // src/commands/config.ts
1858
- import { readFile as readFile12 } from "fs/promises";
1981
+ import { readFile as readFile13 } from "fs/promises";
1859
1982
  import { existsSync } from "fs";
1860
1983
  import { join as join14 } from "path";
1861
1984
  function validateKey(key) {
@@ -1880,7 +2003,7 @@ async function runConfigSet(input) {
1880
2003
  try {
1881
2004
  let originalContent;
1882
2005
  try {
1883
- originalContent = await readFile12(filePath, "utf8");
2006
+ originalContent = await readFile13(filePath, "utf8");
1884
2007
  } catch {
1885
2008
  }
1886
2009
  const existing = originalContent !== void 0 ? parseDotenvText(originalContent) : {};
@@ -2193,7 +2316,7 @@ async function runDoctor(input) {
2193
2316
  }
2194
2317
 
2195
2318
  // src/commands/archive.ts
2196
- import { rename as rename3, mkdir as mkdir5, readFile as readFile13, writeFile as writeFile6 } from "fs/promises";
2319
+ import { rename as rename3, mkdir as mkdir5, readFile as readFile14, writeFile as writeFile7 } from "fs/promises";
2197
2320
  import { join as join18, dirname as dirname7 } from "path";
2198
2321
  async function runArchive(input) {
2199
2322
  const scan = await scanVault(input.vault);
@@ -2216,12 +2339,12 @@ async function runArchive(input) {
2216
2339
  if (!isRaw) {
2217
2340
  const indexPath = join18(input.vault, "index.md");
2218
2341
  try {
2219
- const idx = await readFile13(indexPath, "utf8");
2342
+ const idx = await readFile14(indexPath, "utf8");
2220
2343
  const slug = relPath.replace(/\.md$/, "").split("/").pop();
2221
2344
  const originalLines = idx.split("\n");
2222
2345
  const filtered = originalLines.filter((l) => !l.includes(`[[${slug}]]`));
2223
2346
  if (filtered.length !== originalLines.length) {
2224
- await writeFile6(indexPath, filtered.join("\n"), "utf8");
2347
+ await writeFile7(indexPath, filtered.join("\n"), "utf8");
2225
2348
  indexUpdated = true;
2226
2349
  }
2227
2350
  } catch (e) {
@@ -2234,7 +2357,7 @@ async function runArchive(input) {
2234
2357
 
2235
2358
  // src/commands/drift.ts
2236
2359
  import { createHash as createHash2 } from "crypto";
2237
- import { writeFile as writeFile7 } from "fs/promises";
2360
+ import { writeFile as writeFile8 } from "fs/promises";
2238
2361
 
2239
2362
  // src/utils/fetch.ts
2240
2363
  async function controlledFetch(url, opts) {
@@ -2320,7 +2443,7 @@ async function runDrift(input) {
2320
2443
  ${newFm}
2321
2444
  ---
2322
2445
  ${body}`;
2323
- await writeFile7(raw.absPath, newText, "utf8");
2446
+ await writeFile8(raw.absPath, newText, "utf8");
2324
2447
  results.push({
2325
2448
  raw_path: raw.relPath,
2326
2449
  source_url: sourceUrl,
@@ -2355,7 +2478,7 @@ ${body}`;
2355
2478
  }
2356
2479
 
2357
2480
  // src/commands/migrate-citations.ts
2358
- import { writeFile as writeFile8 } from "fs/promises";
2481
+ import { writeFile as writeFile9 } from "fs/promises";
2359
2482
  var MARKER_RE2 = /\^\[(raw\/[^\]]+)\]/g;
2360
2483
  function moveMarkersToParagraphEnd(body) {
2361
2484
  const lines = body.split("\n");
@@ -2478,7 +2601,7 @@ ${migratedBody}${newFooter}`;
2478
2601
  continue;
2479
2602
  }
2480
2603
  if (!input.dryRun) {
2481
- await writeFile8(page.absPath, newText, "utf8");
2604
+ await writeFile9(page.absPath, newText, "utf8");
2482
2605
  }
2483
2606
  migrated.push(page.relPath);
2484
2607
  }
@@ -2500,7 +2623,7 @@ ${migratedBody}${newFooter}`;
2500
2623
  }
2501
2624
 
2502
2625
  // src/commands/frontmatter-fix.ts
2503
- import { writeFile as writeFile9 } from "fs/promises";
2626
+ import { writeFile as writeFile10 } from "fs/promises";
2504
2627
  function isoToday() {
2505
2628
  return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
2506
2629
  }
@@ -2542,7 +2665,7 @@ ${newBody}`;
2542
2665
  continue;
2543
2666
  }
2544
2667
  if (!input.dryRun) {
2545
- await writeFile9(page.absPath, newText, "utf8");
2668
+ await writeFile10(page.absPath, newText, "utf8");
2546
2669
  }
2547
2670
  fixed.push(page.relPath);
2548
2671
  }
@@ -2626,7 +2749,7 @@ async function runUpdate(input) {
2626
2749
  }
2627
2750
 
2628
2751
  // src/commands/transcripts.ts
2629
- import { readdir as readdir4, stat as stat6, readFile as readFile14 } from "fs/promises";
2752
+ import { readdir as readdir4, stat as stat6, readFile as readFile15 } from "fs/promises";
2630
2753
  import { join as join19 } from "path";
2631
2754
  async function runTranscripts(input) {
2632
2755
  const dir = join19(input.vault, "raw", "transcripts");
@@ -2640,7 +2763,7 @@ async function runTranscripts(input) {
2640
2763
  for (const entry of entries) {
2641
2764
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
2642
2765
  const filePath = join19(dir, entry.name);
2643
- const content = await readFile14(filePath, "utf8");
2766
+ const content = await readFile15(filePath, "utf8");
2644
2767
  const fm = extractFrontmatter(content);
2645
2768
  if (!fm.ok) continue;
2646
2769
  const ingested = typeof fm.data.ingested === "string" ? fm.data.ingested : "";
@@ -2763,7 +2886,7 @@ program.command("log-rotate [vault]").option("--threshold <n>", "entry count thr
2763
2886
  if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
2764
2887
  else emit(await runLogRotate({ vault: v.vault, threshold: opts.threshold, apply: !!opts.apply }));
2765
2888
  });
2766
- program.command("lint [vault]").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("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
2889
+ program.command("lint [vault]").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("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
2767
2890
  const v = await resolveVaultArg(vault, opts.wiki);
2768
2891
  if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
2769
2892
  else emit(await runLint({
@@ -2771,7 +2894,8 @@ program.command("lint [vault]").option("--days <n>", "stale threshold", (s) => p
2771
2894
  source: vault ? "flag" : void 0,
2772
2895
  days: opts.days,
2773
2896
  lines: opts.lines,
2774
- logThreshold: opts.logThreshold
2897
+ logThreshold: opts.logThreshold,
2898
+ fix: opts.fix ?? false
2775
2899
  }));
2776
2900
  });
2777
2901
  var configCmd = program.command("config").description("manage skillwiki configuration");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillwiki",
3
- "version": "0.2.1-beta.10",
3
+ "version": "0.2.1-beta.12",
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.2.1-beta.10",
3
+ "version": "0.2.1-beta.12",
4
4
  "skills": "./",
5
5
  "description": "Project-aware Karpathy-style knowledge base for Claude Code: 15 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/skills",
3
- "version": "0.2.1-beta.10",
3
+ "version": "0.2.1-beta.12",
4
4
  "private": true,
5
5
  "files": [
6
6
  "wiki-*",