Skip to content

Optional CLI and result contract

The independent Go executable boundary, versioned diagnostics, isolated validation, and guarded maintenance plans for the current local CLI candidate.
Current scope; local candidate

This contract describes the reduced local 0.1.0-dev command surface on 2026-10-04. Keep site diagnosis, real Hugo checks, initialization, builds, upgrades, and guarded maintenance plans. Cobra provides command help; colored English text is the default, with JSON/YAML results available. Studio, general editing, context, snippets, editor setup, and CI generation are retired. Historical R1–R8 acceptance applies only to its recorded source and binaries; it does not replace validation of the current implementation. No public CLI release, Homebrew distribution, or deployment is established.

Context and ownership

The theme is a Hugo module; consumer tooling is an optional executable with a different installation and release lifecycle. pgsty/oink-cli owns that executable, named oink, and its Go tests. It invokes an external Hugo binary without importing Hugo’s private runtime or depending on a sibling checkout, Python, Node.js, or unpublished theme scripts at runtime.

Hugo owns configuration resolution, rendering, routes, and anchors. The CLI inspects Hugo’s effective configuration, module graph, mounts, and rendered files. It does not create a second route resolver, navigation authority, or configuration namespace. Theme-only regression scripts remain maintainer tools. Configuration preprocessing relocates workspace, replacement, and cache paths only in the temporary copy. Hugo still owns defaults, configuration merging, language selection, validation, and rendering semantics. The public architecture contract continues to own theme behavior; this page owns the initial CLI boundary and result envelope.

The usage guide contains installation and command examples. The dated acceptance record separates executed checks from open limitations and release states. The maintenance acceptance record preserves the earlier R1–R8 program and its source-bound evidence. The roadmap retains future proposals and adoption hypotheses rather than duplicating the current command reference.

Command and mutation boundary

Help groups commands by daily, maintenance, and release work. Run oink COMMAND --help for the exact options.

Command Behavior and mutation boundary
doctor Read-only toolchain, configuration, module source, workspace/replacement/vendor diagnosis
check [links|translations|style] Actual Hugo output and declared source/translation policy in isolated copies
init DIRECTORY Validate the fixed Starter before creating a new or empty site
dev, build Ordinary Hugo processes; Hugo owns normal output/cache writes
upgrade --to TAG Preview by default; only --write applies verified module changes
translations status, translations diff PAGE Read-only relationships, hash-review states, and differences
translations review SOURCE TARGET, baseline capture Explicit reviewer/reason; preview with optional new --plan
new BUNDLE --title TEXT, move SOURCE TARGET Validate a candidate and preview its full diff; optional new --plan
plans apply FILE Revalidate supported saved plans; write only selected files in the selected site
inspect PAGE, impact --since REF Read-only actual page and historical/current impact facts
workspace list, workspace check [GROUP] Select only explicitly registered sites
build --check Check, seal, and export the same isolated production render
artifacts verify Compare local artifacts with a manifest offline
verify Compare deployed HTTP responses with a manifest after explicit --network

Network access is off by default. All commands are non-interactive. Only dev and build accept Hugo arguments after --. Removed commands and their old plans cannot apply. Supported plan kinds are authoring.new, translations.review, baseline.capture, and content.move.

Versioned result envelope

Option Output
Default Concise colored English text
--json, -J One JSON oink.result/v1 object
--yaml, -Y One YAML document with the same result fields and types
--verbose, -v All findings, coverage details, and tool logs
--no-color Plain English text

Choose one structured format. Nonempty NO_COLOR or TERM=dumb also disables text colors. Structured output adds no terminal colors. Tool logs go to stderr. --format json|yaml and --non-interactive remain hidden compatibility options. Every command is non-interactive.

Default text shows status, counts, up to eight active findings, and explicit coverage omissions. Detailed facts and reviewed findings remain available in structured results. Plan and upgrade previews retain their complete diffs. Cobra owns command dispatch and focused help. CLI messages use short, active English sentences inspired by ASD-STE100; this does not assert certification. User content and external tool evidence retain their original language.

Field Type and meaning
schema_version String; oink.result/v1 for this contract
version String; CLI build version, including a development suffix when applicable
command String; requested command, or help / version
site Optional string; selected source or generated target directory when available
exit_code Integer; the CLI result code defined below
diagnostics Array of findings; an empty array means no recorded findings
coverage Array of scoped coverage statements; callers must inspect these alongside findings
evidence Array of subprocess records; empty when no subprocess ran
data Optional command-specific JSON value; current commands return objects such as inspected facts, initialization provenance, or an upgrade plan

The JSON Schema describes this envelope.

The first version permits additive fields and new rule IDs. Consumers should ignore unknown fields and treat IDs as opaque strings, not parse their spelling. A change to the meaning or type of an existing envelope field requires a new schema version. Command-specific facts and raw tool output are evidence, not an SDK for importing internal Go packages.

