claude-dev-env 2.32.0 → 2.33.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 (96) hide show
  1. package/.agents/agents/clean-coder.md +8 -7
  2. package/.agents/agents/code-quality-agent.md +4 -2
  3. package/.agents/agents/pr-description-writer.md +8 -3
  4. package/.agents/agents/reference/pr-description-illustrative-voice.md +6 -1
  5. package/.agents/agents/test_agent_frontmatter.py +0 -9
  6. package/.agents/agents/test_pr_description_writer_style.py +24 -1
  7. package/.agents/skills/e-simplify/SKILL.md +1 -15
  8. package/.agents/skills/grok-spawn/reference/worker-briefs.md +4 -0
  9. package/.agents/skills/pr-refinement/SKILL.md +2 -2
  10. package/.agents/skills/pr-shared-extraction/SKILL.md +2 -2
  11. package/.agents/skills/pr-title-description/SKILL.md +6 -2
  12. package/.agents/skills/review-tier/SKILL.md +1 -1
  13. package/.agents/skills/review-tier/reference/tier-policy.json +2 -2
  14. package/.agents/skills/review-tier/scripts/review_tier.py +31 -4
  15. package/.agents/skills/review-tier/scripts/test_generation.py +7 -19
  16. package/.agents/skills/review-tier/scripts/test_review_tier.py +11 -11
  17. package/AGENTS.md +83 -0
  18. package/_shared/pr-loop/code-rules-gate.md +5 -2
  19. package/_shared/pr-loop/scripts/code_rules_gate.py +7 -3
  20. package/_shared/pr-loop/scripts/code_rules_gate_parts/gate_arguments.py +13 -1
  21. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_gate_arguments.py +7 -0
  22. package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +47 -1
  23. package/audit-rubrics/audit-categories.json +2 -2
  24. package/audit-rubrics/category_rubrics/category-e-dead-code.md +1 -1
  25. package/audit-rubrics/category_rubrics/category-j-code-rules-compliance.md +5 -3
  26. package/audit-rubrics/category_rubrics/category-l-behavior-equivalence.md +6 -3
  27. package/audit-rubrics/prompts/category-e-dead-code.md +6 -6
  28. package/audit-rubrics/prompts/category-j-code-rules-compliance.md +25 -19
  29. package/audit-rubrics/prompts/category-l-behavior-equivalence.md +28 -25
  30. package/docs/CODE_RULES.md +7 -8
  31. package/hooks/blocking/code_rules_comments.py +118 -59
  32. package/hooks/blocking/code_rules_enforcer.py +19 -7
  33. package/hooks/blocking/code_rules_type_escape.py +16 -23
  34. package/hooks/blocking/javascript_comment_scanner.py +99 -0
  35. package/hooks/blocking/test_code_rules_enforcer_any_type_ignore.py +24 -4
  36. package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +0 -1
  37. package/hooks/blocking/test_code_rules_enforcer_comment_string_awareness.py +190 -3
  38. package/hooks/blocking/test_code_rules_enforcer_javascript_comments.py +44 -0
  39. package/hooks/blocking/test_code_rules_enforcer_join_separator_magic.py +0 -1
  40. package/hooks/git-hooks/pull_request_handoff.py +1 -1
  41. package/hooks/git-hooks/test_post_commit.py +8 -0
  42. package/hooks/hooks_constants/stale_comment_reference_blocker_constants.py +3 -4
  43. package/hooks/hooks_constants/test_unused_module_import_constants.py +9 -1
  44. package/hooks/hooks_constants/unused_module_import_constants.py +1 -1
  45. package/hooks/validators/comment_checks.py +2 -2
  46. package/hooks/validators/fast_save_validators.py +0 -9
  47. package/hooks/validators/git_checks.py +0 -84
  48. package/hooks/validators/run_all_validators.py +2 -9
  49. package/hooks/validators/test_fast_save_validators.py +2 -8
  50. package/hooks/validators/test_git_checks.py +0 -249
  51. package/hooks/validators/test_todo_checks.py +1 -0
  52. package/hooks/validators/todo_checks.py +1 -1
  53. package/package.json +1 -1
  54. package/rules/git-workflow.md +0 -8
  55. package/scripts/check.ps1 +35 -1
  56. package/scripts/codex_compat_materializer.py +1 -0
  57. package/scripts/tests/test_rules.py +111 -0
  58. package/system-prompts/software-engineer.xml +6 -19
  59. package/.agents/skills-archived/split-pr/SKILL.md +0 -85
  60. package/.agents/skills-archived/split-pr/reference/path-layers.md +0 -16
  61. package/.agents/skills-archived/split-pr/reference/proposal-format.md +0 -15
  62. package/.agents/skills-archived/split-pr/reference/split-further-loop.md +0 -10
  63. package/.agents/skills-archived/split-pr/reference/splitting-principles.md +0 -26
  64. package/.agents/skills-archived/split-pr/scripts/analyze_pr.py +0 -279
  65. package/.agents/skills-archived/split-pr/scripts/categorize_files.py +0 -106
  66. package/.agents/skills-archived/split-pr/scripts/config/__init__.py +0 -1
  67. package/.agents/skills-archived/split-pr/scripts/config/dependency_constants.py +0 -14
  68. package/.agents/skills-archived/split-pr/scripts/config/git_operations_constants.py +0 -36
  69. package/.agents/skills-archived/split-pr/scripts/config/packing_constants.py +0 -61
  70. package/.agents/skills-archived/split-pr/scripts/config/plan_constants.py +0 -49
  71. package/.agents/skills-archived/split-pr/scripts/config/split_pr_constants.py +0 -110
  72. package/.agents/skills-archived/split-pr/scripts/execute_split_slices.py +0 -82
  73. package/.agents/skills-archived/split-pr/scripts/pack_files_into_slices.py +0 -212
  74. package/.agents/skills-archived/split-pr/scripts/split_pr_dependency_graph.py +0 -70
  75. package/.agents/skills-archived/split-pr/scripts/split_pr_git_operations.py +0 -184
  76. package/.agents/skills-archived/split-pr/scripts/split_pr_layer_order.py +0 -58
  77. package/.agents/skills-archived/split-pr/scripts/split_pr_paginate.py +0 -119
  78. package/.agents/skills-archived/split-pr/scripts/split_pr_process_runner.py +0 -52
  79. package/.agents/skills-archived/split-pr/scripts/split_pr_script_types.py +0 -126
  80. package/.agents/skills-archived/split-pr/scripts/split_pr_title.py +0 -41
  81. package/.agents/skills-archived/split-pr/scripts/test_analyze_pr.py +0 -228
  82. package/.agents/skills-archived/split-pr/scripts/test_categorize_files.py +0 -55
  83. package/.agents/skills-archived/split-pr/scripts/test_categorize_files_packing.py +0 -59
  84. package/.agents/skills-archived/split-pr/scripts/test_execute_split_slices.py +0 -99
  85. package/.agents/skills-archived/split-pr/scripts/test_split_pr_dependency_graph.py +0 -47
  86. package/.agents/skills-archived/split-pr/scripts/test_split_pr_git_operations.py +0 -125
  87. package/.agents/skills-archived/split-pr/scripts/test_split_pr_layer_order.py +0 -36
  88. package/.agents/skills-archived/split-pr/scripts/test_split_pr_paginate.py +0 -65
  89. package/.agents/skills-archived/split-pr/scripts/test_split_pr_script_types.py +0 -73
  90. package/.agents/skills-archived/split-pr/scripts/test_split_pr_title.py +0 -28
  91. package/.agents/skills-archived/split-pr/scripts/test_verify_dependency_graph.py +0 -46
  92. package/.agents/skills-archived/split-pr/scripts/test_verify_plan.py +0 -56
  93. package/.agents/skills-archived/split-pr/scripts/test_verify_plan_contract.py +0 -50
  94. package/.agents/skills-archived/split-pr/scripts/test_verify_plan_path_normalization.py +0 -45
  95. package/.agents/skills-archived/split-pr/scripts/verify_dependency_graph.py +0 -111
  96. package/.agents/skills-archived/split-pr/scripts/verify_plan.py +0 -139
