claude-dev-env 2.2.1 → 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.
- package/CLAUDE.md +3 -2
- package/_shared/advisor/advisor-protocol.md +2 -2
- package/_shared/pr-loop/scripts/CLAUDE.md +1 -0
- package/_shared/pr-loop/scripts/grant_project_claude_permissions.py +306 -284
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/CLAUDE.md +1 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/claude_permissions_constants.py +3 -3
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/stale_worktree_rule_sweep_constants.py +107 -0
- package/_shared/pr-loop/scripts/revoke_project_claude_permissions.py +5 -0
- package/_shared/pr-loop/scripts/stale_worktree_rule_sweep.py +107 -0
- package/_shared/pr-loop/scripts/tests/CLAUDE.md +2 -0
- package/_shared/pr-loop/scripts/tests/test_agent_config_carveout.py +55 -73
- package/_shared/pr-loop/scripts/tests/test_claude_permissions_constants.py +9 -15
- package/_shared/pr-loop/scripts/tests/test_grant_project_claude_permissions.py +91 -1
- package/_shared/pr-loop/scripts/tests/test_revoke_project_claude_permissions.py +88 -1
- package/_shared/pr-loop/scripts/tests/test_stale_worktree_rule_sweep.py +301 -0
- package/_shared/pr-loop/scripts/tests/test_stale_worktree_rule_sweep_constants.py +85 -0
- package/_shared/pr-loop/worker-spawn.md +1 -1
- package/agents/CLAUDE.md +1 -0
- package/agents/code-verifier.md +4 -4
- package/agents/skill-writer-agent.md +84 -0
- package/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md +141 -41
- package/audit-rubrics/prompts/category-o-docstring-vs-impl-drift.md +29 -13
- package/docs/CLAUDE.md +1 -0
- package/docs/references/CLAUDE.md +1 -0
- package/docs/references/code-review-enforcement.md +97 -0
- package/docs/wsl-docker-cowork-starter-matrix.md +89 -0
- package/hooks/blocking/CLAUDE.md +8 -1
- package/hooks/blocking/code_review_enforcement_config_bootstrap.py +53 -0
- package/hooks/blocking/code_review_gate_deny.py +74 -0
- package/hooks/blocking/code_review_pr_create_gate.py +194 -0
- package/hooks/blocking/code_review_push_gate.py +140 -0
- package/hooks/blocking/code_review_stamp_directory_write_blocker.py +340 -0
- package/hooks/blocking/code_review_stamp_store.py +233 -0
- package/hooks/blocking/code_review_stamp_write_blocker_parts/__init__.py +7 -0
- package/hooks/blocking/code_review_stamp_write_blocker_parts/conftest.py +15 -0
- package/hooks/blocking/code_review_stamp_write_blocker_parts/obfuscated_stamp_path_reference.py +212 -0
- package/hooks/blocking/code_review_stamp_write_blocker_parts/split_directory_change_into_stamp.py +138 -0
- package/hooks/blocking/code_review_stamp_write_blocker_parts/test_obfuscated_stamp_path_reference.py +49 -0
- package/hooks/blocking/code_review_stamp_write_blocker_parts/test_split_directory_change_into_stamp.py +38 -0
- package/hooks/blocking/code_verifier_spawn_preflight_gate.py +39 -27
- package/hooks/blocking/config/code_review_enforcement_constants.py +110 -0
- package/hooks/blocking/config/test_code_review_enforcement_constants.py +108 -0
- package/hooks/blocking/config/verified_commit_constants.py +24 -9
- package/hooks/blocking/conftest.py +2 -0
- package/hooks/blocking/convergence_gate_blocker.py +112 -23
- package/hooks/blocking/destructive_command_blocker.py +19 -6
- package/hooks/blocking/pr_description_proof_of_work.py +52 -34
- package/hooks/blocking/test_bash_pre_tool_use_dispatcher.py +4 -1
- package/hooks/blocking/test_code_review_enforcement_config_bootstrap.py +62 -0
- package/hooks/blocking/test_code_review_gate_deny.py +54 -0
- package/hooks/blocking/test_code_review_pr_create_gate.py +185 -0
- package/hooks/blocking/test_code_review_push_gate.py +189 -0
- package/hooks/blocking/test_code_review_stamp_directory_write_blocker.py +180 -0
- package/hooks/blocking/test_code_review_stamp_store.py +205 -0
- package/hooks/blocking/test_code_verifier_spawn_preflight_gate.py +124 -2
- package/hooks/blocking/test_code_verifier_tools_contract.py +28 -0
- package/hooks/blocking/test_convergence_gate_blocker.py +153 -5
- package/hooks/blocking/test_destructive_command_blocker.py +1 -1
- package/hooks/blocking/test_destructive_command_blocker_deny_mode.py +45 -0
- package/hooks/blocking/test_pr_description_proof_of_work.py +151 -0
- package/hooks/blocking/test_pre_tool_use_dispatcher.py +8 -8
- package/hooks/blocking/test_verification_verdict_store.py +920 -810
- package/hooks/blocking/test_volatile_path_in_post_blocker.py +114 -2
- package/hooks/blocking/verification_verdict_store.py +118 -5
- package/hooks/blocking/verified_commit_gate_parts/gated_invocations.py +29 -17
- package/hooks/blocking/verified_commit_gate_parts/tests/test_gated_invocations.py +35 -0
- package/hooks/blocking/volatile_path_in_post_blocker.py +69 -8
- package/hooks/git-hooks/git_hooks_constants/__init__.py +6 -0
- package/hooks/git-hooks/pre_push.py +89 -2
- package/hooks/git-hooks/test_pre_push.py +103 -0
- package/hooks/hooks.json +16 -1
- package/hooks/hooks_constants/CLAUDE.md +1 -0
- package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +8 -0
- package/hooks/hooks_constants/code_rules_path_utils_constants.py +1 -0
- package/hooks/hooks_constants/code_verifier_spawn_preflight_gate_constants.py +26 -11
- package/hooks/hooks_constants/convergence_gate_blocker_constants.py +20 -3
- package/hooks/hooks_constants/destructive_command_segment_constants.py +3 -1
- package/hooks/hooks_constants/pr_description_proof_of_work_constants.py +0 -4
- package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +4 -0
- package/hooks/hooks_constants/pyproject_config_discovery_constants.py +16 -0
- package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +24 -0
- package/hooks/hooks_constants/test_pre_tool_use_dispatcher_constants.py +6 -0
- package/hooks/hooks_constants/volatile_path_in_post_blocker_constants.py +8 -1
- package/hooks/validators/CLAUDE.md +1 -0
- package/hooks/validators/mypy_integration.py +63 -50
- package/hooks/validators/pyproject_config_discovery.py +101 -0
- package/hooks/validators/ruff_integration.py +211 -23
- package/hooks/validators/run_all_validators.py +21 -14
- package/hooks/validators/test_mypy_integration.py +32 -0
- package/hooks/validators/test_pyproject_config_discovery.py +94 -0
- package/hooks/validators/test_ruff_integration.py +68 -1
- package/hooks/validators/test_run_all_validators.py +25 -0
- package/hooks/validators/test_run_all_validators_config_discovery.py +123 -0
- package/package.json +1 -1
- package/rules/docstring-prose-matches-implementation.md +20 -43
- package/rules/durable-post-artifacts.md +7 -0
- package/scripts/CLAUDE.md +2 -1
- package/scripts/claude-chain.example.json +15 -3
- package/scripts/claude_chain_runner.py +130 -27
- package/scripts/claude_chain_usage.py +346 -0
- package/scripts/codec_forwarding_test_support.py +83 -0
- package/scripts/conftest.py +8 -0
- package/scripts/dev_env_scripts_constants/CLAUDE.md +4 -3
- package/scripts/dev_env_scripts_constants/claude_chain_constants.py +57 -2
- package/scripts/dev_env_scripts_constants/claude_chain_usage_constants.py +58 -0
- package/scripts/dev_env_scripts_constants/code_review_constants.py +129 -12
- package/scripts/dev_env_scripts_constants/test_code_review_constants.py +55 -0
- package/scripts/dev_env_scripts_constants/timing.py +1 -1
- package/scripts/invoke_code_review.py +550 -38
- package/scripts/resolve_worker_spawn.py +8 -1
- package/scripts/test_claude_chain_runner.py +412 -6
- package/scripts/test_claude_chain_usage.py +534 -0
- package/scripts/test_invoke_code_review.py +298 -4
- package/scripts/test_invoke_code_review_codec.py +77 -0
- package/scripts/test_resolve_worker_spawn_codec.py +101 -0
- package/skills/autoconverge/SKILL.md +9 -3
- package/skills/autoconverge/reference/convergence.md +2 -1
- package/skills/autoconverge/reference/multi-pr.md +6 -1
- package/skills/autoconverge/reference/stop-conditions.md +16 -10
- package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +1 -1
- package/skills/autoconverge/workflow/converge.codex-gate.test.mjs +175 -3
- package/skills/autoconverge/workflow/converge.contract.test.mjs +19 -0
- package/skills/autoconverge/workflow/converge.mjs +24 -8
- package/skills/autoconverge/workflow/converge_multi.mjs +7 -3
- package/skills/autoconverge/workflow/converge_multi.run-input.test.mjs +5 -0
- package/skills/fresh-branch/CLAUDE.md +2 -0
- package/skills/fresh-branch/SKILL.md +2 -0
- package/skills/fresh-branch/scripts/create_fresh_branch.py +78 -180
- package/skills/fresh-branch/scripts/fresh_branch_git_commands.py +285 -0
- package/skills/fresh-branch/scripts/fresh_branch_scripts_constants/fresh_branch_cli_constants.py +1 -0
- package/skills/fresh-branch/scripts/pytest.ini +4 -0
- package/skills/fresh-branch/scripts/test_create_fresh_branch.py +98 -0
- package/skills/fresh-branch/scripts/test_fresh_branch_git_commands.py +310 -0
- package/skills/orchestrator/SKILL.md +1 -20
- package/skills/orchestrator-refresh/SKILL.md +1 -1
- package/skills/pr-converge/SKILL.md +3 -3
- package/skills/pr-converge/reference/examples.md +3 -3
- package/skills/pr-converge/reference/fix-protocol.md +1 -1
- package/skills/pr-converge/reference/ground-rules.md +3 -3
- package/skills/pr-converge/reference/per-tick.md +5 -5
- package/skills/pr-converge/reference/progress-checklist.md +1 -1
- package/skills/pr-converge/test_step5_host_branch.py +1 -1
- package/skills/prototype/SKILL.md +86 -0
- package/skills/prototype/reference/honest-limitations.md +23 -0
- package/skills/prototype/reference/promotion-tasks.md +23 -0
- package/skills/prototype/scripts/build_sandbox_settings.py +249 -0
- package/skills/prototype/scripts/conftest.py +15 -0
- package/skills/prototype/scripts/launch_sandbox.py +205 -0
- package/skills/prototype/scripts/probe_sandbox_safety.py +311 -0
- package/skills/prototype/scripts/prototype_scripts_constants/__init__.py +1 -0
- package/skills/prototype/scripts/prototype_scripts_constants/config/__init__.py +0 -0
- package/skills/prototype/scripts/prototype_scripts_constants/config/build_sandbox_settings_constants.py +41 -0
- package/skills/prototype/scripts/prototype_scripts_constants/config/launch_sandbox_constants.py +23 -0
- package/skills/prototype/scripts/prototype_scripts_constants/config/probe_sandbox_safety_constants.py +45 -0
- package/skills/prototype/scripts/prototype_scripts_constants/config/prototype_common_constants.py +10 -0
- package/skills/prototype/scripts/test_build_sandbox_settings.py +275 -0
- package/skills/prototype/scripts/test_launch_sandbox.py +303 -0
- package/skills/prototype/scripts/test_probe_sandbox_safety.py +284 -0
- package/skills/prototype/workflows/promotion.md +27 -0
- package/skills/prototype/workflows/sandbox.md +35 -0
- package/skills/skill-builder/CLAUDE.md +3 -3
- package/skills/skill-builder/SKILL.md +5 -5
- package/skills/skill-builder/references/CLAUDE.md +1 -1
- package/skills/skill-builder/references/delegation-map.md +3 -3
- package/skills/skill-builder/references/description-field.md +1 -1
- package/skills/skill-builder/references/skill-modularity.md +2 -3
- package/skills/skill-builder/workflows/CLAUDE.md +1 -1
- package/skills/skill-builder/workflows/improve-skill.md +1 -1
- package/skills/skill-builder/workflows/new-skill.md +2 -2
- package/skills/team-advisor/SKILL.md +5 -4
|
@@ -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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
-
|
|
24
|
-
- Adversarial probes: (a) grep for the verb's noun-form in sibling modules — did a refactor
|
|
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
|
-
-
|
|
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
|
-
-
|
|
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
|
-
-
|
|
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
|
-
-
|
|
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
|
-
-
|
|
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
|
-
-
|
|
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
|
|
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
|
-
-
|
|
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
|
-
-
|
|
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
|
|
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
|
|
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`.
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# WSL / Docker / Cowork starter matrix
|
|
2
|
+
|
|
3
|
+
Attribution and policy surface for host memory consumers that sit under WSL2, Docker Desktop, and the Claude Cowork HCS VM. Source evidence: forensic capture summarized on [issue #256](https://github.com/jl-cmd/claude-dev-env/issues/256) (`results/05-wsl-who-started.md` in the local ram-process-sprawl evidence pack). This file records supported starters, whether each component is required for day-to-day agent work, and how to shut it down. It does **not** apply a `.wslconfig` memory cap.
|
|
4
|
+
|
|
5
|
+
## Hard rules
|
|
6
|
+
|
|
7
|
+
1. **No unmeasured `.wslconfig` memory cap.** Do not set `memory=` (or other hard caps) in `%UserProfile%\.wslconfig` until (a) the owner of the WSL VM commit is known for the workload under test and (b) a before/after private-working-set measurement is recorded on the same host boot window. A blind cap is out of scope for this matrix.
|
|
8
|
+
2. **Proven starters only.** Rows below use only process-parent, service, HCS owner, registry, and product-log evidence from the capture. Proximity without a parent edge is labeled **unknown**, not a starter claim.
|
|
9
|
+
3. **Two HCS VMs are not one.** Owner=`WSL` maps to `vmmemWSL`. Owner=`cowork-vm-*` maps to plain `vmmem`. Treat them as separate shutdown and policy targets.
|
|
10
|
+
|
|
11
|
+
## Starter matrix
|
|
12
|
+
|
|
13
|
+
| Component | Starter / owner (supported) | Required for daily agent work? | Shutdown / stop |
|
|
14
|
+
|-----------|----------------------------|--------------------------------|-----------------|
|
|
15
|
+
| **WSLService** (`wslservice.exe`) | Windows service **WSLService**, StartMode=Auto, parent `services.exe` since boot | **Platform yes** if any WSL2 distro is used; leave Auto unless WSL is retired on the host | `Stop-Service WSLService` only when deliberately disabling WSL; normal idle path is distro shutdown, not service kill |
|
|
16
|
+
| **`vmmemWSL`** (WSL2 utility VM) | HCS VM Owner=`WSL`; worker chain `vmcompute` → `vmwp` → `vmmemWSL` | **Yes while any distro is Running** (`wsl -l -v`) | `wsl --shutdown` (stops all WSL2 distros and the WSL utility VM; disrupts Docker's WSL backend too) |
|
|
17
|
+
| **First user wake of WSL VM** | **Unknown** in the capture (no Security 4688; no surviving user `wsl.exe` from the create second). Docker was **not** up yet at that create time | N/A — attribution gap | Same as `vmmemWSL` once running |
|
|
18
|
+
| **Docker Desktop / `com.docker.backend`** | Docker Desktop launches backend (product log). **HKCU Run** key registers Docker Desktop for logon. Windows service `com.docker.service` was **Stopped** / Manual — engine path is Desktop/backend user-mode | **Only when containers or Docker tooling are in active use** | Quit Docker Desktop (tray → Quit); confirm `com.docker.backend` gone. Optional: remove or disable the HKCU Run value named `Docker Desktop` so logon does not relaunch it |
|
|
19
|
+
| **Live `wsl.exe` for `docker-desktop` + Ubuntu integration** | Parent **`com.docker.backend.exe` (services)** | Same as Docker Desktop | Stop Docker Desktop; or `wsl --shutdown` (broader blast radius) |
|
|
20
|
+
| **Live `wsl.exe` for `code-index-mcp`** | Parent **`codex.exe`** ← **ChatGPT.exe** ← explorer. Command shape: `wsl.exe …/code-index-mcp` | **Only while Codex/ChatGPT needs the Ubuntu MCP indexer** | Exit Codex / ChatGPT app-server session; confirm no `wsl.exe` whose command line is `code-index-mcp` |
|
|
21
|
+
| **Plain `vmmem` (cowork HCS VM)** | HCS VM Owner=`cowork-vm-*` (name match). Chain `vmcompute` → `vmwp` → `vmmem`. **Exact user process that created the VM is unknown** (no live parent edge). Claude Desktop cmdlines carry `cowork-*` schemes; that is label association, not a proven create edge | **Only while Claude Cowork VM features are in use** | Quit Claude Desktop / Cowork UI that owns the session; if the HCS VM remains Running, treat full stop as an open procedure (see open questions). Do **not** assume `wsl --shutdown` stops this VM — it is not Owner=`WSL` |
|
|
22
|
+
| **grok as WSL parent** | **None** in the capture (high-confidence negative) | N/A | N/A |
|
|
23
|
+
| **claude as parent of live `wsl.exe`** | **None** in the capture (high-confidence negative) | N/A for WSL shells; see cowork row for the separate HCS VM | N/A for `wsl.exe` |
|
|
24
|
+
|
|
25
|
+
### Capture facts that stay fixed for this matrix
|
|
26
|
+
|
|
27
|
+
These are host-capture facts the matrix must not rewrite:
|
|
28
|
+
|
|
29
|
+
- Docker autostart comes from the **HKCU `Run` key** entry for Docker Desktop (not the stopped `com.docker.service`).
|
|
30
|
+
- **Codex `code-index-mcp`** is a live holder of `wsl.exe`; it is not the create-time owner of `vmmemWSL` when Codex starts hours later.
|
|
31
|
+
- **cowork-vm** is a **separate** HCS VM and plain `vmmem` consumer; it is not `vmmemWSL`.
|
|
32
|
+
- **grok** and **claude** were **not** parents of any live `wsl.exe` in the capture.
|
|
33
|
+
|
|
34
|
+
## Open questions
|
|
35
|
+
|
|
36
|
+
Named gaps only — do not fill these with guesses in policy or code:
|
|
37
|
+
|
|
38
|
+
1. **Exact user-mode process that first woke the WSL VM** at the `vmmemWSL` create second (Security 4688 / Sysmon not available in the capture).
|
|
39
|
+
2. **Exact process that created HCS `cowork-vm-*` / plain `vmmem`** (parent chain ends at `vmwp` / `vmcompute`).
|
|
40
|
+
3. **Whether a given Docker start was pure logon Run vs interactive tray open** (Run key proves Docker can autostart; a dead parent PID on the backend does not by itself prove which path fired).
|
|
41
|
+
4. **Identity of dead parents** of mid-session Ubuntu `wslhost` processes whose PPID is already recycled.
|
|
42
|
+
5. **Documented, safe idle-stop for the cowork HCS VM** when Claude UI is gone but `hcsdiag` still lists Owner=`cowork-vm-*` Running.
|
|
43
|
+
6. **Whether historical sessions of grok/claude started WSL earlier in a boot** — unprovable without process-creation audit history; live snapshot negatives do not extend backward.
|
|
44
|
+
|
|
45
|
+
To close (1), (2), (4), or (6): enable process-creation audit (Security 4688) or Sysmon with filters on `wsl.exe`, `vmwp.exe`, and Docker/Claude/Codex image paths, then re-capture on a clean boot.
|
|
46
|
+
|
|
47
|
+
## Policy options (with costs)
|
|
48
|
+
|
|
49
|
+
Choose explicitly. None of these options includes an unmeasured `.wslconfig` `memory=` write.
|
|
50
|
+
|
|
51
|
+
| Option | Action | RAM / sprawl effect (directional) | Cost / risk |
|
|
52
|
+
|--------|--------|-----------------------------------|-------------|
|
|
53
|
+
| **P0 — Observe only** | Keep matrix; no host change | None until a component is stopped | Continues dual-VM + Docker + Codex WSL hold when those apps run |
|
|
54
|
+
| **P1 — Docker on demand** | Remove or disable HKCU Run `Docker Desktop`; start Desktop only when containers are needed | Avoids Docker backend + docker-desktop/Ubuntu integration `wsl.exe` on boots/sessions that never use Docker | Manual start latency; first container work pays cold start; any script that assumes Docker is already up fails until launch |
|
|
55
|
+
| **P2 — Docker fully off when idle** | Quit Docker Desktop after use; optional P1 | Frees backend private set and Docker-held WSL distro activity | Must re-open Desktop before compose/build; `wsl -l -v` may still show Running until `wsl --shutdown` or Docker stops holding distros |
|
|
56
|
+
| **P3 — Codex indexer off when idle** | Exit ChatGPT/Codex app-server when not reviewing; disable code-index MCP if product settings allow | Drops Codex-held `wsl.exe … code-index-mcp` edges | Codex features that need the Ubuntu indexer fail until restart; does **not** by itself tear down `vmmemWSL` if Docker or another client still holds a distro |
|
|
57
|
+
| **P4 — WSL idle shutdown** | When no Docker/Codex/other WSL client is needed: `wsl --shutdown` | Tears down Ubuntu + docker-desktop distros and `vmmemWSL` | **Breaks** any live Docker WSL backend and any in-distro MCP until restart; never use mid-task if containers or WSL MCP are active |
|
|
58
|
+
| **P5 — Cowork VM idle policy** | Quit Claude Cowork/Desktop when the VM is not needed; re-check `hcsdiag list` for Owner=`cowork-vm-*` | Targets plain `vmmem` (~multi-GB private in the capture) without touching Owner=`WSL` | Create/stop procedure for a leftover Running cowork VM is still an open question; wrong kill path can disrupt Cowork artifacts |
|
|
59
|
+
| **P6 — Process-creation audit** | Turn on 4688 or Sysmon for wsl/vmwp/Docker/Claude/Codex | No direct RAM win; closes open questions (1)(2)(4)(6) | Audit volume, privacy review, and storage for event logs |
|
|
60
|
+
| **P7 — `.wslconfig` memory cap** | Set `memory=` under `[wsl2]` | Caps WSL utility VM commit **only after** measured before/after on this host | **Blocked here until measured.** Risk: OOM inside distros, Docker backend instability, false “fix” that leaves cowork `vmmem` untouched |
|
|
61
|
+
|
|
62
|
+
### Measurement gate for P7 (and any memory cap)
|
|
63
|
+
|
|
64
|
+
Before any `.wslconfig` memory write:
|
|
65
|
+
|
|
66
|
+
1. Record `wsl -l -v`, `hcsdiag list`, and private working set for `vmmemWSL` / plain `vmmem` / `com.docker.backend`.
|
|
67
|
+
2. Name which component the cap is meant to bound (Owner=`WSL` only — cowork is outside `.wslconfig`).
|
|
68
|
+
3. Apply cap; reboot or `wsl --shutdown` + re-start workload as required for the setting to apply.
|
|
69
|
+
4. Re-record the same counters under the same workload.
|
|
70
|
+
5. Commit the before/after note next to the policy decision (issue comment or evidence pack). Without that note, leave `.wslconfig` without a memory cap.
|
|
71
|
+
|
|
72
|
+
## Quick identification commands
|
|
73
|
+
|
|
74
|
+
Read-only checks agents and operators use on Windows:
|
|
75
|
+
|
|
76
|
+
```text
|
|
77
|
+
wsl -l -v
|
|
78
|
+
hcsdiag list
|
|
79
|
+
Get-CimInstance Win32_Service -Filter "Name='WSLService'"
|
|
80
|
+
Get-ItemProperty HKCU:\Software\Microsoft\Windows\CurrentVersion\Run
|
|
81
|
+
Get-CimInstance Win32_Process -Filter "Name='wsl.exe'" | Select ProcessId, ParentProcessId, CommandLine
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Resolve each `wsl.exe` ParentProcessId to an image name before blaming an agent binary. A missing parent is **unknown**, not proof of a named starter.
|
|
85
|
+
|
|
86
|
+
## Related
|
|
87
|
+
|
|
88
|
+
- Issue: [jl-cmd/claude-dev-env#256](https://github.com/jl-cmd/claude-dev-env/issues/256) (child of epic #252)
|
|
89
|
+
- Local forensic write-up: `results/05-wsl-who-started.md` under the host evidence pack path named in the issue body
|
package/hooks/blocking/CLAUDE.md
CHANGED
|
@@ -9,6 +9,7 @@ PreToolUse hooks that deny (block) tool calls when a rule is violated. The main
|
|
|
9
9
|
| `config/` | Shared constants for the verified-commit gate family (`verified_commit_constants.py`) |
|
|
10
10
|
| `tdd_enforcer_parts/` | Concern modules the `tdd_enforcer.py` entry hook wires together: path classification, content analysis, candidate-path resolution, freshness, git-tracking restore detection, decisions, and constants |
|
|
11
11
|
| `verified_commit_gate_parts/` | Concern modules the `verified_commit_gate.py` entry hook wires together: command tokenization, directory-change resolution, gated git-invocation resolution, deny-reason resolution, and deny-payload assembly |
|
|
12
|
+
| `code_review_stamp_write_blocker_parts/` | Concern modules the `code_review_stamp_directory_write_blocker.py` entry hook wires together: the split directory-change-into-stamp matcher and the obfuscated-stamp-path-write matcher |
|
|
12
13
|
| `claude_md_orphan_file_blocker_parts/` | Concern modules the `claude_md_orphan_file_blocker.py` entry hook wires together: reference extraction, subtree scan, scan plan, decision, and constants |
|
|
13
14
|
| `package_inventory_stale_blocker_parts/` | Concern modules the `package_inventory_stale_blocker.py` entry hook wires together: inventory detection, decision, and constants |
|
|
14
15
|
| `inventory_intent_records/` | The shared per-session pending-intent store both inventory blockers read to break the file/row add-order deadlock |
|
|
@@ -71,7 +72,10 @@ The check modules it calls are the `code_rules_<concern>.py` files below.
|
|
|
71
72
|
| `block_main_commit.py` | PreToolUse (Bash) | `git commit`/`git push` directly to `main` |
|
|
72
73
|
| `bot_mention_comment_blocker.py` | PreToolUse (Write/Edit) | PR review comments that @-mention a bot |
|
|
73
74
|
| `claude_md_orphan_file_blocker.py` | PreToolUse (Write/Edit/MultiEdit) | Per-directory `CLAUDE.md` table cells naming a bare filename absent from the directory subtree |
|
|
74
|
-
| `
|
|
75
|
+
| `code_review_pr_create_gate.py` | PreToolUse (Bash/MCP GitHub) | `gh pr create` or the MCP `create_pull_request` tool without a clean `xhigh` code-review stamp covering the branch surface |
|
|
76
|
+
| `code_review_push_gate.py` | PreToolUse (Bash/PowerShell) | `git push` without a clean `low` code-review stamp covering the branch surface |
|
|
77
|
+
| `code_review_stamp_directory_write_blocker.py` | PreToolUse (Bash/PowerShell/Write/Edit/MultiEdit) | Shell or file-tool writes into `~/.claude/code-review-stamps/`, and shell references to the stamp store module or its mint call, outside the sanctioned invoker |
|
|
78
|
+
| `code_verifier_spawn_preflight_gate.py` | PreToolUse (Agent) | Spawning the `code-verifier` subagent when the branch has a merge conflict vs its base or a CODE_RULES violation on a working-tree-added line, or the CODE_RULES engine fails to load |
|
|
75
79
|
| `convergence_gate_blocker.py` | PreToolUse (Bash) | Convergence workflow actions on a conflicting PR |
|
|
76
80
|
| `conventional_pr_title_gate.py` | PreToolUse (Bash) | `gh pr create`/`gh pr edit` with a `--title` that is not a Conventional Commit, in a repo whose CI runs a semantic-pull-request title check |
|
|
77
81
|
| `destructive_command_blocker.py` | PreToolUse (Bash/PowerShell) | Shell commands with destructive literals (`rm -rf`, `git reset --hard`, etc.) |
|
|
@@ -121,6 +125,9 @@ The check modules it calls are the `code_rules_<concern>.py` files below.
|
|
|
121
125
|
| File | Role |
|
|
122
126
|
|---|---|
|
|
123
127
|
| `_gh_body_arg_utils.py` | Parsing helpers for `gh_body_arg_blocker.py` |
|
|
128
|
+
| `code_review_enforcement_config_bootstrap.py` | Binds `config.code_review_enforcement_constants` to the sibling `config/` file by explicit location, so the code-review gate family resolves its constants regardless of a foreign `config` package's `sys.path` order |
|
|
129
|
+
| `code_review_gate_deny.py` | Shared deny scaffold for the push and PR-create code-review gates: the `hookSpecificOutput` deny-payload builder and the log-and-emit helper, so both gates share one deny shape |
|
|
130
|
+
| `code_review_stamp_store.py` | Reads and writes the per-work-tree code-review stamp files under `~/.claude/code-review-stamps/`, and decides whether a clean stamp at the needed effort covers the live branch surface |
|
|
124
131
|
| `pr_description_body_audit.py` | Body audit logic for `pr_description_enforcer.py` |
|
|
125
132
|
| `pr_description_command_parser.py` | `gh` command parsing for `pr_description_enforcer.py` |
|
|
126
133
|
| `pr_description_pr_number.py` | PR number extraction logic |
|