skillwiki 0.2.0-beta.14 → 0.2.0-beta.19

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.
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env node
2
+ #!/usr/bin/env node
3
+ import {
4
+ semverGt
5
+ } from "./chunk-XM5IYZX7.js";
6
+
7
+ // src/auto-update-bg.ts
8
+ import { execSync } from "child_process";
9
+ import { writeFileSync, mkdirSync } from "fs";
10
+ import { join, dirname } from "path";
11
+ var home = process.argv[2];
12
+ var currentVersion = process.argv[3];
13
+ if (!home || !currentVersion) process.exit(0);
14
+ var cacheFile = join(home, ".skillwiki", ".update-cache.json");
15
+ setTimeout(() => process.exit(0), 3e4);
16
+ try {
17
+ const latest = execSync("npm view skillwiki@beta version", {
18
+ encoding: "utf8",
19
+ timeout: 15e3
20
+ }).trim();
21
+ mkdirSync(dirname(cacheFile), { recursive: true });
22
+ const cache = {
23
+ lastCheck: Date.now(),
24
+ latestVersion: latest,
25
+ currentVersion
26
+ };
27
+ if (semverGt(latest, currentVersion)) {
28
+ execSync("npm install -g skillwiki@beta", {
29
+ stdio: "ignore",
30
+ timeout: 6e4
31
+ });
32
+ writeFileSync(cacheFile, JSON.stringify({ ...cache, updateAppliedAt: Date.now() }, null, 2));
33
+ } else {
34
+ writeFileSync(cacheFile, JSON.stringify(cache, null, 2));
35
+ }
36
+ } catch {
37
+ try {
38
+ mkdirSync(dirname(cacheFile), { recursive: true });
39
+ writeFileSync(cacheFile, JSON.stringify({ lastCheck: Date.now(), latestVersion: "", currentVersion }, null, 2));
40
+ } catch {
41
+ }
42
+ }
43
+ process.exit(0);
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/utils/semver.ts
4
+ function semverGt(a, b) {
5
+ const pa = parseSemver(a);
6
+ const pb = parseSemver(b);
7
+ if (!pa || !pb) return a > b;
8
+ if (pa.major !== pb.major) return pa.major > pb.major;
9
+ if (pa.minor !== pb.minor) return pa.minor > pb.minor;
10
+ if (pa.patch !== pb.patch) return pa.patch > pb.patch;
11
+ if (!pa.pre && pb.pre) return true;
12
+ if (pa.pre && !pb.pre) return false;
13
+ if (!pa.pre && !pb.pre) return false;
14
+ const aParts = pa.pre.split(".");
15
+ const bParts = pb.pre.split(".");
16
+ const len = Math.max(aParts.length, bParts.length);
17
+ for (let i = 0; i < len; i++) {
18
+ const ai = aParts[i];
19
+ const bi = bParts[i];
20
+ if (ai === void 0) return false;
21
+ if (bi === void 0) return true;
22
+ const aNum = parseInt(ai, 10);
23
+ const bNum = parseInt(bi, 10);
24
+ if (!isNaN(aNum) && !isNaN(bNum)) {
25
+ if (aNum !== bNum) return aNum > bNum;
26
+ } else {
27
+ if (ai !== bi) return ai > bi;
28
+ }
29
+ }
30
+ return false;
31
+ }
32
+ function parseSemver(version) {
33
+ const match = version.match(/^(\d+)\.(\d+)\.(\d+)(?:-(.+))?$/);
34
+ if (!match) return null;
35
+ return {
36
+ major: parseInt(match[1], 10),
37
+ minor: parseInt(match[2], 10),
38
+ patch: parseInt(match[3], 10),
39
+ pre: match[4] ?? null
40
+ };
41
+ }
42
+
43
+ export {
44
+ semverGt
45
+ };
package/dist/cli.js CHANGED
@@ -1,7 +1,10 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ semverGt
4
+ } from "./chunk-XM5IYZX7.js";
2
5
 
3
6
  // src/cli.ts
4
- import { readFileSync } from "fs";
7
+ import { readFileSync as readFileSync4 } from "fs";
5
8
  import { Command } from "commander";
6
9
 
7
10
  // src/utils/output.ts
@@ -108,10 +111,10 @@ var TypedKnowledgeSchema = z.object({
108
111
  });