The machine-readable schema is shipped in the CLI repository as schema/result.v1.schema.json. Its identifier is not evidence that a schema endpoint or public CLI release has been deployed.

Each evidence record has command (an argument array), optional directory, stdout, stderr, and the subprocess’s exit_code. Captured inspection and build output remains available in the result. Directly streamed dev/build output is sent to the logging stream rather than buffered again in evidence. The subprocess status remains distinct from the CLI’s 0/1/2 result; a negative subprocess status can indicate that no normal exit code was obtained.

Findings, severity, and locations

Every diagnostic has rule_id, severity, message, and action, plus an optional location. Stable rule IDs identify the condition. Raw Hugo wording, translated messages, paths, and build-specific details are not stable IDs. Existing IDs must not be reassigned to a different condition.

The optional incomplete: true identifies a required-work failure that policy cannot downgrade. Reviewed exclusions and baseline acknowledgements retain the finding in diagnostics with disposition: "excluded" or "baseline" and review containing reason, reviewed_by, and RFC 3339 reviewed_at. An excluded finding remains visible with its recorded severity; it does not block the completed policy check. Any uncompleted required coverage, including not_checked, determines exit 2; only complete or not_applicable satisfies required coverage.

The severity vocabulary is info, warning, and error. error is blocking. info and warning are advisory unless the underlying required Hugo build fails under --panicOnWarning, in which case required work is incomplete. Informational completion and scope explanations belong in coverage details. Automation must use the result exit code and coverage, not only count severities.

When present, location contains file, with optional kind, line, and pointer. kind distinguishes source from output. A rendered finding points to the actual artifact and may include an element, attribute, or JSON location hint in pointer; that field does not universally claim RFC 6901 syntax. A line number is included only when known. A rendered link failure does not justify inventing a Markdown source line.

The CLI does not reject unknown valid front matter merely because a generated editor schema omits it. Configuration validity continues to follow Hugo and the owning theme resolver/checker; see the generated schema decision.

Coverage and exit semantics

Each coverage entry contains id, status, required (boolean), and detail. Entries describe the scope that actually ran. A report may contain several statements about the same broad area; inspect all of them.

Status Meaning
complete The stated operation, inspection, or artifact-check scope completed
not_checked This run did not inspect the stated scope
not_applicable The stated check is unnecessary for these inputs
unsupported The stated contract or required input shape is unsupported
incomplete The stated work was required but could not finish
CLI exit code Meaning
0 Requested required work completed without blocking findings
1 Completed checks identified a policy violation, such as a broken local link or an unsafe requested write
2 Required work is incomplete, including argument, tool, build, I/O, cancellation, or required unsupported-contract failures

Incomplete work takes precedence over policy findings. Required coverage that has not completed cannot produce success; only complete or not_applicable can satisfy it. Hugo build failure preserves the raw evidence and stops output acceptance; the CLI does not report stale or partial output as a passing check. Disabled optional machine outputs do not become missing-output errors.

The rendered-reference scope includes supported HTML URLs and anchors and supported emitted machine contracts. Coverage explicitly excludes browser interaction, accessibility, visual presentation, external URL availability, hosting redirects, and production deployment. It also identifies uninspected dynamic resources and content semantics. Static output evidence does not prove undeclared translation coverage, semantic translation equivalence or browser execution.

Hugo’s public Page.OutputFormats supplies expected artifact names and URLs per page and per enabled language. The isolated copy adds an unlisted probe with a unique per-run identifier; each enabled language must emit its own verified manifest. Identified probe files are removed before artifact checks. Existing authored pages retain their output selections. Static content hidden from ordinary page lists is resolved through Hugo’s GetPage, without deriving its route or output filename from source syntax. Effective per-language base URLs and local-search settings accompany that enumeration. Enabled supported machine artifacts are checked against these exact expectations; optional disabled outputs remain optional.

The same Hugo probe supplies data.pages through public Page.Path, Page.File, Page.Translations, Page.Aliases, and Page.OutputFormats. Facts retain language, actual URLs, publication settings, translation relationships and declared outputs. sourceKnown: false and sourceScope: "unknown" identify pages without proven source provenance; generated sections do not receive invented source files. Site-owned known paths are relative to the selected site. Known copied dependency inputs have explicit dependency scope. These facts describe the production view; a separate internal analysis view can include drafts, future and expired pages without changing production artifacts or claiming they are published.

data.references records observed HTML and machine-output references, their actual resolved URL, output file/pointer, local target when present, and anchor status when checked. It does not infer a Markdown source line. Page and reference data remain additive command evidence, not a public Go SDK.

Project check policy

An optional regular oink.yaml at the selected site’s root uses schema_version: oink.policy/v1. It owns check selection, severity overrides, reviewed finding exclusions, reviewed external URL scopes, translation scopes, protected prose declarations and the optional baseline file path. Hugo inputs continue to own languages, titles, URLs, menus and configuration; module files own dependency versions. A symlink, unknown key/group, unsupported version, invalid review metadata or multiple YAML documents is required input failure (exit 2). Diagnosis and checks only read this policy.

