@refrakt-md/cli 0.35.0 → 0.37.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.
Files changed (38) hide show
  1. package/dist/bin.js +420 -21
  2. package/dist/bin.js.map +1 -1
  3. package/dist/commands/config.d.ts +3 -2
  4. package/dist/commands/config.d.ts.map +1 -1
  5. package/dist/commands/config.js +110 -2
  6. package/dist/commands/config.js.map +1 -1
  7. package/dist/commands/migrate-snippets.d.ts +65 -0
  8. package/dist/commands/migrate-snippets.d.ts.map +1 -0
  9. package/dist/commands/migrate-snippets.js +218 -0
  10. package/dist/commands/migrate-snippets.js.map +1 -0
  11. package/dist/commands/snippet-review.d.ts +66 -0
  12. package/dist/commands/snippet-review.d.ts.map +1 -0
  13. package/dist/commands/snippet-review.js +271 -0
  14. package/dist/commands/snippet-review.js.map +1 -0
  15. package/dist/commands/stale.d.ts +32 -0
  16. package/dist/commands/stale.d.ts.map +1 -0
  17. package/dist/commands/stale.js +130 -0
  18. package/dist/commands/stale.js.map +1 -0
  19. package/dist/commands/theme-validate.d.ts +23 -0
  20. package/dist/commands/theme-validate.d.ts.map +1 -0
  21. package/dist/commands/theme-validate.js +73 -0
  22. package/dist/commands/theme-validate.js.map +1 -0
  23. package/dist/commands/theme.d.ts +1 -2
  24. package/dist/commands/theme.d.ts.map +1 -1
  25. package/dist/commands/theme.js.map +1 -1
  26. package/dist/commands/validate-config.d.ts +35 -0
  27. package/dist/commands/validate-config.d.ts.map +1 -0
  28. package/dist/commands/validate-config.js +123 -0
  29. package/dist/commands/validate-config.js.map +1 -0
  30. package/dist/commands/validate-core.d.ts +51 -0
  31. package/dist/commands/validate-core.d.ts.map +1 -0
  32. package/dist/commands/validate-core.js +113 -0
  33. package/dist/commands/validate-core.js.map +1 -0
  34. package/dist/commands/validate.d.ts +12 -2
  35. package/dist/commands/validate.d.ts.map +1 -1
  36. package/dist/commands/validate.js +62 -60
  37. package/dist/commands/validate.js.map +1 -1
  38. package/package.json +11 -6
package/dist/bin.js CHANGED
@@ -24,6 +24,15 @@ else if (command === 'scaffold-css') {
24
24
  else if (command === 'validate') {
25
25
  runValidate(args.slice(1));
26
26
  }
27
+ else if (command === 'stale') {
28
+ runStale(args.slice(1));
29
+ }
30
+ else if (command === 'migrate' && args[1] === 'snippets') {
31
+ runMigrateSnippets(args.slice(2));
32
+ }
33
+ else if (command === 'snippet') {
34
+ runSnippetCommand(args.slice(1));
35
+ }
27
36
  else if (command === 'theme') {
28
37
  runTheme(args.slice(1));
29
38
  }
@@ -73,7 +82,9 @@ Commands:
73
82
  contracts [options] Generate structure contracts from theme config
74
83
  i18n <subcommand> i18n tooling (extract translation keys, check coverage)
75
84
  scaffold-css Generate CSS stub files for all runes
76
- validate Validate theme config and manifest
85
+ validate Validate this project's sites (config + content)
86
+ stale Rank documentation references by how far their target has moved
87
+ snippet review Stamp and check review markers on embedded source
77
88
  theme <subcommand> Manage themes (install, info)
78
89
  edit Launch the browser-based content editor
79
90
  reference <subcommand> Emit rune syntax reference for authors and AI agents
@@ -123,12 +134,16 @@ Scaffold-CSS Options:
123
134
  --force Overwrite existing files
124
135
 
125
136
  Validate Options:
126
- --config <path> Path to theme config module (default: auto-detect)
127
- --manifest <path> Path to manifest.json (default: auto-detect)
137
+ --site <name> Restrict to one site from refrakt.config.json
138
+ --only <content|config> Narrow to one layer (both run when absent)
139
+ --deep Add cross-page checks (costs a full pipeline run)
140
+ --format <text|json> Output format (default: text)
141
+ --config-path <path> Path to refrakt.config.json (default: ./refrakt.config.json)
128
142
 
129
143
  Theme Subcommands:
130
144
  theme install <source> Install a theme (directory, .tgz, or npm package)
131
145
  theme info Show current theme details
146
+ theme validate Validate a ThemeConfig and/or theme manifest
132
147
 
133
148
  Examples:
134
149
  refrakt inspect hint --type=warning
@@ -852,47 +867,156 @@ function runScaffoldCss(scaffoldArgs) {
852
867
  });
