claude-dev-env 2.3.0 → 2.4.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 (136) hide show
  1. package/CLAUDE.md +3 -2
  2. package/agents/CLAUDE.md +1 -0
  3. package/agents/code-verifier.md +3 -3
  4. package/agents/skill-writer-agent.md +84 -0
  5. package/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md +141 -41
  6. package/audit-rubrics/prompts/category-o-docstring-vs-impl-drift.md +29 -13
  7. package/docs/CLAUDE.md +1 -0
  8. package/docs/references/CLAUDE.md +1 -0
  9. package/docs/references/code-review-enforcement.md +97 -0
  10. package/docs/wsl-docker-cowork-starter-matrix.md +89 -0
  11. package/hooks/blocking/CLAUDE.md +8 -1
  12. package/hooks/blocking/code_review_enforcement_config_bootstrap.py +53 -0
  13. package/hooks/blocking/code_review_gate_deny.py +74 -0
  14. package/hooks/blocking/code_review_pr_create_gate.py +194 -0
  15. package/hooks/blocking/code_review_push_gate.py +140 -0
  16. package/hooks/blocking/code_review_stamp_directory_write_blocker.py +340 -0
  17. package/hooks/blocking/code_review_stamp_store.py +233 -0
  18. package/hooks/blocking/code_review_stamp_write_blocker_parts/__init__.py +7 -0
  19. package/hooks/blocking/code_review_stamp_write_blocker_parts/conftest.py +15 -0
  20. package/hooks/blocking/code_review_stamp_write_blocker_parts/obfuscated_stamp_path_reference.py +212 -0
  21. package/hooks/blocking/code_review_stamp_write_blocker_parts/split_directory_change_into_stamp.py +138 -0
  22. package/hooks/blocking/code_review_stamp_write_blocker_parts/test_obfuscated_stamp_path_reference.py +49 -0
  23. package/hooks/blocking/code_review_stamp_write_blocker_parts/test_split_directory_change_into_stamp.py +38 -0
  24. package/hooks/blocking/code_verifier_spawn_preflight_gate.py +39 -27
  25. package/hooks/blocking/config/code_review_enforcement_constants.py +110 -0
  26. package/hooks/blocking/config/test_code_review_enforcement_constants.py +108 -0
  27. package/hooks/blocking/config/verified_commit_constants.py +13 -9
  28. package/hooks/blocking/conftest.py +2 -0
  29. package/hooks/blocking/convergence_gate_blocker.py +112 -23
  30. package/hooks/blocking/destructive_command_blocker.py +19 -6
  31. package/hooks/blocking/pr_description_proof_of_work.py +52 -34
  32. package/hooks/blocking/test_bash_pre_tool_use_dispatcher.py +4 -1
  33. package/hooks/blocking/test_code_review_enforcement_config_bootstrap.py +62 -0
  34. package/hooks/blocking/test_code_review_gate_deny.py +54 -0
  35. package/hooks/blocking/test_code_review_pr_create_gate.py +185 -0
  36. package/hooks/blocking/test_code_review_push_gate.py +189 -0
  37. package/hooks/blocking/test_code_review_stamp_directory_write_blocker.py +180 -0
  38. package/hooks/blocking/test_code_review_stamp_store.py +205 -0
  39. package/hooks/blocking/test_code_verifier_spawn_preflight_gate.py +124 -2
  40. package/hooks/blocking/test_convergence_gate_blocker.py +153 -5
  41. package/hooks/blocking/test_destructive_command_blocker.py +1 -1
  42. package/hooks/blocking/test_destructive_command_blocker_deny_mode.py +45 -0
  43. package/hooks/blocking/test_pr_description_proof_of_work.py +151 -0
  44. package/hooks/blocking/test_pre_tool_use_dispatcher.py +8 -8
  45. package/hooks/blocking/test_verification_verdict_store.py +920 -903
  46. package/hooks/blocking/test_volatile_path_in_post_blocker.py +114 -2
  47. package/hooks/blocking/verification_verdict_store.py +27 -5
  48. package/hooks/blocking/verified_commit_gate_parts/gated_invocations.py +29 -17
  49. package/hooks/blocking/verified_commit_gate_parts/tests/test_gated_invocations.py +35 -0
  50. package/hooks/blocking/volatile_path_in_post_blocker.py +69 -8
  51. package/hooks/git-hooks/git_hooks_constants/__init__.py +6 -0
  52. package/hooks/git-hooks/pre_push.py +89 -2
  53. package/hooks/git-hooks/test_pre_push.py +103 -0
  54. package/hooks/hooks.json +16 -1
  55. package/hooks/hooks_constants/CLAUDE.md +1 -0
  56. package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +8 -0
  57. package/hooks/hooks_constants/code_rules_path_utils_constants.py +1 -0
  58. package/hooks/hooks_constants/code_verifier_spawn_preflight_gate_constants.py +26 -11
  59. package/hooks/hooks_constants/convergence_gate_blocker_constants.py +20 -3
  60. package/hooks/hooks_constants/destructive_command_segment_constants.py +3 -1
  61. package/hooks/hooks_constants/pr_description_proof_of_work_constants.py +0 -4
  62. package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +4 -0
  63. package/hooks/hooks_constants/pyproject_config_discovery_constants.py +16 -0
  64. package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +24 -0
  65. package/hooks/hooks_constants/test_pre_tool_use_dispatcher_constants.py +6 -0
  66. package/hooks/hooks_constants/volatile_path_in_post_blocker_constants.py +8 -1
  67. package/hooks/validators/CLAUDE.md +1 -0
  68. package/hooks/validators/mypy_integration.py +63 -50
  69. package/hooks/validators/pyproject_config_discovery.py +101 -0
  70. package/hooks/validators/ruff_integration.py +211 -23
  71. package/hooks/validators/run_all_validators.py +21 -14
  72. package/hooks/validators/test_mypy_integration.py +32 -0
  73. package/hooks/validators/test_pyproject_config_discovery.py +94 -0
  74. package/hooks/validators/test_ruff_integration.py +68 -1
  75. package/hooks/validators/test_run_all_validators.py +25 -0
  76. package/hooks/validators/test_run_all_validators_config_discovery.py +123 -0
  77. package/package.json +1 -1
  78. package/rules/docstring-prose-matches-implementation.md +20 -43
  79. package/rules/durable-post-artifacts.md +7 -0
  80. package/scripts/codec_forwarding_test_support.py +83 -0
  81. package/scripts/conftest.py +8 -0
  82. package/scripts/dev_env_scripts_constants/CLAUDE.md +1 -1
  83. package/scripts/dev_env_scripts_constants/claude_chain_constants.py +44 -1
  84. package/scripts/dev_env_scripts_constants/code_review_constants.py +129 -12
  85. package/scripts/dev_env_scripts_constants/test_code_review_constants.py +55 -0
  86. package/scripts/invoke_code_review.py +550 -38
  87. package/scripts/resolve_worker_spawn.py +8 -1
  88. package/scripts/test_invoke_code_review.py +298 -4
  89. package/scripts/test_invoke_code_review_codec.py +77 -0
  90. package/scripts/test_resolve_worker_spawn_codec.py +101 -0
  91. package/skills/autoconverge/SKILL.md +9 -3
  92. package/skills/autoconverge/reference/convergence.md +2 -1
  93. package/skills/autoconverge/reference/multi-pr.md +6 -1
  94. package/skills/autoconverge/reference/stop-conditions.md +16 -10
  95. package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +1 -1
  96. package/skills/autoconverge/workflow/converge.codex-gate.test.mjs +175 -3
  97. package/skills/autoconverge/workflow/converge.contract.test.mjs +19 -0
  98. package/skills/autoconverge/workflow/converge.mjs +24 -8
  99. package/skills/autoconverge/workflow/converge_multi.mjs +7 -3
  100. package/skills/autoconverge/workflow/converge_multi.run-input.test.mjs +5 -0
  101. package/skills/fresh-branch/CLAUDE.md +2 -0
  102. package/skills/fresh-branch/SKILL.md +2 -0
  103. package/skills/fresh-branch/scripts/create_fresh_branch.py +78 -180
  104. package/skills/fresh-branch/scripts/fresh_branch_git_commands.py +285 -0
  105. package/skills/fresh-branch/scripts/fresh_branch_scripts_constants/fresh_branch_cli_constants.py +1 -0
  106. package/skills/fresh-branch/scripts/pytest.ini +4 -0
  107. package/skills/fresh-branch/scripts/test_create_fresh_branch.py +98 -0
  108. package/skills/fresh-branch/scripts/test_fresh_branch_git_commands.py +310 -0
  109. package/skills/prototype/SKILL.md +86 -0
  110. package/skills/prototype/reference/honest-limitations.md +23 -0
  111. package/skills/prototype/reference/promotion-tasks.md +23 -0
  112. package/skills/prototype/scripts/build_sandbox_settings.py +249 -0
  113. package/skills/prototype/scripts/conftest.py +15 -0
  114. package/skills/prototype/scripts/launch_sandbox.py +205 -0
  115. package/skills/prototype/scripts/probe_sandbox_safety.py +311 -0
  116. package/skills/prototype/scripts/prototype_scripts_constants/__init__.py +1 -0
  117. package/skills/prototype/scripts/prototype_scripts_constants/config/__init__.py +0 -0
  118. package/skills/prototype/scripts/prototype_scripts_constants/config/build_sandbox_settings_constants.py +41 -0
  119. package/skills/prototype/scripts/prototype_scripts_constants/config/launch_sandbox_constants.py +23 -0
  120. package/skills/prototype/scripts/prototype_scripts_constants/config/probe_sandbox_safety_constants.py +45 -0
  121. package/skills/prototype/scripts/prototype_scripts_constants/config/prototype_common_constants.py +10 -0
  122. package/skills/prototype/scripts/test_build_sandbox_settings.py +275 -0
  123. package/skills/prototype/scripts/test_launch_sandbox.py +303 -0
  124. package/skills/prototype/scripts/test_probe_sandbox_safety.py +284 -0
  125. package/skills/prototype/workflows/promotion.md +27 -0
  126. package/skills/prototype/workflows/sandbox.md +35 -0
  127. package/skills/skill-builder/CLAUDE.md +3 -3
  128. package/skills/skill-builder/SKILL.md +5 -5
  129. package/skills/skill-builder/references/CLAUDE.md +1 -1
  130. package/skills/skill-builder/references/delegation-map.md +3 -3
  131. package/skills/skill-builder/references/description-field.md +1 -1
  132. package/skills/skill-builder/references/skill-modularity.md +2 -3
  133. package/skills/skill-builder/workflows/CLAUDE.md +1 -1
  134. package/skills/skill-builder/workflows/improve-skill.md +1 -1
  135. package/skills/skill-builder/workflows/new-skill.md +2 -2
  136. package/skills/team-advisor/SKILL.md +2 -2