Without a policy, links, translations and style are enabled and required. check links, check translations, or check style explicitly selects one required group, regardless of its policy selection. Unselected or disabled optional groups report not_checked. Every check invocation retains the required strict Hugo build and output-enumeration prerequisites. Standalone translation/source checks also use an explicit nonpublishable draft/future/expired analysis view; it never replaces production artifacts. Managed build --check renders only the production view and returns 2 for unknown required scope identities.

The rules mapping assigns error, warning or info to exact opaque rule IDs. A reviewed exclusions entry requires rule_id, a clean relative file glob, reason, reviewed_by and RFC 3339 reviewed_at; ** patterns and path escape forms are unsupported. Findings remain visible with review metadata. Site-local source locations match against their relative site path; source paths outside the selected site cannot match an exclusion. Required build, input, tool or coverage failure cannot become success through severity changes or exclusions.

Same-origin HTML references outside the configured base path are policy findings unless a reviewed external_scopes URL declares a separately deployed path scope. Each scope needs the same review metadata and an absolute HTTP(S) URL without credentials, query or fragment. Matching uses complete path segments. The scope cannot exempt missing targets inside the project or required local machine-output targets. Different-origin references remain explicitly unverified by offline static checks.

Translation policy and review evidence

translations.scopes selects source pages using clean absolute Hugo Page.Path prefixes, then finds targets through Hugo’s translation identity. It does not infer a public route or language from a filename. Each scope has path, source_language, required_languages, mode and drafts. mode defaults to localized; strict is also supported. drafts defaults to include; ignore excludes draft sources/targets, and require-published requires the selected source and required targets to exist in production. Known disabled Hugo languages are not_applicable; unknown languages are invalid policy. The most specific matching path owns a source page.

Without scopes, existing pairs rooted in Hugo’s enabled default language and duplicate relationships are inspected. Universal localization is not required; translations.coverage records the unconfigured language scope as optional not_checked. Missing required targets and duplicate selected relationships are policy findings. Draft/publication state is independent of review state.

Constraints are opt-in: explicit_ids compares the full recognized explicit-ID map in strict mode; localized mode requires a selected ids list. ids requires each named ID in both files, placeholders compares exact declared prose-literal counts, and code_labels protects fenced blocks with each named language/info token. required_fields requires nonempty dotted front matter fields in both files; equal_fields compares their actual values. No rule requires matching heading counts, translated prose or all code blocks by default.

.oink/translations.json uses oink.translations/v1. Explicit review records bind Hugo source/target IDs, source language, complete source/translation byte SHA-256 values, reviewer, reason and RFC 3339 time. An absent record is unknown; equal hashes are current; source-only, target-only or both changes are source_changed, translation_changed or both_changed. These are evidence of changes since review, not semantic judgments. File modification time never establishes review. Unproven sources stay unknown; a recorded review or protected constraint that cannot be verified produces required incompletion.

Native content rules and coverage

Source rules extract evidence from Markdown structure and independent enabled Hugo attributes, preserving original UTF-8 bytes, CRLF/BOM, source offsets and YAML/TOML/JSON front matter with unknown fields. Actual effective Hugo attribute switches and configured math passthrough delimiters control recognition. Fenced/inline code, shortcode bodies, raw HTML and passthrough contents do not become prose or invented headings. Unsupported body syntax remains visible; required source coverage cannot silently pass.

Generic rules detect duplicate recognized explicit IDs and evaluate declared style.protected entries with file, exact prose literal and expected count. The bounded OINK v1.1.0 catalog adds advisory code/table attribute, deprecated front matter and dropped unsafe-attribute findings. Each rule records module, version, immutable revision, module sum, license and exact source-file SHA-256 provenance in data.native_rule_provenance.

The catalog runs only when the actual mounted public v1.1.0 module-cache inputs match those hashes. A replacement, vendor copy, other version or unknown source does not select a latest-theme fallback: native-theme-rules is optional not_checked, while generic syntax checks still run. This catalog does not certify every custom component or theme feature.

Baselines and reviewed file plans

baseline selects a clean relative file, default .oink/baseline.json, using oink.baseline/v1. Capture requires completed work and explicit review metadata. The fingerprint binds exact rule ID, normalized location/pointer and condition message; it excludes severity. Acknowledged findings stay visible with disposition: "baseline" and original severity; new conditions still block according to policy. Required incomplete findings/coverage cannot be acknowledged.

