@hawkeyexl/manni 3.0.0 → 4.0.1

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/index.js CHANGED
@@ -1,11 +1,57 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ BLOCK_KINDS,
4
+ BUILTIN_SOURCE,
5
+ BUILT_IN_DEFAULTS,
3
6
  KeyError,
7
+ LINT_JOBS,
8
+ LINT_TOOLS,
9
+ LintError,
10
+ RULE_ID_PREFIX2 as RULE_ID_PREFIX,
11
+ STDIN_LABEL,
12
+ TOOLS_BY_JOB,
13
+ TOOL_NAME,
4
14
  cite_exports,
15
+ classifyRef as classifyRef2,
16
+ clearMatcherCache,
17
+ clearPatternCache,
18
+ clearTemplateCaches,
19
+ headingMatches,
20
+ isLintTool,
21
+ isWildcard,
22
+ listBuiltins as listBuiltins2,
23
+ listFormats,
24
+ loadConfig as loadConfig2,
25
+ loadTemplate,
26
+ loadTemplateFile,
27
+ matchSections,
28
+ occurrenceRange,
29
+ parseConfig as parseConfig2,
30
+ parserByName,
31
+ parserForExtension,
32
+ rebaseConfig,
5
33
  reencryptMetadata,
34
+ render as render2,
35
+ renderGithub,
36
+ renderJson,
37
+ renderJunit as renderJunit2,
38
+ renderPretty,
39
+ renderSarif as renderSarif2,
40
+ renderTemplates,
41
+ renderTools,
42
+ resolveExtends,
43
+ resolveLintRun,
44
+ ruleId,
45
+ ruleName,
6
46
  runKeyRotate,
