@hawkeyexl/manni 4.1.0 → 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,37 +1,44 @@
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-JODRT4NF.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,
13
11
  DocevalsError,
14
12
  EXECUTION_GRANTS,
13
+ EXPORT_TARGETS,
14
+ GraphError,
15
15
  INFER_FORMATS,
16
16
  KeyError,
17
17
  LINT_JOBS,
18
18
  LintError,
19
19
  REPORT_FORMATS as REPORT_FORMATS2,
20
20
  RULE_ID_PREFIX,
21
+ SEARCH_MODES,
21
22
  SEVERITIES,
22
23
  SEVERITY_LIST,
24
+ STDIN,
23
25
  SUMMARY_FORMATS,
24
26
  TOOLS_BY_JOB,
25
27
  asCiteError,
28
+ assertInputFormat,
29
+ bundledShapesPath,
26
30
  codeEndAt,
27
31
  codeRegions,
32
+ compactIri,
28
33
  errorSite,
29
34
  findingMessage,
30
35
  isCiteRule,
31
36
  isInferFormat,
32
37
  isSeverity,
33
38
  listReviews,
34
- loadConfig2 as loadConfig,
39
+ loadConfig3 as loadConfig,
40
+ loadGraph,
41
+ loadRunConfig,
35
42
  meetsSeverity,
36
43
  parseFormat,
37
44
  rebaseConfig,
@@ -41,20 +48,30 @@ import {
41
48
  renderCheckGithub,
42
49
  renderCheckJson,
43
50
  renderCheckPretty,
51
+ renderEmbed,
44
52
  renderFill,
53
+ renderFill2,
45
54
  renderList,
55
+ renderQuery,
46
56
  renderRemoveJson,
47
57
  renderRemovePretty,
48
58
  renderReviews,
59
+ renderSearch,
60
+ renderStats,
49
61
  renderTemplates,
50
62
  renderTools,
63
+ renderTraverse,
51
64
  renderUpdateJson,
52
65
  renderUpdatePretty,
53
66
  resolveStructureTool,
54
67
  runAdd,
68
+ runBuild,
55
69
  runCalibrate,
56
70
  runCheck,
71
+ runEmbed,
72
+ runExport,
57
73
  runFill,
74
+ runFill2,
58
75
  runGenerate,
59
76
  runInit,
60
77
  runKeyRotate,
@@ -62,20 +79,25 @@ import {
62
79
  runLint,
63
80
  runList,
64
81
  runPromote,
82
+ runQuery,
65
83
  runRemove,
66
84
  runReview,
67
85
  runRun,
86
+ runSearch,
87
+ runStats,
68
88
  runTemplates,
69
89
  runTemplatesInfer,
70
90
  runTools,
91
+ runTraverse,
71
92
  runUpdate,
72
93
  seedGoldenCases,
73
94
  shortCommit,
74
95
  shortLine,
75
96
  shortPin,
76
97
  shortSrc,
77
- spellSource
78
- } from "./chunk-S3NDEIOW.js";
98
+ spellSource,
99
+ validateGraph
100
+ } from "./chunk-UYDRDFYF.js";
79
101
  import {
80
102
  COMMON_FORMAT_LIST,
81
103
  CONCEPT_FIELDS,
@@ -89,9 +111,12 @@ import {
89
111
  TERM_RELATION_FIELDS,
90
112
  ToolError,
91
113
  assertNonEmpty,
114
+ collect,
115
+ configOption,
92
116
  errorMessage,
93
117
  escapeWorkflowCommandMessage,
94
118
  escapeWorkflowCommandProperty,
119
+ explicitFalse,
95
120
  extractorByName,
96
121
  extractorForExtension,
97
122
  fail,
@@ -108,23 +133,28 @@ import {
108
133
  palette,
109
134
  programName,
110
135
  readConfigFile,
136
+ readStdin,
111
137
  registerTermLabelSource,
112
138
  renderJunit,
113
139
  renderSarif,
140
+ reportConfig,
114
141
  resolveBaselineRequest,
115
142
  resolveTargetSet,
116
143
  runIfMain,
117
144
  selectCollections,
118
145
  settleBaseline,
119
146
  shouldColor,
147
+ splitList,
120
148
  splitPageArgument,
121
149
  terminalConfirm,
122
150
  valeConfigPath,
123
- valueAt
124
- } from "./chunk-I57KBF45.js";
151
+ valueAt,
152
+ warn,
153
+ writeTextAtomic
154
+ } from "./chunk-TH565O2N.js";
125
155
 
126
156
  // src/cli.ts
127
- import { Command as Command7 } from "commander";
157
+ import { Command as Command8 } from "commander";
128
158
 
129
159
  // src/a11y/cli.ts
130
160
  import { Command } from "commander";
@@ -224,17 +254,19 @@ async function crawl(opts, analyzer) {
224
254
  url = normalizeUrl(raw);
225
255
  key = dedupeKey(url);
226
256
  } catch {
227
- return;
257
+ return { as: "invalid" };
228
258
  }
229
- if (source !== "seed" && !isPageLink(url)) return;
230
- if (!opts.seeds.some((seed) => sameHost(seed, url))) return;
231
- 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) {
232
263
  excluded.add(key);
233
- return;
264
+ return { as: "excluded", pattern };
234
265
  }