Review and capture preview oink.plan/v1 with selected edits, readable diffs, base existence/bytes/modes, after bytes and read guards. The plan ID excludes mutable validated/applied/recovery state. --plan FILE exclusively saves the plan; these commands do not accept --write. plans apply FILE --site DIR requires that exact selected site, fresh isolated candidate validation through the same checks and rechecked source guards before any write. Escapes, .git, symlinks and nonregular files are protected; overlapping candidate/source trees are refused. Stale plans fail safely. Optional external_inputs_hash binds captured non-site input bytes, full modes and inventory into the plan ID. This opaque SHA-256 grants no external paths or permission to read them. The owning validator compares fresh proven inputs; trusted original external guards are rechecked around selected writes.

Exclusive installation preserves a file created during commit. Partial failures restore owned unchanged writes; subsequent editor bytes, modes or deletions remain intact. The reported recovery directory keeps original bytes/modes and actual concurrent captured evidence. Unrelated files and new editor children are preserved. No file content authorizes shell execution or publication.

Captured page inspection and impact

inspect PAGE selects an actual Hugo page by exact language:path ID, Hugo Path, permalink or proven site-owned source file. A known default language resolves a multilingual Path; remaining ambiguity or an unknown selector is required incomplete (2). data.inspection exposes source byte hash/full mode, actual output identities, observed inbound/outbound references, translations and physical bundle attachments. Physical attachments are distinguished from observed published resources.

impact --since REF compares captured current inputs and an isolated committed Git tree rendered by the same Hugo engine. It retains deleted prior pages and their inbound edges, unchanged referring pages, translation peers, attachments and actual derived outputs. Global or uncertain inputs expand causal scope; an unowned actual HTML output such as an alias also forces conservative full scope. Alias declarations never create guessed route ownership. Historical inputs in proven Git mode scopes compare only Git’s executable bit; other module/foreign inputs and current facts retain full modes.

Historical materialization reads bounded Git objects without checkout hooks, filters, smudge execution or document execution. The limits are 10,000 files, 16 MiB per file and 128 MiB per tree; required private history is bounded at 256 MiB. Symlinks, submodules, capped or missing objects, required incomplete history and unsupported monorepo GitInfo are explicit incomplete states. Committed site-owned dependencies can be proven. Current external local replacement/workspace bytes cannot substitute for historical evidence.

data.impact.baseline_state is complete, incomplete or unavailable. When the required baseline is unavailable, the result returns 2, retains all known current pages/attachments/references/outputs and expands full scope. It creates no prior pages or invented changes; only an actually resolved commit is recorded. check [GROUP] --since REF deliberately performs the full current check and declares data.check_scope: full; no incremental speed or partial validation claim is made. data.impact.full_scope describes causal uncertainty separately from that validation scope.

Completed inspect and impact fact queries return 0 even when their separately reported data.current_check has completed quality findings (1). Required capture failures remain top-level 2. check --since keeps the current policy’s quality exit code and required completion precedence.

context is removed. Read page facts through the JSON/YAML report from inspect.

Content move plans

move SOURCE TARGET [--plan FILE] previews a physical site-relative file or bundle relocation. Actual Hugo identities determine translation peers and old/new outputs. The plan includes byte/full-mode-preserved files and binary attachments, readable diffs, proven Markdown destination rewrites, observed route changes and alias advice. Front matter is not rewritten to install aliases. Raw HTML, shortcode output, transformed destinations and ambiguous source/output ownership stay visible manual actions; opaque source spans are not changed. Repeated ordinary Markdown destinations can also lack a unique source/output occurrence proof, including aggregate/print appearances. Matching URLs alone do not authorize rewriting those occurrences. A relocated physical attachment does not prove its new published URL. Resource URL changes require paired actual rendered edges and equal emitted bytes; a proven processed image URL does not prove an absolute original-resource URL. Unproven original URLs stay manual and are not constructed from the directory move.

The original before check and provisional route_probe are separate from the final candidate check. The provisional relocation may expose findings 1 from stale inbound links. A plan is validated or saved only after the final isolated candidate and reference proof pass. Unsupported identity or required capture failure returns 2; an actual final quality failure remains 1 and cannot save an applicable plan.

Content move plans must be saved outside the selected site. Their additive oink.plan/v1 move selectors and source_inputs_hash bind the complete raw source inventory, bytes and full modes; external-input and fresh-directory guards also apply. Saved apply regenerates the original/relocated Hugo proof and requires exact expected plan ID and files before final reference verification. It rechecks current guards before writes, restores raw original modes rather than private-copy modes, and preserves later editor bytes/modes on refusal or guarded recovery. Stale inputs or an occupied fresh target lack the required proof and return 2. Only explicit plans apply writes selected files; previews never stage or commit Git changes.

Supported input boundary

Initial full validation supports materialized files in a normal checkout or without Git metadata, including supported local module replacements copied into the isolated tree. It does not follow mounted symlinks or external mounts back into the user’s workspace. Auxiliary symlinks outside effective mounts are omitted rather than validated. Linked Git worktrees with a .git file need a materialized review copy; Git-dependent behavior needs a copy with its own Git metadata.