@@ -38,7 +38,6 @@ These shape how you think while writing. Mechanical rules live in the canonical
38
38
  7. **One meaning per variable** — new names for each transformation stage.
39
39
  8. **Visual rhythm** — paragraph breaks; walls become named helpers.
40
40
 
41
- 9. **Complexity budget** — state the budget before implementation. Keep the change to 1–2 files and ~50–300 lines. Keep each function to about 40 executable lines and a nesting level of 2. Split the work or record why the budget does not fit.
42
41
 
43
42
  ## Canonical policy map (do not restate)
44
43
 
@@ -81,7 +80,7 @@ Load only the group that matches the task. Keep session policy details in these
81
80
 
82
81
  Material implementation questions must return to the caller for `AskUserQuestion` handling; do not ask in plain text or guess.
83
82
 
84
- Type-ignore rule (AGENTS Types): a `# type: ignore` needs a second trailing `#` justification of at least five characters. Prefer a real type when available.
83
+ Type-ignore rule (AGENTS Types): avoid `# type: ignore`; remove it and use a typed boundary or real type.
85
84
 
86
85
  Constants (AGENTS Magic values): use named constants from the target layout. Search its constants module first. Do not force a generic `config/` layout. Example:
87
86
 
@@ -134,11 +133,13 @@ Consult the advisor the spawn ticket names. When the ticket names the orchestrat
134
133
  ```
135
134
  [1] Local config searched and reused?
136
135
  [2] Full words; correct naming prefixes?
137
- [3] Parameters and returns typed; no bare Any / bare type: ignore?
138
- [4] No new production inline comments; existing comments preserved?
139
- [5] Magic values and UPPER_SNAKE live in config/ where required?
140
- [6] Function short; one job; guards over else-chains?
141
- [7] Pre-check --check clean for the real destination path?
136
+ [3] Parameters and returns typed; no Any / no type-ignore directives?
137
+ [4] Do not add code comments. Preserve existing comments. Docstrings remain allowed.
138
+ [5] When a change touches code that an existing comment describes or is attached to, remove that comment in the same change and carry its meaning through clear names and structure. Leave comments tied to untouched code unchanged. Keep comment cleanup inside the requested task.
139
+ [6] Production and tests follow one rule. Changed directive, TODO, FIXME, HACK, XXX, and type-ignore comments are removed rather than added or justified.
140
+ [7] Magic values and UPPER_SNAKE live in config/ where required?
141
+ [8] Function short; one job; guards over else-chains?
142
+ [9] Pre-check --check clean for the real destination path?
142
143
  ```
143
144
 
144
145
  ## Scope, TDD, and outcomes
@@ -61,7 +61,9 @@ Tradeoff for category-restricted mode: parallel category invocation loses cross-
61
61
 
62
62
  ## Comment Preservation
63
63
 
64
- Preserve every existing comment. Findings on production code report only on new code added by the diff; existing comments on lines that remain otherwise unchanged stay outside the audit's scope. New inline comments added by this PR's diff are themselves a category J finding (production code self-documents through naming).
64
+ Leave comments tied to untouched code unchanged. Findings on production and test code report only on new code added by the diff; comments on lines that remain otherwise unchanged stay outside the audit's scope. New inline comments added by this PR's diff are themselves a category J finding (code self-documents through naming).
65
+ When a change touches code that an existing comment describes or is attached to, remove that comment in the same change and carry its meaning through clear names and structure. Leave comments tied to untouched code unchanged. Keep comment cleanup inside the requested task.
66
+ Production and tests follow one rule. Changed directive, TODO, FIXME, HACK, XXX, and type-ignore comments are removed rather than added or justified.
65
67
 
66
68
  ## Read-Only Stance
67
69
 
@@ -93,7 +95,7 @@ For each category's full description, examples, sub-bucket decomposition, and co
93
95
  | P | Name / regex / word-list vs behavior-contract precision | `<managed-root>/audit-rubrics/category_rubrics/category-p-name-vs-behavior-contract.md` (source fallback: `packages/claude-dev-env/audit-rubrics/category_rubrics/category-p-name-vs-behavior-contract.md`) |
94
96
  | Q | Cross-surface claim consistency (terminology, PR-description claims, message-vs-guard) | `<managed-root>/audit-rubrics/category_rubrics/category-q-cross-surface-claims.md` (source fallback: `packages/claude-dev-env/audit-rubrics/category_rubrics/category-q-cross-surface-claims.md`) |