235
- if (seen.has(key)) return;
266
+ if (seen.has(key)) return { as: "seen" };
236
267
  seen.add(key);
237
268
  frontier.push({ url, key, source });
269
+ return { as: "queued" };
238
270
  };
239
271
  const landed = (finalUrl) => {
240
272
  if (finalUrl === void 0) return;
@@ -251,6 +283,7 @@ async function crawl(opts, analyzer) {
251
283
  progress({ kind: "excluded", urls: excluded.size, patterns: opts.exclude?.patterns.length ?? 0 });
252
284
  }
253
285
  const pages = [];
286
+ const redirects = [];
254
287
  let duplicates = 0;
255
288
  const capped = () => opts.maxPages !== void 0 && pages.length >= opts.maxPages;
256
289
  for (let next = 0; next < frontier.length && !capped(); next++) {
@@ -263,10 +296,28 @@ async function crawl(opts, analyzer) {
263
296
  }
264
297
  visited.add(key);
265
298
  const index = pages.length + 1;
266
- if (index === 1) progress({ kind: "browser" });
299
+ if (pages.length + redirects.length === 0) progress({ kind: "browser" });
267
300
  progress({ kind: "page", index, queued: frontier.length, url });
268
301
  try {
269
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
+ }
270
321
  pages.push({ ...analyzed.result, url, source });
271
322
  landed(analyzed.finalUrl);
272
323
  if (opts.crawl) for (const link of analyzed.links) enqueue(link, "link");
@@ -278,9 +329,23 @@ async function crawl(opts, analyzer) {
278
329
  progress({ kind: "checked", index, url, violations: 0, error });
279
330
  }
280
331
  }
281
- const skipped2 = frontier.length - pages.length - duplicates;
332
+ const skipped2 = frontier.length - pages.length - duplicates - redirects.length;
282
333
  progress({ kind: "done", checked: pages.length, skipped: skipped2 });
283
- 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
+ }
284
349
  }
285
350
 
286
351
  // src/a11y/core/exclude.ts