The snapshot excludes top-level public, resources, node_modules, tmp, and the Hugo build lock. A mount that needs excluded input cannot silently pass. Root configuration and the standard config tree are supported; explicit configuration files must be inside the selected site, and custom HUGO_CONFIGDIR locations are refused. Supported configuration relocation is not a second implementation of Hugo validation.

Content adapters (_content.gotmpl) can create unlisted pages that cannot be enumerated completely through the supported public Hugo APIs. Full output validation therefore reports incomplete work for those inputs. A disabled page kind or render segment that omits an enabled language’s probe is also incomplete. Multihost language configuration is outside the first full-check scope and returns incomplete work; multilingual paths on a single host remain supported. These cases must not be presented as a successful partial check.

Ordinary content plans

new BUNDLE --title TEXT [--language LANG] [--translations LANGS] [--kind page|docs|blog|book] [--plan FILE] previews an ordinary Hugo leaf bundle through captured site-owned content mounts. The primary language falls back to the effective default; selected peers must be distinct enabled languages. Shared filename and language-directory layouts follow actual Hugo mounts, including observed sites.matrix.languages selection rather than an assumed legacy lang field. Ambiguous, filtered or unsupported mappings require manual authoring. Existing bundles or sibling files owning that page are refused.

The primary index has draft: false; selected peer indexes have draft: true. The title is a supplied literal, not translated text. No review record is created. Full quality analysis and isolated candidate validation precede the shared guarded plan. Every proposed new file must map to exactly one actual site-owned Hugo source page with actual rendered outputs, including translation drafts in the explicit analysis view. Link-only/no-output, ignored, hidden or build-never content cannot pass merely because the existing site renders cleanly; required identity is checked even when ordinary source-check groups are disabled. Saving --plan creates only a new plan file; explicit plans apply FILE --site DIR revalidates and checks source bytes/modes, existence, fresh directories and later attachment conflicts before applying. Fresh-directory state is bound into the plan identity and checked before/after validation, between writes and at completion. Rollback preserves later editor attachments and reports recovery; it does not delete unrelated directory entries.

Editor settings and Markdown snippets belong to the site editor. editor and snippets are removed.

Initialization profiles

init DIR [--profile project|docs|blog|book] [--languages en|en,zh|all] composes one embedded MIT-licensed Starter archive. The default project retains the previous complete language projection byte-for-byte. The language selection is independent of the content profile: all means English, Chinese and French.

Explicit docs, blog, and book retain their corresponding archived content section and shared home, assets, examples, workflows and license. Native section front matter defines their documentation, blog or sequential book model and navigation. Each language’s site title/description comes from its archived section; existing localized home cards/actions/CTA are projected to that section. Only their generated hugo.yaml and data/home YAML files are serialized; retained content/license bytes stay unchanged. There are no four copied Starter trees or runtime template downloads.

Unknown profiles are policy refusals (1) before candidate validation or writes. Missing/failed required Hugo validation remains incomplete (2). New and empty-target, candidate-before-publication, exclusive creation and concurrent-edit recovery protections apply to every profile. Ordinary Hugo builds each generated site with provisioned dependencies. Archived workflows remain source examples; init does not generate or execute the checksum-bound R3 CI templates.

Bounded upgrade comparison

upgrade --to TAG now captures baseline and candidate views from the same original site inputs and returns a readable module diff with full mode changes. Its comparison records actual Hugo pages/outputs/language settings, emitted file hashes/sizes/modes, raw alias declarations and separately observed alias redirect files. It reports removed/added URLs, proven redirects, alias target/ byte changes and enabled language/output/search changes. A clean candidate build alone does not prove route or capability preservation.

A previous URL is preserved only when an observed redirect at its exact old output file targets the corresponding actual candidate page. Unknown/relative custom alias identity remains required incomplete (2). Removed previously emitted routes or outputs are blocking findings (1). Both resolved theme versions must match the explicitly selected pins; unknown/substituted pins, unknown/different normalized Hugo versions or environments, or unexpected other input changes remain incomplete. Comparison supports one HTTP(S) base origin/path; multihost inputs stay incomplete. No configuration migration transform or universal browser/theme compatibility is claimed; manual review remains explicit optional unchecked coverage. Observed aliases that retarget a different unique Hugo page are blocking independently of same-page URL moves.

The v2 upgrade plan ID binds the selected module plan, copied source bytes/full modes/file inventory and normalized actual comparison. --expect-plan ID checks a fresh capture/comparison, not a previously saved successful build. Because emitted file hashes are bound, nondeterministic templates can require a refreshed preview even when source files appear unchanged. Guards are rechecked before returning a preview, before each write and after writes, including proven local dependency/workspace inputs read-only. Only selected module files are written; rollback restores only unchanged files owned by the operation and preserves later editor bytes. Existing dirty target, replacement and vendor safeguards remain in force.

