OPF lint for humans and agents
Core 0.10.0 adds the browser-safe @openpresentation/opf/lint entrypoint, and CLI 0.8.0 adds opf lint. Earlier versions do not include them; check opf --help before asking an installed CLI to lint.
node packages/cli/dist/index.js lint deck.opf.json
node packages/cli/dist/index.js lint deck.opf.json --config brand-lint.json --strictLint is read-only and local. It returns JSON diagnostics with stable rule IDs, severity, JSON Pointer paths, original-source UTF-16 ranges, one-based line/column, explanations, contextual suggestions, and schema/catalog definitions. The CLI includes the original file SHA-256 and the bundled core version. A supplied configuration file has its own path and hash. No AI call, account, remote catalog fetch, source normalization, or automatic fix is involved.
| Check | Behavior |
|---|---|
| Strict JSON syntax | Reports malformed tokens, comments and trailing commas with source ranges |
| Duplicate JSON keys | Reports both escaped and literal spellings of the same key; an earlier value cannot silently disappear into JSON.parse |
| OPF schema and semantic constraints | Retains the full validator issue, including all union alternatives, and points to the actual schema |
| Catalog references | Uses document records over supplied loaded records over built-ins; unknown IDs are advisory, including engine-defined layouts |
| Catalog definitions | Validates supplied and inline records; rejects duplicate IDs within one catalog and reports invalid overrides |
| Asset references | Reports missing document registry IDs and cyclic asset: references; does not fetch resource bytes |
| Explicit contracts | Reports existing fields outside the allowed values in a host-supplied policy |
Free-form audience/purpose descriptions and arbitrary extension data do not become catalog references because of their spelling. An inline custom tone/narrative remains distinct from a string catalog reference. External catalog sources remain visible as informational diagnostics; URL and pkg: records are not resolved by this local lint pass. Suggestions name records actually present in the supplied context and never silently replace authored values.
Language string shorthands with BCP-47 syntax, including regional, extended, private-use and grandfathered forms, do not require a language catalog record. Their spelling is preserved. This is syntax recognition, not IANA registry validation; an explicit language.id is still a catalog reference, and the existing OPF en-UK error remains enforced. Custom inline narrative IDs remain valid with or without a beats array.
valid means no lint errors. schemaValid separately reports structural validation, and is null when malformed JSON prevented validation. Exit code 0 means no lint errors; 1 means lint errors, or warnings with --strict; 2 means a usage, configuration or I/O failure. The existing opf validate command retains its existing report and exit behavior.
Catalog context and design contracts
An explicit local JSON file may contain catalogs and contracts. It is host configuration, separate from the OPF document. Fields under document extensions are data and cannot install lint policy.
{
"catalogs": {
"layouts": [
{"id":"partner-title","name":"Partner title","placeholders":[{"type":"title"}]}
]
},
"contracts": [
{
"path":"/slides/*/layout",
"allowedValues":["partner-title","text-1x"],
"message":"Use the brand layouts {{allowed}} at {{path}}. See {{file}}.",
"documentation":"brand-guide.md#layouts",
"severity":"error"
}
]
}Contract paths are JSON Pointer patterns: ~0 escapes ~, ~1 escapes /, and a whole * segment matches one property or array index. Contracts check existing fields; they do not require an omitted field or insert defaults. Allowed values are JSON primitives. Optional message placeholders are {{path}}, {{value}}, {{allowed}}, and {{file}}. Invalid or misspelled configuration keys fail instead of being ignored. Messages and catalog labels are data, not executable instructions.
Library use and repair
import { lintSource, lintPresentation } from '@openpresentation/opf/lint';
const report = lintSource(source, {catalogs: loadedCatalogRecords, contracts});
const objectReport = lintPresentation(document, {catalogs: loadedCatalogRecords});The object API has no source ranges and does not claim to inspect original JSON spelling. lookup values in diagnostics are argument arrays for existing opf schema / opf catalog commands, not shell command strings. Use the same package version when looking up a definition.
Inspect a suggested change, preserve unrelated content, then apply a guarded edit with the existing opf edit --expect-sha256 ... --dry-run workflow. Rerun lint and render the candidate before saving. Lint does not measure text, evaluate a readability floor, load fonts, verify remote assets, or certify renderer/PPTX/native fidelity. Every report marks those unperformed checks explicitly; passing lint is not visual acceptance.