853
868
  }
854
869
  function runValidate(validateArgs) {
870
+ let site;
871
+ let only;
872
+ let deep = false;
873
+ let format;
855
874
  let configPath;
856
- let manifestPath;
857
875
  for (let i = 0; i < validateArgs.length; i++) {
858
876
  const arg = validateArgs[i];
859
- if (arg === '--config') {
860
- configPath = validateArgs[++i];
861
- if (!configPath) {
862
- console.error('Error: --config requires a file path');
877
+ // SPEC-135 D2 / D12 — retired rather than aliased through a deprecation
878
+ // window. These check theme-authoring artifacts, and they now live in the
879
+ // noun group that owns them. Pre-1.0, and the behaviour they hung off was
880
+ // close to a no-op, so nobody can be meaningfully depending on it; an
881
+ // alias would keep the audience confusion alive for no one's benefit.
882
+ if (arg === '--config' || arg === '--manifest') {
883
+ console.error(`Error: \`${arg}\` has moved to \`refrakt theme validate\`.\n`);
884
+ console.error(` refrakt theme validate ${arg} ${validateArgs[i + 1] ?? '<path>'}\n`);
885
+ console.error('It validates a theme-authoring artifact; `refrakt validate` is the');
886
+ console.error("site author's command. See SPEC-135 D12.");
887
+ process.exit(1);
888
+ }
889
+ else if (arg === '--site') {
890
+ site = validateArgs[++i];
891
+ if (!site) {
892
+ console.error('Error: --site requires a site name');
863
893
  process.exit(1);
864
894
  }
865
895
  }
866
- else if (arg === '--manifest') {
867
- manifestPath = validateArgs[++i];
868
- if (!manifestPath) {
869
- console.error('Error: --manifest requires a file path');
896
+ else if (arg === '--only') {
897
+ const value = validateArgs[++i];
898
+ if (value !== 'content' && value !== 'config') {
899
+ console.error('Error: --only must be "content" or "config"');
870
900
  process.exit(1);
871
901
  }
902
+ only = value;
872
903
  }
873
- else if (arg === '--site') {
874
- // Accepted for forward compatibility; theme/manifest validation
875
- // operates on explicit paths and is not yet site-scoped.
876
- validateArgs[++i];
904
+ else if (arg === '--deep') {
905
+ deep = true;
906
+ }
907
+ else if (arg === '--format') {
908
+ const value = validateArgs[++i];
909
+ if (value !== 'text' && value !== 'json') {
910
+ console.error('Error: --format must be "text" or "json"');
911
+ process.exit(1);
912
+ }
913
+ format = value;
914
+ }
915
+ else if (arg === '--config-path') {
916
+ configPath = validateArgs[++i];
917
+ if (!configPath) {
918
+ console.error('Error: --config-path requires a file path');
919
+ process.exit(1);
920
+ }
921
+ }
922
+ else if (arg === '--strict') {
923
+ // SPEC-135 D3 — deliberately absent. `site/` carries 35 warnings, and
924
+ // a gate that goes red on day one is a gate someone turns off. The
925
+ // severity levels already say which findings are worth stopping for.
926
+ console.error('Error: `--strict` does not exist. Warnings never affect the exit code.\n');
927
+ console.error('Severity already says which findings stop a build: errors fail, warnings');
928
+ console.error('do not. See SPEC-135 D3.');
929
+ process.exit(1);
877
930
  }
878
931
  else if (arg === '--help' || arg === '-h') {
879
- printUsage();
932
+ printValidateUsage();
880
933
  process.exit(0);
881
934
  }
882
935
  else if (arg.startsWith('-')) {
883
936
  console.error(`Error: Unknown flag "${arg}"\n`);
884
- printUsage();
937
+ printValidateUsage();
885
938
  process.exit(1);
886
939
  }
887
940
  else {
888
941
  console.error(`Error: Unexpected argument "${arg}"\n`);
889
- printUsage();
942
+ printValidateUsage();
890
943
  process.exit(1);
891
944
  }
892
945
  }
893
946
  import('./commands/validate.js')
894
- .then(({ validateCommand }) => {
895
- validateCommand({ configPath, manifestPath });
947
+ .then(({ validateCommand }) => validateCommand({ site, only, deep, format, configPath }))
948
+ .catch((err) => {
949
+ console.error(`\nError: ${err.message}`);
950
+ process.exit(1);
951
+ });
952
+ }
953
+ function printValidateUsage() {
954
+ console.log(`
955
+ Usage: refrakt validate [options]
956
+
957
+ Validate this project's sites: config resolution, then content, for every site
958
+ in refrakt.config.json.
959
+
960
+ Options:
961
+ --site <name> Restrict to one site
962
+ --only <what> "content" or "config" (both run when absent)
963
+ --deep Add cross-page checks (broken refs, missing entities).
964
+ Costs a full pipeline run; off by default.
965
+ --format <fmt> "text" (default) or "json"
966
+ --config-path <p> Path to refrakt.config.json (default: ./refrakt.config.json)
967
+
968
+ Exit code is non-zero when any finding is at error severity, zero otherwise.
969
+ Warnings never affect it.
970
+
971
+ For theme-authoring artifacts, use \`refrakt theme validate\`.
972
+ `);
973
+ }
974
+ /** `refrakt theme validate` — the theme-authoring checks, moved off the bare
975
+ * `validate` command by WORK-579 (SPEC-135 D12). */
976
+ function runThemeValidate(tvArgs) {
977
+ let configPath;
978
+ let manifestPath;
979
+ for (let i = 0; i < tvArgs.length; i++) {
980
+ const arg = tvArgs[i];
981
+ if (arg === '--config') {
982
+ configPath = tvArgs[++i];
983
+ if (!configPath) {
984
+ console.error('Error: --config requires a file path');
985
+ process.exit(1);
986
+ }
987
+ }
988
+ else if (arg === '--manifest') {
989
+ manifestPath = tvArgs[++i];
990
+ if (!manifestPath) {
991
+ console.error('Error: --manifest requires a file path');
992
+ process.exit(1);
993
+ }
994
+ }
995
+ else if (arg === '--help' || arg === '-h') {
996
+ console.log(`
997
+ Usage: refrakt theme validate [options]
998
+
999
+ Validate theme-authoring artifacts. For a site's own content and config, use
1000
+ \`refrakt validate\`.
1001
+
1002
+ Options:
1003
+ --config <path> Validate a ThemeConfig JSON file
1004
+ --manifest <path> Validate a theme manifest
1005
+
1006
+ Examples:
1007
+ refrakt theme validate --config ./theme.config.json
1008
+ refrakt theme validate --manifest ./manifest.json
1009
+ `);
1010
+ process.exit(0);
1011
+ }
1012
+ else {
1013
+ console.error(`Error: Unexpected argument "${arg}"\n`);
1014
+ process.exit(1);
1015
+ }
1016
+ }
1017
+ import('./commands/theme-validate.js')
1018
+ .then(({ themeValidateCommand }) => {
1019
+ themeValidateCommand({ configPath, manifestPath });
896
1020
  })
897
1021
  .catch((err) => {
898
1022
  console.error(`\nError: ${err.message}`);
@@ -961,6 +1085,7 @@ Subcommands:
961
1085
  install <source> Install a theme (directory, .tgz, or npm package name)
962
1086
  info Show current theme details
963
1087
  list List installed themes and the active one
1088
+ validate Validate a ThemeConfig and/or a theme manifest
964
1089
  presets list List presets from installed packs + the active theme
965
1090
  presets validate Validate installed preset-pack manifests
966
1091
 
@@ -977,6 +1102,13 @@ Examples:
977
1102
  `);
978
1103
  process.exit(subcommand ? 0 : 1);
979
1104
  }
1105
+ // `theme validate` — the theme-authoring checks WORK-579 moved here from the
1106
+ // bare `refrakt validate` (SPEC-135 D12). Dispatched before `presets` so
1107
+ // `theme validate` and `theme presets validate` stay distinct.
1108
+ if (subcommand === 'validate') {
1109
+ runThemeValidate(themeArgs.slice(1));
1110
+ return;
1111
+ }
980
1112
  // `theme presets <list|validate>` — preset-pack discovery/listing (SPEC-111 §4).
981
1113
  if (subcommand === 'presets') {
982
1114
  const action = themeArgs[1] ?? 'list';
@@ -1424,4 +1556,271 @@ async function buildReferenceContext(runesModule, assembleThemeConfig, configDir
1424
1556
  }
1425
1557
  return { runes: allRunes, fixtures, source };
1426
1558
  }
1559
+ // ─────────────────────────────────────────────────────────────────────
1560
+ // `refrakt stale` — SPEC-136 / WORK-594
1561
+ // ─────────────────────────────────────────────────────────────────────
1562
+ function runStale(staleArgs) {
1563
+ let top;
1564
+ let edgeClass;
1565
+ let min;
1566
+ let format = 'text';
1567
+ for (let i = 0; i < staleArgs.length; i++) {
1568
+ const arg = staleArgs[i];
1569
+ if (arg === '--top') {
1570
+ top = Number(staleArgs[++i]);
1571
+ if (!Number.isInteger(top) || top < 1) {
1572
+ console.error('Error: --top requires a positive integer');
1573
+ process.exit(1);
1574
+ }
1575
+ }
1576
+ else if (arg === '--class') {
1577
+ edgeClass = staleArgs[++i];
1578
+ const known = ['declared', 'embedded', 'described-link', 'prose'];
1579
+ if (!edgeClass || !known.includes(edgeClass)) {
1580
+ console.error(`Error: --class must be one of ${known.join(', ')}`);
1581
+ process.exit(1);
1582
+ }
1583
+ }
1584
+ else if (arg === '--min') {
1585
+ min = Number(staleArgs[++i]);
1586
+ if (!Number.isInteger(min) || min < 1) {
1587
+ console.error('Error: --min requires a positive integer');
1588
+ process.exit(1);
1589
+ }
1590
+ }
1591
+ else if (arg === '--format') {
1592
+ const value = staleArgs[++i];
1593
+ if (value !== 'text' && value !== 'json') {
1594
+ console.error('Error: --format must be "text" or "json"');
1595
+ process.exit(1);
1596
+ }
1597
+ format = value;
1598
+ }
1599
+ else if (arg === '--help' || arg === '-h') {
1600
+ printStaleUsage();
1601
+ process.exit(0);
1602
+ }
1603
+ else {
1604
+ console.error(`Error: Unknown argument "${arg}"\n`);
1605
+ printStaleUsage();
1606
+ process.exit(1);
1607
+ }
1608
+ }
1609
+ import('./commands/stale.js')
1610
+ .then(({ staleCommand }) => staleCommand({ top, class: edgeClass, min, format }))
1611
+ .catch((err) => {
1612
+ console.error(`\nError: ${err.message}`);
1613
+ process.exit(1);
1614
+ });
1615
+ }
1616
+ function printStaleUsage() {
1617
+ console.log(`
1618
+ Usage: refrakt stale [options]
1619
+
1620
+ Rank documentation references by how far their target has moved since the page
1621
+ last changed:
1622
+
1623
+ staleness = commits touching the target since the referrer last changed
1624
+
1625
+ This ranks; it never fails. Findings do not affect the exit code, deliberately:
1626
+ the cheapest way to turn an edge green is to edit the referring page, so a gate
1627
+ would train people to make trivial documentation edits to clear it, destroying
1628
+ the signal it measures.
1629
+
1630
+ A refusal is different and does exit non-zero. A shallow clone or a non-git tree
1631
+ means the tool could not measure at all, which must not look like a clean
1632
+ corpus.
1633
+
1634
+ Options:
1635
+ --top <n> Bound the output (default 10)
1636
+ --class <name> Narrow to one edge class: declared, embedded,
1637
+ described-link, prose
1638
+ --min <n> Floor on the commit count
1639
+ --format <fmt> text (default) or json
1640
+ -h, --help Show this help
1641
+
1642
+ Content roots come from the sites declared in refrakt.config.json, so a
1643
+ multi-site project is scanned whole and nothing needs a flag.
1644
+
1645
+ Two optional lists under "stale" in refrakt.config.json narrow what is measured:
1646
+
1647
+ "stale": {
1648
+ "archival": ["docs/migration/**", "blog/**"],
1649
+ "generated": ["CHANGELOG.md", "**/*.generated.json"]
1650
+ }
1651
+
1652
+ archival globs matched against the referring PAGE — historical records
1653
+ (changelogs, release notes, migration guides) that are correct as
1654
+ written and must never be updated when the code moves on. Matching
1655
+ pages contribute no edges at all, so they also stop answering the
1656
+ "what documents this file" query.
1657
+ generated globs matched against the TARGET file — artifacts that change by
1658
+ construction, where "commits since" measures the generator rather
1659
+ than any divergence.
1660
+
1661
+ Both default to empty. Neither is a place to put a page that is merely noisy: a
1662
+ noisy page means the extraction rule is wrong, or the page's real subject is
1663
+ narrower than what it mentions — say so with "documents:" in its frontmatter,
1664
+ which replaces every inferred edge for that page. Whatever the lists remove is
1665
+ counted in the report footer, so the exclusions cannot quietly grow until the
1666
+ report is empty.
1667
+
1668
+ Note: a zero score is the absence of evidence of staleness, not evidence of
1669
+ freshness. The referrer side resets on any edit to the page.
1670
+ `);
1671
+ }
1672
+ // ─────────────────────────────────────────────────────────────────────
1673
+ // `refrakt migrate snippets` — SPEC-131 D7 / WORK-590
1674
+ // ─────────────────────────────────────────────────────────────────────
1675
+ function runMigrateSnippets(migrateArgs) {
1676
+ let fix = false;
1677
+ let format = 'text';
1678
+ for (const arg of migrateArgs) {
1679
+ if (arg === '--fix')
1680
+ fix = true;
1681
+ else if (arg === '--format=json')
1682
+ format = 'json';
1683
+ else if (arg === '--help' || arg === '-h') {
1684
+ console.log(`
1685
+ Usage: refrakt migrate snippets [--fix]
1686
+
1687
+ Convert \`lines=\` snippet and file-ref invocations to anchors, verifying that
1688
+ the anchored slice is byte-identical to the line-addressed one and refusing to
1689
+ rewrite when it is not.
1690
+
1691
+ A refusal is information rather than an obstacle: it means the anchor engine
1692
+ could not reproduce the slice, which is exactly the case where a human should
1693
+ look at what that snippet was pointing at.
1694
+
1695
+ Fenced invocations are skipped — they are examples of the syntax, not
1696
+ resolvable targets, so they cannot be verified and are rewritten by hand.
1697
+
1698
+ This never stamps \`reviewed\` markers (SPEC-134 D8).
1699
+
1700
+ Options:
1701
+ --fix Write the rewrites (default is a dry run)
1702
+ --format=json Machine-readable output
1703
+ `);
1704
+ process.exit(0);
1705
+ }
1706
+ else {
1707
+ console.error(`Error: Unknown argument "${arg}"`);
1708
+ process.exit(1);
1709
+ }
1710
+ }
1711
+ import('./commands/migrate-snippets.js')
1712
+ .then(({ migrateSnippets }) => {
1713
+ const result = migrateSnippets({ fix, format });
1714
+ if (format === 'json') {
1715
+ console.log(JSON.stringify(result, null, 2));
1716
+ return;
1717
+ }
1718
+ for (const r of result.rewrites) {
1719
+ console.log(`${r.page}:${r.line}`);
1720
+ console.log(` - ${r.before}`);
1721
+ console.log(` + ${r.after}`);
1722
+ console.log('');
1723
+ }
1724
+ for (const r of result.refusals) {
1725
+ console.log(`REFUSED ${r.page}:${r.line}`);
1726
+ console.log(` ${r.invocation}`);
1727
+ console.log(` ${r.reason}`);
1728
+ console.log('');
1729
+ }
1730
+ const langs = Object.entries(result.languages)
1731
+ .map(([k, v]) => `${k} ${v}`)
1732
+ .join(', ');
1733
+ console.log(`${result.rewrites.length} rewritable, ${result.refusals.length} refused, ` +
1734
+ `${result.skippedFenced} fenced (skipped).`);
1735
+ if (langs)
1736
+ console.log(`Languages reached: ${langs}.`);
1737
+ if (!fix && result.rewrites.length > 0)
1738
+ console.log('Dry run — pass --fix to write.');
1739
+ })
1740
+ .catch((err) => {
1741
+ console.error(`\nError: ${err.message}`);
1742
+ process.exit(1);
1743
+ });
1744
+ }
1745
+ // ─────────────────────────────────────────────────────────────────────
1746
+ // `refrakt snippet review` — SPEC-134 / WORK-591 + WORK-592
1747
+ // ─────────────────────────────────────────────────────────────────────
1748
+ function runSnippetCommand(snippetArgs) {
1749
+ const sub = snippetArgs[0];
1750
+ if (sub !== 'review') {
1751
+ console.error(`Error: unknown snippet subcommand "${sub ?? ''}"\n`);
1752
+ printSnippetReviewUsage();
1753
+ process.exit(1);
1754
+ }
1755
+ const rest = snippetArgs.slice(1);
1756
+ let all = false;
1757
+ let check = false;
1758
+ let update = false;
1759
+ let interactive = false;
1760
+ let format = 'text';
1761
+ const pages = [];
1762
+ for (const arg of rest) {
1763
+ if (arg === '--all')
1764
+ all = true;
1765
+ else if (arg === '--check')
1766
+ check = true;
1767
+ else if (arg === '--update')
1768
+ update = true;
1769
+ else if (arg === '--interactive')
1770
+ interactive = true;
1771
+ else if (arg === '--format=json')
1772
+ format = 'json';
1773
+ else if (arg === '--help' || arg === '-h') {
1774
+ printSnippetReviewUsage();
1775
+ process.exit(0);
1776
+ }
1777
+ else if (arg.startsWith('-')) {
1778
+ console.error(`Error: Unknown flag "${arg}"\n`);
1779
+ printSnippetReviewUsage();
1780
+ process.exit(1);
1781
+ }
1782
+ else {
1783
+ pages.push(arg);
1784
+ }
1785
+ }
1786
+ import('./commands/snippet-review.js')
1787
+ .then(({ snippetReviewCommand }) => snippetReviewCommand({ pages, all, check, update, interactive, format }))
1788
+ .catch((err) => {
1789
+ console.error(`\nError: ${err.message}`);
1790
+ process.exit(1);
1791
+ });
1792
+ }
1793
+ function printSnippetReviewUsage() {
1794
+ console.log(`
1795
+ Usage: refrakt snippet review [pages...] [options]
1796
+
1797
+ Record that a human read a quoted region and confirmed the prose around it
1798
+ matched. The marker freezes nothing — the snippet still tracks HEAD and
1799
+ re-resolves every build. When the region changes, the marker asks for a
1800
+ re-read.
1801
+
1802
+ Markers are never hand-written; this command writes them.
1803
+
1804
+ refrakt snippet review site/content/runes/file-ref.md stamp unmarked
1805
+ refrakt snippet review --all stamp every unmarked
1806
+ refrakt snippet review --check report, own exit code
1807
+ refrakt snippet review --update --interactive re-stamp what changed
1808
+
1809
+ A change that alters the content but not its meaning — a reformat — is
1810
+ re-stamped without prompting, because that is provable rather than a judgement
1811
+ made under time pressure. Everything else is held for a human.
1812
+
1813
+ Options:
1814
+ --all Stamp every unmarked invocation (not every invocation)
1815
+ --check Report stale markers; exits non-zero when any are found
1816
+ --update Re-stamp what changed, showing the content diff
1817
+ --interactive Show each held diff one at a time
1818
+ --format=json Machine-readable output
1819
+ -h, --help Show this help
1820
+
1821
+ Adoption is deliberately selective: mark the references whose prose makes
1822
+ specific claims about the code beside them. Marking everything makes --check
1823
+ permanently noisy and trains everyone to ignore it.
1824
+ `);
1825
+ }
1427
1826
  //# sourceMappingURL=bin.js.map