Add doc parity to review agent - #194
Conversation
Claude Configuration ValidationReviewed the Claude material changed in this PR: the new bitwarden-doc-parity plugin (v1.0.0) and the bitwarden-code-review bump (1.13.1 → 1.14.0). Checks run: plugin-validator agent (both plugins), skill-reviewer agent ( Result: no critical issues. No secrets, no dangerous permissions, no malformed manifests. Structure and versioning are correct. The findings below are behavior-consistency problems — three places where the skill's text, the hook's logic, the agent's instructions, and the changelog disagree with each other. Major (should fix before merge)1.
|
| markers | |
|---|---|
| SKILL.md:31 | README.md, docs/, diagram sources — repo root included |
documented_ancestors() |
README (case-insensitive), CLAUDE.md, docs/ — repo root excluded |
The hook's inline comment claims it is "aligning with SKILL.md's scope definition" — as written, it isn't. Consequences: a directory documented only by .mmd/.mermaid diagrams never arms the tripwire, and a CLAUDE.md-only scope arms the hook but is outside the rule the skill applies. The evals already grade the hook's version, not the skill's: case 2 (behavior-eval.json:26) and case 7, named instruction-files-count-as-documentation (behavior-eval.json:89), both require treating util/Seeder/CLAUDE.md as documentation — behavior the skill never states.
Fix: add instruction files to SKILL.md:31 (e.g. "contains a README.md, a CLAUDE.md or equivalent instruction file, a docs/ directory, or diagram sources"), then correct the stale comments in doc-parity-check.sh:7-9 and :113-114. The root-scope exclusion is deliberate and documented at doc-parity-check.sh:8-10 — worth stating in SKILL.md too, so the two layers read as one rule.
Minor (should fix)
4. bitwarden-code-review changelog describes behavior the PR removed
plugins/bitwarden-code-review/CHANGELOG.md:12 — the 1.14.0 entry still reads "verify or update in-repo docs at every documented ancestor scope", the exact wording commit 22aba1f replaced in AGENT.md with report-only. Since 1.14.0 is introduced by this PR, amend the entry in place rather than adding a new one. This is the same code-vs-docs drift class the plugin exists to catch.
5. Any .md touch grants coverage to an entire subtree
plugins/bitwarden-doc-parity/hooks/doc-parity-check.sh:65-71 (is_doc_file), :75-82 (doc_scope)
is_doc_file matches *.md unconditionally, so editing a CHANGELOG.md, a scratch note, or a markdown test fixture under src/ yields scope src and satisfies coverage for every changed file below it. That is precisely the "token documentation edit" the hook's own block message forbids (line 149), reachable without intent.
Fix: restrict the coverage-granting set to README.md, CLAUDE.md, docs/**, and diagram sources.
6. jq --rawfile breaks the SessionStart hook's fail-open contract on jq < 1.6
plugins/bitwarden-doc-parity/hooks/doc-parity-context.sh:19 (contract stated at lines 8-9)
The script guards on command -v jq but not on version; --rawfile landed in jq 1.6. On jq 1.5 the hook exits non-zero and surfaces an error instead of proceeding silently.
Fix: jq -n --arg ctx "$(cat "$FRAGMENT")" ... (works on 1.5+), or append || exit 0.
7. Marker directory in shared TMPDIR is not ownership-checked
plugins/bitwarden-doc-parity/hooks/doc-parity-check.sh:37-40, :146
The -O guard at line 40 protects the marker file, but MARKER_DIR="${TMPDIR:-/tmp}/doc-parity" is created with default perms and never ownership-verified. On a multi-user host with TMPDIR unset, a pre-existing /tmp/doc-parity owned by another user (or symlinked elsewhere) redirects the touch at line 146 and exposes session IDs.
Fix: mkdir -m 700, then [[ -d "$MARKER_DIR" && -O "$MARKER_DIR" ]] || MARKER="" before use.
8. Once-per-session guarantee degrades silently when session_id is absent
plugins/bitwarden-doc-parity/hooks/doc-parity-check.sh:34-43
If .session_id is missing from the payload, MARKER stays empty, nothing is recorded, and only the stop_hook_active guard prevents an immediate re-block — so the hook can block again later in the same session, contrary to the header comment at lines 12-13. Fall back to a PPID- or cwd-derived marker, or document the degradation.
9. Per-file process spawning can exceed the 15 s Stop-hook timeout
plugins/bitwarden-doc-parity/hooks/doc-parity-check.sh:115-125, timeout at hooks/hooks.json:20
documented_ancestors forks dirname twice per path component and runs compgen -G plus two filesystem tests per ancestor, for every changed non-doc file. A few thousand changed files in a deep tree is tens of thousands of forks; on timeout the hook is killed and the check silently does nothing. Memoize the per-directory answer (bash 4 assoc array, or a sorted-string cache to stay bash 3.2-compatible for macOS), and/or cap files inspected.
10. evals/README.md claims a case that does not exist
plugins/bitwarden-doc-parity/skills/verifying-doc-parity/evals/README.md:5 claims coverage of "root-level placement for a repo-wide capability." The ten cases are: enumerate-ancestors, attestation-happy-path, refuses-checkbox-theater, fixes-contradicting-docs, deletion-is-doc-maintenance, drift-two-scopes-up, instruction-files, dismisses-tripwire, placement-follows-doc, review-context. Case 9 (behavior-eval.json:115) tests the opposite — that a doc should not be hoisted to the repo root. The corresponding SKILL.md:39 rule ("A scope's documentation describes what is present at that scope and below…") is therefore ungraded.
Fix: add the case and regenerate the baseline, or drop the claim.
11. Smaller items
- Second-person drift —
SKILL.md:39: "isn't obvious from what you already have" is the file's only second-person usage. Rewrite as "…from the material already gathered." - Description length —
SKILL.md:3: 560 chars, above the ~500-char guidance (well under the 1024 limit). Trim "even if the request does not name a skill or documentation explicitly" rather than any trigger phrase. - Unverified frontmatter fields —
SKILL.md:4-6:agent: general-purpose,context: fork,background: falseappear in no other SKILL.md in this repo. Legitimate forked-skill fields, but confirm CI'svalidate-plugin-structure.shaccepts them, and note in the README's Layer 3 section why this skill forks when others don't. - Zeroed baseline metrics —
behavior-baseline.json:run_summary.time_secondsand alltokensare0. The regression projection atevals/README.md:40correctly excludes them, but a future reader may read zeros as measurements — populate or annotate. - Time-sensitive prose —
evals/README.md:5: "each recently-added instruction" won't age; name the rules instead. - Eval case 8 rationale —
behavior-eval.json:98says the file is code "because it isn't a .md/.mdx/.mmd file";is_doc_file()also matches*.mermaid. Cosmetic, but the prose is grading context.
12. Pre-existing, out of scope for this PR
plugins/bitwarden-code-review/commands/code-review/README.mdandcommands/code-review-local/README.mdsit insidecommands/and are auto-discovered as slash commands (no frontmatter → description from first line). Consider relocating.plugins/bitwarden-code-review/.claude-plugin/plugin.json:22-25lists command paths thatcommands/auto-discovery already picks up. Theagentsentry on line 21 is legitimate (agents/<dir>/AGENT.mdis not an auto-discovery shape).- No
AGENT.mdin this repo uses<example>blocks orcolor; noting for completeness only, since it's a repo-wide convention rather than a defect in this plugin.
Verified clean
Security
- No secrets, API keys, tokens, or hardcoded credentials in any changed file.
- No
settings.local.jsontracked in git. plugins/bitwarden-code-review/.claude/settings.jsonis deny-only, blocking the mutatingghsurface (merge/close/edit/release/secret/workflow, all non-GETgh apimethods). No dangerous auto-approvals, no broad file-access grants.- Hook scripts: no
eval, no unquoted expansion of payload data;session_idscrubbed withtr -cd 'a-zA-Z0-9_-'before use in a path;shellcheck -S styleclean on both.
Structure and manifests
- Both
plugin.jsonfiles: valid JSON, kebab-case names, valid semver, required fields present. - Marketplace consistency:
.claude-plugin/marketplace.jsonmatches both manifests exactly on name, version, description, and source path. - Version consistency across all four required locations —
marketplace.json,plugin.json,README.mdcatalog rows (lines 13, 18), andAGENT.mdfrontmatter (version: 1.14.0). CHANGELOG.mdpresent for both, entries matching the shipped versions (see finding 4 for the entry's wording).- Auto-discovery layout correct:
hooks/hooks.json,skills/*/SKILL.md,agents/,commands/.
Hooks
hooks.jsonuses the{"hooks": {...}}plugin wrapper;SessionStartandStopare valid events; omittingmatcheris correct for both; timeouts set (5 s / 15 s).- Both commands use
${CLAUDE_PLUGIN_ROOT}with quoted paths, invoked viabash; the executable bit is committed anyway (100755). - Stop hook uses the correct
{"decision":"block","reason":...}+ exit 0 contract; SessionStart uses the correcthookSpecificOutput.hookEventName/additionalContextshape. - Fail-open paths verified for missing
jq, non-repo cwd, and empty diff; the pipe-to-whilesubshell bug is avoided via here-strings;core.quotePath=falsehandles non-ASCII paths.
Skill and evals
namematches directory; description is third-person with concrete trigger phrases; body is 792 words — lean enough that noreferences/orexamples/split is warranted.- Only internal reference is the anchor
#out-of-repo-discovery-review-context-only(SKILL.md:19), which resolves to the heading at SKILL.md:62. All README relative links resolve. - Both eval JSON files parse. Baseline is internally consistent: 100 runs = 10 cases × 5 runs × 2 configurations; every expectation text matches
behavior-eval.json;passed + failed == totalandpass_raterecompute for all 100 runs; summary means/stddev reproduce (with_skill 0.987 / 0.0523, without_skill 0.808 / 0.2594, delta +0.18). corpus_refd6c84a7562cc6b464de910dbf829690885500137is identical acrossbehavior-eval.json,behavior-baseline.json, andevals/README.md:19.- All three of the skill's outcomes (Update / Attest / Dismiss) have dedicated cases. The deliberate omission of trigger evals is documented with a reason (
evals/README.md:9-13) rather than left as a silent gap.
Checks that could not run in this environment
pnpm run lint(prettier + cspell) —pnpmis not installed andnpxis blocked by the repo'sdevEngines.packageManagerpin. Formatting and spelling were not verified; CI'slint.ymlwill cover them.validate-plugin-structure.sh/validate-marketplace.sh— these live inbitwarden/gh-actionsand no checkout was available. Their checks (structure, marketplace consistency, version bump across all four files) were performed manually instead and pass.
Note that plugin-dev's bundled validate-hook-schema.sh reports "Unknown event type: hooks" and then crashes on this hooks.json. That script does not understand the plugin-level {"hooks": {...}} wrapper — the config is correct and matches bitwarden-ai-telemetry; the validator script is stale.
Recommendation
Request changes on findings 1–3 (tool declaration, edit-vs-report conflict, scope-definition mismatch) and finding 4 (changelog wording). Findings 5–11 are quality items that can follow.
Per .claude/CLAUDE.md, fixes to findings 1, 2, 3, and 5–9 are substantive changes to bitwarden-doc-parity and need a version bump to 1.0.1 plus a CHANGELOG.md entry, mirrored in .claude-plugin/marketplace.json, plugins/bitwarden-doc-parity/.claude-plugin/plugin.json, and the README.md catalog row. Finding 4 is an in-place amendment to the unreleased 1.14.0 entry and needs no further bump.
🤖 Bitwarden Claude Code ReviewOverall Assessment: APPROVE This PR wires the optional Code Review Details
|
|
|
||
| **Documentation parity** (any code change with a documented ancestor scope — a `README.md`, `docs/` directory, or source-embedded doc comments in the change's ancestor chain — which covers most substantive PRs): | ||
|
|
||
| - invoke `Skill(verifying-doc-parity)` to run the documentation pass in review context: verify or update in-repo docs at every documented ancestor scope of the change, and discover out-of-repo pages the change invalidates. Fold findings into your report per the documentation standard's external-docs flow (work item before merge, stale marker on the page) when applicable. |
There was a problem hiding this comment.
Details and fix
The reviewer agent is a read-only reviewer — its tools: list has no Edit, and in the CI review workflow any file it did write would be silently discarded (never committed or pushed). Instructing it to "verify or update in-repo docs" imports the session-context behavior of verifying-doc-parity Step 3 ("fix the documentation… Edit the documentation in the same change"), which has no review-context carve-out. The likely outcome is a summary that attests updated: … for scopes where nothing was actually changed on the PR branch — or, if the forked general-purpose skill agent does have write access, unexpected mutations to the author's checkout during a review.
This also contradicts the doc-parity plugin's own description of its review face: "Changes that invalidate out-of-repo docs get called out at review."
Suggested wording:
- invoke `Skill(verifying-doc-parity)` to run the documentation pass in review context: identify drifted in-repo docs at every documented ancestor scope of the change, and discover out-of-repo pages the change invalidates. Report drift as findings — do not edit files in the PR — and fold them into your classification and validation in Steps 3–4, applying the documentation standard's external-docs flow (work item before merge, stale marker on the page) when applicable.Note the suggested text also routes findings through Steps 3–4 (confidence scoring and false-positive validation), matching the reviewing-claude-config entry directly above; as written, "fold findings into your report" bypasses those gates.
982587f to
22aba1f
Compare
📔 Objective
Enables doc-parity plugin in review agent.