@hawkeyexl/manni 4.0.3 → 4.2.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
@@ -1,65 +1,108 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- buildProgram,
4
- collect,
5
- configOption,
6
- explicitFalse,
7
- readStdin,
8
- reportConfig,
9
- splitList
10
- } from "./chunk-DEP5AMVA.js";
3
+ buildProgram
4
+ } from "./chunk-BNHQWIAE.js";
11
5
  import {
6
+ ALL_FILL_FIELDS,
7
+ CONFIG_SECTION,
12
8
  CiteError,
9
+ DEFAULT_CONFIG_FILENAME2 as DEFAULT_CONFIG_FILENAME,
10
+ DEFAULT_EXTENSIONS,
11
+ DocevalsError,
12
+ EXECUTION_GRANTS,
13
+ EXPORT_TARGETS,
14
+ GraphError,
13
15
  INFER_FORMATS,
14
16
  KeyError,
15
17
  LINT_JOBS,
16
18
  LintError,
19
+ REPORT_FORMATS as REPORT_FORMATS2,
17
20
  RULE_ID_PREFIX,
21
+ SEARCH_MODES,
18
22
  SEVERITIES,
19
23
  SEVERITY_LIST,
24
+ STDIN,
25
+ SUMMARY_FORMATS,
20
26
  TOOLS_BY_JOB,
21
27
  asCiteError,
28
+ assertInputFormat,
29
+ bundledShapesPath,
22
30
  codeEndAt,
23
31
  codeRegions,
32
+ compactIri,
24
33
  errorSite,
25
34
  findingMessage,
26
35
  isCiteRule,
27
36
  isInferFormat,
28
37
  isSeverity,
29
- loadConfig,
38
+ listReviews,
39
+ loadConfig3 as loadConfig,
40
+ loadGraph,
41
+ loadRunConfig,
30
42
  meetsSeverity,
43
+ parseFormat,
31
44
  rebaseConfig,
32
45
  render,
46
+ render2,
47
+ renderCalibration,
33
48
  renderCheckGithub,
34
49
  renderCheckJson,
35
50
  renderCheckPretty,
51
+ renderEmbed,
52
+ renderFill,
53
+ renderFill2,
54
+ renderList,
55
+ renderQuery,
36
56
  renderRemoveJson,
37
57
  renderRemovePretty,
58
+ renderReviews,
59
+ renderSearch,
60
+ renderStats,
38
61
  renderTemplates,
39
62
  renderTools,
63
+ renderTraverse,
40
64
  renderUpdateJson,
41
65
  renderUpdatePretty,
42
66
  resolveStructureTool,
43
67
  runAdd,
68
+ runBuild,
69
+ runCalibrate,
44
70
  runCheck,
71
+ runEmbed,
72
+ runExport,
73
+ runFill,
74
+ runFill2,
75
+ runGenerate,
76
+ runInit,
45
77
  runKeyRotate,
46
78
  runKeySet,
47
79
  runLint,
80
+ runList,
81
+ runPromote,
82
+ runQuery,
48
83
  runRemove,
84
+ runReview,
85
+ runRun,
86
+ runSearch,
87
+ runStats,
49
88
  runTemplates,
50
89
  runTemplatesInfer,
51
90
  runTools,
91
+ runTraverse,
52
92
  runUpdate,
93
+ seedGoldenCases,
53
94
  shortCommit,
54
95
  shortLine,
55
96
  shortPin,
56
97
  shortSrc,
57
- spellSource
58
- } from "./chunk-JYRY7SKD.js";
98
+ spellSource,
99
+ validateGraph
100
+ } from "./chunk-UYDRDFYF.js";
59
101
  import {
60
102
  COMMON_FORMAT_LIST,
61
103
  CONCEPT_FIELDS,
62
104
  FAMILY_CONFIG_NAMES,
105
+ LOCAL_FLAG_HELP,
63
106
  OMITTED_WHEN_CLEAN,
64
107
  REPORT_FORMATS,
65
108
  REPORT_FORMAT_LIST,
@@ -68,9 +111,12 @@ import {
68
111
  TERM_RELATION_FIELDS,
69
112
  ToolError,
70
113
  assertNonEmpty,
114
+ collect,
115
+ configOption,
71
116
  errorMessage,
72
117
  escapeWorkflowCommandMessage,
73
118
  escapeWorkflowCommandProperty,
119
+ explicitFalse,
74
120
  extractorByName,
75
121
  extractorForExtension,
76
122
  fail,
@@ -87,23 +133,28 @@ import {
87
133
  palette,
88
134
  programName,
89
135
  readConfigFile,
136
+ readStdin,
90
137
  registerTermLabelSource,
91
138
  renderJunit,
92
139
  renderSarif,
140
+ reportConfig,
93
141
  resolveBaselineRequest,
94
142
  resolveTargetSet,
95
143
  runIfMain,
96
144
  selectCollections,
97
145
  settleBaseline,
98
146
  shouldColor,
147
+ splitList,
99
148
  splitPageArgument,
100
149
  terminalConfirm,
101
150
  valeConfigPath,
102
- valueAt
103
- } from "./chunk-TQG3WCJY.js";
151
+ valueAt,
152
+ warn,
153
+ writeTextAtomic
154
+ } from "./chunk-TH565O2N.js";
104
155
 
105
156
  // src/cli.ts
106
- import { Command as Command6 } from "commander";
157
+ import { Command as Command8 } from "commander";
107
158
 
108
159
  // src/a11y/cli.ts
109
160
  import { Command } from "commander";
@@ -203,17 +254,19 @@ async function crawl(opts, analyzer) {
203
254
  url = normalizeUrl(raw);
204
255
  key = dedupeKey(url);
205
256
  } catch {
206
- return;
257
+ return { as: "invalid" };
207
258
  }
208
- if (source !== "seed" && !isPageLink(url)) return;
209
- if (!opts.seeds.some((seed) => sameHost(seed, url))) return;
210
- if (opts.exclude?.matches(url) === true) {
259
+ if (source !== "seed" && !isPageLink(url)) return { as: "asset" };
260
+ if (!opts.seeds.some((seed) => sameHost(seed, url))) return { as: "off-host" };
261
+ const pattern = opts.exclude?.match(url) ?? null;
262
+ if (pattern !== null) {
211
263
  excluded.add(key);
212
- return;
264
+ return { as: "excluded", pattern };
213
265
  }
214
- if (seen.has(key)) return;
266
+ if (seen.has(key)) return { as: "seen" };
215
267
  seen.add(key);
216
268
  frontier.push({ url, key, source });
269
+ return { as: "queued" };
217
270
  };
218
271
  const landed = (finalUrl) => {
219
272
  if (finalUrl === void 0) return;
@@ -230,6 +283,7 @@ async function crawl(opts, analyzer) {
230
283
  progress({ kind: "excluded", urls: excluded.size, patterns: opts.exclude?.patterns.length ?? 0 });
231
284
  }
232
285
  const pages = [];
286
+ const redirects = [];
233
287
  let duplicates = 0;
234
288
  const capped = () => opts.maxPages !== void 0 && pages.length >= opts.maxPages;
235
289
  for (let next = 0; next < frontier.length && !capped(); next++) {
@@ -242,10 +296,28 @@ async function crawl(opts, analyzer) {
242
296
  }
243
297
  visited.add(key);
244
298
  const index = pages.length + 1;
245
- if (index === 1) progress({ kind: "browser" });
299
+ if (pages.length + redirects.length === 0) progress({ kind: "browser" });
246
300
  progress({ kind: "page", index, queued: frontier.length, url });
247
301
  try {
248
302
  const analyzed = await analyzer.analyze(url, opts.analyze);
303
+ if ("redirect" in analyzed) {
304
+ const to = normalized(analyzed.redirect);
305
+ redirects.push({ url, to, source });
306
+ progress({ kind: "redirected", index, url, to });
307
+ const outcome2 = enqueue(to, source);
308
+ if (source === "seed") {
309
+ if (!isHttpUrl(to)) {
310
+ throw new A11yError(`${url} redirects to ${to}, which is not an http(s) URL.`);
311
+ }
312
+ if (outcome2.as === "off-host") {
313
+ throw new A11yError(`${url} redirects to ${to}, which is on another host. Check ${to} instead.`);
314
+ }
315
+ if (outcome2.as === "excluded") {
316
+ throw new A11yError(`${outcome2.pattern.source} excludes ${to}, where the seed ${url} redirects.`);
317
+ }
318
+ }
319
+ continue;
320
+ }
249
321
  pages.push({ ...analyzed.result, url, source });
250
322
  landed(analyzed.finalUrl);
251
323
  if (opts.crawl) for (const link of analyzed.links) enqueue(link, "link");
@@ -257,9 +329,23 @@ async function crawl(opts, analyzer) {
257
329
  progress({ kind: "checked", index, url, violations: 0, error });
258
330
  }
259
331
  }
260
- const skipped2 = frontier.length - pages.length - duplicates;
332
+ const skipped2 = frontier.length - pages.length - duplicates - redirects.length;
261
333
  progress({ kind: "done", checked: pages.length, skipped: skipped2 });
262
- return { pages, discovered: frontier.length, skipped: skipped2, duplicates, excluded: excluded.size };
334
+ return {
335
+ pages,
336
+ discovered: frontier.length,
337
+ skipped: skipped2,
338
+ duplicates,
339
+ redirects,
340
+ excluded: excluded.size
341
+ };
342
+ }
343
+ function normalized(url) {
344
+ try {
345
+ return normalizeUrl(url);
346
+ } catch {
347
+ return url;
348
+ }
263
349
  }
264
350
 
265
351
  // src/a11y/core/exclude.ts
@@ -501,6 +587,7 @@ async function runCheck2(opts, deps) {
501
587
  const results = outcome2.pages.map((page) => finishPage(page, opts.severity));
502
588
  return {
503
589
  results,
590
+ redirects: outcome2.redirects,
504
591
  summary: summarize(results, outcome2, sitemap, opts.crawl)
505
592
  };
506
593
  } finally {
@@ -514,7 +601,7 @@ function finishPage(page, floor) {
514
601
  const score = applicable === 0 ? null : Math.round(100 * page.passes / applicable);
515
602
  return { ...page, violations, score };
516
603
  }
517
- function summarize(results, { discovered, skipped: skipped2, duplicates, excluded }, sitemap, crawl2) {
604
+ function summarize(results, { discovered, skipped: skipped2, duplicates, redirects, excluded }, sitemap, crawl2) {
518
605
  const bySeverity = { notice: 0, warning: 0, error: 0 };
519
606
  let failed = 0;
520
607
  let violations = 0;
@@ -530,6 +617,7 @@ function summarize(results, { discovered, skipped: skipped2, duplicates, exclude
530
617
  checked: results.length,
531
618
  skipped: skipped2,
532
619
  duplicates,
620
+ redirects: redirects.length,
533
621
  excluded,
534
622
  failed,
535
623
  violations,
@@ -574,33 +662,18 @@ function createPlaywrightAnalyzer() {
574
662
  return {
575
663
  async analyze(url, opts) {
576
664
  const { page } = await open();
665
+ const navigations = [];
666
+ const onRequest = (request) => {
667
+ if (request.isNavigationRequest() && request.frame() === page.mainFrame() && request.redirectedFrom() === null) {
668
+ navigations.push(request);
669
+ }
670
+ };
671
+ page.on("request", onRequest);
577
672
  try {
578
- await page.goto(url, { waitUntil: "load", timeout: opts.timeout });
579
- } catch (err) {
580
- throw new A11yError(`Could not load ${url}: ${reason(err)}`);
581
- }
582
- const links = await page.$$eval(
583
- "a[href]",
584
- (anchors) => anchors.map((a) => a.href)
585
- );
586
- let builder = new AxeBuilder({ page });
587
- if (opts.tags.length > 0) builder = builder.withTags(opts.tags);
588
- let results;
589
- try {
590
- results = await builder.analyze();
591
- } catch (err) {
592
- throw new A11yError(`Could not analyze ${url}: ${reason(err)}`);
673
+ return await analyzePage(page, url, opts, navigations);
674
+ } finally {
675
+ page.off("request", onRequest);
593
676
  }
594
- return {
595
- result: {
596
- url,
597
- violations: results.violations.map(toViolation),
598
- passes: results.passes.length,
599
- incomplete: results.incomplete.length
600
- },
601
- links,
602
- finalUrl: page.url()
603
- };
604
677
  },
605
678
  async close() {
606
679
  if (session === null) return;
@@ -614,6 +687,103 @@ function createPlaywrightAnalyzer() {
614
687
  }
615
688
  };
616
689
  }
690
+ async function analyzePage(page, url, opts, navigations) {
691
+ let response;
692
+ try {
693
+ response = await page.goto(url, { waitUntil: "load", timeout: opts.timeout });
694
+ } catch (err) {
695
+ throw new A11yError(`Could not load ${url}: ${reason(err)}`);
696
+ }
697
+ const loaded = response?.url() ?? page.url();
698
+ const nextNavigation = () => {
699
+ let first = response?.request() ?? null;
700
+ for (let from = first?.redirectedFrom() ?? null; from !== null; from = from.redirectedFrom()) {
701
+ first = from;
702
+ }
703
+ const own = first === null ? 0 : navigations.indexOf(first);
704
+ return own === -1 ? void 0 : navigations[own + 1];
705
+ };
706
+ const destination = () => nextNavigation()?.url() ?? page.url();
707
+ const navigatedAway = async () => {
708
+ if (nextNavigation() === void 0 && samePage(page.url(), loaded)) return null;
709
+ try {
710
+ await page.waitForURL((u) => !samePage(u.href, loaded), {
711
+ waitUntil: "commit",
712
+ timeout: opts.timeout
713
+ });
714
+ return { redirect: destination() };
715
+ } catch {
716
+ return null;
717
+ }
718
+ };
719
+ let read8;
720
+ try {
721
+ read8 = await page.evaluate(readPage);
722
+ } catch (err) {
723
+ const moved = await navigatedAway();
724
+ if (moved !== null) return moved;
725
+ throw new A11yError(`Could not analyze ${url}: ${reason(err)}`);
726
+ }
727
+ const refresh = refreshTarget(read8.refresh, page.url());
728
+ if (refresh !== null) return { redirect: refresh };
729
+ let builder = new AxeBuilder({ page });
730
+ if (opts.tags.length > 0) builder = builder.withTags(opts.tags);
731
+ let results;
732
+ try {
733
+ results = await builder.analyze();
734
+ } catch (err) {
735
+ const moved = await navigatedAway();
736
+ if (moved !== null) return moved;
737
+ throw new A11yError(`Could not analyze ${url}: ${reason(err)}`);
738
+ }
739
+ if (!samePage(page.url(), loaded)) return { redirect: destination() };
740
+ return {
741
+ result: {
742
+ url,
743
+ violations: results.violations.map(toViolation),
744
+ passes: results.passes.length,
745
+ incomplete: results.incomplete.length
746
+ },
747
+ links: read8.links,
748
+ finalUrl: page.url()
749
+ };
750
+ }
751
+ function readPage() {
752
+ const doc = globalThis.document;
753
+ return {
754
+ links: Array.from(doc.querySelectorAll("a[href]"), (a) => a.href),
755
+ refresh: doc.querySelector('meta[http-equiv="refresh" i]')?.getAttribute("content") ?? null
756
+ };
757
+ }
758
+ function refreshUrl(content) {
759
+ const match = /^\s*[\d.]*\s*[;,]\s*(?:url\s*=\s*)?(.*)$/is.exec(content);
760
+ let target = match?.[1]?.trim() ?? "";
761
+ const quote = target[0];
762
+ if (quote === "'" || quote === '"') {
763
+ const end = target.indexOf(quote, 1);
764
+ target = (end === -1 ? target.slice(1) : target.slice(1, end)).trim();
765
+ }
766
+ return target === "" ? null : target;
767
+ }
768
+ function refreshTarget(content, base) {
769
+ if (content === null) return null;
770
+ const target = refreshUrl(content);
771
+ if (target === null) return null;
772
+ let to;
773
+ try {
774
+ to = new URL(target, base).href;
775
+ } catch {
776
+ return null;
777
+ }
778
+ return samePage(to, base) ? null : to;
779
+ }
780
+ function samePage(a, b) {
781
+ try {
782
+ return dedupeKey(a) === dedupeKey(b);
783
+ } catch {
784
+ return a === b;
785
+ }
786
+ }
617
787
  function severityOf(impact) {
618
788
  switch (impact) {
619
789
  case "critical":
@@ -798,8 +968,13 @@ function renderPretty(run, opts) {
798
968
  );
799
969
  for (const v of page.violations) lines.push(...violationLines(v, c));
800
970
  }
971
+ if (!opts.quiet) {
972
+ for (const r of run.redirects) {
973
+ lines.push(`${c.dim("\u21AA")} ${c.cyan(r.url)} redirects to ${c.cyan(r.to)}`);
974
+ }
975
+ }
801
976
  const failed = summary.failed;
802
- const footer = `${summary.violations} violation${summary.violations === 1 ? "" : "s"} on ${failed} of ${summary.checked} pages` + (summary.skipped > 0 ? `; ${summary.skipped} skipped (--max-pages)` : "") + (summary.duplicates > 0 ? `; ${summary.duplicates} duplicate${summary.duplicates === 1 ? "" : "s"} dropped` : "") + (summary.excluded > 0 ? `; ${summary.excluded} excluded` : "");
977
+ const footer = `${summary.violations} violation${summary.violations === 1 ? "" : "s"} on ${failed} of ${summary.checked} pages` + (summary.skipped > 0 ? `; ${summary.skipped} skipped (--max-pages)` : "") + (summary.duplicates > 0 ? `; ${summary.duplicates} duplicate${summary.duplicates === 1 ? "" : "s"} dropped` : "") + (summary.redirects > 0 ? `; ${summary.redirects} redirect${summary.redirects === 1 ? "" : "s"}` : "") + (summary.excluded > 0 ? `; ${summary.excluded} excluded` : "");
803
978
  lines.push("");
804
979
  lines.push(failed > 0 ? c.red(footer) : c.green(footer));
805
980
  return lines.join("\n");
@@ -858,7 +1033,7 @@ var A11Y_FORMAT_LIST = A11Y_FORMATS.join(" | ");
858
1033
  function isA11yFormat(value) {
859
1034
  return A11Y_FORMATS.includes(value);
860
1035
  }
861
- function render2(format, run, opts) {
1036
+ function render3(format, run, opts) {
862
1037
  switch (format) {
863
1038
  case "pretty":
864
1039
  return renderPretty(run, opts);
@@ -918,6 +1093,9 @@ function terminalReporter(opts) {
918
1093
  case "checked":
919
1094
  showUrl(`${c.dim(counter(event.index, queued))} ${outcome(event, c)}`, event.url);
920
1095
  return;
1096
+ case "redirected":
1097
+ showUrl(`${c.dim(counter(event.index, queued))} ${c.dim("\u21AA")} redirects to `, event.to);
1098
+ return;
921
1099
  case "done":
922
1100
  stream.write(CLEAR_LINE);
923
1101
  return;
@@ -958,6 +1136,9 @@ function plainReporter(stream) {
958
1136
  else say(`${head} \u2717 ${plural(event.violations, "violation")}`);
959
1137
  return;
960
1138
  }
1139
+ case "redirected":
1140
+ say(`${counter(event.index, queued)} \u21AA redirects to ${event.to}`);
1141
+ return;
961
1142
  case "done":
962
1143
  say(`checked ${plural(event.checked, "page")}, ${event.skipped} skipped`);
963
1144
  return;
@@ -1109,7 +1290,7 @@ function buildProgram2() {
1109
1290
  { analyzer: createPlaywrightAnalyzer(), onProgress: progress.listener }
1110
1291
  );
1111
1292
  const color = resolveColor(parent, process.stdout);
1112
- const text2 = render2(format, run, { color, quiet: Boolean(options.quiet) });
1293
+ const text2 = render3(format, run, { color, quiet: Boolean(options.quiet) });
1113
1294
  if (text2.length > 0 || format !== "github") {
1114
1295
  process.stdout.write(`${text2}
1115
1296
  `);
@@ -1583,8 +1764,414 @@ function buildProgram3() {
1583
1764
  return program;
1584
1765
  }
1585
1766
 
1586
- // src/key/cli.ts
1767
+ // src/docevals/cli.ts
1587
1768
  import { Command as Command3 } from "commander";
1769
+ function colorFor2(command, isTTY, env) {
1770
+ const noColor = colorOwner2(command).opts().color === false;
1771
+ return shouldColor({ noColor, isTTY, env });
1772
+ }
1773
+ function colorOwner2(command) {
1774
+ for (let c = command; c !== null; c = c.parent) {
1775
+ if (c.options.some((o) => o.long === "--no-color")) return c;
1776
+ }
1777
+ return command;
1778
+ }
1779
+ function buildProgram4() {
1780
+ const program = new Command3();
1781
+ program.name("docevals").description(
1782
+ "Deterministic and LLM-as-judge evals for documentation pages, driven by frontmatter."
1783
+ ).version(package_default.version).option("--no-color", "disable colored output").showHelpAfterError("(add --help for usage)").exitOverride();
1784
+ function collectGrant(value, previous) {
1785
+ if (!EXECUTION_GRANTS.includes(value)) {
1786
+ fail(
1787
+ new DocevalsError(
1788
+ `--allow-execution must be one of ${EXECUTION_GRANTS.join(" | ")}, got "${value}"`
1789
+ )
1790
+ );
1791
+ }
1792
+ return [...previous, value];
1793
+ }
1794
+ function parseIntArg(name) {
1795
+ return (value) => {
1796
+ const n = Number.parseInt(value, 10);
1797
+ if (Number.isNaN(n) || n < 1) {
1798
+ fail(new DocevalsError(`${name} must be a positive integer, got "${value}"`));
1799
+ }
1800
+ return n;
1801
+ };
1802
+ }
1803
+ function collectArg(value, previous) {
1804
+ return [...previous, value];
1805
+ }
1806
+ function parseFloatArg(name) {
1807
+ return (value) => {
1808
+ const n = Number.parseFloat(value);
1809
+ if (Number.isNaN(n) || n < 0) {
1810
+ fail(new DocevalsError(`${name} must be a non-negative number, got "${value}"`));
1811
+ }
1812
+ return n;
1813
+ };
1814
+ }
1815
+ function parseFormatArg(name, allowed) {
1816
+ return (value) => {
1817
+ try {
1818
+ return parseFormat(value, allowed, name);
1819
+ } catch (e) {
1820
+ fail(e);
1821
+ }
1822
+ };
1823
+ }
1824
+ function documentInputs(command, verb) {
1825
+ return command.argument(
1826
+ "[paths...]",
1827
+ `Files, directories, or globs to ${verb} (default: the configured collections)`
1828
+ ).option(
1829
+ "--collection <name>",
1830
+ "Configured collection to read (repeatable)",
1831
+ collect,
1832
+ []
1833
+ ).option("--exclude <glob>", "Glob to exclude (repeatable)", collect, []).option("-c, --config <path>", "Path to manni.config.yaml").option("--no-config", "Ignore any discovered config file");
1834
+ }
1835
+ function documentOptions(opts) {
1836
+ const { configPath, noConfig } = configOption(opts.config);
1837
+ return {
1838
+ ...configPath === void 0 ? {} : { config: configPath },
1839
+ ...noConfig === true ? { noConfig } : {},
1840
+ ...opts.collection === void 0 ? {} : { collection: opts.collection },
1841
+ ...opts.exclude === void 0 ? {} : { exclude: opts.exclude }
1842
+ };
1843
+ }
1844
+ documentInputs(
1845
+ program.command("list").description("Show the resolved eval plan per page without running anything"),
1846
+ "list"
1847
+ ).option(
1848
+ "-f, --format <format>",
1849
+ `Output format: ${SUMMARY_FORMATS.join(" | ")}`,
1850
+ parseFormatArg("--format", SUMMARY_FORMATS),
1851
+ "pretty"
1852
+ ).option("--eval <name>", "Show only this eval (repeatable)", collectArg, []).option("--suite <name>", "Show only evals in this suite").action(
1853
+ async (paths, opts, command) => {
1854
+ try {
1855
+ const run = await runList(paths, {
1856
+ ...documentOptions(opts),
1857
+ format: opts.format,
1858
+ evalNames: opts.eval,
1859
+ suite: opts.suite
1860
+ });
1861
+ console.log(
1862
+ renderList(run, opts.format, {
1863
+ color: colorFor2(command, process.stdout.isTTY)
1864
+ })
1865
+ );
1866
+ process.exitCode = run.exitCode;
1867
+ } catch (e) {
1868
+ fail(e);
1869
+ }
1870
+ }
1871
+ );
1872
+ documentInputs(
1873
+ program.command("run").description("Run evals against documentation pages"),
1874
+ "evaluate"
1875
+ ).option(
1876
+ "-f, --format <format>",
1877
+ `Output format: ${REPORT_FORMATS2.join(" | ")}`,
1878
+ parseFormatArg("--format", REPORT_FORMATS2),
1879
+ "pretty"
1880
+ ).option("--deterministic-only", "Run only command/tool graders, skip the AI judge").option("--ai-only", "Run only AI-judged evals, skip deterministic graders").option(
1881
+ // `<kind>`, not `<kind...>`: a variadic option greedily eats the
1882
+ // positional `[paths...]` that follow it, so `--allow-execution
1883
+ // frontmatter-commands docs/**` would silently swallow the glob and run
1884
+ // over the default file set instead. Repeat the flag to grant twice.
1885
+ "--allow-execution <kind>",
1886
+ "Grant content-authored execution: frontmatter-commands",
1887
+ collectGrant,
1888
+ []
1889
+ ).option("--no-execution", "Clear every execution grant for this run").option("--no-generate", "Do not generate scripts for command evals missing a command").option("--no-cache", "Bypass the judge response cache").option("--fail-on-review", "Exit 1 when any eval lands in the human-review zone").option("--provider <name>", "Judge provider: auto (default) | anthropic | openai | claude-cli | llama-cpp").option("--model <model>", "Model override; needs a named provider, from here or config").option("--local", LOCAL_FLAG_HELP).option("--runs <n>", "Ensemble runs per eval", parseIntArg("--runs")).option(
1890
+ "--chunk-chars <n>",
1891
+ "Characters of page per judge call; longer pages are read in parts",
1892
+ parseIntArg("--chunk-chars")
1893
+ ).option(
1894
+ "--eval <name>",
1895
+ "Run only this eval (repeatable); suite targets are not evaluated on a filtered run",
1896
+ collectArg,
1897
+ []
1898
+ ).option("--suite <name>", "Run only evals in this suite").option(
1899
+ "--since <ref>",
1900
+ "Evaluate only pages whose file or eval manifest changed between this git ref and HEAD"
1901
+ ).option(
1902
+ "--max-turns <n>",
1903
+ "Stop after this many ensemble runs (a cached ensemble costs none)",
1904
+ parseIntArg("--max-turns")
1905
+ ).option(
1906
+ "--baseline [path]",
1907
+ "Fail only on findings a recorded baseline does not already hold"
1908
+ ).option("--no-baseline", "Ignore the configured baseline for this run").option(
1909
+ "--write-baseline [path]",
1910
+ "Record this run's findings as the baseline; without a path, the configured one"
1911
+ ).action(async (paths, opts, command) => {
1912
+ try {
1913
+ const report = await runRun(paths, {
1914
+ ...documentOptions({
1915
+ config: opts.config,
1916
+ collection: opts.collection,
1917
+ exclude: opts.exclude
1918
+ }),
1919
+ // parseFormatArg validated this at parse time; the cast only re-narrows
1920
+ // from the `unknown` that the Record-typed options bag erases it to.
1921
+ format: opts.format,
1922
+ deterministicOnly: opts.deterministicOnly,
1923
+ aiOnly: opts.aiOnly,
1924
+ allowExecution: opts.allowExecution,
1925
+ execution: opts.execution,
1926
+ generate: opts.generate,
1927
+ cache: opts.cache,
1928
+ failOnReview: opts.failOnReview,
1929
+ provider: opts.provider,
1930
+ model: opts.model,
1931
+ local: opts.local,
1932
+ runs: opts.runs,
1933
+ chunkChars: opts.chunkChars,
1934
+ maxTurns: opts.maxTurns,
1935
+ evalNames: opts.eval,
1936
+ suite: opts.suite,
1937
+ since: opts.since,
1938
+ // commander collapses `--baseline` to true and `--no-baseline` to
1939
+ // false on the same key; a string is an explicit path.
1940
+ baseline: opts.baseline,
1941
+ writeBaseline: opts.writeBaseline,
1942
+ toolVersion: package_default.version
1943
+ });
1944
+ console.log(
1945
+ render(report, opts.format, {
1946
+ color: colorFor2(command, process.stdout.isTTY)
1947
+ })
1948
+ );
1949
+ process.exitCode = report.exitCode;
1950
+ } catch (e) {
1951
+ fail(e);
1952
+ }
1953
+ });
1954
+ documentInputs(
1955
+ program.command("generate").description(
1956
+ "Generate check scripts for command evals with a plain-language assertion but no command"
1957
+ ),
1958
+ "read"
1959
+ ).option("--provider <name>", "Provider: auto (default) | anthropic | openai | claude-cli | llama-cpp").option("--model <model>", "Model override; needs a named provider, from here or config").option("--local", LOCAL_FLAG_HELP).action(
1960
+ async (paths, opts, command) => {
1961
+ try {
1962
+ const pc = palette(colorFor2(command, process.stdout.isTTY));
1963
+ const result = await runGenerate(paths, {
1964
+ ...documentOptions(opts),
1965
+ provider: opts.provider,
1966
+ model: opts.model,
1967
+ local: opts.local
1968
+ });
1969
+ if (result.targets === 0) {
1970
+ console.log("Nothing to generate \u2014 every command eval has a command.");
1971
+ return;
1972
+ }
1973
+ console.log(
1974
+ `Generated ${result.generatedPaths.length}/${result.targets} check script(s):`
1975
+ );
1976
+ for (const p of result.generatedPaths) console.log(` ${p}`);
1977
+ for (const r of result.refusals) console.log(pc.red(` ${r.message}`));
1978
+ if (result.generatedPaths.length < result.targets) {
1979
+ process.exitCode = 1;
1980
+ }
1981
+ } catch (e) {
1982
+ fail(e);
1983
+ }
1984
+ }
1985
+ );
1986
+ documentInputs(
1987
+ program.command("fill").description(
1988
+ "Propose frontmatter evals for pages with an LLM; writes proposals at or above the confidence threshold"
1989
+ ),
1990
+ "fill"
1991
+ ).option(
1992
+ "-f, --format <format>",
1993
+ `Output format: ${SUMMARY_FORMATS.join(" | ")}`,
1994
+ parseFormatArg("--format", SUMMARY_FORMATS),
1995
+ "pretty"
1996
+ ).option("--dry-run", "Report proposals without writing frontmatter").option(
1997
+ "--confidence <n>",
1998
+ "Minimum confidence to write (0-1, default: config fill.confidenceThreshold)",
1999
+ parseFloatArg("--confidence")
2000
+ ).option(
2001
+ "--max-turns <n>",
2002
+ "Stop after this many inference calls (a cached page costs none)",
2003
+ parseIntArg("--max-turns")
2004
+ ).option("--no-cache", "Bypass the fill proposal cache").option(
2005
+ "--chunk-chars <n>",
2006
+ "Characters of page per fill call; longer pages are proposed in parts",
2007
+ parseIntArg("--chunk-chars")
2008
+ ).option("--provider <name>", "Provider: auto (default) | anthropic | openai | claude-cli | llama-cpp").option("--model <model>", "Model override; needs a named provider, from here or config").option("--local", LOCAL_FLAG_HELP).action(async (paths, opts, command) => {
2009
+ try {
2010
+ const confidence = opts.confidence;
2011
+ if (confidence !== void 0 && confidence > 1) {
2012
+ fail(new DocevalsError(`--confidence must be between 0 and 1, got ${confidence}`));
2013
+ }
2014
+ const report = await runFill(paths, {
2015
+ ...documentOptions({
2016
+ config: opts.config,
2017
+ collection: opts.collection,
2018
+ exclude: opts.exclude
2019
+ }),
2020
+ dryRun: opts.dryRun,
2021
+ confidence,
2022
+ maxTurns: opts.maxTurns,
2023
+ noCache: opts.cache === false ? true : void 0,
2024
+ provider: opts.provider,
2025
+ model: opts.model,
2026
+ local: opts.local,
2027
+ // P1 (proposal 0047): offered only on a terminal, where there is
2028
+ // someone to answer. Off one the evals go to the page and the W1
2029
+ // line says so.
2030
+ confirm: terminalConfirm(),
2031
+ onNotice: notice,
2032
+ onRelocated: (result) => {
2033
+ const manifests = result.manifests.map((m) => m.file).join(", ");
2034
+ notice(
2035
+ `moved ${String(result.summary.moved)} value(s) into ${manifests}.`
2036
+ );
2037
+ }
2038
+ });
2039
+ console.log(
2040
+ renderFill(report, opts.format, {
2041
+ color: colorFor2(command, process.stdout.isTTY)
2042
+ })
2043
+ );
2044
+ process.exitCode = report.exitCode;
2045
+ } catch (e) {
2046
+ fail(e);
2047
+ }
2048
+ });
2049
+ documentInputs(
2050
+ program.command("promote").description(
2051
+ "Find ai-graded evals expressible as deterministic checks; --write converts them"
2052
+ ),
2053
+ "read"
2054
+ ).option("--write", "Apply promotions (write scripts and rewrite evals)").option("--provider <name>", "Provider: auto (default) | anthropic | openai | claude-cli | llama-cpp").option("--model <model>", "Model override; needs a named provider, from here or config").option("--local", LOCAL_FLAG_HELP).action(
2055
+ async (paths, opts, command) => {
2056
+ try {
2057
+ const pc = palette(colorFor2(command, process.stdout.isTTY));
2058
+ const proposals = await runPromote(paths, {
2059
+ ...documentOptions(opts),
2060
+ write: opts.write,
2061
+ provider: opts.provider,
2062
+ model: opts.model,
2063
+ local: opts.local
2064
+ });
2065
+ if (proposals.length === 0) {
2066
+ console.log("No ai-graded evals found.");
2067
+ return;
2068
+ }
2069
+ for (const p of proposals) {
2070
+ const tag = p.promotable ? p.applied ? pc.green("promoted") : pc.cyan("promotable") : pc.dim("keep-ai");
2071
+ const script = p.scriptPath ? pc.dim(` -> ${p.scriptPath}`) : "";
2072
+ console.log(`${tag} ${p.evalName} (${p.source}, ${p.file})${script}`);
2073
+ console.log(pc.dim(` ${p.rationale}`));
2074
+ if (p.error !== void 0) console.log(pc.red(` ${p.error}`));
2075
+ }
2076
+ if (!opts.write && proposals.some((p) => p.promotable)) {
2077
+ console.log(pc.cyan("\nRe-run with --write to apply promotions."));
2078
+ }
2079
+ } catch (e) {
2080
+ fail(e);
2081
+ }
2082
+ }
2083
+ );
2084
+ program.command("calibrate").description(
2085
+ "Measure judge agreement against a human-verified golden set (.manni/docevals/golden/*.yaml)"
2086
+ ).option("-c, --config <path>", "Path to manni.config.yaml").option("--golden <dir>", "Golden set directory", ".manni/docevals/golden").option(
2087
+ "--seed",
2088
+ "Write golden candidates from recorded reviews and exit; judges nothing, needs no provider"
2089
+ ).option("--provider <name>", "Provider: auto (default) | anthropic | openai | claude-cli | llama-cpp").option("--model <model>", "Model override; needs a named provider, from here or config").option("--local", LOCAL_FLAG_HELP).option("--runs <n>", "Ensemble runs per case", parseIntArg("--runs")).option(
2090
+ "--max-turns <n>",
2091
+ "Stop after this many ensemble runs (a cached ensemble costs none)",
2092
+ parseIntArg("--max-turns")
2093
+ ).option("--no-cache", "Bypass the judge response cache").action(
2094
+ async (opts, command) => {
2095
+ try {
2096
+ const color = colorFor2(command, process.stdout.isTTY);
2097
+ const pc = palette(color);
2098
+ if (opts.seed) {
2099
+ const seeded = seedGoldenCases({ golden: opts.golden });
2100
+ if (seeded.total === 0) {
2101
+ console.log(
2102
+ "No recorded reviews to seed from \u2014 run `manni docevals review <file> <eval> <pass|fail>` first."
2103
+ );
2104
+ return;
2105
+ }
2106
+ const changed = seeded.added + seeded.updated;
2107
+ console.log(
2108
+ changed === 0 ? `No new reviews to seed \u2014 ${seeded.total} golden case(s) already in ${seeded.path}.` : `Wrote ${changed} golden candidate(s) to ${seeded.path} (${seeded.added} new, ${seeded.updated} updated; ${seeded.total} total).`
2109
+ );
2110
+ if (seeded.unreviewed > 0) {
2111
+ console.log(
2112
+ pc.yellow(
2113
+ `${seeded.unreviewed} case(s) are \`reviewed: false\`. Read them and set \`reviewed: true\` \u2014 a golden set assembled without a human is not one.`
2114
+ )
2115
+ );
2116
+ }
2117
+ return;
2118
+ }
2119
+ const report = await runCalibrate({
2120
+ config: opts.config,
2121
+ golden: opts.golden,
2122
+ provider: opts.provider,
2123
+ model: opts.model,
2124
+ local: opts.local,
2125
+ runs: opts.runs,
2126
+ maxTurns: opts.maxTurns,
2127
+ noCache: opts.cache === false
2128
+ });
2129
+ console.log(renderCalibration(report, { color }));
2130
+ process.exitCode = report.meetsThreshold && report.unjudged === 0 && report.balanced ? 0 : 1;
2131
+ } catch (e) {
2132
+ fail(e);
2133
+ }
2134
+ }
2135
+ );
2136
+ program.command("init").description("Create a starter manni.config.yaml in the current directory").action(() => {
2137
+ try {
2138
+ console.log(`Created ${runInit()}`);
2139
+ } catch (e) {
2140
+ fail(e);
2141
+ }
2142
+ });
2143
+ program.command("review").description(
2144
+ "Record a human verdict for an eval in the human-review zone (or list recorded reviews)"
2145
+ ).argument("[file]", "Page path").argument("[eval]", "Eval name").argument("[verdict]", "pass | fail").option("--reviewer <name>", "Reviewer name recorded with the verdict").option("--note <text>", "Optional note").action(
2146
+ (file, evalName, verdict, opts, command) => {
2147
+ try {
2148
+ if (!file) {
2149
+ console.log(
2150
+ renderReviews(listReviews(), {
2151
+ color: colorFor2(command, process.stdout.isTTY)
2152
+ })
2153
+ );
2154
+ return;
2155
+ }
2156
+ if (!evalName || !verdict) {
2157
+ throw new DocevalsError(
2158
+ "Usage: manni docevals review <file> <eval> <pass|fail>"
2159
+ );
2160
+ }
2161
+ const entry = runReview(file, evalName, verdict, opts);
2162
+ console.log(
2163
+ `Recorded ${entry.verdict} for ${entry.evalName} on ${entry.file}`
2164
+ );
2165
+ } catch (e) {
2166
+ fail(e);
2167
+ }
2168
+ }
2169
+ );
2170
+ return program;
2171
+ }
2172
+
2173
+ // src/key/cli.ts
2174
+ import { Command as Command4 } from "commander";
1588
2175
 
1589
2176
  // src/key/reporters/json.ts
1590
2177
  function renderRotateJson(result) {
@@ -1681,8 +2268,8 @@ var ROTATE_FORMATS = ["pretty", "json"];
1681
2268
  function isRotateFormat(value) {
1682
2269
  return ROTATE_FORMATS.includes(value);
1683
2270
  }
1684
- function buildProgram4() {
1685
- const program = new Command3();
2271
+ function buildProgram5() {
2272
+ const program = new Command4();
1686
2273
  program.name("key").description("Set and rotate the family key that encrypted values are encrypted with.").version(package_default.version, "-V, --version").option("--no-color", "disable colored output").showHelpAfterError("(add --help for usage)").exitOverride();
1687
2274
  const color = () => shouldColor({ noColor: program.opts().color === false, isTTY: process.stdout.isTTY });
1688
2275
  program.command("set").description("Write the family encryption key to manni.config.yaml; generate one when no value is given").argument("[value]", "the key, at least 32 hex or base64url characters (default: 64 random hex)").option(
@@ -1765,8 +2352,616 @@ function buildProgram4() {
1765
2352
  return program;
1766
2353
  }
1767
2354
 
2355
+ // src/graph/cli.ts
2356
+ import { Command as Command5 } from "commander";
2357
+
2358
+ // src/graph/reporters/index.ts
2359
+ var GRAPH_FORMATS = ["pretty", "json"];
2360
+ var GRAPH_FORMAT_LIST = GRAPH_FORMATS.join(" | ");
2361
+ var CHECK_FORMATS = [...GRAPH_FORMATS, "github"];
2362
+ var CHECK_FORMAT_LIST = CHECK_FORMATS.join(" | ");
2363
+
2364
+ // src/graph/commands/check.ts
2365
+ import { resolve } from "node:path";
2366
+
2367
+ // src/graph/reporters/github.ts
2368
+ function renderCheckGithub2(report) {
2369
+ const lines = [];
2370
+ for (const f of report.findings) {
2371
+ const file = f.docs[0];
2372
+ const where2 = file === void 0 ? "" : ` file=${escapeWorkflowCommandProperty(file)}`;
2373
+ const message = escapeWorkflowCommandMessage(
2374
+ `${f.message} (${compactIri(f.focusNode)})`
2375
+ );
2376
+ lines.push(`::${f.severity}${where2}::${message}`);
2377
+ }
2378
+ return lines.join("\n");
2379
+ }
2380
+
2381
+ // src/graph/commands/check.ts
2382
+ async function runCheck3(opts = {}) {
2383
+ const cwd = opts.cwd ?? process.cwd();
2384
+ const config = loadRunConfig(
2385
+ {
2386
+ ...opts.config === void 0 ? {} : { configPath: opts.config },
2387
+ ...opts.noConfig === void 0 ? {} : { noConfig: opts.noConfig }
2388
+ },
2389
+ cwd
2390
+ );
2391
+ const store = loadGraph(resolve(cwd, opts.graph ?? config.out));
2392
+ const shapesInput = opts.shapes && opts.shapes.length > 0 ? opts.shapes : config.check.shapes;
2393
+ const shapes = shapesInput.length > 0 ? shapesInput.map((p) => resolve(cwd, p)) : [bundledShapesPath(import.meta.url)];
2394
+ const findings = await validateGraph(store, shapes);
2395
+ const count = (severity) => findings.filter((f) => f.severity === severity).length;
2396
+ const errors = count("error");
2397
+ return {
2398
+ findings,
2399
+ errors,
2400
+ warnings: count("warning"),
2401
+ notices: count("notice"),
2402
+ shapes,
2403
+ // Only an `error` fails the run; a warning and a notice are reported and
2404
+ // pass, as they were when they were called violations and warnings.
2405
+ exitCode: errors > 0 ? 1 : 0
2406
+ };
2407
+ }
2408
+ function plural3(n, word) {
2409
+ return `${String(n)} ${word}${n === 1 ? "" : "s"}`;
2410
+ }
2411
+ function renderCheck(report, format) {
2412
+ if (format === "json") {
2413
+ const { exitCode: _exitCode, ...rest } = report;
2414
+ return JSON.stringify(rest, null, 2);
2415
+ }
2416
+ if (format === "github") return renderCheckGithub2(report);
2417
+ const lines = [];
2418
+ for (const f of report.findings) {
2419
+ const where2 = f.docs.length > 0 ? ` [${f.docs.join(", ")}]` : "";
2420
+ lines.push(
2421
+ `${f.severity}: ${compactIri(f.focusNode)}${f.path ? ` ${compactIri(f.path)}` : ""} \u2014 ${f.message}${where2}`
2422
+ );
2423
+ }
2424
+ if (report.findings.length > 0) lines.push("");
2425
+ lines.push(
2426
+ `${plural3(report.errors, "error")}, ${plural3(report.warnings, "warning")}, ${plural3(report.notices, "notice")}`
2427
+ );
2428
+ return lines.join("\n");
2429
+ }
2430
+
2431
+ // src/graph/commands/init.ts
2432
+ import { existsSync, readFileSync } from "node:fs";
2433
+ import { resolve as resolve2 } from "node:path";
2434
+ import { parse as parseYaml } from "yaml";
2435
+ var HEADER = `# manni.config.yaml \u2014 shared configuration for the manni family of tools.
2436
+ # Each tool reads its own top-level key; manni graph reads "graph:".
2437
+ `;
2438
+ var STARTER = `# Base IRI for every minted node. Set this to a namespace you control;
2439
+ # without it, IRIs fall back to the urn:manni:graph: placeholder.
2440
+ # baseIri: https://example.com/graph/
2441
+
2442
+ # There is no document set here. \`manni graph build\` and \`manni graph fill\` read the
2443
+ # paths you name, or the collections declared once for every tool at the TOP
2444
+ # level of this file, beside \`graph:\` rather than inside it:
2445
+ #
2446
+ # collections:
2447
+ # - name: site
2448
+ # paths: ["docs/**/*.md"]
2449
+ #
2450
+ # Output of \`manni graph build\`.
2451
+ out: graph/graph.ttl
2452
+
2453
+ # Map published-site routes back to source files so route-style links
2454
+ # (/docs/actions/find) become graph edges. A route may also carry the BCP-47
2455
+ # \`language\` labelling every document under its root; a page's own \`lang\`
2456
+ # frontmatter wins. Uncomment and adjust:
2457
+ # routes:
2458
+ # - basePath: /docs
2459
+ # root: docs
2460
+ # extensions: [.md, .mdx, .markdown]
2461
+ # indexFiles: [index, README]
2462
+ # - basePath: /docs/de
2463
+ # root: docs/de
2464
+ # language: de
2465
+
2466
+ # What to derive triples from. Remove entries to opt out.
2467
+ build:
2468
+ derive: [frontmatter, sections, links, tags, images, code, provenance]
2469
+
2470
+ # PROV-O settings. Git history is detected, not switched: per-file dates and
2471
+ # author agents, rename -> prov:wasRevisionOf, and the build activity's HEAD
2472
+ # committer date are derived wherever git can run over a repository, and a run
2473
+ # that cannot says so once and builds the rest. Deterministic per commit;
2474
+ # wall-clock time never enters the graph.
2475
+ # qualified: emit prov:qualifiedAttribution/qualifiedAssociation nodes
2476
+ # with roles alongside the direct properties.
2477
+ provenance:
2478
+ qualified: true
2479
+
2480
+ # Which fields a published graph may carry is read from x-manni-graph-output in
2481
+ # the schema set \`manni meta validate\` resolves for each page. Set that under
2482
+ # meta: (schemas, strict, register), not here. The default set keeps the
2483
+ # stewardship people fields (owner, stakeholders, reviewed-by) out.
2484
+
2485
+ # SHACL shapes \`manni graph check\` validates the built graph against. Default:
2486
+ # the shapes contract bundled with manni (shapes/graph/shapes-1.0.0.ttl).
2487
+ # check:
2488
+ # shapes: ["./my-shapes.ttl"]
2489
+
2490
+ # Metadata coverage gate for \`manni graph stats --check\`. A number applies to
2491
+ # every measured field; a map gates named fields only. Unset gates nothing.
2492
+ # stats:
2493
+ # coverageThreshold:
2494
+ # title: 100
2495
+ # description: 50
2496
+
2497
+ # Which provider \`manni graph fill\` sends pages to. Unset, the family's
2498
+ # top-level providers: map decides, and then \`auto\`, which detects one: an
2499
+ # Anthropic key, then an OpenAI key, then the Claude CLI, then a local model.
2500
+ # Connection settings (apiKeyEnv, baseUrl, command) live in that map, declared
2501
+ # once for every manni tool. \`--provider\` wins over this, \`--local\` over both.
2502
+ # provider: anthropic # auto | anthropic | openai | claude-cli | llama-cpp
2503
+ # model: claude-sonnet-4-5 # the provider's default when omitted
2504
+
2505
+ # LLM settings for \`manni graph fill\` (SKOS frontmatter proposals).
2506
+ fill:
2507
+ temperature: 0
2508
+ # Stop after this many inference calls. One page is one turn, and a cached
2509
+ # page spends none. Unset is unbounded.
2510
+ # maxTurns: 50
2511
+ cacheDir: .manni/graph/cache
2512
+ # fill proposes every field; confidence (0..1 per field) gates what is
2513
+ # written. Fields scored below the threshold are reported, not written.
2514
+ confidenceThreshold: 0.7
2515
+ # fields: defaults to every fillable field \u2014 uncomment to restrict.
2516
+ # Record meta-provenance (model + /graph/ pointers + confidence) on filled docs.
2517
+ writeProvenance: true
2518
+ # Reject proposals that would violate the SHACL shapes contract
2519
+ # (broader/narrower cycles, conflicting labels).
2520
+ validateGraph: true
2521
+ # Also propose per-section metadata. Off by default: it costs more output per
2522
+ # call, and section metadata is explicit-only, so review it as carefully.
2523
+ sections: false
2524
+
2525
+ # Local embeddings for semantic search (\`manni graph embed\`). Needs the optional
2526
+ # peer: npm install @huggingface/transformers
2527
+ # embed:
2528
+ # model: onnx-community/granite-embedding-small-english-r2-ONNX
2529
+ # dtype: q8 # q8 keeps embedding reproducible across platforms
2530
+ # out: graph # directory for the per-language vector sidecars
2531
+ # cacheDir: .manni/graph/embed-cache
2532
+
2533
+ # Optional enrichment for \`manni graph export iirds\` (the iiRDS package).
2534
+ # Absent, a minimal valid package is still produced.
2535
+ # export:
2536
+ # iirds:
2537
+ # title: My Docs # package title (default: "manni graph export")
2538
+ # creator: Acme Corp # Creator iirds:Party + vcard:Organization
2539
+ # version: "1.3" # iiRDS version literal: "1.2" | "1.3"
2540
+ `;
2541
+ function starterSection() {
2542
+ const body = STARTER.split("\n").map((line) => line === "" ? "" : ` ${line}`).join("\n");
2543
+ return `${CONFIG_SECTION}:
2544
+ ${body}`;
2545
+ }
2546
+ async function runInit2(cwd = process.cwd()) {
2547
+ const path = resolve2(cwd, DEFAULT_CONFIG_FILENAME);
2548
+ if (!existsSync(path)) {
2549
+ await writeTextAtomic(path, `${HEADER}${starterSection()}`);
2550
+ return path;
2551
+ }
2552
+ const text2 = readFileSync(path, "utf8");
2553
+ let doc;
2554
+ try {
2555
+ doc = parseYaml(text2);
2556
+ } catch (e) {
2557
+ throw new GraphError(
2558
+ `Invalid YAML in ${DEFAULT_CONFIG_FILENAME}: ${errorMessage(e)}`
2559
+ );
2560
+ }
2561
+ if (doc != null && (typeof doc !== "object" || Array.isArray(doc))) {
2562
+ throw new GraphError(
2563
+ `${DEFAULT_CONFIG_FILENAME}: top level must be a mapping \u2014 not adding a \`${CONFIG_SECTION}:\` section to it.`
2564
+ );
2565
+ }
2566
+ if (doc != null && Object.hasOwn(doc, CONFIG_SECTION)) {
2567
+ throw new GraphError(
2568
+ `${DEFAULT_CONFIG_FILENAME} already has a \`${CONFIG_SECTION}:\` section \u2014 not overwriting.`
2569
+ );
2570
+ }
2571
+ const separator = text2 === "" || text2.endsWith("\n") ? "" : "\n";
2572
+ const blank = text2 === "" ? "" : "\n";
2573
+ await writeTextAtomic(
2574
+ path,
2575
+ `${text2}${separator}${blank}${starterSection()}`
2576
+ );
2577
+ return path;
2578
+ }
2579
+
2580
+ // src/graph/cli.ts
2581
+ function buildProgram6() {
2582
+ const program = new Command5();
2583
+ program.name("graph").description(
2584
+ "Deterministic knowledge graphs derived from documentation frontmatter and formatting."
2585
+ ).version(package_default.version).option("--no-color", "disable colored output").showHelpAfterError("(add --help for usage)").exitOverride();
2586
+ function numericOption(flag, { min, max, integer }) {
2587
+ return (raw) => {
2588
+ const value = Number.parseFloat(raw);
2589
+ if (!Number.isFinite(value)) {
2590
+ throw new GraphError(`${flag} expects a number, got "${raw}".`);
2591
+ }
2592
+ if (integer && !Number.isInteger(value)) {
2593
+ throw new GraphError(`${flag} expects a whole number, got ${value}.`);
2594
+ }
2595
+ if (value < min || max !== void 0 && value > max) {
2596
+ const range = max === void 0 ? `>= ${min}` : `${min}..${max}`;
2597
+ throw new GraphError(`${flag} must be ${range}, got ${value}.`);
2598
+ }
2599
+ return value;
2600
+ };
2601
+ }
2602
+ function countOption(flag, min = 1) {
2603
+ return numericOption(flag, { min, integer: true });
2604
+ }
2605
+ function enumOption(flag, allowed) {
2606
+ return (raw) => {
2607
+ if (!allowed.includes(raw)) {
2608
+ throw new GraphError(
2609
+ `${flag} must be one of ${allowed.join(" | ")}, got "${raw}".`
2610
+ );
2611
+ }
2612
+ return raw;
2613
+ };
2614
+ }
2615
+ function choiceOption(flag, allowed) {
2616
+ return (raw) => {
2617
+ if (!allowed.includes(raw)) {
2618
+ throw new GraphError(`Unknown ${flag} "${raw}". Use ${allowed.join(" | ")}.`);
2619
+ }
2620
+ return raw;
2621
+ };
2622
+ }
2623
+ const formatOption = choiceOption("--format", GRAPH_FORMATS);
2624
+ const checkFormatOption = choiceOption("--format", CHECK_FORMATS);
2625
+ const fieldsOption = (raw) => splitList(raw).map((value) => enumOption("--fields", ALL_FILL_FIELDS)(value));
2626
+ function documentInputs(command, verb) {
2627
+ return command.argument(
2628
+ "[paths...]",
2629
+ `files, directories, or globs to ${verb} (use - for stdin)`
2630
+ ).option(
2631
+ "--collection <name>",
2632
+ "Configured collection to read (repeatable)",
2633
+ collect,
2634
+ []
2635
+ ).option("--exclude <glob>", "Glob to exclude (repeatable)", collect, []).option("--as <format>", "force an input format (markdown, mdx)").option(
2636
+ "--ext <list>",
2637
+ "comma-separated extensions for directory walks",
2638
+ DEFAULT_EXTENSIONS.join(",")
2639
+ ).option("--allow-empty", "treat zero matched files as success").option("-c, --config <path>", "Path to manni.config.yaml").option("--no-config", "Ignore any discovered config file");
2640
+ }
2641
+ async function inputOptions2(paths, opts) {
2642
+ assertInputFormat(paths, opts.as);
2643
+ return {
2644
+ ...opts.as === void 0 ? {} : { as: opts.as },
2645
+ ext: splitList(opts.ext),
2646
+ ...opts.allowEmpty === true ? { allowEmpty: true } : {},
2647
+ ...paths.includes(STDIN) ? { stdinContent: await readStdin() } : {}
2648
+ };
2649
+ }
2650
+ function configInputs(command) {
2651
+ return command.option("-c, --config <path>", "Path to manni.config.yaml").option("--no-config", "Ignore any discovered config file");
2652
+ }
2653
+ function rest(opts) {
2654
+ const { config, ...others } = opts;
2655
+ return others;
2656
+ }
2657
+ function documentOptions(opts) {
2658
+ const { configPath, noConfig } = configOption(opts.config);
2659
+ return {
2660
+ ...configPath === void 0 ? {} : { config: configPath },
2661
+ ...noConfig === true ? { noConfig } : {},
2662
+ ...opts.collection === void 0 ? {} : { collection: opts.collection },
2663
+ ...opts.exclude === void 0 ? {} : { exclude: opts.exclude }
2664
+ };
2665
+ }
2666
+ program.command("init").description("Add a starter `graph:` section to manni.config.yaml in the current directory").action(async () => {
2667
+ try {
2668
+ console.log(`Created ${await runInit2()}`);
2669
+ } catch (e) {
2670
+ fail(e);
2671
+ }
2672
+ });
2673
+ documentInputs(
2674
+ program.command("build").description("Derive the knowledge graph and write deterministic Turtle"),
2675
+ "build"
2676
+ ).option("-o, --out <path>", "Output .ttl path (default: config out)").action(async (paths, opts) => {
2677
+ try {
2678
+ const result = await runBuild({
2679
+ paths,
2680
+ ...documentOptions(opts),
2681
+ ...await inputOptions2(paths, opts),
2682
+ out: opts.out
2683
+ });
2684
+ for (const warning of result.warnings) warn(warning);
2685
+ console.log(
2686
+ `Wrote ${result.outPath} (${result.docs} docs, ${result.quads} triples)`
2687
+ );
2688
+ } catch (e) {
2689
+ fail(e);
2690
+ }
2691
+ });
2692
+ configInputs(
2693
+ program.command("check").description(
2694
+ "Validate the built graph against the bundled SHACL shapes (errors exit 1)"
2695
+ )
2696
+ ).option("-g, --graph <path>", "Graph .ttl path (default: config out)").option(
2697
+ "--shapes <path>",
2698
+ "Shapes .ttl file; repeatable (default: config check.shapes, then bundled)",
2699
+ collect,
2700
+ []
2701
+ ).option(
2702
+ "-f, --format <format>",
2703
+ `Output: ${CHECK_FORMAT_LIST}`,
2704
+ checkFormatOption,
2705
+ "pretty"
2706
+ ).action(
2707
+ async (opts) => {
2708
+ try {
2709
+ const report = await runCheck3({ ...rest(opts), ...documentOptions(opts) });
2710
+ console.log(renderCheck(report, opts.format));
2711
+ process.exitCode = report.exitCode;
2712
+ } catch (e) {
2713
+ fail(e);
2714
+ }
2715
+ }
2716
+ );
2717
+ documentInputs(
2718
+ program.command("fill").description(
2719
+ "Propose `graph:` frontmatter fields with an LLM, gated by confidence, and write them back"
2720
+ ),
2721
+ "fill"
2722
+ ).option(
2723
+ "-f, --format <format>",
2724
+ `Output: ${GRAPH_FORMAT_LIST}`,
2725
+ formatOption,
2726
+ "pretty"
2727
+ ).option("--dry-run", "Report proposals without writing files").option("--force", "Overwrite human-set graph fields").option("--no-cache", "Bypass the proposal cache").option("--no-validate-graph", "Skip the SHACL graph guardrail on proposals").option("--sections", "Also propose per-section metadata").option(
2728
+ "--max-turns <n>",
2729
+ "Stop after this many inference calls (a cached page costs none)",
2730
+ countOption("--max-turns")
2731
+ ).option(
2732
+ "--confidence <n>",
2733
+ "Minimum model confidence (0..1) to write a field (default: config, 0.7)",
2734
+ numericOption("--confidence", { min: 0, max: 1 })
2735
+ ).option(
2736
+ // Comma-separated and given once: one separator per list (proposal 0034).
2737
+ "--fields <list>",
2738
+ `Fields to propose (comma-separated; default: config, then every field)`,
2739
+ fieldsOption
2740
+ ).option(
2741
+ "--provider <name>",
2742
+ "Provider: auto (default) | anthropic | openai | claude-cli | llama-cpp"
2743
+ ).option("--model <model>", "Model override; needs a named provider, from here or config").option("--local", LOCAL_FLAG_HELP).action(async (paths, opts) => {
2744
+ try {
2745
+ const report = await runFill2({
2746
+ paths,
2747
+ ...documentOptions(opts),
2748
+ ...await inputOptions2(paths, opts),
2749
+ dryRun: opts.dryRun,
2750
+ force: opts.force,
2751
+ noCache: opts.cache === false,
2752
+ noValidateGraph: opts.validateGraph === false,
2753
+ sections: opts.sections,
2754
+ maxTurns: opts.maxTurns,
2755
+ confidence: opts.confidence,
2756
+ fields: opts.fields,
2757
+ provider: opts.provider,
2758
+ model: opts.model,
2759
+ local: opts.local
2760
+ });
2761
+ for (const warning of report.warnings) warn(warning);
2762
+ const rendered2 = renderFill2(report, opts.format);
2763
+ if (paths.includes(STDIN)) {
2764
+ if (report.stdinDocument !== void 0) {
2765
+ process.stdout.write(report.stdinDocument);
2766
+ }
2767
+ if (rendered2.length > 0) process.stderr.write(`${rendered2}
2768
+ `);
2769
+ } else {
2770
+ console.log(rendered2);
2771
+ }
2772
+ process.exitCode = report.exitCode;
2773
+ } catch (e) {
2774
+ fail(e);
2775
+ }
2776
+ });
2777
+ program.command("query").description(
2778
+ "Match triple patterns against the built graph (omit a term for wildcard)"
2779
+ ).option("--subject <term>", "Subject IRI or prefixed name").option("--predicate <term>", "Predicate IRI or prefixed name").option("--object <term>", "Object IRI, prefixed name, or literal value").option("-c, --config <path>", "Path to manni.config.yaml").option("--no-config", "Ignore any discovered config file").option("-g, --graph <path>", "Graph .ttl path (default: config out)").option(
2780
+ "-f, --format <format>",
2781
+ `Output: ${GRAPH_FORMAT_LIST}`,
2782
+ formatOption,
2783
+ "pretty"
2784
+ ).action(
2785
+ (opts) => {
2786
+ try {
2787
+ const { subject, predicate, object, ...others } = rest(opts);
2788
+ const result = runQuery({
2789
+ ...others,
2790
+ ...documentOptions(opts),
2791
+ ...subject === void 0 ? {} : { s: subject },
2792
+ ...predicate === void 0 ? {} : { p: predicate },
2793
+ ...object === void 0 ? {} : { o: object }
2794
+ });
2795
+ console.log(renderQuery(result, opts.format));
2796
+ } catch (e) {
2797
+ fail(e);
2798
+ }
2799
+ }
2800
+ );
2801
+ program.command("stats").description(
2802
+ "Summarize the built graph: counts, orphans, broken links, hubs, metadata coverage"
2803
+ ).option("-c, --config <path>", "Path to manni.config.yaml").option("--no-config", "Ignore any discovered config file").option("-g, --graph <path>", "Graph .ttl path (default: config out)").option(
2804
+ "-f, --format <format>",
2805
+ `Output: ${GRAPH_FORMAT_LIST}`,
2806
+ formatOption,
2807
+ "pretty"
2808
+ ).option(
2809
+ "--check",
2810
+ "Exit 1 when broken internal links exist or coverage is below threshold"
2811
+ ).option(
2812
+ "--top <n>",
2813
+ "How many most-connected docs to list",
2814
+ countOption("--top")
2815
+ ).option(
2816
+ "--coverage-threshold <pct>",
2817
+ "Minimum metadata coverage % (all fields); overrides config for this run",
2818
+ // A percentage, so 0..100 rather than the 0..1 of a confidence. NaN here is
2819
+ // worse than a wrong number: `pct < NaN` is false for every field, so the
2820
+ // gate this flag exists to tighten would silently pass instead.
2821
+ numericOption("--coverage-threshold", { min: 0, max: 100 })
2822
+ ).action(
2823
+ (opts) => {
2824
+ try {
2825
+ const report = runStats({ ...rest(opts), ...documentOptions(opts) });
2826
+ console.log(renderStats(report, opts.format));
2827
+ process.exitCode = report.exitCode;
2828
+ } catch (e) {
2829
+ fail(e);
2830
+ }
2831
+ }
2832
+ );
2833
+ program.command("search").description("Rank graph nodes for a text query (needs `export search`)").argument("<query>", "Text query").option("-c, --config <path>", "Path to manni.config.yaml").option("--no-config", "Ignore any discovered config file").option("-g, --graph <path>", "Graph .ttl path (default: config out)").option(
2834
+ "-i, --index <dir>",
2835
+ "Directory holding the indexes and manifest (default: beside the graph)"
2836
+ ).option(
2837
+ "--lang <tag>",
2838
+ "Which localization to search (required when the corpus has more than one)"
2839
+ ).option("--limit <n>", "Maximum results (default 10)", countOption("--limit")).option(
2840
+ "--vectors <path>",
2841
+ "Vector sidecar path (default: from the manifest)"
2842
+ ).option(
2843
+ "--mode <mode>",
2844
+ `Which legs to run: ${SEARCH_MODES.join(" | ")} (default: hybrid when vectors exist)`,
2845
+ choiceOption("--mode", SEARCH_MODES)
2846
+ ).option(
2847
+ "-f, --format <format>",
2848
+ `Output: ${GRAPH_FORMAT_LIST}`,
2849
+ formatOption,
2850
+ "pretty"
2851
+ ).action(
2852
+ async (query, opts) => {
2853
+ try {
2854
+ const report = await runSearch({
2855
+ ...rest(opts),
2856
+ ...documentOptions(opts),
2857
+ query
2858
+ });
2859
+ console.log(renderSearch(report, opts.format));
2860
+ } catch (e) {
2861
+ fail(e);
2862
+ }
2863
+ }
2864
+ );
2865
+ program.command("traverse").description(
2866
+ "Walk the graph from a node, honoring scope rules, with the full trace"
2867
+ ).argument("<node>", "Starting node: a full IRI or a prefix:local CURIE").option("-c, --config <path>", "Path to manni.config.yaml").option("--no-config", "Ignore any discovered config file").option("-g, --graph <path>", "Graph .ttl path (default: config out)").option(
2868
+ "-d, --depth <n>",
2869
+ "Maximum hops from the node (default 1; 3 under --impact)",
2870
+ // Zero is allowed: it means the node itself, which is a real answer.
2871
+ countOption("--depth", 0)
2872
+ ).option(
2873
+ "--predicates <list>",
2874
+ "Only follow these predicates (comma-separated)"
2875
+ ).option("--reverse", "Follow inbound edges (who points at this node)").option("--impact", "Transitive inbound reach: what a change here affects").option(
2876
+ "--variant <variant>",
2877
+ "Scope filter: product variant IRI, title, or slug"
2878
+ ).option("--software-subject <subject>", "Scope filter: software subject").option("--lang <tag>", "Scope filter: BCP-47 language tag, matched in any case").option("--limit <n>", "Stop after this many nodes", countOption("--limit")).option(
2879
+ "-f, --format <format>",
2880
+ `Output: ${GRAPH_FORMAT_LIST}`,
2881
+ formatOption,
2882
+ "pretty"
2883
+ ).action(
2884
+ (node, opts) => {
2885
+ try {
2886
+ const { softwareSubject, ...others } = rest(opts);
2887
+ const report = runTraverse({
2888
+ ...others,
2889
+ ...documentOptions(opts),
2890
+ ...softwareSubject === void 0 ? {} : { subject: softwareSubject },
2891
+ node,
2892
+ predicates: opts.predicates === void 0 ? void 0 : splitList(opts.predicates)
2893
+ });
2894
+ console.log(renderTraverse(report, opts.format));
2895
+ } catch (e) {
2896
+ fail(e);
2897
+ }
2898
+ }
2899
+ );
2900
+ program.command("embed").description(
2901
+ "Compute local embeddings for the search index (needs @huggingface/transformers)"
2902
+ ).option("-c, --config <path>", "Path to manni.config.yaml").option("--no-config", "Ignore any discovered config file").option("-g, --graph <path>", "Graph .ttl path (default: config out)").option(
2903
+ "-i, --index <dir>",
2904
+ "Directory holding the indexes and manifest (default: beside the graph)"
2905
+ ).option(
2906
+ "-o, --out <dir>",
2907
+ "Directory for the sidecars (default: the index directory)"
2908
+ ).option(
2909
+ "--embedding-model <id>",
2910
+ "Embedding model id (any id; `mock` for offline runs)"
2911
+ ).option("--dtype <dtype>", "Weight quantization (default q8)").option("--no-cache", "Ignore the vector cache").option(
2912
+ "-f, --format <format>",
2913
+ `Output: ${GRAPH_FORMAT_LIST}`,
2914
+ formatOption,
2915
+ "pretty"
2916
+ ).action(
2917
+ async (opts) => {
2918
+ try {
2919
+ const { embeddingModel, ...others } = rest(opts);
2920
+ const report = await runEmbed({
2921
+ ...others,
2922
+ ...documentOptions(opts),
2923
+ ...embeddingModel === void 0 ? {} : { model: embeddingModel },
2924
+ noCache: opts.cache === false
2925
+ });
2926
+ console.log(renderEmbed(report, opts.format));
2927
+ } catch (e) {
2928
+ fail(e);
2929
+ }
2930
+ }
2931
+ );
2932
+ program.command("export").description(
2933
+ "Reserialize the built graph into a consumer format (jsonld file, iirds package, or search index)"
2934
+ ).argument("<target>", `What to write: ${EXPORT_TARGETS.join(" | ")}`).option("-c, --config <path>", "Path to manni.config.yaml").option("--no-config", "Ignore any discovered config file").option("-g, --graph <path>", "Graph .ttl path (default: config out)").option(
2935
+ "-o, --out <path>",
2936
+ "Output path (default: the graph path with the target's extension)"
2937
+ ).action(
2938
+ async (target, opts) => {
2939
+ try {
2940
+ if (!EXPORT_TARGETS.includes(target)) {
2941
+ throw new GraphError(
2942
+ `Unknown export target "${target}". Use ${EXPORT_TARGETS.join(" | ")}.`
2943
+ );
2944
+ }
2945
+ const result = await runExport({
2946
+ ...documentOptions(opts),
2947
+ graph: opts.graph,
2948
+ format: target,
2949
+ out: opts.out
2950
+ });
2951
+ for (const warning of result.warnings) warn(warning);
2952
+ console.log(
2953
+ `Wrote ${result.nodes} node${result.nodes === 1 ? "" : "s"} to ${result.outPath}`
2954
+ );
2955
+ } catch (e) {
2956
+ fail(e);
2957
+ }
2958
+ }
2959
+ );
2960
+ return program;
2961
+ }
2962
+
1768
2963
  // src/lint/cli.ts
1769
- import { Command as Command4 } from "commander";
2964
+ import { Command as Command6 } from "commander";
1770
2965
  var LIST_FORMATS = /* @__PURE__ */ new Set(["pretty", "json"]);
1771
2966
  var TOOL_OPTIONS_GROUP = "Tool options:";
1772
2967
  function resolveColor2(program) {
@@ -1844,8 +3039,8 @@ function inputOptions(paths, options) {
1844
3039
  onNotice: notice
1845
3040
  };
1846
3041
  }
1847
- function buildProgram5() {
1848
- const program = new Command4();
3042
+ function buildProgram7() {
3043
+ const program = new Command6();
1849
3044
  program.name("lint").description(
1850
3045
  "Validate document structure against doctype templates. Deterministic, CI-friendly."
1851
3046
  ).version(package_default.version, "-V, --version").option("--no-color", "disable colored output").showHelpAfterError().exitOverride();
@@ -1870,7 +3065,7 @@ function buildProgram5() {
1870
3065
  });
1871
3066
  const color = resolveColor2(command.parent ?? command);
1872
3067
  const chosen = explain ? "explain" : format;
1873
- const text2 = render(run, chosen, { color });
3068
+ const text2 = render2(run, chosen, { color });
1874
3069
  if (text2.length > 0 || !omittedWhenClean(chosen)) {
1875
3070
  process.stdout.write(`${text2}
1876
3071
  `);
@@ -2044,13 +3239,13 @@ function buildProgram5() {
2044
3239
  }
2045
3240
 
2046
3241
  // src/term/cli.ts
2047
- import { Command as Command5 } from "commander";
3242
+ import { Command as Command7 } from "commander";
2048
3243
 
2049
3244
  // src/term/commands/check.ts
2050
- import { existsSync as existsSync2 } from "node:fs";
3245
+ import { existsSync as existsSync3 } from "node:fs";
2051
3246
 
2052
3247
  // src/term/core/config.ts
2053
- import { resolve } from "node:path";
3248
+ import { resolve as resolve3 } from "node:path";
2054
3249
 
2055
3250
  // src/term/errors.ts
2056
3251
  var TermError = class extends ToolError {
@@ -2230,11 +3425,11 @@ function toLoaded(file) {
2230
3425
  tools: file.tools
2231
3426
  };
2232
3427
  }
2233
- var STDIN = "-";
3428
+ var STDIN2 = "-";
2234
3429
  async function resolveTermRun(opts) {
2235
- const cwd = resolve(opts.cwd);
3430
+ const cwd = resolve3(opts.cwd);
2236
3431
  const wanted = opts.collection ?? [];
2237
- if (wanted.length > 0 && opts.inputs.some((input) => input !== STDIN)) {
3432
+ if (wanted.length > 0 && opts.inputs.some((input) => input !== STDIN2)) {
2238
3433
  throw new TermError(
2239
3434
  "--collection selects a configured collection; it cannot be combined with paths."
2240
3435
  );
@@ -2249,7 +3444,7 @@ async function resolveTermRun(opts) {
2249
3444
  const fromCollections = opts.inputs.length === 0;
2250
3445
  const inputs = fromCollections ? collections.flatMap((c) => c.paths) : opts.inputs;
2251
3446
  const base = fromCollections && inputs.length > 0 && loaded ? loaded.dir : cwd;
2252
- const manifests = loaded ? (loaded.config.manifests ?? []).map((written) => ({ path: resolve(loaded.dir, written), written })) : [];
3447
+ const manifests = loaded ? (loaded.config.manifests ?? []).map((written) => ({ path: resolve3(loaded.dir, written), written })) : [];
2253
3448
  return {
2254
3449
  config: loaded ? loaded.config : null,
2255
3450
  inputs,
@@ -2566,12 +3761,12 @@ function compareFindings2(a, b) {
2566
3761
  }
2567
3762
 
2568
3763
  // src/term/commands/run.ts
2569
- import { existsSync } from "node:fs";
2570
- import { isAbsolute, relative, resolve as resolve3 } from "node:path";
3764
+ import { existsSync as existsSync2 } from "node:fs";
3765
+ import { isAbsolute, relative, resolve as resolve5 } from "node:path";
2571
3766
 
2572
3767
  // src/term/core/load-set.ts
2573
3768
  import { readFile } from "node:fs/promises";
2574
- import { extname, resolve as resolve2 } from "node:path";
3769
+ import { extname, resolve as resolve4 } from "node:path";
2575
3770
 
2576
3771
  // src/term/core/writers/markup.ts
2577
3772
  function xmlText(value) {
@@ -4153,7 +5348,7 @@ async function loadTermSet(opts) {
4153
5348
  });
4154
5349
  const namedFiles = new Set(named);
4155
5350
  for (const file of files) {
4156
- const path = resolve2(run.base, file);
5351
+ const path = resolve4(run.base, file);
4157
5352
  if (namedFiles.has(file) && MANIFEST_EXTENSIONS.has(extname(file).toLowerCase())) {
4158
5353
  let content2;
4159
5354
  try {
@@ -4204,7 +5399,7 @@ async function loadTermSet(opts) {
4204
5399
 
4205
5400
  // src/term/commands/run.ts
4206
5401
  async function loadTerms(opts) {
4207
- const cwd = resolve3(opts.cwd ?? process.cwd());
5402
+ const cwd = resolve5(opts.cwd ?? process.cwd());
4208
5403
  const run = await resolveTermRun({
4209
5404
  cwd,
4210
5405
  inputs: opts.inputs,
@@ -4250,18 +5445,18 @@ function displayPath(path, cwd) {
4250
5445
  function valeConfigFor(run) {
4251
5446
  if (run.configDir === void 0) return void 0;
4252
5447
  const path = valeConfigPath(run.tools, run.configDir);
4253
- if (path !== void 0 && !existsSync(path)) {
5448
+ if (path !== void 0 && !existsSync2(path)) {
4254
5449
  const source = run.configSource ?? "manni.config.yaml";
4255
5450
  throw new TermError(`${source}: tools.vale.config "${run.tools.vale?.config ?? path}" does not exist.`);
4256
5451
  }
4257
5452
  return path;
4258
5453
  }
4259
- function plural3(n, one, many = `${one}s`) {
5454
+ function plural4(n, one, many = `${one}s`) {
4260
5455
  return `${String(n)} ${n === 1 ? one : many}`;
4261
5456
  }
4262
5457
 
4263
5458
  // src/term/commands/check.ts
4264
- async function runCheck3(opts) {
5459
+ async function runCheck4(opts) {
4265
5460
  const { cwd, run, set } = await loadTerms(opts);
4266
5461
  const config = run.config;
4267
5462
  const findings = checkTermSet(set, {
@@ -4277,7 +5472,7 @@ async function runCheck3(opts) {
4277
5472
  cwd,
4278
5473
  { current: DEFAULT_TERM_BASELINE_PATH }
4279
5474
  );
4280
- if (request !== null && !existsSync2(request.absPath)) {
5475
+ if (request !== null && !existsSync3(request.absPath)) {
4281
5476
  if (opts.baseline !== true) {
4282
5477
  throw new TermError(
4283
5478
  `Baseline "${request.label}" not found. Record one with \`manni term check --baseline\`.`
@@ -4303,7 +5498,7 @@ async function runCheck3(opts) {
4303
5498
  import { join } from "node:path";
4304
5499
 
4305
5500
  // src/term/core/writers/page.ts
4306
- import { parse as parseYaml, stringify as stringifyYaml2 } from "yaml";
5501
+ import { parse as parseYaml2, stringify as stringifyYaml2 } from "yaml";
4307
5502
  function pageMetadata(term) {
4308
5503
  const { record } = term;
4309
5504
  const metadata = { title: record.label };
@@ -4330,7 +5525,7 @@ ${stringifyYaml2(metadata, { lineWidth: 0 })}---
4330
5525
  function metaContent(value) {
4331
5526
  if (typeof value === "string" && !value.includes("\n")) {
4332
5527
  try {
4333
- if (parseYaml(value) === value) return value;
5528
+ if (parseYaml2(value) === value) return value;
4334
5529
  } catch {
4335
5530
  }
4336
5531
  }
@@ -4661,7 +5856,7 @@ var SCHEME_ID = "";
4661
5856
  function literal(term, value) {
4662
5857
  return term.language === void 0 ? value : { "@value": value, "@language": term.language };
4663
5858
  }
4664
- function resolve4(terms, self, value) {
5859
+ function resolve6(terms, self, value) {
4665
5860
  const wanted = value.toLowerCase();
4666
5861
  const others = terms.filter((t) => t !== self);
4667
5862
  return others.find((t) => t.record.label.toLowerCase() === wanted) ?? others.find((t) => t.id.toLowerCase() === wanted);
@@ -4669,7 +5864,7 @@ function resolve4(terms, self, value) {
4669
5864
  function references(terms, self, values) {
4670
5865
  const ids = [];
4671
5866
  for (const value of values ?? []) {
4672
- const target = resolve4(terms, self, value);
5867
+ const target = resolve6(terms, self, value);
4673
5868
  if (target !== void 0 && !ids.includes(target.id)) ids.push(target.id);
4674
5869
  }
4675
5870
  return ids.map((id) => ({ "@id": `#${id}` }));
@@ -4995,9 +6190,9 @@ var INTERCHANGE_LABELS = {
4995
6190
  };
4996
6191
  function rendered(writers, format, construct) {
4997
6192
  return writers.some((writer) => {
4998
- const render3 = DOCUMENT_RENDERS[writer.format];
4999
- if (render3?.input !== format) return false;
5000
- return writer.shapes.some((shape) => render3.shapes[shape] === construct);
6193
+ const render4 = DOCUMENT_RENDERS[writer.format];
6194
+ if (render4?.input !== format) return false;
6195
+ return writer.shapes.some((shape) => render4.shapes[shape] === construct);
5001
6196
  });
5002
6197
  }
5003
6198
  function listFormats(opts = {}) {
@@ -5066,7 +6261,7 @@ async function runGet(opts) {
5066
6261
  set.terms.flatMap((t) => [t.id, t.record.label])
5067
6262
  );
5068
6263
  const hint = nearest === void 0 ? "." : `; did you mean "${nearest}"?`;
5069
- throw new TermError(`no term "${opts.term}". ${plural3(set.terms.length, "term")}${hint}`);
6264
+ throw new TermError(`no term "${opts.term}". ${plural4(set.terms.length, "term")}${hint}`);
5070
6265
  }
5071
6266
 
5072
6267
  // src/term/core/lint.ts
@@ -5313,15 +6508,15 @@ async function runLint2(opts) {
5313
6508
 
5314
6509
  // src/term/commands/list.ts
5315
6510
  var LIST_FORMATS2 = ["pretty", "json", "csv"];
5316
- async function runList(opts) {
6511
+ async function runList2(opts) {
5317
6512
  const { set } = await loadTerms(opts);
5318
6513
  return { terms: set.terms, set };
5319
6514
  }
5320
6515
 
5321
6516
  // src/term/commands/write.ts
5322
- import { existsSync as existsSync3, statSync } from "node:fs";
6517
+ import { existsSync as existsSync4, statSync } from "node:fs";
5323
6518
  import { lstat, mkdir, readdir, readFile as readFile2, rm as rm2, writeFile as writeFile2 } from "node:fs/promises";
5324
- import { dirname, extname as extname2, join as join4, resolve as resolve5 } from "node:path";
6519
+ import { dirname, extname as extname2, join as join4, resolve as resolve7 } from "node:path";
5325
6520
  var SUGGESTED_SECTION = "*.md";
5326
6521
  var STYLE2 = "Terms";
5327
6522
  function writeFormats(writers = TERM_WRITERS) {
@@ -5355,7 +6550,7 @@ async function existingFor(target) {
5355
6550
  const existing = /* @__PURE__ */ new Map();
5356
6551
  if (target.shape === "directory") {
5357
6552
  await filesUnder(target.path, existing);
5358
- } else if (existsSync3(target.path) && !isDirectory(target.path)) {
6553
+ } else if (existsSync4(target.path) && !isDirectory(target.path)) {
5359
6554
  existing.set(target.path, await readFile2(target.path, "utf8"));
5360
6555
  }
5361
6556
  return existing;
@@ -5375,7 +6570,7 @@ function droppedFrom(format, shape, readers) {
5375
6570
  async function applyChanges(files, removals, existing, cwd, write) {
5376
6571
  const changes = [];
5377
6572
  for (const file of files) {
5378
- const before = existing.get(file.path) ?? (existsSync3(file.path) ? await readFile2(file.path, "utf8") : void 0);
6573
+ const before = existing.get(file.path) ?? (existsSync4(file.path) ? await readFile2(file.path, "utf8") : void 0);
5379
6574
  if (before === file.content) continue;
5380
6575
  changes.push({ path: displayPath(file.path, cwd), action: before === void 0 ? "create" : "change" });
5381
6576
  if (write) {
@@ -5384,14 +6579,14 @@ async function applyChanges(files, removals, existing, cwd, write) {
5384
6579
  }
5385
6580
  }
5386
6581
  for (const path of removals) {
5387
- if (!existsSync3(path)) continue;
6582
+ if (!existsSync4(path)) continue;
5388
6583
  changes.push({ path: displayPath(path, cwd), action: "remove" });
5389
6584
  if (write) await rm2(path, { force: true });
5390
6585
  }
5391
6586
  return changes;
5392
6587
  }
5393
6588
  async function runWrite(opts) {
5394
- const cwd = resolve5(opts.cwd ?? process.cwd());
6589
+ const cwd = resolve7(opts.cwd ?? process.cwd());
5395
6590
  const writers = opts.writers ?? TERM_WRITERS;
5396
6591
  const readers = opts.readers ?? TERM_READERS;
5397
6592
  const check = opts.check === true;
@@ -5419,31 +6614,31 @@ async function runWrite(opts) {
5419
6614
  "Vale found no config file. Set tools.vale.config in manni.config.yaml, or pass -o <styles directory>."
5420
6615
  );
5421
6616
  }
5422
- targetPath = resolve5(cwd, resolved.stylesPath);
6617
+ targetPath = resolve7(cwd, resolved.stylesPath);
5423
6618
  if (!Object.values(resolved.baseStyles).some((styles) => styles.includes(STYLE2))) {
5424
6619
  const first = Object.entries(resolved.baseStyles)[0];
5425
6620
  const [section, styles] = first ?? [SUGGESTED_SECTION, []];
5426
6621
  wiring = {
5427
- rootIni: displayPath(resolve5(cwd, resolved.rootIni), cwd),
6622
+ rootIni: displayPath(resolve7(cwd, resolved.rootIni), cwd),
5428
6623
  section,
5429
6624
  styles: [...styles, STYLE2],
5430
6625
  addSection: first === void 0
5431
6626
  };
5432
6627
  }
5433
6628
  } else {
5434
- targetPath = resolve5(cwd, opts.out);
6629
+ targetPath = resolve7(cwd, opts.out);
5435
6630
  }
5436
6631
  const shape = format === "vale" || /[\\/]$/.test(opts.out ?? "") || isDirectory(targetPath) ? "directory" : "file";
5437
6632
  const target = { path: targetPath, shape };
5438
6633
  const existing = await existingFor(target);
5439
- let render3;
6634
+ let render4;
5440
6635
  try {
5441
- render3 = writer.render(set.terms, target, { existing });
6636
+ render4 = writer.render(set.terms, target, { existing });
5442
6637
  } catch (error) {
5443
6638
  if (error instanceof TermError) throw relativized(error, [...existing.keys(), targetPath], cwd);
5444
6639
  throw error;
5445
6640
  }
5446
- const changes = await applyChanges(render3.files, render3.removals, existing, cwd, !check && !dryRun);
6641
+ const changes = await applyChanges(render4.files, render4.removals, existing, cwd, !check && !dryRun);
5447
6642
  const shown = format === "vale" ? displayPath(join4(targetPath, STYLE2), cwd) : opts.out ?? displayPath(targetPath, cwd);
5448
6643
  return {
5449
6644
  mode: "render",
@@ -5452,11 +6647,11 @@ async function runWrite(opts) {
5452
6647
  target: shown,
5453
6648
  terms: set.terms.length,
5454
6649
  changes,
5455
- files: render3.files.map((file) => ({ path: displayPath(file.path, cwd), content: file.content })),
6650
+ files: render4.files.map((file) => ({ path: displayPath(file.path, cwd), content: file.content })),
5456
6651
  // A Vale style holds designations only, by design; its dropped definitions are not news.
5457
- dropped: format === "vale" ? [] : render3.dropped,
6652
+ dropped: format === "vale" ? [] : render4.dropped,
5458
6653
  droppedFrom: droppedFrom(format, shape, readers),
5459
- skipped: render3.skipped,
6654
+ skipped: render4.skipped,
5460
6655
  check,
5461
6656
  dryRun,
5462
6657
  ...wiring === void 0 ? {} : { wiring }
@@ -5692,7 +6887,7 @@ function renderListPretty(terms) {
5692
6887
  const rows = terms.map(
5693
6888
  (t) => `${t.id.padEnd(idWidth)}${t.record.label.padEnd(labelWidth)}${(t.record["alt-labels"] ?? []).join(", ")}`.trimEnd()
5694
6889
  );
5695
- return [...rows, plural3(terms.length, "term")].join("\n");
6890
+ return [...rows, plural4(terms.length, "term")].join("\n");
5696
6891
  }
5697
6892
  var GET_FIELDS = [
5698
6893
  "alt-labels",
@@ -5733,21 +6928,21 @@ function severityText(c, severity) {
5733
6928
  function counts(findings) {
5734
6929
  const parts = [];
5735
6930
  const n = (severity) => findings.filter((f) => f.severity === severity).length;
5736
- if (n("error") > 0) parts.push(plural3(n("error"), "error"));
5737
- if (n("warning") > 0) parts.push(plural3(n("warning"), "warning"));
5738
- if (n("notice") > 0) parts.push(plural3(n("notice"), "notice"));
6931
+ if (n("error") > 0) parts.push(plural4(n("error"), "error"));
6932
+ if (n("warning") > 0) parts.push(plural4(n("warning"), "warning"));
6933
+ if (n("notice") > 0) parts.push(plural4(n("notice"), "notice"));
5739
6934
  return parts.join(", ");
5740
6935
  }
5741
6936
  function renderFindingsPretty(report, opts) {
5742
6937
  const c = palette(opts.color);
5743
6938
  const { baseline } = report;
5744
6939
  if (baseline?.written === true) {
5745
- return `${c.green("\u2713")} ${plural3(report.found, "finding")} recorded in ${baseline.path}`;
6940
+ return `${c.green("\u2713")} ${plural4(report.found, "finding")} recorded in ${baseline.path}`;
5746
6941
  }
5747
6942
  const baselined = baseline !== void 0 && baseline.suppressed > 0 ? `, ${String(baseline.suppressed)} baselined` : "";
5748
- const terms = plural3(report.terms, "term");
6943
+ const terms = plural4(report.terms, "term");
5749
6944
  if (report.findings.length === 0) {
5750
- const references2 = opts.references === false ? "" : `, ${plural3(report.references, "reference")}`;
6945
+ const references2 = opts.references === false ? "" : `, ${plural4(report.references, "reference")}`;
5751
6946
  return `${c.green("\u2713")} ${terms}${references2}, no findings${baselined}`;
5752
6947
  }
5753
6948
  const ruleWidth = Math.max(RULE_WIDTH, ...report.findings.map((f) => f.ruleId.length + 2));
@@ -5797,11 +6992,11 @@ function valeLines(report) {
5797
6992
  const acronyms = [];
5798
6993
  for (const file of report.files) {
5799
6994
  const name = baseName2(file.path);
5800
- if (CASING_RULES.has(name)) rows.push([name, plural3(swapCount(file.content), "label")]);
5801
- else if (name === "Deprecated.yml") rows.push([name, plural3(swapCount(file.content), "swap")]);
6995
+ if (CASING_RULES.has(name)) rows.push([name, plural4(swapCount(file.content), "label")]);
6996
+ else if (name === "Deprecated.yml") rows.push([name, plural4(swapCount(file.content), "swap")]);
5802
6997
  else acronyms.push(name);
5803
6998
  }
5804
- if (acronyms.length > 0) rows.push([acronyms.join(", "), plural3(acronyms.length, "acronym")]);
6999
+ if (acronyms.length > 0) rows.push([acronyms.join(", "), plural4(acronyms.length, "acronym")]);
5805
7000
  const width = Math.max(0, ...rows.map(([name]) => name.length)) + 2;
5806
7001
  return rows.map(([name, what]) => ` ${name.padEnd(width)}${what}`);
5807
7002
  }
@@ -5814,13 +7009,13 @@ function droppedLine(report) {
5814
7009
  ids.set(field, set);
5815
7010
  }
5816
7011
  const fields = [...ids.entries()].map(([field, set]) => ({ field, n: set.size })).sort((a, b) => a.n - b.n || TERM_FIELDS.indexOf(a.field) - TERM_FIELDS.indexOf(b.field));
5817
- const parts = fields.map(({ field, n }, i) => `${field} on ${i === 0 ? plural3(n, "term") : String(n)}`);
7012
+ const parts = fields.map(({ field, n }, i) => `${field} on ${i === 0 ? plural4(n, "term") : String(n)}`);
5818
7013
  return ` dropped from ${report.droppedFrom ?? report.format ?? "the render"}: ${parts.join(", ")}`;
5819
7014
  }
5820
7015
  function skippedLine(report) {
5821
7016
  if (report.skipped.length === 0) return void 0;
5822
7017
  const construct = report.droppedFrom ?? report.format ?? "the render";
5823
- const count = plural3(report.skipped.length, "term");
7018
+ const count = plural4(report.skipped.length, "term");
5824
7019
  return ` skipped ${count} ${construct} cannot read without a definition: ${report.skipped.join(", ")}`;
5825
7020
  }
5826
7021
  function renderNotes(report) {
@@ -5838,11 +7033,11 @@ function renderWritePretty(report) {
5838
7033
  return [...changes, ...renderNotes(report)].join("\n");
5839
7034
  }
5840
7035
  if (report.mode === "in-place") {
5841
- return report.changes.length === 0 ? "Nothing to write" : `Wrote ${plural3(report.changes.length, "file")}`;
7036
+ return report.changes.length === 0 ? "Nothing to write" : `Wrote ${plural4(report.changes.length, "file")}`;
5842
7037
  }
5843
7038
  const each = report.shape === "directory" && report.format !== "vale" ? ", one file each" : "";
5844
7039
  const written = report.terms - report.skipped.length;
5845
- const lines = [`Wrote ${plural3(written, "term")} to ${report.target ?? ""}${each}`];
7040
+ const lines = [`Wrote ${plural4(written, "term")} to ${report.target ?? ""}${each}`];
5846
7041
  if (report.format === "vale") lines.push(...valeLines(report));
5847
7042
  lines.push(...renderNotes(report));
5848
7043
  return lines.join("\n");
@@ -5877,7 +7072,7 @@ async function termLabels(request) {
5877
7072
 
5878
7073
  // src/term/cli.ts
5879
7074
  registerTermLabelSource(termLabels);
5880
- function colorFor2(options) {
7075
+ function colorFor3(options) {
5881
7076
  return shouldColor({ noColor: !options.color, isTTY: process.stdout.isTTY });
5882
7077
  }
5883
7078
  async function coreOptions(paths, options) {
@@ -5905,7 +7100,7 @@ function renderFindings(report, format, options, references2) {
5905
7100
  let text2;
5906
7101
  switch (format) {
5907
7102
  case "pretty":
5908
- text2 = renderFindingsPretty(report, { color: colorFor2(options), references: references2 });
7103
+ text2 = renderFindingsPretty(report, { color: colorFor3(options), references: references2 });
5909
7104
  break;
5910
7105
  case "json":
5911
7106
  text2 = renderFindingsJson(report);
@@ -5923,8 +7118,8 @@ function renderFindings(report, format, options, references2) {
5923
7118
  if (text2.length > 0 || !OMITTED_WHEN_CLEAN.has(format)) print(text2);
5924
7119
  process.exitCode = report.summary.failed > 0 ? 1 : 0;
5925
7120
  }
5926
- function buildProgram6() {
5927
- const program = new Command5();
7121
+ function buildProgram8() {
7122
+ const program = new Command7();
5928
7123
  program.name("term").description("Check, lint and render a docset's terms and the references into them.").version(package_default.version, "-V, --version").showHelpAfterError("(add --help for usage)").exitOverride();
5929
7124
  withInputs(
5930
7125
  program.command("list").description("List the resolved entries").argument("[paths...]", "files, directories, or globs to read (use - for stdin)")
@@ -5940,7 +7135,7 @@ function buildProgram6() {
5940
7135
  ).action(async (paths, options) => {
5941
7136
  try {
5942
7137
  const format = formatChoice(options.format, LIST_FORMATS2);
5943
- const { terms } = await runList(await coreOptions(paths, options));
7138
+ const { terms } = await runList2(await coreOptions(paths, options));
5944
7139
  print(format === "json" ? renderListJson(terms) : format === "csv" ? renderListCsv(terms) : renderListPretty(terms));
5945
7140
  process.exitCode = 0;
5946
7141
  } catch (err) {
@@ -5956,7 +7151,7 @@ function buildProgram6() {
5956
7151
  try {
5957
7152
  const format = formatChoice(options.format, GET_FORMATS);
5958
7153
  const found = await runGet({ ...await coreOptions(paths, options), term });
5959
- print(format === "json" ? renderGetJson(found) : renderGetPretty(found, { color: colorFor2(options) }));
7154
+ print(format === "json" ? renderGetJson(found) : renderGetPretty(found, { color: colorFor3(options) }));
5960
7155
  process.exitCode = 0;
5961
7156
  } catch (err) {
5962
7157
  fail(err);
@@ -5976,7 +7171,7 @@ function buildProgram6() {
5976
7171
  ).action(async (paths, options) => {
5977
7172
  try {
5978
7173
  const format = formatChoice(options.format, FINDING_FORMATS);
5979
- const report = await runCheck3({
7174
+ const report = await runCheck4({
5980
7175
  ...await coreOptions(paths, options),
5981
7176
  ...options.baseline === true ? { baseline: true } : {}
5982
7177
  });
@@ -6042,12 +7237,12 @@ function buildProgram6() {
6042
7237
  }
6043
7238
 
6044
7239
  // src/cli.ts
6045
- function buildProgram7() {
7240
+ function buildProgram9() {
6046
7241
  const meta = buildProgram().name("meta").description(
6047
7242
  "Validate, read, query and fill document metadata against JSON Schema"
6048
7243
  );
6049
7244
  const metaCommands = new Set(meta.commands.map((c) => c.name()));
6050
- const program = new Command6();
7245
+ const program = new Command8();
6051
7246
  program.name("manni").description("Tools for documentation that is meant to be checked.").version(package_default.version, "-V, --version").showHelpAfterError("(add --help for usage)").exitOverride().configureOutput({
6052
7247
  outputError: (str, write) => {
6053
7248
  write(str);
@@ -6073,20 +7268,30 @@ function buildProgram7() {
6073
7268
  )
6074
7269
  );
6075
7270
  program.addCommand(
6076
- buildProgram5().name("lint").description(
7271
+ buildProgram7().name("lint").description(
6077
7272
  "Validate document structure against doctype templates, routed by a page's type"
6078
7273
  )
6079
7274
  );
6080
7275
  program.addCommand(
6081
- buildProgram6().name("term").description("Check, lint and render a docset's terms and the references into them")
7276
+ buildProgram4().name("docevals").description(
7277
+ "Deterministic and LLM-as-judge evals for documentation pages, driven by frontmatter"
7278
+ )
7279
+ );
7280
+ program.addCommand(
7281
+ buildProgram8().name("term").description("Check, lint and render a docset's terms and the references into them")
7282
+ );
7283
+ program.addCommand(
7284
+ buildProgram6().name("graph").description(
7285
+ "Deterministic knowledge graphs derived from documentation frontmatter and formatting"
7286
+ )
6082
7287
  );
6083
7288
  program.addCommand(
6084
- buildProgram4().name("key").description("Set and rotate the family key that encrypted values are encrypted with.")
7289
+ buildProgram5().name("key").description("Set and rotate the family key that encrypted values are encrypted with.")
6085
7290
  );
6086
7291
  return program;
6087
7292
  }
6088
- runIfMain(import.meta.url, buildProgram7);
7293
+ runIfMain(import.meta.url, buildProgram9);
6089
7294
  export {
6090
- buildProgram7 as buildProgram
7295
+ buildProgram9 as buildProgram
6091
7296
  };
6092
7297
  //# sourceMappingURL=cli.js.map