File preservation and release checks

Initialization embeds the complete Starter commit and preserves its license. The provenance records its hash and every projection: independent content/language selection, the exact public theme pin/checksums, and disabling Git metadata for a fresh directory. The default project retains prior bytes; selected content profiles share the same archived source and license. Candidate validation precedes target writes. Exclusive creation refuses existing files; rollback removes only unchanged files created by that invocation and preserves concurrent user edits with recovery evidence.

Upgrade owns only a single site’s selected go.mod and go.sum changes. It preserves unrelated dependencies and directives, refuses dirty target files for --write, checks the reviewed plan ID when provided, rechecks targets before writing, and records backups/recovery. Unrelated dirty source files do not block read-only diagnosis or justify overwriting them.

check --release disables GOWORK and HUGO_MODULE_WORKSPACE and removes the environment replacement for the subprocess. It preserves go.mod replacements, reports conflicting local OINK replacement policy, and keeps vendor evidence separate from the public requirement. Upgrade refuses an OINK replacement and refuses _vendor; vendor refresh remains a separate explicit workflow. No module pin change is described as updating vendor bytes. If Hugo actually selects vendored OINK, --release reports required public-source verification as incomplete (exit 2); matching version metadata is not proof that the vendor bytes match the public tag. Ordinary check still validates the actual vendor build.

Configured Hugo module replacements are also disabled only in the release snapshot. If Hugo changes module files in that snapshot during resolution or build, the CLI reports that dependency inputs need explicit preparation and review. It preserves the original bytes rather than silently accepting a build that depended on an unreviewed generated module-file change.

Checked builds and artifact identity

build --check --destination DIR --manifest FILE uses an isolated, warning-strict production build. Hugo renders once; the check engines inspect that output, then seal and export those same bytes. It never invokes a second renderer to create the publication tree. The source checkout remains unchanged. Only an outcome of 0 with complete or inapplicable required coverage can be sealed. Blocked or incomplete checks do not create a verified export.

This production view does not include the separate nonpublishable maintenance render. An explicit scoped translation policy whose required Hugo identities are unknown because publication excludes their sources returns 2. It does not infer missing translations from filenames or silently skip the scope. Standalone check and translations retain the full maintenance view.

The destination must be new or empty, with an existing parent; the local manifest must be a new file outside that tree. Export uses exclusive creation, preserves exact bytes and full regular-file modes regardless of umask, and rechecks source and destination against the manifest. Existing entries, symlinks and overlapping trees are refused. Failed partial exports remain unverified evidence and are preserved. Empty directories and directory modes are outside the published file inventory.

--marker is optional and off by default. It adds .well-known/oink-build.json with only oink.build-marker/v1 and the artifact ID. That file’s entry is excluded from artifact-ID calculation to avoid a circular hash, then its exact digest is included in the final inventory. An existing marker path is refused. The manifest is never copied into the public tree automatically.

The separately saved oink.artifact/v1 manifest records the source-input hash, known source Git revision and dirty state, actual resolved theme identity, CLI/Hugo versions, effective environment/base URL/release settings, required coverage, actual Hugo route contexts and each file’s relative path, size, full mode and SHA-256. Canonical URLs and HTML language values come from emitted HTML; Hugo language keys remain separate. Unknown Git state stays unknown. Original input bytes and modes are captured before temporary probe overlays or workspace/replacement path rebasing. The public manifest omits absolute local paths, arbitrary arguments, process logs and free-form coverage details. Hashes prove byte identity, not a signature or publication of a local checkout.

Managed builds accept only the boolean Hugo flags --minify, --gc, --ignoreCache and --noTimes after --, including =true/=false forms. Other passthrough flags are unsupported inputs. Ordinary build keeps its existing transparent passthrough behavior. Effective example/local addresses are warnings during ordinary diagnosis and errors for checked release builds. --release still requires separate evidence for actual public theme resolution; a local Git revision or declared pin does not attest to vendor/replacement bytes.

Local and deployed verification

artifacts verify --artifact DIR --manifest FILE is read-only and offline. It compares the exact file set, bytes, sizes and full modes. Changed, missing, additional, unsafe or mode-changed files invalidate identity (1). Invalid manifests, unreadable inputs and unsupported or interrupted inspection return 2. Verify the export again immediately before an uploader consumes it; later edits cannot inherit a previous successful result.

verify --site URL --manifest FILE --network explicitly authorizes HTTP reads. It checks every declared file and distinct actual Hugo route URL, including all language/subpath contexts, against the manifest’s bounded response size and decoded-byte digest. Recorded HTML canonical/language values and an enabled marker are checked when available. Shared URL/file requests may be coalesced without removing their recorded contexts. HTTP cannot verify local file-mode bits.

