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.
- package/_shared/pr-loop/scripts/code_rules_gate.py +2 -3
- package/_shared/pr-loop/scripts/test_code_rules_gate.py +0 -26
- package/agents/CLAUDE.md +1 -2
- package/agents/pr-description-writer.md +11 -81
- package/bin/install.mjs +48 -9
- package/bin/install.prune.test.mjs +26 -0
- package/bin/install.settings-defaults.test.mjs +60 -33
- package/bin/install.test.mjs +2 -4
- package/commands/sr-loop.md +9 -5
- package/docs/CLAUDE.md +0 -1
- package/docs/references/CLAUDE.md +0 -1
- package/hooks/CLAUDE.md +1 -1
- package/hooks/blocking/CLAUDE.md +2 -27
- package/hooks/blocking/config/CLAUDE.md +1 -13
- package/hooks/blocking/conventional_pr_title_gate.py +1 -2
- package/hooks/blocking/send_user_file_open_locally_blocker.py +1 -1
- package/hooks/blocking/stop_dispatcher.py +4 -4
- package/hooks/blocking/test_bash_pre_tool_use_dispatcher.py +0 -3
- package/hooks/blocking/test_pre_tool_use_dispatcher.py +6 -7
- package/hooks/blocking/test_send_user_file_open_locally_blocker.py +4 -3
- package/hooks/blocking/test_shared_stdin_adoption.py +0 -2
- package/hooks/diagnostic/CLAUDE.md +3 -3
- package/hooks/diagnostic/hook_log_extractor.py +2 -36
- package/hooks/diagnostic/hook_log_stop_wrapper.py +6 -155
- package/hooks/diagnostic/test_hook_log_extractor.py +8 -21
- package/hooks/diagnostic/test_hook_log_stop_wrapper.py +3 -331
- package/hooks/git-hooks/git_hooks_constants/__init__.py +0 -6
- package/hooks/git-hooks/pre_push.py +3 -85
- package/hooks/git-hooks/test_pre_push.py +0 -130
- package/hooks/hooks.json +0 -35
- package/hooks/hooks_constants/CLAUDE.md +0 -9
- package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +0 -11
- package/hooks/hooks_constants/convergence_branch_constants.py +0 -1
- package/hooks/hooks_constants/fable_spawn_gate_constants.py +3 -4
- package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +0 -8
- package/hooks/hooks_constants/send_user_file_open_locally_blocker_constants.py +1 -6
- package/hooks/hooks_constants/stop_dispatcher_constants.py +0 -1
- package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +1 -26
- package/hooks/hooks_constants/test_pre_tool_use_dispatcher_constants.py +0 -5
- package/hooks/hooks_constants/test_stop_dispatcher_constants.py +0 -1
- package/hooks/lifecycle/CLAUDE.md +1 -3
- package/package.json +1 -1
- package/rules/CLAUDE.md +0 -2
- package/rules/gh-cli-conventions.md +0 -1
- package/rules/git-workflow.md +1 -3
- package/scripts/CLAUDE.md +0 -1
- package/scripts/_code_review_test_support.py +0 -95
- package/scripts/dev_env_scripts_constants/code_review_constants.py +9 -98
- package/scripts/dev_env_scripts_constants/test_code_review_constants.py +1 -44
- package/scripts/invoke_code_review.py +8 -451
- package/scripts/test_invoke_code_review.py +5 -61
- package/scripts/test_invoke_code_review_cli.py +1 -45
- package/scripts/test_invoke_code_review_contract.py +1 -118
- package/settings.json +1 -10
- package/skills/CLAUDE.md +8 -0
- package/skills/autoconverge/reference/CLAUDE.md +1 -1
- package/skills/autoconverge/reference/convergence.md +7 -7
- package/skills/autoconverge/reference/gotchas.md +3 -10
- package/skills/autoconverge/workflow/converge.contract.test.mjs +53 -1388
- package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +4 -3
- package/skills/autoconverge/workflow/converge.fix-recovery.test.mjs +33 -493
- package/skills/autoconverge/workflow/converge.merge-conflict.test.mjs +15 -97
- package/skills/autoconverge/workflow/converge.mjs +201 -238
- package/skills/autoconverge/workflow/converge.precatch.test.mjs +1 -1
- package/skills/bugteam/CONSTRAINTS.md +1 -1
- package/skills/bugteam/SKILL.md +9 -0
- package/skills/bugteam/reference/audit-and-teammates.md +10 -6
- package/skills/bugteam/test_skill_additions.py +9 -0
- package/skills/comments/SKILL.md +65 -0
- package/skills/descriptions/SKILL.md +51 -0
- package/skills/emergencies/SKILL.md +42 -0
- package/skills/imagegen/SKILL.md +21 -0
- package/skills/imagegen/scripts/config/__init__.py +1 -0
- package/skills/imagegen/scripts/config/constants.py +28 -0
- package/skills/imagegen/scripts/imagegen.py +45 -0
- package/skills/imagegen/scripts/imagegen_core.py +528 -0
- package/skills/imagegen/scripts/test_imagegen.py +204 -0
- package/skills/orchestrator/SKILL.md +2 -4
- package/skills/plan-to-pr/SKILL.md +6 -7
- package/skills/plan-to-pr/reference/final-validation-tasks.md +1 -1
- package/skills/plan-to-pr/reference/process-inventory.md +2 -2
- package/skills/plan-to-pr/reference/review-loop.md +2 -2
- package/skills/plan-to-pr/reference/run-record.schema.json +1 -1
- package/skills/plan-to-pr/reference/task-seeds.md +1 -1
- package/skills/plan-to-pr/reference/task-ticket.md +3 -4
- package/skills/plan-to-pr/scripts/config/constants.py +1 -2
- package/skills/plan-to-pr/scripts/test_validate_protocol.py +1 -1
- package/skills/plan-to-pr/test_skill_contract.py +2 -2
- package/skills/pr-converge/SKILL.md +13 -16
- package/skills/pr-converge/reference/progress-checklist.md +6 -5
- package/skills/pr-converge/reference/state-schema.md +1 -27
- package/skills/pr-loop-cloud-transport/SKILL.md +1 -1
- package/skills/pr-loop-cloud-transport/reference/identity-and-hooks.md +3 -3
- package/skills/pr-loop-lifecycle/SKILL.md +1 -1
- package/skills/pr-loop-lifecycle/reference/teardown-publish-permissions.md +1 -1
- package/skills/prototype/SKILL.md +4 -4
- package/skills/prototype/reference/honest-limitations.md +4 -4
- package/skills/prototype/reference/promotion-tasks.md +2 -2
- package/skills/prototype/workflows/promotion.md +4 -6
- package/skills/reviews/SKILL.md +85 -0
- package/skills/small-cl/SKILL.md +52 -0
- package/agents/code-verifier.md +0 -74
- package/docs/PR_DESCRIPTION_GUIDE.md +0 -157
- package/docs/references/code-review-enforcement.md +0 -132
- package/hooks/blocking/code_review_enforcement_config_bootstrap.py +0 -53
- package/hooks/blocking/code_review_gate_deny.py +0 -74
- package/hooks/blocking/code_review_pr_create_gate.py +0 -198
- package/hooks/blocking/code_review_push_gate.py +0 -145
- package/hooks/blocking/code_review_stamp_directory_write_blocker.py +0 -348
- package/hooks/blocking/code_review_stamp_store.py +0 -233
- package/hooks/blocking/code_review_stamp_write_blocker_parts/__init__.py +0 -7
- package/hooks/blocking/code_review_stamp_write_blocker_parts/conftest.py +0 -15
- package/hooks/blocking/code_review_stamp_write_blocker_parts/obfuscated_stamp_path_reference.py +0 -212
- package/hooks/blocking/code_review_stamp_write_blocker_parts/split_directory_change_into_stamp.py +0 -138
- package/hooks/blocking/code_review_stamp_write_blocker_parts/test_obfuscated_stamp_path_reference.py +0 -49
- package/hooks/blocking/code_review_stamp_write_blocker_parts/test_split_directory_change_into_stamp.py +0 -38
- package/hooks/blocking/code_verifier_spawn_preflight_gate.py +0 -483
- package/hooks/blocking/config/code_review_enforcement_constants.py +0 -143
- package/hooks/blocking/config/test_code_review_enforcement_constants.py +0 -166
- package/hooks/blocking/config/verified_commit_constants.py +0 -160
- package/hooks/blocking/config/verified_commit_context_constants.py +0 -21
- package/hooks/blocking/config/verified_commit_gate_output_constants.py +0 -14
- package/hooks/blocking/conftest.py +0 -123
- package/hooks/blocking/convergence_gate_blocker.py +0 -310
- package/hooks/blocking/pr_converge_bugteam_enforcer.py +0 -170
- package/hooks/blocking/pr_description_body_audit.py +0 -148
- package/hooks/blocking/pr_description_command_parser.py +0 -237
- package/hooks/blocking/pr_description_enforcer.py +0 -227
- package/hooks/blocking/pr_description_pr_number.py +0 -155
- package/hooks/blocking/pr_description_proof_of_work.py +0 -385
- package/hooks/blocking/pr_description_readability.py +0 -364
- package/hooks/blocking/reviewer_spawn_gate.py +0 -182
- package/hooks/blocking/test_code_review_enforcement_config_bootstrap.py +0 -62
- package/hooks/blocking/test_code_review_gate_deny.py +0 -54
- package/hooks/blocking/test_code_review_pr_create_gate.py +0 -199
- package/hooks/blocking/test_code_review_push_gate.py +0 -205
- package/hooks/blocking/test_code_review_stamp_directory_write_blocker.py +0 -199
- package/hooks/blocking/test_code_review_stamp_store.py +0 -205
- package/hooks/blocking/test_code_verifier_spawn_preflight_gate.py +0 -710
- package/hooks/blocking/test_code_verifier_tools_contract.py +0 -28
- package/hooks/blocking/test_convergence_gate_blocker.py +0 -428
- package/hooks/blocking/test_pr_converge_bugteam_enforcer.py +0 -311
- package/hooks/blocking/test_pr_converge_bugteam_enforcer_state_tolerance.py +0 -184
- package/hooks/blocking/test_pr_description_enforcer.py +0 -221
- package/hooks/blocking/test_pr_description_enforcer_body_audit.py +0 -247
- package/hooks/blocking/test_pr_description_enforcer_body_rules.py +0 -493
- package/hooks/blocking/test_pr_description_enforcer_command_parser.py +0 -366
- package/hooks/blocking/test_pr_description_enforcer_pr_number.py +0 -159
- package/hooks/blocking/test_pr_description_enforcer_proof_gate.py +0 -175
- package/hooks/blocking/test_pr_description_enforcer_readability.py +0 -443
- package/hooks/blocking/test_pr_description_proof_of_work.py +0 -313
- package/hooks/blocking/test_reviewer_spawn_gate.py +0 -230
- package/hooks/blocking/test_verdict_directory_write_blocker.py +0 -804
- package/hooks/blocking/test_verification_verdict_store.py +0 -974
- package/hooks/blocking/test_verified_commit_config_bootstrap.py +0 -67
- package/hooks/blocking/test_verified_commit_docs_delta.py +0 -176
- package/hooks/blocking/test_verified_commit_gate.py +0 -581
- package/hooks/blocking/test_verified_commit_gate_additional_context.py +0 -134
- package/hooks/blocking/test_verified_commit_message_accuracy_blocker.py +0 -131
- package/hooks/blocking/test_verifier_verdict_minter.py +0 -299
- package/hooks/blocking/tests/test_verified_commit_gate.py +0 -41
- package/hooks/blocking/verdict_directory_write_blocker.py +0 -687
- package/hooks/blocking/verification_verdict_store.py +0 -1039
- package/hooks/blocking/verified_commit_config_bootstrap.py +0 -63
- package/hooks/blocking/verified_commit_gate.py +0 -113
- package/hooks/blocking/verified_commit_gate_parts/CLAUDE.md +0 -28
- package/hooks/blocking/verified_commit_gate_parts/__init__.py +0 -1
- package/hooks/blocking/verified_commit_gate_parts/command_tokenization.py +0 -174
- package/hooks/blocking/verified_commit_gate_parts/deny_payload.py +0 -53
- package/hooks/blocking/verified_commit_gate_parts/deny_reason.py +0 -80
- package/hooks/blocking/verified_commit_gate_parts/directory_resolution.py +0 -170
- package/hooks/blocking/verified_commit_gate_parts/gated_invocations.py +0 -217
- package/hooks/blocking/verified_commit_gate_parts/tests/conftest.py +0 -10
- package/hooks/blocking/verified_commit_gate_parts/tests/test_command_tokenization.py +0 -94
- package/hooks/blocking/verified_commit_gate_parts/tests/test_deny_payload.py +0 -17
- package/hooks/blocking/verified_commit_gate_parts/tests/test_deny_reason.py +0 -38
- package/hooks/blocking/verified_commit_gate_parts/tests/test_directory_resolution.py +0 -71
- package/hooks/blocking/verified_commit_gate_parts/tests/test_gated_invocations.py +0 -96
- package/hooks/blocking/verified_commit_message_accuracy_blocker.py +0 -167
- package/hooks/blocking/verifier_verdict_minter.py +0 -280
- package/hooks/hooks_constants/code_verifier_spawn_preflight_gate_constants.py +0 -64
- package/hooks/hooks_constants/convergence_gate_blocker_constants.py +0 -53
- package/hooks/hooks_constants/pr_converge_bugteam_enforcer_constants.py +0 -55
- package/hooks/hooks_constants/pr_converge_bugteam_enforcer_state.py +0 -67
- package/hooks/hooks_constants/pr_description_enforcer_constants.py +0 -167
- package/hooks/hooks_constants/pr_description_proof_of_work_constants.py +0 -107
- package/hooks/hooks_constants/reviewer_spawn_gate_constants.py +0 -41
- package/hooks/hooks_constants/test_pr_description_enforcer_constants.py +0 -292
- package/hooks/lifecycle/pr_converge_bugteam_skill_tracker.py +0 -198
- package/hooks/lifecycle/test_pr_converge_bugteam_skill_tracker.py +0 -283
- package/rules/proof-of-work-pr-comments.md +0 -22
- package/rules/verified-commit-gate-skip.md +0 -4
- 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
|
-
|
|
120
|
-
|
|
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 |
|
|
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
|
|
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
|
-
|
|
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
|
-
##
|
|
11
|
+
## Draft the body
|
|
12
12
|
|
|
13
|
-
|
|
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
|
-
|
|
18
|
+
## Publish through GitHub CLI
|
|
16
19
|
|
|
17
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
1495
|
+
* Load deny entries an earlier manifest records as package-managed.
|
|
1495
1496
|
*
|
|
1496
|
-
* @returns {
|
|
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
|
|
1500
|
-
|
|
1501
|
-
|
|
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
|
|
1507
|
-
|
|
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
|
|
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,
|
|
65
|
-
assert.equal(result.addedCount,
|
|
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,
|
|
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
|
|
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,
|
|
99
|
-
assert.equal(first.addedCount,
|
|
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
|
-
|
|
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,
|
|
110
|
+
const second = mergeManagedPermissionsIntoSettings(target, SAMPLE_MANAGED_DENY_ENTRIES);
|
|
106
111
|
assert.equal(second.addedCount, 0);
|
|
107
|
-
assert.equal(second.alreadyPresentCount,
|
|
112
|
+
assert.equal(second.alreadyPresentCount, SAMPLE_MANAGED_DENY_ENTRIES.length);
|
|
108
113
|
assert.equal(
|
|
109
|
-
target.permissions.deny.filter((each) => each ===
|
|
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
|
-
...
|
|
126
|
+
...SAMPLE_MANAGED_DENY_ENTRIES,
|
|
122
127
|
],
|
|
123
128
|
},
|
|
124
129
|
};
|
|
125
|
-
const outcome = pruneManagedPermissionsFromSettings(target,
|
|
126
|
-
assert.equal(outcome.removedCount,
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
}
|
package/bin/install.test.mjs
CHANGED
|
@@ -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
|
|
975
|
-
assert.equal(FOLDED_HOOK_RELATIVE_PATHS.size,
|
|
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',
|
package/commands/sr-loop.md
CHANGED
|
@@ -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
|
|
24
|
-
|
|
25
|
-
|
|
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
|
|
33
|
-
|
|
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
|
|
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 |
|