package/CLAUDE.md CHANGED
@@ -38,10 +38,11 @@ Reserve `Read`/`Grep`/`Glob` for files you will actually touch this turn. Compos
38
38
 
39
39
  ## Target Execution Workflow for Code Tasks
40
40
 
41
- Run every multi-step code task in two phases:
41
+ Run every multi-step code task in three ordered phases:
42
42
 
43
43
  1. **Coders** — one coder agent per scoped assignment writes the code. A coder that hits a decision it can't reasonably solve consults the advisor (see beginning of this file).
44
- 2. **Verification** — when the coders finish, the main session spawns the `code-verifier` agent in a fresh context, but you must first verify that their work is based on upstream's origin main (aka: the commit live on github). It derives and runs the checks itself rather than trusting coder reports: the task's named gates, tests against baselines recorded before the coders ran, and a two-way diff-vs-assignment reading (every task item maps to a hunk, every hunk maps to a task item, nothing missing). A finding must cite a failing command or a named task item. Before it emits the verdict, it puts the draft through one strongest-tier validation subagent — selected per the advisor protocol's host detection and tier ladder — that tries to refute it, and it re-checks and corrects any part the validator overturns. Source: the fresh-context review step in Claude Code best practices (https://code.claude.com/docs/en/best-practices) the agent doing the work isn't the one grading it.
44
+ 2. **Commit-gate preflight** — before spawning `code-verifier`, the branch must be merge-clean vs base and CODE_RULES-clean on added working-tree lines (committed on the branch or uncommitted). The `code_verifier_spawn_preflight_gate` enforces this on Agent/Task spawns with `subagent_type` `code-verifier`; do not spawn the verifier until those gates are green.
45
+ 3. **Verification** — the main session spawns the `code-verifier` agent in a fresh context, but you must first verify that their work is based on upstream's origin main (aka: the commit live on github). It derives and runs the checks itself rather than trusting coder reports: the task's named gates, tests against baselines recorded before the coders ran, and a two-way diff-vs-assignment reading (every task item maps to a hunk, every hunk maps to a task item, nothing missing). A finding must cite a failing command or a named task item. Before it emits the verdict, it puts the draft through one strongest-tier validation subagent — selected per the advisor protocol's host detection and tier ladder — that tries to refute it, and it re-checks and corrects any part the validator overturns. Source: the fresh-context review step in Claude Code best practices (https://code.claude.com/docs/en/best-practices) — the agent doing the work isn't the one grading it.
45
46
 
46
47
  Repair agents run only on reported findings; the verifier re-checks after each repair. Work lands (commit, push, draft PR) only on a clean verdict — enforced by the `verified_commit_gate` hook, which blocks `git commit`/`git push` unless a hook-minted verdict covers the current branch diff. One exemption is mechanical, not discretionary: a diff whose every changed file is non-code (docs, images), a pytest test file by name (`test_*.py`, `*_test.py`, or `conftest.py`), or a Python file whose docstring-stripped AST is unchanged (docstring, comment, or formatting-only edits). One escape hatch is manual and narrow: appending `# verify-skip` as a trailing shell comment (outside every quoted region) to the blocked commit or push command bypasses the gate for that one command — allowed only when the branch surface is the same code a code-verifier already passed clean and the gate is blocking on a verdict that doesn't cover it (an unminted verdict, staging churn, a reverted concurrent write); any real code change since the clean verdict runs a fresh verification instead. Full rule: `~/.claude/rules/verified-commit-gate-skip.md`.
47
48
 
package/agents/CLAUDE.md CHANGED
@@ -18,6 +18,7 @@ Agent definition files installed into `~/.claude/agents/` by `bin/install.mjs`.
18
18
  | `plan-packet-validator.md` | Plan Packet Validator | Fresh-context validator for workflow-generated plan packets under `docs/plans/` |
19
19
  | `pr-description-writer.md` | PR Description Writer | Authors PR descriptions in Anthropic-style shapes that pass the `pr_description_enforcer` hook's body audit |
20
20
  | `session-advisor.md` | Session Advisor | Standing multi-consumer reviewer; SendMessage only; returns endorse/correction/plan/stop |
21
+ | `skill-writer-agent.md` | Skill Writer Agent | Authors SKILL.md and companion files to skill-builder conventions; caller-agnostic authoring specialist |
21
22
 
22
23
  ## Format
23
24
 
@@ -1,12 +1,12 @@
1
1
  ---
2
2
  name: code-verifier
3
- description: Post-hoc verification agent for the two-phase code workflow. Spawned by the main session after coder agents finish. Runs every check itself in a fresh context — named gates, tests against recorded baselines, two-way diff-vs-task reading — puts the draft verdict through one strongest-tier validation subagent that tries to refute it, then ends with a fenced verdict block the verifier_verdict_minter hook turns into the commit-gate verdict. Read and execute only; it never edits files.
3
+ description: Post-hoc verification agent for the three-phase code workflow. Spawned by the main session after coder agents finish. Runs every check itself in a fresh context — named gates, tests against recorded baselines, two-way diff-vs-task reading — puts the draft verdict through one strongest-tier validation subagent that tries to refute it, then ends with a fenced verdict block the verifier_verdict_minter hook turns into the commit-gate verdict. Read and execute only; it never edits files.
4
4
  tools: Read, Grep, Glob, Bash, Task
5
5
  model: sonnet
6
6
  color: orange
7
7
  ---
8
8
 