A wrong body, soft-404, wrong route, changed captured canonical/language value or wrong marker is a conclusive finding (1). Timeouts, authentication failures, rate limiting, server unavailability and an absent required marker leave work incomplete (2). Redirects outside the selected origin/base path are blocked; the command does not discover or send credentials. Static build checks do not perform this deployment check. Network permission for a build does not authorize a later verification request or an upload.

Retired CI generation

ci init is removed. Keep CI configuration in the site or Starter. Local CLI validation does not execute hosted CI or deploy a site. Previously saved CI plans are rejected by plans apply.

Explicit workspace registry

R6 supported local scope accepted

The registry and optional-tool boundaries passed frozen owning/runtime, actual protocol, four-consumer parity/preservation and canonical source/render gates. A07 adapter and A15 workspace supported scope is accepted locally in the maintenance record. The recorded R1–R8 and A18 scope passed for its historical source and binaries; changes to the current CLI require new evidence.

A workspace is one explicitly supplied YAML registry, independently versioned as oink.workspace/v1. It contains only site names and directories:

schema_version: oink.workspace/v1
sites:
  - name: docs
    directory: ../docs-site
  - name: blog
    directory: ../blog-site

The registry must be a regular nonsymlink file containing exactly one YAML document with known fields, 1–64 entries and at most 256 KiB. Names match [A-Za-z][A-Za-z0-9_-]{0,63} and are case sensitive. Directories are literal relative paths from the registry’s actual parent, or absolute paths; variables, globs and sibling discovery are not evaluated. Explicit directory symlinks and operating-system aliases resolve to their canonical identity. Duplicate names, duplicate or overlapping actual roots, filesystem roots, dangling symlinks and nondirectory ancestors are rejected. Missing directories with a proven existing ancestor remain listed; checking one returns that site’s 2 without preventing later selected sites from being checked.

workspace list|check [GROUP] --workspace FILE [--sites NAME,NAME] selects all registered sites when --sites is omitted. Explicit selections require exact, nonempty, distinct registered names and retain registry order, including when the names were supplied in another order. list needs no Hugo renderer. check reuses the single-site engine and each site’s own Hugo inputs and oink.policy/v1 policy. It never duplicates Hugo configuration in the registry.

The existing oink.result/v1 envelope contains data.registry, selected_sites, sites: [{name, path, result}], completed_sites, finding_sites and incomplete_sites. Each child is a full single-site result. Completed sites include exits 0 and 1; finding sites are the 1 subset. The aggregate exit is 2 if any selected site is incomplete, otherwise 1 if any has blocking findings, otherwise 0. Human output includes each site’s result and findings. This aggregation does not infer completion for unselected sites.

Supported single-site commands accept --workspace FILE --site NAME, with one explicit registered name and no default site. init, artifacts and verify do not accept this selection. A saved plans apply FILE must bind to the selected canonical directory; selecting another registered site returns 2 before source writes. There is no automatic multi-site apply or upgrade. Existing candidate validation and source/dependency byte and mode guards still apply. Registry listing/checking neither provisions missing sites nor installs tools, commits or writes consumer configuration.

Optional check adapters

Explicit tools entries in each site’s oink.yaml select already provisioned executables. These entries extend oink.policy/v1; they are not a second Hugo configuration or an installer. Each kind has enabled (default true), required (default false), command (default the kind’s name), config (a clean site-relative regular file when supplied) and timeout_seconds (default 60 seconds; bounded nondefault values 1–300). Commands are one executable name or absolute path, not shell expressions.

Kind Owning check group Current supported protocol Configuration boundary
markdownlint style markdownlint-cli 0.49.1 Optional declarative JSON/YAML/TOML; no JS, JSONC, custom rules or extends
vale style Vale 3.24.0 Explicit INI plus captured styles from the supported declarative subset
lychee links lychee 0.24.2 Optional bounded request settings; explicit network consent

Unconfigured tools are not discovered. A tool outside the selected check group is visibly not_checked. A configured optional tool that is unavailable, unsupported or cannot complete leaves an omission; required incompletion returns 2 and cannot be downgraded by rule severity, exclusions or a problem baseline. Required and disabled cannot be combined. Completed typed findings still follow policy: blocking findings return 1. An unrecognized tool version or invalid protocol output does not count as a completed check.

data.adapters records each kind, requirement, status, typed diagnostics, adapter.KIND coverage, raw process evidence, omissions and provenance. Provenance includes the observed supported version, executable SHA-256, captured configuration/style paths with SHA-256 and full mode, and pinned public protocol sources. Tool logs stay in stderr and evidence; JSON stdout remains one result. Per-process time and output are bounded, and changed executables, captured configurations or tool-modified private inputs invalidate their evidence.