7
- runKeySet
8
- } from "./chunk-W75SLU7Z.js";
47
+ runKeySet,
48
+ runLint,
49
+ runTemplates,
50
+ runTools,
51
+ supportedExtensions as supportedExtensions2,
52
+ validateDocument,
53
+ validateSections
54
+ } from "./chunk-Z2RBN4N4.js";
9
55
  import {
10
56
  BinMissing,
11
57
  COMMON_FORMATS,
@@ -40,6 +86,7 @@ import {
40
86
  ReviewCache,
41
87
  SCHEMA_CACHE_DIR,
42
88
  SCHEMA_CACHE_VERSION,
89
+ STDIN_TOKEN,
43
90
  SchemaCache,
44
91
  Validator,
45
92
  __export,
@@ -92,6 +139,7 @@ import {
92
139
  orphanEntries,
93
140
  orphanError,
94
141
  orphanJoins,
142
+ palette,
95
143
  parseCollections,
96
144
  parseConfig,
97
145
  parseOriginUrl,
@@ -121,6 +169,7 @@ import {
121
169
  schemaEntryRef,
122
170
  schemaLoadOptions,
123
171
  selectCollections,
172
+ shouldColor,
124
173
  sourcesFor,
125
174
  staleFindings,
126
175
  stringifyValue,
@@ -128,7 +177,64 @@ import {
128
177
  valueOf,
129
178
  vendorFileName,
130
179
  writeFileAtomic
131
- } from "./chunk-I6PYC7EP.js";
180
+ } from "./chunk-DJ2WOJIG.js";
181
+
182
+ // src/lint/index.ts
183
+ var lint_exports = {};
184
+ __export(lint_exports, {
185
+ BLOCK_KINDS: () => BLOCK_KINDS,
186
+ BUILTIN_SOURCE: () => BUILTIN_SOURCE,
187
+ BUILT_IN_DEFAULTS: () => BUILT_IN_DEFAULTS,
188
+ LINT_JOBS: () => LINT_JOBS,
189
+ LINT_TOOLS: () => LINT_TOOLS,
190
+ LintError: () => LintError,
191
+ RULE_ID_PREFIX: () => RULE_ID_PREFIX,
192
+ STDIN_LABEL: () => STDIN_LABEL,
193
+ STDIN_TOKEN: () => STDIN_TOKEN,
194
+ TOOLS_BY_JOB: () => TOOLS_BY_JOB,
195
+ TOOL_NAME: () => TOOL_NAME,
196
+ classifyRef: () => classifyRef2,
197
+ clearCaches: () => clearCaches,
198
+ headingMatches: () => headingMatches,
199
+ isLintTool: () => isLintTool,
200
+ isWildcard: () => isWildcard,
201
+ listBuiltins: () => listBuiltins2,
202
+ listFormats: () => listFormats,
203
+ loadConfig: () => loadConfig2,
204
+ loadTemplate: () => loadTemplate,
205
+ loadTemplateFile: () => loadTemplateFile,
206
+ matchSections: () => matchSections,
207
+ occurrenceRange: () => occurrenceRange,
208
+ palette: () => palette,
209
+ parseConfig: () => parseConfig2,
210
+ parserByName: () => parserByName,
211
+ parserForExtension: () => parserForExtension,
212
+ rebaseConfig: () => rebaseConfig,
213
+ render: () => render2,
214
+ renderGithub: () => renderGithub,
215
+ renderJson: () => renderJson,
216
+ renderJunit: () => renderJunit2,
217
+ renderPretty: () => renderPretty,
218
+ renderSarif: () => renderSarif2,
219
+ renderTemplates: () => renderTemplates,
220
+ renderTools: () => renderTools,
221
+ resolveExtends: () => resolveExtends,
222
+ resolveLintRun: () => resolveLintRun,
223
+ ruleId: () => ruleId,
224
+ ruleName: () => ruleName,
225
+ runLint: () => runLint,
226
+ runTemplates: () => runTemplates,
227
+ runTools: () => runTools,
228
+ shouldColor: () => shouldColor,
229
+ supportedExtensions: () => supportedExtensions2,
230
+ validateDocument: () => validateDocument,
231
+ validateSections: () => validateSections
232
+ });
233
+ function clearCaches() {
234
+ clearTemplateCaches();
235
+ clearMatcherCache();
236
+ clearPatternCache();
237
+ }
132
238
 
133
239
  // src/key/index.ts
134
240
  var key_exports = {};
@@ -214,6 +320,7 @@ export {
214
320
  isQueryFormat,
215
321
  isReportFormat,
216
322
  key_exports as key,
323
+ lint_exports as lint,
217
324
  listBuiltins,
218
325
  loadConfig,
219
326
  loadExternalMetadata,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/key/index.ts"],"sourcesContent":["/**\n * The key domain's programmatic API, exported from the package as the `key`\n * namespace: `import { key } from \"@hawkeyexl/manni\"`. The command cores, and\n * the family's encryption primitives a caller needs beside them.\n */\nexport * from \"./types.js\";\nexport { KeyError } from \"./errors.js\";\nexport { runKeySet } from \"./commands/set.js\";\nexport { runKeyRotate } from \"./commands/rotate.js\";\nexport { decryptValue, encryptValue, isEncryptedValue } from \"../shared/encryption.js\";\nexport type { EncryptionContext } from \"../shared/encryption.js\";\nexport { resolveEncryptionKey } from \"../shared/encryption-key.js\";\nexport type { KeySource, ResolvedKey } from \"../shared/encryption-key.js\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;","names":[]}
1
+ {"version":3,"sources":["../src/lint/index.ts","../src/key/index.ts"],"sourcesContent":["/** Programmatic API for manni lint. */\nimport { clearTemplateCaches } from \"./core/template-registry.js\";\nimport { clearMatcherCache } from \"./core/resolve-template.js\";\nimport { clearPatternCache } from \"./rules/index.js\";\n\n/**\n * Drop every process-lifetime cache: parsed built-ins, fetched template files,\n * compiled override globs, and compiled heading patterns.\n *\n * A CLI run never needs this - it lints one docset and exits, and the caches\n * are what stop a tree of one doctype from re-reading the same template per\n * page. A long-lived host is the different case: the maps grow with the number\n * of distinct refs and patterns seen, and a cached remote template is served\n * for the life of the process, so one republished upstream is never picked up.\n * Call this between runs, or when a template may have changed underneath.\n */\nexport function clearCaches(): void {\n clearTemplateCaches();\n clearMatcherCache();\n clearPatternCache();\n}\n\nexport { runLint, STDIN_LABEL } from \"./commands/lint.js\";\nexport type {\n LintOptions,\n LintRun,\n LintSummary,\n LintFileResult,\n} from \"./commands/lint.js\";\nexport { runTemplates, BUILTIN_SOURCE } from \"./commands/templates.js\";\nexport type {\n TemplateInfo,\n TemplatesInfo,\n TemplatesOptions,\n} from \"./commands/templates.js\";\nexport { runTools, BUILT_IN_DEFAULTS } from \"./commands/tools.js\";\nexport type { FormatInfo, ToolInfo, ToolsOptions } from \"./commands/tools.js\";\nexport {\n LINT_JOBS,\n LINT_TOOLS,\n TOOLS_BY_JOB,\n isLintTool,\n loadConfig,\n parseConfig,\n rebaseConfig,\n resolveLintRun,\n} from \"./core/config.js\";\nexport type {\n LintConfig,\n LintJob,\n LintJobConfig,\n LintRunOptions,\n LintTool,\n ResolvedLintRun,\n} from \"./core/config.js\";\nexport { validateDocument, validateSections } from \"./core/validator.js\";\nexport { matchSections } from \"./core/match.js\";\nexport type { Match, MatchResult } from \"./core/match.js\";\nexport {\n BLOCK_KINDS,\n headingMatches,\n isWildcard,\n occurrenceRange,\n} from \"./core/template.js\";\nexport type {\n AdmonitionsRule,\n AttributesRule,\n BlockKind,\n BlockRule,\n CodeBlocksRule,\n ElementsRule,\n HeadingRule,\n ImagesRule,\n ListItemsRule,\n ListsRule,\n Occurrences,\n ParagraphsRule,\n Rule,\n TablesRule,\n Template,\n TemplateFile,\n} from \"./core/template.js\";\nexport {\n listBuiltins,\n loadTemplate,\n loadTemplateFile,\n resolveExtends,\n classifyRef,\n} from \"./core/template-registry.js\";\nexport type {\n BuiltinInfo,\n LoadTemplateOptions,\n RefKind,\n TemplateResolver,\n} from \"./core/template-registry.js\";\n// Targets resolve through the family's walker (`src/meta/internal.ts`), which\n// is what makes `--ext`, `--exclude`, `.gitignore` handling and `--allow-empty`\n// mean the same thing here as they do for `meta` and `cite`.\nexport { STDIN_TOKEN } from \"../meta/internal.js\";\nexport {\n listFormats,\n parserByName,\n parserForExtension,\n supportedExtensions,\n} from \"./parsers/index.js\";\nexport {\n render,\n renderPretty,\n renderJson,\n renderGithub,\n renderJunit,\n renderTemplates,\n renderTools,\n} from \"./reporters/index.js\";\nexport type {\n ReportFormat,\n ReportOptions,\n ListFormat,\n} from \"./reporters/index.js\";\nexport { renderSarif } from \"./reporters/sarif.js\";\nexport type { SarifOptions } from \"./reporters/sarif.js\";\n// A finding's durable identity, so a caller filing or correlating findings\n// spells the id the same way every reporter does.\nexport { RULE_ID_PREFIX, TOOL_NAME, ruleId, ruleName } from \"./core/rule-id.js\";\n// Colour is the family's decision, made once in `src/shared/color.js`. These\n// are re-exported here so a library caller building lint's reports does not\n// have to reach for another tool's entry point to turn ANSI on or off.\nexport { palette, shouldColor } from \"../shared/color.js\";\nexport * from \"./types.js\";\n","/**\n * The key domain's programmatic API, exported from the package as the `key`\n * namespace: `import { key } from \"@hawkeyexl/manni\"`. The command cores, and\n * the family's encryption primitives a caller needs beside them.\n */\nexport * from \"./types.js\";\nexport { KeyError } from \"./errors.js\";\nexport { runKeySet } from \"./commands/set.js\";\nexport { runKeyRotate } from \"./commands/rotate.js\";\nexport { decryptValue, encryptValue, isEncryptedValue } from \"../shared/encryption.js\";\nexport type { EncryptionContext } from \"../shared/encryption.js\";\nexport { resolveEncryptionKey } from \"../shared/encryption-key.js\";\nexport type { KeySource, ResolvedKey } from \"../shared/encryption-key.js\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAAAA;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,sBAAAC;AAAA,EAAA;AAAA,oBAAAC;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAAAC;AAAA,EAAA;AAAA;AAAA;AAAA,gBAAAC;AAAA,EAAA;AAAA;AAAA,qBAAAC;AAAA,EAAA;AAAA,qBAAAC;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,6BAAAC;AAAA,EAAA;AAAA;AAAA;AAgBO,SAAS,cAAoB;AAClC,sBAAoB;AACpB,oBAAkB;AAClB,oBAAkB;AACpB;;;ACpBA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;","names":["classifyRef","listBuiltins","loadConfig","parseConfig","render","renderJunit","renderSarif","supportedExtensions"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hawkeyexl/manni",
3
- "version": "3.0.0",
3
+ "version": "4.0.1",
4
4
  "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.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -18,7 +18,9 @@
18
18
  "./package.json": "./package.json"
19
19
  },
20
20
  "files": [
21
- "dist"
21
+ "dist",
22
+ "schemas",
23
+ "templates"
22
24
  ],
23
25
  "repository": {
24
26
  "type": "git",
@@ -47,6 +49,8 @@
47
49
  "lint": "eslint .",
48
50
  "check:deps": "node scripts/check-deps.mjs",
49
51
  "query:ui": "npm run build && node dist/cli.js meta query --db .manni/meta/query.db && node scripts/query-ui.mjs .manni/meta/query.db",
52
+ "check:tgdp-pin": "node scripts/lint-check-tgdp-pin.mjs",
53
+ "smoke:lint": "npm run build && node scripts/lint-smoke.mjs",
50
54
  "docs:check-cli": "node scripts/check-cli-reference.mjs",
51
55
  "docs:check-action": "node scripts/check-action-reference.mjs",
52
56
  "docs:check-api": "node scripts/check-api-reference.mjs",
@@ -77,6 +81,8 @@
77
81
  "esbuild": "^0.28.2"
78
82
  },
79
83
  "dependencies": {
84
+ "@apidevtools/json-schema-ref-parser": "^11.9.3",
85
+ "@asciidoctor/core": "^3.0.4",
80
86
  "@axe-core/playwright": "^4.13.0",
81
87
  "@hawkeyexl/inference": "^0.3.2",
82
88
  "@xmldom/xmldom": "^0.9.12",
@@ -86,11 +92,18 @@
86
92
  "commander": "^15.0.0",
87
93
  "fast-glob": "^3.3.2",
88
94
  "github-slugger": "^2.0.0",
95
+ "mdast-util-to-string": "^4.0.0",
89
96
  "parse5": "^8.0.1",
90
97
  "picocolors": "^1.1.1",
91
98
  "picomatch": "^4.0.7",
92
99
  "playwright-core": "^1.63.0",
100
+ "remark-frontmatter": "^5.0.0",
101
+ "remark-gfm": "^4.0.1",
102
+ "remark-mdx": "^3.1.1",
103
+ "remark-parse": "^11.0.0",
93
104
  "smol-toml": "^1.8.0",
105
+ "unified": "^11.0.5",
106
+ "unist-util-visit": "^5.1.0",
94
107
  "yaml": "^2.6.1"
95
108
  },
96
109
  "devDependencies": {
@@ -103,8 +116,10 @@
103
116
  "@semantic-release/github": "^12.0.8",
104
117
  "@semantic-release/npm": "^13.1.5",
105
118
  "@semantic-release/release-notes-generator": "^14.1.1",
119
+ "@types/mdast": "^4.0.4",
106
120
  "@types/node": "^26.3.0",
107
121
  "@types/picomatch": "^4.0.3",
122
+ "@types/unist": "^3.0.3",
108
123
  "@vitest/coverage-v8": "^4.1.11",
109
124
  "eslint": "^10.9.1",
110
125
  "globals": "^17.11.0",
@@ -0,0 +1,73 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "manni-lint:config:1",
4
+ "title": "manni lint config section",
5
+ "description": "The `lint:` section of a shared `manni.config.yaml`. The file's root is a mapping of tool name to that tool's settings, so this schema describes the section only - never the file. The document set is not here: proposal 0041 declares it once for every tool, under the family-level `collections:` key, and `paths`/`exclude` under `lint:` are refused by name. Every key is optional: a repo that configures no manni lint at all is a legitimate config, and so is one that carries only a sibling tool's section. `additionalProperties: false` applies at every level below `lint:`, which is what turns a typo'd key into a loud failure instead of a silent default.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "properties": {
9
+ "allowEmpty": {
10
+ "description": "Treat a run that matched zero files as a success rather than an operational error. The same key, with the same meaning, as `cite.allowEmpty`: for a shared CI template or a pre-commit hook whose file list may legitimately be empty. `--allow-empty` on the command line sets it for one run.",
11
+ "type": "boolean"
12
+ },
13
+ "overrides": {
14
+ "description": "Repo policy: everything under a glob is linted with one template, whatever the pages say they are. First matching entry wins, and an override outranks a page's own `type`.",
15
+ "type": "array",
16
+ "items": {
17
+ "description": "One policy entry. Both keys are required: an entry naming no template, or no files, is a line that silently does nothing.",
18
+ "type": "object",
19
+ "additionalProperties": false,
20
+ "required": ["files", "template"],
21
+ "properties": {
22
+ "files": {
23
+ "description": "Glob matched against each file's path, written with forward slashes whatever the platform uses.",
24
+ "type": "string",
25
+ "minLength": 1
26
+ },
27
+ "template": {
28
+ "description": "Template ref applied to the files this entry matches.",
29
+ "type": "string",
30
+ "minLength": 1
31
+ }
32
+ }
33
+ }
34
+ },
35
+ "structure": {
36
+ "description": "The structure job, which `manni lint structure` and `manni lint check` both run. Declaring it with no keys is how a repo says the job applies. Its one setting is the tool that performs it.",
37
+ "type": "object",
38
+ "additionalProperties": false,
39
+ "properties": {
40
+ "tool": {
41
+ "description": "Which tool performs the job. `manni` is manni's own template-structure engine; `dita-ot` is DITA Open Toolkit, which validates a DITA map and the topics it reaches and is located through the top-level `tools.dita-ot.home`. `--tool <name>` on the command line names the same thing.",
42
+ "type": "string",
43
+ "enum": ["manni", "dita-ot"]
44
+ }
45
+ }
46
+ },
47
+ "template": {
48
+ "description": "Template ref applied to a page that declares no doctype and matches no override. The last stage of the resolution chain, not an override: `--template`, a page's `$template`, an `overrides` entry, and the page's own `type` all outrank it.",
49
+ "type": "string",
50
+ "minLength": 1
51
+ },
52
+ "templates": {
53
+ "description": "Template files to load. Their templates' `types:` join the doctype routing table, outranking the built-ins, so adopting a repo's own how-to shape is one entry here and no flag.",
54
+ "type": "array",
55
+ "items": {
56
+ "type": "string",
57
+ "minLength": 1
58
+ }
59
+ },
60
+ "types": {
61
+ "description": "Explicit doctype -> template ref map, for a doctype no template file claims with its own `types:`. Keys are the vocabulary a page's `type` frontmatter is written in, so they are the author's names rather than this schema's - constrained only to the shape a doctype slug takes, not to a fixed set.",
62
+ "type": "object",
63
+ "additionalProperties": false,
64
+ "patternProperties": {
65
+ "^[A-Za-z0-9._:-]+$": {
66
+ "description": "A template ref: a built-in id, a path, or a URL, optionally with a `#name` fragment.",
67
+ "type": "string",
68
+ "minLength": 1
69
+ }
70
+ }
71
+ }
72
+ }
73
+ }
@@ -0,0 +1,346 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "manni-lint:template:2",
4
+ "title": "manni lint template file",
5
+ "description": "A template file: named doctype templates, plus the reusable fragments they point at with `$ref`. Validated after dereferencing, so no `$ref` survives to be seen here. A template is itself a rule: it describes the page, and it takes every rule key plus `title`, `types` and `extends`.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": ["templates"],
9
+ "properties": {
10
+ "$schema": {
11
+ "description": "URL of this schema, so an editor can offer completion. Read by editors, never by the linter.",
12
+ "type": "string"
13
+ },
14
+ "info": {
15
+ "description": "Free-form metadata about the file (title, version, author). Never read by the linter; declared so a file that carries it is not rejected.",
16
+ "type": "object"
17
+ },
18
+ "templates": {
19
+ "description": "The doctype templates this file defines, keyed by name.",
20
+ "type": "object",
21
+ "additionalProperties": false,
22
+ "patternProperties": {
23
+ "^[A-Za-z][A-Za-z0-9-_]*$": { "$ref": "#/definitions/template" }
24
+ }
25
+ },
26
+ "components": {
27
+ "description": "Reusable fragments, targeted by `$ref` from elsewhere in the file. Left unconstrained: a fragment is validated where it is inlined, not where it is declared.",
28
+ "type": "object"
29
+ }
30
+ },
31
+ "definitions": {
32
+ "min": {
33
+ "description": "Fewest occurrences. Absent means one. Deliberately carries no JSON Schema `default`, and neither does any other key in this file: Ajv's `useDefaults` would write one in at load time, and a written-in value is indistinguishable from a value the author typed, so it wins the `extends` merge against the parent's real value. The code reads `undefined` instead.",
34
+ "type": "integer",
35
+ "minimum": 0
36
+ },
37
+ "max": {
38
+ "description": "Most occurrences. Absent is unbounded, so write `max: 1` for exactly once and `max: 0` to forbid. Must be at least `min` where both are written; the loader checks that, because JSON Schema cannot compare two siblings.",
39
+ "type": "integer",
40
+ "minimum": 0
41
+ },
42
+ "id": {
43
+ "description": "Handle for this rule, unique among its siblings. It appears in messages, and `extends` targets it to replace one inherited rule in place.",
44
+ "type": "string",
45
+ "pattern": "^[A-Za-z][A-Za-z0-9-_]*$"
46
+ },
47
+ "description": {
48
+ "description": "Prose description of what the rule is for. Never checked.",
49
+ "type": "string"
50
+ },
51
+ "heading": {
52
+ "description": "The heading of the section this rule describes. Absent means any heading, or none. A string is exact text, a list is one-of, `{ pattern }` is an unanchored regular expression, and `false` means the section has no heading of its own.",
53
+ "oneOf": [
54
+ { "type": "string", "minLength": 1 },
55
+ {
56
+ "type": "array",
57
+ "minItems": 1,
58
+ "items": { "type": "string", "minLength": 1 }
59
+ },
60
+ {
61
+ "type": "object",
62
+ "additionalProperties": false,
63
+ "required": ["pattern"],
64
+ "properties": {
65
+ "pattern": { "type": "string", "minLength": 1 }
66
+ }
67
+ },
68
+ { "const": false }
69
+ ]
70
+ },
71
+ "attributes": {
72
+ "description": "Literal attributes the node must carry. A string is the required value; `true` requires the attribute to be present, and `false` requires it to be absent.",
73
+ "type": "object",
74
+ "additionalProperties": {
75
+ "oneOf": [{ "type": "string" }, { "type": "boolean" }]
76
+ }
77
+ },
78
+ "oneOfSequenceOrContains": {
79
+ "description": "A rule says what it holds in order (`sequence`) or in any order (`contains`), never both.",
80
+ "type": "object",
81
+ "not": { "required": ["sequence", "contains"] }
82
+ },
83
+ "noHeadingOnRepeat": {
84
+ "description": "`repeat` groups sibling rules into one repeated unit, so the group has no heading of its own. Put the heading on the first rule inside it.",
85
+ "type": "object",
86
+ "not": { "required": ["heading", "repeat"] }
87
+ },
88
+ "noInstructions": {
89
+ "description": "`instructions` was the language-model hook of doc-structure-lint. manni lint is deterministic and has no model, so a template carrying it is rejected outright rather than silently ignored; the loader turns this into a migration message pointing at manni docevals.",
90
+ "type": "object",
91
+ "not": { "required": ["instructions"] }
92
+ },
93
+ "template": {
94
+ "description": "One doctype template. A template is a rule describing the page itself, so it takes every rule key, plus `title`, `types` and `extends`.",
95
+ "type": "object",
96
+ "additionalProperties": false,
97
+ "allOf": [
98
+ { "$ref": "#/definitions/noInstructions" },
99
+ { "$ref": "#/definitions/oneOfSequenceOrContains" },
100
+ { "$ref": "#/definitions/noHeadingOnRepeat" }
101
+ ],
102
+ "properties": {
103
+ "title": {
104
+ "description": "Human-readable name for the template, shown by `manni lint templates`.",
105
+ "type": "string"
106
+ },
107
+ "types": {
108
+ "description": "Doctypes this template serves, matched against a page's `type` frontmatter.",
109
+ "type": "array",
110
+ "items": { "type": "string", "minLength": 1 }
111
+ },
112
+ "extends": {
113
+ "description": "Template reference (built-in id, path, or URL, optionally with a `#name` fragment) whose rules this template inherits.",
114
+ "type": "string",
115
+ "minLength": 1
116
+ },
117
+ "id": { "$ref": "#/definitions/id" },
118
+ "description": { "$ref": "#/definitions/description" },
119
+ "heading": { "$ref": "#/definitions/heading" },
120
+ "repeat": { "$ref": "#/definitions/repeat" },
121
+ "min": {
122
+ "description": "Refused on a template: a page is one page. Declared here only so the loader can say that in a sentence, rather than leaving Ajv to report an unexpected key.",
123
+ "type": "integer",
124
+ "minimum": 0
125
+ },
126
+ "max": {
127
+ "description": "Refused on a template, for the same reason as `min`.",
128
+ "type": "integer",
129
+ "minimum": 0
130
+ },
131
+ "sequence": { "$ref": "#/definitions/sequence" },
132
+ "contains": { "$ref": "#/definitions/blockRules" },
133
+ "sections": { "$ref": "#/definitions/rules" }
134
+ }
135
+ },
136
+ "rule": {
137
+ "description": "One rule. It describes a section of the page, or, with `repeat`, a run of sections matched as one unit.",
138
+ "type": "object",
139
+ "additionalProperties": false,
140
+ "allOf": [
141
+ { "$ref": "#/definitions/noInstructions" },
142
+ { "$ref": "#/definitions/oneOfSequenceOrContains" },
143
+ { "$ref": "#/definitions/noHeadingOnRepeat" }
144
+ ],
145
+ "properties": {
146
+ "id": { "$ref": "#/definitions/id" },
147
+ "description": { "$ref": "#/definitions/description" },
148
+ "heading": { "$ref": "#/definitions/heading" },
149
+ "repeat": { "$ref": "#/definitions/repeat" },
150
+ "min": { "$ref": "#/definitions/min" },
151
+ "max": { "$ref": "#/definitions/max" },
152
+ "sequence": { "$ref": "#/definitions/sequence" },
153
+ "contains": { "$ref": "#/definitions/blockRules" },
154
+ "sections": { "$ref": "#/definitions/rules" }
155
+ }
156
+ },
157
+ "rules": {
158
+ "description": "Rules for the subsections, in document order.",
159
+ "type": "array",
160
+ "items": { "$ref": "#/definitions/rule" }
161
+ },
162
+ "repeat": {
163
+ "description": "Rules matched in order as one unit, repeated by this rule's `min` and `max`.",
164
+ "type": "array",
165
+ "minItems": 1,
166
+ "items": { "$ref": "#/definitions/rule" }
167
+ },
168
+ "sequence": {
169
+ "description": "What the section holds, in order. Each entry is one run of one content kind.",
170
+ "type": "array",
171
+ "minItems": 1,
172
+ "items": {
173
+ "allOf": [
174
+ { "$ref": "#/definitions/blockRules" },
175
+ { "type": "object", "maxProperties": 1 }
176
+ ]
177
+ }
178
+ },
179
+ "blockRules": {
180
+ "description": "Block rules, keyed by content kind. The key is plural because it carries a count: the node model says `codeBlock`, a template says `codeBlocks: { min: 1 }`.",
181
+ "type": "object",
182
+ "additionalProperties": false,
183
+ "minProperties": 1,
184
+ "properties": {
185
+ "paragraphs": { "$ref": "#/definitions/paragraphs" },
186
+ "codeBlocks": { "$ref": "#/definitions/codeBlocks" },
187
+ "lists": { "$ref": "#/definitions/lists" },
188
+ "tables": { "$ref": "#/definitions/tables" },
189
+ "admonitions": { "$ref": "#/definitions/admonitions" },
190
+ "images": { "$ref": "#/definitions/images" },
191
+ "blockquotes": { "$ref": "#/definitions/counted" },
192
+ "definitionLists": { "$ref": "#/definitions/counted" },
193
+ "elements": { "$ref": "#/definitions/elements" }
194
+ }
195
+ },
196
+ "counted": {
197
+ "description": "A block rule that only counts.",
198
+ "type": "object",
199
+ "additionalProperties": false,
200
+ "properties": {
201
+ "min": { "$ref": "#/definitions/min" },
202
+ "max": { "$ref": "#/definitions/max" }
203
+ }
204
+ },
205
+ "paragraphs": {
206
+ "description": "Paragraph requirements.",
207
+ "type": "object",
208
+ "additionalProperties": false,
209
+ "properties": {
210
+ "min": { "$ref": "#/definitions/min" },
211
+ "max": { "$ref": "#/definitions/max" },
212
+ "pattern": {
213
+ "description": "Unanchored regular expression every paragraph in the run must match.",
214
+ "type": "string",
215
+ "minLength": 1
216
+ }
217
+ }
218
+ },
219
+ "codeBlocks": {
220
+ "description": "Code block requirements.",
221
+ "type": "object",
222
+ "additionalProperties": false,
223
+ "properties": {
224
+ "min": { "$ref": "#/definitions/min" },
225
+ "max": { "$ref": "#/definitions/max" },
226
+ "language": {
227
+ "description": "Language every code block must be tagged with, as the node model spells it. A list is one-of: the block must declare one of them.",
228
+ "oneOf": [
229
+ { "type": "string", "minLength": 1 },
230
+ {
231
+ "type": "array",
232
+ "minItems": 1,
233
+ "items": { "type": "string", "minLength": 1 }
234
+ }
235
+ ]
236
+ },
237
+ "fenceInfo": {
238
+ "description": "Info-string tail after the language, where the format carries one.",
239
+ "type": "string"
240
+ }
241
+ }
242
+ },
243
+ "lists": {
244
+ "description": "List requirements.",
245
+ "type": "object",
246
+ "additionalProperties": false,
247
+ "properties": {
248
+ "min": { "$ref": "#/definitions/min" },
249
+ "max": { "$ref": "#/definitions/max" },
250
+ "ordered": {
251
+ "description": "Whether the list must be ordered. Absent accepts either.",
252
+ "type": "boolean"
253
+ },
254
+ "items": { "$ref": "#/definitions/listItems" }
255
+ }
256
+ },
257
+ "listItems": {
258
+ "description": "Requirements applied to every item of every list in the run. Recurses arbitrarily deep.",
259
+ "type": "object",
260
+ "additionalProperties": false,
261
+ "allOf": [{ "$ref": "#/definitions/oneOfSequenceOrContains" }],
262
+ "properties": {
263
+ "min": { "$ref": "#/definitions/min" },
264
+ "max": { "$ref": "#/definitions/max" },
265
+ "sequence": { "$ref": "#/definitions/sequence" },
266
+ "contains": { "$ref": "#/definitions/blockRules" }
267
+ }
268
+ },
269
+ "tables": {
270
+ "description": "Table requirements.",
271
+ "type": "object",
272
+ "additionalProperties": false,
273
+ "properties": {
274
+ "min": { "$ref": "#/definitions/min" },
275
+ "max": { "$ref": "#/definitions/max" },
276
+ "columns": {
277
+ "description": "The header cells, in order.",
278
+ "type": "array",
279
+ "items": { "type": "string" }
280
+ }
281
+ }
282
+ },
283
+ "admonitions": {
284
+ "description": "Admonition requirements.",
285
+ "type": "object",
286
+ "additionalProperties": false,
287
+ "properties": {
288
+ "min": { "$ref": "#/definitions/min" },
289
+ "max": { "$ref": "#/definitions/max" },
290
+ "variant": {
291
+ "description": "Which flavor of admonition. The node model's `variant`, not any one format's spelling.",
292
+ "enum": [
293
+ "note",
294
+ "tip",
295
+ "important",
296
+ "caution",
297
+ "warning",
298
+ "danger"
299
+ ]
300
+ }
301
+ }
302
+ },
303
+ "images": {
304
+ "description": "Image requirements.",
305
+ "type": "object",
306
+ "additionalProperties": false,
307
+ "properties": {
308
+ "min": { "$ref": "#/definitions/min" },
309
+ "max": { "$ref": "#/definitions/max" },
310
+ "url": {
311
+ "description": "Where the image must point.",
312
+ "type": "string"
313
+ },
314
+ "alt": {
315
+ "description": "Alternative text the image must carry.",
316
+ "type": "string"
317
+ },
318
+ "attributes": { "$ref": "#/definitions/attributes" }
319
+ }
320
+ },
321
+ "elements": {
322
+ "description": "Named-wrapper requirements. `sequence` and `contains` here describe what the element holds, which is never the enclosing section's content.",
323
+ "type": "object",
324
+ "additionalProperties": false,
325
+ "allOf": [{ "$ref": "#/definitions/oneOfSequenceOrContains" }],
326
+ "properties": {
327
+ "min": { "$ref": "#/definitions/min" },
328
+ "max": { "$ref": "#/definitions/max" },
329
+ "tag": {
330
+ "description": "The element's name, as an author of the format would write it. A list is one-of: an element counts when it carries any of them.",
331
+ "oneOf": [
332
+ { "type": "string", "minLength": 1 },
333
+ {
334
+ "type": "array",
335
+ "minItems": 1,
336
+ "items": { "type": "string", "minLength": 1 }
337
+ }
338
+ ]
339
+ },
340
+ "attributes": { "$ref": "#/definitions/attributes" },
341
+ "sequence": { "$ref": "#/definitions/sequence" },
342
+ "contains": { "$ref": "#/definitions/blockRules" }
343
+ }
344
+ }
345
+ }
346
+ }