@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.
@@ -8,7 +8,7 @@ var __export = (target, all) => {
8
8
  // package.json
9
9
  var package_default = {
10
10
  name: "@hawkeyexl/manni",
11
- version: "4.1.0",
11
+ version: "4.2.0",
12
12
  description: "One command line for documentation that is meant to be checked. `manni meta` validates the presence and format of document metadata (Markdown frontmatter and more) against JSON Schema, built for CI. Formerly docmeta.",
13
13
  type: "module",
14
14
  bin: {
@@ -23,11 +23,20 @@ var package_default = {
23
23
  types: "./dist/index.d.ts",
24
24
  import: "./dist/index.js"
25
25
  },
26
+ "./graph/runtime": {
27
+ types: "./dist/graph/runtime.d.ts",
28
+ import: "./dist/graph/runtime.js"
29
+ },
30
+ "./graph/embed": {
31
+ types: "./dist/graph/embed.d.ts",
32
+ import: "./dist/graph/embed.js"
33
+ },
26
34
  "./package.json": "./package.json"
27
35
  },
28
36
  files: [
29
37
  "dist",
30
38
  "schemas",
39
+ "shapes",
31
40
  "templates"
32
41
  ],
33
42
  repository: {
@@ -46,11 +55,12 @@ var package_default = {
46
55
  },
47
56
  scripts: {
48
57
  prebuild: "npm run check:deps",
49
- build: "tsup",
50
- dev: "tsup --watch",
58
+ build: "node scripts/clean-dist.mjs && tsup",
59
+ dev: "node scripts/clean-dist.mjs && tsup --watch",
51
60
  pretest: "npm run check:deps",
52
61
  test: "vitest run",
53
62
  "test:watch": "vitest",
63
+ "test:graph:real": "vitest run --config vitest.graph-real.config.ts",
54
64
  "test:coverage": "vitest run --coverage",
55
65
  pretypecheck: "npm run check:deps",
56
66
  typecheck: "tsc --noEmit",
@@ -64,6 +74,7 @@ var package_default = {
64
74
  "docs:check-api": "node scripts/check-api-reference.mjs",
65
75
  "docs:check-links": "node scripts/check-doc-links.mjs",
66
76
  "docs:check-docevals": "node dist/cli.js docevals run --deterministic-only --no-generate",
77
+ "docs:check-graph": "node scripts/check-docs-graph.mjs",
67
78
  "docs:check-tables": "node scripts/check-table-rows.mjs",
68
79
  "docs:check-versions": "node scripts/check-versions.mjs",
69
80
  "docs:sync-versions": "node scripts/sync-versions.mjs",
@@ -94,6 +105,7 @@ var package_default = {
94
105
  "@asciidoctor/core": "^3.0.4",
95
106
  "@axe-core/playwright": "^4.13.0",
96
107
  "@hawkeyexl/inference": "^0.4.0",
108
+ "@types/n3": "^1.26.3",
97
109
  "@xmldom/xmldom": "^0.9.12",
98
110
  ajv: "^8.17.1",
99
111
  "ajv-draft-04": "^1.0.0",
@@ -102,10 +114,13 @@ var package_default = {
102
114
  "fast-glob": "^3.3.2",
103
115
  "github-slugger": "^2.0.0",
104
116
  "mdast-util-to-string": "^4.0.0",
117
+ minisearch: "^7.2.0",
118
+ n3: "^1.26.0",
105
119
  parse5: "^8.0.1",
106
120
  picocolors: "^1.1.1",
107
121
  picomatch: "^4.0.7",
108
122
  "playwright-core": "^1.63.0",
123
+ "rdf-validate-shacl": "^0.6.5",
109
124
  "remark-frontmatter": "^5.0.0",
110
125
  "remark-gfm": "^4.0.1",
111
126
  "remark-mdx": "^3.1.1",
@@ -118,31 +133,42 @@ var package_default = {
118
133
  devDependencies: {
119
134
  "@commitlint/cli": "^21.2.2",
120
135
  "@commitlint/config-conventional": "^21.2.2",
136
+ "@comunica/query-sparql-rdfjs-lite": "^5.3.0",
121
137
  "@eslint/js": "^10.0.1",
138
+ "@rdfjs/types": "^2.0.1",
122
139
  "@semantic-release/changelog": "^6.0.3",
123
140
  "@semantic-release/commit-analyzer": "^13.0.1",
124
141
  "@semantic-release/git": "^10.0.1",
125
142
  "@semantic-release/github": "^12.0.8",
126
143
  "@semantic-release/npm": "^13.1.5",
127
144
  "@semantic-release/release-notes-generator": "^14.1.1",
145
+ "@types/jsonld": "^1.5.15",
128
146
  "@types/mdast": "^4.0.4",
129
147
  "@types/node": "^26.3.0",
130
148
  "@types/picomatch": "^4.0.3",
131
149
  "@types/unist": "^3.0.3",
150
+ "@types/yauzl": "^3.4.0",
132
151
  "@vitest/coverage-v8": "^4.1.11",
133
152
  eslint: "^10.9.1",
134
153
  globals: "^17.11.0",
135
154
  husky: "^9.1.7",
155
+ jsonld: "^9.0.0",
156
+ "rdfxml-streaming-parser": "^3.3.0",
136
157
  "semantic-release": "^25.0.9",
137
158
  tsup: "^8.3.5",
138
159
  typescript: "^6.0.3",
139
160
  "typescript-eslint": "^8.68.0",
140
- vitest: "^4.1.11"
161
+ vitest: "^4.1.11",
162
+ yauzl: "^3.4.0"
141
163
  },
142
164
  peerDependencies: {
165
+ "@huggingface/transformers": ">=4",
143
166
  "node-llama-cpp": "^3.19.0"
144
167
  },
145
168
  peerDependenciesMeta: {
169
+ "@huggingface/transformers": {
170
+ optional: true
171
+ },
146
172
  "node-llama-cpp": {
147
173
  optional: true
148
174
  }
@@ -8353,6 +8379,251 @@ var __default47 = {
8353
8379
  }
8354
8380
  };
8355
8381
 
8382
+ // src/meta/schemas/stewardship/1.1.0.json
8383
+ var __default48 = {
8384
+ $schema: "https://json-schema.org/draft/2020-12/schema",
8385
+ $id: "manni:stewardship:1.1.0",
8386
+ title: "manni stewardship vocabulary v1.1.0",
8387
+ description: "Is this page cared for? This vocabulary covers attribution, ownership, review, the document's own dates, and the anchors that make drift detectable. Every field records a fact a person asserts about the document, not one a tool reads off the file. That is why `created`, `last-updated` and `authors` are here. Git records when a path changed and who committed the change, not when the document was written or who wrote it. Those part company at every migration, split, squashed import, typo fix and bulk frontmatter sweep. A published page carries no repo, so a stamped date travels with the document where a derived one cannot. The cost is that a hand-typed `last-updated` goes stale quietly, and no JSON Schema can catch that. The review dates are records, not freshness gates. Tooling that can read a clock derives the due date from `last-reviewed` and `review-interval`. Version 1.1.0 adds `x-manni-graph-output: false` to `owner`, `stakeholders` and `reviewed-by`. Those three name people, so a published graph leaves them out: its Turtle, its JSON-LD, its iiRDS package and its search index. Validation is the same as in 1.0.0.",
8388
+ type: "object",
8389
+ additionalProperties: true,
8390
+ properties: {
8391
+ authors: {
8392
+ type: [
8393
+ "string",
8394
+ "object",
8395
+ "array"
8396
+ ],
8397
+ description: "Who wrote the page: a name, a list of names, or the structured person objects MyST and Docusaurus define. Attribution rather than answerability \u2014 `owner` is the party on the hook for the page now, and the two part company the moment an author moves on. Not a `stringList`, and not the only field here that isn't: `verified-against` and `source-of-truth` take the same string-or-object-or-list shape, as `entryList`. `authors` keeps its own inline copy rather than sharing that definition, because MyST and Docusaurus claim this key too and the composability law holds it at their loosest published definition, which `entryList`'s uniqueness and member floors go past. List members are names or person objects \u2014 never bare numbers or nulls. Never empty in any form: minLength, minItems and minProperties each bind to their own type and are ignored for the others.",
8398
+ items: {
8399
+ type: [
8400
+ "string",
8401
+ "object"
8402
+ ]
8403
+ },
8404
+ minLength: 1,
8405
+ minItems: 1,
8406
+ minProperties: 1,
8407
+ "x-manni-location": "external"
8408
+ },
8409
+ owner: {
8410
+ $ref: "#/$defs/stringList",
8411
+ description: "The person or team answerable for this page \u2014 the one required field of every docs-ownership system from Backstage to GitLab. A name, a handle, a chat channel, a team slug, or a list of them; write the spelling people can actually reach. A published graph does not carry it, because it names people.",
8412
+ "x-manni-location": "external",
8413
+ "x-manni-graph-output": false
8414
+ },
8415
+ stakeholders: {
8416
+ $ref: "#/$defs/stringList",
8417
+ description: "Who has a stake in this page being right: the feature's engineer, the product owner, the support lead who fields its tickets. The standing parties to consult before changing the page \u2014 where `reviewed-by` records who actually did the last pass. Page-level on purpose: the right stakeholders differ page by page, and project-wide ones belong in your project docs, not in every file's frontmatter. A published graph does not carry it, because it names people.",
8418
+ "x-manni-location": "external",
8419
+ "x-manni-graph-output": false
8420
+ },
8421
+ "reviewed-by": {
8422
+ $ref: "#/$defs/stringList",
8423
+ description: "Who last reviewed the page. Named `reviewed-by` rather than `reviewers` because MyST already defines `reviewers` as scholarly person objects. A published graph does not carry it, because it names people.",
8424
+ "x-manni-location": "external",
8425
+ "x-manni-graph-output": false
8426
+ },
8427
+ created: {
8428
+ $ref: "#/$defs/w3cdtf",
8429
+ description: "When the document was written, as a W3CDTF date. The document's date, not the file's: a page migrated from a CMS, imported from another repo, split out of a longer one, or landed through a squashed history has a path in this repo that is younger than the content it holds, and `git log` can only report the path. Where the two agree, git agreeing is a bonus rather than the source.",
8430
+ "x-manni-location": "external"
8431
+ },
8432
+ "last-updated": {
8433
+ $ref: "#/$defs/w3cdtf",
8434
+ description: "When the content last changed in a way a reader would notice, as a W3CDTF date. The editorial fact, not the file's: a commit date also moves for a typo fix, a link sweep, a formatting pass and a bulk frontmatter migration, none of which change what the page says. Distinct from `last-reviewed`, which records the page being checked and found still true \u2014 an update changes the content, a review confirms it, and a page can have either without the other. Spelled with the `last-` prefix for exactly the reason `last-reviewed` carries one: both name the most recent instance of a repeating event, and the two read as the pair they are. That also keeps the key clear of every neighbour's spelling of the same fact, which are `lastmod`, `last_update`, `lastUpdated` and `article:modified_time`. A stale value here is the accepted cost of stamping a date instead of deriving one, and a check comparing it against the repo's own last-change date is the tooling answer, not a schema's.",
8435
+ "x-manni-location": "page"
8436
+ },
8437
+ "last-reviewed": {
8438
+ $ref: "#/$defs/w3cdtf",
8439
+ description: "When the page was last reviewed, as a W3CDTF date. The field freshness tooling reads to derive review deadlines. A record, not a freshness gate \u2014 JSON Schema cannot compare a date to today.",
8440
+ "x-manni-location": "external"
8441
+ },
8442
+ "review-interval": {
8443
+ type: "string",
8444
+ pattern: "^P(?:\\d+W|(?=\\d|T\\d)(?:\\d+Y)?(?:\\d+M)?(?:\\d+D)?(?:T(?=\\d)(?:\\d+H)?(?:\\d+M)?(?:\\d+S)?)?)$",
8445
+ description: "How often the page should be reviewed, as an ISO 8601 duration: `P90D`, `P1Y`. ISO rather than `90d` so the value is machine-comparable. With `last-reviewed`, this is everything review tooling needs to derive the due date \u2014 a stored due date would only ever agree with them or lie.",
8446
+ "x-manni-location": "external"
8447
+ },
8448
+ "verified-against": {
8449
+ $ref: "#/$defs/entryList",
8450
+ description: 'The product version, API revision, or spec the content was last verified against. A string is the short spelling \u2014 `operator 1.4.2` \u2014 and an object the checkable one, such as `{name: operator, version: 1.4.2}`, which a drift check can compare without parsing prose. A list says a page was verified against more than one thing, such as the operator and the cluster it runs on. What turns "reviewed" into "reviewed against something".',
8451
+ "x-manni-location": "external"
8452
+ },
8453
+ "source-of-truth": {
8454
+ $ref: "#/$defs/entryList",
8455
+ description: "Where reality lives for this page: a repo path, an API spec, a schema file. A string names it, an object structures it \u2014 `{path: charts/operator/values.yaml, kind: helm-values}` \u2014 and a list carries several, because a page usually answers to more than one source. The anchor a drift check compares the page against; a page without one cannot be drift-monitored.",
8456
+ "x-manni-location": "external"
8457
+ }
8458
+ },
8459
+ $defs: {
8460
+ stringList: {
8461
+ if: {
8462
+ type: "array"
8463
+ },
8464
+ then: {
8465
+ type: "array",
8466
+ minItems: 1,
8467
+ uniqueItems: true,
8468
+ items: {
8469
+ type: "string",
8470
+ minLength: 1
8471
+ }
8472
+ },
8473
+ else: {
8474
+ type: "string",
8475
+ minLength: 1
8476
+ },
8477
+ description: "One non-empty string, or a non-empty list of unique ones \u2014 the same shape as the `labelList` of manni:graph:1.0.0."
8478
+ },
8479
+ entryList: {
8480
+ type: [
8481
+ "string",
8482
+ "object",
8483
+ "array"
8484
+ ],
8485
+ items: {
8486
+ $comment: "The parent's keyword-per-type pattern again, one level down: minLength binds to a string member, minProperties to an object one, and each is ignored for the other. Repeated here rather than hoisted because a member's floor is a different assertion from the whole value's.",
8487
+ type: [
8488
+ "string",
8489
+ "object"
8490
+ ],
8491
+ minLength: 1,
8492
+ minProperties: 1
8493
+ },
8494
+ minLength: 1,
8495
+ minItems: 1,
8496
+ minProperties: 1,
8497
+ uniqueItems: true,
8498
+ description: 'One entry, or a non-empty list of unique ones, where an entry is a non-empty string or a non-empty object. The shape `authors` carries, made a definition because two more fields need it: the short human spelling and the structured machine-checkable one, without minting a second field name for the same fact. Object keys are left open on purpose, and the recommendations live in each field\'s description. Every non-empty floor binds to its own type \u2014 minLength for the string, minProperties for the object, minItems and uniqueItems for the list \u2014 and `items` repeats the first two, so `""`, `[]`, `{}`, `[""]` and `[{}]` all fail. Unlike `authors`, no other schema claims the keys that use this, so the family is free to set that floor.',
8499
+ $comment: "minLength, minItems, minProperties and uniqueItems each apply to one instance type and are ignored for the others; that is what lets one subschema hold three shapes without an anyOf whose errors name all three branches."
8500
+ },
8501
+ w3cdtf: {
8502
+ type: "string",
8503
+ pattern: "^[0-9]{4}(-(0[1-9]|1[0-2])(-(0[1-9]|[12][0-9]|3[01])(T([01][0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9](\\.[0-9]+)?)?(Z|[+-]([01][0-9]|2[0-3]):[0-5][0-9]))?)?)?$",
8504
+ description: "A W3CDTF date, permitting reduced precision: `2026`, `2026-08`, `2026-08-23`, or a full timestamp \u2014 in which case the timezone designator is required, as the W3CDTF profile itself mandates (no local-time form). Field-ranged (month 01-12, day 01-31, hour 00-23) so an impossible date fails here rather than becoming Invalid Date in the tooling that derives review deadlines; not calendar-exact \u2014 February 31 is a reviewer's catch."
8505
+ }
8506
+ }
8507
+ };
8508
+
8509
+ // src/meta/schemas/stewardship-strict/1.1.0.json
8510
+ var __default49 = {
8511
+ $schema: "https://json-schema.org/draft/2020-12/schema",
8512
+ $id: "manni:stewardship-strict:1.1.0",
8513
+ title: "manni stewardship strict overlay v1.1.0",
8514
+ description: "The strict overlay for manni:stewardship:1.1.0. Stack it beside the open vocabulary. It narrows the form of values the open vocabulary already claims and requires no key.",
8515
+ additionalProperties: true,
8516
+ type: "object",
8517
+ properties: {
8518
+ authors: {
8519
+ type: "array",
8520
+ uniqueItems: true,
8521
+ items: {
8522
+ pattern: "^\\S(?:.*\\S)?$",
8523
+ required: [
8524
+ "name"
8525
+ ],
8526
+ properties: {
8527
+ name: {
8528
+ type: "string",
8529
+ pattern: "^\\S(?:.*\\S)?$"
8530
+ },
8531
+ email: {
8532
+ type: "string",
8533
+ pattern: "^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$"
8534
+ },
8535
+ url: {
8536
+ type: "string",
8537
+ pattern: "^https?://\\S+$"
8538
+ }
8539
+ }
8540
+ },
8541
+ description: "Strict takes authors as a list of unique entries. A string entry is one line with no surrounding space. A person object names the person, and its `email` and `url` are well formed."
8542
+ },
8543
+ owner: {
8544
+ $ref: "#/$defs/handles",
8545
+ description: "Strict holds each owner to a handle a GitHub or GitLab CODEOWNERS file accepts, in the string form and the list form alike."
8546
+ },
8547
+ stakeholders: {
8548
+ $ref: "#/$defs/names",
8549
+ description: "Strict holds each stakeholder to one line with no surrounding space. A stakeholder is a person or team, named in any form."
8550
+ },
8551
+ "reviewed-by": {
8552
+ $ref: "#/$defs/names",
8553
+ description: "Strict holds each reviewer to one line with no surrounding space. `manni meta derive` writes review-API logins and commit-trailer names, so no handle form is required."
8554
+ },
8555
+ created: {
8556
+ $ref: "#/$defs/date",
8557
+ description: "Strict holds the creation date to an RFC 3339 full date or date-time. A year or month alone fails, and so does a timestamp without seconds."
8558
+ },
8559
+ "last-updated": {
8560
+ $ref: "#/$defs/date",
8561
+ description: "Strict holds the update date to an RFC 3339 full date or date-time. A year or month alone fails, and so does a timestamp without seconds."
8562
+ },
8563
+ "last-reviewed": {
8564
+ $ref: "#/$defs/date",
8565
+ description: "Strict holds the review date to an RFC 3339 full date or date-time. A year or month alone fails, and so does a timestamp without seconds."
8566
+ },
8567
+ "verified-against": {
8568
+ required: [
8569
+ "name",
8570
+ "version"
8571
+ ],
8572
+ items: {
8573
+ required: [
8574
+ "name",
8575
+ "version"
8576
+ ]
8577
+ },
8578
+ description: "Strict requires an object entry to carry `name` and `version`, alone or in a list. The version's format stays free, and a string entry is unchanged. The top-level `required` holds the single-object form, and `items` holds each object in a list, since `required` applies only to an object."
8579
+ },
8580
+ "source-of-truth": {
8581
+ $ref: "#/$defs/source",
8582
+ items: {
8583
+ $ref: "#/$defs/source"
8584
+ },
8585
+ description: "Strict requires an object entry to carry `path` or `url`, alone or in a list. A string entry is one id, path or URL on one line with no surrounding space. The top-level `$ref` holds the string and single-object forms, and `items` holds each entry in a list, since `required` applies only to an object."
8586
+ }
8587
+ },
8588
+ $defs: {
8589
+ handles: {
8590
+ pattern: "^(?:@@?[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?(?:/[A-Za-z0-9._-]+)*|[^\\s@]+@[^\\s@]+\\.[^\\s@]+)$",
8591
+ items: {
8592
+ pattern: "^(?:@@?[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?(?:/[A-Za-z0-9._-]+)*|[^\\s@]+@[^\\s@]+\\.[^\\s@]+)$"
8593
+ },
8594
+ description: "A handle a CODEOWNERS file accepts, as a string or as each member of a list. That is `@user`, `@org/team`, a GitLab nested group or `@@role`, or an email address."
8595
+ },
8596
+ names: {
8597
+ pattern: "^\\S(?:.*\\S)?$",
8598
+ items: {
8599
+ pattern: "^\\S(?:.*\\S)?$"
8600
+ },
8601
+ description: "One line with no surrounding space, as a string or as each member of a list."
8602
+ },
8603
+ date: {
8604
+ type: "string",
8605
+ pattern: "^[0-9]{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12][0-9]|3[01])(?:T(?:[01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9](?:\\.[0-9]+)?(?:Z|[+-](?:[01][0-9]|2[0-3]):[0-5][0-9]))?$",
8606
+ description: "An RFC 3339 full date, or a date-time with seconds and an offset."
8607
+ },
8608
+ source: {
8609
+ pattern: "^\\S(?:.*\\S)?$",
8610
+ anyOf: [
8611
+ {
8612
+ required: [
8613
+ "path"
8614
+ ]
8615
+ },
8616
+ {
8617
+ required: [
8618
+ "url"
8619
+ ]
8620
+ }
8621
+ ],
8622
+ description: "A source entry. A string is one id, path or URL on one line with no surrounding space. An object carries `path` or `url`."
8623
+ }
8624
+ }
8625
+ };
8626
+
8356
8627
  // src/meta/core/schema-registry.ts
8357
8628
  var BUILTINS = /* @__PURE__ */ new Map([
8358
8629
  ["google:okf:0.1", __default],
@@ -8401,7 +8672,13 @@ var BUILTINS = /* @__PURE__ */ new Map([
8401
8672
  ["manni:graph-strict:1.0.0", __default44],
8402
8673
  ["manni:citations-strict:1.0.0", __default45],
8403
8674
  ["tgdp:templates:1.1", __default46],
8404
- ["tgdp:templates-strict:1.1", __default47]
8675
+ ["tgdp:templates-strict:1.1", __default47],
8676
+ ["manni:stewardship:1.1.0", __default48],
8677
+ ["manni:stewardship-strict:1.1.0", __default49]
8678
+ ]);
8679
+ var ANNOTATION_ONLY_PREDECESSORS = /* @__PURE__ */ new Map([
8680
+ ["manni:stewardship:1.1.0", "manni:stewardship:1.0.0"],
8681
+ ["manni:stewardship-strict:1.1.0", "manni:stewardship-strict:1.0.0"]
8405
8682
  ]);
8406
8683
  function assertPublishableBuiltinId(id) {
8407
8684
  const first = id.split(":")[0];
@@ -8494,7 +8771,9 @@ var PUBLISHED_PATHS = [
8494
8771
  ["graph-strict/1.0.0.json", "manni:graph-strict:1.0.0"],
8495
8772
  ["citations-strict/1.0.0.json", "manni:citations-strict:1.0.0"],
8496
8773
  ["tgdp/1.1.json", "tgdp:templates:1.1"],
8497
- ["tgdp-strict/1.1.json", "tgdp:templates-strict:1.1"]
8774
+ ["tgdp-strict/1.1.json", "tgdp:templates-strict:1.1"],
8775
+ ["stewardship/1.1.0.json", "manni:stewardship:1.1.0"],
8776
+ ["stewardship-strict/1.1.0.json", "manni:stewardship-strict:1.1.0"]
8498
8777
  ];
8499
8778
  var PUBLISHED_ALIAS = new Map(
8500
8779
  PUBLISHED_PATHS.map(([path, id]) => [`${PUBLISHED_BASE}${path}`, id])
@@ -8850,7 +9129,7 @@ var DEFAULT_SCHEMAS = Object.freeze([
8850
9129
  "manni:core:1.0.0",
8851
9130
  "manni:audience:1.0.0",
8852
9131
  "manni:structure:1.0.0",
8853
- "manni:stewardship:1.0.0",
9132
+ "manni:stewardship:1.1.0",
8854
9133
  "manni:lifecycle:1.0.0",
8855
9134
  "manni:ai-context:1.0.0",
8856
9135
  "manni:evals:1.0.0",
@@ -8858,7 +9137,14 @@ var DEFAULT_SCHEMAS = Object.freeze([
8858
9137
  "manni:citations:1.0.0"
8859
9138
  ]);
8860
9139
  var FILE_SCHEMA_KEY = "$schema";
8861
- var DEFAULT_SCHEMA_SET = new Set(DEFAULT_SCHEMAS);
9140
+ function vocabularyOf(id) {
9141
+ const parts = id.split(":");
9142
+ if (parts.length !== 3) return void 0;
9143
+ return `${parts[0] ?? ""}:${parts[1] ?? ""}`;
9144
+ }
9145
+ var DEFAULT_VOCABULARIES = new Set(
9146
+ DEFAULT_SCHEMAS.map(vocabularyOf).filter((v) => v !== void 0)
9147
+ );
8862
9148
  function schemaEntryRef(entry) {
8863
9149
  return typeof entry === "string" ? entry : entry.ref;
8864
9150
  }
@@ -8969,14 +9255,34 @@ function withStrict(refs, registered2) {
8969
9255
  out.push(ref);
8970
9256
  const strictId = strictIdOf(ref);
8971
9257
  if (strictId === void 0) continue;
8972
- if (DEFAULT_SCHEMA_SET.has(ref) && isBuiltinId(strictId)) out.push(strictId);
9258
+ const vocabulary = vocabularyOf(ref);
9259
+ const isDefault = vocabulary !== void 0 && DEFAULT_VOCABULARIES.has(vocabulary);
9260
+ if (isDefault && isBuiltinId(ref) && isBuiltinId(strictId)) out.push(strictId);
8973
9261
  else if (registered2?.has(ref) === true && registered2.has(strictId)) out.push(strictId);
8974
9262
  }
8975
9263
  return dedupe(out);
8976
9264
  }
9265
+ function withoutReplacedVersions(set, listed) {
9266
+ const explicit = /* @__PURE__ */ new Set();
9267
+ for (const ref of listed) {
9268
+ if (!isBuiltinId(ref)) continue;
9269
+ explicit.add(ref);
9270
+ const strictId = strictIdOf(ref);
9271
+ if (strictId !== void 0) explicit.add(strictId);
9272
+ }
9273
+ const pinned = new Set(
9274
+ [...explicit].map(vocabularyOf).filter((v) => v !== void 0)
9275
+ );
9276
+ return set.filter((ref) => {
9277
+ if (explicit.has(ref) || !isBuiltinId(ref)) return true;
9278
+ const vocabulary = vocabularyOf(ref);
9279
+ return vocabulary === void 0 || !pinned.has(vocabulary);
9280
+ });
9281
+ }
8977
9282
  function joined(listed, defaults, strict, registered2) {
8978
9283
  const set = dedupe([...defaults ? DEFAULT_SCHEMAS : [], ...listed]);
8979
- return strict ? withStrict(set, registered2) : set;
9284
+ const paired = strict ? withStrict(set, registered2) : set;
9285
+ return defaults ? withoutReplacedVersions(paired, listed) : paired;
8980
9286
  }
8981
9287
  function isDefaultSetOnly(resolved) {
8982
9288
  if (!resolved.defaults) return false;
@@ -10709,6 +11015,14 @@ function schemaTrustRoot(cwd, configDir) {
10709
11015
  async function loadConfig(explicitPath, cwd = process.cwd()) {
10710
11016
  const file = explicitPath ? await readConfigFile(explicitPath, cwd, CONFIG_FILE) : await findConfigFile(cwd, CONFIG_FILE);
10711
11017
  if (file === null) return null;
11018
+ return loadedFrom(file);
11019
+ }
11020
+ async function loadMetaSection(explicitPath, cwd = process.cwd()) {
11021
+ if (explicitPath === void 0) return loadConfig(void 0, cwd);
11022
+ const file = await readConfigFile(explicitPath, cwd, CONFIG_FILE);
11023
+ return file.wrapped ? loadedFrom(file) : null;
11024
+ }
11025
+ async function loadedFrom(file) {
10712
11026
  const section = file.wrapped ? META_SECTION : void 0;
10713
11027
  const config = parseConfigValue(file.value, file.source, section);
10714
11028
  assertOverrideCollections(config, file.collections, file.source, section);
@@ -11050,8 +11364,9 @@ function canonicalSchemaRef(ref, ctx) {
11050
11364
  return relative7(ctx.base, abs).replace(/\\/g, "/");
11051
11365
  }
11052
11366
  function fingerprint(e, ctx) {
11367
+ const ref = canonicalSchemaRef(e.schema, ctx);
11053
11368
  const parts = [
11054
- canonicalSchemaRef(e.schema, ctx),
11369
+ ANNOTATION_ONLY_PREDECESSORS.get(ref) ?? ref,
11055
11370
  e.instancePath,
11056
11371
  e.keyword,
11057
11372
  e.subject ?? ""
@@ -12992,6 +13307,9 @@ function isFieldLocation(value) {
12992
13307
  return value === "page" || value === "external";
12993
13308
  }
12994
13309
 
13310
+ // src/meta/core/graph-output.ts
13311
+ var GRAPH_OUTPUT_KEYWORD = "x-manni-graph-output";
13312
+
12995
13313
  // src/meta/core/validator.ts
12996
13314
  import * as AjvDraft07Ns from "ajv";
12997
13315
  import * as Ajv2019Ns from "ajv/dist/2019.js";
@@ -13023,6 +13341,7 @@ function buildAjv(dialect, coerceTypes = false) {
13023
13341
  if (dialect === "draft7") ajv.addMetaSchema(draft06MetaSchema);
13024
13342
  registerEncryptKeyword(ajv);
13025
13343
  registerLocationKeyword(ajv);
13344
+ registerGraphOutputKeyword(ajv);
13026
13345
  registerBuiltins(ajv, dialect);
13027
13346
  return ajv;
13028
13347
  }
@@ -13071,6 +13390,28 @@ function registerLocationKeyword(ajv) {
13071
13390
  function isLocationShapeError(err) {
13072
13391
  return err instanceof Error && err.message.includes(`"${LOCATION_KEYWORD}"`);
13073
13392
  }
13393
+ var graphOutputRecorder;
13394
+ function registerGraphOutputKeyword(ajv) {
13395
+ ajv.addKeyword({
13396
+ keyword: GRAPH_OUTPUT_KEYWORD,
13397
+ metaSchema: { type: "boolean" },
13398
+ errors: false,
13399
+ validate: (schema, _data, _parent, cxt) => {
13400
+ if (graphOutputRecorder !== void 0 && typeof schema === "boolean") {
13401
+ const key = topLevelKey(cxt?.instancePath ?? "");
13402
+ if (key !== void 0) {
13403
+ const seen = graphOutputRecorder.get(key);
13404
+ if (seen) seen.add(schema);
13405
+ else graphOutputRecorder.set(key, /* @__PURE__ */ new Set([schema]));
13406
+ }
13407
+ }
13408
+ return true;
13409
+ }
13410
+ });
13411
+ }
13412
+ function isGraphOutputShapeError(err) {
13413
+ return err instanceof Error && err.message.includes(`"${GRAPH_OUTPUT_KEYWORD}"`);
13414
+ }
13074
13415
  function marksSchemaExternal(schema) {
13075
13416
  const properties = schema["properties"];
13076
13417
  if (typeof properties !== "object" || properties === null) return false;
@@ -13081,7 +13422,7 @@ function marksSchemaExternal(schema) {
13081
13422
  function isRecord2(value) {
13082
13423
  return typeof value === "object" && value !== null && !Array.isArray(value);
13083
13424
  }
13084
- function definiteLocationConflict(root) {
13425
+ function definiteMarkConflict(root, keyword, isMark) {
13085
13426
  const marks = /* @__PURE__ */ new Map();
13086
13427
  const localRef = (node, embedded) => {
13087
13428
  const ref = node["$ref"];
@@ -13109,8 +13450,8 @@ function definiteLocationConflict(root) {
13109
13450
  if (!isRecord2(node) || visited.has(node)) return;
13110
13451
  visited.add(node);
13111
13452
  const inner = isEmbedded(node, embedded);
13112
- const mark = node[LOCATION_KEYWORD];
13113
- if (isFieldLocation(mark)) {
13453
+ const mark = node[keyword];
13454
+ if (isMark(mark)) {
13114
13455
  const seen = marks.get(key);
13115
13456
  if (seen) seen.add(mark);
13116
13457
  else marks.set(key, /* @__PURE__ */ new Set([mark]));
@@ -13240,12 +13581,22 @@ var Validator = class {
13240
13581
  `${ref}: "${FILE_SCHEMA_KEY}" cannot be stored in external metadata.`
13241
13582
  );
13242
13583
  }
13243
- const contradicted = definiteLocationConflict(schema);
13584
+ const contradicted = definiteMarkConflict(schema, LOCATION_KEYWORD, isFieldLocation);
13244
13585
  if (contradicted !== void 0) {
13245
13586
  throw new DocmetaError(
13246
13587
  `${ref}: "${LOCATION_KEYWORD}" says both "page" and "external" for "${contradicted}".`
13247
13588
  );
13248
13589
  }
13590
+ const graphContradicted = definiteMarkConflict(
13591
+ schema,
13592
+ GRAPH_OUTPUT_KEYWORD,
13593
+ (value) => typeof value === "boolean"
13594
+ );
13595
+ if (graphContradicted !== void 0) {
13596
+ throw new DocmetaError(
13597
+ `${ref}: "${GRAPH_OUTPUT_KEYWORD}" says both true and false for "${graphContradicted}".`
13598
+ );
13599
+ }
13249
13600
  try {
13250
13601
  const ajv = this.ajvFor(dialectOf(schema));
13251
13602
  const id = typeof schema["$id"] === "string" ? schema["$id"] : void 0;
@@ -13267,6 +13618,11 @@ var Validator = class {
13267
13618
  `${ref}: "${LOCATION_KEYWORD}" must be "page" or "external".`
13268
13619
  );
13269
13620
  }
13621
+ if (isGraphOutputShapeError(err)) {
13622
+ throw new DocmetaError(
13623
+ `${ref}: "${GRAPH_OUTPUT_KEYWORD}" must be true or false.`
13624
+ );
13625
+ }
13270
13626
  throw new DocmetaError(
13271
13627
  `Schema "${ref}" failed to compile: ${errorMessage(err)}`
13272
13628
  );
@@ -13356,6 +13712,45 @@ var Validator = class {
13356
13712
  }
13357
13713
  return claimed;
13358
13714
  }
13715
+ /**
13716
+ * Whether each top-level key of `data` belongs in a published graph under
13717
+ * `refs`, as its `x-manni-graph-output` mark says. A key no schema marks has no
13718
+ * entry, which the caller reads as `true`: absent means harvested.
13719
+ *
13720
+ * A first pass with the recording keyword, as `locationPreferences` runs, so
13721
+ * a mark counts exactly where validation would evaluate it, and a mark nested
13722
+ * inside `graph` is ignored. Only present values are marked: Ajv applies a
13723
+ * property's subschema to a property that exists. `$schema` is stripped
13724
+ * first, as validation does.
13725
+ *
13726
+ * Refs are evaluated in order and a later ref's mark wins, so a house schema
13727
+ * listed after a vocabulary refines it. A schema that says both on paths
13728
+ * every document takes is refused when it compiles. A ref whose conditional
13729
+ * branches say both `true` and `false` gives that key no preference: with
13730
+ * `allErrors`, Ajv evaluates failing branches too, so neither value is that
13731
+ * ref's answer and an earlier ref's stands.
13732
+ */
13733
+ async graphOutputPreferences(data, refs) {
13734
+ const { [FILE_SCHEMA_KEY]: _omit, ...subject } = data;
13735
+ void _omit;
13736
+ const preferences = /* @__PURE__ */ new Map();
13737
+ for (const ref of refs) {
13738
+ const fn = await this.compile(ref);
13739
+ const seen = /* @__PURE__ */ new Map();
13740
+ graphOutputRecorder = seen;
13741
+ try {
13742
+ fn(subject);
13743
+ } finally {
13744
+ graphOutputRecorder = void 0;
13745
+ }
13746
+ for (const [key, values] of seen) {
13747
+ const [value, ...others] = [...values];
13748
+ if (value === void 0 || others.length > 0) continue;
13749
+ preferences.set(key, value);
13750
+ }
13751
+ }
13752
+ return preferences;
13753
+ }
13359
13754
  /**
13360
13755
  * Validate `data` against every schema in `refs`. Returns all violations,
13361
13756
  * each tagged with the schema that produced it and a source line via
@@ -16114,17 +16509,20 @@ function lazyValidator(opts) {
16114
16509
  let built;
16115
16510
  return () => opts.validator instanceof Validator ? opts.validator : built ??= opts.validator();
16116
16511
  }
16512
+ function pageSchemaSet(opts, label, probe, memberOf2) {
16513
+ return resolveSchemaSetWithSource({
16514
+ filePath: label,
16515
+ fileSchema: probe[FILE_SCHEMA_KEY],
16516
+ ...opts.cliSchemas !== void 0 ? { cliSchemas: [...opts.cliSchemas] } : {},
16517
+ config: opts.config,
16518
+ memberOf: opts.memberOf?.(label) ?? memberOf2,
16519
+ fileBase: opts.cwd,
16520
+ trustRoot: opts.trustRoot
16521
+ }).schemas;
16522
+ }
16117
16523
  function pageRefs(opts, label, probe, memberOf2) {
16118
16524
  try {
16119
- return resolveSchemaSetWithSource({
16120
- filePath: label,
16121
- fileSchema: probe[FILE_SCHEMA_KEY],
16122
- ...opts.cliSchemas !== void 0 ? { cliSchemas: [...opts.cliSchemas] } : {},
16123
- config: opts.config,
16124
- memberOf: opts.memberOf?.(label) ?? memberOf2,
16125
- fileBase: opts.cwd,
16126
- trustRoot: opts.trustRoot
16127
- }).schemas;
16525
+ return pageSchemaSet(opts, label, probe, memberOf2);
16128
16526
  } catch (err) {
16129
16527
  if (err instanceof DocmetaError) return void 0;
16130
16528
  throw err;
@@ -17990,6 +18388,36 @@ function stampManifests(label, data, collections, configDir, base, marked, field
17990
18388
  return out;
17991
18389
  }
17992
18390
 
18391
+ // src/shared/cli-options.ts
18392
+ import { basename as basename6, relative as relative15 } from "node:path";
18393
+ function collect(value, prev) {
18394
+ return prev.concat([value]);
18395
+ }
18396
+ function splitList(value) {
18397
+ return value.split(",").map((s) => s.trim()).filter(Boolean);
18398
+ }
18399
+ var STDIN_REQUIRES_AS = "Reading from stdin (`-`) requires --as <format> to choose an extractor.";
18400
+ async function readStdin() {
18401
+ const chunks = [];
18402
+ for await (const chunk of process.stdin) chunks.push(chunk);
18403
+ return Buffer.concat(chunks).toString("utf8");
18404
+ }
18405
+ function configOption(value) {
18406
+ if (value === false) return { noConfig: true };
18407
+ return typeof value === "string" ? { configPath: value } : {};
18408
+ }
18409
+ function explicitFalse(value) {
18410
+ return value === false ? false : void 0;
18411
+ }
18412
+ function reportConfig(toStdout, cwd) {
18413
+ return (info) => {
18414
+ const where = (relative15(cwd, info.dir) || ".").replace(/\\/g, "/");
18415
+ const line = `Using ${basename6(info.path)} (${where})
18416
+ `;
18417
+ (toStdout ? process.stdout : process.stderr).write(line);
18418
+ };
18419
+ }
18420
+
17993
18421
  // src/meta/commands/validate.ts
17994
18422
  function parseErrorResult(file, format, message, keyword) {
17995
18423
  const err = {
@@ -18378,7 +18806,7 @@ async function runValidate(opts) {
18378
18806
  const content = opts.stdinContent ?? "";
18379
18807
  if (!forcedExtractor) {
18380
18808
  throw new DocmetaError(
18381
- "Reading from stdin (`-`) requires --as <format> to choose an extractor."
18809
+ STDIN_REQUIRES_AS
18382
18810
  );
18383
18811
  }
18384
18812
  await processOne(STDIN_LABEL, content, forcedExtractor.extensions[0] ?? "");
@@ -18670,7 +19098,7 @@ async function runGet(opts) {
18670
19098
  if (usingStdin) {
18671
19099
  if (!forced) {
18672
19100
  throw new DocmetaError(
18673
- "Reading from stdin (`-`) requires --as <format> to choose an extractor."
19101
+ STDIN_REQUIRES_AS
18674
19102
  );
18675
19103
  }
18676
19104
  await readOne(STDIN_LABEL, opts.stdinContent ?? "", forced.extensions[0] ?? "");
@@ -18803,7 +19231,7 @@ function parseJsonPointer(pointer) {
18803
19231
  // src/meta/commands/schemas.ts
18804
19232
  import { existsSync as existsSync4 } from "node:fs";
18805
19233
  import { mkdir as mkdir3, readFile as readFile11 } from "node:fs/promises";
18806
- import { dirname as dirname10, extname as extname6, isAbsolute as isAbsolute10, join as join10, relative as relative15, resolve as resolve21, sep as sep10 } from "node:path";
19234
+ import { dirname as dirname10, extname as extname6, isAbsolute as isAbsolute10, join as join10, relative as relative16, resolve as resolve21, sep as sep10 } from "node:path";
18807
19235
  import { parseDocument as parseDocument8, parse as parseYaml6, stringify as stringify4 } from "yaml";
18808
19236
  function getSchemasInfo(registered2) {
18809
19237
  return {
@@ -18815,7 +19243,7 @@ function getSchemasInfo(registered2) {
18815
19243
  var DEFAULT_VENDOR_DIR = "./schema";
18816
19244
  var DEFAULT_CONFIG_NAME = "manni.config.yaml";
18817
19245
  function posixRelative(from, to) {
18818
- return relative15(from, to).split(sep10).join("/");
19246
+ return relative16(from, to).split(sep10).join("/");
18819
19247
  }
18820
19248
  function vendorFileName(url) {
18821
19249
  let parsed2;
@@ -19074,7 +19502,7 @@ async function runInferSchema(opts) {
19074
19502
  }
19075
19503
  if (usingStdin && !forced) {
19076
19504
  throw new DocmetaError(
19077
- "Reading from stdin (`-`) requires --as <format> to choose an extractor."
19505
+ STDIN_REQUIRES_AS
19078
19506
  );
19079
19507
  }
19080
19508
  let absOut;
@@ -19591,7 +20019,7 @@ async function runQuery(opts) {
19591
20019
  if (usingStdin) {
19592
20020
  if (!forced) {
19593
20021
  throw new DocmetaError(
19594
- "Reading from stdin (`-`) requires --as <format> to choose an extractor."
20022
+ STDIN_REQUIRES_AS
19595
20023
  );
19596
20024
  }
19597
20025
  const ext = forced.extensions[0];
@@ -22585,7 +23013,7 @@ async function runFill(opts) {
22585
23013
  }
22586
23014
  if (usingStdin && !forcedExtractor) {
22587
23015
  throw new DocmetaError(
22588
- "Reading from stdin (`-`) requires --as <format> to choose an extractor."
23016
+ STDIN_REQUIRES_AS
22589
23017
  );
22590
23018
  }
22591
23019
  const threshold = requireNumber(
@@ -23860,7 +24288,7 @@ async function mapConcurrent(items2, limit, fn) {
23860
24288
 
23861
24289
  // src/meta/commands/derive.ts
23862
24290
  import { readFile as readFile16 } from "node:fs/promises";
23863
- import { extname as extname10, relative as relative16, resolve as resolve25, sep as sep12 } from "node:path";
24291
+ import { extname as extname10, relative as relative17, resolve as resolve25, sep as sep12 } from "node:path";
23864
24292
  var GENERATED_BY_ENV = "MANNI_GENERATED_BY";
23865
24293
  var SOURCE_HINT = "narrow --sources or --fields";
23866
24294
  async function runDerive(opts) {
@@ -23978,7 +24406,7 @@ async function runDerive(opts) {
23978
24406
  const files = fromCollections ? retainMembers(walked, membersFor) : walked;
23979
24407
  const rangesByPage = /* @__PURE__ */ new Map();
23980
24408
  for (const t of targets) {
23981
- const label = relative16(base, resolve25(base, t.page)).split(sep12).join("/");
24409
+ const label = relative17(base, resolve25(base, t.page)).split(sep12).join("/");
23982
24410
  if (!files.includes(label)) {
23983
24411
  throw new DocmetaError(
23984
24412
  `${t.target} does not name one file that derive reads; a range names lines of one file.`
@@ -25255,6 +25683,13 @@ export {
25255
25683
  runIfMain,
25256
25684
  escapeWorkflowCommandMessage,
25257
25685
  escapeWorkflowCommandProperty,
25686
+ collect,
25687
+ splitList,
25688
+ STDIN_REQUIRES_AS,
25689
+ readStdin,
25690
+ configOption,
25691
+ explicitFalse,
25692
+ reportConfig,
25258
25693
  INTEGRITY_SHAPE,
25259
25694
  integrityOf,
25260
25695
  isIntegrity,
@@ -25309,7 +25744,8 @@ export {
25309
25744
  schemaCacheDir,
25310
25745
  SchemaCache,
25311
25746
  __default31 as __default,
25312
- __default34 as __default2,
25747
+ __default33 as __default2,
25748
+ __default34 as __default3,
25313
25749
  listBuiltins,
25314
25750
  classifyRef,
25315
25751
  fetchSchemaBytes,
@@ -25333,8 +25769,10 @@ export {
25333
25769
  staleFindings,
25334
25770
  LEGACY_CONFIG_NAMES,
25335
25771
  parseConfig,
25772
+ urlManifestMessage,
25336
25773
  schemaTrustRoot,
25337
25774
  loadConfig,
25775
+ loadMetaSection,
25338
25776
  resolveRunConfig,
25339
25777
  renderSarif,
25340
25778
  renderJunit,
@@ -25360,6 +25798,7 @@ export {
25360
25798
  EXTERNAL_METADATA_FETCH_MAX_BYTES,
25361
25799
  fetchExternalMetadata,
25362
25800
  META_CONTEXT,
25801
+ lazyKey,
25363
25802
  withValueAt,
25364
25803
  EXTERNAL_OWNED_SCHEMA,
25365
25804
  EXTERNAL_DUPLICATE_SCHEMA,
@@ -25381,8 +25820,10 @@ export {
25381
25820
  LOCATION_KEYWORD,
25382
25821
  LOCATION_EXTERNAL_SCHEMA,
25383
25822
  LOCATION_PAGE_SCHEMA,
25823
+ GRAPH_OUTPUT_KEYWORD,
25384
25824
  Validator,
25385
25825
  pageMarks,
25826
+ pageSchemaSet,
25386
25827
  marksValidator,
25387
25828
  configMarks,
25388
25829
  relocateFailed,
@@ -25454,4 +25895,4 @@ export {
25454
25895
  renderQuery,
25455
25896
  renderQueryCsv
25456
25897
  };
25457
- //# sourceMappingURL=chunk-I57KBF45.js.map
25898
+ //# sourceMappingURL=chunk-TH565O2N.js.map