95
97
 
96
- Test files (`test_*.py`, `*_test.py`, `*.test.*`, `*.spec.*`, `conftest.py`, and any path under `/tests/`) are exempt from category J. The exempt path families documented in the J reference also opt out of the constants-location sub-item.
98
+ Test files use the same no-new-comment policy. The exempt path families documented in the J reference still opt out of the constants-location sub-item.
97
99
 
98
100
  Category K Shape A findings always cite TWO line locations: the changed line and the unchanged-but-should-have-changed parallel line. The `failure_mode` field describes the contradiction between the two states. K is narrow but recurrent — linters and unit tests rarely catch these findings.
99
101
 
@@ -16,16 +16,21 @@ Explain the change so a smart reader who knows nothing about the code can pictur
16
16
  - Prefer a tiny Before / After story when the change is about how something is found, asked for, checked, saved, retried, or stopped.
17
17
  - Use small words and picture words.
18
18
  - Replace jargon with the action it represents. When one technical term matters, explain it immediately in plain words.
19
- - Keep the main behavior in `What this adds`, the reason in `Why`, and proof in `Verification`.
20
19
  - Keep titles concrete. Say what someone gives it, what happens, or what comes back.
21
20
 
21
+ ## Full-diff behavior standard
22
+
23
+ Before drafting, inventory every independently observable behavior in the full diff. A behavior is independent when a user, caller, operator, automation, output, error, exit status, fallback, or side effect can observe it separately. Lead with the central behavior for readability, then give every other behavior its own short paragraph or bullet.
24
+
25
+ For each behavior, state the trigger or observer, the Before -> After result, the affected surface/caller when shared, and focused proof. Include important preserved behavior and fallback paths when they help a coder rule a regression in or out. Describe outcomes, not line-by-line implementation, and do not hide multiple behaviors under vague umbrella wording.
26
+
22
27
  For a concrete voice sample only when needed, read `reference/pr-description-illustrative-voice.md` beside this agent. For a concrete verification sample only when needed, read `reference/pr-description-verification.md`. These samples guide the shape and voice; they are not content to copy into unrelated pull requests.
23
28
 
24
29
  ## Draft the body
25
30
 
26
31
  1. Inspect the cumulative diff, the current pull request body, and the validation results.
27
- 2. Find the main behavior a user, caller, operator, or reviewer can see.
28
- 3. Write that behavior first in plain illustrative language.
32
+ 2. Build the full-diff inventory and name every independently observable behavior.
33
+ 3. Write the central behavior first, then give each other behavior its own short paragraph or bullet.
29
34
  4. Preserve accurate issue links and curated sections from the current body when they remain useful.
30
35
  5. Use headings when they help a reviewer scan the change.
31
36
  6. In `Verification`, lead with what a person can open, see, click, compare, or try for themselves. Put test results in one short supporting line when useful.
@@ -12,6 +12,12 @@ After: show the new action in a tiny scene.
12
12
 
13
13
  Keep machine words out when a plain action works. If one technical term matters, explain it in the same breath with a simple picture.
14
14
 
15
+ ## Full-diff behavior standard
16
+
17
+ Before writing, inventory every independently observable behavior in the full diff. A behavior is independent when a user, caller, operator, automation, output, error, exit status, fallback, or side effect can observe it separately. Lead with the central behavior for readability, then give every other behavior its own short paragraph or bullet.
18
+
19
+ For each behavior, state the trigger or observer, the Before -> After result, the affected surface/caller when shared, and focused proof. Include important preserved behavior and fallback paths when they help a coder rule a regression in or out. Describe outcomes, not line-by-line implementation, and do not hide multiple behaviors under vague umbrella wording.
20
+
15
21
  ## Sample 1 — call the right helper directly
16
22
 
17
23
  The theme package has a phone icon somewhere. We need to find it.
@@ -39,7 +45,6 @@ The person running the loop asked for a full look, so every pass gets a full loo
39
45
  - Prefer Before / After when the change is about how something is asked, found, checked, saved, retried, or stopped.
40
46
  - Use picture words: hand it, open it, find it, check it, save it, skip it, stop, call by name.
41
47
  - Prefer spoken results: “found it,” “missing,” “duplicated,” “couldn’t.”
42
- - Keep the main behavior in `What this adds`.
43
48
  - Keep `Why` short and concrete.
44
49
  - Put proof only in `Verification`.
45
50
  - Explain jargon immediately or replace it.
@@ -934,15 +934,6 @@ def test_code_quality_agent_contract_forbids_mutating_commands() -> None:
934
934
  assert "run no commands that write files or create PRs." in body
935
935
 
936
936
 
937
- def test_clean_coder_sets_a_small_complexity_budget() -> None:
938
- body = _clean_coder_body()
939
- assert "Complexity budget" in body
940
- assert "1–2 files" in body
941
- assert "~50–300 lines" in body
942
- assert "about 40 executable lines" in body
943
- assert "nesting level of 2" in body
944
-
945
-
946
937
  def test_clean_coder_defines_a_liveness_boundary_for_dead_code_cleanup() -> None:
947
938
  body = _clean_coder_body()
948
939
  assert "orphaned or dead code" in body.lower()
@@ -29,7 +29,6 @@ def test_voice_reference_keeps_the_plain_illustrative_shape() -> None:
29
29
  "found it",
30
30
  "missing",
31
31
  "duplicated",
32
- "What this adds",
33
32
  "Why",
34
33
  "Verification",
35
34
  ):
@@ -45,3 +44,27 @@ def test_verification_leads_with_a_human_visible_check() -> None:
45
44
  assert visible_action in reference_text
46
45
  assert "Tests: focused checks pass." in reference_text
47
46
  assert "Machine checks can support the claim." in reference_text
47
+
48
+
49
+ def test_full_diff_behavior_standard_is_explicit_in_writer_sources() -> None:
50
+ all_writer_source_texts = [
51
+ (AGENT_DIRECTORY.parent / "skills" / "pr-title-description" / "SKILL.md").read_text(
52
+ encoding="utf-8"
53
+ ),
54
+ AGENT_PATH.read_text(encoding="utf-8"),
55
+ VOICE_REFERENCE_PATH.read_text(encoding="utf-8"),
56
+ ]
57
+
58
+ for each_required_text in (
59
+ "independently observable behavior",
60
+ "Before -> After result",
61
+ "affected surface/caller when shared",
62
+ "focused proof",
63
+ "preserved behavior and fallback paths",
64
+ "outcomes, not line-by-line implementation",
65
+ "vague umbrella wording",
66
+ ):
67
+ assert all(
68
+ each_required_text in each_source_text
69
+ for each_source_text in all_writer_source_texts
70
+ )
@@ -40,23 +40,9 @@ include the working-tree changes in scope — the review often runs before the
40
40
  commit. If a PR number, branch name, or file path was passed as an argument,