9
- You are the verifier in a two-phase code workflow: coder agents wrote changes, and you grade the result on its own terms (Claude Code best practices, fresh-context review: https://code.claude.com/docs/en/best-practices). The agent doing the work is never the one grading it — that is you, so you trust nothing you did not run or read yourself this session.
9
+ You are the verifier in a three-phase code workflow: coder agents wrote changes, and you grade the result on its own terms (Claude Code best practices, fresh-context review: https://code.claude.com/docs/en/best-practices). The agent doing the work is never the one grading it — that is you, so you trust nothing you did not run or read yourself this session.
10
10
 
11
11
  The caller gives you task texts, the diff scope, and baselines recorded before the coders ran. Treat every claim in the caller's message — and any coder summary quoted in it — as a hypothesis to test, never as a fact.
12
12
 
@@ -43,4 +43,4 @@ End your final message with exactly one fenced verdict block — the verifier_ve
43
43
  {"all_pass": false, "findings": [{"check": "<gate or task item>", "detail": "<command + output, or the named task item and what is missing>"}], "manifest_sha256": "<hash the CLI printed>"}
44
44
  ```
45
45
 
46
- Set `all_pass` to true with an empty `findings` list only when every layer came back clean. Always include `manifest_sha256` so the verdict clears the commit regardless of which work tree the verifier or the committer ran in. Any file change after you finish moves that hash and invalidates the verdict, so you are the last step before the commit.
46
+ Set `all_pass` to true with an empty `findings` list only when every layer came back clean. Always include `manifest_sha256` so the verdict clears the commit regardless of which work tree the verifier or the committer ran in. Commit-committability gates (CODE_RULES / merge conflicts) must already be green before you are spawned; you are the last semantic check before commit. Any file change after you finish moves that hash and invalidates the verdict.
@@ -0,0 +1,84 @@
1
+ ---
2
+ name: skill-writer-agent
3
+ description: >-
4
+ Authors a skill package — SKILL.md plus companion files — to skill-builder conventions: hub layout, trigger-catalog description, deterministic work shipped as scripts and task-seed lists. Triggers: author a skill, write a SKILL.md, skill-writer-agent, delegate skill file authoring, produce skill package files.
5
+ tools: Read, Write, Edit, Grep, Glob
6
+ color: green
7
+ ---
8
+
9
+ # Skill Writer Agent
10
+
11
+ You author skill files. A caller hands you a skill to build; you read the live skill-building references, write the SKILL.md and its companion files to current conventions, work a before-return pass, and reply with a file manifest. skill-builder is the common caller and delegates authoring to you; a direct request reaches you the same way and you serve it yourself.
12
+
13
+ ## Input
14
+
15
+ Callers reach you in one of two shapes.
16
+
17
+ **Structured packet** (skill-builder sends this). A new-skill packet carries these fields, in order:
18
+
19
+ - `Skill type` — one of the skill-builder skill types.
20
+ - `Folder structure` — the directories to create (`reference/`, `scripts/`, `templates/`, `workflow/`).
21
+ - `What it does` — one sentence naming the single job.
22
+ - `Domain context` — what the authored skill needs Claude to know.
23
+ - `Initial gotchas` — failure patterns to document from the start.
24
+ - `Degree of freedom` — high, medium, or low, with reasoning.
25
+ - `Constraints` — the non-negotiables.
26
+ - `Composition plan` — related skills, sub-skills with when/produces/missing, leaf-versus-orchestrator call.
27
+ - `Description` — the exact frontmatter string, written as a trigger catalog.
28
+ - `Deterministic inventory` — each process step mapped to its class, its home path, and its paired test.
29
+
30
+ A caller may bundle three of these fields — `Composition plan`, `Description`, and `Deterministic inventory` — under one `gap analysis` label; treat that label as those three fields together, and read them from the packet either way.
31
+
32
+ A refine packet instead carries: the current SKILL.md, what was observed, what to change, new gotchas to add, what to preserve, a description rewrite, a composition change, and a deterministic fix.
33
+
34
+ **Direct request.** A looser prompt names the skill to build or the file to edit. Fill the packet yourself from the references below, and ask the caller for any field only they hold, such as domain context or constraints.
35
+
36
+ ## Read first
37
+
38
+ At the start of every job, read the live references at `~/.claude/skills/skill-builder/references/` so you author to current conventions:
39
+
40
+ - `delegation-map.md` — the handoff packet and the Produce contract.
41
+ - `deterministic-elements.md` — the rule that deterministic work ships as code, artifacts, or session tasks.
42
+ - `description-field.md` — the trigger-catalog description shape.
43
+ - `skill-modularity.md` — hub layout and compose-by-name.
44
+
45
+ Read them fresh each run; they carry the rules you author to.
46
+
47
+ ## Standards every file meets
48
+
49
+ 1. SKILL.md follows the hub layout: principle, gotchas, when-applies, process, file index, folder map.
50
+ 2. The frontmatter description is the exact trigger-catalog string — a `capability stem` plus a `Triggers:` list.
51
+ 3. A sub-skills table appears whenever the composition plan lists peer skills.
52
+ 4. Companion files carry the detail: reference docs, workflow steps, templates, scripts.
53
+ 5. Every deterministic-inventory row gets a script or workflow; SKILL.md points to it and handles its exit codes.
54
+ 6. Skill scripts follow CODE_RULES: a `*_constants/` package, full type hints, specific errors, and a paired test.
55
+ 7. Every file stays under 500 lines, with a table of contents on any file over 100 lines.
56
+ 8. A file index lists every file and its purpose.
57
+ 9. Steps owned by a named sub-skill stay in that sub-skill; SKILL.md invokes it by name.
58
+
59
+ ## Deterministic elements
60
+
61
+ Honor `deterministic-elements.md` in every skill you author. A step is deterministic when the same inputs give the same outputs, success is machine-checkable, and a human could write it as a pure function or a fixed sequence. Route each kind to its home:
62
+
63
+ - Validators, transforms, detection, and mechanical sequences ship as `scripts/` or `workflow/*.mjs`, each with tests and a `*_constants/` package.
64
+ - Verbatim templates ship in `templates/`.
65
+ - Long fixed catalogs Claude reads word-for-word ship in `reference/`.
66
+ - Ordered work the future skill-runner must finish ships as a task-seed list: plain bullets or numbered lines in a reference file, plus an instruction in SKILL.md to register one session task per item through the host task tool (`TaskCreate`, else `TodoWrite`). Judgment, routing, and refusal stay in markdown prose.
67
+
68
+ Keep required step lists on the task list. Where a host task tool is present, the authored SKILL.md registers each item through it; where the host offers only prose, the SKILL.md states that limit and stops.
69
+
70
+ ## Before you return
71
+
72
+ Work your before-return checks as one pass over the file set:
73
+
74
+ - The SKILL.md hub layout is present and complete.
75
+ - The description reads as a trigger catalog.
76
+ - Every deterministic row carries its script and paired test.
77
+ - Files stay under the line caps, with a table of contents wherever the cap calls for one.
78
+ - The file index is present, and the sub-skills table is present whenever the skill composes others.
79
+
80
+ On a direct call, this pass is your whole quality gate — work it in full. When skill-builder is the caller, it runs its 38-point audit next; hand it a coherent, complete file set so that audit starts clean.
81
+
82
+ ## Return
83
+
84
+ Reply with a file manifest kept thin: each path you created or edited on its own line, then one line naming what the caller picks up next, such as the audit or the tests to run. Keep the reply light so the caller acts on it directly.
@@ -1,41 +1,141 @@
1
- # Category O — Docstring / fixture-prose vs implementation drift
2
-
3
- **What this category audits:** module docstrings, fixture docstrings, helper-function docstrings, and free-form narrative prose inside docstrings (step ordering, named sentinels, predicate-breadth claims, list-of-responsibilities sentences) whose claims diverge from the implementation they describe. The gate-time `check_docstring_args_match_signature` validator covers only the `Args:` section parameter names; every other docstring claim — module-level `"This module detects X"`, fixture-level `"readability is disabled for these tests"`, predicate-level `"resolves to shared temp only"`, step-ordering narrative `"strip ceremony, then drop blockquotes"` — drifts past it.
4
-
5
- **Examples of Category O findings:**
6
- - A module docstring says the module recovers PR numbers, but a refactor split that logic into a sibling module.
7
- - A fixture docstring asserts a global disable invariant that sibling tests in the same file explicitly violate.
8
- - A predicate name and docstring promise a narrow check, but the body also matches a broader input class (HOME/TMP env vars when the docstring says shared-temp only).
9
- - A docstring lists three responsibilities; only one is implemented, the other two live elsewhere.
10
- - A docstring describes step ordering `A then B`; the body does `B then A`.
11
- - A docstring references a sentinel marker (`# pragma: no-tdd-gate`) or filename shape (`test_code-rules-enforcer.py`) that the module body and the repo's naming convention do not use.
12
-
13
- **Companion reference:** see `../source-material-section-types.md`.
14
-
15
- ---
16
-
17
- ## Sub-bucket decomposition (Category O)
18
-
19
- Decomposition is by the **kind of docstring claim** that needs to be cross-checked against the implementation.
20
-
21
- | ID | Axis name | Concrete checks |
22
- |---|---|---|
23
- | O1 | Module-level responsibility verbs | A module docstring uses verbs (`detects`, `validates`, `enforces`, `recovers`, `parses`, `routes`) every claimed responsibility is implemented by an exported symbol in the same module. Symbols absent from the module body should not appear as this module's responsibilities. |
24
- | O2 | Fixture docstring vs sibling-test behavior | An autouse / module-scope fixture docstring asserts an invariant (`readability is disabled`, `network is mocked`, `tmp_path is empty`). No sibling test in the same module explicitly opts out of the invariant. |
25
- | O3 | Predicate-name and -docstring vs body breadth | A boolean helper's name and docstring promise a narrow predicate. Walk the body's branches: every branch's `return True` path is consistent with the promised name. Bodies that accept inputs broader than the name (`_dir_value_resolves_to_shared_temp` also accepting HOME/TMP env-derived paths) are O3 findings. |
26
- | O4 | Step-ordering narrative | A docstring describes processing as `A then B then C`. Walk the body and confirm the call order matches. Mismatched order is an O4 finding regardless of whether the final output is the same. A docstring step enumeration that names the body's linear steps but omits a corrective workflow step the body guards inside an `if`/`elif` branch (`if not await cancel_and_reinitiate_update(...): return`) is also an O4 finding: the reader trusts the step list to be complete and misses the conditional path. The branch-guarded-dispatch shape of this drift — a docstring that names two or more linear-step callees while the body guards a two-or-more-token dispatch callee inside a branch whose name the prose never spells out — is gated deterministically at Write/Edit time by `check_docstring_step_enumeration_dispatch_coverage` (`packages/claude-dev-env/hooks/blocking/code_rules_docstrings.py`), so the audit lane focuses on the step-ordering shapes the gate cannot match (re-ordered steps, plain unguarded steps the prose omits). |
27
- | O5 | Named-sentinel / filename references | A docstring names a sentinel marker, environment variable, filename, or magic string. Confirm the named token actually exists in the module body or in the repo's naming convention. |
28
- | O6 | Free-form `Args:`-adjacent claims | A docstring's `Returns:` / `Raises:` / `Note:` / `Example:` sections make claims (`returns shared-temp only`, `raises ValueError on missing key`). Verify each claim against the body. When a docstring enumerates the inputs a body counts (a "field counts as read when ..." list, a list of conditions treated as a match, a list of cases the body skips), list every union member and every suppressor the body applies (`read_names = a | b | c`, each early-return guard) and confirm each appears in the prose enumeration. A union member or suppressor the body applies but the prose omits is an O6 finding. When a docstring sentence excludes a named category of input from what the function flags (`X are not dispatch steps`, `Y is not a match`), confirm the axis the prose excludes on is the axis the body's branch condition actually keys on. A body that flags a call when it sits inside an `If.test` guard, paired with prose that excludes by the call's receiver shape (`method-on-local calls inside a branch are not dispatch steps`), is an O6 finding: a guarded method-on-local call is flagged even though the prose lists it as excluded — the exclusion is keyed to the wrong axis. The single-condition shared-fallback shape of this drift — a summary that scopes a fallback call to one condition while the body routes to that same call from two or more early-return guards — is gated deterministically at Write/Edit time by `check_docstring_fallback_branch_coverage`, so the audit lane focuses on the O6 shapes the gate cannot match. The exception-guard shape of this drift — a docstring that promises a malformed payload `resolves to None` while a payload subscript (`payload["key"]`, `float(payload["key"])`) sits outside the try/except whose handler returns None, so a present-but-malformed payload raises rather than resolving to None — is gated deterministically at Write/Edit time by `check_docstring_unguarded_malformed_payload_claim`, so the audit lane focuses on the wider Raises/None-on-failure claims the gate cannot match. A `Returns:` that names the mechanism, tool, or output format the function produces (`instructing a StructuredOutput summary`, `returns a YAML document`, `emits a JSON object`) matches the artifact the body actually builds: a prompt body that asks the agent to "Return strictly a JSON object" while the docstring claims it "instruct[s] a StructuredOutput" summary is an O6 finding, because the named tool appears nowhere in the emitted text. See `../../rules/docstring-prose-matches-implementation.md`. |
29
- | O7 | Module-doc-vs-split-module after refactor | When a refactor moves a responsibility to a sibling module, the originating module's docstring and the receiving module's docstring both describe the home of that responsibility. A module docstring should describe only the responsibilities it owns. |
30
- | O8 | Companion-doc ordering/content vs producer | When a PR changes a producer function's ordering or union, read that skill's companion `SKILL.md` and sibling `.md` docs for any sentence naming the same produced artifact (a file path, a JSON key, a named list). A doc sentence that claims the artifact is `sorted` / `alphabetical` / `in sorted order`, or holds `just the at-risk names` / `only the current set`, while the producer merges stored names with new names and appends — preserving file order, not re-sorting the union — is an O8 finding on both counts (wrong order claim, hidden merged-in entries). The finding stands even when the PR diff never touched the `.md` file, because the behavior change orphaned the doc claim. See `../../rules/docstring-prose-matches-implementation.md`. |
31
- | O9 | Python docstring plainness for a general developer | A changed module / class / public-function docstring's narrative prose — the summary and description before the first `Args:` / `Returns:` / `Raises:` / `Yields:` section — reads plainly and paints a concrete scene a general developer follows on first read. Flag a narrative that stacks abstract machinery nouns into a wall (`the SIGINT install/restore/installability check, the atexit terminal-record registration, and the interrupted-run finalizer`), that defines a thing by what it is not (`the non-promoter-specific machinery`), or that runs one sentence long while joining clauses with an em-dash or a semicolon. The diagram-first shape carries this best: a summary line, then a `::` example block or a doctest that shows a concrete input and its marked outcome, then a couple of short prose lines. The deterministic run-on mark is gated at Write/Edit time by `check_docstring_runon_sentence` in `code_rules_docstrings.py`, and a narrative that runs more than six prose lines with no such block is gated by `check_docstring_prose_wall_without_illustration` in the same module, so this lane carries the judgment the gates cannot: whether a stranger to the code pictures the moment, the input, and the outcome after one read, and whether the diagram truly illustrates — a real input, a marked outcome, an `ok:` / `flag:` contrast a reader learns from. See `../../rules/plain-illustrative-docstrings.md`. |
32
-
33
- ---
34
-
35
- ## Sample prompt
36
-
37
- The reusable Variant C template for Category O is in [`../prompts/category-o-docstring-vs-impl-drift.md`](../prompts/category-o-docstring-vs-impl-drift.md). Inline every changed module's docstring (module-level + every helper-function docstring whose function body was touched + every fixture docstring) alongside the symbols defined in the same module under `## Source material`.
38
-
39
- ## Why Category O matters as its own bucket
40
-
41
- Signature-shaped claims parameter names, return types, exceptions in the `Raises:` block — have a gate-time validator (`check_docstring_args_match_signature`) and signature-oriented audit categories to catch them. Free-form narrative prose in docstrings is the other half of the docstring contract: the part that tells a reader what the module is for, what the fixture does, what the predicate means. When that prose drifts from the body, the gate cannot catch it because there is no signature to compare against. Category O forces the audit teammate to list docstring claims and verify each against the body, the same way signature claims are verified against the body.
1
+ # Category O — Docstring / fixture-prose vs implementation drift
2
+
3
+ **What this category audits:** module docstrings, fixture docstrings, helper-function docstrings, and free-form narrative prose inside docstrings (step ordering, named sentinels, predicate-breadth claims, list-of-responsibilities sentences) whose claims diverge from the implementation they describe. The gate-time `check_docstring_args_match_signature` validator covers only the `Args:` section parameter names; every other docstring claim — module-level `"This module detects X"`, fixture-level `"readability is disabled for these tests"`, predicate-level `"resolves to shared temp only"`, step-ordering narrative `"strip ceremony, then drop blockquotes"` — drifts past it.
4
+
5
+ **Examples of Category O findings:**
6
+ - A module docstring says the module recovers PR numbers, but a refactor split that logic into a sibling module.
7
+ - A fixture docstring asserts a global disable invariant that sibling tests in the same file explicitly violate.
8
+ - A predicate name and docstring promise a narrow check, but the body also matches a broader input class (HOME/TMP env vars when the docstring says shared-temp only).
9
+ - A docstring lists three responsibilities; only one is implemented, the other two live elsewhere.
10
+ - A docstring describes step ordering `A then B`; the body does `B then A`.
11
+ - A docstring references a sentinel marker (`# pragma: no-tdd-gate`) or filename shape (`test_code-rules-enforcer.py`) that the module body and the repo's naming convention do not use.
12
+
13
+ ## Division of labor
14
+
15
+ This file is the **single thick source** for Category O judgment (sub-buckets O1–O9, the write-time gate inventory, free-form checklists, and worked examples).
16
+
17
+ | Surface | Role |
18
+ |---|---|
19
+ | `packages/claude-dev-env/rules/docstring-prose-matches-implementation.md` | Always-on write-time policy: the policy sentence, a compact checklist a writer applies at Write/Edit, and a pointer here. |
20
+ | **This rubric** | On-demand thick home. The code-quality agent loads it per category. Holds every judgment standard, gate inventory, and worked example. |
21
+ | `packages/claude-dev-env/audit-rubrics/prompts/category-o-docstring-vs-impl-drift.md` | Variant C audit template: source-material slots, forced-exhaustion protocol, adversarial probes, cross-bucket questions, output shape, and a PR worked example. Points here for the judgment standard. |
22
+
23
+ Plainness for a general developer (diagram-first shape) also lives under O9; the write-time companion rule for that slice is `packages/claude-dev-env/rules/plain-illustrative-docstrings.md`.
24
+
25
+ **Companion reference:** see `../source-material-section-types.md`.
26
+
27
+ ---
28
+
29
+ ## Sub-bucket decomposition (Category O)
30
+
31
+ Decomposition is by the **kind of docstring claim** that needs to be cross-checked against the implementation.
32
+
33
+ | ID | Axis name | Concrete checks |
34
+ |---|---|---|
35
+ | O1 | Module-level responsibility verbs | A module docstring uses verbs (`detects`, `validates`, `enforces`, `recovers`, `parses`, `routes`) — every claimed responsibility is implemented by an exported symbol in the same module. Symbols absent from the module body should not appear as this module's responsibilities. A module whose one-line docstring scopes its contents to user-facing text (`User-facing strings: CLI flag names, help text, and log messages`) also names every category of constant the body holds. When the body also defines serialization field keys (`JSONL_FIELD_*`), run-metadata schema keys (`RUN_METADATA_CLI_ARG_KEY_*`), or runtime config (`STDOUT_ENCODING`, `MAIN_LOGGING_FORMAT_STRING`), the strings-only summary under-describes the module. Broaden the summary to name the data-schema keys and runtime config. The `check_module_docstring_scope_omits_data_schema_constants` gate blocks this drift at Write/Edit time when the summary claims a user-facing-text scope and names no data-schema or runtime-config category. |
36
+ | O2 | Fixture docstring vs sibling-test behavior | An autouse / module-scope fixture docstring asserts an invariant (`readability is disabled`, `network is mocked`, `tmp_path is empty`). No sibling test in the same module explicitly opts out of the invariant. |
37
+ | O3 | Predicate-name and -docstring vs body breadth | A boolean helper's name and docstring promise a narrow predicate. Walk the body's branches: every branch's `return True` path is consistent with the promised name. Bodies that accept inputs broader than the name (`_dir_value_resolves_to_shared_temp` also accepting HOME/TMP env-derived paths) are O3 findings. |
38
+ | O4 | Step-ordering narrative | A docstring describes processing as `A then B then C`. Walk the body and confirm the call order matches. Mismatched order is an O4 finding regardless of whether the final output is the same. A docstring step enumeration that names the body's linear steps but omits a corrective workflow step the body guards inside an `if`/`elif` branch (`if not await cancel_and_reinitiate_update(...): return`) is also an O4 finding: the reader trusts the step list to be complete and misses the conditional path. The branch-guarded-dispatch shape of this drift — a docstring that names two or more linear-step callees while the body guards a two-or-more-token dispatch callee inside a branch whose name the prose never spells out — is gated deterministically at Write/Edit time by `check_docstring_step_enumeration_dispatch_coverage` (`packages/claude-dev-env/hooks/blocking/code_rules_docstrings.py`), so the audit lane focuses on the step-ordering shapes the gate cannot match (re-ordered steps, plain unguarded steps the prose omits). |
39
+ | O5 | Named-sentinel / filename references | A docstring names a sentinel marker, environment variable, filename, or magic string. Confirm the named token actually exists in the module body or in the repo's naming convention. |
40
+ | O6 | Free-form `Args:`-adjacent claims | A docstring's `Returns:` / `Raises:` / `Note:` / `Example:` sections make claims (`returns shared-temp only`, `raises ValueError on missing key`). Verify each claim against the body. When a docstring enumerates the inputs a body counts (a "field counts as read when ..." list, a list of conditions treated as a match, a list of cases the body skips), list every union member and every suppressor the body applies (`read_names = a \| b \| c`, each early-return guard) and confirm each appears in the prose enumeration. A union member or suppressor the body applies but the prose omits is an O6 finding. When a docstring sentence excludes a named category of input from what the function flags (`X are not dispatch steps`, `Y is not a match`), confirm the axis the prose excludes on is the axis the body's branch condition actually keys on. A body that flags a call when it sits inside an `If.test` guard, paired with prose that excludes by the call's receiver shape (`method-on-local calls inside a branch are not dispatch steps`), is an O6 finding: a guarded method-on-local call is flagged even though the prose lists it as excluded — the exclusion is keyed to the wrong axis. A thin delegating method whose docstring names its actions and points at the home of the real body lists the same actions the delegated function's own summary lists; when an edit moves one action out of the delegated body, the same edit rewords both summaries (`check_docstring_delegation_summary_enumeration_drift`). A conditional bullet in the delegated prose also names every exception the body honors — that conditional-completeness slice stays a judgment call for this lane. A `Returns:` that names the mechanism, tool, or output format the function produces (`instructing a StructuredOutput summary`, `returns a YAML document`, `emits a JSON object`) matches the artifact the body actually builds. A dataclass or `TypedDict` field documented in the class `Attributes:` block states what the field means for one record; when the code sets that field the same way for every record (a run-mode flag such as `is_dry_run = not is_execute`), the description states the run-mode meaning, not a per-record outcome (`check_docstring_field_runmode_outcome` covers the single-file shape). Many deterministic O6 shapes are gated at Write/Edit time — see **Write-time gate inventory** below — so the audit lane focuses on the free-form shapes the gates cannot match. |
41
+ | O7 | Module-doc-vs-split-module after refactor | When a refactor moves a responsibility to a sibling module, the originating module's docstring and the receiving module's docstring both describe the home of that responsibility. A module docstring should describe only the responsibilities it owns. |
42
+ | O8 | Companion-doc ordering/content vs producer | When a PR changes a producer function's ordering or union, read that skill's companion `SKILL.md` and sibling `.md` docs for any sentence naming the same produced artifact (a file path, a JSON key, a named list). A doc sentence that claims the artifact is `sorted` / `alphabetical` / `in sorted order`, or holds `just the at-risk names` / `only the current set`, while the producer merges stored names with new names and appends — preserving file order, not re-sorting the union — is an O8 finding on both counts (wrong order claim, hidden merged-in entries). The finding stands even when the PR diff never touched the `.md` file, because the behavior change orphaned the doc claim. A producer docstring asserting that no consumer reads its output yet (`producer-only artifact`, `no submission-run consumer reads it yet`) is the deterministic slice of this companion-doc producer/consumer drift (`check_docstring_no_consumer_claim`). |
43
+ | O9 | Python docstring plainness for a general developer | A changed module / class / public-function docstring's narrative prose — the summary and description before the first `Args:` / `Returns:` / `Raises:` / `Yields:` section — reads plainly and paints a concrete scene a general developer follows on first read. Flag a narrative that stacks abstract machinery nouns into a wall (`the SIGINT install/restore/installability check, the atexit terminal-record registration, and the interrupted-run finalizer`), that defines a thing by what it is not (`the non-promoter-specific machinery`), or that runs one sentence long while joining clauses with an em-dash or a semicolon. The diagram-first shape carries this best: a summary line, then a `::` example block or a doctest that shows a concrete input and its marked outcome, then a couple of short prose lines. The deterministic run-on mark is gated at Write/Edit time by `check_docstring_runon_sentence` in `code_rules_docstrings.py`, and a narrative that runs more than six prose lines with no such block is gated by `check_docstring_prose_wall_without_illustration` in the same module, so this lane carries the judgment the gates cannot: whether a stranger to the code pictures the moment, the input, and the outcome after one read, and whether the diagram truly illustrates — a real input, a marked outcome, an `ok:` / `flag:` contrast a reader learns from. See `../../rules/plain-illustrative-docstrings.md`. |
44
+
45
+ ---
46
+
47
+ ## Write-time gate inventory
48
+
49
+ Deterministic slices of Category O that fire at Write/Edit. The free-form rest stays judgment (this rubric + the audit prompt).
50
+
51
+ ### Python — `packages/claude-dev-env/hooks/blocking/code_rules_docstrings.py`
52
+
53
+ | Gate | Drift it blocks |
54
+ |---|---|
55
+ | `check_docstring_args_match_signature` | `Args:` section parameter names vs the signature. |
56
+ | `check_docstring_delegation_summary_enumeration_drift` | Thin wrapper summary enumerates actions the same-named sibling summary omits (both save directions). |
57
+ | `check_docstring_names_absent_type_checking_gate` | Docstring names a `TYPE_CHECKING` gate or `type-checking-gate` helper family while no identifier in the module carries the `type_checking` marker. |
58
+ | `check_docstring_length_constant_superlative_vs_exact_gate` | Module docstring describes an integer `*_LENGTH` constant with a superlative or range word while every consumer compares `len(...)` with `==`/`!=` (exact-length gate). Scans the constant module's package tree. |
59
+ | `check_docstring_fallback_branch_coverage` | Summary scopes a fallback to one condition while the body routes to that fallback from two or more early-return guards. |
60
+ | `check_class_docstring_names_public_methods` | Class docstring is a single summary line while the class exposes two or more public methods the summary never names. |
61
+ | `check_docstring_no_consumer_claim` | Producer docstring asserts no consumer reads its output yet. |
62
+ | `check_docstring_returns_plural_cardinality` | `Returns:` names a dict-key prefix family with a plural noun while the returned dict holds exactly one key in that family. |
63
+ | `check_docstring_args_single_line_scope_vs_span` | `Args:` scopes a finding to one named line while the body scopes through a `range(...)` span-intersection. |
64
+ | `check_docstring_cardinal_count_matches_constant_family` | Docstring states a cardinal count of an outcome family and lists members, while the module references more members of the same `UPPER_SNAKE` family than the count names. Runs on test modules as well as production. |
65
+ | `check_docstring_raises_unraisable_largezipfile` | `Raises:` names `zipfile.LargeZipFile` while the writer opens with `allowZip64` at its default of True. |
66
+ | `check_docstring_no_network_claim_with_metadata_access` | Docstring promises a path returns without touching the network while the body calls path-metadata methods (`is_file`, `is_dir`, `exists`, `stat`, `lstat`). |
67
+ | `check_docstring_step_enumeration_dispatch_coverage` | Step-enumeration docstring omits a two-or-more-token dispatch step the body guards inside a branch. |
68
+ | `check_docstring_unguarded_malformed_payload_claim` | Docstring promises a malformed payload resolves to None while a payload subscript sits outside the try/except whose handler returns None. |
69
+ | `check_docstring_field_runmode_outcome` | `Attributes:` entry for a run-mode flag field (name carrying `dry_run`) whose description carries a per-record write-outcome phrase and no run-mode phrase. |
70
+ | `check_module_docstring_scope_omits_data_schema_constants` | Module summary claims user-facing-text scope while the body also defines data-schema or runtime-config constants. |
71
+ | `check_module_docstring_names_public_checks` | One-line check-registry module docstring omits a public `check_*` function the module dispatches. |
72
+ | `check_docstring_tuple_enumeration_match` | Docstring enumerates inline-code tokens that drift from the literal string tuple the body reads (a listed token the tuple lacks, or a tuple member the prose omits). |
73
+ | `check_docstring_punctuation_mark_enumeration_coverage` | Docstring names some marks of a punctuation-glyph tuple by their English names but omits one the tuple holds. |
74
+ | `check_docstring_no_inline_literal_claim` | Constants-module docstring asserts no literals appear inline in a companion file. |
75
+ | `check_docstring_names_undefined_constant` | Docstring names an `UPPER_SNAKE` constant identifier nothing in the module backs. |
76
+ | `check_docstring_runon_sentence` | Narrative run-on mark (O9 backstop). |
77
+ | `check_docstring_prose_wall_without_illustration` | Narrative longer than six prose lines with no `::` / doctest illustration (O9 backstop). |
78
+
79
+ ### JavaScript / `.mjs` — `packages/claude-dev-env/hooks/blocking/code_rules_imports_logging.py`
80
+
81
+ These are the `.mjs` slice of the same Category O standard. The Python AST docstring gates never inspect JavaScript source.
82
+
83
+ | Gate | Drift it blocks |
84
+ |---|---|
85
+ | `check_js_resume_task_enumeration_coverage` | A `spawn<Role>Agent` JSDoc enumerates sibling `resume<Role>Agent` resume tasks and omits a `task === '<name>'` branch the resume body dispatches on. |
86
+ | `check_js_returns_object_schemaless_branch` | `@returns {Promise<object>}` JSDoc whose body returns the same agent-spawn helper both with a `schema` options object and without one (schema-less branch resolves to a transcript string). |
87
+ | `check_js_sibling_return_object_key_drift` | A `return { ... }` object literal whose key set misses exactly one key of a sibling return in the same function or module scope. Discriminated-union variants and two-or-more-key exit shapes are left alone. |
88
+ | `check_js_bare_flag_return_directive` | A `return <name>: true`/`false` prose directive anywhere in the file that repeats a status flag a stated full-result contract rules out (no proximity or ordering check between the two). |
89
+
90
+ ---
91
+
92
+ ## Free-form judgment checklist (write time and audit)
93
+
94
+ Read the body and the docstring side by side. Apply each check that matches the prose. When the body changes the set of behaviors it applies, the same edit updates the prose enumeration.
95
+
96
+ - **Read-source / match-source unions.** A body that computes `read_names = a | b | c` (or any union of "what counts") names each union member in the prose enumeration.
97
+ - **Suppressor / skip lists.** A body with several early returns that suppress the check names each suppressor in the prose.
98
+ - **Shared fallback routes.** A summary that scopes a fallback call to one condition names every condition that reaches that call. Gated form: `check_docstring_fallback_branch_coverage`.
99
+ - **Step order.** A docstring that says `A then B then C` matches the call order in the body. A step enumeration that names the body's linear steps also names every corrective step the body guards inside an `if`/`elif` branch. Gated form: `check_docstring_step_enumeration_dispatch_coverage`.
100
+ - **Delegation pointer summaries.** A thin delegating method whose docstring names its actions and points at the home of the real body lists the same actions the delegated function's own summary lists. Gated form: `check_docstring_delegation_summary_enumeration_drift`. A conditional bullet in the delegated prose also names every exception the body honors — judgment for this lane.
101
+ - **JS/`.mjs` resume-task, `@returns` object, sibling return keys, bare-flag directives.** See the JavaScript gate inventory above.
102
+ - **Returns-clause cardinality.** A `Returns:` clause that names a dict-key prefix family with a plural noun matches the count of keys in that family in the returned dict literal. Gated form: `check_docstring_returns_plural_cardinality`.
103
+ - **Length-constant superlative vs exact gate.** A module docstring that describes an integer `*_LENGTH` constant with a superlative or range word matches how the code consumes the constant. Gated form: `check_docstring_length_constant_superlative_vs_exact_gate`.
104
+ - **Args single-line scope vs span body.** An `Args:` entry that scopes a finding to one named line matches the line breadth the body scopes by. Gated form: `check_docstring_args_single_line_scope_vs_span`.
105
+ - **Cardinal-count enumerations.** A docstring that states a count of an outcome family and lists those members names every member of that family the module references. Gated form: `check_docstring_cardinal_count_matches_constant_family`.
106
+ - **Raises-clause reachability for `LargeZipFile`.** A `Raises:` clause that names `zipfile.LargeZipFile` matches a writer the body opens with ZIP64 forbidden. Gated form: `check_docstring_raises_unraisable_largezipfile`.
107
+ - **Module summary scope versus data-schema constants.** A module whose one-line docstring scopes its contents to user-facing text names every category of constant the body holds. Gated form: `check_module_docstring_scope_omits_data_schema_constants`.
108
+ - **Field meaning: run mode versus per record.** A dataclass or `TypedDict` field documented in the class `Attributes:` block states what the field means for one record. When the code sets that field the same way for every record, the description states the run-mode meaning. Gated form: `check_docstring_field_runmode_outcome` (single-file shape); assignment in another module stays judgment.
109
+ - **Predicate breadth.** A boolean helper whose prose promises a narrow check accepts only the inputs the prose names — no broader input class the name and prose do not mention.
110
+ - **Exclusion-clause distinguisher.** A docstring sentence that says a named category of input "are not" / "is not" the thing the function flags keys the exclusion to the same axis the body's classification keys on. Read the body's actual branch condition, then state the exclusion on that same axis.
111
+ - **Companion-doc ordering and content claims.** A `SKILL.md` (or sibling `.md`) sentence that names a produced artifact and claims its order or its content matches the producer function's docstring and body for that same artifact. The two move together in one commit, even when the producer edit does not touch the `.md` file.
112
+ - **TYPE_CHECKING gate claim vs code.** A docstring that names a `TYPE_CHECKING` gate-detection step matches a module whose code handles TYPE_CHECKING. Gated form: `check_docstring_names_absent_type_checking_gate`.
113
+
114
+ ---
115
+
116
+ ## Worked example (union enumeration)
117
+
118
+ A `@dataclass` dead-field check builds its set of "field counts as read" sources by union:
119
+
120
+ ```python
121
+ read_names = (
122
+ attribute_read_names
123
+ | dynamic_literal_names
124
+ | _match_pattern_attribute_names(tree)
125
+ | _exported_names(tree)
126
+ )
127
+ ```
128
+
129
+ A docstring that enumerates "attribute read, augmented-assignment target, class-pattern keyword, literal `getattr`/`attrgetter`" but omits the `__all__` source (`_exported_names`) is drifted: a field whose name appears in `__all__` is treated as read, and the prose hides that. The fix adds the missing source to the enumeration so the list matches the union.
130
+
131
+ ---
132
+
133
+ ## Sample prompt
134
+
135
+ The reusable Variant C template for Category O is in [`../prompts/category-o-docstring-vs-impl-drift.md`](../prompts/category-o-docstring-vs-impl-drift.md). Inline every changed module's docstring (module-level + every helper-function docstring whose function body was touched + every fixture docstring) alongside the symbols defined in the same module under `## Source material`.
136
+
137
+ ## Why Category O matters as its own bucket
138
+
139
+ Signature-shaped claims — parameter names, return types, exceptions in the `Raises:` block — have a gate-time validator (`check_docstring_args_match_signature`) and signature-oriented audit categories to catch them. Free-form narrative prose in docstrings is the other half of the docstring contract: the part that tells a reader what the module is for, what the fixture does, what the predicate means. When that prose drifts from the body, the gate cannot catch it because there is no signature to compare against. Category O forces the audit teammate to list docstring claims and verify each against the body, the same way signature claims are verified against the body.
140
+
141
+ A docstring enumeration earns its place by being trustworthy. A complete list lets a reader reason about the function without scanning the body; a list missing one item is worse than no list, because it asserts completeness it does not have.
@@ -1,5 +1,21 @@
1
+ # Category O audit prompt (Variant C)
2
+
3
+ **Judgment standard (thick source):** `packages/claude-dev-env/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md`
4
+
5
+ This file is the audit **template** only: source-material slots, forced-exhaustion protocol, adversarial probes, cross-bucket questions, output shape, and a worked example. For every sub-bucket's judgment standard, gate inventory, and free-form checklist, read the thick rubric above. Do not treat this prompt as a second full copy of the standard.
6
+
7
+ ## Division of labor
8
+
9
+ | Surface | Role |
10
+ |---|---|
11
+ | Category O rubric | Single thick judgment source (O1–O9, gates, checklists, examples). |
12
+ | **This prompt** | Variant C protocol shell that points at the rubric for judgment. |
13
+ | `packages/claude-dev-env/rules/docstring-prose-matches-implementation.md` | Always-on write-time policy + compact checklist. |
14
+
1
15
  Audit [REPO/ARTIFACT] [TARGET_ID] for **Category O only** (docstring / fixture-prose vs implementation drift). Skip A–N, P. Sub-bucket forced-exhaustion mode: Category O is decomposed into 9 sub-buckets below. Each sub-bucket REQUIRES at least one Shape A finding OR exactly one Shape B proof-of-absence with **at least 3 adversarial probes** specific to that sub-bucket. A sub-bucket returning neither is a protocol gap.
2
16
 
17
+ Apply each sub-bucket's **judgment standard** from the thick rubric. The bullets under each sub-bucket here are protocol probes only.
18
+
3
19
  [ARTIFACT METADATA — include every changed module's docstring AND the exported symbols of that module so the audit can compare claim vs body]
4
20
 
5
21
  - Title / one-line summary: [TITLE]
@@ -20,39 +36,39 @@ ID prefix: `find`.
20
36
  ## Sub-buckets (each requires Shape A finding OR Shape B with ≥3 adversarial probes)
21
37
 
22
38
  **O1. Module-level responsibility verbs** ⭐ canonical O case
23
- - For every changed module, list the verbs the docstring uses (`detects`, `validates`, `enforces`, `recovers`, `parses`, `routes`). For each verb, name the exported symbol that delivers that responsibility. Verbs without a matching exported symbol are O1 findings.
24
- - Adversarial probes: (a) grep for the verb's noun-form in sibling modules — did a refactor move the responsibility out; (b) inspect the module's `__all__` (if present) — does every claimed responsibility appear; (c) check git log for recent splits — does the docstring still describe the pre-split scope.
39
+ - Judgment: thick rubric O1 (responsibility verbs + user-facing-text vs data-schema scope).
40
+ - Adversarial probes: (a) grep for the verb's noun-form in sibling modules — did a refactor place the responsibility elsewhere; (b) inspect the module's `__all__` (if present) — does every claimed responsibility appear; (c) check git log for recent splits — does the docstring still describe the pre-split scope.
25
41
 
26
42
  **O2. Fixture docstring vs sibling-test behavior**
27
- - For every changed fixture (especially `autouse=True` or module-scope), parse the fixture's docstring claims. For each claim, walk every test function in the same module — does any test explicitly opt out of the claimed invariant via a different fixture, `monkeypatch.setattr`, or environment override?
43
+ - Judgment: thick rubric O2.
28
44
  - Adversarial probes: (a) grep for the fixture's invariant-setting call in test bodies — does any test re-call it with a different argument; (b) check for `pytest.mark.parametrize` arguments that reach a code path the fixture claim says is disabled; (c) check for explicit teardown / reset calls inside tests that contradict the fixture's blanket scope.
29
45
 
30
46
  **O3. Predicate-name and -docstring vs body breadth**
31
- - For every changed boolean helper, compare the helper's name and docstring to the body's `return True` branches. Every branch's True path must be consistent with the promised name.
47
+ - Judgment: thick rubric O3.
32
48
  - Adversarial probes: (a) walk each `return True` branch and ask whether the input that reached it satisfies the name's promise; (b) construct an input class outside the named promise that still returns True — that is an O3 finding; (c) check the name against neighboring helpers — is one of them the better home for the broader case.
33
49
 
34
50
  **O4. Step-ordering narrative**
35
- - For every changed helper whose docstring describes processing as `step A then step B then step C`, trace the body and confirm the call order matches.
51
+ - Judgment: thick rubric O4 (includes branch-guarded dispatch; gated form `check_docstring_step_enumeration_dispatch_coverage`).
36
52
  - Adversarial probes: (a) read the body strictly top-to-bottom and label each call A/B/C against the docstring's named steps; (b) check for early returns that reorder visible steps; (c) check for `try/finally` blocks where the finally clause is itself one of the named steps and runs out of declared order.
37
53
 
38
54
  **O5. Named-sentinel / filename references**
39
- - For every docstring mention of a sentinel marker (`# pragma: ...`), environment variable name, filename, or magic string, grep the module body and the broader repo for the named token. Tokens not present anywhere are O5 findings.
55
+ - Judgment: thick rubric O5.
40
56
  - Adversarial probes: (a) grep the exact sentinel string in this module and sibling modules; (b) grep the named filename against the repo's naming convention (underscore vs hyphen); (c) check for case-sensitivity mismatches between the docstring and the body.
41
57
 
42
58
  **O6. Free-form `Args:`-adjacent claims**
43
- - For every docstring `Returns:` / `Raises:` / `Note:` / `Example:` section, extract each claim sentence. Verify each against the body. (The gate-time validator only checks `Args:` parameter names, not these adjacent sections.)
59
+ - Judgment: thick rubric O6 (unions, suppressors, exclusion axis, delegation summaries, Returns/Raises/Note claims, run-mode field meaning). Gate inventory and free-form checklist live in the rubric.
44
60
  - Adversarial probes: (a) check `Returns:` claims against every `return` statement in the body — is the documented return shape the actual return shape; (b) check `Raises:` claims against every `raise` and propagating callee — is every documented raise reachable; (c) check `Example:` snippets — does the snippet actually compile against the signature.
45
61
 
46
62
  **O7. Module-doc-vs-split-module after refactor**
47
- - When the diff includes a module split (one file becomes two), verify both modules' docstrings describe the responsibility each one actually owns after the split.
48
- - Adversarial probes: (a) for each module in the split, list its exported symbols and compare to the docstring's claimed responsibilities; (b) grep the responsibility's verb against the originating module — does the originating docstring still claim what moved; (c) check for cross-module imports that reveal which file hosts each responsibility.
63
+ - Judgment: thick rubric O7.
64
+ - Adversarial probes: (a) for each module in the split, list its exported symbols and compare to the docstring's claimed responsibilities; (b) grep the responsibility's verb against the originating module — does the originating docstring still claim what left; (c) check for cross-module imports that reveal which file hosts each responsibility.
49
65
 
50
66
  **O8. Companion-doc ordering/content vs producer**
51
- - When the diff changes a producer function's ordering or union, read that skill's companion `SKILL.md` and sibling `.md` docs for any sentence naming the same produced artifact (a file path, a JSON key, a named list). A doc sentence that claims the artifact is `sorted` / `alphabetical` / `in sorted order`, or holds `just the at-risk names` / `only the current set`, while the producer merges stored names with new names and appends — preserving file order, not re-sorting the union — is an O8 finding on both counts (wrong order claim, hidden merged-in entries). The finding stands even when the diff never touched the `.md` file, because the behavior change orphaned the doc claim.
67
+ - Judgment: thick rubric O8 (order/content claims vs producer; `check_docstring_no_consumer_claim` for the producer-only assertion slice).
52
68
  - Adversarial probes: (a) for each changed producer, name the artifact it builds and grep the skill's `SKILL.md` and sibling `.md` files for any sentence naming that artifact; (b) walk the producer body's build step — does it sort, or does it merge stored names and append in file order — and compare against the doc's order word (`sorted`, `alphabetical`); (c) check whether the doc's content claim (`just the at-risk names`, `only the current set`) hides merged-in prior entries the producer carries over from the stored file.
53
69
 
54
70
  **O9. Python docstring plainness for a general developer**
55
- - For every changed module / class / public-function docstring, read the narrative prose before the first `Args:` / `Returns:` / `Raises:` / `Yields:` section as a stranger to the code. Flag a narrative that stacks abstract machinery nouns into a single wall, that defines a thing by what it is not (`the non-promoter-specific machinery`), or that runs one sentence past the word limit while joining clauses with an em-dash or a semicolon. The run-on mark is gated at Write/Edit time by `check_docstring_runon_sentence`, so this lane judges the part the gate cannot: whether the prose paints a concrete scene — the moment the code matters, the input it sees, the outcome it produces — that a general developer follows on the first read.
71
+ - Judgment: thick rubric O9 (and `packages/claude-dev-env/rules/plain-illustrative-docstrings.md`). Run-on and prose-wall gates backstop the deterministic marks.
56
72
  - Adversarial probes: (a) read each changed narrative and name the concrete moment, input, and outcome it paints — a narrative that names none is an O9 finding; (b) count the longest sentence's words and check for an em-dash or semicolon join — over the limit with a join is the wall mark the gate also catches; (c) rewrite each "is not" clause as a positive statement — a clause that resists rewriting because the body offers no positive description is an O9 finding.
57
73
 
58
74
  ## Cross-bucket questions to answer at the end
@@ -65,7 +81,7 @@ Q3: Of the changed docstrings, which one most clearly shows a refactor was incom
65
81
 
66
82
  ## Output
67
83
 
68
- Lead: `Total: N (P0=N, P1=N, P2=N)`. For each sub-bucket O1-O9, produce Shape A or Shape B (with ≥3 probes). Each Shape A finding must cite (a) the docstring file:line, (b) the body file:line that contradicts it, and (c) one sentence describing the contradiction in concrete terms. Cross-bucket Q1-Q3 answers after the per-sub-bucket walk. Adversarial second pass: "assume your first pass missed at least 3 module-level docstring claims whose implementation moved during a refactor — find them." Open Questions section for ambiguities. Read-only. No edits, no commits.
84
+ Lead: `Total: N (P0=N, P1=N, P2=N)`. For each sub-bucket O1-O9, produce Shape A or Shape B (with ≥3 probes). Each Shape A finding must cite (a) the docstring file:line, (b) the body file:line that contradicts it, and (c) one sentence describing the contradiction in concrete terms. Cross-bucket Q1-Q3 answers after the per-sub-bucket walk. Adversarial second pass: "assume your first pass missed at least 3 module-level docstring claims whose implementation left during a refactor — find them." Open Questions section for ambiguities. Read-only. No edits, no commits.
69
85
 
70
86
  ---
71
87
 
@@ -77,6 +93,6 @@ PR #522 split `pr_description_command_parser.py` into two modules — the origin
77
93
 
78
94
  Expected findings on PR #522:
79
95
  - **O1 finding:** `pr_description_body_audit.py:8` docstring uses verb `detects`, but the only exported symbol prepares input for a regex scan that fires in a different module. Body line(s) showing `_extract_vague_scan_text` returning normalized text without a detection call.
80
- - **O7 finding:** `pr_description_command_parser.py` module docstring still names PR-number recovery as a responsibility; the split moved that to `pr_description_pr_number.py`. The originating docstring needs an O7-shaped rewrite to drop the moved claim.
96
+ - **O7 finding:** `pr_description_command_parser.py` module docstring still names PR-number recovery as a responsibility; the split placed that in `pr_description_pr_number.py`. The originating docstring needs an O7-shaped rewrite to drop the claim that left.
81
97
  - **O2 finding:** `test_pr_description_enforcer_readability.py` autouse fixture docstring claims readability is globally disabled `for these tests`; sibling tests in the same module explicitly re-enable readability through a different state path.
82
98
  - **O5 finding:** `code_rules_magic_values.py` docstring references a `# pragma: no-tdd-gate` sentinel and a hyphenated `test_code-rules-enforcer.py` filename; neither token exists in the module body or matches the repo's underscore-only test-file naming convention.
package/docs/CLAUDE.md CHANGED
@@ -17,6 +17,7 @@ Reference documentation installed into `~/.claude/docs/` by `bin/install.mjs`. T
17
17
  | `agent-spawn-protocol.md` | Full agent-spawn protocol behind the `rules/agent-spawn-protocol.md` kernel: context-sufficiency check, `/prompt-generator` prompt crafting, and the spawn step |
18
18
  | `nas-ssh-invocation.md` | Full NAS ssh policy behind the `rules/nas-ssh-invocation.md` kernel: the OpenSSH binary form, config sources, and hook enforcement |
19
19
  | `worker-completion-gate.md` | Full worker-completion gate behind the `rules/workers-done-before-complete.md` kernel: the checklist, examples, and run-state records |
20
+ | `wsl-docker-cowork-starter-matrix.md` | Host matrix: WSL/Docker/cowork component → starter → required? → shutdown; policy options with costs; no unmeasured `.wslconfig` memory cap |
20
21
 
21
22
  ## Subdirectory
22
23
 
@@ -7,6 +7,7 @@ Pointer documents to external sources and standard terminology. Files here are l
7
7
  | File | Purpose |
8
8
  |---|---|
9
9
  | `dead-code-elimination.md` | External sources and standard terms behind CODE_RULES §9.8 (remove code you orphan): DCE, tree shaking, reachability analysis, and the Lava Flow anti-pattern |
10
+ | `code-review-enforcement.md` | How the code-review gates work: the two required efforts (push at low, PR creation at xhigh), the stamp bound to the branch-surface hash, the single sanctioned minter, the two-layer stamp-directory guard, and the bypass surfaces the gates leave open |
10
11
 
11
12
  ## Role
12
13
 
@@ -0,0 +1,97 @@
1
+ # Code-review enforcement
2
+
3
+ This feature ties two git actions to a clean run of the built-in
4
+ `/code-review --fix`:
5
+
6
+ - **`git push`** needs a clean review at effort **low** or higher.
7
+ - **Pull-request creation** (`gh pr create` and the MCP `create_pull_request`
8
+ tool) needs a clean review at effort **xhigh** or higher.
9
+
10
+ The gates follow the same shape as the `verified_commit` gate family.
11
+
12
+ ## How a stamp works
13
+
14
+ A stamp is a small JSON file that records one fact: a clean `/code-review` pass
15
+ ran against an exact branch surface at a given effort. Each work tree keeps one
16
+ file under `~/.claude/code-review-stamps/`, named by a hash of the resolved
17
+ work-tree path.
18
+
19
+ The stamp binds to a **branch-surface hash** — the hash of every changed path
20
+ and untracked file, each bound by its content digest, measured against the
21
+ merge base. When any byte of the change surface moves, the live hash stops
22
+ matching the stored hash, so the stamp stops covering the surface and the gate
23
+ asks for a fresh review.
24
+
25
+ A gate allows the action only when a stored stamp matches the live hash exactly
26
+ and its effort ranks at or above the effort the action needs. A missing,
27
+ unreadable, or malformed stamp reads as no coverage, so the gate fails closed.
28
+
29
+ ## The single sanctioned minter
30
+
31
+ Only `invoke_code_review.py --record-stamp` writes a stamp. It forces a headless
32
+ `/code-review <effort> --fix` run, then mints a stamp only when the review
33
+ returns a clean exit code and leaves the branch surface unchanged in the same
34
+ pass. A pass that applies fixes mints nothing; the run loops on the new surface
35
+ up to a capped number of passes and mints only on a stable clean pass.
36
+
37
+ ## Two layers guard the stamp directory
38
+
39
+ The gates trust one rule: only the sanctioned minter writes stamp files. Two
40
+ layers hold that rule.
41
+
42
+ 1. **File-tool deny in `settings.json`.** `Write`, `Edit`, and `MultiEdit`
43
+ under `~/.claude/code-review-stamps/` are denied. This layer covers work
44
+ inside the repository. The installer merges hook groups into a user's
45
+ `settings.json` and does not ship this package's `permissions.deny`, so on a
46
+ user's machine this layer protects contributor work, at parity with the
47
+ `verified_commit` gate's own file-tool deny.
48
+ 2. **`code_review_stamp_directory_write_blocker` hook.** This hook ships through
49
+ `hooks.json`, so it reaches every install. It has two arms:
50
+ - a shell arm that denies any Bash or PowerShell command naming the stamp
51
+ directory, or importing the stamp store module, or calling its mint
52
+ function — while it lets the sanctioned minter command through;
53
+ - a file-tool arm that denies any `Write`, `Edit`, or `MultiEdit` whose path
54
+ resolves under the stamp directory. This arm closes the plain file-tool
55
+ forge on every shipped install, which the package `settings.json` deny
56
+ cannot reach on its own.
57
+
58
+ ## What the gates block
59
+
60
+ - **Casual and accidental forges.** A plain file-tool write to the stamp
61
+ directory, and a casual shell write to it, are both denied.
62
+ - **Hidden-path and split-step shell forges.** A shell command that assembles
63
+ the stamp path from hex, base64, or character math is decoded and denied. A
64
+ command that splits the directory change across steps to walk into the stamp
65
+ directory is traced and denied.
66
+ - **Lazy skips.** A push or a pull-request creation cannot go ahead without a
67
+ stamp that matches the live surface at the needed effort.
68
+
69
+ ## What the gates do not block
70
+
71
+ The chain-mode `/code-review` runs as a subprocess spawn of the `claude`
72
+ binary, not a harness-recorded subagent, so there is no signed sidecar to
73
+ anchor a forgery-proof mint. The stamp reaches the same posture the
74
+ `verified_commit` gate holds, and no further. These bypass surfaces stay open:
75
+
76
+ - **Pull requests that skip the tool paths.** A PR opened through
77
+ `gh api -X POST .../pulls` or the GitHub web page never triggers the
78
+ create-PR gate.
79
+ - **`git push --no-verify`.** This flag tells git to skip the native pre-push
80
+ hook, so the native backstop does not run.
81
+ - **A rebuilt store.** A script that re-implements the stamp store in memory
82
+ and writes a matching file can mint a stamp the gates accept.
83
+
84
+ In short: these gates stop casual forges and lazy skips. They do not stop a
85
+ determined attacker who sets out to defeat them.
86
+
87
+ ## Where the pieces live
88
+
89
+ - Gates: `hooks/blocking/code_review_push_gate.py`,
90
+ `hooks/blocking/code_review_pr_create_gate.py`.
91
+ - Stamp store: `hooks/blocking/code_review_stamp_store.py`.
92
+ - Directory guard: `hooks/blocking/code_review_stamp_directory_write_blocker.py`.
93
+ - Shared constants: `hooks/blocking/config/code_review_enforcement_constants.py`.
94
+ - Native backstop: `hooks/git-hooks/pre_push.py` reuses the push gate's
95
+ `deny_reason_for_directory` so the native hook and the Claude gate share one
96
+ decision source.
97
+ - Minter: `scripts/invoke_code_review.py --record-stamp`.