109
112
  var sha256Hex = z.string().regex(/^[0-9a-f]{64}$/);
110
113
  var RawSourceSchema = z.object({
111
- title: z.string().min(1),
112
- source_url: z.string().url().nullable(),
114
+ title: z.string().min(1).optional(),
115
+ source_url: z.string().nullable(),
113
116
  ingested: isoDate,
114
- ingested_by: z.enum(["wiki-ingest", "proj-work", "manual"]),
117
+ ingested_by: z.enum(["wiki-ingest", "proj-work", "manual"]).optional(),
115
118
  sha256: sha256Hex,
116
119
  project: wikilink.optional(),
117
120
  work_item: wikilink.optional(),
@@ -745,6 +748,48 @@ function isLegacyCitationStyle(body) {
745
748
  }
746
749
  return false;
747
750
  }
751
+ function hasOrphanedCitations(body) {
752
+ const stripped = stripFences(body);
753
+ const lines = stripped.split("\n");
754
+ let inSources = false;
755
+ let sourcesEnded = false;
756
+ let sourcesStartLine = -1;
757
+ let lastNonBlankInSources = -1;
758
+ for (let i = 0; i < lines.length; i++) {
759
+ const line = lines[i];
760
+ const trimmed = line.trim();
761
+ if (/^## Sources\b/.test(trimmed)) {
762
+ inSources = true;
763
+ sourcesStartLine = i;
764
+ continue;
765
+ }
766
+ if (!inSources || sourcesEnded) continue;
767
+ if (trimmed.length === 0) {
768
+ if (lastNonBlankInSources >= 0) {
769
+ sourcesEnded = true;
770
+ }
771
+ continue;
772
+ }
773
+ const isListItem = /^\s*[-*]\s+/.test(line);
774
+ const hasMarker = /\^\[raw\//.test(line);
775
+ if (isListItem && hasMarker) {
776
+ lastNonBlankInSources = i;
777
+ } else if (hasMarker && !isListItem) {
778
+ return true;
779
+ } else {
780
+ sourcesEnded = true;
781
+ }
782
+ }
783
+ if (sourcesStartLine === -1) return false;
784
+ if (sourcesEnded) {
785
+ for (let i = lastNonBlankInSources + 1; i < lines.length; i++) {
786
+ if (/\^\[raw\//.test(lines[i])) {
787
+ return true;
788
+ }
789
+ }
790
+ }
791
+ return false;
792
+ }
748
793
 
749
794
  // src/commands/audit.ts
750
795
  async function runAudit(input) {
@@ -1475,9 +1520,11 @@ async function runDedup(input) {
1475
1520
  }
1476
1521
 
1477
1522
  // src/commands/lint.ts
1523
+ var STRUCT_MIN_BODY_LINES = 60;
1524
+ var STRUCT_MIN_SECTIONS = 3;
1478
1525
  var ERROR_ORDER = ["broken_wikilinks", "invalid_frontmatter", "raw_dedup", "tag_not_in_taxonomy"];
1479
- var WARNING_ORDER = ["index_incomplete", "index_link_format", "stale_page", "page_too_large", "log_rotate_needed", "contested", "orphans", "legacy_citation_style"];
1480
- var INFO_ORDER = ["bridges", "low_confidence_single_source", "topic_map_recommended"];
1526
+ var WARNING_ORDER = ["index_incomplete", "index_link_format", "stale_page", "page_too_large", "log_rotate_needed", "contested", "orphans", "legacy_citation_style", "orphaned_citations"];
1527
+ var INFO_ORDER = ["bridges", "low_confidence_single_source", "page_structure", "topic_map_recommended"];
1481
1528
  async function runLint(input) {
1482
1529
  const buckets = {};
1483
1530
  const links = await runLinks({ vault: input.vault });
@@ -1523,13 +1570,32 @@ async function runLint(input) {
1523
1570
  const scan = await scanVault(input.vault);
1524
1571
  if (scan.ok) {
1525
1572
  const legacyPages = [];
1573
+ const orphanedPages = [];
1574
+ const structFlags = [];
1526
1575
  for (const page of scan.data.typedKnowledge) {
1527
1576
  const text = await readPage(page);
1528
1577
  const split = splitFrontmatter(text);
1529
1578
  if (!split.ok) continue;
1530
- if (isLegacyCitationStyle(split.data.body)) legacyPages.push(page.relPath);
1579
+ const body = split.data.body;
1580
+ if (isLegacyCitationStyle(body)) legacyPages.push(page.relPath);
1581
+ if (hasOrphanedCitations(body)) orphanedPages.push(page.relPath);
1582
+ const bodyLines = body.split("\n").filter((l) => l.trim().length > 0).length;
1583
+ if (bodyLines < STRUCT_MIN_BODY_LINES) {
1584
+ const hasOverview = /^## Overview/m.test(body);
1585
+ const hasRelated = /^## (Related|Relationships)/m.test(body);
1586
+ const sectionCount = (body.match(/^## /gm) ?? []).length;
1587
+ if (!hasOverview || !hasRelated || sectionCount < STRUCT_MIN_SECTIONS) {
1588
+ const reasons = [];
1589
+ if (!hasOverview) reasons.push("no Overview");
1590
+ if (!hasRelated) reasons.push("no Related or Relationships");
1591
+ if (sectionCount < STRUCT_MIN_SECTIONS) reasons.push(`only ${sectionCount} sections`);
1592
+ structFlags.push(`${page.relPath}: ${bodyLines} lines, ${reasons.join(", ")}`);
1593
+ }
1594
+ }
1531
1595
  }
1532
1596
  if (legacyPages.length > 0) buckets.legacy_citation_style = legacyPages;
1597
+ if (orphanedPages.length > 0) buckets.orphaned_citations = orphanedPages;
1598
+ if (structFlags.length > 0) buckets.page_structure = structFlags;
1533
1599
  }
1534
1600
  const errorOut = ERROR_ORDER.flatMap((k) => buckets[k] ? [{ kind: k, items: buckets[k] }] : []);
1535
1601
  const warningOut = WARNING_ORDER.flatMap((k) => buckets[k] ? [{ kind: k, items: buckets[k] }] : []);
@@ -1610,9 +1676,72 @@ async function runConfigPath(input) {
1610
1676
  }
1611
1677
 
1612
1678
  // src/commands/doctor.ts
1613
- import { existsSync as existsSync2, readdirSync, statSync } from "fs";
1614
- import { join as join14 } from "path";
1679
+ import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync2, statSync } from "fs";
1680
+ import { join as join15 } from "path";
1615
1681
  import { execSync } from "child_process";
1682
+
1683
+ // src/utils/auto-update.ts
1684
+ import { readFileSync, writeFileSync, existsSync as existsSync2, mkdirSync } from "fs";
1685
+ import { join as join14, dirname as dirname6 } from "path";
1686
+ import { spawn } from "child_process";
1687
+
1688
+ // src/utils/update-consts.ts
1689
+ var CACHE_FILENAME = ".update-cache.json";
1690
+ var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
1691
+ var ENV_DISABLE_KEY = "NO_UPDATE_NOTIFIER";
1692
+ var CLI_DISABLE_FLAG = "--no-update-notifier";
1693
+
1694
+ // src/utils/auto-update.ts
1695
+ function cachePath(home) {
1696
+ return join14(home, ".skillwiki", CACHE_FILENAME);
1697
+ }
1698
+ function readCacheRaw(home) {
1699
+ try {
1700
+ const raw = readFileSync(cachePath(home), "utf8");
1701
+ return JSON.parse(raw);
1702
+ } catch {
1703
+ return null;
1704
+ }
1705
+ }
1706
+ function readCache(home) {
1707
+ const cache = readCacheRaw(home);
1708
+ if (!cache) return { cache: null, hasUpdate: false, isStale: true };
1709
+ const isStale = Date.now() - cache.lastCheck >= CHECK_INTERVAL_MS;
1710
+ const hasUpdate = !!cache.latestVersion && semverGt(cache.latestVersion, cache.currentVersion);
1711
+ return { cache, hasUpdate, isStale };
1712
+ }
1713
+ function writeCache(home, cache) {
1714
+ const p = cachePath(home);
1715
+ mkdirSync(dirname6(p), { recursive: true });
1716
+ writeFileSync(p, JSON.stringify(cache, null, 2));
1717
+ }
1718
+ function latestFromCache(home, currentVersion) {
1719
+ const { cache } = readCache(home);
1720
+ if (!cache || !cache.latestVersion) return { hasUpdate: false, latest: null };
1721
+ return {
1722
+ hasUpdate: semverGt(cache.latestVersion, currentVersion),
1723
+ latest: cache.latestVersion
1724
+ };
1725
+ }
1726
+ function isDisabled() {
1727
+ return !!(process.env[ENV_DISABLE_KEY] || process.env.NODE_ENV === "test" || process.argv.includes(CLI_DISABLE_FLAG));
1728
+ }
1729
+ function triggerAutoUpdate(home, currentVersion) {
1730
+ if (isDisabled()) return;
1731
+ const { isStale } = readCache(home);
1732
+ if (!isStale) return;
1733
+ const bgScript = new URL("../auto-update-bg.js", import.meta.url).pathname;
1734
+ if (!existsSync2(bgScript)) return;
1735
+ const child = spawn(process.execPath, [bgScript, home, currentVersion], {
1736
+ detached: true,
1737
+ stdio: "ignore"
1738
+ });
1739
+ child.on("error", () => {
1740
+ });
1741
+ child.unref();
1742
+ }
1743
+
1744
+ // src/commands/doctor.ts
1616
1745
  function check(status, id, label, detail) {
1617
1746
  return { id, label, status, detail };
1618
1747
  }
@@ -1639,7 +1768,7 @@ function checkCliOnPath(argv) {
1639
1768
  }
1640
1769
  async function checkConfigFile(home) {
1641
1770
  const cfgPath = configPath(home);
1642
- if (!existsSync2(cfgPath)) {
1771
+ if (!existsSync3(cfgPath)) {
1643
1772
  return check("warn", "config_file", "Config file exists", `${cfgPath} not found`);
1644
1773
  }
1645
1774
  try {
@@ -1654,7 +1783,7 @@ function checkWikiPathExists(resolvedPath) {
1654
1783
  if (resolvedPath === void 0) {
1655
1784
  return check("error", "wiki_path_exists", "Vault directory exists", "Cannot check \u2014 WIKI_PATH not resolved");
1656
1785
  }
1657
- if (existsSync2(resolvedPath) && statSync(resolvedPath).isDirectory()) {
1786
+ if (existsSync3(resolvedPath) && statSync(resolvedPath).isDirectory()) {
1658
1787
  return check("pass", "wiki_path_exists", "Vault directory exists", resolvedPath);
1659
1788
  }
1660
1789
  return check("error", "wiki_path_exists", "Vault directory exists", `${resolvedPath} does not exist or is not a directory`);
@@ -1663,13 +1792,13 @@ function checkVaultStructure(resolvedPath) {
1663
1792
  if (resolvedPath === void 0) {
1664
1793
  return check("error", "vault_structure", "Vault structure valid", "Cannot check \u2014 WIKI_PATH not resolved");
1665
1794
  }
1666
- if (!existsSync2(resolvedPath)) {
1795
+ if (!existsSync3(resolvedPath)) {
1667
1796
  return check("error", "vault_structure", "Vault structure valid", "Cannot check \u2014 vault directory does not exist");
1668
1797
  }
1669
1798
  const missing = [];
1670
- if (!existsSync2(join14(resolvedPath, "SCHEMA.md"))) missing.push("SCHEMA.md");
1799
+ if (!existsSync3(join15(resolvedPath, "SCHEMA.md"))) missing.push("SCHEMA.md");
1671
1800
  for (const dir of ["raw", "entities", "concepts", "meta"]) {
1672
- if (!existsSync2(join14(resolvedPath, dir))) missing.push(dir + "/");
1801
+ if (!existsSync3(join15(resolvedPath, dir))) missing.push(dir + "/");
1673
1802
  }
1674
1803
  if (missing.length === 0) {
1675
1804
  return check("pass", "vault_structure", "Vault structure valid", "All required files and directories present");
@@ -1677,8 +1806,8 @@ function checkVaultStructure(resolvedPath) {
1677
1806
  return check("error", "vault_structure", "Vault structure valid", `Missing: ${missing.join(", ")}`);
1678
1807
  }
1679
1808
  function checkSkillsInstalled(home) {
1680
- const skillsDir = join14(home, ".claude", "skills");
1681
- if (!existsSync2(skillsDir)) {
1809
+ const skillsDir = join15(home, ".claude", "skills");
1810
+ if (!existsSync3(skillsDir)) {
1682
1811
  return check("warn", "skills_installed", "Skills installed", `${skillsDir} not found`);
1683
1812
  }
1684
1813
  const found = findSkillMd(skillsDir);
@@ -1687,6 +1816,42 @@ function checkSkillsInstalled(home) {
1687
1816
  }
1688
1817
  return check("warn", "skills_installed", "Skills installed", "No SKILL.md files found in ~/.claude/skills/");
1689
1818
  }
1819
+ function checkNpmUpdate(home, currentVersion) {
1820
+ const { hasUpdate, latest } = latestFromCache(home, currentVersion);
1821
+ if (!latest) {
1822
+ return check("pass", "npm_update", "npm CLI version", `v${currentVersion} (no cache yet)`);
1823
+ }
1824
+ if (hasUpdate) {
1825
+ return check("warn", "npm_update", "npm CLI version", `v${currentVersion} \u2014 update available: v${latest}. Run \`skillwiki update\`.`);
1826
+ }
1827
+ return check("pass", "npm_update", "npm CLI version", `v${currentVersion} (latest: v${latest})`);
1828
+ }
1829
+ function checkPluginVersionDrift(home, currentVersion) {
1830
+ const pluginJsonPath = join15(home, ".claude", "plugins", "cache", "llm-wiki", "plugin.json");
1831
+ if (!existsSync3(pluginJsonPath)) {
1832
+ return check("pass", "plugin_version_drift", "Plugin/CLI version", "Plugin cache not found \u2014 plugin not installed");
1833
+ }
1834
+ try {
1835
+ const content = readFileSync2(pluginJsonPath, { encoding: "utf8" });
1836
+ const pluginData = JSON.parse(content);
1837
+ const pluginVersion = pluginData.version;
1838
+ if (!pluginVersion) {
1839
+ return check("pass", "plugin_version_drift", "Plugin/CLI version", "Plugin version not found in cache");
1840
+ }
1841
+ if (pluginVersion === currentVersion) {
1842
+ return check("pass", "plugin_version_drift", "Plugin/CLI version", `Both at v${currentVersion}`);
1843
+ }
1844
+ const updateCmd = semverGt(pluginVersion, currentVersion) ? "npm install -g skillwiki@beta" : "claude plugin update skillwiki@llm-wiki";
1845
+ return check(
1846
+ "warn",
1847
+ "plugin_version_drift",
1848
+ "Plugin/CLI version",
1849
+ `Plugin v${pluginVersion} \u2260 CLI v${currentVersion} \u2014 run \`${updateCmd}\``
1850
+ );
1851
+ } catch {
1852
+ return check("pass", "plugin_version_drift", "Plugin/CLI version", "Could not read plugin cache");
1853
+ }
1854
+ }
1690
1855
  function findSkillMd(dir) {
1691
1856
  const results = [];
1692
1857
  let entries;
@@ -1697,9 +1862,9 @@ function findSkillMd(dir) {
1697
1862
  }
1698
1863
  for (const entry of entries) {
1699
1864
  if (entry.isFile() && entry.name === "SKILL.md") {
1700
- results.push(join14(dir, entry.name));
1865
+ results.push(join15(dir, entry.name));
1701
1866
  } else if (entry.isDirectory()) {
1702
- results.push(...findSkillMd(join14(dir, entry.name)));
1867
+ results.push(...findSkillMd(join15(dir, entry.name)));
1703
1868
  }
1704
1869
  }
1705
1870
  return results;
@@ -1719,6 +1884,8 @@ async function runDoctor(input) {
1719
1884
  checks.push(checkWikiPathExists(resolvedPath));
1720
1885
  checks.push(checkVaultStructure(resolvedPath));
1721
1886
  checks.push(checkSkillsInstalled(input.home));
1887
+ checks.push(checkNpmUpdate(input.home, input.currentVersion));
1888
+ checks.push(checkPluginVersionDrift(input.home, input.currentVersion));
1722
1889
  const summary = {
1723
1890
  pass: checks.filter((c) => c.status === "pass").length,
1724
1891
  warn: checks.filter((c) => c.status === "warn").length,
@@ -1739,7 +1906,7 @@ async function runDoctor(input) {
1739
1906
 
1740
1907
  // src/commands/archive.ts
1741
1908
  import { rename as rename3, mkdir as mkdir5, readFile as readFile13, writeFile as writeFile6 } from "fs/promises";
1742
- import { join as join15, dirname as dirname6 } from "path";
1909
+ import { join as join16, dirname as dirname7 } from "path";
1743
1910
  async function runArchive(input) {
1744
1911
  const scan = await scanVault(input.vault);
1745
1912
  if (!scan.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scan };
@@ -1751,10 +1918,10 @@ async function runArchive(input) {
1751
1918
  }
1752
1919
  if (!relPath) return { exitCode: ExitCode.ARCHIVE_TARGET_NOT_FOUND, result: err("ARCHIVE_TARGET_NOT_FOUND", { page: input.page }) };
1753
1920
  if (relPath.startsWith("_archive/")) return { exitCode: ExitCode.ARCHIVE_ALREADY_ARCHIVED, result: err("ARCHIVE_ALREADY_ARCHIVED", { page: relPath }) };
1754
- const archivePath = join15("_archive", relPath);
1755
- await mkdir5(dirname6(join15(input.vault, archivePath)), { recursive: true });
1921
+ const archivePath = join16("_archive", relPath);
1922
+ await mkdir5(dirname7(join16(input.vault, archivePath)), { recursive: true });
1756
1923
  let indexUpdated = false;
1757
- const indexPath = join15(input.vault, "index.md");
1924
+ const indexPath = join16(input.vault, "index.md");
1758
1925
  try {
1759
1926
  const idx = await readFile13(indexPath, "utf8");
1760
1927
  const slug = relPath.replace(/\.md$/, "").split("/").pop();
@@ -1767,7 +1934,7 @@ async function runArchive(input) {
1767
1934
  } catch (e) {
1768
1935
  if (e?.code !== "ENOENT") throw e;
1769
1936
  }
1770
- await rename3(join15(input.vault, relPath), join15(input.vault, archivePath));
1937
+ await rename3(join16(input.vault, relPath), join16(input.vault, archivePath));
1771
1938
  return { exitCode: ExitCode.OK, result: ok({ archived_from: relPath, archived_to: archivePath, index_updated: indexUpdated, humanHint: `${relPath} -> ${archivePath}${indexUpdated ? " (index updated)" : ""}` }) };
1772
1939
  }
1773
1940
 
@@ -1999,8 +2166,69 @@ ${migratedBody}${newFooter}`;
1999
2166
  };
2000
2167
  }
2001
2168
 
2169
+ // src/commands/update.ts
2170
+ import { execSync as execSync2 } from "child_process";
2171
+ import { readFileSync as readFileSync3 } from "fs";
2172
+ async function runUpdate(input) {
2173
+ const pkg2 = JSON.parse(
2174
+ readFileSync3(new URL("../../package.json", import.meta.url), "utf8")
2175
+ );
2176
+ const currentVersion = pkg2.version;
2177
+ const tag = input.distTag ?? "beta";
2178
+ let latest;
2179
+ try {
2180
+ latest = execSync2(`npm view skillwiki@${tag} version`, {
2181
+ encoding: "utf8",
2182
+ timeout: 15e3
2183
+ }).trim();
2184
+ } catch (e) {
2185
+ return {
2186
+ exitCode: ExitCode.PREFLIGHT_FAILED,
2187
+ result: err("PREFLIGHT_FAILED", { message: `Failed to query npm registry: ${String(e)}` })
2188
+ };
2189
+ }
2190
+ const cache = {
2191
+ lastCheck: Date.now(),
2192
+ latestVersion: latest,
2193
+ currentVersion
2194
+ };
2195
+ if (latest === currentVersion) {
2196
+ writeCache(input.home, cache);
2197
+ return {
2198
+ exitCode: ExitCode.OK,
2199
+ result: ok({
2200
+ previousVersion: currentVersion,
2201
+ newVersion: null,
2202
+ wasAlreadyLatest: true,
2203
+ humanHint: `Already on latest ${tag}: v${currentVersion}`
2204
+ })
2205
+ };
2206
+ }
2207
+ try {
2208
+ execSync2(`npm install -g skillwiki@${tag}`, {
2209
+ stdio: "pipe",
2210
+ timeout: 6e4
2211
+ });
2212
+ } catch (e) {
2213
+ return {
2214
+ exitCode: ExitCode.PREFLIGHT_FAILED,
2215
+ result: err("PREFLIGHT_FAILED", { message: `npm install failed: ${String(e)}` })
2216
+ };
2217
+ }
2218
+ writeCache(input.home, { ...cache, updateAppliedAt: Date.now() });
2219
+ return {
2220
+ exitCode: ExitCode.OK,
2221
+ result: ok({
2222
+ previousVersion: currentVersion,
2223
+ newVersion: latest,
2224
+ wasAlreadyLatest: false,
2225
+ humanHint: `Updated skillwiki ${currentVersion} \u2192 ${latest}`
2226
+ })
2227
+ };
2228
+ }
2229
+
2002
2230
  // src/cli.ts
2003
- var pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
2231
+ var pkg = JSON.parse(readFileSync4(new URL("../package.json", import.meta.url), "utf8"));
2004
2232
  var program = new Command();
2005
2233
  program.name("skillwiki").description("Deterministic helpers for CodeWiki skills").version(pkg.version);
2006
2234
  program.option("--human", "render terminal-readable output instead of JSON");
@@ -2117,7 +2345,8 @@ configCmd.command("path").description("print the config file path").action(async
2117
2345
  program.command("doctor").description("diagnose skillwiki setup issues").action(async () => emit(await runDoctor({
2118
2346
  home: process.env.HOME ?? "",
2119
2347
  envValue: process.env.WIKI_PATH,
2120
- argv: process.argv
2348
+ argv: process.argv,
2349
+ currentVersion: pkg.version
2121
2350
  })));
2122
2351
  program.command("archive <page> [vault]").description("archive a typed-knowledge page").action(async (page, vault) => {
2123
2352
  const v = await resolveVaultArg(vault);
@@ -2139,6 +2368,11 @@ program.command("migrate-citations [vault]").description("migrate ^[raw/...] mar
2139
2368
  if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
2140
2369
  else emit(await runMigrateCitations({ vault: v.vault, dryRun: !!opts.dryRun }));
2141
2370
  });
2371
+ program.command("update").description("update skillwiki CLI to the latest version").option("--tag <tag>", "npm dist-tag", "beta").action(async (opts) => emit(await runUpdate({
2372
+ home: process.env.HOME ?? "",
2373
+ distTag: opts.tag
2374
+ })));
2375
+ triggerAutoUpdate(process.env.HOME ?? "", pkg.version);
2142
2376
  program.parseAsync(process.argv).catch((e) => {
2143
2377
  process.stdout.write(JSON.stringify({ ok: false, error: "INTERNAL", detail: { message: String(e) } }) + "\n");
2144
2378
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillwiki",
3
- "version": "0.2.0-beta.14",
3
+ "version": "0.2.0-beta.19",
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.0-beta.14",
3
+ "version": "0.2.0-beta.19",
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": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skillwiki/skills",
3
- "version": "0.2.0-beta.14",
3
+ "version": "0.2.0-beta.19",
4
4
  "private": true,
5
5
  "files": [
6
6
  "wiki-*",
@@ -25,6 +25,31 @@ Invoke a skillwiki skill when the user:
25
25
  - Has a spec/plan in a non-skillwiki format (CodeStable, RFC, AIDE)
26
26
  - Asks about their skillwiki configuration or setup health
27
27
 
28
+ ## Vault Structure
29
+
30
+ A skillwiki vault has two layers:
31
+
32
+ **Layer 1 — Raw (`raw/`):** Immutable source material. Never modify after ingest.
33
+
34
+ ```
35
+ raw/
36
+ ├── articles/ # Web articles, clippings
37
+ ├── papers/ # PDFs, arxiv papers
38
+ ├── transcripts/ # Meeting notes, interviews
39
+ └── assets/ # Images, diagrams referenced by sources
40
+ ```
41
+
42
+ Raw frontmatter:
43
+ ```yaml
44
+ ---
45
+ source_url: https://…
46
+ ingested: YYYY-MM-DD
47
+ sha256: # computed by skillwiki hash over body bytes after closing ---
48
+ ---
49
+ ```
50
+
51
+ **Layer 2 — Agent-owned pages:** `entities/`, `concepts/`, `comparisons/`, `queries/`, `meta/`, `projects/`. Citations use `^[raw/articles/source-file.md]` markers at paragraph-end.
52
+
28
53
  ## Skill Map
29
54
 
30
55
  | Skill | When to Invoke |