41
41
  review that target instead. Treat this diff as the review scope.
42
42
 
43
- **Diff-size cap.** Run `git diff --shortstat` on the same range Phase 0 used
44
- to get the diff, and add its insertions and deletions for the changed-line
45
- count. Default ceiling: 400 changed lines for one run — a tunable default the
46
- user may adjust. Above the ceiling, narrow the target first — a subdirectory,
47
- a package, or one file path — and review that narrowed target. Do not launch
48
- Phase 1 over a diff above the ceiling.
49
-
50
43
  ### Phase 1 — Review (up to 4 cleanup agents in parallel)
51
44
 
52
- **Lens cap.** Four lenses is the ceiling, not the floor. Default split — also
53
- tunable — by the `git diff --shortstat` changed-line count from Phase 0:
54
- under 100 changed lines, run 2 lenses (reuse and simplification); at 100
55
- changed lines or above, and under the diff-size cap, run all 4 lenses.
56
-
57
- Launch the review agents for the lenses in scope for this run — 2 or 4 by the
58
- cap above — via the Agent tool, all in a single message so they run
59
- concurrently. Pass each agent the diff and one of the angles below. Each
45
+ Launch all four review agents via the Agent tool in one message so they run concurrently. Pass each agent the diff and one angle below. Each
60
46
  returns its findings with `file`, `line`, a one-line `summary`, and the
61
47
  concrete cost (what is duplicated, wasted, or harder to maintain).
62
48
 
@@ -61,6 +61,10 @@ session owns every git and GitHub step.
61
61
  You edit code and run tests for one closed task. You never commit, push, or call
62
62
  `gh`. The lead session stages, verifies, commits, pushes, and posts.
63
63
 
64
+ Do not add code comments. Preserve existing comments. Docstrings remain allowed.
65
+ When a change touches code that an existing comment describes or is attached to, remove that comment in the same change and carry its meaning through clear names and structure. Leave comments tied to untouched code unchanged. Keep comment cleanup inside the requested task.
66
+ Production and tests follow one rule. Changed directive, TODO, FIXME, HACK, XXX, and type-ignore comments are removed rather than added or justified.
67
+
64
68
  ## Scope
65
69
 
66
70
  - Working directory: [absolute path]
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  name: pr-refinement
3
- description: Coordinate shared-extraction and capability-naming audits, then update an existing pull request or publish a required focused GitHub pull-request stack. Use when a user asks to turn a PR architecture audit into extracted shared code and small pull requests.
3
+ description: Coordinate shared-extraction and capability-naming audits, then update an existing pull request or publish a focused GitHub pull-request stack. Use when a user asks to turn a PR architecture audit into extracted shared code and small pull requests.
4
4
  ---
5
5
 
6
6
  # PR Refinement
7
7
 
8
- Turn audit findings into a focused existing pull-request update or a required dependency-ordered GitHub pull-request stack.
8
+ Turn audit findings into a focused existing pull-request update or a dependency-ordered GitHub pull-request stack.
9
9
 
10
10
  ## Peer skills
11
11
 
@@ -42,7 +42,7 @@ Shared extraction audit:
42
42
  - [ ] 3. Grep for offense patterns (see reference/offense-taxonomy.md)
43
43
  - [ ] 4. Write prioritized findings (P0–P3)
44
44
  - [ ] 5. Apply **Mode routing** after the report
45
- - [ ] 6. Extract in small CLs (~100 lines) + move/adjust tests
45
+ - [ ] 6. Extract in small CLs + move/adjust tests
46
46
  - [ ] 7. Run scoped pytest, then follow the selected mode's mutation boundary
47
47
  - [ ] 8. When the mode is `preflight-proposal`, record proposal evidence from the [shared preflight proposal contract](../_shared/pr-loop/preflight-proposal.md) and require downstream owner selection before reapplication
