Continuous Integration
DocGuard gates a pipeline with exit codes and writes reports in formats that CI systems read. This page covers the GitHub Action, starter workflows for GitHub Actions, and recipes for GitLab CI, Azure Pipelines, CircleCI, Jenkins and plain shell.
In the examples, replace X.Y.Z with the DocGuard version you have reviewed. The maintained source for these recipes is docs-canonical/CI-RECIPES.md.
Before you add a gate
- Adopt DocGuard in the repository first. Commit
.docguard.json. Without it, guard exits3, which means “not initialised”. See Existing Projects. - On an existing codebase, commit a baseline. Otherwise the first run fails on every finding that already exists. See Baselines in CI.
- Pin the version. Use
npx docguard-cli@X.Y.Z, a pinned devDependency, or a pinned action tag. - Clone with full history. Freshness,
diff --since,specs requireandspecs completeread Git history. A shallow clone makes them partial or inconclusive. - Run against the revision that will merge. Use the pull request’s merge commit or a merge queue trigger, not an older branch head.
- Require the check in branch protection. Adding a workflow does not make it required.
Choose the command
guard | ci | |
|---|---|---|
| Runs | Every enabled validator | guard, then the structural maturity score |
| Fails on a low score | No | Yes, with --threshold <n> |
| Output formats | text, json, sarif, junit | text, json |
| Strict on warnings | Warnings already exit 2 | --fail-on-warning turns warnings into exit 1 |
| History | Not recorded | Appends to .docguard/history.jsonl unless --no-history |
| Uninitialised project | Exit 3 | Exit 1 |
Use guard when you need SARIF or JUnit. Use ci when you also want a minimum score, or a single READY / ATTENTION / BLOCKED verdict in JSON. Both use the committed baseline and the severity overrides in .docguard.json, so the same findings give the same verdict from both.
The score measures documentation structure, not whether the documentation is correct. Use guard findings for enforcement; a high score alone proves neither.
Exit codes
| Command | 0 | 1 | 2 | 3 |
|---|---|---|---|---|
guard (any format) | Pass | Errors, or an invalid flag combination | Warnings only | Errors in a project with no .docguard.json |
ci | READY | BLOCKED: guard errors, a score below --threshold, or warnings with --fail-on-warning | ATTENTION: warnings only | (not used) |
specs require | Covered | A governed change names no spec | Inconclusive: unknown base, shallow history or an oversized inventory | (not used) |
specs --check | Registry current | (not used) | Registry missing, stale or inconsistent | (not used) |
verify --evidence | Every declaration verified | A contradiction, or an invalid manifest | Stale, inconclusive or unsupported evidence | (not used) |
A plain shell step fails on any non-zero exit, so by default warnings fail the job. To let warnings pass, capture the status and allow only 0 and 2:
status=0
npx --yes docguard-cli@X.Y.Z guard || status=$?
[ "$status" -eq 0 ] || [ "$status" -eq 2 ]
Any other status is a failure, including a missing executable or a crash. Do not treat it as a clean scan.
guard accepts --fail-on-warning, but it does not change guard’s exit code, because warnings already exit 2. To make warnings fail, treat 2 as a failure (the shell default), use ci --fail-on-warning, or set the action’s fail-on-warning: 'true'.
Report formats
| Flag | Output | For |
|---|---|---|
--format json | The guard or ci result as JSON | Scripts, dashboards, agents |
--format sarif (guard only) | SARIF 2.1.0: finding codes become rules, locations become regions | GitHub code scanning and other SARIF viewers |
--format junit (guard only) | JUnit XML, one test case per validator | GitLab, Jenkins, Azure DevOps, CircleCI test reports |
With a machine format, stdout contains only the report, and notes go to stderr. A large report is flushed completely before the process exits, so > report.json is safe.
Each SARIF result carries the finding’s channels in properties: disposition, confidence, evidence, parserTier, reportable, suggestionKind, and the intrinsic and effective severities. Base automation on disposition, not on severity alone:
act: DocGuard names the correction.escalate: a person must judge it. Applying an automated fix to an escalation silences the signal without resolving it.
A parserTier of regex-fallback or fallback-language means no syntax tree was available for that file. The absence of findings there is weak evidence.
Baselines in CI
On a repository that already has findings, freeze them once and gate only new ones:
npx docguard-cli@X.Y.Z guard --update-baseline # writes .docguard.baseline.json
git add .docguard.baseline.json
guard and ci then suppress the frozen findings and fail only on new ones. The suppression is always reported: a “N pre-existing finding(s) suppressed” line in text output, and baselineSuppressed in JSON. --no-baseline shows everything for one run. "baseline": false in .docguard.json turns the baseline off.
--update-baselinecannot be combined with--changed-only. The baseline must freeze the full validator set.- Regenerate the baseline only in a reviewed change, so the diff shows what was accepted.
GitHub Actions
The DocGuard action
name: DocGuard
on:
pull_request:
push:
branches: [main]
permissions:
contents: read
pull-requests: write # only for the sticky PR comment
jobs:
docguard:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- uses: raccioly/docguard@vX.Y.Z
with:
command: guard
The action installs the docguard-cli version it was released with, so pinning raccioly/docguard@vX.Y.Z also pins the CLI. For a stronger pin, use a full commit SHA and keep the release tag in a comment.
Inputs (from action.yml):
| Input | Default | Description |
|---|---|---|
command | guard | guard, score, spec-first, fix, sync, audit or diff. Any other value is passed to the CLI as a command name. |
format | text | text or json. SARIF and JUnit are not available through this input; see SARIF to code scanning. |
fail-on-warning | false | With guard: fail on warnings. Without it, warnings (exit 2) pass with a ::warning. |
score-threshold | 0 | With score: fail when the score is below this value. |
working-directory | . | Project directory to check, for example one package of a monorepo. |
node-version | 20 | Node.js version to set up. |
docguard-version | empty | Empty installs the CLI version the action was released with. Accepts x.y.z or latest. |
annotations | true | With guard: inline ::error / ::warning annotations, one per finding, capped at 50. |
pr-comment | true | With guard on pull_request events: one sticky comment with the verdict, the top findings and the canonical docs that the changed files affect. Needs pull-requests: write. Without it, the action logs a warning. |
auto-commit | false | With fix or sync: commit the mechanical fixes to the PR branch. Needs contents: write. Skipped for pull requests from forks. |
commit-message | docs: apply DocGuard mechanical fixes | Message for the auto-commit. |
comment-on-pr | false | With fix or sync: post a summary comment on the PR. |
bot-name / bot-email | docguard-bot / docguard-bot@users.noreply.github.com | Author of the auto-commit. |
Outputs: score and grade (with command: score), result (the JSON result, with command: score and format: json), fixes-applied, changed-files and committed (with fix or sync).
Annotations and the PR comment run even when guard fails, because that is when they matter. The action passes every input and every piece of event data, such as the PR body and branch names, through environment variables. None of it is spliced into a script.
The action does not run ci with a threshold. score-threshold applies only to command: score. To combine guard and a minimum score in one step, call the CLI directly, as the starter workflow below does.
Starter workflow
docguard init --with ci writes .github/workflows/docguard.yml from templates/ci/github-actions.yml. It keeps an existing workflow; --force backs it up and replaces it. The starter:
- checks out the full history with
persist-credentials: false; - installs a pinned
docguard-cliwith--ignore-scripts; - runs
docguard ci --format json --threshold 70 --no-history; - validates that the report is JSON and records its verdict from the real exit code;
- writes the report to the job summary and uploads it as an artifact.
Its actions are pinned to commit SHAs. Review the pins and the threshold before you commit it.
The Spec Kit extension ships two more starters in extensions/spec-kit-docguard/templates/github-workflows/:
docguard-guard.ymlrunsguard --format json. Warnings do not block unless you set itsFAIL_ON_WARNINGvariable to'true'. On pull requests it also captures the score.docguard-autofix.ymlapplies mechanical fixes on pull requests.
SARIF to code scanning
Write SARIF with the CLI and upload it with GitHub’s upload-sarif action. The repository must have code scanning available.
permissions:
contents: read
security-events: write
jobs:
docguard:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- uses: actions/setup-node@v4
with: { node-version: '20' }
- name: DocGuard (SARIF)
id: docguard
run: |
status=0
npx --yes docguard-cli@X.Y.Z guard --format sarif > docguard.sarif || status=$?
echo "status=$status" >> "$GITHUB_OUTPUT"
- uses: github/codeql-action/upload-sarif@v4
if: always()
with:
sarif_file: docguard.sarif
- name: Gate (allow warnings)
env:
STATUS: $
run: '[ "$STATUS" -eq 0 ] || [ "$STATUS" -eq 2 ]'
Uploading comes before the gate, so findings reach code scanning even when the job fails.
Spec-first gate
Make every pull request that changes governed code name its spec:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- uses: raccioly/docguard@vX.Y.Z
with: { command: spec-first }
The action runs `docguard specs require –since origin/