@@ -522,6 +587,7 @@ async function runCheck2(opts, deps) {
522
587
  const results = outcome2.pages.map((page) => finishPage(page, opts.severity));
523
588
  return {
524
589
  results,
590
+ redirects: outcome2.redirects,
525
591
  summary: summarize(results, outcome2, sitemap, opts.crawl)
526
592
  };
527
593
  } finally {
@@ -535,7 +601,7 @@ function finishPage(page, floor) {
535
601
  const score = applicable === 0 ? null : Math.round(100 * page.passes / applicable);
536
602
  return { ...page, violations, score };
537
603
  }
538
- function summarize(results, { discovered, skipped: skipped2, duplicates, excluded }, sitemap, crawl2) {
604
+ function summarize(results, { discovered, skipped: skipped2, duplicates, redirects, excluded }, sitemap, crawl2) {
539
605
  const bySeverity = { notice: 0, warning: 0, error: 0 };
540
606
  let failed = 0;
541
607
  let violations = 0;
@@ -551,6 +617,7 @@ function summarize(results, { discovered, skipped: skipped2, duplicates, exclude
551
617
  checked: results.length,
552
618
  skipped: skipped2,
553
619
  duplicates,
620
+ redirects: redirects.length,
554
621
  excluded,
555
622
  failed,
556
623
  violations,
@@ -595,33 +662,18 @@ function createPlaywrightAnalyzer() {
595
662
  return {
596
663
  async analyze(url, opts) {
597
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);
598
672
  try {
599
- await page.goto(url, { waitUntil: "load", timeout: opts.timeout });
600
- } catch (err) {
601
- throw new A11yError(`Could not load ${url}: ${reason(err)}`);
602
- }
603
- const links = await page.$$eval(
604
- "a[href]",
605
- (anchors) => anchors.map((a) => a.href)
606
- );
607
- let builder = new AxeBuilder({ page });
608
- if (opts.tags.length > 0) builder = builder.withTags(opts.tags);
609
- let results;
610
- try {
611
- results = await builder.analyze();
612
- } catch (err) {
613
- throw new A11yError(`Could not analyze ${url}: ${reason(err)}`);
673
+ return await analyzePage(page, url, opts, navigations);
674
+ } finally {
675
+ page.off("request", onRequest);
614
676
  }
615
- return {
616
- result: {
617
- url,
618
- violations: results.violations.map(toViolation),
619
- passes: results.passes.length,
620
- incomplete: results.incomplete.length
621
- },
622
- links,
623
- finalUrl: page.url()
624
- };
625
677
  },
626
678
  async close() {
627
679
  if (session === null) return;
@@ -635,6 +687,103 @@ function createPlaywrightAnalyzer() {
635
687
  }
636
688
  };
637
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
+ }
638
787
  function severityOf(impact) {
639
788
  switch (impact) {
640
789
  case "critical":
@@ -819,8 +968,13 @@ function renderPretty(run, opts) {
819
968
  );
820
969
  for (const v of page.violations) lines.push(...violationLines(v, c));
821
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
+ }
822
976
  const failed = summary.failed;
823
- 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` : "");
824
978
  lines.push("");
825
979
  lines.push(failed > 0 ? c.red(footer) : c.green(footer));
826
980
  return lines.join("\n");
@@ -939,6 +1093,9 @@ function terminalReporter(opts) {
939
1093
  case "checked":
940
1094
  showUrl(`${c.dim(counter(event.index, queued))} ${outcome(event, c)}`, event.url);
941
1095
  return;
1096
+ case "redirected":
1097
+ showUrl(`${c.dim(counter(event.index, queued))} ${c.dim("\u21AA")} redirects to `, event.to);
1098
+ return;
942
1099
  case "done":
943
1100
  stream.write(CLEAR_LINE);
944
1101
  return;
@@ -979,6 +1136,9 @@ function plainReporter(stream) {
979
1136
  else say(`${head} \u2717 ${plural(event.violations, "violation")}`);
980
1137
  return;
981
1138
  }
1139
+ case "redirected":
1140
+ say(`${counter(event.index, queued)} \u21AA redirects to ${event.to}`);
1141
+ return;
982
1142
  case "done":
983
1143
  say(`checked ${plural(event.checked, "page")}, ${event.skipped} skipped`);
984
1144
  return;
@@ -2192,8 +2352,616 @@ function buildProgram5() {
2192
2352
  return program;
2193
2353
  }
2194
2354
 
2195
- // src/lint/cli.ts
2355
+ // src/graph/cli.ts
2196
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
+
2963
+ // src/lint/cli.ts
2964
+ import { Command as Command6 } from "commander";
2197
2965
  var LIST_FORMATS = /* @__PURE__ */ new Set(["pretty", "json"]);
2198
2966
  var TOOL_OPTIONS_GROUP = "Tool options:";
2199
2967
  function resolveColor2(program) {
@@ -2271,8 +3039,8 @@ function inputOptions(paths, options) {
2271
3039
  onNotice: notice
2272
3040
  };
2273
3041
  }
2274
- function buildProgram6() {
2275
- const program = new Command5();
3042
+ function buildProgram7() {
3043
+ const program = new Command6();
2276
3044
  program.name("lint").description(
2277
3045
  "Validate document structure against doctype templates. Deterministic, CI-friendly."
2278
3046
  ).version(package_default.version, "-V, --version").option("--no-color", "disable colored output").showHelpAfterError().exitOverride();
@@ -2471,13 +3239,13 @@ function buildProgram6() {
2471
3239
  }
2472
3240
 
2473
3241
  // src/term/cli.ts
2474
- import { Command as Command6 } from "commander";
3242
+ import { Command as Command7 } from "commander";
2475
3243
 
2476
3244
  // src/term/commands/check.ts
2477
- import { existsSync as existsSync2 } from "node:fs";
3245
+ import { existsSync as existsSync3 } from "node:fs";
2478
3246
 
2479
3247
  // src/term/core/config.ts
2480
- import { resolve } from "node:path";
3248
+ import { resolve as resolve3 } from "node:path";
2481
3249
 
2482
3250
  // src/term/errors.ts
2483
3251
  var TermError = class extends ToolError {
@@ -2657,11 +3425,11 @@ function toLoaded(file) {
2657
3425
  tools: file.tools
2658
3426
  };
2659
3427
  }
2660
- var STDIN = "-";
3428
+ var STDIN2 = "-";
2661
3429
  async function resolveTermRun(opts) {
2662
- const cwd = resolve(opts.cwd);
3430
+ const cwd = resolve3(opts.cwd);
2663
3431
  const wanted = opts.collection ?? [];
2664
- if (wanted.length > 0 && opts.inputs.some((input) => input !== STDIN)) {
3432
+ if (wanted.length > 0 && opts.inputs.some((input) => input !== STDIN2)) {
2665
3433
  throw new TermError(
2666
3434
  "--collection selects a configured collection; it cannot be combined with paths."
2667
3435
  );
@@ -2676,7 +3444,7 @@ async function resolveTermRun(opts) {
2676
3444
  const fromCollections = opts.inputs.length === 0;
2677
3445
  const inputs = fromCollections ? collections.flatMap((c) => c.paths) : opts.inputs;
2678
3446
  const base = fromCollections && inputs.length > 0 && loaded ? loaded.dir : cwd;
2679
- 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 })) : [];
2680
3448
  return {
2681
3449
  config: loaded ? loaded.config : null,
2682
3450
  inputs,
@@ -2993,12 +3761,12 @@ function compareFindings2(a, b) {
2993
3761
  }
2994
3762
 
2995
3763
  // src/term/commands/run.ts
2996
- import { existsSync } from "node:fs";
2997
- 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";
2998
3766
 
2999
3767
  // src/term/core/load-set.ts
3000
3768
  import { readFile } from "node:fs/promises";
3001
- import { extname, resolve as resolve2 } from "node:path";
3769
+ import { extname, resolve as resolve4 } from "node:path";
3002
3770
 
3003
3771
  // src/term/core/writers/markup.ts
3004
3772
  function xmlText(value) {
@@ -4580,7 +5348,7 @@ async function loadTermSet(opts) {
4580
5348
  });
4581
5349
  const namedFiles = new Set(named);
4582
5350
  for (const file of files) {
4583
- const path = resolve2(run.base, file);
5351
+ const path = resolve4(run.base, file);
4584
5352
  if (namedFiles.has(file) && MANIFEST_EXTENSIONS.has(extname(file).toLowerCase())) {
4585
5353
  let content2;
4586
5354
  try {
@@ -4631,7 +5399,7 @@ async function loadTermSet(opts) {
4631
5399
 
4632
5400
  // src/term/commands/run.ts
4633
5401
  async function loadTerms(opts) {
4634
- const cwd = resolve3(opts.cwd ?? process.cwd());
5402
+ const cwd = resolve5(opts.cwd ?? process.cwd());
4635
5403
  const run = await resolveTermRun({
4636
5404
  cwd,
4637
5405
  inputs: opts.inputs,
@@ -4677,18 +5445,18 @@ function displayPath(path, cwd) {
4677
5445
  function valeConfigFor(run) {
4678
5446
  if (run.configDir === void 0) return void 0;
4679
5447
  const path = valeConfigPath(run.tools, run.configDir);
4680
- if (path !== void 0 && !existsSync(path)) {
5448
+ if (path !== void 0 && !existsSync2(path)) {
4681
5449
  const source = run.configSource ?? "manni.config.yaml";
4682
5450
  throw new TermError(`${source}: tools.vale.config "${run.tools.vale?.config ?? path}" does not exist.`);
4683
5451
  }
4684
5452
  return path;
4685
5453
  }
4686
- function plural3(n, one, many = `${one}s`) {
5454
+ function plural4(n, one, many = `${one}s`) {
4687
5455
  return `${String(n)} ${n === 1 ? one : many}`;
4688
5456
  }
4689
5457
 
4690
5458
  // src/term/commands/check.ts
4691
- async function runCheck3(opts) {
5459
+ async function runCheck4(opts) {
4692
5460
  const { cwd, run, set } = await loadTerms(opts);
4693
5461
  const config = run.config;
4694
5462
  const findings = checkTermSet(set, {
@@ -4704,7 +5472,7 @@ async function runCheck3(opts) {
4704
5472
  cwd,
4705
5473
  { current: DEFAULT_TERM_BASELINE_PATH }
4706
5474
  );
4707
- if (request !== null && !existsSync2(request.absPath)) {
5475
+ if (request !== null && !existsSync3(request.absPath)) {
4708
5476
  if (opts.baseline !== true) {
4709
5477
  throw new TermError(
4710
5478
  `Baseline "${request.label}" not found. Record one with \`manni term check --baseline\`.`
@@ -4730,7 +5498,7 @@ async function runCheck3(opts) {
4730
5498
  import { join } from "node:path";
4731
5499
 
4732
5500
  // src/term/core/writers/page.ts
4733
- import { parse as parseYaml, stringify as stringifyYaml2 } from "yaml";
5501
+ import { parse as parseYaml2, stringify as stringifyYaml2 } from "yaml";
4734
5502
  function pageMetadata(term) {
4735
5503
  const { record } = term;
4736
5504
  const metadata = { title: record.label };
@@ -4757,7 +5525,7 @@ ${stringifyYaml2(metadata, { lineWidth: 0 })}---
4757
5525
  function metaContent(value) {
4758
5526
  if (typeof value === "string" && !value.includes("\n")) {
4759
5527
  try {
4760
- if (parseYaml(value) === value) return value;
5528
+ if (parseYaml2(value) === value) return value;
4761
5529
  } catch {
4762
5530
  }
4763
5531
  }
@@ -5088,7 +5856,7 @@ var SCHEME_ID = "";
5088
5856
  function literal(term, value) {
5089
5857
  return term.language === void 0 ? value : { "@value": value, "@language": term.language };
5090
5858
  }
5091
- function resolve4(terms, self, value) {
5859
+ function resolve6(terms, self, value) {
5092
5860
  const wanted = value.toLowerCase();
5093
5861
  const others = terms.filter((t) => t !== self);
5094
5862
  return others.find((t) => t.record.label.toLowerCase() === wanted) ?? others.find((t) => t.id.toLowerCase() === wanted);
@@ -5096,7 +5864,7 @@ function resolve4(terms, self, value) {
5096
5864
  function references(terms, self, values) {
5097
5865
  const ids = [];
5098
5866
  for (const value of values ?? []) {
5099
- const target = resolve4(terms, self, value);
5867
+ const target = resolve6(terms, self, value);
5100
5868
  if (target !== void 0 && !ids.includes(target.id)) ids.push(target.id);
5101
5869
  }
5102
5870
  return ids.map((id) => ({ "@id": `#${id}` }));
@@ -5493,7 +6261,7 @@ async function runGet(opts) {
5493
6261
  set.terms.flatMap((t) => [t.id, t.record.label])
5494
6262
  );
5495
6263
  const hint = nearest === void 0 ? "." : `; did you mean "${nearest}"?`;
5496
- 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}`);
5497
6265
  }
5498
6266
 
5499
6267
  // src/term/core/lint.ts
@@ -5746,9 +6514,9 @@ async function runList2(opts) {
5746
6514
  }
5747
6515
 
5748
6516
  // src/term/commands/write.ts
5749
- import { existsSync as existsSync3, statSync } from "node:fs";
6517
+ import { existsSync as existsSync4, statSync } from "node:fs";
5750
6518
  import { lstat, mkdir, readdir, readFile as readFile2, rm as rm2, writeFile as writeFile2 } from "node:fs/promises";
5751
- 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";
5752
6520
  var SUGGESTED_SECTION = "*.md";
5753
6521
  var STYLE2 = "Terms";
5754
6522
  function writeFormats(writers = TERM_WRITERS) {
@@ -5782,7 +6550,7 @@ async function existingFor(target) {
5782
6550
  const existing = /* @__PURE__ */ new Map();
5783
6551
  if (target.shape === "directory") {
5784
6552
  await filesUnder(target.path, existing);
5785
- } else if (existsSync3(target.path) && !isDirectory(target.path)) {
6553
+ } else if (existsSync4(target.path) && !isDirectory(target.path)) {
5786
6554
  existing.set(target.path, await readFile2(target.path, "utf8"));
5787
6555
  }
5788
6556
  return existing;
@@ -5802,7 +6570,7 @@ function droppedFrom(format, shape, readers) {
5802
6570
  async function applyChanges(files, removals, existing, cwd, write) {
5803
6571
  const changes = [];
5804
6572
  for (const file of files) {
5805
- 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);
5806
6574
  if (before === file.content) continue;
5807
6575
  changes.push({ path: displayPath(file.path, cwd), action: before === void 0 ? "create" : "change" });
5808
6576
  if (write) {
@@ -5811,14 +6579,14 @@ async function applyChanges(files, removals, existing, cwd, write) {
5811
6579
  }
5812
6580
  }
5813
6581
  for (const path of removals) {
5814
- if (!existsSync3(path)) continue;
6582
+ if (!existsSync4(path)) continue;
5815
6583
  changes.push({ path: displayPath(path, cwd), action: "remove" });
5816
6584
  if (write) await rm2(path, { force: true });
5817
6585
  }
5818
6586
  return changes;
5819
6587
  }
5820
6588
  async function runWrite(opts) {
5821
- const cwd = resolve5(opts.cwd ?? process.cwd());
6589
+ const cwd = resolve7(opts.cwd ?? process.cwd());
5822
6590
  const writers = opts.writers ?? TERM_WRITERS;
5823
6591
  const readers = opts.readers ?? TERM_READERS;
5824
6592
  const check = opts.check === true;
@@ -5846,19 +6614,19 @@ async function runWrite(opts) {
5846
6614
  "Vale found no config file. Set tools.vale.config in manni.config.yaml, or pass -o <styles directory>."
5847
6615
  );
5848
6616
  }
5849
- targetPath = resolve5(cwd, resolved.stylesPath);
6617
+ targetPath = resolve7(cwd, resolved.stylesPath);
5850
6618
  if (!Object.values(resolved.baseStyles).some((styles) => styles.includes(STYLE2))) {
5851
6619
  const first = Object.entries(resolved.baseStyles)[0];
5852
6620
  const [section, styles] = first ?? [SUGGESTED_SECTION, []];
5853
6621
  wiring = {
5854
- rootIni: displayPath(resolve5(cwd, resolved.rootIni), cwd),
6622
+ rootIni: displayPath(resolve7(cwd, resolved.rootIni), cwd),
5855
6623
  section,
5856
6624
  styles: [...styles, STYLE2],
5857
6625
  addSection: first === void 0
5858
6626
  };
5859
6627
  }
5860
6628
  } else {
5861
- targetPath = resolve5(cwd, opts.out);
6629
+ targetPath = resolve7(cwd, opts.out);
5862
6630
  }
5863
6631
  const shape = format === "vale" || /[\\/]$/.test(opts.out ?? "") || isDirectory(targetPath) ? "directory" : "file";
5864
6632
  const target = { path: targetPath, shape };
@@ -6119,7 +6887,7 @@ function renderListPretty(terms) {
6119
6887
  const rows = terms.map(
6120
6888
  (t) => `${t.id.padEnd(idWidth)}${t.record.label.padEnd(labelWidth)}${(t.record["alt-labels"] ?? []).join(", ")}`.trimEnd()
6121
6889
  );
6122
- return [...rows, plural3(terms.length, "term")].join("\n");
6890
+ return [...rows, plural4(terms.length, "term")].join("\n");
6123
6891
  }
6124
6892
  var GET_FIELDS = [
6125
6893
  "alt-labels",
@@ -6160,21 +6928,21 @@ function severityText(c, severity) {
6160
6928
  function counts(findings) {
6161
6929
  const parts = [];
6162
6930
  const n = (severity) => findings.filter((f) => f.severity === severity).length;
6163
- if (n("error") > 0) parts.push(plural3(n("error"), "error"));
6164
- if (n("warning") > 0) parts.push(plural3(n("warning"), "warning"));
6165
- 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"));
6166
6934
  return parts.join(", ");
6167
6935
  }
6168
6936
  function renderFindingsPretty(report, opts) {
6169
6937
  const c = palette(opts.color);
6170
6938
  const { baseline } = report;
6171
6939
  if (baseline?.written === true) {
6172
- 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}`;
6173
6941
  }
6174
6942
  const baselined = baseline !== void 0 && baseline.suppressed > 0 ? `, ${String(baseline.suppressed)} baselined` : "";
6175
- const terms = plural3(report.terms, "term");
6943
+ const terms = plural4(report.terms, "term");
6176
6944
  if (report.findings.length === 0) {
6177
- const references2 = opts.references === false ? "" : `, ${plural3(report.references, "reference")}`;
6945
+ const references2 = opts.references === false ? "" : `, ${plural4(report.references, "reference")}`;
6178
6946
  return `${c.green("\u2713")} ${terms}${references2}, no findings${baselined}`;
6179
6947
  }
6180
6948
  const ruleWidth = Math.max(RULE_WIDTH, ...report.findings.map((f) => f.ruleId.length + 2));
@@ -6224,11 +6992,11 @@ function valeLines(report) {
6224
6992
  const acronyms = [];
6225
6993
  for (const file of report.files) {
6226
6994
  const name = baseName2(file.path);
6227
- if (CASING_RULES.has(name)) rows.push([name, plural3(swapCount(file.content), "label")]);
6228
- 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")]);
6229
6997
  else acronyms.push(name);
6230
6998
  }
6231
- 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")]);
6232
7000
  const width = Math.max(0, ...rows.map(([name]) => name.length)) + 2;
6233
7001
  return rows.map(([name, what]) => ` ${name.padEnd(width)}${what}`);
6234
7002
  }
@@ -6241,13 +7009,13 @@ function droppedLine(report) {
6241
7009
  ids.set(field, set);
6242
7010
  }
6243
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));
6244
- 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)}`);
6245
7013
  return ` dropped from ${report.droppedFrom ?? report.format ?? "the render"}: ${parts.join(", ")}`;
6246
7014
  }
6247
7015
  function skippedLine(report) {
6248
7016
  if (report.skipped.length === 0) return void 0;
6249
7017
  const construct = report.droppedFrom ?? report.format ?? "the render";
6250
- const count = plural3(report.skipped.length, "term");
7018
+ const count = plural4(report.skipped.length, "term");
6251
7019
  return ` skipped ${count} ${construct} cannot read without a definition: ${report.skipped.join(", ")}`;
6252
7020
  }
6253
7021
  function renderNotes(report) {
@@ -6265,11 +7033,11 @@ function renderWritePretty(report) {
6265
7033
  return [...changes, ...renderNotes(report)].join("\n");
6266
7034
  }
6267
7035
  if (report.mode === "in-place") {
6268
- 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")}`;
6269
7037
  }
6270
7038
  const each = report.shape === "directory" && report.format !== "vale" ? ", one file each" : "";
6271
7039
  const written = report.terms - report.skipped.length;
6272
- const lines = [`Wrote ${plural3(written, "term")} to ${report.target ?? ""}${each}`];
7040
+ const lines = [`Wrote ${plural4(written, "term")} to ${report.target ?? ""}${each}`];
6273
7041
  if (report.format === "vale") lines.push(...valeLines(report));
6274
7042
  lines.push(...renderNotes(report));
6275
7043
  return lines.join("\n");
@@ -6350,8 +7118,8 @@ function renderFindings(report, format, options, references2) {
6350
7118
  if (text2.length > 0 || !OMITTED_WHEN_CLEAN.has(format)) print(text2);
6351
7119
  process.exitCode = report.summary.failed > 0 ? 1 : 0;
6352
7120
  }
6353
- function buildProgram7() {
6354
- const program = new Command6();
7121
+ function buildProgram8() {
7122
+ const program = new Command7();
6355
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();
6356
7124
  withInputs(
6357
7125
  program.command("list").description("List the resolved entries").argument("[paths...]", "files, directories, or globs to read (use - for stdin)")
@@ -6403,7 +7171,7 @@ function buildProgram7() {
6403
7171
  ).action(async (paths, options) => {
6404
7172
  try {
6405
7173
  const format = formatChoice(options.format, FINDING_FORMATS);
6406
- const report = await runCheck3({
7174
+ const report = await runCheck4({
6407
7175
  ...await coreOptions(paths, options),
6408
7176
  ...options.baseline === true ? { baseline: true } : {}
6409
7177
  });
@@ -6469,12 +7237,12 @@ function buildProgram7() {
6469
7237
  }
6470
7238
 
6471
7239
  // src/cli.ts
6472
- function buildProgram8() {
7240
+ function buildProgram9() {
6473
7241
  const meta = buildProgram().name("meta").description(
6474
7242
  "Validate, read, query and fill document metadata against JSON Schema"
6475
7243
  );
6476
7244
  const metaCommands = new Set(meta.commands.map((c) => c.name()));
6477
- const program = new Command7();
7245
+ const program = new Command8();
6478
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({
6479
7247
  outputError: (str, write) => {
6480
7248
  write(str);
@@ -6500,7 +7268,7 @@ function buildProgram8() {
6500
7268
  )
6501
7269
  );
6502
7270
  program.addCommand(
6503
- buildProgram6().name("lint").description(
7271
+ buildProgram7().name("lint").description(
6504
7272
  "Validate document structure against doctype templates, routed by a page's type"
6505
7273
  )
6506
7274
  );
@@ -6510,15 +7278,20 @@ function buildProgram8() {
6510
7278
  )
6511
7279
  );
6512
7280
  program.addCommand(
6513
- buildProgram7().name("term").description("Check, lint and render a docset's terms and the references into them")
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
+ )
6514
7287
  );
6515
7288
  program.addCommand(
6516
7289
  buildProgram5().name("key").description("Set and rotate the family key that encrypted values are encrypted with.")
6517
7290
  );
6518
7291
  return program;
6519
7292
  }
6520
- runIfMain(import.meta.url, buildProgram8);
7293
+ runIfMain(import.meta.url, buildProgram9);
6521
7294
  export {
6522
- buildProgram8 as buildProgram
7295
+ buildProgram9 as buildProgram
6523
7296
  };
6524
7297
  //# sourceMappingURL=cli.js.map