48
48
  ```
@@ -115,7 +115,7 @@ Use [reference/offense-taxonomy.md](reference/offense-taxonomy.md). Assign prior
115
115
  3. **Retarget every caller, then delete the old module** — grep the old import path; remove re-export facades
116
116
  4. **Constants:** one source; workflow CLI keeps only workflow-specific names (`LEDGER_FILENAME`, exit codes)
117
117
  5. **Tests move with behavior** — shared module gets shared tests; orchestration keeps integration tests only
118
- 6. **Small CLs** — one concern per commit (~100 lines); run scoped pytest after each
118
+ 6. **Small CLs** — one concern per commit; run scoped pytest after each
119
119
 
120
120
  ### Canonical target map (this repo)
121
121
 
@@ -12,7 +12,12 @@ Review this pull request: <PR link>
12
12
 
13
13
  Read the PR and its full diff. Do not use the branch name, commit message, labels, current title, or a shallow summary.
14
14
 
15
- Find the main thing this adds for a user, caller, or operator.
15
+ ## Full-diff behavior standard
16
+
17
+ Before writing, inventory every independently observable behavior in the full diff. A behavior is independent when a user, caller, operator, automation, output, error, exit status, fallback, or side effect can observe it separately. Lead with the central behavior for readability, then give every other behavior its own short paragraph or bullet.
18
+
19
+ For each behavior, state the trigger or observer, the Before -> After result, the affected surface/caller when shared, and focused proof. Include important preserved behavior and fallback paths when they help a coder rule a regression in or out. Describe outcomes, not line-by-line implementation, and do not hide multiple behaviors under vague umbrella wording.
20
+
16
21
 
17
22
  Write like the reader is smart but knows nothing about this code. Pretend they asked you to explain it like they are five and feel stupid. If a word needs another sentence to explain it, replace the word — or turn it into a tiny story.
18
23
 
@@ -68,7 +73,6 @@ Then write a short, paste-ready PR description.
68
73
  Use this structure:
69
74
 
70
75
  What this adds
71
- 1–2 short paragraphs in that illustrative voice. Lead with a scene when you can (“The theme package has a phone icon somewhere…”). If the change is a new way to ask, use Before / After. Say what you give it, what it does with its hands, and what you get back. If an abstract machine-word must appear, gloss it immediately (“That’s all X is — …”). Describe the job people can see. Do not describe inner machinery. Mention a check only if it is central to the feature. If there are many changes, keep the main one. Mention others only if they explain the main one.
72
76
 
73
77
  Why
74
78
  One short paragraph. Same plain level. Say who needs this and what goes wrong without it (the “yell and wait for a hand” problem). If this is needed to decode the feature, rewrite “What this adds.”
@@ -29,7 +29,7 @@ Use for `/review-tier` and automatic review classification. Refuse execution req
29
29
 
30
30
  ## Examples
31
31
 
32
- Input `{"files": 0, "lines": 0}` produces `T1`. Input `{"hard_triggers": ["migration"]}` produces `T3`. Input `{"files": 0}` with override `T2` produces calculated `T1` and effective `T2`.
32
+ Input `{"packages": 0, "risk": 0}` produces `T1`. Input `{"hard_triggers": ["migration"]}` produces `T3`. An empty input with override `T2` produces calculated `T1` and effective `T2`.
33
33
 
34
34
  ## Dependencies
35
35
 
@@ -3,7 +3,7 @@
3
3
  "tiers": ["T1", "T2", "T3"],
4
4
  "thresholds": {"T1": 0, "T2": 2, "T3": 5},
5
5
  "hard_triggers": ["security", "migration", "public-api", "release"],
6
- "axes": ["files", "lines", "packages", "risk", "public_api", "dependencies"],
7
- "axis_weights": {"files": 1, "lines": 1, "packages": 2, "risk": 2, "public_api": 2, "dependencies": 1},
6
+ "axes": ["packages", "risk", "public_api", "dependencies"],
7
+ "axis_weights": {"packages": 2, "risk": 2, "public_api": 2, "dependencies": 1},
8
8
  "approved_downgrades": ["T1", "T2"]
9
9
  }
@@ -6,10 +6,38 @@ import hashlib
6
6
  import json
7
7
  import os
8
8
  import subprocess
9
+ from collections.abc import Mapping, Sequence
9
10
  from pathlib import Path
10
- from typing import Mapping, Sequence
11
11
 
12
- from review_tier_constants.config.constants import ALL_DEPENDENCY_MARKERS, ALL_HARD_TRIGGER_MARKERS, ALL_PACKAGE_ROOT_NAMES, ALL_PUBLIC_API_MARKERS, ALL_SOURCE_SUFFIXES, ALL_STATUS_ARGUMENTS, ALL_STATUS_DOMAINS, ALL_TIER_ORDER, ALL_UNTRACKED_ARGUMENTS, AMBIGUOUS_BASE_REF, DEFAULT_BRANCH_FALLBACK, GIT_COMMAND_FAILED, GIT_HEAD_REF, INVALID_BASE_REF, JSON_INDENT, MALFORMED_TIER_POLICY, MAX_AXIS_VALUE, MIN_NONEMPTY_RISK, PATH_SEPARATOR, PLUGIN_DATA_ENVIRONMENT, REMOTE_HEAD_REF, ROOT_PACKAGE, ROUTING_STATE_ROOT_TRACKED, STATUS_CODE_LENGTH, STATUS_PREFIX_LENGTH, UNAPPROVED_TIER_DOWNGRADE, UNKNOWN_TIER
12
+ from review_tier_constants.config.constants import (
13
+ ALL_DEPENDENCY_MARKERS,
14
+ ALL_HARD_TRIGGER_MARKERS,
15
+ ALL_PACKAGE_ROOT_NAMES,
16
+ ALL_PUBLIC_API_MARKERS,
17
+ ALL_SOURCE_SUFFIXES,
18
+ ALL_STATUS_ARGUMENTS,
19
+ ALL_STATUS_DOMAINS,
20
+ ALL_TIER_ORDER,
21
+ ALL_UNTRACKED_ARGUMENTS,
22
+ AMBIGUOUS_BASE_REF,
23
+ DEFAULT_BRANCH_FALLBACK,
24
+ GIT_COMMAND_FAILED,
25
+ GIT_HEAD_REF,
26
+ INVALID_BASE_REF,
27
+ JSON_INDENT,
28
+ MALFORMED_TIER_POLICY,
29
+ MAX_AXIS_VALUE,
30
+ MIN_NONEMPTY_RISK,
31
+ PATH_SEPARATOR,
32
+ PLUGIN_DATA_ENVIRONMENT,
33
+ REMOTE_HEAD_REF,
34
+ ROOT_PACKAGE,
35
+ ROUTING_STATE_ROOT_TRACKED,
36
+ STATUS_CODE_LENGTH,
37
+ STATUS_PREFIX_LENGTH,
38
+ UNAPPROVED_TIER_DOWNGRADE,
39
+ UNKNOWN_TIER,
40
+ )
13
41
 
14
42
 
15
43
  def router_state_root(repo_root: str | Path, all_environment: Mapping[str, str] | None = None) -> Path:
@@ -197,9 +225,8 @@ def inventory_generation(repo_root: str | Path, base_ref: str | None = None, sta
197
225
  untracked_identities = [{"path": each_path, "sha256": record["content_sha256"]} for each_path, record in ((record["path"], record) for record in records) if "untracked" in record["domains"]]
198
226
  base_source = "explicit" if base_ref else ("remote_default" if requested_base == REMOTE_HEAD_REF else "configured_fallback")
199
227
  canonical = json.dumps({"base_ref": requested_base, "base_source": base_source, "base_sha": base, "merge_base_sha": merge_base, "head_sha": head, "domains": {each_domain: sorted(all_paths) for each_domain, all_paths in domains.items()}, "files": records, "untracked_identities": sorted(untracked_identities, key=lambda record: record["path"])}, sort_keys=True, separators=(",", ":")).encode() + patch_bytes
200
- lines = sum(int(record["added"]) + int(record["deleted"]) for record in records)
201
228
  digest = hashlib.sha256(canonical).hexdigest()
202
- return {"paths": [record["path"] for record in records], "files_by_domain": records, "committed": sorted(committed), "staged": sorted(domains["staged"]), "unstaged": sorted(domains["unstaged"]), "untracked": sorted(domains["untracked"]), "base_ref": requested_base, "base_source": base_source, "base_sha": base, "merge_base_sha": merge_base, "head_sha": head, "merge_base": merge_base, "HEAD": head, "inventory_hash": digest, "diff_hash": digest, "files": len(records), "lines": lines, "packages": len(packages), "risk": MIN_NONEMPTY_RISK if records else 0, "public_api": public_api, "dependencies": dependencies, "hard_triggers": triggers}
229
+ return {"paths": [record["path"] for record in records], "files_by_domain": records, "committed": sorted(committed), "staged": sorted(domains["staged"]), "unstaged": sorted(domains["unstaged"]), "untracked": sorted(domains["untracked"]), "base_ref": requested_base, "base_source": base_source, "base_sha": base, "merge_base_sha": merge_base, "head_sha": head, "merge_base": merge_base, "HEAD": head, "inventory_hash": digest, "diff_hash": digest, "packages": len(packages), "risk": MIN_NONEMPTY_RISK if records else 0, "public_api": public_api, "dependencies": dependencies, "hard_triggers": triggers}
203
230
 
204
231
 
205
232
  def _package_key(path: str) -> str:
@@ -2,8 +2,13 @@ import subprocess
2
2
  from pathlib import Path
3
3
 
4
4
  import pytest
5
-
6
- from review_tier import build_generation, inventory_generation, router_state_directory, router_state_root, router_state_root_id
5
+ from review_tier import (
6
+ build_generation,
7
+ inventory_generation,
8
+ router_state_directory,
9
+ router_state_root,
10
+ router_state_root_id,
11
+ )
7
12
 
8
13
 
9
14
  def test_generation_uses_live_git_surface(tmp_path: Path) -> None:
@@ -94,23 +99,6 @@ def test_diff_hash_changes_when_same_path_content_changes(tmp_path: Path) -> Non
94
99
  assert inventory_generation(tmp_path)["diff_hash"] != first_hash
95
100
 
96
101
 
97
- def test_effective_lines_include_committed_staged_unstaged_and_untracked(tmp_path: Path) -> None:
98
- _git_repository(tmp_path)
99
- committed_path = tmp_path / "committed.txt"
100
- committed_path.write_text("one\ntwo\n", encoding="utf-8")
101
- subprocess.run(["git", "add", "."], cwd=tmp_path, check=True)
102
- subprocess.run(["git", "commit", "-qm", "committed"], cwd=tmp_path, check=True)
103
- staged_path = tmp_path / "staged.txt"
104
- staged_path.write_text("one\ntwo\n", encoding="utf-8")
105
- subprocess.run(["git", "add", str(staged_path)], cwd=tmp_path, check=True)
106
- staged_path.write_text("one\ntwo\nthree\n", encoding="utf-8")
107
- unstaged_path = tmp_path / "seed.txt"
108
- unstaged_path.write_text("seed\nchanged\n", encoding="utf-8")
109
- (tmp_path / "untracked.txt").write_text("one\ntwo\n", encoding="utf-8")
110
- inventory = inventory_generation(tmp_path)
111
- assert inventory["lines"] >= 6
112
-
113
-
114
102
  def test_inventory_preserves_rename_prior_path_and_operations(tmp_path: Path) -> None:
115
103
  _git_repository(tmp_path)
116
104
  old_path = tmp_path / "old name.txt"
@@ -1,4 +1,11 @@
1
- from review_tier import build_decision, calculate_tier, canonical_json_hash, effective_tier, load_policy, policy_hash
1
+ from review_tier import (
2
+ build_decision,
3
+ calculate_tier,
4
+ canonical_json_hash,
5
+ effective_tier,
6
+ load_policy,
7
+ policy_hash,
8
+ )
2
9
 
3
10
 
4
11
  def test_public_policy_functions_return_behavior() -> None:
@@ -10,24 +17,17 @@ def test_public_policy_functions_return_behavior() -> None:
10
17
 
11
18
 
12
19
  def test_tier_boundaries_and_hard_trigger() -> None:
13
- assert calculate_tier({"files": 0, "lines": 0, "packages": 0, "risk": 0}) == "T1"
14
- assert calculate_tier({"files": 2}) == "T2"
15
- assert calculate_tier({"files": 5}) == "T2"
20
+ assert calculate_tier({"packages": 0, "risk": 0}) == "T1"
16
21
  assert calculate_tier({"hard_triggers": ["security"]}) == "T3"
17
22
 
18
23
 
19
- def test_all_six_axes_accept_zero_one_two_boundaries() -> None:
20
- axes = ("files", "lines", "packages", "risk", "public_api", "dependencies")
24
+ def test_all_axes_accept_zero_one_two_boundaries() -> None:
25
+ axes = ("packages", "risk", "public_api", "dependencies")
21
26
  assert calculate_tier({axis: 0 for axis in axes}) == "T1"
22
27
  assert calculate_tier({axis: 1 for axis in axes}) == "T3"
23
28
  assert calculate_tier({axis: 2 for axis in axes}) == "T3"
24
29
 
25
30
 
26
- def test_score_boundaries_keep_small_changes_below_t3() -> None:
27
- assert calculate_tier({"files": 1, "lines": 1}) == "T2"
28
- assert calculate_tier({"files": 2, "packages": 1, "risk": 1}) == "T3"
29
-
30
-
31
31
  def test_upward_and_approved_downward_override() -> None:
32
32
  assert effective_tier("T1", "T3") == "T3"
33
33
  assert effective_tier("T3", "T1") == "T1"
package/AGENTS.md CHANGED
@@ -1,3 +1,7 @@
1
+ Do not add code comments. Preserve existing comments. Docstrings remain allowed.
2
+ When a change touches code that an existing comment describes or is attached to, remove that comment in the same change and carry its meaning through clear names and structure. Leave comments tied to untouched code unchanged. Keep comment cleanup inside the requested task.
3
+ Production and tests follow one rule. Changed directive, TODO, FIXME, HACK, XXX, and type-ignore comments are removed rather than added or justified.
4
+
1
5
  Use positive, present-focused prose.
2
6
 
3
7
  State what should be done, what something does, what was done, or what needs to be done.
@@ -5,3 +9,82 @@ State what should be done, what something does, what was done, or what needs to
5
9
  Include only information directly relevant to the immediate task or remaining work.
6
10
 
7
11
  Use direct outcome-focused wording. Omit filler, failed attempts, alternatives considered, and process narration.
12
+
13
+ Cut AI tells from any writing. Must always apply.
14
+
15
+ Edit text to remove AI patterns and add human voice.
16
+
17
+ Process
18
+ Scan for the patterns below.
19
+ Rewrite. Preserve meaning, match intended tone.
20
+ Add soul (see next section).
21
+ Self-audit: "What makes this obviously AI generated?" Fix remaining tells.
22
+
23
+
24
+ Adding soul
25
+
26
+ Removing patterns is half the job. Sterile, voiceless writing is just as obvious.
27
+ Have opinions. React to facts instead of neutrally listing pros and cons.
28
+ Vary rhythm. Short sentences. Then longer ones that take their time. Mix it up.
29
+ Acknowledge complexity. "Impressive but also kind of unsettling" beats "impressive."
30
+ Use "I" when it fits. First person isn't unprofessional.
31
+ Let some mess in. Perfect structure looks machine-made.
32
+ Be specific. Not "this is concerning" but "there's something unsettling about agents churning away at 3am."
33
+ Patterns to detect and fix
34
+
35
+
36
+ Content
37
+
38
+ Puffery. "pivotal moment", "testament to", "evolving landscape", "setting the stage for", "indelible mark", "deeply rooted". Cut puffery, state what happened.
39
+ Name-dropping. Listing media outlets without context. Pick one, say what was said.
40
+ Superficial -ing phrases. "highlighting...", "ensuring...", "reflecting...", "showcasing...", "fostering...". Delete or expand with real sources.
41
+ Promotional language. "nestled", "vibrant", "breathtaking", "groundbreaking", "renowned", "stunning", "must-visit". Use neutral descriptions.
42
+ Vague attributions. "Experts believe", "Industry reports suggest", "Some critics argue". Name the source or delete.
43
+ Formulaic challenges. "Despite challenges... continues to thrive." Replace with specific facts.
44
+
45
+
46
+ Language
47
+
48
+ AI vocabulary. Additionally, crucial, delve, enduring, enhance, fostering, garner, interplay, intricate, landscape (abstract), pivotal, showcase, tapestry (abstract), testament, underscore, vibrant. Replace with plain words.
49
+ Fancy ways to say "is". "serves as", "stands as", "boasts", "features". Just say "is" or "has".
50
+ "Not just X, but Y." State the point directly instead.
51
+ Rule of three. Forcing ideas into groups of three. Use the natural number.
52
+ Synonym cycling. Protagonist, main character, central figure, hero all in one paragraph. Pick one, repeat it.
53
+ False ranges. "from X to Y" where X and Y aren't on a meaningful scale. List topics directly.
54
+
55
+
56
+ Style
57
+
58
+ Em dash overuse. Avoid em dashes entirely. Use periods or commas only (no parentheses, no en dashes, no hyphen-as-dash substitutes). Em dashes are an AI tell, and reaching for parentheses instead just trades one tell for another. If a thought needs separation, end the sentence or use a comma.
59
+ Colon overuse. Colons are fine before a list or example. Not as mid-sentence connectors. "If you're coming from traditional automation: instead of registering event handlers, you describe conditions" adds nothing with the colon. Rewrite to let the point stand on its own without comparison framing. "Describing when the scheduler should fire works best as plain English." Same meaning, no crutch punctuation.
60
+ Boldface overuse. Don't bold every proper noun or acronym.
61
+ Inline-header lists. The tell is a bold label and colon that restates the line: "Performance: Performance improved...". Convert those to prose. A bold lead-in that ends in a period, names the item, and is followed by genuinely new detail ("Schema in TypeScript. Tables live in one file.") is fine, not a tell.
62
+ Title case headings. Use sentence case.
63
+ Decorative emojis. Remove from headings and bullets.
64
+ Curly quotes. Replace with straight quotes.
65
+
66
+
67
+ Communication artifacts
68
+
69
+ Chatbot phrases. "I hope this helps!", "Let me know if...", "Of course!", "Certainly!", "Found the smoking gun!" Remove.
70
+ Cutoff disclaimers. "While specific details are limited..." Find sources or remove.
71
+ Sycophantic tone. "Great question! You're absolutely right!" Respond directly.
72
+
73
+
74
+ Filler
75
+ Filler phrases. "In order to" becomes "To". "Due to the fact that" becomes "Because". "It is important to note that" gets deleted.
76
+ Excessive hedging. "could potentially possibly be argued that it might" becomes "may".
77
+ Generic conclusions. "The future looks bright." State specific plans or facts.
78
+
79
+
80
+ Jargon
81
+ Abstract metaphor nouns. Substrate, wedge, vector, locus, vantage, nexus, primitive (as noun), harness (as metaphor), surface (as in "API surface"), bedrock, scaffolding (as metaphor), modality, paradigm, gold-plating, ratchet (as metaphor), evacuate (for moving code), endgame, north star, flywheel. These read as technical but usually have a plainer concrete word. "Substrate" becomes "base". "Wedge in" becomes "add". "Vector" becomes "way" or "method". "Gold-plating" becomes "more than the job needs". "Ratchet" becomes the mechanism's real name or "a limit that only tightens". "Evacuate" becomes "move out". "Endgame" becomes "the last phase". Pick the concrete word.
82
+
83
+
84
+ Plain speech
85
+
86
+ Say what it does, not how it feels. "the database stays close at hand", "SQL you can read", "types that follow your schema" name a feeling. The fix names the mechanism or a number: ".toSQL() returns the exact string sent to the database", "a column rename fails the build". Ask what the sentence tells the reader to do or know, then write that. If you can't restate it as a concrete instruction, fact, or number, cut it. One more check: if the sentence could appear unchanged in another project's docs, it says nothing about this one. Cut it.
87
+ Shorten or split dense sentences. If the reader has to backtrack to parse a sentence, break it in two or drop clauses. One idea per sentence.
88
+ Active voice. Prefer it. Catch "is/are/was/were + past participle" and name the actor: "queries are validated" becomes "the compiler validates queries", "the file is parsed by the loader" becomes "the loader parses the file". Passive is fine only when the actor is unknown or genuinely doesn't matter.
89
+ Cut adverbs, or use a stronger verb. "runs quickly" becomes "is fast" or the number. "significantly improves" becomes the measured delta. An adverb propping up a weak verb means the verb is wrong.
90
+ Prefer the plain word. "utilize" becomes "use", "leverage" becomes "use", "facilitate" becomes "help", "numerous" becomes "many", "in the event that" becomes "if". The fancier synonym is rarely clearer.
@@ -44,13 +44,16 @@ python "<gate_script>" --base origin/<base_branch> --only-under <prefix> [--only
44
44
  - File-global UPPER_SNAKE constants (must live in `config/` outside exempt path families)
45
45
  - Magic values in production function bodies (literals other than 0, 1, -1)
46
46
  - Imports outside top of module
47
- - New comments in production code (existing comments preserved untouched)
47
+ - New code comments anywhere; comments tied to touched code are removed and comments tied to untouched code remain unchanged
48
48
  - File-global constants used by fewer than 2 functions/methods
49
49
  - Logging format-arg violations
50
50
  - Database column-name string magic (snake_case strings as first element of 2-tuples in function bodies)
51
51
  - Public-wrapper-drops-optional-kwargs of same-file delegates
52
52
 
53
- Test files (`test_*.py`, `*_test.py`, `*.spec.*`, `conftest.py`, paths under `/tests/`) are exempt from comment, magic-value, and constants-location rules.
53
+ Test files use the same no-new-comment policy; their magic-value and constants-location exemptions remain.
54
+
55
+ When a change touches code that an existing comment describes or is attached to, remove that comment in the same change and carry its meaning through clear names and structure. Leave comments tied to untouched code unchanged. Keep comment cleanup inside the requested task.
56
+ Production and tests follow one rule. Changed directive, TODO, FIXME, HACK, XXX, and type-ignore comments are removed rather than added or justified.
54
57
 
55
58
  ## Halt-fix-rerun protocol
56
59
 
@@ -2,7 +2,8 @@
2
2
 
3
3
  ::
4
4
 
5
- default mode: git diff since merge-base, joined with untracked files
5
+ default mode: git diff since merge-base, joined with untracked files;
6
+ --comment-policy: opt into changed-comment findings
6
7
  --staged: validate the staged index; --paths: validate explicit files
7
8
  --immediate: validate staged rules and terminology at commit time
8
9
  every mode ends by naming how many files it inspected
@@ -13,6 +14,7 @@ and re-exports their surface for the test suite.
13
14
 
14
15
  import argparse
15
16
  import sys
17
+ from functools import partial
16
18
  from pathlib import Path
17
19
 
18
20
 
@@ -44,7 +46,6 @@ try:
44
46
  ALL_WINDOWS_VENV_PYTHON_RELATIVE_PATH_SEGMENTS,
45
47
  EMPTY_FILE_SET_EXIT_CODE,
46
48
  EMPTY_FILE_SET_MESSAGE,
47
- IMMEDIATE_SCOPE_ARGUMENT,
48
49
  INSPECTED_COUNT_MESSAGE,
49
50
  MAXIMUM_STAGED_PYTEST_COMMAND_LINE_CHARACTERS,
50
51
  MINIMUM_STAGED_PYTEST_PYTHON_MAJOR,
@@ -357,7 +358,10 @@ def main(all_arguments: list[str]) -> int:
357
358
  repository_root = (
358
359
  arguments.repo_root.resolve() if arguments.repo_root is not None else Path.cwd().resolve()
359
360
  )
360
- validate_content = load_validate_content_for_full_gate()
361
+ validate_content = partial(
362
+ load_validate_content_for_full_gate(),
363
+ include_comment_policy=arguments.comment_policy,
364
+ )
361
365
  if arguments.paths:
362
366
  return _run_explicit_paths_mode(validate_content, arguments, repository_root)
363
367
  if arguments.immediate:
@@ -9,6 +9,16 @@ from pr_loop_shared_constants.code_rules_gate_constants import (
9
9
  )
10
10
 
11
11
 
12
+ def _add_comment_policy_argument(parser: argparse.ArgumentParser) -> None:
13
+ """Add the opt-in diff-aware comment-policy switch."""
14
+ parser.add_argument(
15
+ "--comment-policy",
16
+ action="store_true",
17
+ default=False,
18
+ help="Include the opt-in diff-aware comment-policy checks.",
19
+ )
20
+
21
+
12
22
  def _add_source_arguments(parser: argparse.ArgumentParser) -> None:
13
23
  """Add the repo-root, base-ref, and staged-scope arguments to *parser*."""
14
24
  parser.add_argument(
@@ -22,6 +32,7 @@ def _add_source_arguments(parser: argparse.ArgumentParser) -> None:
22
32
  default="origin/main",
23
33
  help="Merge-base ref for git diff (default: origin/main).",
24
34
  )
35
+ _add_comment_policy_argument(parser)
25
36
  staged_scope_group = parser.add_mutually_exclusive_group()
26
37
  staged_scope_group.add_argument(
27
38
  "--staged",
@@ -77,6 +88,7 @@ def parse_arguments(all_arguments: list[str]) -> argparse.Namespace:
77
88
 
78
89
  Returns:
79
90
  The parsed namespace with ``repo_root``, ``base``, ``staged``,
80
- ``immediate``, ``only_under``, and ``paths`` attributes.
91
+ ``immediate``, ``comment_policy``, ``only_under``, and ``paths``
92
+ attributes.
81
93
  """
82
94
  return _build_argument_parser().parse_args(all_arguments)
@@ -25,5 +25,12 @@ def test_parse_arguments_collects_only_under_and_paths() -> None:
25
25
  def test_parse_arguments_defaults_base_to_origin_main() -> None:
26
26
  arguments = gate_arguments.parse_arguments([])
27
27
  assert arguments.base == "origin/main"
28
+ assert arguments.comment_policy is False
28
29
  assert arguments.staged is False
29
30
  assert arguments.only_under == []
31
+
32
+
33
+ def test_parse_arguments_accepts_opt_in_comment_policy() -> None:
34
+ arguments = gate_arguments.parse_arguments(["--comment-policy"])
35
+
36
+ assert arguments.comment_policy is True