# docguard
> The enforcement tool for Canonical-Driven Development (CDD). Audit, generate, and guard your project documentation.

<!-- llms-full.txt — full-content form. The link-index form is llms.txt. -->
<!-- Generated by DocGuard (docguard llms --full). Regenerate after doc changes. -->

---

## docs-canonical/ARCHITECTURE.md
> System architecture, component boundaries, and tech stack

# Architecture

<!-- docguard:version 1.5.0 -->
<!-- docguard:status active -->
<!-- docguard:last-reviewed 2026-09-18 -->

| Metadata | Value |
|----------|-------|
| **Status** | ![Status](https://img.shields.io/badge/status-active-brightgreen) |
| **Version** | `1.5.0` |
| **Last Updated** | 2026-09-18 |
| **Project Size** | ~39K lines across `cli/` — measured 2026-09-18 with `wc -l` over `cli/**/*.mjs`; re-measure rather than trust this figure |

---

## System Overview

DocGuard is a near-zero-dependency Node.js CLI tool. It carries one exact-pinned npm runtime dependency, `@babel/parser`, for AST-accurate JS/TS parsing, and uses the developer's own `python3` (no pip/npm dependency) for an AST-accurate Python tier. Both parsers load **optionally**. Route and schema scanners retain bounded fallbacks; architecture analysis reports Python import coverage as unsupported when the interpreter is absent rather than fabricating a graph. Other language scanners retain their declared regex (beta) scope. It enforces **Canonical-Driven Development (CDD)** — a methodology where documentation is the source of truth. DocGuard audits, scores, and guards project documentation. It generates AI-actionable fix prompts and integrates with CI/CD pipelines.

It targets development teams and AI coding agents that need to maintain documentation quality across projects of any stack (JavaScript, Python, Java, etc.).

## Component Map

| Component | Responsibility | Location | Key Files |
|-----------|---------------|----------|-----------|
| **CLI Entry Point** | Argument parsing, config loading, command routing | `cli/` | `docguard.mjs` |
| **Commands** | User-facing commands (the Daily 5 — init/guard/diff/sync/score — plus situational tools including reconcile, retire, specs, and `init --with` scaffolders) | `cli/commands/` | `*.mjs` |
| **Document lifecycle** | Finds exact terminal-status docs and completed-task review candidates; explicit retirement removes documentation from active context only after its source revision is reachable from a retained Git ref | `cli/scanners/document-lifecycle.mjs`, `cli/validators/document-lifecycle.mjs`, `cli/commands/retire.mjs` | Scanner is read-only; retirement uses the shared multi-file transaction and remains explicit |
| **Spec lifecycle registry** | Projects immutable spec identities, reviewed lifecycle/lineage/scope, artifact digests, task state, qualified implementation/test evidence, bounded outcomes, and recovery tombstones into one byte-stable control file | `cli/scanners/spec-registry.mjs`, `cli/scanners/requirement-evidence.mjs`, `cli/validators/spec-registry.mjs`, `cli/commands/specs.mjs` | `specs --write` preserves reviewed fields; stale checks identify bounded field paths and distinguish canonical ordering from changed content; only committed, clean, digest-current lifecycle entries can defer traceability, while a current planned entry that is new, removed from the index, or modified pending commit remains advisory; `specs complete` is the only verified-delivery writer |
| **Reconciliation graph** | Inventories changed paths independently from bounded patch text, maps them to direct spec evidence, and keeps mechanical facts, approved intent, decisions, unrelated changes, and unsupported evidence separate | `cli/shared-git.mjs`, `cli/scanners/reconciliation.mjs`, `cli/commands/reconcile.mjs` | Timeout, overflow, parse failure, or incomplete inventory blocks a ready result; planning is read-only and `--write` never rewrites requirements |
| **Analyzer tier** | Decides which analyzer produced each piece of evidence at the two entry points (`parseJsTs`, `extractPythonFiles`), carries `tier`/`tierReason` on scanned items, reports `parserTier` on every finding, and downgrades a validator to `partial` when an AST-capable language was read by the pattern fallback | `cli/shared-source.mjs` (`tierFor`, `summarizeTiers`, `tierApplicability`), `cli/scanners/routes.mjs`, `cli/validators/api-surface.mjs` | Findings are retained; only coverage is downgraded. `fallback-language` is not a gap — no AST tier exists for that language |
| **Headline coverage** | Reports how many validators actually checked beside the passed/total check count, and caps the badge colour at `green` when any active validator was partial, unsupported, missing a prerequisite, or errored | `cli/commands/guard.mjs` (`badgeColor`, `incompleteCoverage`) | `no-matches` keeps the top grade: a validator that ran and found nothing applicable completed its job |
| **Finding channels** | Separates the three questions a finding answers — does CI block (`severity`), who decides (`disposition`), how well is the code measured (`evidence`), and which analyzer produced it (`parserTier`) — so a certain observation is never labelled uncertain and the feedback loop samples the labels that have never been validated | `cli/findings.mjs`, `cli/precision-evidence.mjs`, `cli/writers/sarif.mjs` | Channels are additive to the published finding contract; `confidence` keeps its meaning as the detector's certainty in its own observation |
| **Precision evidence** | Runs labelled synthetic and exact-commit public cases, separates deterministic results from observations, calculates null-safe quality metrics and confidence bounds, compares case-first baselines, persists them in a provenance envelope whose measure (`benchmark-precision`) and caveat are derived from the cases and re-verified on load, and projects per-code evidence into a generated module the CLI quotes at finding time | `benchmarks/`, `cli/precision-evidence.mjs`, `schemas/docguard-benchmark.schema.json`, `schemas/docguard-benchmark-baseline.schema.json`, `schemas/docguard-precision-evidence.schema.json` | External runs are explicit; third-party project code is never executed and disposable checkouts are removed by default |
| **Feedback fixtures** | Validates synthetic reproductions and opposite controls, reduces them under an explicit predicate, derives duplicate identities, and emits test-only contributions | `cli/feedback-fixture.mjs`, `cli/commands/feedback.mjs`, `schemas/docguard-feedback-fixture.schema.json` | Publication remains user-controlled; contribution generation requires reviewed redaction, scope, and benchmark-delta evidence |
| **Evidence-scoped verification** | Binds one exact Markdown statement to a typed JSON Pointer value, bounded file collection, static Python container literal, or saved upstream compatibility report and returns one of five explicit states | `cli/evidence/`, `cli/validators/evidence.mjs`, `cli/commands/verify.mjs`, `schemas/docguard-evidence.schema.json` | Reads stay local, bounded, non-executable, and symlink/private-path safe; direct verification exits 1 for contradiction/invalid input, 2 for unresolved evidence, and 0 only for verified or unconfigured evidence |
| **Managed Git hooks** | Installs bounded DocGuard blocks while preserving user-owned hook commands before and after them | `cli/commands/hooks.mjs` | Reinstall and removal use one outer marker pair, repair nested markers from affected releases, fail closed on enforcement errors, and fall through after success so user postludes execute |
| **Readiness assessment** | Combines guard enforcement and optional CI score policy without changing structural score semantics | `cli/assessment.mjs`, `cli/commands/ci.mjs`, `cli/commands/diagnose.mjs`, `cli/commands/report.mjs` | READY requires a passing guard and configured gates; ATTENTION carries advisory warnings; BLOCKED identifies failed enforcement |
| **Task-specific agent context** | Ranks exact task paths, qualified requirements, finding codes, identifiers, and bounded lexical overlap across current governed evidence | `cli/scanners/task-context.mjs`, `cli/commands/agent.mjs`, `schemas/docguard-task-context.schema.json` | Read-only and deterministic; excludes retired, unapproved, digest-stale, private, and unsafe material; abstains on weak relevance and never upgrades prose accuracy |
| **Cross-language import graph** | Resolves repository-local JS/TS and Python static imports for cycle and layer checks | `cli/validators/architecture.mjs`, `cli/scanners/py-ast.mjs` | Python supports regular flat/`src/` packages and explicit relatives; dynamic imports, runtime path changes, parse failures, missing interpreters, and ambiguous modules remain explicit limitations |
| **Repository-root guidance** | Detects a likely governing ancestor without changing the selected scan directory | `cli/repository-root.mjs`, `cli/docguard.mjs` | Requires ancestor DocGuard configuration or npm/pnpm membership, respects nested Git boundaries, and uses typed stderr diagnostics for machine modes |
| **Lifecycle transactions and context** | Stages registry, recovery, spec outcome, and current-context changes before any visible mutation and rolls the set back on write or validation failure | `cli/writers/file-transaction.mjs`, `cli/writers/spec-outcomes.mjs`, `cli/scanners/lifecycle-context.mjs` | Active context includes approved current spec pointers and content hashes; retired prose is excluded |
| **Validators** | Independent validation modules that check specific aspects of CDD compliance — all emitting structured findings with stable codes (the `CODES` registry in `findings.mjs`) | `cli/validators/` | `*.mjs` |
| **Scanners** | Project file scanners for test discovery, route detection, schema mapping, CDK/IaC, doc-tools, integrations, frontend surface, spec-kit, memory-plan, semantic claims, agent readability | `cli/scanners/` | `*.mjs` |
| **Writers** | Deterministic doc-mutation and output modules — section-addressable edits, mapped-role ownership authorization, mechanical fix registry, API-Reference writer, generate I/O + doc builders (split from generate.mjs), SARIF emitter (no LLM) | `cli/writers/`, `cli/shared-doc-roles.mjs` | Mapped human docs expose only unique `source=code` sections; new or explicitly generated single-role targets permit whole-document writes; all replacements use backups and `--force` cannot grant ownership |
| **Config** | Configuration loading, schema migration, validator policy, and exact finding-code policy | `cli/` | `config.mjs`, `shared.mjs` |
| **Shared** | Cross-cutting utilities — ignore/glob filters, Git-ignore-aware bounded indexing, package capability counts, source-root resolution, Git helpers, declaration-shaped requirement identity parsing, and the shared doc→code trace patterns used by both `trace` and the Traceability validator | `cli/` | `shared-ignore.mjs`, `shared-validator-surface.mjs`, `shared-source.mjs`, `shared-git.mjs`, `shared-requirements.mjs`, `shared-trace-patterns.mjs`, `shared.mjs` |
| **Templates** | Document skeletons (ARCHITECTURE, SECURITY, etc.) and slash command files for AI agents | `templates/` | `*.template`, `commands/*.md` |
| **Extension** | Spec Kit extension with 5 AI skills, 4 bash scripts, workflow hooks | `extensions/spec-kit-docguard/` | `skills/*/SKILL.md`, `scripts/bash/*.sh` |
| **Tests** | Per-validator unit tests + command-level integration tests using `node:test` | `tests/` | `*.test.mjs` |

## Tech Stack

| Category | Technology | Rationale |
|----------|-----------|-----------|
| Language | JavaScript (ES Modules) | Universal runtime, zero-friction `npx` usage |
| Runtime | Node.js ≥ 18 | Native `node:test`, `node:fs`, `node:child_process` |
| Dependencies | **One npm dep** — `@babel/parser` (exact-pinned, optional-load) | AST-accurate JS/TS parsing; minimal, vetted supply-chain surface |
| Optional external | `python3` (the developer's own) | AST-accurate Python route/schema/import parsing; not an npm/pip dependency; import-graph coverage abstains when absent |
| Package Manager | npm | Standard for Node.js CLIs |
| Testing | `node:test` + `node:assert` | Built-in, no test framework dependency |
| Docker | `Dockerfile` (MCP server image) | Published to GHCR for stdio MCP use; HTTP transport is also available with explicit configuration |

### Recognized Config Files

DocGuard recognizes and validates these project config files:

| File | Purpose |
|------|---------|
| `.docguard.json` | Project-level DocGuard configuration |
| `.docguardignore` | Per-project file exclusions (like `.gitignore`) |
| `vitest.config.ts` / `jest.config.ts` | Test runner config (scanned for custom test patterns) |
| `.storybook/` | Component documentation tool (detected for docs-coverage) |
| `.jules-setup.sh` | This repo's own Google Jules environment bootstrap script (internal tooling, not shipped) |
| `.pre-commit-hooks.yaml` | This repo as a pre-commit hook source — consumers reference `repo: raccioly/docguard` to run `docguard-guard` (changed-only) per commit |
| `glama.json` | Glama MCP directory metadata — declares repo maintainers so the Glama listing can be claimed/managed |
| `server.json` | Official MCP Registry manifest (`io.github.raccioly/docguard`) — server name, npm package, stdio transport |

## Layer Boundaries

The architecture separates command orchestration, validation, extraction, output, configuration, and shared utilities. The boundaries below describe responsibilities and permitted dependencies.

| Layer | Contains | Can Import From | Cannot Import From |
|-------|----------|----------------|--------------------|
| **Extension** (`extensions/spec-kit-docguard/`) | AI skills (SKILL.md), bash scripts, hooks, commands | CLI (via npx), Node.js built-ins | Isolated — spec-kit integration layer |
| **Commands** (`cli/commands/`) | User-facing command logic | Validators, Config (via `docguard.mjs` exports) | Isolated — each command is self-contained |
| **Validators** (`cli/validators/`) | Independent validation modules | Scanners, Shared utilities, Node.js built-ins | Cannot import from Commands or Writers |
| **Evidence** (`cli/evidence/`) | Strict manifest loading, exact Markdown selection, file-only adapters, scoped identities | Safe scanner primitives, Shared utilities, Node.js built-ins | Cannot execute project code, external tools, package managers, or network requests |
| **Scanners** (`cli/scanners/`) | Project intelligence — detect routes, schemas, IaC, frontend surface | Shared utilities, Node.js built-ins | Cannot import from Validators, Commands, Writers |
| **Writers** (`cli/writers/`) | Mutate canonical docs surgically (section-addressable, no LLM) | Shared helpers, Scanners for generated content, Node.js built-ins | Cannot import from Commands or Validators |
| **Shared** (`cli/shared-*.mjs`) | Cross-cutting utilities: ignore/glob filters, source-root resolution, static Worker/Pages binding scopes, git helpers, shared trace patterns | Node.js built-ins plus optional direct parser loading where documented | Cannot import from Validators, Commands, or Writers |
| **Config** (`cli/config.mjs`) | `loadConfig` + defaults/profile merge + project-type detection | Shared utilities, Node.js built-ins | Cannot import from Commands (extracted so `demo`→`docguard` is no longer a cycle) |
| **Entry Point** (`cli/docguard.mjs`) | ANSI colors, argument parsing, command dispatch, banner/help | Commands, Config (`loadConfig`) | Calls validators only through commands |

### Key rule

**Key Rule**: Validators are pure functions. They receive `projectDir` and `config`, then return results. They stay isolated from commands and the CLI entry point. The Extension layer operates independently, using the CLI as an external tool.

### Layer graph

```mermaid
graph TD
    A["CLI Entry Point<br/>docguard.mjs"] --> B["Shared Constants<br/>shared.mjs"]
    A --> C["Commands<br/>cli/commands/*.mjs"]
    C --> B
    C --> D["Validators<br/>cli/validators/*.mjs"]
    D --> E["Node.js Built-ins<br/>fs, path, child_process"]
    C --> E
    A --> F[".docguard.json<br/>Project Config"]
    D --> G["docs-canonical/<br/>Canonical Docs"]

    style A fill:#4a9eff,color:#fff
    style B fill:#6c757d,color:#fff
    style C fill:#28a745,color:#fff
    style D fill:#ffc107,color:#000
    style F fill:#17a2b8,color:#fff
    style G fill:#e83e8c,color:#fff
```

## Data Flow

### Request Lifecycle: `docguard guard`

```
User runs: npx docguard guard
     │
     ▼
docguard.mjs
  ├── parseArgs(process.argv)      → flags: { format, dir, ... }
  ├── loadConfig(projectDir)       → .docguard.json → merged with defaults
  │     ├── Reads .docguard.json
  │     ├── Reads package.json (name, type detection)
  │     └── Merges: defaults ← config ← CLI flags
  │
  ▼
guard.mjs
  ├── For each enabled validator:
  │     ├── structure.mjs    → checks docs-canonical/ exists, required files present
  │     ├── docs-sync.mjs    → checks DocGuard metadata headers
  │     ├── drift.mjs        → checks DRIFT-LOG.md for staleness
  │     ├── changelog.mjs    → checks Unreleased section, version entries
  │     ├── architecture.mjs → validates component map, layer boundaries
  │     ├── test-spec.mjs    → checks test framework, coverage docs
  │     ├── security.mjs     → checks auth, secrets documentation
  │     ├── environment.mjs  → checks setup steps, env vars documentation
  │     └── freshness.mjs    → checks git commit dates vs doc last-modified
  │
  ├── Collects: { pass: [...], warn: [...], fail: [...] }
  │
  ▼
Output (text | json)
  └── Exit code: 0 (pass) | 1 (fail) | 2 (warn) | 3 (errors, but project not initialised)
```

### AI Fix Flow: `docguard fix --doc architecture`

```
fix.mjs
  ├── Looks up DOC_EXPECTATIONS['docs-canonical/ARCHITECTURE.md']
  ├── assessDocQuality(content, expectations)
  │     └── Checks: line count, placeholder count, content quality signals
  ├── Outputs: TASK, PURPOSE, RESEARCH STEPS, WRITE THE DOCUMENT
  │
  ▼
AI Agent (Claude Code, Cursor, Copilot, etc.)
  ├── Reads stdout (the research instructions)
  ├── Executes research: reads package.json, scans directories, maps imports
  ├── Writes docs-canonical/ARCHITECTURE.md with real content
  │
  ▼
docguard guard → validates the newly written document
```

## Key Design Decisions

| Decision | Rationale |
|----------|-----------|
| **Minimal dependencies** | One exact-pinned, vetted runtime dep (`@babel/parser`) earns its place by fixing silent regex truncation; it loads optionally so installs stay robust. Everything else is Node.js built-ins. |
| **Config-driven validation** | `.docguard.json` lets projects customize which validators run. A CLI project can skip database docs. |
| **Validators are independent** | Each validator is a self-contained module. Adding a validator keeps existing ones stable. |
| **AI as author, CLI as orchestrator** | The CLI detects problems and generates structured prompts. Documentation writing is the AI's responsibility. |
| **Exit codes for CI** | `0` (pass), `1` (fail), `2` (warn), `3` (errors in a project with no `.docguard.json`) enables `docguard ci` to gate deployments. `3` stays non-zero so an any-non-zero gate is unchanged, but it lets the generated Git hook distinguish "never adopted DocGuard" from "failed its checks". |
| **Scoped factual evidence** | `.docguard-evidence.json` declares narrow, typed source-to-statement predicates. Contradictions fail guard; stale, inconclusive, and unsupported evidence stays visible. A verified statement never exempts its document from freshness or semantic review. |
| **Evidence before context volume** | `agent --task` returns a bounded retrieval packet only after the frozen evaluation showed equal hidden-test safety and lower steps/latency. It remains opt-in because uncached token use increased and the synthetic protocol does not establish universal benefit. |

---

## External Dependencies

DocGuard declares one exact-pinned runtime dependency, `@babel/parser`. It loads optionally: installations without Babel use a less precise regex fallback. The modules below supply the remaining runtime functionality.

| Module | Usage |
|--------|-------|
| `node:fs` | File system operations (read docs, check existence) |
| `node:path` | Path resolution and manipulation |
| `node:child_process` | Git operations (freshness checks) |
| `node:url` | ES Module URL resolution |
| `node:readline` | Interactive prompts (init command) |
| `node:test` | Built-in test framework |
| `node:assert` | Test assertions |
| `node:os` | Temp directory for tests |

**Dev dependencies**: None. Tests use `node:test` (built-in since Node.js 18).

---

## Revision History

| Version | Date | Author | Changes |
|---------|------|--------|---------|
| 1.5.0 | 2026-09-18 | DocGuard Team | Freshness review: corrected a project-size figure stale since 2026-05-29 and recorded guard exit code 3 for uninitialised projects |
| 1.4.0 | 2026-09-15 | DocGuard Team | Bound package capability claims to shipped modules, pruned ignored and nested checkout copies from instruction pointers, and made non-clean planned lifecycle state advisory only |
| 1.3.0 | 2026-09-15 | DocGuard Team | Made managed hooks composable and self-repairing, aligned direct evidence exit codes with guard severity, and exposed field-level registry drift |
| 1.2.0 | 2026-09-15 | DocGuard Team | Made router mounts symbol-aware and statically composable, retained negative scan evidence as review-only, and aligned monorepo/config/design-sync discovery boundaries |
| 1.1.0 | 2026-09-15 | DocGuard Team | Added packed adoption qualification, independent diff inventory, exact finding-code policy, combined readiness assessment, lifecycle-aware traceability, static Python literal evidence, and transitive static router-mount resolution with test-client exclusion |
| 1.0.0 | 2026-09-14 | DocGuard Team | Added deterministic task-specific context selection, lifecycle and safe-reader boundaries, strict packet schema, and the frozen promotion benchmark |
| 0.9.0 | 2026-09-14 | DocGuard Team | Added strict evidence manifests, typed local adapters, five-state evaluation, exact semantic-claim coverage, and guard/agent assurance integration |
| 0.8.0 | 2026-09-14 | DocGuard Team | Added transactional retirement/completion writes, reconciliation review graphs, qualified implementation evidence, bounded outcomes, active-context regeneration, and Spec Kit completion hooks |
| 0.7.0 | 2026-09-14 | DocGuard Team | Added the deterministic spec lifecycle registry, immutable spec-ID resolution, shared requirement evidence scanner, recovery tombstones, and two-stage preflight boundary |
| 0.6.0 | 2026-05-31 | DocGuard Team | Refresh for v0.24.0: Python promoted to full support via a `python3` AST tier (`cli/scanners/py-ast.mjs`); JS/TS route extraction extended with cross-file mount-prefix resolution, object-form route declarations, and AST router-screen detection (`cli/scanners/js-ast.mjs`); removed the retired editor extension from the tech stack |
| 0.5.0 | 2026-05-29 | DocGuard Team | Refresh for v0.22–v0.23: validator + scanner set updated, new `config.mjs` (config extracted to break the demo↔docguard cycle) and `shared-trace-patterns.mjs` (shared multilingual trace patterns) |
| 0.4.0 | 2026-03-13 | DocGuard Team | Complete rewrite with real project data, AI orchestration architecture |
| 0.1.0 | 2026-03-13 | DocGuard Generate | Auto-generated skeleton |


### Requirement identity across documents

Requirement definitions are identified by immutable spec ID plus requirement ID when a spec declares `Spec ID` metadata. Repository-relative path qualifiers remain supported during migration. A bare test annotation such as `@req FR-001` earns linkage credit only when that ID is defined in one active or retired document. Prefer `@req acme.payments#FR-001`; `@req specs/payments/spec.md#FR-001` remains valid while the spec is active. Path qualifiers use forward slashes and are repository-relative.

Validation, `trace --features`, and the spec registry share definition parsing and reference resolution. A qualified reference credits only its target document. Ambiguous bare references credit neither feature and produce a review finding for each unresolved definition. A wrong qualifier is an orphan reference and never falls back to a bare match. Registry completion evidence always requires an explicit spec ID or exact path qualifier, even when a bare ID is currently unique. Repeated mentions within one document do not create additional identities. Linkage remains evidence of a declaration, not proof of behavioral correctness.

Completion also supports reviewed maintenance of a verified or released living spec. It reconciles from the prior reviewed revision and appends a status-preserving outcome when a linked source, test, canonical document, or decision changed. Eligibility comes exclusively from those reviewable changes; generated registry, active-context, and implementation-outcome updates are excluded.


---

## docs-canonical/CI-RECIPES.md

# CI Recipes

<!-- docguard:last-reviewed 2026-09-18 -->
<!-- docguard:status active -->

## Recipe 1 — Guard (mandatory CI gate)

Run `docguard init --with ci` to create `.github/workflows/docguard.yml`. Existing workflows are preserved; explicit `--force` backs up and replaces the file. The standalone `docguard ci` command continues to execute checks. Start from `templates/ci/github-actions.yml` or the Spec Kit guard workflow in `extensions/spec-kit-docguard/templates/github-workflows/`. These checked-in templates are the maintained source for action pins, runtime selection, and report handling. Copying a template does not configure repository branch protection; require its check independently.

Use a fixed tool version, full Git history for freshness, and explicit warning policy. Run the check against the actual revision proposed for merging. A missing executable, malformed report, or unexpected nonzero exit is a tool failure, not a successful scan. Configure merge-queue triggers if the repository uses a merge queue.

```sh
node_modules/.bin/docguard ci --format json --no-history > docguard-report.json
```

The CLI exits 0 for pass, 1 for failure, and 2 for warning-only results. A plain shell step treats both 1 and 2 as failures. To permit warnings, capture the exit status explicitly and allow only 0 or 2. To block warnings, use `ci --fail-on-warning`. Severity overrides retain their configured meaning.

When `.docguard-evidence.json` exists, guard also evaluates its declarations.
Contradictions are high-confidence errors. Stale input digests, missing or
ambiguous targets, malformed evidence, and unsupported report shapes remain
visible warnings. Inspect the complete contract with:

```bash
npx docguard-cli verify --evidence --format json
```

This direct command exits 0 when every configured declaration is verified, 2
when evidence is stale, inconclusive, or unsupported, and 1 when a declaration
is contradicted or the manifest is invalid. CI that permits unresolved evidence
must explicitly allow only status 2; a contradiction is always a failed gate.

Generate oasdiff or Buf reports in an earlier pinned CI step, save their machine
output, and declare SHA-256 identities for every repository input. DocGuard
consumes those artifacts; it does not install or invoke either producer. Keep
the broad freshness and semantic review paths enabled because exact evidence
does not cover undeclared prose.

## Recipe 2 — Auto-Fix (PR-time mechanical fixes)

Run `fix --write` on a controlled checkout when documentation mutation is intended. Review the resulting diff and rerun guard. Preserve human-authored intent; a disagreement may require fixing implementation rather than rewriting the specification.

Mechanical replacements require their existing provenance and generated-section safeguards. A scheduled or PR repair workflow should create a reviewable branch/PR and deduplicate existing repair work. Grant write privileges only to that explicitly enabled workflow. Fork contributions should receive read-only verification unless a separate trusted process handles repair.

The shipped auto-fix template and composite action expose optional commit/comment behavior. Review those flags and their permissions before enabling them. A generated workflow is executable code and deserves the same review as another repository change.

## Recipe 3 — Sync (memory refresh on a schedule or pre-merge)

`sync --write` regenerates sections declared as code-derived. Human sections retain judgment and rationale. Cache identity reflects relevant inputs, so ordinary source edits invalidate a prior plan.

On a schedule, produce a diff, check for an existing repair PR, and create a new proposal only when meaningful work remains. Keep clean runs quiet. Set an owner and response expectation for unresolved findings. Scheduled source scans cannot detect every external deployment or vendor change; operational checks need their own evidence.

## Recipe 3a — Protected scheduled releases

The repository's scheduled release workflow opens a reviewable `release/vX.Y.Z`
pull request because `main` requires pull requests and four runtime checks. GitHub
places pull-request workflows created with the repository `GITHUB_TOKEN` into an
approval-required state. Explicit `workflow_dispatch` events run, but their jobs
do not satisfy branch protection's required pull-request checks, and their
completion does not produce a downstream `workflow_run` when the repository token
authored the dispatch. GitHub documents a personal token or GitHub App as the
fully automated alternative. DocGuard instead keeps the repository token and one
explicit maintainer action: select **Approve workflows to run** on the generated
PR. No release credential is stored.

After approval, ordinary pull-request CI supplies the four required contexts.
Before the branch is pushed, the trusted scheduler validates the base repository,
bot author, branch/title/version agreement, next-version increment, synchronized
package surfaces, and changed-file allowlist. It then arms GitHub's native squash
auto-merge. Native auto-merge remains blocked by the four required checks, binds
eligibility to the current PR head, and resets when that head changes. The
scheduler waits up to ten minutes for the merge and then dispatches the
idempotent release workflow. An hourly tag-driven release sweep covers approvals
that happen after this bounded wait; tagged versions exit after the small detect
job. If publication is interrupted, either the hourly sweep or the next release
schedule sees the current package version without a tag and retries publication
before considering another bump. An orphaned release branch fails closed; an
existing open release PR is reused and has auto-merge re-armed.

Do not use a post-approval `workflow_run` listener as the release continuation.
The approval-required completion is the event that listener observes; approving
the held run executes its jobs without producing a second completion event for
the listener. Release PR #380 demonstrated this boundary while publishing
v0.40.1. Release PR #383 then proved repository-token native auto-merge, while
also proving that its resulting push is recursion-suppressed and cannot be the
sole publication trigger. The bounded wait handles the normal approval path; the
hourly tag sweep supplies durable recovery without continuous polling or another
credential.

The v0.40.3 release is the retained end-to-end proof. Scheduled run
`34922506777` opened repository-token PR #386 and armed native auto-merge. After
one maintainer workflow approval, CI run `34922605581` and supply-chain run
`34922605917` passed, GitHub merged
`e27d6bf0203708ee8206a1434eb292520f4c4494`, and the bounded wait dispatched
publication run `34922784629`. That run published npm, PyPI, GHCR, the GitHub
Release, extension ZIP, and MCPB and refreshed the catalog reminder.

Catalog submission remains an explicit human action. The release and manual
catalog workflows maintain one open reminder in this repository: each run
refreshes the newest matching issue to the current version and closes older
matching reminders as superseded.

## Recipe 3b — Spec completion and post-hoc reconciliation

Run `docguard reconcile --since <merge-base> --format json` when implementation
may have changed approved behavior outside the original Spec Kit flow. Review
unsupported files and intent-change classifications; write mode can refresh only
DocGuard-owned mechanical sections. After declared tasks, source and test
evidence, and affected canonical docs are reviewed, run `docguard specs complete --id <spec-id>
--since <merge-base> --check` as the merge gate. Apply the same command with
`--write --reason "<reviewed outcome>"` on a clean controlled checkout to record
verification. A taskless living verification contract is eligible only when every
requirement has qualified evidence. Keep living specs current; archive only when the registry reports
that the selected persistence model is ready.

## Recipe 4 — Score (track CDD maturity over time)

`score --format json` reports structural maturity. Its numeric threshold is stable, while `assurance` explicitly states that factual accuracy remains unverified. Comparing scores is meaningful only with the same tool/configuration and a comparable coverage scope.

Use guard findings and declared verification evidence for enforcement. A high score alone does not establish current documentation, correct prose, or regulatory compliance.

## Recipe 1b — GitLab CI / Jenkins (JUnit output)

`guard --format sarif` emits SARIF 2.1.0 for GitHub Code Scanning and enterprise dashboards. Every result carries the finding's channels in `properties`: `disposition` (`act` when DocGuard names the correction, `escalate` when the judgement is the reader's), `confidence`, `evidence` (`measured` or `not-measured`), `parserTier`, `reportable`, `suggestionKind`, and the intrinsic and effective severities. Gate automation on `disposition` rather than severity alone — a blocking result can still be an escalation, and applying an automated fix to one silences a signal without resolving it. `parserTier` of `regex-fallback` or `fallback-language` means no syntax tree was available for that file, so absence of a result there is weak evidence.

`guard --format junit` emits a test report suitable for GitLab/Jenkins ingestion. Install a fixed DocGuard version in the job, capture the exit status, and upload the report even on failures. Permitting exit 2 is an explicit warning policy; other nonzero statuses remain failures.

## Recipe 4b — Score history across ephemeral CI runs

`ci` records history by default. `--no-history` opts out. Ephemeral runners need an explicitly configured artifact or cache policy if trends are to span runs. Treat restored history as informational data, not proof that the current checkout was verified. Avoid sharing writable caches between untrusted pull requests and privileged release workflows.

## Recipe 4c — Multi-repo scorecard (no extra tooling)

Run `ci --format json` per repository and retain project, revision, tool version, configuration, status, and assurance scope. Aggregate findings by code while preserving their repository ownership. Report unsupported and unclassified coverage alongside successful checks.

## Recipe 4d — Detector precision regression

Run the network-free synthetic corpus on ordinary pull requests:

```sh
node benchmarks/run.mjs --baseline benchmarks/baseline.json
```

Run the full pinned public corpus in a separate trusted, network-enabled job when detector or scanner behavior changes:

```sh
node benchmarks/run.mjs --external --baseline benchmarks/baseline.json
```

Treat a core comparison failure as a quality regression. In the network-free run the pinned public baseline cases were not selected; they appear under `comparison.core.outOfSelection` and are not regressions. The loader validates the baseline envelope before the run starts and refuses one whose metrics or caveat no longer match its cases. Persisted runtime snapshots stay advisory even when environment labels match. Apply the 20-percent gate only to at least five controlled samples from the same paired comparison session. Updating the baseline is a reviewed change: inspect every added or removed case, label, unsupported result, and confidence limit before using `--replace-baseline`.

## Pre-commit hook (no GitHub Actions required)

`docguard hooks --type pre-commit` installs a local gate that prefers the repository's installed DocGuard binary. The hook blocks an unavailable runtime. `--auto-fix` additionally applies mechanical fixes and stages their output; enable it only when that mutation is intended.

A Git hook lives in the shared `.git/hooks` and is active on every branch and linked worktree, while `.docguard.json` is a branch-local tracked file. The installed hook therefore skips any working tree with no `.docguard.json` and lets the commit through, and treats guard exit `3` (errors in an uninitialised project) as allowed rather than blocking. A project that never adopted DocGuard is not blocked by a hook installed from another branch; adopted projects are gated exactly as before.

Regenerate installed hooks after upgrading to pick up changes in hook behavior. The pre-push score hook parses real JSON and enforces its configured minimum; it complements the full CI gate. Local hooks can be bypassed, so protected merges remain necessary for shared enforcement.

## Recipe 5 — Pre-commit lite (changed files only)

`guard --changed-only --since <ref>` runs its curated validator subset with changed-file scoping, plus explicitly escalated validators. Use a full guard at the merge boundary. The entry point and `guard.mjs` define the current subset; a copied list in this recipe would drift.

## Permissions cheatsheet

| Operation | Default authority | Additional authority |
|---|---|---|
| Guard, score, report | Repository read | Artifact storage if configured |
| Mechanical repair | Read/write controlled checkout | Branch/PR publication only when enabled |
| Feedback preview | Local analysis | User submits reviewed public metadata voluntarily |
| External precision corpus | Public read-only Git fetch | Network access to exact pinned commits; no project script execution |
| Scheduled review | Repository read | Notification or publication only when explicitly configured |

## Action inputs reference

`action.yml` is the authoritative composite-action input contract. Review command selection, warning policy, score threshold, working directory, and optional commit/comment flags. Pin the action to a reviewed commit and retain the corresponding release label for maintenance.

## Action outputs reference

Read the outputs declared in `action.yml` and the command's JSON schema before wiring downstream steps. Preserve unknown/unverified values. An integrity digest detects changes to covered report data; it is neither a trusted signature nor proof of a correct scanner.


---

## docs-canonical/DATA-MODEL.md
> Database schemas, entity relationships, and data flow

# Data Model

<!-- docguard:version 0.9.0 -->
<!-- docguard:status active -->
<!-- docguard:last-reviewed 2026-09-18 -->

| Metadata | Value |
|----------|-------|
| **Status** | ![Status](https://img.shields.io/badge/status-active-brightgreen) |
| **Version** | `0.9.0` |
| **Database** | None — DocGuard is a stateless CLI tool |
| **Storage** | File-system only (reads project files, writes generated docs) |

---

## Entities

DocGuard uses filesystem artifacts for configuration, optional caches, and history. Commands read project files and produce structured output. The "data model" consists of the configuration schemas, validator output formats, and document metadata structures documented below. All data is file-system based — DocGuard reads `.docguard.json`, scans the project directory, and validates canonical documents against the codebase.

## Configuration: `.docguard.json`

The primary data structure. Controls all CLI behavior.

### Identity and required files

| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `projectName` | `string` | No | Inferred from `package.json` name or directory | Display name for reports |
| `version` | `string` | No | `"0.1"` | Config schema version |
| `projectType` | `string` | No | Auto-detected | One of: `cli`, `webapp`, `api`, `library`, `monorepo` |
| `requiredFiles.canonical` | `string[]` | No | 5 docs-canonical files | Paths to required CDD documents |
| `requiredFiles.agentFile` | `string[]` | No | `["AGENTS.md", "CLAUDE.md"]` | AI agent config file options |
| `requiredFiles.changelog` | `string` | No | `"CHANGELOG.md"` | Changelog file path |
| `requiredFiles.driftLog` | `string` | No | `"DRIFT-LOG.md"` | Drift log file path |

### Project-type behavior

| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `projectTypeConfig.needsEnvVars` | `boolean` | No | `true` | Whether ENVIRONMENT.md should check for env var docs |
| `projectTypeConfig.needsEnvExample` | `boolean` | No | `true` | Whether `.env.example` is expected |
| `projectTypeConfig.needsE2E` | `boolean` | No | `true` | Whether E2E test docs are expected |
| `projectTypeConfig.needsDatabase` | `boolean` | No | `true` | Whether DATA-MODEL should expect entity docs |
| `projectTypeConfig.testFramework` | `string` | No | Auto-detected | Test framework name (e.g., `"node:test"`, `"jest"`) |
| `projectTypeConfig.runCommand` | `string` | No | Auto-detected | Command to run the project |

### Validator tuning

| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `validators.*` | `boolean` | No | `true` | Enable/disable individual validators |
| `collections.*` | `string` (glob) | No | — | Binds a documentation noun to a code collection: `"extractors": "src/extractors/*.py"` lets Metrics-Consistency flag a documented count that disagrees with the file count |
| `docs.dirs` | `string[]` | No | Auto-detected | EXTENDS the auto-detected documentation homes (docs/, documentation/, guides/, …) with non-standard dirs; exclude via `.docguardignore` |
| `severity.*` | `"high" \| "medium" \| "low"` | No | `"medium"` | Per-validator exit-code weight — `high` promotes warnings to blocking, `low` demotes them (display unchanged) |
| `findingSeverity.<CODE>` | `"high" \| "medium" \| "low"` | No | — | Exact stable-code enforcement; takes precedence over validator policy. Intrinsic errors require an exact code entry to be demoted. |

### Example Configuration

```json
{
  "projectName": "docguard",
  "version": "0.3",
  "projectType": "cli",
  "requiredFiles": {
    "canonical": [
      "docs-canonical/ARCHITECTURE.md",
      "docs-canonical/DATA-MODEL.md",
      "docs-canonical/SECURITY.md",
      "docs-canonical/TEST-SPEC.md",
      "docs-canonical/ENVIRONMENT.md"
    ],
    "agentFile": ["AGENTS.md", "CLAUDE.md"],
    "changelog": "CHANGELOG.md",
    "driftLog": "DRIFT-LOG.md"
  },
  "projectTypeConfig": {
    "needsEnvVars": false,
    "needsE2E": false,
    "needsDatabase": false,
    "testFramework": "node:test"
  },
  "validators": {
    "structure": true,
    "docsSync": true,
    "drift": true,
    "changelog": true,
    "architecture": false,
    "testSpec": true,
    "security": false,
    "environment": true,
    "freshness": true
  }
}
```

## Retirement Manifest: `.docguard-archive.json`

The manifest is an append-only recovery ledger for documentation removed from
active context by `docguard retire`. Git content remains authoritative; the
manifest stores no retired prose.

| Field | Type | Description |
|-------|------|-------------|
| `schemaVersion` | `number` | Manifest contract version; currently `1` |
| `strategy` | `"git-history"` | Recovery storage strategy |
| `entries[].path` | `string` | Former repository-relative document path |
| `entries[].archivedAt` | ISO timestamp | Historical field name for retirement time |
| `entries[].archivedFrom` | Git object ID | Source revision containing the exact document |
| `entries[].blob` | Git object ID | Exact retired content identity; length follows repository object format |
| `entries[].reason` | `string` | Reviewed retirement rationale |
| `entries[].supersededBy` | `string` | Optional current replacement document |
| `entries[].evidence` | `string[]` | Optional clean documents containing consolidated outcomes |
| `entries[].requirementIds` | `string[]` | Requirement identities declared by the retired file; traceability keeps them as tombstones and never treats them as active requirements |
| `entries[].retentionRef` | `string` | Branch ref proven to contain the source revision |
| `entries[].objectFormat` | `"sha1" \| "sha256"` | Git repository object format |
| `entries[].recoverability` | `"verified"` | Result of the retained-ref ancestor check at retirement time |
| `entries[].restore` | `string` | Convenience command derived from structured source/path fields |

Existing manifests may carry one shared top-level `retention` record for a
batch created before per-entry retention metadata was introduced. The spec
registry projects both forms into one normalized tombstone model. Lifecycle
and traceability consumers reject incomplete recovery entries; an unverified
manifest cannot suppress active-context or orphan-reference findings.

## Spec Lifecycle Registry: `.docguard-specs.json`

The committed registry indexes which specifications govern the project and what
the repository can prove about them. It never copies requirement prose. Its
normative JSON Schema is `schemas/docguard-specs.schema.json`.

| Field | Authority | Description |
|-------|-----------|-------------|
| `$schema`, `schemaVersion` | Contract | Exact schema URL and version `2`; version 1 is read for migration and projects stale until refreshed |
| `specs[].specId` | Spec metadata | Immutable lowercase namespaced identity; never generated or reused |
| `specs[].path` | Projection | Current spec path or former path for a retired record |
| `specs[].reviewed.lifecycle` | Human review | Orthogonal approval, delivery, context, retirement reason, storage, and persistence policy |
| `specs[].reviewed.relations` | Human review | `extends`, `duplicates`, `conflictsWith`, `supersedes`, and `supersededBy` spec-ID edges |
| `specs[].reviewed.scope.canonicalDocs` | Human review | Canonical documents affected by the specification |
| `specs[].reviewed.reconciliation.lastReviewedRevision` | Human review | Exact Git revision whose doc impact was reviewed, or `null` |
| `specs[].reviewed.reconciliation.outcomes` | Human review | Up to 20 reviewed implementation outcomes with revision, bounded rationale, evidence paths, deviations, and optional successor |
| `specs[].intent.requirements` | Projection | `specId#requirementId` identities parsed from the active spec |
| `specs[].observed.artifacts` | Projection | Paths and SHA-256 content identities for spec, plan, and tasks |
| `specs[].observed.taskCompletion` | Projection | Checked and total Markdown task boxes; not proof of delivery |
| `specs[].observed.testEvidence` | Projection | Explicitly spec-qualified test annotations or labels only |
| `specs[].observed.implementationEvidence` | Projection | Explicit `@implements specId#requirementId` source annotations only; names and proximity do not earn completion credit |
| `tombstones[]` | Recovery projection | Retired identities linked to source revision, blob, retention ref, object format, and recoverability |

`docguard specs --write` regenerates only projected fields and preserves the
entire `reviewed` block. Unknown reviewed fields, invalid lifecycle values,
duplicate identities, and archive/storage contradictions fail closed. The
output omits timestamps and sorts unordered collections, so `specs --check`
can compare a byte-stable result in CI. A non-current projection exposes up to
25 bounded `differences` with a JSON-style field path, kind, and explanation.
Order-only differences use kind `order`; changed, missing, and unexpected
content remain distinct. Additional differences are reported as truncated.

`docguard specs complete` requires a clean Git revision, coverage for every
requirement through qualified implementation or test evidence, existing affected
canonical documents, a supported reconciliation plan, and a guard result without
errors. Declared task ledgers must be non-empty and fully checked. An approved
`living` verification contract may omit the task ledger because its qualified
requirement evidence is the durable completion proof; other persistence models
still require one. Its staged transaction updates the bounded outcome,
registry, feature-spec outcome index, and `.docguard/current-context.json` as one
validated set. The context file contains pointers and content hashes rather than
copying governing prose, and excludes every retired spec. A verified or released
living spec can append a status-preserving maintenance outcome only when a new
linked source, test, canonical document, or decision changed after the last
reviewed revision. Generated registry and outcome updates do not satisfy that
gate.

## Task Context Packet

`docguard agent --task <text> --format json` emits a transient
`docguard.task-context` object governed by
`schemas/docguard-task-context.schema.json`. The command does not persist the
task or packet.

| Field | Description |
|-------|-------------|
| `task.digest`, `task.characters` | Normalized task identity and bounded length; raw task text is omitted |
| `provenance.git`, `provenance.registry` | Captured Git and lifecycle-registry state |
| `assurance` | Retrieval-only scope, unknown factual accuracy, and unverified status |
| `selection` | Targeted or abstained state, threshold, candidate/omission counts, excluded lifecycle documents, and fixed budgets |
| `excerpts[]` | Repository-relative path, line range, content/file hashes, kind, optional spec ID, score, reasons, and bounded content |
| `pointers[]` | Safe task, cited-source, implementation, or test paths with hashes and qualified requirements |
| `verification[]` | Commands and purposes that still need execution |
| `navigation` | Safe canonical-document inventory and approved current spec paths |
| `limitations`, `coreDigest` | Explicit epistemic limits and deterministic packet-core identity |

Selection reads at most 32 documents and 256 chunks, emits at most six
16-line excerpts totaling 6,000 characters and eight pointers, and limits task
input to 2,000 characters. An abstention emits no excerpts or pointers.

## Document Metadata Headers

Every CDD document includes DocGuard metadata as HTML comments at the top:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `docguard:version` | `string` | Yes | Semantic version of the document |
| `docguard:status` | `string` | Yes | One of: `draft`, `active`, `deprecated` |
| `docguard:last-reviewed` | `string` | Yes | ISO date (`YYYY-MM-DD`) |
| `docguard:generated` | `boolean` | No | `true` if auto-generated by DocGuard |

### Example Metadata Header

```markdown
<!-- docguard:version 0.4.0 -->
<!-- docguard:status active -->
<!-- docguard:last-reviewed 2026-03-13 -->
```

## Validator Output Format

Validators emit findings and aggregate counts. The guard adapter adds names and statuses:

| Field | Type | Description |
|-------|------|-------------|
| `name` | `string` | Validator name (e.g., `"structure"`, `"changelog"`) |
| `status` | `string` | `"pass"`, `"warn"`, or `"fail"` |
| `findings` | `object[]` | Stable code, validator, intrinsic `severity`, `effectiveSeverity`, enforcement source/key, `confidence`, `disposition`, `evidence`, `parserTier`, location, message, and normalized suggestion |
| `passed`, `total` | `number` | Applicable check counts |
| `errors`, `warnings` | `string[]` | Compatibility message arrays |
| `applicable` | `boolean` | Optional applicability indicator; false becomes N/A |
| `effectiveErrors`, `effectiveWarnings`, `effectiveInfos` | `number` | Exit-code counts after exact-code and validator policy |
| `effectiveStatus` | `string` | Per-validator `pass`, `warn`, or `fail` after policy; intrinsic `status` remains available |

## Precision evidence contract

`guard` results carry `precisionEvidence`, scoped to the finding codes that run emitted (`schemas/docguard-precision-evidence.schema.json`). The unit of evidence is the finding code. DocGuard defines many more codes than the reviewed corpus measures, so a code the corpus never exercised reports `status: "not-measured"`, carries no ratio, and never inherits the measured precision of another code in the same validator. A measured code whose own precision denominator is below `minN` is marked `quotable: false` with a reason, and may carry a `backoff` to a coarser measured tier that names that tier (`validator` or `aggregate`). `measures` is always `benchmark-precision`; `caveat` is the sentence a consumer must show beside any quoted ratio; `source.matchesRunningVersion` is false when the numbers were measured on a different build than the one reporting them. `coverage` counts codes in the run by measurement status.

The block is served from `cli/precision-evidence-data.mjs`, a generated module derived from `benchmarks/baseline.json` by `npm run generate:precision-evidence`, because `benchmarks/` is not part of the published package. A test compares the committed module against that projection, so a stale number fails the suite rather than shipping. Findings are written verbatim into feedback records, so their shape is a contract: it grows only by specification, and a test pins the exact key set.

## Finding channels

A finding answers three independent questions, one field each. A single `confidence` field had to serve all three, so a certain observation read as an uncertain one, and the feedback loop sampled only the findings its own label already doubted.

| Field | Question | Values |
|-------|----------|--------|
| `severity` / `effectiveSeverity` | Does CI block? | `error`, `warn`, `info` (effective only) |
| `disposition` | Who decides — the tool or the reader? | `act`, `escalate` |
| `confidence` | How sure is the detector of its **observation**? | `high`, `low` |
| `evidence` | Has the reviewed corpus ever measured this code? | `{ status: 'measured' \| 'not-measured', … }` |
| `parserTier` | Which analyzer produced it? | `js-ast`, `py-ast`, `regex-fallback`, `fallback-language`, `mixed`, `not-applicable` |

`disposition` is `act` when DocGuard asserts a defect and names the correction, `escalate` when it reports a signal whose judgement belongs to the reader. A detector may set it explicitly; otherwise it derives from `suggestion.kind` (`fix`/`suppress` → `act`; `review`/`report` → `escalate`), and falls back to `escalate` when the suggestion is absent or malformed — a finding DocGuard can describe but can only describe is one a human should read. The three channels vary independently: FRS002 counts commits read directly from Git, so it is `confidence: high` and `disposition: escalate`. The count is a fact; the inference to staleness remains the reader's call.

`evidence` is projected per finding code from the reviewed baseline by the same checked projection that serves `precisionEvidence` (`docguard.precision-evidence-loop#FR-019`). A `measured` entry carries `n`, its Wilson 95% interval, and a point estimate once `n` meets the published floor; an unmeasured code reports `not-measured` and always stands alone, inheriting no sibling's number. The object is frozen and shared per code, so a large run allocates one evidence object per code rather than one per finding.

`reportable` is true when `evidence.status` is `not-measured` **or** `confidence` is `low`. Under the previous rule, which read confidence alone, the default feedback sample omitted the population where a wrong label costs most: a confident label on a code the corpus has yet to measure.

`location` is always a string (`path` or `path:line`) or `null`. A detector that supplies `{ file, line }` is normalized at construction. Under the previous contract such findings rendered as `[object Object]`, and the SARIF location parser dropped them, so six codes reached GitHub Code Scanning with no file annotation at all.

## Fix Command Issue Format

The `fix --format json` output follows this structure:

| Field | Type | Description |
|-------|------|-------------|
| `status` | `string` | `"clean"` or `"issues-found"` |
| `project` | `string` | Project name |
| `projectType` | `string` | Detected project type |
| `issueCount` | `number` | Total issues found |
| `autoFixable` | `number` | Issues fixable by `--auto` |
| `issues[].type` | `string` | `"missing-file"`, `"empty-doc"`, `"partial-doc"`, `"missing-config"` |
| `issues[].severity` | `string` | `"error"`, `"warning"`, `"info"` |
| `issues[].file` | `string` | Affected file path |
| `issues[].autoFixable` | `boolean` | Can be auto-fixed |
| `issues[].fix.action` | `string` | `"create"`, `"rewrite"`, `"improve"` |
| `issues[].fix.ai_instruction` | `string` | AI-actionable fix instruction |

## Score Output Format

The `score --format json` output:

| Field | Type | Description |
|-------|------|-------------|
| `score` | `number` | CDD maturity score (0-100) |
| `grade` | `string` | Letter grade: `A+`, `A`, `B`, `C`, `D`, `F` |
| `categories` | `object` | Per-category score, weight, weighted contribution, and axis |
| `scoreKind` | `string` | `structural-maturity` |
| `assurance` | `object` | Factual accuracy remains unverified; extracted candidate count is heuristic |
| `memory` | `object` | Completeness and structural alignment proxies; accuracy is null |

---

## Revision History

| Version | Date | Author | Changes |
|---------|------|--------|---------|
| 0.9.0 | 2026-09-15 | DocGuard Team | Add bounded field-level spec-registry differences and direct evidence verification exit semantics |
| 0.6.0 | 2026-09-14 | DocGuard Team | Add the document-retirement recovery manifest, retained-ref proof, and retired requirement tombstones |
| 0.4.0 | 2026-03-13 | DocGuard Team | Complete rewrite — documented all config formats, output schemas, metadata headers |
| 0.1.0 | 2026-03-13 | DocGuard Generate | Auto-generated skeleton |

## Score assurance contract

The numeric CDD score estimates structural maturity. Factual accuracy and regulatory assurance require separate evidence. Existing score and grade thresholds remain stable. Score JSON identifies its scope as `structural-maturity`. `memory.accuracy` is nullable: `null` represents unverified factual accuracy; the former proxy is exposed as `memory.structuralAlignment`. Consumers must preserve null as an unknown value.

An `assurance` object accompanies score, diagnose, CI, and report output. It contains `status` (`unverified`), `factualAccuracy` (`null`), and `unverifiedClaims` (a count of extracted candidates, or null if extraction failed). Even zero extracted candidates leaves prose unverified. Claim discovery uses a bounded heuristic. These fields explain evidence limits while existing CI thresholds retain their numeric meaning.

## Evidence verification contract: `.docguard-evidence.json`

The optional version-1 manifest contains at most 128 declarations. Each immutable
ID owns an `always` applicability declaration, one Markdown target, one source,
and one compatible predicate. Unknown fields, duplicate IDs, unsafe paths, and
ambiguous predicate combinations invalidate the manifest.

| Source adapter | Required contract | Compatible predicate |
|---|---|---|
| `json-pointer` | Safe JSON file plus an RFC 6901 pointer | `equals` with an explicit JSON type, or `set-equals` for a duplicate-free string array |
| `collection-count` | One bounded repository-relative glob and explicit `allowEmpty` policy | `count-equals` |
| `python-literal-count` | Safe `.py` path, one ASCII module-level symbol, one uniquely assigned static list/tuple/set/dict literal, and explicit `allowEmpty` policy | `count-equals` |
| `oasdiff` | Saved bounded JSON array, adapter version, producer version, `breaking` or `changelog` command, and current input hashes | `no-findings` |
| `buf` | Saved bounded JSON Lines, adapter version, producer version, `breaking` command, and current input hashes | `no-findings` |

Every result contains the declaration ID, stable claim and evidence identities,
document location, adapter, predicate, captured input hashes, evidence hash,
reason code, and scope limitation. The state is exactly one of
`verified-within-scope`, `contradicted`, `stale`, `inconclusive`, or
`unsupported`. Line movement and unrelated file edits preserve identity;
changes to the selected statement, declaration, source, report, producer
metadata, or declared inputs invalidate it. A verified declaration removes a
heuristic claim from the unverified count only through a unique same-line,
same-value match.

`docguard verify --evidence` exits `0` for `verified-within-scope` and for an
unconfigured manifest, `2` for `attention-required` (stale, inconclusive, or
unsupported evidence), and `1` for `contradicted` or `invalid`. The JSON status
and process status therefore carry the same enforcement meaning in direct CI use.

## Feedback contribution contract

## Adjudicated disagreements

A reported false positive the maintainers review and decline to act on used to leave no trace. `assertContributionReady` refuses `ambiguous` and `policy_disagreement` — correctly, since a detector behaving as designed has a "this no longer fires" test it can never satisfy — and every other contribution path produces exactly that test. The corpus could therefore only ever absorb a false positive that had already been repaired, which made the measured precision a property of the contribution pipeline rather than of the detectors.

`buildAdjudicationRow()` produces the missing artifact: a corpus case classified `ambiguous` or `policy_disagreement`, carrying the same synthetic and redaction attestations and the same opposite control as a measured case, plus an `adjudication` record (`rationale`, `adjudicatedAt`) so a later reviewer can audit the decision. Such a case declares an empty `expected` and `forbidden` set, because it asserts nothing about what the detector should emit.

These rows are counted and kept outside every ratio. Scoring one as a false positive would let a user who dislikes a rule move its measured precision; scoring it as a true positive would let a maintainer turn disagreement into validation. Both would misreport, so `metrics.mjs` and the per-code derivation report `adjudicated: { policyDisagreements, ambiguous }` beside every rate and inside none of them, at corpus, per-code and run scope (`precisionEvidence.coverage.adjudicated`). A code appearing only in adjudication rows reports `not-measured` with its counts, standing alone as any unmeasured code does. Removing such a row fails baseline comparison as `case-removed`, which keeps a recorded disagreement auditable.

`feedback` defaults to findings whose code is unmeasured or whose confidence is low, and reports what that leaves out (`excluded.findings`, `excluded.codes`). Selecting on confidence alone meant the channel that would validate the confidence label sampled only findings that label already doubted: on a repository with 17 confident findings across never-benchmarked codes, the default selected zero. `--code <CODE>` selects a finding regardless of confidence; `--all` includes all active findings. Classifications are `false_positive`, `false_negative`, `unsupported_syntax`, `ambiguous`, and `policy_disagreement`. False-negative and unsupported intake require a strict synthetic fixture manifest with an exact expected identity, explicit interestingness predicate, same-path opposite control, parser tier, bounded configuration, and synthetic/redaction attestations.

`--fixture-manifest` verifies the reproduction and its control in separate temporary projects. `--reduce` removes fixture lines in deterministic order only while the declared predicate remains true. Duplicate identity hashes detector code, classification, parser tier, and normalized synthetic shape; preview returns all/open/closed GitHub searches and never submits. `--contribution tests/<name>.test.mjs` requires test-only, scope, and benchmark-delta evidence before writing a generated regression test. `--preview` skips every local write.

## Precision benchmark contract

`benchmarks/corpus.json` is a strict versioned manifest. Cases carry immutable ID, split, repository and causal groups, parser tier, classification, exact source revision or fixture digest, bounded config, scoped expected/forbidden identities, mutation preconditions, opposite control, and repair outcome. `benchmarks/baseline.json` is a strict envelope (`schemas/docguard-benchmark-baseline.schema.json`, envelope `schemaVersion` 2 around core `schemaVersion` 1) storing the reviewed deterministic core, grouped metrics, Wilson 95% confidence bounds, and separately identified environment/timing observations. Zero denominators remain `null`. The `review` block carries provenance: `status` (`candidate` from `--write-baseline`, `reviewed` only with `reviewedAt` and `reviewer`), `methodology`, `limitations`, `measures` (always `benchmark-precision` — never a calibrated probability), and `caveat`, the sentence a consumer must show beside any quoted ratio. `caveat` and `core.metrics` are derived from `core.cases`; `benchmarks/lib/baseline.mjs` recomputes both on load and rejects an envelope where either disagrees, an envelope with unknown fields, unsorted cases, or the pre-provenance `schemaVersion` 1 shape. Every run report also carries `provenance.{measures,caveat}` and `selection.{split,includeExternal,caseIds}`. Baseline comparison fails on case removal, new false positives, new false negatives, or new supported-case abstention even when aggregate warning count improves; baseline cases outside the run's selection (for example pinned public cases in a network-free run) are listed under `outOfSelection` and are not counted as removed.

## Check coverage and document roles

Each guard validator adds applicability with status and reason. checkCoverage contains counts by status, limitations naming checks that were not fully performed, and an explanatory limitation. These fields describe coverage independently from legacy status, totals, findings, and exit codes. CI/report consumers preserve them, including disabled-check counts.

Document discovery does not depend on the default filenames alone. A filename is
normalised to letters and digits — separators, case and Markdown extension are
noise — and matched against an alias table per role, so `data_model.md`,
`datamodel.md`, `Data Model.md` and `DATA-MODEL.md` all resolve to the same
role, and `API.md` resolves to the API-Reference role. Directory detection is
deliberately tight: a directory must hold at least two distinct roles before it
counts as a canonical home, so a lone root-level `SECURITY.md` — GitHub's
security policy, not a design document — never triggers a match on its own.
This also removes an accidental platform dependency: literal-path probing was
case-insensitive only on case-insensitive filesystems, so the same repository
was judged differently on macOS and Linux.

Optional docs.roles maps canonical roles to safe project-relative Markdown paths. Configuration normalization replaces each mapped default in requiredFiles.canonical and documentTypes. A mapped write is authorized either for a unique `source=code` section in an existing human file or for a missing/explicitly generated single-role whole document. Marker shape, role cardinality, and ownership are validated before mutation; `--force` does not alter that model. The configuration schema and docs/configuration.md define the role names and operation-specific contract.

Repository-root guidance is an ephemeral diagnostic and is never persisted in
project configuration. Its machine shape is
`{selectedDir,suggestedDir,reason,evidence,packagePath,gitRoot,rerun,automaticScopeChange}`.
`reason` is `ancestor_docguard_config`, `npm_workspace`, or `pnpm_workspace`;
`automaticScopeChange` is always false. Machine modes wrap it with type
`docguard.repository-root-guidance` on stderr so their primary stdout schema is
unchanged.

## Readiness assessment contract

CI, diagnose, and report include `assessment`. Its status is `BLOCKED` when
guard enforcement fails, a configured structural threshold fails, or CI is set
to block warnings; `ATTENTION` means advisory guard warnings remain; `READY`
means guard and configured gates pass. The object carries reason codes, raw
guard status, effective finding counts, structural maturity, and threshold
state. It does not replace legacy status, score, grade, assurance, or exit-code
fields.


---

## docs-canonical/ENVIRONMENT.md
> Setup instructions, environment variables, and prerequisites

# Environment

<!-- docguard:quality negation-load off — an environment doc precisely describes the ABSENCE of requirements (no install step, no database, no credential for the CLI); the prohibitive phrasing is accurate and intentional, not sloppy writing -->

<!-- docguard:version 0.8.0 -->
<!-- docguard:status active -->
<!-- docguard:last-reviewed 2026-09-18 -->

> The DocGuard CLI needs no environment variables. One optional variable, `DOCGUARD_API_KEY`, applies only to the HTTP MCP server. DocGuard has a single optional-load npm dependency (`@babel/parser`) and optionally uses the developer's own `python3`; everything else is Node.js built-ins.

| Metadata | Value |
|----------|-------|
| **Status** | ![Status](https://img.shields.io/badge/status-active-brightgreen) |
| **Version** | `0.8.0` |

---

## Prerequisites

| Tool | Version | Installation |
|------|---------|-------------|
| Node.js | ≥18.0.0 | [nodejs.org](https://nodejs.org) |
| npm | ≥8 | Included with Node.js |
| Git | Any | [git-scm.com](https://git-scm.com) |
| Python 3 | **Optional** — ≥3.8, enables the AST-accurate Python scanning tier; the scanners use regex otherwise | [python.org](https://python.org) |

## Environment Variables

> **None required.** Every CLI command (`guard`, `score`, `diff`, `trace`, …)
> reads project files directly — no `.env` file, no database connections, no
> credential of any kind. (Its one npm dependency, `@babel/parser`, needs no
> configuration.)

One **optional** variable applies to the HTTP MCP server only
(`docguard mcp --transport http`):

| Variable | When it applies | Purpose |
|----------|-----------------|---------|
| `DOCGUARD_API_KEY` | Optional on loopback; **required to bind a non-loopback host** | Shared secret for the HTTP MCP server. Equivalent to `--api-key <key>`, which takes precedence. When set, every request must carry `Authorization: Bearer <key>` or `X-API-Key: <key>`, else `401`. |

The server binds `127.0.0.1` by default and **refuses to start** on a
non-loopback host without a key, rather than exposing project read access to
the network. The stdio transport (`docguard mcp`, the default) never reads it.
See [SECURITY.md](SECURITY.md) for the full posture.

## Setup Steps

1. Clone the repository: `git clone https://github.com/raccioly/docguard.git`
2. Run `npm ci` to install the locked Babel parser dependency for the full JS/TS extraction tier
3. Run directly: `node cli/docguard.mjs --help`
4. Or use via npx: `npx docguard-cli --help`

## Development

```bash
# Run CLI locally
node cli/docguard.mjs audit

# Run the full test suite (node:test)
npm test

# Test a command on a target project
node cli/docguard.mjs diagnose --dir /path/to/project

# Quick health check
node cli/docguard.mjs guard --format json

# Regenerate llms.txt / llms-full.txt after editing a canonical or optional doc
# (tests/llms-bundle-drift.test.mjs fails and names this command)
npm run llms
```

## CI/CD

```bash
# GitHub Actions — use the shipped template
cp templates/ci/github-actions.yml .github/workflows/docguard.yml

# GitLab CI — the shipped component (see CI-RECIPES.md, Recipe 1b)
cp templates/ci/gitlab-component.yml .gitlab-ci.yml

# Or run CI command directly
node cli/docguard.mjs ci --threshold 70 --format json
```

---

## Revision History

| Version | Date | Author | Changes |
|---------|------|--------|---------|
| 0.8.0 | 2026-09-18 | DocGuard Team | Freshness review: verified the MCP key contract against `cli/commands/mcp.mjs`; added `npm run llms` and the shipped GitLab CI component, both of which were missing |
| 0.7.0 | 2026-09-17 | @raccioly | Documented `DOCGUARD_API_KEY` (HTTP MCP server); corrected the blanket "no API keys" claim that contradicted SECURITY.md |
| 0.6.0 | 2026-05-31 | DocGuard Team | v0.24.0: documented Python 3 as an optional prerequisite (enables the AST Python tier; regex fallback when absent); de-bristled the test-count example |
| 0.5.0 | 2026-03-13 | @raccioly | Added diagnose, CI template, development examples |
| 0.3.0 | 2026-03-12 | @raccioly | Proper CLI environment docs, no env vars |
| 0.1.0 | 2026-03-12 | DocGuard Generate | Auto-generated (corrected) |


---

## docs-canonical/REQUIREMENTS.md

# Requirements

<!-- docguard:quality negation-load off — requirements define explicit failure and non-disclosure boundaries -->
<!-- docguard:version 0.7.0 -->
<!-- docguard:status active -->
<!-- docguard:last-reviewed 2026-09-18 -->

## Functional Requirements

| ID | Priority | Requirement | Verification |
|---|---|---|---|
| FR-001 | P1 | Score distinguishes structural maturity from unverified factual accuracy, even when candidate extraction finds nothing. | tests/score-assurance.test.mjs |
| FR-002 | P1 | Users can dispute any active finding, preview feedback, and prepare public metadata without sharing source-derived strings automatically. | tests/feedback-contributions.test.mjs |
| FR-003 | P1 | CI, diagnose, and report preserve score assurance limits in machine output. Existing score thresholds keep their numeric meaning. | tests/score-assurance.test.mjs |
| FR-004 | P1 | Detector quality is measured with independently labelled defect/control pairs, split-safe repository groups, explicit unsupported coverage, null-safe metrics, confidence limits, and case-first baseline comparison. | tests/benchmark-manifest.test.mjs, tests/benchmark-metrics.test.mjs, tests/benchmark-runner.test.mjs |
| FR-005 | P1 | Users can turn a false positive, false negative, unsupported syntax case, ambiguity, or policy dispute into a redaction-attested synthetic fixture with an opposite control, deterministic reduction, duplicate search, and optional test-only contribution. | tests/feedback-fixture.test.mjs, tests/feedback-contributions.test.mjs |
| FR-006 | P1 | Teams can bind an exact Markdown statement to safe, local, typed evidence and receive scoped verified, contradicted, stale, inconclusive, or unsupported results through verify, guard, and agent assurance without granting whole-document accuracy. Direct verification must fail CI on contradiction or invalid input and distinguish unresolved evidence with the warning exit status. | tests/evidence-manifest.test.mjs, tests/evidence-adapters.test.mjs, tests/evidence-integration.test.mjs |
| FR-016 | P1 | A finding answers three independent questions in three fields: whether CI blocks (`severity`), who decides (`disposition`: act or escalate), and how well the code is measured (`evidence`), alongside the analyzer that produced it (`parserTier`). A certain observation is never labelled uncertain, and a suggestion with an unsupported kind is omitted rather than coerced. | tests/findings-channels.test.mjs, tests/calibrated-channels-acceptance.test.mjs |
| FR-017 | P1 | Feedback selection covers every finding whose code is unmeasured or whose confidence is low, and discloses what it excludes, so the channel that validates a confidence label is never restricted to findings that label already doubts. | tests/feedback-sampling.test.mjs |
| FR-018 | P1 | A reviewed disagreement is recorded as a corpus row with rationale, date and opposite control, counted per code and corpus-wide, and excluded from every precision and recall denominator. Removing one fails baseline comparison. | tests/adjudication-record.test.mjs |
| FR-019 | P1 | The analyzer tier is computed at run time, carried on every finding, and a validator whose inputs fell back to pattern matching for an AST-capable language reports partial coverage naming the cause. Findings are retained; only coverage is downgraded. | tests/parser-tier.test.mjs |
| FR-020 | P2 | A threshold derived from observed data is fitted against a strictly proper scoring rule; every hand-set threshold names that constraint. The badge reports coverage honestly and withholds its top grade from a run that could not check everything it was asked to. | tests/threshold-constraint.test.mjs, tests/coverage-headline.test.mjs |
| FR-021 | P2 | A completed task naming a deliverable path that exists but that the feature never changed is reported as a review signal, distinct from a task whose deliverable is absent. | tests/untouched-claims.test.mjs |
| FR-007 | P1 | An agent can request a deterministic bounded task-context packet that prioritizes exact current evidence, excludes retired and unsafe material, preserves retrieval-only assurance, and abstains rather than returning weak matches. Existing task-graph behavior remains compatible. | tests/task-context.test.mjs, tests/agent-context-benchmark.test.mjs |
| FR-008 | P1 | The packed-package adoption journey distinguishes foreign hooks, composes one self-repairing managed block with user hook commands, emits complete remediation text, explains deterministic registry drift by field, follows proposed actions, and verifies the resulting state. | tests/adoption-workflow.test.mjs, tests/hooks.test.mjs, tests/hooks-contract.test.mjs, tests/spec-registry.test.mjs, tests/npm-pack-smoke.test.mjs |
| FR-009 | P1 | Reconciliation keeps changed-path inventory independent from bounded patch text and reports partial coverage instead of a ready result after timeout, overflow, or Git failure. | tests/shared-git.test.mjs, tests/reconcile.test.mjs |
| FR-010 | P1 | Instruction pointers resolve only safe exact paths or one unique basename; Git-ignored paths and nested Git checkouts are excluded from basename evidence, while ambiguity, symlinks, unsafe paths, and incomplete indexes remain explicit. | tests/instruction-audit.test.mjs |
| FR-011 | P1 | Planned requirements defer test traceability only when committed, clean, digest-current schema-v2 lifecycle evidence proves they remain planned. A structurally current registry that is new, removed from the Git index, or modified pending commit remains non-authoritative and explains restore-or-commit remediation without recommending artificial test markers. | tests/traceability-lifecycle.test.mjs |
| FR-012 | P1 | Exact finding-code policy can promote or demote one finding without weakening its validator, while intrinsic and effective severity remain visible in machine formats. | tests/severity.test.mjs, tests/sarif.test.mjs, tests/junit.test.mjs |
| FR-013 | P1 | CI, diagnose, and report expose combined READY, ATTENTION, or BLOCKED assessment while standalone score remains structural maturity rather than a guard verdict. | tests/assessment.test.mjs |
| FR-014 | P1 | Python collection-size evidence uses bounded non-executable static literal parsing and abstains on dynamic or ambiguous syntax. | tests/evidence-python-literal.test.mjs, tests/evidence-integration.test.mjs |
| FR-015 | P1 | JavaScript route discovery excludes HTTP-client calls and non-product helpers before deduplication, and composes static Express mounts across imported routers. | tests/js-ast.test.mjs, tests/routes-express-mounts.test.mjs |
| FR-016 | P1 | API contract omissions remain review-only because negative route extraction cannot prove runtime absence or authorize deletion. | tests/api-authority-precision.test.mjs, tests/api-write.test.mjs, tests/doc-role-boundaries.test.mjs |
| FR-017 | P1 | Field warning precision preserves historical prose, multiline skip reasons, test-fixture context, package-local capability counts, package-local env templates, authoritative OpenAPI selection, route-parameter equivalence, service boundaries, and runtime/schema parity. | tests/metrics-consistency.test.mjs, tests/todo-tracking.test.mjs, tests/field-context-precision.test.mjs, tests/environment.test.mjs, tests/docs-sync.test.mjs, tests/docguard-config-schema.test.mjs |
| FR-018 | P1 | A repo-wide Git hook must not block a working tree that never adopted DocGuard: guard reports errors in a project without `.docguard.json` as exit 3 rather than 1, the installed hook skips such a tree and permits exit 3, and adopted projects stay gated. Overwriting a foreign hook requires explicit repeated `--force` and never silently discards its backup. | tests/hook-fail-open.test.mjs, tests/hooks-contract.test.mjs |

## Non-Functional Requirements

| ID | Category | Requirement | Verification |
|---|---|---|---|
| NFR-001 | Security | Untrusted input passed to subprocesses uses argv-based invocation and validation appropriate to the command. | tests/security-init-injection.test.mjs |
| NFR-002 | Portability | The distributed CLI runs on supported Node versions. Babel supplies the full JS/TS tier; the CLI retains a regex fallback when the parser is absent. | tests/npm-pack-smoke.test.mjs |
| NFR-003 | Correctness | Cached memory plans invalidate when relevant working-tree inputs, configuration, or scanner implementation change. Unreadable or unsupported cache inputs cause a miss. | tests/plan-disk-cache.test.mjs |
| NFR-004 | Distribution integrity | Every relative README link in the npm artifact resolves inside that artifact; repository-only material uses an absolute URL. | tests/npm-pack-smoke.test.mjs |

## Success Criteria

The full supported-runtime test matrix and guard determine local release readiness. The reviewed benchmark records observed detector precision and recall with explicit coverage limits; its finite confidence interval is not universal accuracy. The frozen R7 evaluation supports opt-in task context through equal measured correctness, 50% fewer median steps, and 17% lower median latency against context packs. It also recorded 80% more median uncached input and does not establish universal agent productivity.

## User Scenarios

A developer edits a source file without committing. The next memory plan reflects that change. An adopter upgrades DocGuard in an existing repository and can inspect hooks, follow every proposed remediation, and verify the result without hidden initialization. An agent requests context for one qualified requirement and receives current hashed excerpts and linked tests, or an explicit abstention. The agent inspects a high structural grade and sees the separate readiness verdict and that factual accuracy remains unverified. A contributor challenges a confident finding, previews a metadata-only report, checks existing work, and supplies a synthetic regression example voluntarily.

## Traceability Matrix

The verification column above links each requirement to executable tests. The tests carry explicit requirement annotations. Fixture content and example IDs cannot satisfy a real requirement.

## Revision History

| Version | Date | Changes |
|---|---|---|
| 0.7.0 | 2026-09-18 | Record the uninitialised-project hook boundary (exit 3) and foreign-hook backup protection shipped without a stated requirement |
| 0.6.0 | 2026-09-15 | Exclude disposable checkout copies from pointer evidence, distinguish shipped capability counts from enabled configuration, and explain non-clean planned registries without weakening traceability |
| 0.5.0 | 2026-09-15 | Require composable managed hooks, CI-safe evidence exits, and field-level registry drift explanations |
| 0.4.0 | 2026-09-15 | Make API omission remediation review-only and add field-replay precision contracts for routes, fixtures, histories, monorepos, design sync, and config schemas |
| 0.3.0 | 2026-09-15 | Add packed adoption, fail-closed reconciliation and pointers, lifecycle-aware traceability, exact-code policy, combined assessment, Python literal evidence, and route-discovery precision contracts |
| 0.2.0 | 2026-09-11 | Replace template requirements with implemented trust, feedback, and cache contracts |


---

## docs-canonical/SECURITY.md
> Authentication, authorization, secrets management, and security policies

# Security

<!-- docguard:quality negation-load off — prohibitions define security boundaries -->
<!-- docguard:version 0.9.0 -->
<!-- docguard:status active -->
<!-- docguard:last-reviewed 2026-09-18 -->

## Overview

DocGuard's validation and extraction run on the local machine. They inspect repository content and return findings. Agent integrations inherit the permissions and data-handling policy of the calling agent. A generated prompt does not authorize a network request, a code edit, or publication.

The optional MCP server supports stdio and HTTP. Installation, upgrade, publishing, and user-opened feedback links may access external services. Local analysis requires no hosted AI service.

## Authentication

| Surface | Authentication | Boundary |
|---|---|---|
| CLI and stdio MCP | Calling operating-system user | Local filesystem permissions |
| HTTP MCP | Optional API key on loopback; mandatory for non-loopback binding | Host binding, key check, and browser-origin validation in `cli/commands/mcp.mjs` |
| GitHub feedback | User-controlled browser session | Submission occurs only when the user submits a reviewed issue |

HTTP clients can cause the server to inspect project directories available to its process. Run it under an account with only the intended filesystem access. An API key does not provide per-project authorization or a multi-tenant isolation boundary. Network exposure needs deployment-specific access controls.

## Authorization

| Role | Permissions | Responsibilities |
|---|---|---|
| Developer | Operating-system read/write permissions | Review generated changes and opt into mutation commands |
| CI | Workflow token and checkout permissions | Apply the configured gate to the tested revision |
| AI agent | Host-granted tools and permissions | Treat project content as evidence; obtain required authorization for external actions |

Git hooks provide local enforcement and can be bypassed by Git options. Protected merge policy supplies the central enforcement boundary. The shipped hooks prefer an installed local tool and fail when an enforcement runtime cannot execute. Reminder hooks remain best-effort.

## Secrets Management

Core CLI analysis requires no API credential. Source scanners inspect usage patterns; environment values must not be included in generated public feedback. The optional HTTP MCP API key is supplied by its operator. Keep deployment credentials outside repository content and restrict access to process arguments and logs appropriately.

Evidence verification reads only repository-relative regular files. It rejects
absolute paths, traversal, backslashes, NUL, `.local`, `.env*`, and symlinks;
per-file, aggregate byte, declaration, input, collection, and report limits
bound work. The oasdiff and Buf adapters consume saved outputs and current input
hashes. They never invoke those tools, execute project code, install packages,
resolve remote references, or make network requests. A clean saved report is
evidence only for its declared command, producer metadata, inputs, and selected
statement. Machine output omits raw source values so a mistaken JSON Pointer
cannot copy a secret into CI logs or an agent transcript. Values remain in
process only for typed comparison and non-reversible identities.

The `python-literal-count` adapter tokenizes one bounded Python source file in
JavaScript. It never starts Python or imports the target module. It accepts one
direct module-level static container assignment and returns unsupported or
inconclusive for comprehensions, unpacking, aliases, concatenation, conditionals,
duplicate assignments, malformed syntax, and parser-budget exhaustion.

Reconciliation invokes Git with argument arrays and disables text conversion.
Changed-path inventory and patch text have separate budgets. Any timeout,
overflow, parse failure, or path-limit breach returns partial coverage and cannot
produce a ready claim. Instruction-pointer indexing rejects traversal, private
paths, symlinks, and ambiguous basenames and never follows a match outside the
selected repository.

Task-context selection reuses the same bounded safe reader. It accepts at most
2,000 normalized task characters, stores only the task digest in output, and
does not execute project code, hooks, package managers, an LLM, or network
requests. Eligible prose is limited to configured canonical documents, approved
current specs whose recorded digest matches, and bounded project rules. `.local`,
environment files, traversal, backslashes, symlinks, oversized files, retired or
unapproved specs, and stale registry artifacts never enter selected content.

Feedback issue URLs contain allowlisted detector metadata, classification, parser tier, and a synthetic-shape duplicate identity. Full local finding records can include private paths and diagnostic text. Fixture manifests are accepted only with explicit synthetic-content and redaction-review attestations; they reject escaping paths, `.git`, `.local`, symlinked inputs, unsafe config values, and oversized content. Preview mode avoids all writes. Generated tests contain the attested synthetic fixture, so users must review it before contribution.

The optional external benchmark accepts only credential-free public HTTPS Git URLs pinned to full commit hashes. It disables interactive Git authentication and global/system Git configuration, forbids the file protocol, never runs project scripts, copies no `.git` metadata into case projects, and removes its temporary root by default. External execution is absent from ordinary tests and package installation.

## Subprocess Safety

Pass untrusted arguments through argv arrays and validate values for their intended operation. Avoid interpolating configuration or repository content into shell commands. Existing static command strings do not authorize expanding their input surface. Regression tests in `tests/security-init-injection.test.mjs` exercise the input boundary.

## Command Safety Levels

| Operation | Source writes | Auxiliary writes / effects |
|---|---|---|
| guard, score, diff, diagnose | None by default | Plan caching may create `.docguard/` artifacts; explicit mutation flags change behavior |
| ci | None | Records history unless `--no-history` is set |
| feedback | None | Saves local records or an explicitly requested direct `tests/*.test.mjs` contribution unless `--preview`; prints opt-in URLs but never submits |
| memory --pack | None | Writes a generated context pack unless `--stdout` is used |
| agent, agent --task | None | Emits a task graph or transient bounded context; never stores raw task text or selected output |
| fix --write, sync --write | Targeted documentation edits | Mapped human documents permit only unique `source=code` sections; backups and fix history remain enabled where supported |
| reconcile | None by default | `--write` delegates only mechanical generated-section refreshes to `sync` |
| specs, specs preflight | None for check/plan modes | `specs --write` refreshes the registry; `specs complete --write` transactionally records a reviewed outcome and active context |
| verify --evidence | None | Reads the strict local manifest, selected Markdown, source files, and saved reports; guard consumes the same evaluator |
| retire --write | Explicit clean tracked documentation only | Requires retained-ref recovery proof, clean replacement/evidence docs, and no live Markdown backreferences |
| init, generate | Documentation and configuration scaffolding | Explicit force options may overwrite content |
| hooks | Hook configuration and executable scripts | Inventory distinguishes managed, legacy, foreign, missing, and unreadable hooks; removal preserves foreign commands around a managed block; auto-fix hooks may edit and stage documentation. A foreign hook is skipped under `--force` and no `.bak` is written, so overwriting one requires `--force` twice rather than a plain re-install. An installed hook skips a working tree with no `.docguard.json` and permits guard exit 3, so a branch or worktree that never adopted DocGuard is not blocked by a repo-wide hook |
| report | None by default | `--out` writes an artifact |

Review the exact command and flags before assigning privileges. CLI help is the authoritative command inventory.

Mapped paths do not weaken the write boundary. A new target or an existing
`docguard:generated true` file can receive a single-role full-document write;
otherwise only an exact code-owned section can change. The command validates
all mapped targets before its first visible write, rejects malformed or shared
ownership, and treats `--force` as overwrite intent rather than authorization.

## Supply Chain

The package declares one exact-pinned dependency, `@babel/parser`, with its transitive Babel dependencies recorded in `package-lock.json`. AST extraction degrades to a regex fallback when Babel is unavailable. Python AST extraction optionally uses the installed `python3` runtime. No additional runtime package is introduced by the trust improvements.

Cloudflare binding extraction reads source and Wrangler file presence only. It
does not load configuration, import application modules, contact Cloudflare, or
read binding values. AST trust requires an official handler/export/import/class
signal and lexical identity; similarly named local objects do not grant binding
status. The parser fallback discloses unsupported class, Pages, and imported-env
forms instead of treating their absence from the inventory as proof.

Repository-root guidance reads only bounded ancestor metadata: regular
`.docguard.json`, `package.json`, and `pnpm-workspace.yaml` files, plus Git's
reported working-tree root. It does not execute package managers or project
code, follow manifest symlinks, scan outside the selected Git boundary, or
change the directory passed to a command.

Dependency audit results are time-specific observations. Run the current audit and supported Node-version matrix before release; a historical clean audit is not a continuing guarantee. Pin third-party CI actions to verified commit SHAs and install from the lockfile.

## .gitignore Audit

Exclude `node_modules`, environment values, generated build output, and private local files from version control. `.docguardignore` controls analysis coverage separately; it is not a secrecy boundary for every tool that runs in the repository.

## Security Rules Checklist

- Validate subprocess inputs at their call boundaries.
- Preserve provenance checks before mechanical edits.
- Keep private diagnostics separate from public feedback payloads.
- Treat submitted reproductions as untrusted data.
- Require credentials for non-loopback HTTP MCP binding.
- Disclose unknown or unsupported verification instead of asserting success.
- Verify protected merge policy independently of local hook installation.

## Revision History

| Version | Date | Changes |
|---|---|---|
| 0.10.0 | 2026-09-14 | Prevent scoped evidence output from exposing raw source values |
| 0.9.0 | 2026-09-14 | Document public benchmark isolation and synthetic feedback-fixture privacy boundaries |
| 0.8.0 | 2026-09-14 | Document reconciliation and transactional spec lifecycle authority |
| 0.7.0 | 2026-09-11 | Document HTTP MCP, auxiliary writes, enforcement scope, and feedback privacy |


---

## docs-canonical/TEST-SPEC.md
> Test coverage requirements, testing strategy, and quality rules

# Test Specification

<!-- docguard:version 1.1.0 -->
<!-- docguard:status active -->
<!-- docguard:last-reviewed 2026-09-18 -->

> DocGuard has a single optional-load npm dependency (`@babel/parser`) and an optional `python3` AST tier. CLI integration tests cover the full stack with `node:test` (zero dev dependencies) and exercise both AST extractors (`js-ast`, `py-ast`) plus their regex fallbacks. The Python AST tests skip themselves automatically on a machine that lacks `python3`.

| Metadata | Value |
|----------|-------|
| **Status** | ![Status](https://img.shields.io/badge/status-active-brightgreen) |
| **Project Type** | CLI |
| **Test Framework** | `node:test` (built-in) |
| **Test Files** | `tests/` |

---

DocGuard's tests verify command behavior through subprocess execution. Each test runs the full CLI binary via execSync, capturing stdout and checking output patterns. This approach tests the complete stack in a single pass: argument parsing, config loading, validator execution, and output formatting.

Tests are designed to be config-aware. They verify that project-type settings like needsEnvExample and testFramework correctly influence scoring and validation behavior. Regression guards preserve known failures with dedicated assertions and neighboring valid cases.

All tests use the built-in node:test framework with zero test dependencies. CI runs the suite on Node 18, 20, 22, and 24. Its runtime budget catches large regressions; local timing depends on runtime and filesystem. Record measured timing with its environment rather than asserting a universal duration.

Test names follow the pattern: "verb + expected behavior" (e.g., "runs and shows a score", "respects projectTypeConfig"). Each test should isolate its mutable fixtures and clean up its resources.

## Test Categories

| Category | Framework | Location | Run Command |
|----------|-----------|----------|-------------|
| Unit | node:test | tests/ | `npm test` |
| CLI Integration | node:test | tests/ | `npm test` |

> **CLI integration tests cover command routing; packed adoption tests cover the
> release boundary.** Source-tree subprocess success does not prove that npm
> contains every linked or imported file, that remediation is actionable, or
> that an existing repository remains unchanged during inspection.

All test files live in `tests/` and match the glob `tests/*.test.mjs` — the test runner supplies the current inventory as the suite grows; see the Source-to-Test Map below for the source→test traceability that matters.

## Coverage Rules

| Metric | Target | Current |
|--------|:------:|:-------:|
| Command Coverage | Every public command | Scenario coverage; inspect tests before claiming exhaustive behavior |
| Validator Coverage | Every validator | Positive, negative, and regression cases |
| Flag Coverage | Risk-based | Tested scenarios; no exhaustive coverage claim |
| Test Count | — | Current count is emitted by `npm test` |

## Source-to-Test Map

| Source File | Test File | Status |
|------------|-----------|:------:|
| `cli/docguard.mjs` | `tests/commands.test.mjs` | ✅ |
| `cli/shared.mjs` | `tests/commands.test.mjs` | ✅ |
| `cli/commands/init.mjs` | `tests/commands.test.mjs` | ✅ |
| `cli/commands/guard.mjs` | `tests/commands.test.mjs` | ✅ |
| `cli/commands/score.mjs` | `tests/commands.test.mjs` | ✅ |
| `cli/commands/diff.mjs` | `tests/commands.test.mjs` | ✅ |
| `cli/commands/generate.mjs` | `tests/commands.test.mjs` | ✅ |
| `cli/commands/agents.mjs` | `tests/commands.test.mjs` | ✅ |
| `cli/commands/agent.mjs`, `cli/scanners/task-context.mjs` | `tests/agent.test.mjs`, `tests/task-context.test.mjs` | ✅ |
| `cli/commands/hooks.mjs` | `tests/hooks.test.mjs`, `tests/hooks-contract.test.mjs` | ✅ |
| `extensions/spec-kit-docguard/extension.yml`, `extensions/spec-kit-docguard/commands/*.md` | `tests/hooks-contract.test.mjs` | ✅ |
| `cli/commands/diagnose.mjs` | `tests/commands.test.mjs` | ✅ |
| `cli/commands/badge.mjs` | `tests/commands.test.mjs` | ✅ |
| `cli/commands/ci.mjs` | `tests/commands.test.mjs` | ✅ |
| `cli/commands/fix.mjs` | `tests/commands.test.mjs` | ✅ |
| `cli/commands/watch.mjs` | `tests/commands.test.mjs` | ✅ pass |
| `cli/commands/publish.mjs` | `tests/commands.test.mjs` | ✅ |
| `cli/commands/trace.mjs` | `tests/commands.test.mjs` | ✅ |
| `cli/shared-requirements.mjs` | `tests/traceability.test.mjs`, `tests/archive.test.mjs` | ✅ |
| `cli/scanners/requirement-evidence.mjs` | `tests/traceability.test.mjs`, `tests/spec-registry.test.mjs` | ✅ |
| `cli/commands/retire.mjs` | `tests/archive.test.mjs` | ✅ |
| `cli/writers/file-transaction.mjs` | `tests/file-transaction.test.mjs` | ✅ |
| `cli/scanners/reconciliation.mjs`, `cli/commands/reconcile.mjs` | `tests/reconcile.test.mjs` | ✅ |
| `cli/commands/specs.mjs` completion path | `tests/spec-completion.test.mjs` | ✅ |
| Packed install adoption journey | `tests/adoption-workflow.test.mjs`, `tests/npm-pack-smoke.test.mjs` | ✅ |
| `cli/shared-git.mjs`, reconciliation coverage | `tests/shared-git.test.mjs`, `tests/reconcile.test.mjs` | ✅ |
| `cli/shared-ignore.mjs`, `cli/scanners/instruction-audit.mjs` | `tests/docguardignore.test.mjs`, `tests/instruction-audit.test.mjs` | ✅ |
| `cli/shared-validator-surface.mjs`, `cli/validators/metrics-consistency.mjs`, `cli/validators/canonical-sync.mjs` | `tests/metrics-consistency.test.mjs`, `tests/canonical-sync.test.mjs`, `tests/mechanical.test.mjs` | ✅ |
| `cli/validators/traceability.mjs`, lifecycle trust in `cli/scanners/spec-registry.mjs` | `tests/traceability-lifecycle.test.mjs` | ✅ |
| Finding-code enforcement and machine writers | `tests/severity.test.mjs`, `tests/sarif.test.mjs`, `tests/junit.test.mjs` | ✅ |
| Combined readiness assessment | `tests/assessment.test.mjs` | ✅ |
| `benchmarks/lib/manifest.mjs`, `benchmarks/lib/metrics.mjs`, `benchmarks/lib/compare.mjs` | `tests/benchmark-manifest.test.mjs`, `tests/benchmark-metrics.test.mjs` | ✅ |
| `benchmarks/lib/runner.mjs`, `benchmarks/run.mjs` | `tests/benchmark-runner.test.mjs` | ✅ |
| `benchmarks/lib/baseline.mjs`, `schemas/docguard-benchmark-baseline.schema.json`, `benchmarks/baseline.json` | `tests/benchmark-baseline.test.mjs` | ✅ |
| `benchmarks/lib/precision-evidence.mjs`, `benchmarks/generate-precision-evidence.mjs`, `cli/precision-evidence.mjs`, `cli/precision-evidence-data.mjs`, `schemas/docguard-precision-evidence.schema.json` | `tests/precision-evidence.test.mjs` | ✅ |
| `benchmarks/agent-context/run.mjs`, task-context schemas and fixtures | `tests/agent-context-benchmark.test.mjs` | ✅ |
| `cli/evidence/*.mjs`, `cli/validators/evidence.mjs` | `tests/evidence-manifest.test.mjs`, `tests/evidence-adapters.test.mjs`, `tests/evidence-integration.test.mjs` | ✅ |
| `cli/feedback-fixture.mjs`, `cli/commands/feedback.mjs` | `tests/feedback-fixture.test.mjs`, `tests/feedback-contributions.test.mjs` | ✅ |
| `cli/validators/document-lifecycle.mjs` | `tests/document-lifecycle.test.mjs` | ✅ |
| `cli/commands/specs.mjs`, `cli/scanners/spec-registry.mjs`, `cli/validators/spec-registry.mjs` | `tests/spec-registry.test.mjs` | ✅ |
| `cli/validators/structure.mjs` | `tests/commands.test.mjs` | ✅ |
| `cli/validators/docs-diff.mjs` | `tests/commands.test.mjs` | ✅ |

Completion tests cover initial `implemented → verified` delivery, evidence and
dirty-tree blockers, living-spec `verified → verified` maintenance, and refusal
of empty repeats driven only by generated lifecycle artifacts.

> **Note**: `watch.mjs` is an interactive file-watcher (uses `fs.watch` + process signals). It is
> covered by automated lifecycle tests, including filesystem watcher error handling.
> Manual checks supplement platform-specific event behavior.

## Critical CLI Flows

| # | Flow | Test File | Status |
|---|------|-----------|:------:|
| 1 | `docguard audit` | `tests/commands.test.mjs` | ✅ |
| 2 | `docguard init` | `tests/commands.test.mjs` | ✅ |
| 3 | `docguard guard` | `tests/commands.test.mjs` | ✅ |
| 4 | `docguard guard --format json` | `tests/commands.test.mjs` | ✅ |
| 5 | `docguard score` | `tests/commands.test.mjs` | ✅ |
| 6 | `docguard score --format json` | `tests/commands.test.mjs` | ✅ |
| 7 | `docguard score --tax` | `tests/commands.test.mjs` | ✅ |
| 8 | `docguard diagnose` | `tests/commands.test.mjs` | ✅ |
| 9 | `docguard diagnose --format json` | `tests/commands.test.mjs` | ✅ |
| 10 | `docguard generate` | `tests/commands.test.mjs` | ✅ |
| 11 | `docguard init --profile starter` | `tests/commands.test.mjs` | ✅ |

---

## Revision History

| Version | Date | Author | Changes |
|---------|------|--------|---------|
| 1.1.0 | 2026-09-15 | DocGuard Team | Add field-replay controls for shipped validator counts, ignored checkout pointer copies, and non-clean planned lifecycle advice |
| 1.0.0 | 2026-09-15 | DocGuard Team | Add executable hook-composition, direct evidence exit-code, and field-level registry-difference regressions |
| 0.7.0 | 2026-03-13 | @raccioly | Added trace, publish; watch.mjs coverage justified (ISO 29119); 15 commands |
| 0.5.0 | 2026-03-13 | @raccioly | Added diagnose, guard JSON, profile, tax tests (24→30) |
| 0.3.0 | 2026-03-12 | @raccioly | Real tests, project-type-aware spec |
| 0.1.0 | 2026-03-12 | DocGuard Generate | Auto-generated (corrected) |

## Trust regression scenarios

`tests/score-assurance.test.mjs` checks that structural grades never claim factual verification and that CI, diagnose, and reports retain this boundary. `tests/feedback-contributions.test.mjs` checks confident-finding selection, preview behavior, and outbound metadata privacy. Cache tests must change source contents without changing a manifest or Git HEAD, including repeated edits and fresh-process reads. Hook tests execute generated scripts against controlled runtimes rather than merely matching shell text. Traceability tests pair synthetic fixture IDs with genuine requirement annotations.

A detector fix should include a clean near-miss and a real defect. Held-out neighboring cases are required to evaluate generalization. `benchmarks/baseline.json` is the reviewed reference: 24 measured cases across 12 repository groups plus one explicit unsupported case. Its observed perfect point estimates retain Wilson confidence limits and do not establish exhaustive correctness. The envelope's `review.measures` is `benchmark-precision` and its `review.caveat` must accompany any quoted number; tests assert the committed metrics and caveat are recomputable from the committed cases, and that a hand-edited ratio, stale caveat, or pre-provenance envelope is rejected.

The finding shape is itself a contract, because findings are copied verbatim into shareable feedback records. `tests/precision-evidence.test.mjs` pins the exact key set a guard run emits, so a field added without a specification fails the suite; it also asserts that `location` is a string or null and that `disposition` and `evidence.status` hold values from their declared vocabularies. `tests/findings-channels.test.mjs` pins the derivation rules for the three channels: `disposition` from `suggestion.kind` with `escalate` as the fail-closed default, `evidence` projected per code and always standing alone, and `reportable` true for an unmeasured code even at high confidence. A malformed `suggestion.kind` must omit the suggestion, so a typo stays visible as a missing suggestion instead of reclassifying a fix as a review.

Retirement tests use disposable Git repositories and verify both sides of the boundary: completed planning material is reported for review, while active neighboring material stays clean. Write-path tests must prove retained-ref recovery metadata and refusal of source code, dirty, untracked, required, symlinked, private, protected, submodule, and out-of-root paths. Read-only plan and check modes must not modify repository state.

## Enterprise precision regressions

Regression cases are synthetic and name no consumer repositories. Keep a valid near-neighbor beside every detected defect: formatting versus declaration deletion; negated versus current technology use; explained versus unexplained skips; mock expectations versus credentials; implemented versus omitted contract endpoints; Worker bindings versus local variables; historical versus active documents. Check coverage tests distinguish unsupported and missing inputs from executed checks. Document-role tests exercise mapped findings, raw/loaded configuration parity, unsafe paths, read-only planning, successful section-bounded generation/sync/repair, backup creation, multi-target preflight atomicity, and force-resistant malformed/shared ownership.

Worker fixtures reproduce Cloudflare's module-handler argument, exported Pages
context, imported global `env`, and entrypoint-class property forms. Every form
has a lookalike source, class, function, shadow, or computed-key control. The
packed-package test runs without installed Babel and requires AST-only forms to
remain disclosed as limitations.

Python import-graph fixtures cover regular flat and `src/` packages, absolute
and explicit-relative imports, cycles, and configured layer violations. Paired
controls keep standard-library and third-party imports out of the local graph;
dynamic imports, `sys.path` mutation, parse failures, interpreter absence, and
ambiguous workspace modules must remain partial or unsupported applicability.
Tests never import or execute fixture modules.

Repository-root fixtures pair ancestor DocGuard, npm workspace, and pnpm
include evidence with explicit-dir, local-config, exclusion, Git-only, and
nested-repository controls. Command tests require the selected package result to
remain unchanged, human guidance to contain an exact rerun, and machine stdout
to remain parseable while stderr carries the typed diagnostic.

Independent review must challenge suppression paths, not only the original false-positive example. Cross-project runs use disposable snapshots and verify consumer content remains unchanged. Finding counts alone cannot establish precision or recall.

Task-context tests require exact path and qualified-requirement priority,
determinism, fixed excerpt/read budgets, stale and retired lifecycle exclusion,
private/symlink rejection, honest abstention, aligned human/JSON output, and
unchanged task-graph behavior. The frozen agent benchmark keeps hidden
evaluators outside copied repositories, proves original fail-to-pass and
pass-to-pass states plus reviewed references, records all 27 observations, and
applies its committed non-inferiority and efficiency gate without an LLM judge.

Run `node benchmarks/run.mjs --baseline benchmarks/baseline.json` for the network-free synthetic corpus (CI does this on every push; pinned public baseline cases are reported as `outOfSelection`, not as removed). Run `node benchmarks/run.mjs --external --baseline benchmarks/baseline.json` explicitly for pinned public sources. The comparator gates new case-level false positives, false negatives, removals within the selection, and supported-case abstentions. Persisted cold/warm timings are observational because matching Node and platform metadata cannot exclude host contention. Runtime regression claims require at least five controlled samples from the same paired comparison session and a greater-than-20-percent real-workload change.

Evidence adapter tests use paired valid and invalid fixtures. They cover RFC
6901 escaping and array indices, strict typed values, duplicate-free sets,
bounded collections, saved clean and non-clean oasdiff/Buf reports, malformed
reports, unknown adapter versions, input-digest staleness, missing and ambiguous
Markdown targets, unsafe paths, and stable identity behavior. Integration tests
confirm that guard, JSON, SARIF, JUnit, score assurance, agent context, and direct
CLI exit codes retain the five-state boundary. Contradicted and invalid direct
verification exits 1; stale, inconclusive, and unsupported evidence exits 2;
verified evidence exits 0. No test invokes third-party project code or an
external compatibility binary.

Python literal evidence adds paired list, tuple, set, dictionary, annotated,
multiline, string/comment, dynamic, duplicate-assignment, malicious, symlink,
and explicit-budget fixtures. Tests assert that project Python is never
executed. The packed adopter workflow starts with a foreign hook and a
specification missing immutable metadata, runs the installed CLI, validates
every rendered suggestion, follows the proposed remediation, and requires the
post-action registry check to pass.
Managed-hook tests reinstall into a hook with commands on both sides, require
exactly one marker pair, repair nested markers emitted by affected releases,
execute every successful postlude, and retain fail-closed exits. Spec-registry
tests require stale JSON and text output to identify bounded field paths,
including order-only canonicalization.

Express route fixtures pair real registrations with chained HTTP-client calls,
same-path test requests, conventional test-helper trees, single-level mounts,
and nested imported mounts. They also cover static path constants, pathless
mounts, aliased router exports, trailing middleware, and multiple router symbols
in one module. Non-product evidence is rejected before route-key deduplication
so a discarded test call cannot hide a later product route.

API omission tests preserve documented endpoints across generated and human
documents, mapped roles, and forced writes whenever the only runtime-absence
evidence is a bounded scan. Field-replay controls pair historical and current
metrics, package capability and enabled-project validator counts, multiline and
disconnected skip reasons, repeated and unrepeated test credentials,
package-local and ignored environment templates, ignored and nested-checkout
pointer copies, committed and non-clean planned lifecycle registries,
authoritative and stale OpenAPI copies, parameter-equivalent and different
routes, frontend utilities and real services, and known versus unknown validator
schema keys.


---

## DRIFT-LOG.md
> Known deviations from canonical documentation

# Drift Log

<!-- docguard:last-reviewed 2026-09-14 -->

> Documents conscious deviations from canonical specifications.
> Every `// DRIFT: reason` in code must have a corresponding entry here.

| Date | File | Canonical Doc | Drift Description | Severity | Resolution |
|------|------|---------------|-------------------|----------|------------|
| 2026-03-13 | `cli/commands/generate.mjs` | ARCHITECTURE.md | AGENTS.md template includes `// DRIFT: reason` as an instruction pattern for end users. These are template strings, not actual code deviations. | Info | By design — template content |
| 2026-03-13 | `cli/commands/generate.mjs` | ARCHITECTURE.md | DRIFT-LOG.md template includes `// DRIFT: reason` as placeholder text. | Info | By design — template content |
| 2026-03-13 | `cli/commands/agents.mjs` | ARCHITECTURE.md | Agent config generators include `// DRIFT: reason` as instruction text for AI agents. 3 occurrences across Windsurf, Cursor, and generic agent configs. | Info | By design — instruction content |
| 2026-03-13 | `cli/validators/drift.mjs` | ARCHITECTURE.md | Drift validator references `// DRIFT:` pattern in JSDoc and regex. | Info | By design — validator implementation |
| 2026-05-12 | `tests/drift.test.mjs` | ARCHITECTURE.md | Drift validator tests use `// DRIFT:` comments to simulate project files having drift comments. | Info | By design — test implementation |
| 2026-05-26 | `tests/scoping-extended.test.mjs` | ARCHITECTURE.md | v0.15 P3 test fixture builds `// D' + 'RIFT:` strings via concat to test changed-files scoping without false-positiving the outer scan. | Info | By design — test implementation; mitigated by v0.15.1 hotfix that skips test files by default in Drift-Comments |
| 2026-05-26 | `cli/validators/drift.mjs` | ARCHITECTURE.md | Drift-Comments validator v0.15.1+ skips test files by default (matches TODO-Tracking's pattern). Opt in via `config.drift.includeTestFiles` if your project genuinely uses DRIFT markers in test code. | Info | By design — defensive default to prevent fixture false-positives |
| 2026-05-26 | `CHANGELOG.md` / `extensions/spec-kit-docguard/skills/*` | None | v0.12-v0.15 changelogs and release notes reference `// DRIFT:` in feature descriptions (e.g. K-3 .docguardignore, v0.13 sync, v0.14 P3 scoping). Documentation prose only, not actionable drift. | Info | By design — release notes |
| 2026-07-03 | `templates/commands/*`, `CHANGELOG.md`, `docs/ai-integration.md` | None | v0.29 batch audit: the DRIFT mentions in recently-committed files are the known by-design classes above (template instruction text, validator docstrings, changelog prose, and the new AI-integration guide's workflow step 6 teaching the drift protocol). No new code deviations from canonical docs were introduced by the findings migration, generate split, or integration-surface work. | Info | Audited — no actionable drift |
| 2026-07-03 | post-v0.29 batch (`cli/scanners/speckit.mjs`, `tests/speckit-phantom.test.mjs`, `packaging/*`) | None | Post-release batch audit (phantom detection, instruction audit, trace --features, distribution files): DRIFT mentions are validator/test/doc prose of the by-design classes above. No new code deviations. | Info | Audited — no actionable drift |


---

## CHANGELOG.md
> Version history and release notes

# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added
- **Three channels on every finding, replacing one field that answered three
  questions.** `confidence` described the detector's certainty, decided whether
  a human should look, AND gated which findings the feedback loop sampled.
  Collapsed together, a certain observation read as an uncertain one, and —
  because `reportable` derived from `confidence` — the channel that would
  validate the label sampled only findings the label already doubted. On a
  repository with 17 high-confidence findings across never-benchmarked codes,
  `docguard feedback` selected zero.

  Every finding now carries `disposition` (`act` when DocGuard asserts a defect
  and names the correction, `escalate` when the judgement is the reader's),
  `evidence` (whether the reviewed corpus has measured this code, with `n` and
  a Wilson interval when it has), and `parserTier` (which analyzer produced it).
  All additive: no existing field changed meaning, and `guard --format json`,
  SARIF `properties` and feedback records carry all of them.

  Guard now prints `N to fix · M to review` under the verdict. Across fourteen
  real repositories, 671 findings split 234 act / 437 escalate — and 5 of 671
  (0.7%) carry any benchmark evidence at all.

- **`docguard feedback` samples the labels nobody has validated.** Selection is
  now "unmeasured code OR low confidence", and the command states what it left
  out (`excluded.findings`, `excluded.codes`) instead of hiding it.

- **Reviewed disagreements leave a record.** A reported false positive the
  maintainers reviewed and declined to act on previously vanished: the corpus
  could only absorb one that had already been repaired, which made "precision
  1.0" a property of the contribution pipeline rather than of the detectors.
  `buildAdjudicationRow()` produces a corpus case carrying the same opposite
  control and attestations as a measured case plus a rationale and date.
  Reported as `adjudicated: { policyDisagreements, ambiguous }` beside every
  rate and inside none of them — scoring such a case either way would let a
  disputed policy move a measurement.

- **A design constraint for any future threshold fitted from data**
  (`docguard.calibrated-finding-channels#FR-018`): it must be fitted against a
  strictly proper scoring rule (Brier or logarithmic). Accuracy, F1 and "fewest
  reported false positives" are all maximised by a detector that abstains, or
  that asserts high confidence on whatever it still emits; none of them
  penalises a confidently wrong label. Nothing tunes from data today, which is
  why the constraint is recorded now — a test pins the pointer comment at all
  six hand-set threshold sites.

- **The unverified-claims count travels with the badge.** A green
  `628/628 passed` badge printed a few lines under "9 documented claim(s) are
  unverified against code" was the last misleading surface: the two numbers were
  adjacent and unlinked, and only one of them got pasted into READMEs. Both
  `docguard badge` and the badge `guard` prints now carry a companion
  `claims_unverified` badge, amber above zero. The score and pass badges keep
  their own colour — they report structural gates that did pass, and folding
  factual verification into them would overload one number with two meanings.
  The count is rendered in three states, not two: `buildScoreAssurance` returns
  null rather than zero when claim extraction fails, and that renders as
  `unknown`, because "0 unverified" printed when DocGuard checked nothing is
  precisely the false green this tool exists to prevent.

- **An adoption baseline now reports its age and what it is still hiding.**
  `saveBaseline` had always written `generatedAt` and `loadBaseline` had always
  discarded it, so 233 frozen findings looked identical on day 1 and day 400 and
  a baseline could silently become permanent amnesty. Runs now print
  `baseline is N day(s) old; M still suppressed; K no longer occur — safe to
  drop`. Both counts fall out of the existing suppression loop at no added cost,
  and the stale count is the one that lets a baseline shrink instead of only
  ever growing. The wording stays inside what the file can prove: entries carry
  occurrence counts, not per-entry timestamps, so "still suppressed" is
  demonstrable where "never triaged" would be a guess.

- **Benchmark evidence says whether it still applies to the build that ran.**
  `explain` reported "Measured on DocGuard 0.41.7; you are running 0.42.0" —
  true, and useless: it compared version strings when the question is whether
  the measured number still describes the code that just ran. It was also a
  false alarm. The detectors shipped in 0.42.0 are byte-identical to the
  benchmarked build, so every number remained exactly as valid. Comparing git
  revisions could not answer it either, since `.npmignore` excludes `.git/` and
  `benchmarks/` is unpublished, leaving an installed user with no repository to
  diff. `benchmarks/baseline.json` now records a `detectorsDigest` over the
  detector sources the corpus was actually run against — reviewed input, never
  recomputed from the working tree, so an edited detector cannot re-stamp itself
  as calibrated — and the runtime hashes the shipped `cli/validators` and
  `cli/scanners` to compare. A release that changes only docs or the CLI shell
  now says the detectors are unchanged and the number still applies. The hash is
  only computed when the version strings already disagree, so the matching path
  costs nothing and the mismatching path costs about 1.2ms. An unreadable
  install reports `unknown` and falls back to the old wording, never to a false
  all-clear.

- **A short list of untiered files is printed instead of counted.** "3 file(s)
  in no validation tier" with the names one `--verbose` away is a nag that
  cannot be acted on from the run that printed it. At three files or fewer the
  list is the message, so it is shown inline; above that the calm count and the
  `--verbose` pointer still win, because a wall of paths every run is what
  trains users to ignore the line.

### Changed
- **The agent skills triage on `disposition`, not severity alone.** `docguard-guard`
  told agents to sort findings by severity and, for warnings, to "consider
  running `/docguard.fix` for automated remediation" — which would have an agent
  auto-fixing findings whose judgement belongs to a human. The skill now leads
  with the act/escalate split, documents all five finding channels, reports work
  as `N to fix · M to review`, and states that absence of a finding under a
  `partial` validator or a `regex-fallback` tier is weak evidence.
  `docguard-fix` now declares `act` findings its only scope and refuses to edit
  a document to silence an escalation — stamping a fresh review date to clear a
  freshness signal destroys the signal without doing the review.

  `docguard-review`, `docguard-score` and `docguard-sync` now carry the same
  channels. Each had its own way of losing them: `review` ranks findings by
  severity into a CRITICAL list, which put an unadjudicated signal at the top
  with an edit beside it; `score` promised point gains for clearing guard
  warnings, which prices an escalation and turns "reach grade A" into an
  instruction to edit documents until messages stop printing; `sync` refreshes
  code-truth sections, and a re-sync that moves a timestamp is the fastest way
  to make a freshness escalation disappear without anyone reviewing anything.

- **`diagnose`, `ci` and `report` surface the disposition split.** They consume
  the same guard run as `guard` but showed the pre-channel view, so an agent
  reading them got back the flat list the channels exist to break up.
  `diagnose` matters most — it turns guard output into AI fix prompts, and the
  prompt said `TASK: Fix N documentation issue(s)` over a list that mixed
  defects with signals. An escalation handed to an agent under that heading
  produces a document edited until the message stops printing.

  The prompt now splits `DEFECTS TO FIX` from `SIGNALS TO REVIEW`, gives each
  signal the detector's own decision text rather than the validator's fix verb,
  excludes escalations from the remediation steps and `fixCommands`, and states
  that a judged signal will still print — an agent that reads a non-zero count
  as failure otherwise keeps editing. `diagnose --format json` adds
  `dispositionCounts` and the five channels per issue, with `fixKind: 'review'`
  for escalations. `ci` reports the split beside the verdict, because an exit
  code cannot distinguish a green-and-silent run from a green-but-escalating
  one. `report` adds a disposition/confidence/evidence/parser column per code
  and a per-run summary row, so an auditor reading "3 × FRS002, severity warn"
  can tell DocGuard never asserted those documents were wrong.

- **SPK010 — a completed task that names a file the feature never changed.**
  The phantom check (SPK008) asks whether a checked task's deliverable EXISTS,
  which a task naming already-existing files satisfies immediately, whether or
  not the work happened. That hole is not hypothetical: in this repository a
  documentation task listed six contract files, was marked `[x]`, and shipped
  with one of them — a SKILL.md — unmodified, still telling agents to auto-fix
  findings a human was supposed to judge.

  SPK010 asks the other question: did this feature's history ever touch it? The
  window is the commit that introduced the spec directory through HEAD, plus
  the working tree, so uncommitted work counts. Only falsifiable slashed paths
  convict; a non-existent path stays SPK008's business, a path inside the
  spec's own directory is never its own deliverable, and the check is silent
  when git is unavailable or the spec has no introducing commit. Suppress one
  task with `<!-- docguard:ignore SPK010 — reason -->`, or disable with
  `"specKit": { "untouchedClaimCheck": false }`.

  Reported `confidence: high` and `disposition: escalate` — the git fact is
  exact, the conclusion is the reader's. Run against this branch it found eight
  tasks; seven were real gaps in this very feature, now closed.

### Fixed

- **Guard's per-finding "review" annotation never printed.** The summary counted
  `N to fix · M to review` correctly, but the line under each finding that says
  `(review — signal, not a verdict)` was unreachable: the renderer builds its
  items from `findings` and did not copy `disposition` into them, so the check
  tested `undefined` for every structured finding — which is all of them. A
  reader scanning the enumerated list, which is where the decision about a
  specific document actually gets made, saw no distinction at all.

- **API-surface checking never ran on Python, Go, Rust, Java or Ruby projects.**
  `detectFramework` read `package.json` and nothing else, so it returned an
  empty framework for every non-JS project, and `scanRoutesDeep` gates its
  Flask, FastAPI, Django, Gin, Axum, Spring and Rails walkers on that name.
  The scanners worked; nothing reached them. A Flask service reported
  `no-matches` — "no checkable inputs matched this detector" — and its entire
  API surface went unchecked. Detection now comes from `detectEcosystems`,
  which already reads pyproject/requirements/Cargo/go.mod/pom. On one real
  repository this surfaces six genuinely undocumented endpoints.

- **The analyzer tier was invisible, so degraded scans looked like clean ones.**
  Both AST tiers are optional by design (`@babel/parser`; the developer's own
  `python3`), and the regex fallback cannot see a multi-line decorator. A Flask
  route written across several lines is read by the AST tier and missed
  entirely by the pattern tier — and both produced identical-looking output.
  A validator whose inputs fell back now reports `partial` naming the cause,
  and every finding carries the tier that produced it. Findings are retained;
  only coverage is downgraded.

- **Freshness called its own arithmetic uncertain.** FRS002–FRS005 count
  commits, days and added DRIFT lines with `git log`, and the adapter labelled
  every one of them `confidence: low` — telling readers DocGuard might have
  miscounted, which was never the claim. They are now high-confidence
  escalations: the count is a fact, the inference to staleness is the reader's.
  FRS001 stays low-confidence, because it fires on the ABSENCE of a dated
  signal, which an uncommitted file or a shallow clone can produce. Across the
  sample this relabels 196 findings.

- **Five finding codes reached SARIF with no file location.** APS001, APS002,
  DSP001, REF001 and REF002 emitted `location` as an object, which rendered as
  `[object Object]` and which SARIF's location parser dropped entirely, so
  GitHub Code Scanning could not annotate them. `location` is now normalized to
  a string at construction. Verified against the pre-change build on real
  repositories: every SARIF result now resolves to a file.

- **A malformed `suggestion.kind` was silently coerced to `review`**, turning a
  typo into an escalation. It is now omitted, per
  `adoption-workflow-integrity#FR-002`.

- **The badge claimed full coverage on a partial run.** `passed/total` counts
  checks and its denominator excludes every validator that could not run, so a
  repository could print `628/628` and brightgreen while the same run reported
  a partial validator and a missing prerequisite. The colour is now capped at
  `green` in that case — green rather than yellow, because everything that ran
  did pass. `no-matches` keeps the top grade: a validator that ran and found
  nothing applicable did its job. The coverage line leads with
  `N of M validator(s) checked`.

- **A version bump no longer flips the verdict on an unchanged tree.** Upgrading
  0.41.3 -> 0.42.0 turned `PASS 1870/1870` into `WARN` with `SPR001`
  (`.docguard-specs.json` digest projection differs) with no code and no docs
  changed. Registry currency was decided by byte-equality of the serialized
  projection, which cannot tell "your registry is stale" from "my output format
  changed", so every format change reached users as project drift. Two encodings
  were already doing it: the pre-0.42 artifact digest (taken over raw bytes
  before the `docguard:last-reviewed` stamp was excluded in #410), and
  `schemaVersion: 1` — which `readSpecRegistry` explicitly accepts as supported
  while the projection reported it stale forever, a state no content edit could
  ever clear. The currency check now consults a declared table of older
  encodings. This is not a weakening: a legacy digest is accepted only when it
  equals the legacy digest OF THE CURRENT CONTENT, so edited content matches
  neither form and still reports stale. `ACCEPTED_SCHEMA_VERSIONS` is derived
  from that table, so a version cannot be accepted by the reader without the
  currency check knowing how to treat it, and a new projection encoding without
  a declared equivalence fails the suite. The older form is reported as a note
  rather than a finding, and `docguard specs --write` still migrates it, so the
  compatibility shim cannot quietly become load-bearing.
  This is the failure mode that forces teams to pin, and pinning is what keeps
  them on stale detectors.

- **`docguard upgrade --apply` migrates the config schema even when the CLI
  upgrade fails.** The global `npm install -g` ran first and exited 1 on
  failure, before the schema block — so every user installed through npx, pnpm,
  a devDependency or Docker saw a permanent "Schema vX is behind vY" nudge that
  no command could clear, because the one command that clears it aborted first.
  The schema migration is local and offline and no longer depends on it. A
  failed CLI install is still reported and still exits 1, so CI cannot read a
  partial upgrade as a complete one.

### Changed

- **The release cut no longer regenerates `llms.txt` / `llms-full.txt`.** Adding
  that step bought a footer version stamp and the spliced release header, and
  cost two outages in one evening. The paths were not in
  `RELEASE_PATH_ALLOWLIST`, which failed the v0.42.0 cut outright before it
  opened a PR; and because a wholesale-regenerated file has no line-level common
  ancestor, any pull request merged while the release PR waits for its approval
  conflicts hard and forces the release to be recut. `CHANGELOG.md` never had
  this problem — it auto-merges — so the bundles introduced a genuinely new
  conflict class rather than another instance of an old one.
  `tests/llms-bundle-drift.test.mjs` already fails any pull request that changes
  a canonical or optional document without running `npm run llms`, so every
  section except the inlined changelog body is current on `main` at all times;
  the release job was only ever covering that one exempted body.
  The allowlist now refuses the two paths again, and the release job is asserted
  neither to run the regeneration nor to stage the files, so the outage cannot
  return by someone re-admitting them to make a failing cut pass.
  Consequence, stated plainly: a published `llms-full.txt` carries its changelog
  entries under `## [Unreleased]` rather than under the version header it ships
  with. The entries are all present — only the header is not — and the footer
  now truthfully records the version that generated the bundle instead of being
  restamped by a job that regenerated nothing else.


## [0.42.0] - 2026-09-18

Automated weekly release — batches everything merged since `v0.41.7`.

### Changed

- docs: review ENVIRONMENT.md, completing the freshness pass (#431)
- fix: let the release cut include the llms bundles it regenerates (#430)
- chore: consolidate the Unreleased changelog into one section per kind (#429)
- chore: regenerate llms.txt / llms-full.txt and keep them from rotting (#424)
- docs: freshness review of the nine docs stamped 2026-09-15 (#426)
- fix: init no longer adopts a feature folder and then refuses its own config (#427)
- test(benchmark): measure ARC001 — the architecture validator's first evidence (#428)
- feat: tell the reader whether a finding's detector has ever been benchmarked (#423)
- feat: say what the benchmark measures — provenance envelope, honest wording, CI-run recipe (#422)
- test: add missing tests for shared-git.mjs functions (#421)


### Added

- `ENVIRONMENT.md` now lists `npm run llms` under Development and the shipped
  GitLab CI component under CI/CD. Both were missing: the drift test added with
  the llms bundles names `npm run llms` in its failure message, so a contributor
  who edits a canonical document meets that command with nothing in the
  environment doc to explain it, and `templates/ci/gitlab-component.yml` ships
  beside the Actions workflow but only the latter was shown.

- **The architecture validator has benchmark evidence for the first time.** A
  new `synthetic-python-layers` pair measures ARC001 on a statically analysable
  three-layer Python package: the clean control routes every call
  `routes -> services -> models`, and the defect makes the route layer import
  the model layer directly, bypassing the declared service boundary. This is
  what the static Python import-graph support added after 0.38.0 made possible;
  before it, the only architecture case in the corpus was a dynamic import the
  analyzer cannot see. `docguard explain ARC001` now reports measured evidence
  instead of `not-measured`. Its denominator is 1, below the reporting floor of
  5, so it publishes counts and a Wilson interval (20.7%-100%) and backs off to
  the whole-corpus tier rather than quoting a rate of its own.

- **Findings now say whether they have ever been benchmarked.** `guard`
  results carry a `precisionEvidence` block scoped to the finding codes that
  run emitted, `guard`'s summary reports how many of them have measured
  precision, and `docguard explain <CODE>` prints the evidence in prose.
  DocGuard defines 104 finding codes and its reviewed corpus measures 7, so for
  most codes the honest answer is "never benchmarked" — and that is now stated
  rather than left for a reader to discover. A code never inherits the measured
  precision of other codes in its validator; a measured code with fewer than
  five labelled findings behind it quotes no point estimate on its own and may
  fall back to a named coarser tier. Contract:
  `schemas/docguard-precision-evidence.schema.json`. Findings themselves are
  unchanged, so SARIF, JUnit, baselines and feedback records are unaffected.
- `npm run generate:precision-evidence` projects the reviewed baseline into
  `cli/precision-evidence-data.mjs`, which ships with the package because
  `benchmarks/` deliberately does not. A test fails on drift.

### Changed

- **`ENVIRONMENT.md` reviewed against the code, completing the freshness pass.**
  It was the one canonical document still on an older marker, and it crossed the
  10-commit review threshold. Every claim was checked against
  `cli/commands/mcp.mjs` and holds: the HTTP MCP server binds `127.0.0.1` by
  default, refuses a non-loopback host without a key, lets `--api-key` take
  precedence over `DOCGUARD_API_KEY`, and answers `401` without a matching
  `Authorization: Bearer` or `X-API-Key` header. No correction was needed, so the
  marker bump is the review record rather than a cosmetic refresh.

- **Published benchmark aggregates moved**, because two labelled cases entered
  the corpus: 12 defect/12 control pairs across 12 repository groups became
  13/13 across 13. Precision and recall stay 1.0 with zero false positives; the
  Wilson 95% lower bound on precision rises from 75.7% to 77.2% on the larger
  denominator, and the unsupported rate falls from 4.0% to 3.7%. The
  `architecture` detector and the `py-ast` parser tier move from a null
  precision (no measured cases) to 1.0 with a 20.7% lower bound. The reviewed
  baseline and `cli/precision-evidence-data.mjs` were regenerated together.
  Note that `review.limitations` already claimed seven measured finding codes
  while `byCode` carried six; it counted ARC001, which the data reported as
  `not-measured`. The count is now accurate rather than aspirational.

- **The reviewed benchmark was re-run in full on the current release.** It had
  last run on 0.38.0 while the tool was at 0.41.7, so the evidence now quoted
  at finding time would have been four releases stale. The re-run, including
  the five pinned public repositories, compares PASS with zero regressions and
  byte-identical metrics. One case moved: the Python architecture case is now
  `checked` rather than `unsupported`, because the analyzer gained Python
  import-graph support after 0.38.0. It stays classified `unsupported_syntax`
  and excluded from every ratio pending re-adjudication.

- **Canonical docs reviewed against the code, not just re-stamped.** All nine
  documents carrying a `last-reviewed 2026-09-15` marker were read against the 14
  commits that landed since, and four carried real drift:
  - `ARCHITECTURE.md` stated the project as `~24K lines across cli/`. That figure
    was written on 2026-05-29, when `cli/` held 21.7K lines; it is 39.4K today, so
    it had survived several reviews. It now states the measurement date and the
    command to reproduce it.
  - Guard's exit code `3` (errors in a project with no `.docguard.json`) shipped
    with the hook fail-open fix and was documented nowhere. `ARCHITECTURE.md` had
    `0 | 1 | 2` in two places, and `AGENTS.md` told agents `status` "matches exit
    code 0/2/1" — which silently misleads any agent branching on `1` for FAIL.
    Both now record `3`, and `CI-RECIPES.md` explains why a repo-wide hook lets an
    unadopted working tree commit.
  - `SECURITY.md`'s command-safety table gained the foreign-hook backup boundary:
    `--force` skips a foreign hook and writes no `.bak`, so overwriting one takes
    `--force` twice, not a plain re-install.
  - `DATA-MODEL.md` now describes name-based document discovery — normalised
    filenames matched against per-role aliases, a two-role minimum before a
    directory counts as canonical, and the removal of the accidental
    macOS/Linux difference that literal-path probing produced.
  - New `FR-018` records the uninitialised-project hook boundary, which shipped
    with `tests/hook-fail-open.test.mjs` but no stated requirement.

  `TEST-SPEC.md`, `REQUIREMENTS.md`, `ROADMAP.md` and `CLAUDE.md` were verified
  accurate and carry a marker bump only. Checked and found correct: every test
  file cited across the canonical set exists; the benchmark claim of "24 measured
  cases across 12 repository groups plus one explicit unsupported case" matches
  `benchmarks/baseline.json` exactly; the validator count (29) is right. The
  "15 commands" that `verify --semantic` flags in `TEST-SPEC.md` sits in a
  historical revision row from 2026-03-13 and is deliberately left alone.

- **`llms.txt` stopped claiming DocGuard is "v0.6".** The footer quoted
  `config.version` — the `.docguard.json` *schema* version — so every generated
  `llms.txt`, in every adopting project, announced the config format version as

<!-- truncated: 3538 more lines — read CHANGELOG.md directly -->

---

## ROADMAP.md
> Planned features and development roadmap

# DocGuard Roadmap

<!-- docguard:last-reviewed 2026-09-18 -->

This file contains current product intent only. Released work belongs in
`CHANGELOG.md`; implementation history remains recoverable from Git. Completed or
superseded specifications leave the working tree through `docguard retire` so
people and AI agents do not mistake old plans for current requirements.

DocGuard's product goal is dependable, low-maintenance evidence that connects
approved intent, implementation facts, tests, and operational reality. A clean
structural score is useful, but it is not proof that arbitrary prose is true.

## Active roadmap

No unreleased milestone is currently committed. New work starts with a reviewed
specification and measurable acceptance criteria rather than an evergreen task
list that can become stale.

## Delivered roadmap

R1–R9 are implemented, evidence-reviewed, and released. The maintained living
specifications remain current verification contracts; historical implementation
plans are recoverable from Git and do not create a second source of truth.

### R9 — Adoption workflow integrity (released in v0.41.0)

Governing spec: `specs/012-adoption-workflow-integrity/spec.md`.

The installed journey now covers existing repository state, rendered
remediation, post-action verification, and the packed documentation surface.
Incomplete diff coverage, lifecycle evidence, and path resolution fail closed.
Exact finding-code policy preserves intrinsic detector severity, and structural
maturity is reported separately from the combined readiness verdict.

- [x] Merge the reviewed implementation in PR #395 at `f925e40`.
- [x] Pass the complete 1,864-test release suite, frozen precision benchmark,
  packed-package workflow, self-guard, and read-only web/Python adopter replays.
- [x] Pass the approved CI run `34989354122` and supply-chain run `34989355584`
  for release PR #396.
- [x] Merge and tag v0.41.0 at `7ec6356`.
- [x] Complete publication run `34989969217` for npm, PyPI, GHCR, GitHub
  Release, the Spec Kit extension ZIP, MCPB, and catalog sync.
- [x] Re-run the expanded 1,866-test suite while finalizing the living lifecycle
  evidence, with no failures or accepted deviations.

This milestone intentionally has no `plan.md` or `tasks.md`. Its living spec and
qualified implementation/test links are the maintained verification contract;
the release history is retained in Git and `CHANGELOG.md` without leaving a
one-time plan in active AI context.

### R1 — Document lifecycle foundation (released in v0.37.0)

Give specifications and planning documents an explicit end of life.

- [x] Ship `docguard retire --plan|--check` and explicit, fail-closed writes.
- [x] Keep archived content in Git and record only recovery metadata in
  `.docguard-archive.json`; do not copy obsolete prose into a second document tree.
- [x] Retire DocGuard's own completed specs, migration plans, and historical
  audits after their current outcomes are represented in canonical docs and the
  changelog.
- [x] Add lifecycle status validation for `active`, `completed`, `superseded`,
  and `archived`; task completion and `Completed` artifact maturity remain
  review signals rather than proof of retirement.
- [x] Add `.docguard-specs.json`, a committed lifecycle control plane. Reviewed
  approval, delivery, context, storage, persistence policy, lineage, and scope
  are authoritative; requirement references and
  implementation/test evidence are deterministic projections. Approved prose
  remains the source of behavioral intent.
- [x] Add a dedicated `docguard specs` command family with deterministic
  `--write|--check`, advisory request briefing, and generated-spec preflight.
  Generic retirement refuses active registered specs so it cannot bypass the
  lifecycle control plane.
- [x] Add a pre-specification briefing and a generated-spec gate so the actual
  draft is checked against active and prior requirements plus current code before
  planning starts. The briefing informs; only the reviewable draft can be gated.
- [x] Connect the Spec Kit extension's mandatory `before_specify` and
  `before_tasks` hooks to the same deterministic briefing and generated-spec
  gate. Keep `docguard specs --check` as the CI enforcement surface because
  hooks are agent-dispatched workflow automation.
- [x] Give every active spec an immutable metadata ID; use
  `specId#requirementId` for completion evidence and preserve retired identities
  as registry tombstones so bare IDs cannot rebind.
- [x] Cross-check spec storage state against `.docguard-archive.json`. The
  archive manifest owns document recovery; the spec registry owns governance,
  and disagreement between them blocks a transition.
- [x] Merge the reviewed implementation in PR #349 and publish the verified
  npm, PyPI, GHCR, MCPB, and Spec Kit extension artifacts as v0.37.0.

R1 deliberately shipped the registry and safe-retirement boundary before adding
completion writes. Transaction rollback, status adapters, monorepo identity,
restore/re-retire handling, and reviewed completion transitions followed in R2
and shipped in v0.38.0; they are outside the v0.37.0 contract.

### R2 — Completion and post-hoc reconciliation (released in v0.38.0)

Close the lifecycle loop without allowing current code to silently redefine
approved intent.

- [x] Add staged registry/recovery transactions with rollback before any command
  can update both lifecycle ledgers.
- [x] Add an `implemented → verified` completion transaction that appends a
  bounded outcome record, refreshes mechanical facts, records the exact
  reconciliation revision, and regenerates active AI context.
- [x] Add optional Spec Kit hooks that check archive readiness after convergence
CLI and CI remain the enforcement boundary for lifecycle verification.

Post-hoc implementation changes are classified without silently redefining
approved intent. `docguard reconcile --since <ref>` reports affected material:

1. mechanical code facts that `sync` can safely refresh;
2. approved requirements that may indicate a code regression;
3. superseded decisions that need a replacement or archive action;
4. unsupported or ambiguous evidence that needs human review.

The command produces a review plan before any write. It never rewrites a
requirement merely because the current code differs. Acceptance requires seeded
examples for intentional behavior changes, accidental regressions, and unrelated
edits; each class must remain distinguishable in JSON output.

Spec Kit already publishes persistence models and supports lifecycle hooks, while
community Archive and Reconcile extensions perform agent-authored artifact
updates. DocGuard will validate and index those outcomes rather than duplicate
their prompt workflows. A future upstream contribution should standardize only
the generic lifecycle metadata or hook contract after interoperability is proven.

Delivered slices include explicit changed-file-to-spec evidence edges,
replacement-spec fields in bounded outcomes, decision-record classification,
transaction rollback fixtures, and `after_implement`/`after_converge` evidence
gates. Broader symbol inference remains intentionally unsupported until R3 can
measure its false-positive cost.

### R3 — Independent precision benchmark (released in v0.39.0; living baseline)

Governing spec: `specs/007-precision-evidence-loop/spec.md`.

The reproducible corpus now covers JavaScript, TypeScript, Python,
fallback-language, monorepo, generated-code, and sparse-doc shapes. Five pinned
public projects supplement repository-owned fixtures. Labels and exact mutations
are fixed before output review, and the evaluation split is isolated by
repository and causal family.

The reviewed baseline reports case-level and grouped TP/FP/FN, precision, recall,
false positives per repository, abstention, unsupported coverage, cold/warm
runtime, repair outcomes, and Wilson confidence limits. Comparisons fail on a
new miss, false positive, removed case, or supported-case abstention even when
the total warning count falls.

Maintained artifacts: `benchmarks/corpus.json`, `benchmarks/baseline.json`, the
runner, comparison, and baseline-envelope libraries,
`schemas/docguard-benchmark.schema.json`, and
`schemas/docguard-benchmark-baseline.schema.json`. The baseline states what its
ratios measure (`benchmark-precision`) and carries a derived caveat; it is not,
and will not become, a calibration document — a balanced corpus has no base
rate to calibrate against. Per-code evidence is projected from that baseline
into a shipped module and quoted beside findings, where the dominant and honest
answer is that a code has never been benchmarked.

### R4 — Contribution-to-regression loop (released in v0.39.0; open to contributions)

Governing spec: `specs/007-precision-evidence-loop/spec.md`.

`feedback` accepts redaction-attested synthetic fixture manifests with detector,
configuration, expected identity, parser tier, explicit predicate, and opposite
control. False positives, false negatives, unsupported syntax, ambiguity, and
policy disagreements retain distinct classifications.

Public payloads remain opt-in and use reviewed synthetic content. Preview exposes
a deterministic duplicate identity and open/closed searches without submission.
The reducer preserves explicit interestingness, and test-only generation enforces
reproduction, neighboring control, scope, redaction, and benchmark-delta evidence.

Maintained artifacts: `templates/feedback-fixture.json`,
`schemas/docguard-feedback-fixture.schema.json`, and the generated direct
`tests/*.test.mjs` contribution path.

### R5 — Evidence-scoped verification (released in v0.40.0)

Governing spec: `specs/008-evidence-scoped-verification/spec.md`.

Replace broad age-based review prompts with declared source-to-document
dependencies where available. Start with bounded claim types such as named JSON
values, enum sets, and counts tied to documented collections. Results remain one
of verified-within-scope, contradicted, unsupported, inconclusive, or stale.

Contribution slices: dependency declarations, exact claim predicates, saved
oasdiff/Buf evidence adapters, and review invalidation fixtures. Upstream tools
retain ownership of their domain semantics; DocGuard links results to affected
prose, examples, requirements, and migration guidance.

The implementation exposes the same scoped result through CLI, guard, score,
agent context, SARIF/JUnit findings, and MCP. Verification passed 1,733 tests on
Node 18, 20, 22, and 24, package extraction without the optional parser, Draft
2020-12 schema validation, and the frozen 24-case public/synthetic corpus with
no baseline regression.

### R6 — Language and repository coverage (released in v0.40.0)

Governing spec: `specs/009-language-repository-coverage/spec.md`.

Add capabilities only with explicit applicability and controls. Priorities are
Python import relationships, additional Worker binding forms, custom document
role writers with section ownership, and repository-root guidance for monorepos.
Unsupported extraction must remain visible and must not become a success claim.

Contribution slices: one parser or framework per pull request, paired supported
and unsupported fixtures, and benchmark deltas for any performance-sensitive
scanner change.

The implementation now covers Python static import graphs, current Cloudflare
binding forms, ownership-safe mapped document writers, and advisory npm/pnpm
workspace-root discovery. Verification passed 1,765 tests on Node 18, 20, 22,
and 24, package extraction without the optional parser, schema and source syntax
checks, and the frozen corpus with 24 evaluable cases passing plus one expected
unsupported dynamic-Python case. Self-guard has no errors; its three DSP001
warnings are low-confidence review prompts for canonical documents already
updated in the same change set.

### R7 — Task-specific agent context (released in v0.40.0)

Governing spec: `specs/010-task-specific-agent-context/spec.md`.

Evaluate targeted evidence packets against ordinary repository context and the
existing DocGuard context pack. Freeze repository snapshots, model/harness
versions, prompts, and budgets; measure hidden-test success, requirement
violations, unnecessary edits, tokens, latency, and human intervention.

Ship only if repeated trials improve task outcomes or reduce cost within a
predeclared non-inferiority margin. An LLM judge or DocGuard score alone is not
sufficient evidence.

Protocol v1 freezes three synthetic tasks, three conditions, three repetitions,
the model and harness identity, hidden regression tests, safety checks, metrics,
and promotion threshold before any trial results. The experimental selector
must abstain on weak evidence and remains outside the public CLI until all gates
pass.

All 27 frozen trials passed their hidden requirements, visible regressions, and
changed-file policy. Against the existing context pack, targeted packets reduced
median tool steps from 10 to 5 and median latency by 17%, while increasing
median uncached input by 80%. The result clears the predeclared gate through
steps and latency, supports an explicit opt-in interface, and does not support a
general token-cost claim. The retained result and limitations live under
`benchmarks/agent-context/results/`.

The promoted CLI, selector, schemas, docs, and evaluator pass 1,784 tests on
Node 18, 20, 22, and 24. Packed-package tests run task context without the
optional parser, and the independent detector corpus remains regression-free
across 24 evaluable cases plus one explicit unsupported case.

### R8 — Tokenless scheduled releases (released in v0.40.3)

Governing spec: `specs/011-tokenless-scheduled-releases/spec.md`.

Replace the long-lived release PR credential with an ephemeral repository token
and one explicit maintainer approval for the generated PR's workflows. The
implementation validates the generated candidate before push, arms protected
native auto-merge, starts publication from the merged version push, and recovers
a missing tag before another version increment.

- [x] Freeze the security and recovery contract before implementation.
- [x] Add pure release-candidate and exact-run policy tests.
- [x] Publish without a stored personal or app credential while preserving the
  maintainer workflow approval required by protected pull-request checks.
- [x] Preserve Dependabot/Jules policy and pinned-action controls.
- [x] Record the reviewed lifecycle outcome at durable revision `46531e4` with
  no accepted deviations.
- [x] Publish v0.40.1 through release PR #380 and verify npm, PyPI, GHCR, GitHub
  release assets, and the Spec Kit catalog reminder.
- [x] Verify the corrected repository-token native auto-merge continuation on
  v0.40.3 and retain exact evidence.

The retained live probe used CI run `34912654565` and privileged gate run
`34912788971`. All Node 18, 20, 22, and 24 jobs passed. The trusted gate
identified pull request #372 as a non-release candidate and refused to merge it;
the temporary pull request and branch were then removed. The complete reviewed
evidence landed through pull requests #371, #373, and #374.

Release PR #380 passed CI run `34920481989` and supply-chain run `34920482409`,
merged at `5b2dfe4`, and published v0.40.1 in run `34920702346`. That run also
proved the final GitHub boundary: approving the held workflow does not emit a
second `workflow_run` completion. Release PR #383 proved the repository token
can arm native auto-merge and GitHub can merge after approved checks, while its
bot-originated push proved publication needs an explicit continuation.

Scheduled run `34922506777` created repository-token release PR #386 and armed
native auto-merge. After one maintainer approval, CI run `34922605581` and
supply-chain run `34922605917` passed; GitHub merged the exact head at
`e27d6bf0203708ee8206a1434eb292520f4c4494`. The bounded scheduler wait then
dispatched publication run `34922784629`, which passed the four-runtime matrix,
self-guard, npm, PyPI, GHCR, GitHub Release, extension ZIP, MCPB, and catalog
reminder jobs. The published npm and PyPI versions and GitHub tag all resolve to
v0.40.3, completing R8 without a stored release credential.

## Contribution standard

Before opening work, search existing open and closed issues and pull requests.
Each proposal should name the failure mode, include a minimal reproduction and a
valid control, state supported and unsupported scope, and define the acceptance
test. See `CONTRIBUTING.md` for repository mechanics.

## Deferred ideas

A hosted dashboard, leaderboards, and notification integrations remain deferred
until user research shows that the CLI, CI outputs, and existing observability
systems cannot meet a concrete team need. They are not active commitments.


---

## AGENTS.md
> AI agent behavior rules and workflow instructions

# AI Agent Instructions — DocGuard

<!-- docguard:last-reviewed 2026-09-18 -->

> This project follows **Canonical-Driven Development (CDD)**.
> Documentation is the source of truth. Read before coding.
> DocGuard is an official [GitHub Spec Kit](https://github.com/github/spec-kit) community extension.

## Workflow

1. **Read** `docs-canonical/` before suggesting changes
2. **Check** existing patterns in the codebase
3. **Run** `docguard diagnose` to see what needs fixing
4. **Confirm** your approach before writing code
5. **Implement** matching existing code style
6. **Log** any deviations in `DRIFT-LOG.md` with `// DRIFT: reason`
7. **Verify** with `docguard guard` — all checks must pass

## Project Stack

- **Language**: JavaScript (ES modules)
- **Runtime**: Node.js 18+
- **Dependencies**: One — `@babel/parser` (exact-pinned, optional-load); Node.js built-ins otherwise
- **Testing**: `node:test` (built-in)
- **Distribution**: npm + PyPI
- **Version**: see `package.json` (single source of truth — do not hardcode here)

## Key Files

| File | Purpose |
|------|---------|
| `docs-canonical/ARCHITECTURE.md` | System design |
| `docs-canonical/DATA-MODEL.md` | Database schemas |
| `docs-canonical/SECURITY.md` | Auth & secrets |
| `docs-canonical/TEST-SPEC.md` | Test requirements |
| `docs-canonical/ENVIRONMENT.md` | Environment setup |
| `docs-canonical/REQUIREMENTS.md` | Spec-kit aligned requirements |
| `CHANGELOG.md` | Change tracking |
| `DRIFT-LOG.md` | Documented deviations |

## Commands

`docguard --help` is the authoritative list (counts intentionally not hardcoded
here — they drift). The surface, grouped as `--help` shows it:

**The Daily 5** — `init` (bootstrap + scan), `guard` (CI gate, all validators),
`diff` (doc↔code gaps; `--since <ref>` for changed-file impact), `sync` (refresh
code-truth sections), `score` (CDD maturity 0-100).

**Tools** — `demo` (zero-install tour), `diagnose` (guard → AI fix prompts),
`fix` (AI fix instructions; `--doc <name>`), `generate` (reverse-engineer docs;
`--plan`), `agent` (task graph; `--task <text>` for bounded current evidence),
`retire` (remove reviewed docs from active context),
`specs` (check/refresh/preflight/complete the spec lifecycle registry),
`reconcile` (classify code/spec changes since a Git ref without rewriting intent),
`explain` (explain a validator/warning), `memory` (what DocGuard
remembers), `trace` (requirements traceability; `--reverse`), `upgrade` (migrate
config/CLI), `watch` (live re-guard).

**`init --with <name>`** scaffolders — `agents`, `hooks`, `ci`, `badge`, `llms`,
`publish` (also reachable as standalone deprecation aliases).

**Deprecation aliases** — `setup` → `init --wizard`; `audit` → `guard`
(permanent); `impact` → `diff --since`.

## Consuming Guard Output (agents)

Prefer the machine contract over parsing prose: `docguard guard --format json`
returns `status` (PASS/WARN/FAIL, matches exit code 0/2/1 — but FAIL exits `3`,
not `1`, when the project has no `.docguard.json`, so branch on `status` rather
than assuming `1`), `findings[]`
(`{code, severity, confidence, disposition, evidence, parserTier, message, location, suggestion}`),
`nextStep`, `reportable[]` (findings whose code is unmeasured or whose
confidence is low — verify before acting), `coverage`
(Markdown tier map incl. `unclassified[]`), `evidence`, and `semanticClaims.count`
(documented numbers not yet verified against code).

When a command is launched from an implicitly selected nested package, inspect
stderr for repository-root guidance. Human output supplies an exact rerun;
machine modes emit a `docguard.repository-root-guidance` JSON diagnostic there
so stdout remains a valid JSON/SARIF/JUnit artifact. The current command still
checks only the selected package unless it is explicitly rerun with `--dir`.

- Every structured finding has a stable code (`STR001`, `ENV003`, `XRF002`, …).
  `docguard explain <CODE>` gives the contract and fix.
- Triage on `disposition`, not on severity alone. `act` means DocGuard asserts a
  defect and names the correction — apply it. `escalate` means DocGuard observed
  a signal and the judgement is yours; read the source before changing anything.
  A high-confidence finding can still be an escalation: FRS002 counts commits
  exactly and still does not establish that the document is stale.
- Read `evidence.status` before trusting a confidence label. `measured` quotes
  the reviewed corpus with `n` and a Wilson interval; `not-measured` means the
  label is a maintainer's prior that has never been checked against labelled
  cases. Most codes are unmeasured — that does not make their findings wrong,
  only unverified.
- Read `parserTier` when a finding concerns source code. `regex-fallback` or
  `fallback-language` means an AST was unavailable for that file, so absence of
  a finding is weak evidence; the owning validator also reports `partial`.
- When `evidence.configured` is true, inspect `docguard verify --evidence
  --format json` first. A `verified-within-scope` result covers only its selected
  statement. Contradictions can indicate code regressions from approved intent;
  stale reports must be regenerated from their declared inputs.
- Mechanical fixes go through `docguard fix --write` (provenance-checked,
  fail-closed) — never hand-apply what the tool fixes deterministically.
- Genuine false positives: suppress at the site with `// docguard:ignore <CODE>`
  (reason required) or `<!-- docguard:validator <key> n/a — reason -->`, and
  report them via `docguard feedback`.
- Doc≠code does not mean the doc is wrong — canonical docs are the spec. If the
  code regressed from a documented decision, fix the code or log a
  `// DRIFT: reason` + DRIFT-LOG.md entry instead of rewriting the doc.
- Treat `specs/` and planning docs as active intent only. Review candidates with
  `docguard retire --plan`; retire only explicit, clean tracked documents after
  their shipped outcomes are represented in current docs and `CHANGELOG.md`.
- Run `docguard specs preflight` before drafting and `docguard specs preflight
  --path <spec>` before planning. Refresh `.docguard-specs.json` with `specs
  --write`; never edit generated intent/evidence fields or treat bare requirement
  IDs as completion proof.
- Run `docguard reconcile --since <ref> --format json` before completing a
  changed feature. Only its mechanical write plan may run automatically;
  unsupported changes and possible regressions require review. `docguard specs
  complete` records that review and regenerates the active context projection.
- For a concrete implementation task, prefer `docguard agent --task <text>
  --format json`. Follow only selected current evidence, retain its
  retrieval-only assurance, inspect additional repository facts as needed, and
  fall back to normal discovery when `selection.status` is `abstained`.

## AI Skills

DocGuard provides enterprise-grade AI behavior protocols via the Spec Kit extension:

| Skill | Purpose |
|-------|---------|
| `docguard-guard` | 6-step quality gate with severity triage and structured reporting |
| `docguard-fix` | 7-step research workflow with validation loops (max 3 iterations) |
| `docguard-review` | Read-only semantic cross-document consistency analysis |
| `docguard-score` | CDD maturity assessment with ROI-based improvement roadmap |
| `docguard-sync` | Mechanical code-truth refresh with explicit prose review boundaries |

Skills are located at `extensions/spec-kit-docguard/skills/*/SKILL.md`. They tell agents **how to think**, not just what to run.

## Spec Kit Hooks

DocGuard integrates into the spec-kit workflow:

| Hook | When | Required? |
|------|------|-----------|
| `after_implement` | After `/speckit.implement` | Mandatory |
| `before_tasks` | Before `/speckit.tasks` | Optional |
| `after_tasks` | After `/speckit.tasks` | Optional |

## Extension Structure

```
extensions/spec-kit-docguard/
├── skills/                    # AI behavior protocols
│   ├── docguard-guard/SKILL.md
│   ├── docguard-fix/SKILL.md
│   ├── docguard-review/SKILL.md
│   └── docguard-score/SKILL.md
├── scripts/bash/              # Orchestration scripts (--json output)
├── commands/                  # Spec Kit slash commands
├── templates/                 # Hook registration templates
└── extension.yml              # Skills, scripts, hooks declaration
```

## Rules

- **PR-first workflow — no direct-to-main commits.** Create a branch (`git checkout -b <type>/<slug>`), push, `gh pr create`, let CI run, self-review, squash-merge. Tag releases only after merge on `main`. The only acceptable direct-to-main: typo fixes in comments or README badge URLs.
- Never commit without updating CHANGELOG.md
- If code deviates from docs, add `// DRIFT: reason`
- Security rules in SECURITY.md are mandatory
- Test requirements in TEST-SPEC.md must be met
- Run `docguard guard` before pushing — all checks must pass
- Standalone file writes use `safeWrite()` with backups before overwrite.
  Lifecycle operations spanning multiple files use `commitFileTransaction()` so
  preparation, rollback, and post-write validation cover the complete set.


## Agent Rules

### Automated agents / bots (Jules "Sentinel", "Bolt", "Palette", and any auto-PR agent)
- **Never open a duplicate PR.** Before opening ANY PR, search existing **open
  AND closed** PRs and issues for the same topic/title. If it exists, STOP — do
  not open another. (Dozens of duplicate command-injection and diff-optimization
  PRs were closed as noise.)
- **Do not re-open resolved work.** See `.jules/sentinel.md` (execSync/command
  injection — RESOLVED in v0.21.1 + #296) and `.jules/bolt.md` (diff/scan
  micro-optimizations — already applied; code refactored since). These are
  historical learnings, **not** standing mandates to re-scan every run.
- **Bar for a new PR:** a genuinely new, unaddressed finding, with evidence — a
  concrete exploit path / failing test (security) or a benchmark showing >20%
  real-workload improvement (performance). A Big-O note alone is insufficient.
- This repo has **no web UI and no VS Code extension** — skip all UX tasks.

### Dependencies
- Never add a package without first verifying it exists on the official registry (npm/PyPI).
- Always pin to exact versions in `package.json` and `requirements.txt`. No ^, ~, or >= ranges.
- Prefer packages with >10k weekly downloads and >1 maintainer.
- If you suggest a package, confirm its first-publish date is older than 30 days.
- Never modify .npmrc, pnpm-workspace.yaml, or dependabot.yml without explicit user confirmation.

### CI/CD
- Never write a workflow using `pull_request_target` with checkout of PR-controlled refs.
- Always pin third-party GitHub Actions to commit SHA, not @v1 or @main.

## Claim probing (TestGuard)

`testguard.claims.json` states what this codebase guarantees, as claims with
mechanical faults. `testguard probe` injects each fault and reports the ones the
suite fails to detect — a test that pins a *defect* stays green under coverage,
so this is the check that catches it.

This suite runs on `node --test`, which ships no `json` reporter, while TestGuard
reads Jest-shape JSON. `tools/node-test-json-reporter.mjs` translates between
them:

```bash
npx testguard-cli probe --confirm 3 --serial \
  --node-modules "$PWD/node_modules" \
  --runner-cmd "node --test --test-reporter=$PWD/tools/node-test-json-reporter.mjs --test-reporter-destination={out} {files}"
```

Pass an absolute reporter path: the probe runs in a scratch worktree built from
a commit, so a relative path resolves there and untracked files do not exist.

`testguard gate --changed origin/main` reports changed source files that carry no
claim. It does not find bugs; it refuses to let a change land without stating
what must be true, which is the step that surfaces them.

## Evidence and contributions

A structural score is a maturity proxy. Preserve `assurance` and nullable factual accuracy in automation; a clean guard does not establish arbitrary prose correctness. Review human intent separately from generated code facts. To challenge any finding, run `docguard feedback --code <CODE> --preview`, inspect the metadata-only public draft, and check the supplied search link for existing open and closed work. Contribute a synthetic failing example paired with a neighboring valid case. Submission remains opt-in.


---
Generated by DocGuard v0.42.0 | [docguard-cli](https://www.npmjs.com/package/docguard-cli)
