docguard-cli 0.39.0 → 0.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/README.md +43 -20
  2. package/cli/commands/agent.mjs +47 -1
  3. package/cli/commands/explain.mjs +16 -0
  4. package/cli/commands/fix.mjs +13 -11
  5. package/cli/commands/generate.mjs +52 -18
  6. package/cli/commands/guard.mjs +13 -2
  7. package/cli/commands/mcp.mjs +22 -2
  8. package/cli/commands/score.mjs +13 -1
  9. package/cli/commands/sync.mjs +20 -7
  10. package/cli/commands/verify.mjs +65 -2
  11. package/cli/config.mjs +3 -0
  12. package/cli/docguard.mjs +33 -12
  13. package/cli/evidence/adapters.mjs +200 -0
  14. package/cli/evidence/evaluate.mjs +185 -0
  15. package/cli/evidence/manifest.mjs +194 -0
  16. package/cli/evidence/markdown.mjs +107 -0
  17. package/cli/findings.mjs +31 -0
  18. package/cli/repository-root.mjs +159 -0
  19. package/cli/scanners/py-ast.mjs +39 -2
  20. package/cli/scanners/task-context.mjs +312 -0
  21. package/cli/shared-doc-roles.mjs +44 -1
  22. package/cli/shared-source.mjs +101 -28
  23. package/cli/validators/architecture.mjs +186 -13
  24. package/cli/validators/environment.mjs +14 -1
  25. package/cli/validators/evidence.mjs +52 -0
  26. package/cli/validators/todo-tracking.mjs +45 -2
  27. package/cli/writers/doc-generators.mjs +31 -17
  28. package/cli/writers/mechanical.mjs +44 -14
  29. package/cli/writers/sections.mjs +31 -3
  30. package/docs/ai-integration.md +31 -6
  31. package/docs/commands.md +43 -5
  32. package/docs/configuration.md +11 -3
  33. package/docs/quickstart.md +1 -1
  34. package/extensions/spec-kit-docguard/commands/fix.md +4 -2
  35. package/extensions/spec-kit-docguard/commands/generate.md +6 -1
  36. package/extensions/spec-kit-docguard/commands/guard.md +3 -2
  37. package/extensions/spec-kit-docguard/commands/sync.md +1 -1
  38. package/extensions/spec-kit-docguard/extension.yml +1 -1
  39. package/extensions/spec-kit-docguard/skills/docguard-fix/SKILL.md +14 -3
  40. package/extensions/spec-kit-docguard/skills/docguard-guard/SKILL.md +16 -5
  41. package/extensions/spec-kit-docguard/skills/docguard-review/SKILL.md +8 -3
  42. package/extensions/spec-kit-docguard/skills/docguard-score/SKILL.md +3 -2
  43. package/extensions/spec-kit-docguard/skills/docguard-sync/SKILL.md +6 -3
  44. package/extensions/spec-kit-docguard/templates/github-workflows/docguard-autofix.yml +2 -2
  45. package/extensions/spec-kit-docguard/templates/github-workflows/docguard-guard.yml +4 -4
  46. package/package.json +2 -1
  47. package/schemas/docguard-agent-context-benchmark.schema.json +92 -0
  48. package/schemas/docguard-agent-context-result.schema.json +95 -0
  49. package/schemas/docguard-config.schema.json +1 -0
  50. package/schemas/docguard-evidence.schema.json +169 -0
  51. package/schemas/docguard-task-context.schema.json +144 -0
  52. package/templates/AGENTS.md.template +9 -4
  53. package/templates/ci/github-actions.yml +4 -4
  54. package/templates/commands/docguard.guard.md +5 -1
  55. package/templates/commands/docguard.review.md +6 -1
  56. package/templates/evidence-manifest.json +21 -0
package/README.md CHANGED
@@ -77,7 +77,7 @@ graph TD
77
77
  Commands --> setup["setup wizard"]
78
78
  Commands --> other["diff · init · fix · trace · impact · sync · reconcile · retire · specs<br/>explain · memory · upgrade · agents · hooks · badge · ci · watch"]
79
79
 
80
- guard --> Validators["Validators (29)"]
80
+ guard --> Validators["Validators (30)"]
81
81
  generate --> Scanners["Scanners (4)<br/>routes · schemas · doc-tools · speckit"]
82
82
  score --> Scoring["Weighted Scoring<br/>8 categories"]
83
83
  diagnose --> Validators
@@ -104,7 +104,7 @@ graph TD
104
104
 
105
105
  DocGuard checks declared documentation facts against repository evidence and gives agents structured repair tasks. Deterministic checks cover supported facts, references, and generated sections. Human-authored requirements and architectural decisions retain their authority when implementation diverges.
106
106
 
107
- A guard result describes the checks performed. The CDD grade measures structural maturity. Factual accuracy stays explicitly unverified until the relevant claims have supporting evidence. Coverage and unresolved claims remain visible, so teams can choose an appropriate enforcement policy.
107
+ A guard result describes the checks performed. The CDD grade measures structural maturity. Exact declarations in `.docguard-evidence.json` can verify selected statements against current local evidence; every other statement remains unverified. Coverage and unresolved claims remain visible, so teams can choose an appropriate enforcement policy.
108
108
 
109
109
  Research motivates evaluation of this approach. A 2026 study found that repository context files did not generally improve task success and increased inference cost in its evaluated settings. It also found agents generally followed the instructions. These results support testing concise, relevant context and measuring actual task outcomes; they do not establish DocGuard's effectiveness. [Evaluating AGENTS.md, revised June 2026](https://arxiv.org/abs/2602.11988v2).
110
110
 
