claude-dev-env 2.12.0 → 2.13.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 (193) hide show
  1. package/_shared/pr-loop/scripts/code_rules_gate.py +2 -3
  2. package/_shared/pr-loop/scripts/test_code_rules_gate.py +0 -26
  3. package/agents/CLAUDE.md +1 -2
  4. package/agents/pr-description-writer.md +11 -81
  5. package/bin/install.mjs +48 -9
  6. package/bin/install.prune.test.mjs +26 -0
  7. package/bin/install.settings-defaults.test.mjs +60 -33
  8. package/bin/install.test.mjs +2 -4
  9. package/commands/sr-loop.md +9 -5
  10. package/docs/CLAUDE.md +0 -1
  11. package/docs/references/CLAUDE.md +0 -1
  12. package/hooks/CLAUDE.md +1 -1
  13. package/hooks/blocking/CLAUDE.md +2 -27
  14. package/hooks/blocking/config/CLAUDE.md +1 -13
  15. package/hooks/blocking/conventional_pr_title_gate.py +1 -2
  16. package/hooks/blocking/send_user_file_open_locally_blocker.py +1 -1
  17. package/hooks/blocking/stop_dispatcher.py +4 -4
  18. package/hooks/blocking/test_bash_pre_tool_use_dispatcher.py +0 -3
  19. package/hooks/blocking/test_pre_tool_use_dispatcher.py +6 -7
  20. package/hooks/blocking/test_send_user_file_open_locally_blocker.py +4 -3
  21. package/hooks/blocking/test_shared_stdin_adoption.py +0 -2
  22. package/hooks/diagnostic/CLAUDE.md +3 -3
  23. package/hooks/diagnostic/hook_log_extractor.py +2 -36
  24. package/hooks/diagnostic/hook_log_stop_wrapper.py +6 -155
  25. package/hooks/diagnostic/test_hook_log_extractor.py +8 -21
  26. package/hooks/diagnostic/test_hook_log_stop_wrapper.py +3 -331
  27. package/hooks/git-hooks/git_hooks_constants/__init__.py +0 -6
  28. package/hooks/git-hooks/pre_push.py +3 -85
  29. package/hooks/git-hooks/test_pre_push.py +0 -130
  30. package/hooks/hooks.json +0 -35
  31. package/hooks/hooks_constants/CLAUDE.md +0 -9
  32. package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +0 -11
  33. package/hooks/hooks_constants/convergence_branch_constants.py +0 -1
  34. package/hooks/hooks_constants/fable_spawn_gate_constants.py +3 -4
  35. package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +0 -8
  36. package/hooks/hooks_constants/send_user_file_open_locally_blocker_constants.py +1 -6
  37. package/hooks/hooks_constants/stop_dispatcher_constants.py +0 -1
  38. package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +1 -26
  39. package/hooks/hooks_constants/test_pre_tool_use_dispatcher_constants.py +0 -5
  40. package/hooks/hooks_constants/test_stop_dispatcher_constants.py +0 -1
  41. package/hooks/lifecycle/CLAUDE.md +1 -3
  42. package/package.json +1 -1
  43. package/rules/CLAUDE.md +0 -2
  44. package/rules/gh-cli-conventions.md +0 -1
  45. package/rules/git-workflow.md +1 -3
  46. package/scripts/CLAUDE.md +0 -1
  47. package/scripts/_code_review_test_support.py +0 -95
  48. package/scripts/dev_env_scripts_constants/code_review_constants.py +9 -98
  49. package/scripts/dev_env_scripts_constants/test_code_review_constants.py +1 -44
  50. package/scripts/invoke_code_review.py +8 -451
  51. package/scripts/test_invoke_code_review.py +5 -61
  52. package/scripts/test_invoke_code_review_cli.py +1 -45
  53. package/scripts/test_invoke_code_review_contract.py +1 -118
  54. package/settings.json +1 -10
  55. package/skills/CLAUDE.md +8 -0
  56. package/skills/autoconverge/reference/CLAUDE.md +1 -1
  57. package/skills/autoconverge/reference/convergence.md +7 -7
  58. package/skills/autoconverge/reference/gotchas.md +3 -10
  59. package/skills/autoconverge/workflow/converge.contract.test.mjs +53 -1388
  60. package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +4 -3
  61. package/skills/autoconverge/workflow/converge.fix-recovery.test.mjs +33 -493
  62. package/skills/autoconverge/workflow/converge.merge-conflict.test.mjs +15 -97
  63. package/skills/autoconverge/workflow/converge.mjs +201 -238
  64. package/skills/autoconverge/workflow/converge.precatch.test.mjs +1 -1
  65. package/skills/bugteam/CONSTRAINTS.md +1 -1
  66. package/skills/bugteam/SKILL.md +9 -0
  67. package/skills/bugteam/reference/audit-and-teammates.md +10 -6
  68. package/skills/bugteam/test_skill_additions.py +9 -0
  69. package/skills/comments/SKILL.md +65 -0
  70. package/skills/descriptions/SKILL.md +51 -0
  71. package/skills/emergencies/SKILL.md +42 -0
  72. package/skills/imagegen/SKILL.md +21 -0
  73. package/skills/imagegen/scripts/config/__init__.py +1 -0
  74. package/skills/imagegen/scripts/config/constants.py +28 -0
  75. package/skills/imagegen/scripts/imagegen.py +45 -0
  76. package/skills/imagegen/scripts/imagegen_core.py +528 -0
  77. package/skills/imagegen/scripts/test_imagegen.py +204 -0
  78. package/skills/orchestrator/SKILL.md +2 -4
  79. package/skills/plan-to-pr/SKILL.md +6 -7
  80. package/skills/plan-to-pr/reference/final-validation-tasks.md +1 -1
  81. package/skills/plan-to-pr/reference/process-inventory.md +2 -2
  82. package/skills/plan-to-pr/reference/review-loop.md +2 -2
  83. package/skills/plan-to-pr/reference/run-record.schema.json +1 -1
  84. package/skills/plan-to-pr/reference/task-seeds.md +1 -1
  85. package/skills/plan-to-pr/reference/task-ticket.md +3 -4
  86. package/skills/plan-to-pr/scripts/config/constants.py +1 -2
  87. package/skills/plan-to-pr/scripts/test_validate_protocol.py +1 -1
  88. package/skills/plan-to-pr/test_skill_contract.py +2 -2
  89. package/skills/pr-converge/SKILL.md +13 -16
  90. package/skills/pr-converge/reference/progress-checklist.md +6 -5
  91. package/skills/pr-converge/reference/state-schema.md +1 -27
  92. package/skills/pr-loop-cloud-transport/SKILL.md +1 -1
  93. package/skills/pr-loop-cloud-transport/reference/identity-and-hooks.md +3 -3
  94. package/skills/pr-loop-lifecycle/SKILL.md +1 -1
  95. package/skills/pr-loop-lifecycle/reference/teardown-publish-permissions.md +1 -1
  96. package/skills/prototype/SKILL.md +4 -4
  97. package/skills/prototype/reference/honest-limitations.md +4 -4
  98. package/skills/prototype/reference/promotion-tasks.md +2 -2
  99. package/skills/prototype/workflows/promotion.md +4 -6
  100. package/skills/reviews/SKILL.md +85 -0
  101. package/skills/small-cl/SKILL.md +52 -0
  102. package/agents/code-verifier.md +0 -74
  103. package/docs/PR_DESCRIPTION_GUIDE.md +0 -157
  104. package/docs/references/code-review-enforcement.md +0 -132
  105. package/hooks/blocking/code_review_enforcement_config_bootstrap.py +0 -53
  106. package/hooks/blocking/code_review_gate_deny.py +0 -74
  107. package/hooks/blocking/code_review_pr_create_gate.py +0 -198
  108. package/hooks/blocking/code_review_push_gate.py +0 -145
  109. package/hooks/blocking/code_review_stamp_directory_write_blocker.py +0 -348
  110. package/hooks/blocking/code_review_stamp_store.py +0 -233
  111. package/hooks/blocking/code_review_stamp_write_blocker_parts/__init__.py +0 -7
  112. package/hooks/blocking/code_review_stamp_write_blocker_parts/conftest.py +0 -15
  113. package/hooks/blocking/code_review_stamp_write_blocker_parts/obfuscated_stamp_path_reference.py +0 -212
  114. package/hooks/blocking/code_review_stamp_write_blocker_parts/split_directory_change_into_stamp.py +0 -138
  115. package/hooks/blocking/code_review_stamp_write_blocker_parts/test_obfuscated_stamp_path_reference.py +0 -49
  116. package/hooks/blocking/code_review_stamp_write_blocker_parts/test_split_directory_change_into_stamp.py +0 -38
  117. package/hooks/blocking/code_verifier_spawn_preflight_gate.py +0 -483
  118. package/hooks/blocking/config/code_review_enforcement_constants.py +0 -143
  119. package/hooks/blocking/config/test_code_review_enforcement_constants.py +0 -166
  120. package/hooks/blocking/config/verified_commit_constants.py +0 -160
  121. package/hooks/blocking/config/verified_commit_context_constants.py +0 -21
  122. package/hooks/blocking/config/verified_commit_gate_output_constants.py +0 -14
  123. package/hooks/blocking/conftest.py +0 -123
  124. package/hooks/blocking/convergence_gate_blocker.py +0 -310
  125. package/hooks/blocking/pr_converge_bugteam_enforcer.py +0 -170
  126. package/hooks/blocking/pr_description_body_audit.py +0 -148
  127. package/hooks/blocking/pr_description_command_parser.py +0 -237
  128. package/hooks/blocking/pr_description_enforcer.py +0 -227
  129. package/hooks/blocking/pr_description_pr_number.py +0 -155
  130. package/hooks/blocking/pr_description_proof_of_work.py +0 -385
  131. package/hooks/blocking/pr_description_readability.py +0 -364
  132. package/hooks/blocking/reviewer_spawn_gate.py +0 -182
  133. package/hooks/blocking/test_code_review_enforcement_config_bootstrap.py +0 -62
  134. package/hooks/blocking/test_code_review_gate_deny.py +0 -54
  135. package/hooks/blocking/test_code_review_pr_create_gate.py +0 -199
  136. package/hooks/blocking/test_code_review_push_gate.py +0 -205
  137. package/hooks/blocking/test_code_review_stamp_directory_write_blocker.py +0 -199
  138. package/hooks/blocking/test_code_review_stamp_store.py +0 -205
  139. package/hooks/blocking/test_code_verifier_spawn_preflight_gate.py +0 -710
  140. package/hooks/blocking/test_code_verifier_tools_contract.py +0 -28
  141. package/hooks/blocking/test_convergence_gate_blocker.py +0 -428
  142. package/hooks/blocking/test_pr_converge_bugteam_enforcer.py +0 -311
  143. package/hooks/blocking/test_pr_converge_bugteam_enforcer_state_tolerance.py +0 -184
  144. package/hooks/blocking/test_pr_description_enforcer.py +0 -221
  145. package/hooks/blocking/test_pr_description_enforcer_body_audit.py +0 -247
  146. package/hooks/blocking/test_pr_description_enforcer_body_rules.py +0 -493
  147. package/hooks/blocking/test_pr_description_enforcer_command_parser.py +0 -366
  148. package/hooks/blocking/test_pr_description_enforcer_pr_number.py +0 -159
  149. package/hooks/blocking/test_pr_description_enforcer_proof_gate.py +0 -175
  150. package/hooks/blocking/test_pr_description_enforcer_readability.py +0 -443
  151. package/hooks/blocking/test_pr_description_proof_of_work.py +0 -313
  152. package/hooks/blocking/test_reviewer_spawn_gate.py +0 -230
  153. package/hooks/blocking/test_verdict_directory_write_blocker.py +0 -804
  154. package/hooks/blocking/test_verification_verdict_store.py +0 -974
  155. package/hooks/blocking/test_verified_commit_config_bootstrap.py +0 -67
  156. package/hooks/blocking/test_verified_commit_docs_delta.py +0 -176
  157. package/hooks/blocking/test_verified_commit_gate.py +0 -581
  158. package/hooks/blocking/test_verified_commit_gate_additional_context.py +0 -134
  159. package/hooks/blocking/test_verified_commit_message_accuracy_blocker.py +0 -131
  160. package/hooks/blocking/test_verifier_verdict_minter.py +0 -299
  161. package/hooks/blocking/tests/test_verified_commit_gate.py +0 -41
  162. package/hooks/blocking/verdict_directory_write_blocker.py +0 -687
  163. package/hooks/blocking/verification_verdict_store.py +0 -1039
  164. package/hooks/blocking/verified_commit_config_bootstrap.py +0 -63
  165. package/hooks/blocking/verified_commit_gate.py +0 -113
  166. package/hooks/blocking/verified_commit_gate_parts/CLAUDE.md +0 -28
  167. package/hooks/blocking/verified_commit_gate_parts/__init__.py +0 -1
  168. package/hooks/blocking/verified_commit_gate_parts/command_tokenization.py +0 -174
  169. package/hooks/blocking/verified_commit_gate_parts/deny_payload.py +0 -53
  170. package/hooks/blocking/verified_commit_gate_parts/deny_reason.py +0 -80
  171. package/hooks/blocking/verified_commit_gate_parts/directory_resolution.py +0 -170
  172. package/hooks/blocking/verified_commit_gate_parts/gated_invocations.py +0 -217
  173. package/hooks/blocking/verified_commit_gate_parts/tests/conftest.py +0 -10
  174. package/hooks/blocking/verified_commit_gate_parts/tests/test_command_tokenization.py +0 -94
  175. package/hooks/blocking/verified_commit_gate_parts/tests/test_deny_payload.py +0 -17
  176. package/hooks/blocking/verified_commit_gate_parts/tests/test_deny_reason.py +0 -38
  177. package/hooks/blocking/verified_commit_gate_parts/tests/test_directory_resolution.py +0 -71
  178. package/hooks/blocking/verified_commit_gate_parts/tests/test_gated_invocations.py +0 -96
  179. package/hooks/blocking/verified_commit_message_accuracy_blocker.py +0 -167
  180. package/hooks/blocking/verifier_verdict_minter.py +0 -280
  181. package/hooks/hooks_constants/code_verifier_spawn_preflight_gate_constants.py +0 -64
  182. package/hooks/hooks_constants/convergence_gate_blocker_constants.py +0 -53
  183. package/hooks/hooks_constants/pr_converge_bugteam_enforcer_constants.py +0 -55
  184. package/hooks/hooks_constants/pr_converge_bugteam_enforcer_state.py +0 -67
  185. package/hooks/hooks_constants/pr_description_enforcer_constants.py +0 -167
  186. package/hooks/hooks_constants/pr_description_proof_of_work_constants.py +0 -107
  187. package/hooks/hooks_constants/reviewer_spawn_gate_constants.py +0 -41
  188. package/hooks/hooks_constants/test_pr_description_enforcer_constants.py +0 -292
  189. package/hooks/lifecycle/pr_converge_bugteam_skill_tracker.py +0 -198
  190. package/hooks/lifecycle/test_pr_converge_bugteam_skill_tracker.py +0 -283
  191. package/rules/proof-of-work-pr-comments.md +0 -22
  192. package/rules/verified-commit-gate-skip.md +0 -4
  193. package/scripts/Show-Asset.ps1 +0 -106