Prose tools receive private masked copies of proven site-owned Markdown. Front matter, BOM/CRLF and UTF-8 offsets, code, shortcodes, raw HTML, configured math and attributes retain their source boundaries. Code prose is outside source attribution; Markdown structure and fence/inline code boundaries remain available to markdownlint, while Vale receives the prose-only mask. Findings touching excluded or synthetic mask text are not attributed to original source. Source locations are emitted only for proven original lines/ranges; unsupported syntax and suppressed findings remain visible omissions. These adapters do not format or rewrite original content.

Markdownlint uses an unpredictable generated JSON pointer to isolate the captured rule object after upstream rc merging. Executable configs, custom rule loaders and recursive extends are refused. Vale uses an explicit captured INI, --no-global and copied declarative styles; sync, packages, actions, scripts, conversions and style pipelines are unsupported. Lychee accepts bounded timeout, max_retries and max_concurrency settings, plus the literal cache = false; caching remains disabled and cache = true is refused. Preprocessors and arbitrary command options are refused.

Offline is the default. Lychee is not invoked, including its version probe, unless --network is explicit: optional coverage is not_checked, required coverage is incomplete 2. It receives only observed external HTTP(S) references from actual Hugo output; local links remain the native check’s responsibility. Definitive failed 4xx responses are policy findings, except 401, 403, 408, 425 and 429; those, 5xx, DNS/TLS failures and timeouts are inconclusive, returning 2 when required and an omission when optional. External fragments, browser behavior and remote content identity are not verified. Findings retain the rendered output file and DOM pointer; no Markdown line is invented from an external URL.

Child processes do not receive caller proxy-URL/credential settings or Node preload variables. Literal NO_PROXY/no_proxy host-list data may be forwarded for the qualified runtime. This is not a promise that every operating-system proxy route is disabled, or an OS network sandbox. Tool preparation and any network operation remain separate explicit actions; no tool is installed by these commands.

Offline and compatibility boundary

Offline is the default for managed subprocesses. Dependency misses are incomplete work. --network explicitly enables network use for the current operation; it conflicts with --offline. Isolated checks may seed disposable caches from already provisioned local modules. Downloading into a disposable cache does not promise a persistent cache for the next invocation.

Only module download artifacts are seeded; isolated resource caches start fresh. The CLI does not reuse a global GetRemote cache to promise offline remote-resource builds. Required resources must be available as local inputs, or that operation must explicitly enable network access.

The CLI does not download a Go toolchain, install packages, alter global configuration, or enable telemetry. Its process policy is not an operating system network sandbox. Qualification records distinguish ordinary offline execution from tests that actually deny outbound access at the OS boundary.

Compatibility is declared from executed evidence, not inferred from a successful cross compilation. The local candidate has an exercised macOS arm64 path with Hugo Extended 0.166.0 and public OINK v1.1.0; the version gate accepts Hugo Extended 0.160.1 or newer without claiming all such versions were tested. Initialized sites retain normal Hugo inputs and require only their documented dependencies after removing the CLI.

Retired local Studio

studio is removed from the CLI. Use an ordinary editor and oink dev for a site preview. Read maintenance facts through inspect and structured reports. The dated R7 acceptance remains historical evidence for its identified inputs.

Retired management API

The CLI no longer serves a management API. Earlier Studio API acceptance does not describe the current executable.

Historical capture limits

Historical R7 limits belong to the dated acceptance record. Current command coverage and supported inputs are defined in this contract.

Retired general editing

edit and Studio editing are removed. Edit source with an ordinary editor, then run check. new, move, review records, and baseline plans retain candidate validation and byte/mode guards. Previously saved editing plans are rejected. The dated R8 record remains historical evidence.

Retired text and field editing

The CLI no longer owns general text or front matter editing forms.

Retired snippets and attachment editing

Write Markdown and add attachments with the site editor. The CLI no longer provides a snippet catalog or general attachment editing command.

Retired Studio editing

The CLI does not serve an editor or accept browser Apply requests.

Verification and remaining scope

Use make test for offline Go tests and vet. Use make test-hugo for actual Hugo integration and make test-tools for configured optional tools. Skipped integration cases are not passing runtime evidence. Current changes need new source/binary-bound evidence; an older record does not qualify them.

Current integration gate is incomplete

On 2026-10-04, make test passed on macOS arm64 with Go 1.27.1 and Hugo Extended 0.166.0. The make test-hugo run failed TestPublicR5CachedPublicModuleMovePreviewApplyAndOrdinaryHugo: module-collection text preceded the configuration JSON, and the move returned 2 with Hugo config did not return JSON. Candidate validation refused the operation and reported the source unchanged. An immediate targeted rerun passed. The intermittent failure remains unexplained; the rerun does not establish a passing full integration gate for the current candidate.

The dated maintenance acceptance record preserves earlier R1–R8 and A18 evidence. Declared targets are macOS arm64 and Linux arm64/amd64. Darwin amd64 is experimental and unqualified; Windows is unsupported. Archive creation, signing, distribution, consumer adoption, and deployment are separate states. This contract authorizes no automatic commit, push, publication, or deployment.