@@ -275,7 +275,7 @@ DocGuard ships **23 commands** (the "Daily 5" + 18 situational tools, including
275
275
  | Command | What It Does |
276
276
  |:--------|:-------------|
277
277
  | `init` | Bootstrap a project (`--wizard` for interactive · `--with <name>` for scaffolders) |
278
- | `guard` | Validate against canonical docs — 29 validators |
278
+ | `guard` | Validate against canonical docs — 30 validators |
279
279
  | `diff` | Show gaps between docs and code (`--since <ref>` for impact mode) |
280
280
  | `sync` | Refresh code-truth doc sections — keeps memory always up to date |
281
281
  | `score` | CDD maturity score (0-100; `--diff` for delta between refs) |
@@ -290,8 +290,9 @@ DocGuard ships **23 commands** (the "Daily 5" + 18 situational tools, including
290
290
  | `fix --write` | Apply deterministic fixes (no AI — version bumps, counts, anchors, sections) |
291
291
  | `fix --history` | Audit log of every mechanical fix applied (from `.docguard/fixed.json`) |
292
292
  | `generate` | Reverse-engineer docs from existing codebase (`--plan` for AI scan) — includes auto-generated Mermaid ER diagrams from your detected schemas (Prisma/Drizzle/TypeORM/Sequelize/Django/Rails) in DATA-MODEL.md |
293
- | `agent` | One-shot agent task graph ordered, pre-filled code-truth, per-task verify (`--format json`) |
293
+ | `agent` | One-shot agent task graph, or a bounded current-evidence packet for one task (`--task <text>`, `--format json`) |
294
294
  | `explain <warning\|CODE>` | Paste any warning — or a finding code like `SEC001` — to get the validator's docstring, fix path, and how to suppress |
295
+ | `verify --evidence` | Evaluate strict statement-to-source declarations for typed JSON values, bounded collection counts, saved oasdiff JSON, and saved Buf JSON Lines. Results distinguish scoped verification, contradiction, stale inputs, inconclusive evidence, and unsupported formats. |
295
296
  | `verify --semantic` | Extract documented numbers/limits/enums (retention days, rate limits, GSI/role counts, status enums) as a task list for an agent to check against code — the semantic-drift class regex/AST can't see |
296
297
  | `verify --instructions` | Audit AGENTS.md/CLAUDE.md themselves for drift: duplicate rules, never-vs-always contradictions, stale file pointers, unknown commands — plus clustered rule pairs as agent judgment tasks |
297
298
  | `feedback` | Review any finding or a synthetic false-positive/false-negative/unsupported fixture; verify its opposite control, reduce it deterministically, search open and closed duplicates, and optionally emit a test-only contribution. Nothing is submitted automatically. |
@@ -326,16 +327,23 @@ DocGuard ships **23 commands** (the "Daily 5" + 18 situational tools, including
326
327
 
327
328
  Run them solo (`docguard init --with hooks`) or stacked (`docguard init --with agents,hooks,badge,ci`).
328
329
 
330
+ To declare an exact fact, copy `templates/evidence-manifest.json` to
331
+ `.docguard-evidence.json`, point its literal Markdown template at one unique
332
+ statement, and bind that value to a supported local source. Run
333
+ `docguard verify --evidence --format json` before enabling the guard in CI.
334
+ External compatibility declarations consume saved oasdiff or Buf output and
335
+ require current SHA-256 identities for every declared repository input.
336
+
329
337
  **Deprecation aliases** — `setup` · `agents` · `hooks` · `badge` · `llms` · `publish` · `impact` remain compatible until v1.0 with a yellow stderr warning. `audit → guard` is permanent and silent; `ci` is a current first-class pipeline command.
330
338
 
331
339
  ### CLI Flags
332
340
 
333
341
  | Flag | Description | Commands |
334
342
  |:-----|:------------|:---------|
335
- | `--dir <path>` | Project directory (default: `.`) | All |
343
+ | `--dir <path>` | Project directory (default: `.`); explicit selection suppresses ancestor-root guidance | All |
336
344
  | `--verbose` | Show detailed output | All |
337
345
  | `--quiet` / `-q` | Suppress banner — for hooks, CI loops, scripts | All |
338
- | `--format json` | Machine-readable output (clean JSON, no ANSI bleed) | guard, score, diff, trace, diagnose, memory, impact, explain, reconcile, retire, specs |
346
+ | `--format json` | Machine-readable output (clean JSON, no ANSI bleed) | guard, score, diff, trace, diagnose, memory, impact, explain, verify, reconcile, retire, specs |
339
347
  | `--format sarif` | SARIF 2.1.0 output — findings as rules/results for GitHub Code Scanning and SARIF dashboards | guard |
340
348
  | `--format junit` | JUnit XML output — one testcase per validator, for GitLab CI (`artifacts:reports:junit`), Jenkins, Azure DevOps, CircleCI | guard |
341
349
  | `--update-baseline` | Adopt DocGuard on a legacy repo without a red day one: freeze today's findings into a committed `.docguard.baseline.json`; guard/ci then gate only NEW drift. Suppression is always visible ("N pre-existing finding(s) suppressed"), and `--no-baseline` shows the full picture | guard |
@@ -347,7 +355,7 @@ Run them solo (`docguard init --with hooks`) or stacked (`docguard init --with a
347
355
  | `--force-redo` | Bypass ping-pong suppression in `.docguard/fixed.json` | fix --write |
348
356
  | `--profile <name>` | Starter / standard / enterprise | init |
349
357
  | `--no-spec-kit` | Skip auto-init of `.specify/` / `.agent/` scaffolding | init |
350
- | `--changed-only [--since <ref>]` | Pre-commit lite mode (5 fast validators on changed files only) | guard |
358
+ | `--changed-only [--since <ref>]` | Pre-commit lite mode (6 fast validators on changed files only) | guard |
351
359
  | `--timings` | Per-validator wall-time profile (slowest first) | guard |
352
360
  | `--show-failing` | Show warnings/errors even when status is PASS | guard |
353
361
  | `--pin` | Record running CLI version into `.docguard.json` (reproducibility) | guard |
@@ -361,6 +369,15 @@ Run them solo (`docguard init --with hooks`) or stacked (`docguard init --with a
361
369
  | `--transport http` `--port` `--host` `--api-key` `--path` | Serve MCP over Streamable HTTP instead of stdio (team-shared server; loopback-only unless an api-key is set) | mcp |
362
370
  | `--history` | Show fix audit log | fix |
363
371
 
372
+ When run from a nested package without `--dir`, DocGuard checks only that
373
+ selected directory. If a bounded ancestor scan finds a `.docguard.json` or an
374
+ npm/pnpm workspace declaration that owns the package, stderr shows an exact
375
+ repository-scope rerun command. DocGuard never changes scope automatically. JSON,
376
+ SARIF, and JUnit stdout remain valid; machine runs receive one typed
377
+ `docguard.repository-root-guidance` JSON diagnostic on stderr. A local config,
378
+ an explicit `--dir`, an unmatched workspace, or a nested Git boundary suppresses
379
+ the suggestion.
380
+
364
381
  ### Example Output
365
382
 
366
383
  ```
@@ -394,7 +411,7 @@ $ npx docguard-cli generate
394
411
 
395
412
  ## 🔍 Validators
396
413
 
397
- DocGuard runs **29 automated validators** on every `guard` check. Source-facing validators are language-aware where their evidence model applies; repository and document validators operate independently of source language.
414
+ DocGuard runs **30 automated validators** on every `guard` check. Source-facing validators are language-aware where their evidence model applies; repository and document validators operate independently of source language.
398
415
 
399
416
  | # | Validator | What It Checks | Default |
400
417
  |:--|:----------|:--------------|:--------|
@@ -419,14 +436,15 @@ DocGuard runs **29 automated validators** on every `guard` check. Source-facing
419
436
  | 19 | **Spec-Kit** | Spec quality validation (FR-IDs, mandatory sections, phased tasks) | ✅ On |
420
437
  | 20 | **Document-Lifecycle** | Exact terminal states, advisory completion signals, incomplete coverage, and manifest/working-tree inconsistencies | ✅ On |
421
438
  | 21 | **Spec-Registry** | Immutable spec identities, byte-stable evidence projection, reviewed lifecycle preservation, and archive/storage consistency | ✅ On |
422
- | 22 | **Cross-Reference** | Internal markdown links + anchors resolve (with "did you mean?" hints); Obsidian wikilinks validated when the repo uses them as file links (`.obsidian` present or a target resolves) | ✅ On |
423
- | 23 | **Generated-Staleness** | `source=code` sections match scanner output; `status: draft` doc age | ✅ On |
424
- | 24 | **Canonical-Sync** | DocGuard's own README count claims match code-truth (DocGuard repo only N/A elsewhere) | ✅ On |
425
- | 25 | **Metrics-Consistency** | Hardcoded numbers match actual counts | ✅ On |
426
- | 26 | **Surface-Sync** | Item-level enumerable drift — names in doc tables/lists (commands, checks, etc.) match code-truth (opt-in via `surfaceSync.surfaces`; N/A unless configured) | ✅ On |
427
- | 27 | **Diff-Suspicion** | Change-driven: a doc/agent-instruction file that references code changed since the ref AND shares removed domain symbols is flagged for review (arXiv 2010.01625, F1 74.7) | ✅ On |
428
- | 28 | **Reference-Existence** | Two-revision check: a backticked code symbol present when the doc was last updated but gone at HEAD is flagged as outdated (arXiv 2212.01479) | ✅ On |
429
- | 29 | **API-Doc-Smells** | Bloated (≥300 words) / Lazy (≤6 prose words) API documentation units, keyed on signature-headed sections (F1 0.90/0.95) | ✅ On |
439
+ | 22 | **Evidence** | Exact declared Markdown statements match current typed JSON, bounded collections, or saved compatibility reports; unsupported and missing evidence stays visible | ✅ On |
440
+ | 23 | **Cross-Reference** | Internal markdown links + anchors resolve (with "did you mean?" hints); Obsidian wikilinks validated when the repo uses them as file links (`.obsidian` present or a target resolves) | ✅ On |
441
+ | 24 | **Generated-Staleness** | `source=code` sections match scanner output; `status: draft` doc age | ✅ On |
442
+ | 25 | **Canonical-Sync** | DocGuard's own README count claims match code-truth (DocGuard repo only — N/A elsewhere) | ✅ On |
443
+ | 26 | **Metrics-Consistency** | Hardcoded numbers match actual counts | ✅ On |
444
+ | 27 | **Surface-Sync** | Item-level enumerable drift names in doc tables/lists (commands, checks, etc.) match code-truth (opt-in via `surfaceSync.surfaces`; N/A unless configured) | ✅ On |
445
+ | 28 | **Diff-Suspicion** | Change-driven: a doc/agent-instruction file that references code changed since the ref AND shares removed domain symbols is flagged for review (arXiv 2010.01625, F1 74.7) | ✅ On |
446
+ | 29 | **Reference-Existence** | Two-revision check: a backticked code symbol present when the doc was last updated but gone at HEAD is flagged as outdated (arXiv 2212.01479) | ✅ On |
447
+ | 30 | **API-Doc-Smells** | Bloated (≥300 words) / Lazy (≤6 prose words) API documentation units, keyed on signature-headed sections (F1 0.90/0.95) | ✅ On |
430
448
 
431
449
  **Per-validator controls** (in `.docguard.json`):
432
450
  ```json
@@ -517,7 +535,7 @@ DocGuard provides AI agent slash commands for integrated workflows. Installed au
517
535
  | Command | What It Does |
518
536
  |:--------|:-------------|
519
537
  | `/docguard.init` | Initialize Canonical-Driven Development in a new or existing project |
520
- | `/docguard.guard` | Run quality validation — check all 29 validators |
538
+ | `/docguard.guard` | Run quality validation — check all 30 validators |
521
539
  | `/docguard.review` | Analyze doc quality and suggest improvements |
522
540
  | `/docguard.fix` | Generate targeted fix prompts for specific issues |
523
541
  | `/docguard.update` | Update canonical docs after code changes — detect drift and sync documentation |
@@ -703,11 +721,16 @@ Highlights of the current line (v0.29 → v0.33):
703
721
  agent tools: `claude mcp add docguard -- npx docguard-cli mcp`.
704
722
  - **Agent-file family sync** — `agents --sync` treats AGENTS.md as canonical and regenerates
705
723
  CLAUDE.md / `.cursor/rules` / Copilot / Gemini variants with drift-proof source-hash markers.
706
- - **`verify --semantic` and `verify --instructions`** — extract documented numbers/limits/enums
707
- as agent verification tasks; audit the agent-instruction files themselves for contradictions
708
- and stale pointers.
724
+ - **`verify --evidence`, `verify --semantic`, and `verify --instructions`** — check exact local
725
+ evidence declarations first, extract remaining numbers/limits/enums as agent tasks, and audit
726
+ agent-instruction files for contradictions and stale pointers.
709
727
  - **`docguard agent`** — one-shot ordered task graph with pre-filled code-truth, collapsing ~10
710
728
  agent round-trips into one call.
729
+ - **`docguard agent --task <text>`** — opt-in task context from approved current
730
+ specs and canonical docs, with hashed excerpts, source/test pointers, strict
731
+ budgets, and honest abstention. The frozen 27-run evaluation preserved every
732
+ tested behavior and cut median steps by 50% and latency by 17% versus the
733
+ context pack, while using 80% more uncached input tokens.
711
734
 
712
735
  See [CHANGELOG.md](CHANGELOG.md) for the full history.
713
736
 
@@ -1,4 +1,6 @@
1
1
  /**
2
+ * @implements docguard.evidence-scoped-verification#FR-012
3
+ * @implements docguard.task-specific-agent-context#FR-010
2
4
  * `docguard agent` — the one-shot agent task graph.
3
5
  *
4
6
  * Field report §2: an LLM told "run docguard and fix the docs" had to drive ~10
@@ -25,6 +27,8 @@ import { buildMemoryPlan } from '../scanners/memory-plan.mjs';
25
27
  import { c } from '../shared.mjs';
26
28
  import { createEvidenceReader, citedSources, taskEvidence, gitEvidence, SEMANTIC_COVERAGE_LIMITATION } from '../scanners/semantic-claims.mjs';
27
29
  import { buildScoreAssurance } from './score.mjs';
30
+ import { evaluateEvidence } from '../evidence/evaluate.mjs';
31
+ import { buildTaskContextPacket } from '../scanners/task-context.mjs';
28
32
 
29
33
  const PHASES = ['config', 'canonical-docs', 'verify'];
30
34
 
@@ -89,7 +93,7 @@ export function buildAgentTaskGraph(projectDir, config, plan) {
89
93
  phase: 'verify',
90
94
  file: null,
91
95
  kind: 'verify',
92
- instruction: 'Run `docguard guard --format json`. Resolve every error, then re-run until there are 0 errors. Triage warnings and record unresolved warnings; warnings do not fail this acceptance gate. Run `docguard score` for structural maturity, and `docguard verify --semantic` to obtain unverified claim tasks for separate source review. Neither guard nor score verifies prose or factual accuracy.',
96
+ instruction: 'Run `docguard guard --format json`. Resolve every error, then re-run until there are 0 errors. Triage warnings and record unresolved warnings; warnings do not fail this acceptance gate. Run `docguard verify --evidence` for exact declared checks and `docguard verify --semantic` for remaining heuristic claim tasks. Run `docguard score` for structural maturity. Neither guard nor score verifies prose; scoped evidence verifies only its selected statements and does not establish whole-document factual accuracy.',
93
97
  prefilled: null,
94
98
  grounding: null,
95
99
  acceptance: { verify: 'docguard guard --format json', expect: '0 errors', warnings: 'triage-and-report', scope: 'structural-only', factualAccuracy: 'unknown' },
@@ -105,11 +109,13 @@ export function buildAgentTaskGraph(projectDir, config, plan) {
105
109
 
106
110
  const assurance = buildScoreAssurance(projectDir, config);
107
111
  assurance.limitation += ` ${SEMANTIC_COVERAGE_LIMITATION}`;
112
+ const evidence = evaluateEvidence(projectDir, config);
108
113
 
109
114
  return {
110
115
  project: config.projectName,
111
116
  provenance: { kind: 'snapshot', git: gitEvidence(projectDir) },
112
117
  assurance,
118
+ evidence,
113
119
  profile: { name: profileName, kind: plan.profile.kind, languages: plan.profile.languages, frameworks: plan.profile.frameworks },
114
120
  order: PHASES,
115
121
  counts: {
@@ -123,6 +129,46 @@ export function buildAgentTaskGraph(projectDir, config, plan) {
123
129
  }
124
130
 
125
131
  export function runAgent(projectDir, config, flags) {
132
+ if (flags.task !== undefined) {
133
+ let packet;
134
+ try {
135
+ packet = buildTaskContextPacket(projectDir, config, flags.task);
136
+ } catch (error) {
137
+ const message = error instanceof Error ? error.message : 'Task context input is invalid.';
138
+ if (flags.format === 'json') console.log(JSON.stringify({ status: 'error', code: 'TASK_CONTEXT_INPUT', message }, null, 2));
139
+ else console.error(`${c.red}Error:${c.reset} ${message}`);
140
+ process.exitCode = 1;
141
+ return null;
142
+ }
143
+ if (flags.format === 'json') {
144
+ console.log(JSON.stringify(packet, null, 2));
145
+ return packet;
146
+ }
147
+
148
+ console.log(`${c.bold}🎯 DocGuard Task Context — ${config.projectName}${c.reset}`);
149
+ console.log(`${c.dim} ${packet.selection.status} · ${packet.selection.selectedExcerpts} excerpt(s) · task ${packet.task.digest}${c.reset}\n`);
150
+ console.log(` ${c.dim}Retrieval only · factual accuracy remains unknown · verify before editing.${c.reset}\n`);
151
+ if (packet.selection.status === 'abstained') {
152
+ console.log(` ${c.yellow}No evidence met the relevance threshold. Use the navigation map for normal repository discovery.${c.reset}\n`);
153
+ }
154
+ for (const excerpt of packet.excerpts) {
155
+ console.log(` ${c.bold}${excerpt.path}:${excerpt.startLine}-${excerpt.endLine}${c.reset} ${c.dim}[${excerpt.kind} · score ${excerpt.score}]${c.reset}`);
156
+ for (const line of excerpt.content.split('\n')) console.log(` ${line}`);
157
+ console.log('');
158
+ }
159
+ if (packet.pointers.length) {
160
+ console.log(` ${c.bold}Evidence pointers${c.reset}`);
161
+ for (const pointer of packet.pointers) console.log(` - ${pointer.path} (${pointer.kind}: ${pointer.reasons.join(', ')})`);
162
+ console.log('');
163
+ }
164
+ console.log(` ${c.bold}Verify${c.reset}`);
165
+ for (const item of packet.verification) console.log(` - ${item.command} — ${item.purpose}`);
166
+ console.log(`\n ${c.bold}Limits${c.reset}`);
167
+ for (const limitation of packet.limitations) console.log(` - ${limitation}`);
168
+ console.log(`\n ${c.dim}Run with --format json for hashes, machine-readable reasons, assurance, and navigation.${c.reset}`);
169
+ return packet;
170
+ }
171
+
126
172
  // Allow `--profile <name>` to preview a profile's plan without having to run
127
173
  // `init` first (the field-report agent had no config yet on its first call).
128
174
  const cfg = flags.profile ? { ...config, profile: flags.profile } : config;
@@ -1,4 +1,5 @@
1
1
  /**
2
+ * @implements docguard.evidence-scoped-verification#FR-013
2
3
  * Explain Command — v0.16-P6.
3
4
  *
4
5
  * Asked for by a user who'd spent 5-10 minutes per warning spelunking
@@ -278,6 +279,20 @@ const EXPLAINERS = {
278
279
  example: '`docguard specs preflight --path specs/007-feature/spec.md` blocks reused IDs and reports prior lifecycle/evidence before planning.',
279
280
  standard: 'Canonical-Driven Development lifecycle registry contract',
280
281
  },
282
+ evidence: {
283
+ title: 'Evidence — declared statements match bounded local sources',
284
+ what: 'Evaluates opt-in declarations in `.docguard-evidence.json`. Each declaration selects one exact statement under one Markdown heading, reads a bounded local JSON value, file collection, saved oasdiff report, or saved Buf report, and reports verified-within-scope, contradicted, stale, inconclusive, or unsupported.',
285
+ why: 'Structural checks can prove that documentation exists and is connected, but they cannot prove factual claims. Evidence declarations add reproducible checks for selected high-value statements without claiming that the whole document is accurate.',
286
+ triggers: [
287
+ ['Evidence manifest is invalid', 'Validate `.docguard-evidence.json` against `schemas/docguard-evidence.schema.json`; unknown fields and ambiguous declarations fail closed.'],
288
+ ['Declared evidence contradicts', 'Inspect the selected statement and source. Update the implementation when the canonical statement is still intended, or revise the statement through the normal reviewed documentation workflow.'],
289
+ ['Declared evidence is stale', 'Regenerate the saved external-tool report from the declared inputs and update their SHA-256 digests.'],
290
+ ['Declared evidence is inconclusive', 'Repair the missing or ambiguous heading/statement, unreadable source, unsafe collection, or malformed saved report before relying on the declaration.'],
291
+ ['Declared evidence uses an unsupported', 'Use a documented adapter version and command, or upgrade DocGuard when support for that producer shape becomes available.'],
292
+ ],
293
+ example: 'A retention statement under `## Policy` declares JSON Pointer `/retentionDays`; `docguard verify --evidence` reports verified-within-scope only when the exact typed values match.',
294
+ standard: 'RFC 6901 JSON Pointer; JSON Schema 2020-12; DocGuard evidence-scoping contract',
295
+ },
281
296
 
282
297
  // ── Backfilled in v0.24 (field report, Issue A) ─────────────────────────
283
298
  // These validators were registered in guard but had no explain entry, so
@@ -410,6 +425,7 @@ const DISPLAY_NAMES = {
410
425
  surfaceSync: 'Surface-Sync',
411
426
  canonicalSync: 'Canonical-Sync',
412
427
  metricsConsistency: 'Metrics-Consistency',
428
+ evidence: 'Evidence',
413
429
  diffSuspicion: 'Diff-Suspicion',
414
430
  referenceExistence: 'Reference-Existence',
415
431
  apiDocSmells: 'API-Doc-Smells',
@@ -1,4 +1,8 @@
1
- import { assertDefaultDocWrites } from '../shared-doc-roles.mjs';
1
+ /**
2
+ * @implements docguard.language-repository-coverage#FR-010
3
+ * @implements docguard.language-repository-coverage#FR-011
4
+ */
5
+ import { assertMappedFullDocumentWrites, docRolePath, isMappedDocPath, resolveDocRole } from '../shared-doc-roles.mjs';
2
6
  /**
3
7
  * Fix Command — The AI Orchestrator
4
8
  *
@@ -14,7 +18,7 @@ import { assertDefaultDocWrites } from '../shared-doc-roles.mjs';
14
18
  * --auto Create skeleton files (NOT content) via init
15
19
  */
16
20
 
17
- import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
21
+ import { existsSync, readFileSync, mkdirSync } from 'node:fs';
18
22
  import { resolve, basename, dirname } from 'node:path';
19
23
  import { execSync, execFileSync } from 'node:child_process';
20
24
  import { fileURLToPath } from 'node:url';
@@ -23,10 +27,9 @@ import { computeApiSurfaceDrift } from '../validators/api-surface.mjs';
23
27
  import { removeEndpoints, hasGeneratedMarker } from '../writers/api-reference.mjs';
24
28
  import { applyMechanicalFixes } from '../writers/mechanical.mjs';
25
29
  import { loadFixMemory } from '../writers/fix-memory.mjs';
30
+ import { safeWrite } from '../writers/generate-io.mjs';
26
31
  import { runGuardInternal } from './guard.mjs';
27
32
 
28
- const API_DOC = 'docs-canonical/API-REFERENCE.md';
29
-
30
33
  /**
31
34
  * Apply DETERMINISTIC, no-LLM API-surface fixes: remove endpoints documented in
32
35
  * API-REFERENCE.md that the OpenAPI spec confirms no longer exist. Removes the
@@ -38,13 +41,13 @@ const API_DOC = 'docs-canonical/API-REFERENCE.md';
38
41
  * @returns {{ applied: boolean, removed: Array<{method,path}>, skipped?: string }}
39
42
  */
40
43
  export function applyApiSurfaceWrites(projectDir, config, { force = false } = {}) {
41
- assertDefaultDocWrites(config);
42
44
  const drift = computeApiSurfaceDrift(projectDir, config);
43
45
  // Only spec-confirmed absences are safe to delete deterministically.
44
46
  const removable = drift.confidence === 'spec' ? drift.documentedButAbsent : [];
45
47
  if (removable.length === 0) return { applied: false, removed: [] };
46
48
 
47
- const apiDocPath = resolve(projectDir, API_DOC);
49
+ const apiDoc = docRolePath(config, 'apiReference');
50
+ const apiDocPath = resolveDocRole(projectDir, config, 'apiReference');
48
51
  if (!existsSync(apiDocPath)) return { applied: false, removed: [] };
49
52
 
50
53
  const content = readFileSync(apiDocPath, 'utf-8');
@@ -52,17 +55,18 @@ export function applyApiSurfaceWrites(projectDir, config, { force = false } = {}
52
55
  return {
53
56
  applied: false,
54
57
  removed: [],
55
- skipped: `${API_DOC} is not marked '<!-- docguard:generated true -->'. ` +
58
+ skipped: `${apiDoc} is not marked '<!-- docguard:generated true -->'. ` +
56
59
  `Re-run with --force to edit it, or fix it via an AI agent (/docguard.fix --doc api-reference).`,
57
60
  };
58
61
  }
62
+ if (isMappedDocPath(config, apiDoc)) assertMappedFullDocumentWrites(projectDir, config, ['apiReference']);
59
63
 
60
64
  const { content: newContent, removed } = removeEndpoints(content, removable);
61
65
  if (removed.length === 0 || newContent === content) {
62
66
  return { applied: false, removed: [] }; // idempotent no-op
63
67
  }
64
68
 
65
- writeFileSync(apiDocPath, newContent, 'utf-8');
69
+ safeWrite(apiDocPath, newContent);
66
70
  // Map removed keys back to {method,path} for reporting.
67
71
  const removedEndpoints = removable.filter(e => removed.includes(`${e.method.toUpperCase()} ${normalizeForKey(e.path)}`));
68
72
  return { applied: true, removed: removedEndpoints.length ? removedEndpoints : removable };
@@ -275,7 +279,6 @@ IMPORTANT: A new contributor should be able to follow this doc and have the proj
275
279
  * @returns {{ applied: object[], skipped: object[], total: number }}
276
280
  */
277
281
  export function applyAllMechanicalFixes(projectDir, config, opts = {}) {
278
- assertDefaultDocWrites(config);
279
282
  const { force = false, forceRedo = false } = opts;
280
283
  const guardData = runGuardInternal(projectDir, config);
281
284
  const fixes = [];
@@ -284,7 +287,7 @@ export function applyAllMechanicalFixes(projectDir, config, opts = {}) {
284
287
  }
285
288
  // v0.14-P1: forwarding forceRedo so users with `--force-redo` can override
286
289
  // ping-pong suppression for a specific fix they actually want re-applied.
287
- const { applied, skipped } = applyMechanicalFixes(projectDir, fixes, { force, forceRedo });
290
+ const { applied, skipped } = applyMechanicalFixes(projectDir, fixes, { force, forceRedo, config });
288
291
  return { applied, skipped, total: fixes.length };
289
292
  }
290
293
 
@@ -375,7 +378,6 @@ function runWriteMode(projectDir, config, flags) {
375
378
  // ── Main Entry ─────────────────────────────────────────────────────────────
376
379
 
377
380
  export function runFix(projectDir, config, flags) {
378
- if (flags.write) assertDefaultDocWrites(config);
379
381
  const isJson = flags.format === 'json';
380
382
  const isPrompt = flags.format === 'prompt';
381
383
  const autoFix = flags.auto || false;
@@ -1,4 +1,9 @@
1
- import { assertDefaultDocWrites } from '../shared-doc-roles.mjs';
1
+ /**
2
+ * @implements docguard.language-repository-coverage#FR-009
3
+ * @implements docguard.language-repository-coverage#FR-010
4
+ * @implements docguard.language-repository-coverage#FR-011
5
+ */
6
+ import { assertMappedFullDocumentWrites, docRolePath, isMappedDocPath, mappedRolesForPath, resolveDocRole } from '../shared-doc-roles.mjs';
2
7
  /**
3
8
  * Generate Command — Reverse-engineer canonical docs from an existing codebase
4
9
  * Scans source code and creates documentation templates pre-filled with project data.
@@ -6,7 +11,7 @@ import { assertDefaultDocWrites } from '../shared-doc-roles.mjs';
6
11
  * This is the "killer feature" — take any project and auto-generate CDD docs.
7
12
  */
8
13
 
9
- import { existsSync, readFileSync, mkdirSync } from 'node:fs';
14
+ import { existsSync, readFileSync } from 'node:fs';
10
15
  import { resolve, extname, basename, relative } from 'node:path';
11
16
  import { c } from '../shared.mjs';
12
17
  import { walkFiles as sharedWalkFiles } from '../shared-ignore.mjs';
@@ -14,7 +19,7 @@ import { detectDocTools } from '../scanners/doc-tools.mjs';
14
19
  import { scanRoutesDeep } from '../scanners/routes.mjs';
15
20
  import { scanSchemasDeep } from '../scanners/schemas.mjs';
16
21
  import { buildMemoryPlan } from '../scanners/memory-plan.mjs';
17
- import { upsertSection } from '../writers/sections.mjs';
22
+ import { assertOwnedCodeSection, replaceSection, upsertSection } from '../writers/sections.mjs';
18
23
  import { safeWrite, registerGeneratedCanonicalDocs, surfaceConfidence } from '../writers/generate-io.mjs';
19
24
  import {
20
25
  generateArchitecture, generateApiReference, generateDataModel,
@@ -40,11 +45,32 @@ const CODE_EXTENSIONS = new Set([
40
45
  * inserted as agent-task placeholders), respecting human prose via markers.
41
46
  */
42
47
  export function runGeneratePlan(projectDir, config, flags) {
43
- if (flags.write) assertDefaultDocWrites(config);
44
48
  // `--profile <name>` previews a profile's doc set without needing `init` first.
45
49
  if (flags.profile) config = { ...config, profile: flags.profile };
46
50
  const plan = buildMemoryPlan(projectDir, config);
47
51
 
52
+ // Existing mapped human documents grant ownership section-by-section. Check
53
+ // every target before the first write so one malformed later file cannot
54
+ // leave an earlier document partially updated.
55
+ if (flags.write) {
56
+ for (const doc of plan.docs) {
57
+ const full = resolve(projectDir, doc.path);
58
+ if (!isMappedDocPath(config, doc.path)) continue;
59
+ if (!existsSync(full)) {
60
+ if (mappedRolesForPath(config, doc.path).length > 1) {
61
+ throw new Error(`Mapped document ${doc.path} serves multiple roles; whole-document scaffolding is unavailable.`);
62
+ }
63
+ continue;
64
+ }
65
+ const content = readFileSync(full, 'utf8');
66
+ const fullyOwned = /^[ \t]*<!--\s*docguard:generated\s+true\s*-->[ \t]*$/mi.test(content);
67
+ if (fullyOwned) assertMappedFullDocumentWrites(projectDir, config, mappedRolesForPath(config, doc.path));
68
+ else for (const sec of doc.sections.filter(item => item.source === 'code')) {
69
+ assertOwnedCodeSection(content, sec.id, doc.path);
70
+ }
71
+ }
72
+ }
73
+
48
74
  if (flags.format === 'json') {
49
75
  console.log(JSON.stringify({
50
76
  project: config.projectName,
@@ -80,8 +106,6 @@ export function runGeneratePlan(projectDir, config, flags) {
80
106
 
81
107
  // --write: scaffold the skeleton docs with code sections + agent-task placeholders.
82
108
  if (flags.write) {
83
- const docsDir = resolve(projectDir, 'docs-canonical');
84
- if (!existsSync(docsDir)) mkdirSync(docsDir, { recursive: true });
85
109
  let wrote = 0;
86
110
  for (const doc of plan.docs) {
87
111
  const full = resolve(projectDir, doc.path);
@@ -89,11 +113,16 @@ export function runGeneratePlan(projectDir, config, flags) {
89
113
  let content = existsSync(full)
90
114
  ? readFileSync(full, 'utf-8')
91
115
  : `# ${title}\n\n<!-- docguard:generated true -->\n`;
116
+ const boundedMapped = isMappedDocPath(config, doc.path) && existsSync(full)
117
+ && !/^[ \t]*<!--\s*docguard:generated\s+true\s*-->[ \t]*$/mi.test(content);
92
118
  for (const sec of doc.sections) {
119
+ if (boundedMapped && sec.source !== 'code') continue;
93
120
  const body = sec.source === 'code'
94
121
  ? sec.body
95
122
  : `> **AI task:** ${sec.task}\n<!-- docguard:pending agent writes this section -->`;
96
- content = upsertSection(content, sec.id, body, { source: sec.source }).content;
123
+ content = boundedMapped
124
+ ? replaceSection(content, sec.id, body).content
125
+ : upsertSection(content, sec.id, body, { source: sec.source }).content;
97
126
  }
98
127
  // Route through safeWrite: creates the parent dir (docs-implementation/ may
99
128
  // not exist yet — was an ENOENT crash) and snapshots a .bak before writing.
@@ -139,7 +168,6 @@ export function runGeneratePlan(projectDir, config, flags) {
139
168
  }
140
169
 
141
170
  export function runGenerate(projectDir, config, flags) {
142
- if (!flags.plan || flags.write) assertDefaultDocWrites(config);
143
171
  // --plan: emit the AI-powered "memory plan" — the agent task manifest. The CLI
144
172
  // builds the code-truth skeleton (marked sections) + tells the agent exactly
145
173
  // what prose to write per section. This is the language-aware Generate path.
@@ -191,17 +219,23 @@ export function runGenerate(projectDir, config, flags) {
191
219
  console.log('');
192
220
 
193
221
  // ── 6. Generate Documents ──
194
- const docsDir = resolve(projectDir, 'docs-canonical');
195
- if (!existsSync(docsDir)) {
196
- mkdirSync(docsDir, { recursive: true });
197
- }
222
+ // Preflight every mapped whole-document target before the first generator
223
+ // writes. Existing targets are skipped unless --force, so they do not need
224
+ // ownership merely to run ordinary generation.
225
+ const candidateRoles = ['architecture', ...(deepRoutes.length > 0 ? ['apiReference'] : []),
226
+ 'dataModel', 'environment', 'testSpec', 'security'];
227
+ const writableRoles = candidateRoles.filter(role => {
228
+ const target = resolveDocRole(projectDir, config, role);
229
+ return !existsSync(target) || flags.force;
230
+ });
231
+ assertMappedFullDocumentWrites(projectDir, config, writableRoles);
198
232
 
199
233
  // ── Safety: warn if --force will overwrite existing files ──
200
234
  if (flags.force) {
201
235
  const targetFiles = [
202
- 'docs-canonical/ARCHITECTURE.md', 'docs-canonical/API-REFERENCE.md',
203
- 'docs-canonical/DATA-MODEL.md', 'docs-canonical/ENVIRONMENT.md',
204
- 'docs-canonical/TEST-SPEC.md', 'docs-canonical/SECURITY.md',
236
+ docRolePath(config, 'architecture'), docRolePath(config, 'apiReference'),
237
+ docRolePath(config, 'dataModel'), docRolePath(config, 'environment'),
238
+ docRolePath(config, 'testSpec'), docRolePath(config, 'security'),
205
239
  'AGENTS.md', 'CHANGELOG.md', 'DRIFT-LOG.md',
206
240
  ];
207
241
  const existing = targetFiles.filter(f => existsSync(resolve(projectDir, f)));
@@ -248,9 +282,9 @@ export function runGenerate(projectDir, config, flags) {
248
282
  // B7: keep guard coherent — register the canonical docs we emitted so the
249
283
  // traceability validator doesn't flag the generator's own output.
250
284
  const registered = registerGeneratedCanonicalDocs(projectDir, [
251
- 'docs-canonical/ARCHITECTURE.md', 'docs-canonical/API-REFERENCE.md',
252
- 'docs-canonical/DATA-MODEL.md', 'docs-canonical/ENVIRONMENT.md',
253
- 'docs-canonical/TEST-SPEC.md', 'docs-canonical/SECURITY.md',
285
+ docRolePath(config, 'architecture'), docRolePath(config, 'apiReference'),
286
+ docRolePath(config, 'dataModel'), docRolePath(config, 'environment'),
287
+ docRolePath(config, 'testSpec'), docRolePath(config, 'security'),
254
288
  ]);
255
289
 
256
290
  console.log(`\n${c.bold} ─────────────────────────────────────${c.reset}`);
@@ -1,6 +1,7 @@
1
1
  import { describeCheckCoverage, summarizeCheckCoverage } from '../validator-coverage.mjs';
2
2
  import { applyDocRoles } from '../shared-doc-roles.mjs';
3
3
  /**
4
+ * @implements docguard.evidence-scoped-verification#FR-010
4
5
  * Guard Command — Validate project against its canonical documentation
5
6
  * Runs all enabled validators and reports results.
6
7
  *
@@ -157,6 +158,8 @@ import { validateReferenceExistence } from '../validators/reference-existence.mj
157
158
  import { validateApiDocSmells } from '../validators/api-doc-smells.mjs';
158
159
  import { validateDocumentLifecycle } from '../validators/document-lifecycle.mjs';
159
160
  import { validateSpecRegistry } from '../validators/spec-registry.mjs';
161
+ import { validateEvidence } from '../validators/evidence.mjs';
162
+ import { coverSemanticClaims } from '../evidence/evaluate.mjs';
160
163
 
161
164
  /**
162
165
  * Internal guard — returns structured data, no console output, no process.exit.
@@ -323,6 +326,7 @@ export function runGuardInternal(projectDir, config) {
323
326
  { key: 'specKit', name: 'Spec-Kit', fn: () => validateSpecKitIntegration(projectDir, config) },
324
327
  { key: 'documentLifecycle', name: 'Document-Lifecycle', fn: () => validateDocumentLifecycle(projectDir, config) },
325
328
  { key: 'specRegistry', name: 'Spec-Registry', fn: () => validateSpecRegistry(projectDir, config) },
329
+ { key: 'evidence', name: 'Evidence', fn: () => validateEvidence(projectDir, config) },
326
330
  { key: 'crossReference', name: 'Cross-Reference', fn: () => validateCrossReferences(projectDir, config) },
327
331
  { key: 'generatedStaleness', name: 'Generated-Staleness', fn: () => validateGeneratedStaleness(projectDir, config) },
328
332
  { key: 'surfaceSync', name: 'Surface-Sync', fn: () => validateSurfaceSync(projectDir, config) },
@@ -492,9 +496,14 @@ export function runGuardInternal(projectDir, config) {
492
496
  const lite = Array.isArray(config.changedFiles);
493
497
  let coverage = null;
494
498
  let semanticClaims = null;
499
+ const evidence = results.find(result => result.key === 'evidence')?.evidence || null;
495
500
  if (!lite) {
496
501
  try { coverage = computeDocCoverage(projectDir, config); } catch { coverage = null; }
497
- try { semanticClaims = { count: extractSemanticClaims(projectDir, config).length }; }
502
+ try {
503
+ const claims = extractSemanticClaims(projectDir, config);
504
+ const scoped = coverSemanticClaims(claims, evidence);
505
+ semanticClaims = { count: scoped.unverified, discovered: scoped.total, verifiedWithinScope: scoped.verifiedWithinScope, coveredClaimIds: scoped.covered };
506
+ }
498
507
  catch { semanticClaims = null; }
499
508
  }
500
509
 
@@ -518,6 +527,7 @@ export function runGuardInternal(projectDir, config) {
518
527
  coverage,
519
528
  checkCoverage,
520
529
  semanticClaims,
530
+ evidence,
521
531
  validators: results,
522
532
  // Unknown keys in `docguard:validator … n/a` markers — typo protection so
523
533
  // a mistyped key doesn't silently fail to suppress. Surfaced by runGuard.
@@ -539,7 +549,7 @@ export function runGuardInternal(projectDir, config) {
539
549
  * Freshness (git log), Traceability (REQ scan), Doc-Quality (prose lint) —
540
550
  * stay off for speed.
541
551
  */
542
- export const CHANGED_ONLY_VALIDATORS = ['docsSync', 'environment', 'apiSurface', 'drift', 'todoTracking'];
552
+ export const CHANGED_ONLY_VALIDATORS = ['docsSync', 'environment', 'apiSurface', 'drift', 'todoTracking', 'evidence'];
543
553
 
544
554
  /**
545
555
  * Build a validators map that enables the pre-commit-lite set — PLUS any
@@ -560,6 +570,7 @@ export function liteValidatorsConfig(config = {}) {
560
570
  'apiSurface', 'metadataSync', 'docsCoverage', 'docQuality', 'todoTracking',
561
571
  'schemaSync', 'specKit', 'crossReference', 'generatedStaleness',
562
572
  'canonicalSync', 'metricsConsistency',
573
+ 'evidence',
563
574
  ];
564
575
  const userValidators = (config && config.validators) || {};
565
576
  const out = {};
@@ -1,8 +1,10 @@
1
1
  /**
2
+ * @implements docguard.evidence-scoped-verification#FR-009
3
+ * @implements docguard.evidence-scoped-verification#FR-012
2
4
  * MCP Command — DocGuard as a Model Context Protocol server (stdio).
3
5
  *
4
6
  * `docguard mcp` exposes the read-only core (guard / score / explain /
5
- * verify-claims / diagnose) as MCP tools any MCP client (Claude, Cursor,
7
+ * verify-evidence / verify-claims / diagnose) as MCP tools any MCP client (Claude, Cursor,
6
8
  * agent SDKs) can call over stdio. JSON-RPC 2.0, newline-delimited, per the
7
9
  * MCP stdio transport (protocol revision 2024-11-05).
8
10
  *
@@ -31,6 +33,7 @@ import { buildReport } from './report.mjs';
31
33
  import { loadConfig } from '../config.mjs';
32
34
  import { CODES } from '../findings.mjs';
33
35
  import { extractSemanticClaims, buildSemanticVerifyTasks } from '../scanners/semantic-claims.mjs';
36
+ import { coverSemanticClaims, evaluateEvidence } from '../evidence/evaluate.mjs';
34
37
 
35
38
  const _PKG = JSON.parse(readFileSync(resolve(dirname(fileURLToPath(import.meta.url)), '..', '..', 'package.json'), 'utf-8'));
36
39
 
@@ -101,6 +104,16 @@ const TOOLS = [
101
104
  },
102
105
  annotations: READONLY_ANNOTATIONS,
103
106
  },
107
+ {
108
+ name: 'docguard_verify_evidence',
109
+ title: 'Verify declared evidence',
110
+ description: 'Evaluate `.docguard-evidence.json` against bounded local sources. Returns explicit verified-within-scope, contradicted, stale, inconclusive, and unsupported states; verification applies only to each selected statement.',
111
+ inputSchema: {
112
+ type: 'object',
113
+ properties: { ...PROJECT_DIR_PROP },
114
+ },
115
+ annotations: READONLY_ANNOTATIONS,
116
+ },
104
117
  {
105
118
  name: 'docguard_verify_claims',
106
119
  title: 'Extract claims to verify',
@@ -173,13 +186,20 @@ const TOOL_HANDLERS = {
173
186
  docguard_verify_claims(args, defaultDir) {
174
187
  const { dir, config } = resolveTarget(args, defaultDir);
175
188
  const claims = extractSemanticClaims(dir, config);
189
+ const coverage = coverSemanticClaims(claims, evaluateEvidence(dir, config));
176
190
  return {
177
191
  claimCount: claims.length,
192
+ verifiedWithinScope: coverage.verifiedWithinScope,
178
193
  note: 'Deterministic discovery, LLM judgment — the caller verifies each claim against the code and reports any mismatch with both values.',
179
- tasks: buildSemanticVerifyTasks(claims),
194
+ tasks: buildSemanticVerifyTasks(coverage.remaining),
180
195
  };
181
196
  },
182
197
 
198
+ docguard_verify_evidence(args, defaultDir) {
199
+ const { dir, config } = resolveTarget(args, defaultDir);
200
+ return evaluateEvidence(dir, config);
201
+ },
202
+
183
203
  docguard_report(args, defaultDir) {
184
204
  const { dir, config } = resolveTarget(args, defaultDir);
185
205
  return buildReport(dir, config);