@@ -116,9 +116,8 @@ def _report_partitioned_violations(
116
116
  ) -> int:
117
117
  """Print the violation sections and return the gate exit code.
118
118
 
119
- Importers outside this directory (the code-verifier spawn-preflight hook)
120
- call this surface with the partition spread across positional arguments,
121
- so this wrapper keeps that calling shape and folds the pieces into the
119
+ External callers pass the partition across positional arguments, so this
120
+ wrapper keeps that calling shape and folds the pieces into the
122
121
  ``PartitionedViolations`` tuple the gate-running module consumes.
123
122
 
124
123
  Args:
@@ -250,29 +250,3 @@ def _gate_import_names_in_source(module_source: str) -> set[str]:
250
250
  for each_line in import_block.splitlines()
251
251
  if each_line.strip() and not each_line.strip().startswith("#")
252
252
  }
253
-
254
-
255
- def test_entry_exposes_every_symbol_the_preflight_hook_imports() -> None:
256
- """The gate entry re-exports the whole surface the spawn-preflight hook uses.
257
-
258
- The code-verifier spawn-preflight hook imports gate helpers by name at
259
- runtime; a name the entry drops breaks that hook's import and silently
260
- turns its deny paths into allows.
261
- """
262
- hook_path = (
263
- Path(__file__).resolve().parents[3]
264
- / "hooks"
265
- / "blocking"
266
- / "code_verifier_spawn_preflight_gate.py"
267
- )
268
- hook_imported_names = _gate_import_names_in_source(
269
- hook_path.read_text(encoding="utf-8")
270
- )
271
-
272
- assert hook_imported_names, "hook no longer imports from code_rules_gate"
273
- missing_names = sorted(
274
- each_name
275
- for each_name in hook_imported_names
276
- if not hasattr(gate_module, each_name)
277
- )
278
- assert not missing_names, f"gate entry drops hook imports: {missing_names}"
package/agents/CLAUDE.md CHANGED
@@ -11,13 +11,12 @@ Agent definition files installed into `~/.claude/agents/` by `bin/install.mjs`.
11
11
  | `clean-coder.md` | Clean Coder | Primary code-writing agent; links AGENTS.md / CODE_RULES / enforcer; task-local discovery and gate-clean first writes |
12
12
  | `code-advisor.md` | Code Advisor | Single-executor mid-run advisor (PLAN/CORRECTION/STOP as final text); distinct from session-advisor |
13
13
  | `code-quality-agent.md` | Code Quality Agent | Multi-file code quality review across an entire diff or set of files |
14
- | `code-verifier.md` | Code Verifier | Post-hoc verification, run only when the task owner asks for a pass; never edits files in the verified tree, with one exception, a deliberate break at an off-tree break site outside it, defined in the agent body; fresh context, puts the draft verdict through one strongest-tier validation subagent, ends with a fenced verdict; `all_pass` true needs a complete shown-red table as well as clean layers |
15
14
  | `deep-research.md` | Deep Research | Citation-grounded research with web search |
16
15
  | `docs-agent.md` | Docs Agent | Documentation authoring and maintenance |
17
16
  | `git-commit-crafter.md` | Git Commit Crafter | Stages changes, writes conventional commit messages, creates commits |
18
17
  | `issue-tracker.md` | Issue Tracker | Primary handler for one GitHub issue action per spawn; loads the issue-tracker skill (plain-brief); returns issue numbers and URLs |
19
18
  | `plan-packet-validator.md` | Plan Packet Validator | Fresh-context validator for workflow-generated plan packets under `docs/plans/` |
20
- | `pr-description-writer.md` | PR Description Writer | Authors PR descriptions in Anthropic-style shapes that pass the `pr_description_enforcer` hook's body audit |
19
+ | `pr-description-writer.md` | PR Description Writer | Drafts PR descriptions and comments from the current diff using the canonical description and comment guides |
21
20
  | `session-advisor.md` | Session Advisor | Standing multi-consumer reviewer; SendMessage only; returns endorse/correction/plan/stop |
22
21
  | `skill-writer-agent.md` | Skill Writer Agent | Authors SKILL.md and companion files to skill-builder conventions; caller-agnostic authoring specialist |
23
22
 
@@ -1,94 +1,24 @@
1
1
  ---
2
2
  name: pr-description-writer
3
- description: "Optional agent for PR descriptions and PR comments. Authors bodies in one of three Anthropic-derived shapes (Trivial / Standard / Heavy) so PRs match merged-PR style in `anthropics/claude-code`, `anthropics/claude-code-action`, and `anthropics/claude-code-sdk-python`, and pass the `pr_description_enforcer` PreToolUse hook's body audit on `gh pr create`, `gh pr edit`, and `gh pr comment`."
3
+ description: "Optional agent that drafts factual pull request descriptions and comments from the current diff and validates each statement against the master review guides."
4
4
  tools: Read,Grep,Glob,Bash
5
5
  ---
6
6
 
7
7
  # PR Description Writer
8
8
 
9
- You write PR bodies and PR comments. Your output follows Anthropic Claude Code house style. The style derives from a 120-PR sample across `anthropics/claude-code`, `anthropics/claude-code-action`, and `anthropics/claude-code-sdk-python`. Pick a shape that matches the diff size. Open with an imperative verb. Explain WHY the change exists in prose a reviewer can scan. The companion reference `packages/claude-dev-env/docs/PR_DESCRIPTION_GUIDE.md` carries the full header catalog and the readability escape-hatch flow.
9
+ Write the body from the current pull request diff and task. Use the [description guide](../skills/descriptions/SKILL.md#required-content) for required content and [description shape](../skills/descriptions/SKILL.md#description-shape). Use the [comment guide](../skills/comments/SKILL.md#writing-useful-review-comments) for a review comment.
10
10
 
11
- ## The three shapes
11
+ ## Draft the body
12
12
 
13
- Choose one shape per body. Mixing shapes signals confusion to the reviewer.
13
+ 1. Inspect the cumulative diff, the current pull request body, and the validation results.
14
+ 2. State the behavior change, its scope, and the validation that ran.
15
+ 3. Preserve accurate issue links and curated sections from the current body.
16
+ 4. Use headings when they help a reviewer scan the change.
14
17
 
15
- ### Trivial diff ≤ 10 lines
18
+ ## Publish through GitHub CLI
16
19
 
17
- One to three sentences of prose. Zero headers. No `## Summary`, no `## Test plan`. The opening verb describes the change directly.
20
+ Place markdown in a BOM-free temporary file and pass its path with `--body-file`. Follow [gh CLI conventions](../rules/gh-cli-conventions.md#body-content-goes-in-a-file).
18
21
 
19
- ```markdown
20
- Bump bun to 1.3.14. Picks up the bugfix for the runtime panic on empty stdin.
21
- ```
22
+ ## Check the draft
22
23
 
23
- ### Standard diff 11–500 lines
24
-
25
- Opens with an imperative-verb paragraph. Optional headers drawn from the Anthropic set: `## Summary`, `## Problem`, `## Fix`, `## Changes`, `## Test plan`, `## Tests`, `## Testing`, `## Approach`, `## Root cause`. Most Standard PRs use one or two — pick the ones the change earns.
26
-
27
- ```markdown
28
- Adds a syllable-counted Flesch reading score to the PR description enforcer. Bodies above the readability ceiling surface a targeted block message before the strike counter increments.
29
-
30
- ## Test plan
31
-
32
- - [ ] `pytest packages/claude-dev-env/hooks/blocking/test_pr_description_enforcer.py`
33
- - [ ] Open a draft PR with a 45-word sentence and confirm the metric block fires
34
- ```
35
-
36
- ### Heavy — diff > 500 lines, or a cross-cutting bug fix
37
-
38
- Two required header categories. Body MUST contain at least one of `## Problem` or `## Summary`. Body MUST contain at least one of `## Test plan`, `## Testing`, `## Tests`, `## Verification`, or `## Validation`. Add `## Fix`, `## Changes`, `## Root cause`, or `## Approach` when the change earns them.
39
-
40
- ```markdown
41
- ## Problem
42
-
43
- Long-running `gh api` review fetches drop pages silently when the reviewer count crosses 30. Bugbot findings on PRs past the first review cycle stay hidden.
44
-
45
- ## Fix
46
-
47
- Routes every `gh api .../reviews` and `.../comments` call through `--paginate --slurp | jq` so the cross-page filter sees the full set.
48
-
49
- ## Test plan
50
-
51
- - [ ] Mock paginated API and assert `jq` filter operates on the merged stream
52
- - [ ] Replay PR #467 review history and confirm the late bugbot comment surfaces
53
- ```
54
-
55
- ## Style rules
56
-
57
- - Open with an imperative verb: `Adds`, `Fixes`, `Updates`, `Removes`, `Ports`, `Tightens`. 51% of prose-opening Anthropic PRs follow this pattern.
58
- - Do not open with `This PR`. The phrase appears in 1 of 120 corpus PRs. The enforcer hook treats `This PR adds/fixes/updates…` as a hard block.
59
- - Backticked identifiers (`function_name`, `--flag`, `CONST`) belong in intro prose. Anthropic PRs use them routinely.
60
- - Em-dashes — like these — work as parenthetical separators. 48% of corpus PRs use them.
61
- - Focus on WHY. The diff shows WHAT.
62
- - No code snippets in the description body. Reviewers read the diff for code.
63
- - No implementation-detail dumping. `Add a 5-second timeout` beats ``Add `pullStartedAt: Date.now()` parameter to the `runPull()` callback``.
64
- - No filler. Strike `In this PR I have made the following changes:` and start with the action.
65
-
66
- ## Gotchas
67
-
68
- - **Self-closing reference.** Do not write `Fixes #<the PR's own number>` in a `gh pr edit` or `gh pr comment` body. The enforcer hook reads the positional after `pr edit` or `pr comment` and blocks the body when the self-reference matches.
69
- - **Trivial shape with `## Summary` header.** A four-line body wrapped in `## Summary` trips the ceremony-on-Trivial check. Keep tiny bodies as plain prose.
70
- - **Heavy shape missing a testing header.** A 600-line PR body with `## Problem` and `## Fix` but no `## Test plan` / `## Testing` / `## Tests` block trips the Heavy required-headers check.
71
- - **`This PR` opening on any shape.** Hard block regardless of size.
72
- - **Dual-mode dispatch (unique pattern in `hooks/blocking/`).** The `pr_description_enforcer.py` hook reads `sys.argv` for `--readability-*` CLI flags as well as its stdin-JSON hook input. This dual-mode pattern is the only one of its kind across the blocking hooks directory. When extending the hook, preserve the precedence: CLI flags handled first and exited; stdin-JSON path falls through.
73
-
74
- ## Readability targets
75
-
76
- The enforcer hook computes a Flesch Reading Ease score plus sentence-length metrics on the intro paragraph and the first body section combined.
77
-
78
- | Metric | Target |
79
- |---|---|
80
- | Longest sentence | ≤ 28 words |
81
- | Average sentence | ≤ 18 words |
82
- | Flesch Reading Ease | ≥ 50 |
83
-
84
- The hook tracks a per-user strike counter at `~/.claude/state/pr_description_readability_strikes.json`. The first two readability failures emit a metric-specific block (e.g., `Readability: longest sentence is 32 words (maximum 28); split or rewrite the longest sentence`). The third triggering failure fires an escape-hatch message listing four recovery actions. The full action list lives in `PR_DESCRIPTION_GUIDE.md` under "Escape hatch".
85
-
86
- Hit the targets on first attempt by writing short sentences in common Anglo-Saxon words. The average tracks the corpus median of 14.5.
87
-
88
- ## Refusals
89
-
90
- Decline to write a body when:
91
-
92
- - The PR has zero diff lines (genuinely empty).
93
- - The user describes intent that contradicts the diff (e.g., "describe this as a bug fix" when the diff adds a feature).
94
- - The user requests a shape that violates the hook (e.g., `## Summary` on a one-line bump). Offer the correct shape.
24
+ Confirm every statement matches the diff and current pull request state. Return the final markdown and the file path ready for publication.
package/bin/install.mjs CHANGED
@@ -115,8 +115,10 @@ export const CORE_INCLUDE_DIRECTORIES = [
115
115
  ];
116
116
 
117
117
  export const CORE_SKILLS = [
118
+ 'imagegen',
118
119
  'orchestrator', 'orchestrator-refresh', 'team-advisor', 'grokify',
119
120
  'grok-spawn',
121
+ 'small-cl', 'comments', 'reviews', 'descriptions', 'emergencies',
120
122
  'anthropic-plan', 'everything-search',
121
123
  'privacy-hygiene',
122
124
  'issue-tracker',
@@ -928,7 +930,6 @@ export const FOLDED_HOOK_RELATIVE_PATHS = new Set([
928
930
  'blocking/state_description_blocker.py',
929
931
  'blocking/subprocess_budget_completeness.py',
930
932
  'blocking/hook_prose_detector_consistency.py',
931
- 'blocking/verified_commit_message_accuracy_blocker.py',
932
933
  'blocking/workflow_substitution_slot_blocker.py',
933
934
  'blocking/claude_md_orphan_file_blocker.py',
934
935
  'blocking/env_var_table_code_drift_blocker.py',
@@ -1491,23 +1492,58 @@ function loadPackageManagedDenyEntries() {
1491
1492
  }
1492
1493
 
1493
1494
  /**
1494
- * Merge package-owned permission defaults into ~/.claude/settings.json.
1495
+ * Load deny entries an earlier manifest records as package-managed.
1495
1496
  *
1496
- * @returns {{addedCount: number, alreadyPresentCount: number, managedDenyEntries: string[]}}
1497
+ * @returns {string[]}
1498
+ */
1499
+ function loadPriorManagedDenyEntries() {
1500
+ if (!existsSync(MANIFEST_FILE)) return [];
1501
+ try {
1502
+ const priorManifest = JSON.parse(readFileSync(MANIFEST_FILE, 'utf8'));
1503
+ return managedDenyEntriesFromPackageSettings({
1504
+ permissions: priorManifest?.[MANIFEST_MANAGED_PERMISSIONS_KEY],
1505
+ });
1506
+ } catch {
1507
+ return [];
1508
+ }
1509
+ }
1510
+
1511
+ /**
1512
+ * Reconcile package-managed permission defaults with ~/.claude/settings.json.
1513
+ *
1514
+ * @returns {{addedCount: number, alreadyPresentCount: number, removedCount: number, managedDenyEntries: string[]}}
1497
1515
  */
1498
1516
  function mergeManagedPermissions() {
1499
- const managedDenyEntries = loadPackageManagedDenyEntries();
1500
- if (managedDenyEntries.length === 0) {
1501
- return { addedCount: 0, alreadyPresentCount: 0, managedDenyEntries: [] };
1517
+ const allCurrentManagedDenyEntries = loadPackageManagedDenyEntries();
1518
+ const allPriorManagedDenyEntries = loadPriorManagedDenyEntries();
1519
+ const allCurrentManagedDenyEntrySet = new Set(allCurrentManagedDenyEntries);
1520
+ const allRetiredManagedDenyEntries = allPriorManagedDenyEntries.filter(
1521
+ eachEntry => !allCurrentManagedDenyEntrySet.has(eachEntry),
1522
+ );
1523
+ const hasCurrentManagedDenyEntries = allCurrentManagedDenyEntries.length > 0;
1524
+ const hasRetiredManagedDenyEntries = allRetiredManagedDenyEntries.length > 0;
1525
+ if (!hasCurrentManagedDenyEntries && !hasRetiredManagedDenyEntries) {
1526
+ return {
1527
+ addedCount: 0,
1528
+ alreadyPresentCount: 0,
1529
+ removedCount: 0,
1530
+ managedDenyEntries: [],
1531
+ };
1502
1532
  }
1503
1533
  const settingsPath = join(CLAUDE_HOME, SETTINGS_FILE_NAME);
1504
1534
  const settingsExisted = existsSync(settingsPath);
1505
1535
  const settings = loadClaudeSettingsObjectOrExit(settingsPath);
1506
- const mergeOutcome = mergeManagedPermissionsIntoSettings(settings, managedDenyEntries);
1507
- if (mergeOutcome.addedCount > 0 || !settingsExisted) {
1536
+ const pruneOutcome = pruneManagedPermissionsFromSettings(settings, allRetiredManagedDenyEntries);
1537
+ const mergeOutcome = hasCurrentManagedDenyEntries
1538
+ ? mergeManagedPermissionsIntoSettings(settings, allCurrentManagedDenyEntries)
1539
+ : { addedCount: 0, alreadyPresentCount: 0, managedDenyEntries: [] };
1540
+ const shouldWriteSettings = pruneOutcome.removedCount > 0
1541
+ || mergeOutcome.addedCount > 0
1542
+ || (!settingsExisted && hasCurrentManagedDenyEntries);
1543
+ if (shouldWriteSettings) {
1508
1544
  writeFileSync(settingsPath, JSON.stringify(settings, null, 4) + '\n');
1509
1545
  }
1510
- return mergeOutcome;
1546
+ return { ...mergeOutcome, removedCount: pruneOutcome.removedCount };
1511
1547
  }
1512
1548
 
1513
1549
  /**
@@ -2010,6 +2046,9 @@ function executeInstallPlanMutations(plan, transactionHelpers) {
2010
2046
 
2011
2047
  const permissionMerge = mergeManagedPermissions();
2012
2048
  summary.managedPermissions = permissionMerge;
2049
+ if (permissionMerge.removedCount > 0) {
2050
+ console.log(` Permissions: ${permissionMerge.removedCount} retired managed deny(s) removed`);
2051
+ }
2013
2052
  if (permissionMerge.managedDenyEntries.length > 0) {
2014
2053
  console.log(
2015
2054
  ` Permissions: ${permissionMerge.addedCount} managed deny(s) added, `
@@ -37,6 +37,13 @@ const RUNTIME_ARTIFACT_RELATIVE_SEGMENTS = ['scripts', '__pycache__', 'helper.cp
37
37
  const DEPENDENCY_STUB_PACKAGE_SEGMENTS = ['@jl-cmd', 'prompt-generator'];
38
38
  const DEPENDENCY_SUPPLIED_SKILL_DIRECTORY = 'dependency-supplied-skill';
39
39
  const SCOPED_GROUP_SKILL_DIRECTORY = 'orchestrator';
40
+ const CORE_REVIEW_GUIDE_SKILL_DIRECTORIES = [
41
+ 'small-cl',
42
+ 'comments',
43
+ 'reviews',
44
+ 'descriptions',
45
+ 'emergencies',
46
+ ];
40
47
  const PRIOR_RUN_BACKUP_DIRECTORY_NAMES = [
41
48
  '2020-01-01T00-00-00-000Z',
42
49
  '2021-06-15T12-30-45-123Z',
@@ -829,6 +836,25 @@ test('a scoped --only install records the skill names it installed', () => {
829
836
  }
830
837
  });
831
838
 
839
+ test('a scoped core install ships the canonical review guide skills', () => {
840
+ const sandbox = createSandbox();
841
+ try {
842
+ runInstaller(sandbox.homeDirectory, ['--only', 'core']);
843
+
844
+ const missingGuideSkillNames = CORE_REVIEW_GUIDE_SKILL_DIRECTORIES.filter(
845
+ eachSkillName => !existsSync(join(sandbox.skillsDirectory, eachSkillName, 'SKILL.md')),
846
+ );
847
+ assert.deepEqual(missingGuideSkillNames, []);
848
+
849
+ const recordedSkillNames = new Set(readManifest(sandbox.manifestPath).skills);
850
+ for (const eachSkillName of CORE_REVIEW_GUIDE_SKILL_DIRECTORIES) {
851
+ assert.equal(recordedSkillNames.has(eachSkillName), true);
852
+ }
853
+ } finally {
854
+ rmSync(sandbox.homeDirectory, { recursive: true, force: true });
855
+ }
856
+ });
857
+
832
858
  test('the prune skip is scoped: once the dependency resolves a later full install prunes normally', () => {
833
859
  const sandbox = createSandbox();
834
860
  try {
@@ -26,12 +26,15 @@ const PACKAGE_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
26
26
  const PACKAGE_SETTINGS_PATH = join(PACKAGE_ROOT, 'settings.json');
27
27
  const INSTALL_ENTRY = join(PACKAGE_ROOT, 'bin', 'install.mjs');
28
28
 
29
- const EXPECTED_DENY_ENTRIES = [
29
+ const EXPECTED_DENY_ENTRIES = [];
30
+ const SAMPLE_MANAGED_DENY_ENTRIES = ['Edit($HOME/.claude/managed-test/**)'];
31
+ const LEGACY_MANAGED_DENY_ENTRIES = [
30
32
  'Edit($HOME/.claude/verification/**)',
31
33
  'Write($HOME/.claude/code-review-stamps/**)',
32
34
  'Edit($HOME/.claude/code-review-stamps/**)',
33
35
  'MultiEdit($HOME/.claude/code-review-stamps/**)',
34
36
  ];
37
+ const USER_OWNED_DENY_ENTRY = 'Bash(rm -rf /)';
35
38
 
36
39
  function packageDenyEntries() {
37
40
  const packageSettings = JSON.parse(readFileSync(PACKAGE_SETTINGS_PATH, 'utf8'));
@@ -53,19 +56,18 @@ function runInstallerInSandbox(sandboxHome, installerArguments = []) {
53
56
  });
54
57
  }
55
58
 
56
- test('package settings.json publishes exactly the four managed deny entries', () => {
59
+ test('package settings.json publishes no managed deny entries', () => {
57
60
  const denyEntries = packageDenyEntries();
58
- assert.equal(denyEntries.length, 4);
59
61
  assert.deepEqual(denyEntries, EXPECTED_DENY_ENTRIES);
60
62
  });
61
63
 
62
64
  test('an array-valued permissions field is replaced so managed denies survive a round trip', () => {
63
65
  const targetSettings = { permissions: [] };
64
- const result = mergeManagedPermissionsIntoSettings(targetSettings, EXPECTED_DENY_ENTRIES);
65
- assert.equal(result.addedCount, EXPECTED_DENY_ENTRIES.length);
66
+ const result = mergeManagedPermissionsIntoSettings(targetSettings, SAMPLE_MANAGED_DENY_ENTRIES);
67
+ assert.equal(result.addedCount, SAMPLE_MANAGED_DENY_ENTRIES.length);
66
68
  assert.equal(Array.isArray(targetSettings.permissions), false);
67
69
  const roundTripped = JSON.parse(JSON.stringify(targetSettings));
68
- assert.deepEqual(roundTripped.permissions.deny, EXPECTED_DENY_ENTRIES);
70
+ assert.deepEqual(roundTripped.permissions.deny, SAMPLE_MANAGED_DENY_ENTRIES);
69
71
  });
70
72
 
71
73
  test('npm pack includes package settings.json in the published artifact', () => {
@@ -88,25 +90,28 @@ test('npm pack includes package settings.json in the published artifact', () =>
88
90
  );
89
91
  });
90
92
 
91
- test('mergeManagedPermissionsIntoSettings adds each package deny exactly once', () => {
93
+ test('mergeManagedPermissionsIntoSettings adds each managed deny exactly once', () => {
92
94
  const target = {
93
95
  permissions: {
94
96
  allow: ['Bash(git status)'],
95
97
  deny: ['Bash(rm -rf /)'],
96
98
  },
97
99
  };
98
- const first = mergeManagedPermissionsIntoSettings(target, EXPECTED_DENY_ENTRIES);
99
- assert.equal(first.addedCount, 4);
100
+ const first = mergeManagedPermissionsIntoSettings(target, SAMPLE_MANAGED_DENY_ENTRIES);
101
+ assert.equal(first.addedCount, SAMPLE_MANAGED_DENY_ENTRIES.length);
100
102
  assert.equal(first.alreadyPresentCount, 0);
101
- assert.equal(target.permissions.deny.filter((each) => EXPECTED_DENY_ENTRIES.includes(each)).length, 4);
103
+ const managedDenyCount = target.permissions.deny.filter((each) => (
104
+ SAMPLE_MANAGED_DENY_ENTRIES.includes(each)
105
+ )).length;
106
+ assert.equal(managedDenyCount, SAMPLE_MANAGED_DENY_ENTRIES.length);
102
107
  assert.ok(target.permissions.deny.includes('Bash(rm -rf /)'));
103
108
  assert.deepEqual(target.permissions.allow, ['Bash(git status)']);
104
109
 
105
- const second = mergeManagedPermissionsIntoSettings(target, EXPECTED_DENY_ENTRIES);
110
+ const second = mergeManagedPermissionsIntoSettings(target, SAMPLE_MANAGED_DENY_ENTRIES);
106
111
  assert.equal(second.addedCount, 0);
107
- assert.equal(second.alreadyPresentCount, 4);
112
+ assert.equal(second.alreadyPresentCount, SAMPLE_MANAGED_DENY_ENTRIES.length);
108
113
  assert.equal(
109
- target.permissions.deny.filter((each) => each === EXPECTED_DENY_ENTRIES[0]).length,
114
+ target.permissions.deny.filter((each) => each === SAMPLE_MANAGED_DENY_ENTRIES[0]).length,
110
115
  1,
111
116
  );
112
117
  });
@@ -118,18 +123,18 @@ test('pruneManagedPermissionsFromSettings removes only package-owned deny entrie
118
123
  ask: ['Edit(./**)'],
119
124
  deny: [
120
125
  'Bash(rm -rf /)',
121
- ...EXPECTED_DENY_ENTRIES,
126
+ ...SAMPLE_MANAGED_DENY_ENTRIES,
122
127
  ],
123
128
  },
124
129
  };
125
- const outcome = pruneManagedPermissionsFromSettings(target, EXPECTED_DENY_ENTRIES);
126
- assert.equal(outcome.removedCount, 4);
130
+ const outcome = pruneManagedPermissionsFromSettings(target, SAMPLE_MANAGED_DENY_ENTRIES);
131
+ assert.equal(outcome.removedCount, SAMPLE_MANAGED_DENY_ENTRIES.length);
127
132
  assert.deepEqual(target.permissions.deny, ['Bash(rm -rf /)']);
128
133
  assert.deepEqual(target.permissions.allow, ['Bash(git status)']);
129
134
  assert.deepEqual(target.permissions.ask, ['Edit(./**)']);
130
135
  });
131
136
 
132
- test('sandbox install adds all four deny entries once and is idempotent on repeat', () => {
137
+ test('sandbox install keeps default managed denies empty on repeat', () => {
133
138
  const sandboxHome = mkdtempSync(join(tmpdir(), 'cde-settings-defaults-'));
134
139
  try {
135
140
  const firstInstall = runInstallerInSandbox(sandboxHome, []);
@@ -139,30 +144,52 @@ test('sandbox install adds all four deny entries once and is idempotent on repea
139
144
  assert.ok(existsSync(settingsPath), 'settings.json written');
140
145
  const firstSettings = JSON.parse(readFileSync(settingsPath, 'utf8'));
141
146
  const firstDeny = firstSettings.permissions?.deny ?? [];
142
- for (const eachEntry of EXPECTED_DENY_ENTRIES) {
143
- assert.ok(firstDeny.includes(eachEntry), `missing deny: ${eachEntry}`);
144
- assert.equal(
145
- firstDeny.filter((candidate) => candidate === eachEntry).length,
146
- 1,
147
- `deny not unique: ${eachEntry}`,
148
- );
149
- }
147
+ assert.deepEqual(firstDeny, EXPECTED_DENY_ENTRIES);
150
148
 
151
149
  const manifestPath = join(sandboxHome, '.claude', '.claude-dev-env-manifest.json');
152
150
  const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
153
- assert.deepEqual(manifest.managedPermissions?.deny, EXPECTED_DENY_ENTRIES);
151
+ assert.deepEqual(manifest.managedPermissions?.deny ?? [], EXPECTED_DENY_ENTRIES);
154
152
 
155
153
  const secondInstall = runInstallerInSandbox(sandboxHome, []);
156
154
  assert.equal(secondInstall.status, 0, secondInstall.stderr || secondInstall.stdout);
157
155
  const secondSettings = JSON.parse(readFileSync(settingsPath, 'utf8'));
158
156
  const secondDeny = secondSettings.permissions?.deny ?? [];
159
- for (const eachEntry of EXPECTED_DENY_ENTRIES) {
160
- assert.equal(
161
- secondDeny.filter((candidate) => candidate === eachEntry).length,
162
- 1,
163
- `repeat install duplicated deny: ${eachEntry}`,
164
- );
165
- }
157
+ assert.deepEqual(secondDeny, EXPECTED_DENY_ENTRIES);
158
+ } finally {
159
+ rmSync(sandboxHome, { recursive: true, force: true });
160
+ }
161
+ });
162
+
163
+ test('a normal upgrade retires manifest-owned denies and preserves user entries', () => {
164
+ const sandboxHome = mkdtempSync(join(tmpdir(), 'cde-settings-upgrade-'));
165
+ try {
166
+ const initialInstall = runInstallerInSandbox(sandboxHome, []);
167
+ assert.equal(initialInstall.status, 0, initialInstall.stderr || initialInstall.stdout);
168
+
169
+ const settingsPath = join(sandboxHome, '.claude', 'settings.json');
170
+ const settings = JSON.parse(readFileSync(settingsPath, 'utf8'));
171
+ settings.permissions = {
172
+ allow: ['Bash(git status)'],
173
+ ask: ['Edit(./**)'],
174
+ deny: [USER_OWNED_DENY_ENTRY, ...LEGACY_MANAGED_DENY_ENTRIES],
175
+ };
176
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 4) + '\n');
177
+
178
+ const manifestPath = join(sandboxHome, '.claude', '.claude-dev-env-manifest.json');
179
+ const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
180
+ manifest.managedPermissions = { deny: LEGACY_MANAGED_DENY_ENTRIES };
181
+ writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n');
182
+
183
+ const upgradedInstall = runInstallerInSandbox(sandboxHome, []);
184
+ assert.equal(upgradedInstall.status, 0, upgradedInstall.stderr || upgradedInstall.stdout);
185
+
186
+ const upgradedSettings = JSON.parse(readFileSync(settingsPath, 'utf8'));
187
+ assert.deepEqual(upgradedSettings.permissions?.deny, [USER_OWNED_DENY_ENTRY]);
188
+ assert.deepEqual(upgradedSettings.permissions?.allow, ['Bash(git status)']);
189
+ assert.deepEqual(upgradedSettings.permissions?.ask, ['Edit(./**)']);
190
+
191
+ const upgradedManifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
192
+ assert.equal(Object.hasOwn(upgradedManifest, 'managedPermissions'), false);
166
193
  } finally {
167
194
  rmSync(sandboxHome, { recursive: true, force: true });
168
195
  }
@@ -953,7 +953,6 @@ const OLD_FOLDED_HOOKS_SETTINGS = {
953
953
  { type: 'command', command: 'py -3 C:/Users/x/.claude/hooks/blocking/state_description_blocker.py', timeout: 10 },
954
954
  { type: 'command', command: 'py -3 C:/Users/x/.claude/hooks/blocking/subprocess_budget_completeness.py', timeout: 10 },
955
955
  { type: 'command', command: 'py -3 C:/Users/x/.claude/hooks/blocking/hook_prose_detector_consistency.py', timeout: 10 },
956
- { type: 'command', command: 'py -3 C:/Users/x/.claude/hooks/blocking/verified_commit_message_accuracy_blocker.py', timeout: 10 },
957
956
  ],
958
957
  },
959
958
  {
@@ -971,8 +970,8 @@ const OLD_FOLDED_HOOKS_SETTINGS = {
971
970
  };
972
971
 
973
972
 
974
- test('FOLDED_HOOK_RELATIVE_PATHS contains all 16 hooks removed from hooks.json plus the retired md_to_html_blocker', () => {
975
- assert.equal(FOLDED_HOOK_RELATIVE_PATHS.size, 17);
973
+ test('FOLDED_HOOK_RELATIVE_PATHS contains all 15 hooks removed from hooks.json plus the retired md_to_html_blocker', () => {
974
+ assert.equal(FOLDED_HOOK_RELATIVE_PATHS.size, 16);
976
975
  assert.ok(FOLDED_HOOK_RELATIVE_PATHS.has('blocking/write_existing_file_blocker.py'));
977
976
  assert.ok(FOLDED_HOOK_RELATIVE_PATHS.has('blocking/plain_language_blocker.py'));
978
977
  assert.ok(FOLDED_HOOK_RELATIVE_PATHS.has('blocking/code_rules_enforcer.py'));
@@ -992,7 +991,6 @@ test('FOLDED_HOOK_RELATIVE_PATHS lists every hook the PreToolUse dispatcher host
992
991
  'blocking/state_description_blocker.py',
993
992
  'blocking/subprocess_budget_completeness.py',
994
993
  'blocking/hook_prose_detector_consistency.py',
995
- 'blocking/verified_commit_message_accuracy_blocker.py',
996
994
  'blocking/workflow_substitution_slot_blocker.py',
997
995
  'blocking/claude_md_orphan_file_blocker.py',
998
996
  'blocking/env_var_table_code_drift_blocker.py',
@@ -20,17 +20,21 @@ tool and repeats it until a pass returns zero new findings.
20
20
  3. Repeat the invocation. **Skips are sticky:** carry every adjudicated skip
21
21
  forward into the next pass as "already adjudicated — do not re-report: ..."
22
22
  context, or the loop never converges.
23
- 4. Phase A converges when a full pass returns zero new findings. When the
24
- previous pass changed only a line or two, a single combined-lens
25
- confirmation agent may serve as the final pass.
23
+ 4. Phase A converges when a full pass returns zero new findings, or when a
24
+ pass's new findings are all nits — doc phrasing, naming, comment altitude,
25
+ or any fix below a behavior change. On a nit-only pass: apply the fixes,
26
+ test, commit, push, and stop with no confirming pass. When the previous
27
+ pass changed only a line or two, a single combined-lens confirmation agent
28
+ may serve as the final pass.
26
29
 
27
30
  ## Phase B — loop /code-review low --fix
28
31
 
29
32
  1. Invoke the `code-review` skill with arguments `low --fix` on the same
30
33
  target. Let it report findings and apply its fixes.
31
34
  2. After any pass that changed files: test, commit, push as in Phase A.
32
- 3. Repeat until a pass reports zero findings. Phase B usually converges in one
33
- pass when Phase A ran first.
35
+ 3. Repeat until a pass reports zero findings, or until a pass reports only
36
+ nits then fix, test, commit, push, and stop with no confirming pass.
37
+ Phase B usually converges in one pass when Phase A ran first.
34
38
 
35
39
  ## Finish
36
40
 
package/docs/CLAUDE.md CHANGED
@@ -11,7 +11,6 @@ Reference documentation installed into `~/.claude/docs/` by `bin/install.mjs`. T
11
11
  | `BDD_DISCOVERY_PROTOCOL.md` | Example Mapping algorithm for discovery before implementation; based on Smart & Molak *BDD in Action* §6.4 |
12
12
  | `BDD_SCENARIO_QUALITY.md` | Seven scenario quality patterns (§7.6-style catalog) |
13
13
  | `BDD_TEST_LAYOUT.md` | `describe/when/should` test layout and soap-opera personas |
14
- | `PR_DESCRIPTION_GUIDE.md` | Authoritative reference for the `pr-description-writer` agent; PR body shapes derived from a 120-PR Anthropic corpus |
15
14
  | `DJANGO_PATTERNS.md` | Django-specific coding patterns |
16
15
  | `REACT_PATTERNS.md` | React-specific coding patterns |
17
16
  | `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 |
@@ -7,7 +7,6 @@ Pointer documents to external sources, standard terminology, and internal tool o
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 |
11
10
  | `prose-style-enforcement.md` | How `CLAUDE_PROSE_STYLE_ENFORCEMENT` arms opinionated prose gates (default off) while AskUserQuestion lean-block stays always on |
12
11
  | `advisor-tool.md` | Canonical consult bones for any stronger reviewer: when to call, hard rule before first write, how to treat advice; maps to the Anthropic advisor tool |
13
12
  | `team-advisor-skill.md` | `/team-advisor` map: sole-consumer warm bind, ref index, and advisor selection |
package/hooks/CLAUDE.md CHANGED
@@ -8,7 +8,7 @@ Python hook scripts wired into Claude Code's lifecycle via `settings.json`. Each
8
8
  |---|---|
9
9
  | `advisory/` | Hooks that warn but do not block (`permissionDecision: "ask"`) |
10
10
  | `blocking/` | Hooks that deny tool calls when a rule is violated |
11
- | `blocking/config/` | Shared constants for the verified-commit gate family |
11
+ | `blocking/config/` | Shared constants for blocking hooks |
12
12
  | `diagnostic/` | Hooks that record and extract hook-firing records into Neon |
13
13
  | `diagnostic/migrations/` | SQL migrations for the `hook_events` Neon schema |
14
14
  | `diagnostic/queries/` | Parameterized SQL queries for inspecting blocked commands |