claude-dev-env 1.94.0 → 2.0.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 (280) hide show
  1. package/_shared/advisor/CLAUDE.md +2 -2
  2. package/_shared/advisor/advisor-protocol.md +41 -33
  3. package/_shared/advisor/scripts/config/advisor_scripts_constants/model_tier_run_validator_constants.py +17 -13
  4. package/_shared/advisor/scripts/model_tier_run_validator.py +27 -18
  5. package/_shared/advisor/scripts/tests/test_model_tier_run_validator.py +95 -25
  6. package/_shared/advisor/scripts/tests/test_tier_model_ids.py +17 -17
  7. package/_shared/advisor/scripts/tier_model_ids.py +18 -18
  8. package/_shared/pr-loop/CLAUDE.md +1 -0
  9. package/_shared/pr-loop/scripts/CLAUDE.md +2 -1
  10. package/_shared/pr-loop/scripts/README.md +1 -0
  11. package/_shared/pr-loop/scripts/code_rules_gate.py +253 -1980
  12. package/_shared/pr-loop/scripts/code_rules_gate_parts/CLAUDE.md +32 -0
  13. package/_shared/pr-loop/scripts/code_rules_gate_parts/__init__.py +7 -0
  14. package/_shared/pr-loop/scripts/code_rules_gate_parts/added_line_maps.py +268 -0
  15. package/_shared/pr-loop/scripts/code_rules_gate_parts/enforcer_loading.py +172 -0
  16. package/_shared/pr-loop/scripts/code_rules_gate_parts/gate_arguments.py +70 -0
  17. package/_shared/pr-loop/scripts/code_rules_gate_parts/gate_running.py +326 -0
  18. package/_shared/pr-loop/scripts/code_rules_gate_parts/git_blob_readers.py +85 -0
  19. package/_shared/pr-loop/scripts/code_rules_gate_parts/git_file_sets.py +331 -0
  20. package/_shared/pr-loop/scripts/code_rules_gate_parts/staged_test_running.py +369 -0
  21. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/conftest.py +14 -0
  22. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_added_line_maps.py +118 -0
  23. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_enforcer_loading.py +17 -0
  24. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_gate_arguments.py +29 -0
  25. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_gate_running.py +99 -0
  26. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_git_blob_readers.py +69 -0
  27. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_git_file_sets.py +137 -0
  28. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_staged_test_running.py +116 -0
  29. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_violation_scoping.py +75 -0
  30. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_wrapper_plumb_check.py +49 -0
  31. package/_shared/pr-loop/scripts/code_rules_gate_parts/violation_scoping.py +328 -0
  32. package/_shared/pr-loop/scripts/code_rules_gate_parts/wrapper_plumb_check.py +206 -0
  33. package/_shared/pr-loop/scripts/pr_loop_shared_constants/code_rules_gate_constants.py +24 -17
  34. package/_shared/pr-loop/scripts/pr_loop_shared_constants/reviews_disabled_constants.py +1 -0
  35. package/_shared/pr-loop/scripts/reviews_disabled.py +19 -2
  36. package/_shared/pr-loop/scripts/test_code_rules_gate.py +278 -0
  37. package/_shared/pr-loop/scripts/tests/test_code_rules_gate_constants.py +6 -39
  38. package/_shared/pr-loop/scripts/tests/test_reviews_disabled.py +43 -0
  39. package/_shared/pr-loop/worker-spawn.md +186 -0
  40. package/agents/code-verifier.md +1 -1
  41. package/bin/CLAUDE.md +10 -1
  42. package/bin/ever-shipped-skills.mjs +73 -0
  43. package/bin/expand_home_directory_tokens.mjs +1 -1
  44. package/bin/install.mjs +140 -7
  45. package/bin/install.prune.test.mjs +457 -0
  46. package/docs/CODE_RULES.md +1 -1
  47. package/hooks/advisory/refactor_guard.py +3 -4
  48. package/hooks/blocking/CLAUDE.md +7 -1
  49. package/hooks/blocking/block_main_commit.py +2 -2
  50. package/hooks/blocking/claude_md_orphan_file_blocker.py +75 -699
  51. package/hooks/blocking/claude_md_orphan_file_blocker_parts/CLAUDE.md +28 -0
  52. package/hooks/blocking/claude_md_orphan_file_blocker_parts/__init__.py +1 -0
  53. package/hooks/blocking/claude_md_orphan_file_blocker_parts/config/__init__.py +1 -0
  54. package/hooks/blocking/claude_md_orphan_file_blocker_parts/config/orphan_blocker_constants.py +18 -0
  55. package/hooks/blocking/claude_md_orphan_file_blocker_parts/decision.py +81 -0
  56. package/hooks/blocking/claude_md_orphan_file_blocker_parts/references.py +307 -0
  57. package/hooks/blocking/claude_md_orphan_file_blocker_parts/scan_plan.py +124 -0
  58. package/hooks/blocking/claude_md_orphan_file_blocker_parts/subtree_scan.py +179 -0
  59. package/hooks/blocking/claude_md_orphan_file_blocker_parts/tests/conftest.py +10 -0
  60. package/hooks/blocking/claude_md_orphan_file_blocker_parts/tests/test_decision.py +34 -0
  61. package/hooks/blocking/claude_md_orphan_file_blocker_parts/tests/test_references.py +42 -0
  62. package/hooks/blocking/claude_md_orphan_file_blocker_parts/tests/test_scan_plan.py +27 -0
  63. package/hooks/blocking/claude_md_orphan_file_blocker_parts/tests/test_subtree_scan.py +30 -0
  64. package/hooks/blocking/code_rules_boolean_mustcheck.py +1 -1
  65. package/hooks/blocking/code_rules_enforcer.py +4 -0
  66. package/hooks/blocking/code_rules_mock_completeness.py +1 -1
  67. package/hooks/blocking/code_rules_optional_params.py +2 -2
  68. package/hooks/blocking/code_rules_shared.py +83 -1
  69. package/hooks/blocking/code_rules_test_assertions.py +1 -1
  70. package/hooks/blocking/code_rules_test_layout.py +9 -3
  71. package/hooks/blocking/code_rules_typeddict_stub.py +1 -1
  72. package/hooks/blocking/gh_pr_author_enforcer.py +1 -1
  73. package/hooks/blocking/inventory_intent_records/CLAUDE.md +26 -0
  74. package/hooks/blocking/inventory_intent_records/__init__.py +1 -0
  75. package/hooks/blocking/inventory_intent_records/config/__init__.py +1 -0
  76. package/hooks/blocking/inventory_intent_records/config/intent_records_constants.py +20 -0
  77. package/hooks/blocking/inventory_intent_records/records.py +271 -0
  78. package/hooks/blocking/inventory_intent_records/tests/conftest.py +10 -0
  79. package/hooks/blocking/inventory_intent_records/tests/test_records.py +80 -0
  80. package/hooks/blocking/package_inventory_stale_blocker.py +54 -384
  81. package/hooks/blocking/package_inventory_stale_blocker_parts/CLAUDE.md +26 -0
  82. package/hooks/blocking/package_inventory_stale_blocker_parts/__init__.py +1 -0
  83. package/hooks/blocking/package_inventory_stale_blocker_parts/config/__init__.py +1 -0
  84. package/hooks/blocking/package_inventory_stale_blocker_parts/config/inventory_blocker_constants.py +16 -0
  85. package/hooks/blocking/package_inventory_stale_blocker_parts/decision.py +84 -0
  86. package/hooks/blocking/package_inventory_stale_blocker_parts/inventory_detection.py +307 -0
  87. package/hooks/blocking/package_inventory_stale_blocker_parts/tests/conftest.py +10 -0
  88. package/hooks/blocking/package_inventory_stale_blocker_parts/tests/test_decision.py +38 -0
  89. package/hooks/blocking/package_inventory_stale_blocker_parts/tests/test_inventory_detection.py +61 -0
  90. package/hooks/blocking/pii_payload_scan.py +138 -42
  91. package/hooks/blocking/pii_prevention_blocker.py +185 -291
  92. package/hooks/blocking/pii_prevention_blocker_parts/CLAUDE.md +24 -0
  93. package/hooks/blocking/pii_prevention_blocker_parts/__init__.py +1 -0
  94. package/hooks/blocking/pii_prevention_blocker_parts/config/__init__.py +1 -0
  95. package/hooks/blocking/pii_prevention_blocker_parts/config/repository_resolution_constants.py +28 -0
  96. package/hooks/blocking/pii_prevention_blocker_parts/repository_exemption.py +214 -0
  97. package/hooks/blocking/pii_prevention_blocker_parts/repository_resolution.py +208 -0
  98. package/hooks/blocking/plain_language_blocker.py +138 -4
  99. package/hooks/blocking/pr_description_command_parser.py +8 -4
  100. package/hooks/blocking/precommit_code_rules_gate.py +3 -3
  101. package/hooks/blocking/sensitive_file_protector.py +114 -48
  102. package/hooks/blocking/tdd_enforcer.py +97 -601
  103. package/hooks/blocking/tdd_enforcer_parts/CLAUDE.md +30 -0
  104. package/hooks/blocking/tdd_enforcer_parts/__init__.py +1 -0
  105. package/hooks/blocking/tdd_enforcer_parts/candidate_paths.py +142 -0
  106. package/hooks/blocking/tdd_enforcer_parts/config/__init__.py +1 -0
  107. package/hooks/blocking/tdd_enforcer_parts/config/tdd_enforcer_constants.py +32 -0
  108. package/hooks/blocking/tdd_enforcer_parts/content_analysis.py +268 -0
  109. package/hooks/blocking/tdd_enforcer_parts/decisions.py +92 -0
  110. package/hooks/blocking/tdd_enforcer_parts/freshness.py +80 -0
  111. package/hooks/blocking/tdd_enforcer_parts/git_tracking.py +63 -0
  112. package/hooks/blocking/tdd_enforcer_parts/path_classification.py +119 -0
  113. package/hooks/blocking/tdd_enforcer_parts/tests/conftest.py +10 -0
  114. package/hooks/blocking/tdd_enforcer_parts/tests/test_candidate_paths.py +31 -0
  115. package/hooks/blocking/tdd_enforcer_parts/tests/test_content_analysis.py +30 -0
  116. package/hooks/blocking/tdd_enforcer_parts/tests/test_decisions.py +34 -0
  117. package/hooks/blocking/tdd_enforcer_parts/tests/test_freshness.py +28 -0
  118. package/hooks/blocking/tdd_enforcer_parts/tests/test_git_tracking.py +48 -0
  119. package/hooks/blocking/tdd_enforcer_parts/tests/test_path_classification.py +36 -0
  120. package/hooks/blocking/test_code_rules_enforcer_scratchpad.py +105 -0
  121. package/hooks/blocking/test_code_rules_shared.py +181 -0
  122. package/hooks/blocking/test_inventory_deadlock_resolution.py +154 -0
  123. package/hooks/blocking/test_pii_payload_scan.py +168 -0
  124. package/hooks/blocking/test_plain_language_blocker_allowlist.py +184 -0
  125. package/hooks/blocking/test_sensitive_file_protector.py +185 -0
  126. package/hooks/blocking/test_tdd_enforcer_restore.py +108 -0
  127. package/hooks/blocking/test_tdd_enforcer_scratchpad.py +105 -0
  128. package/hooks/blocking/tests/conftest.py +10 -0
  129. package/hooks/blocking/tests/test_pii_prevention_blocker.py +260 -0
  130. package/hooks/blocking/tests/test_repository_exemption.py +105 -0
  131. package/hooks/blocking/tests/test_repository_resolution.py +108 -0
  132. package/hooks/diagnostic/hook_log_extractor.py +12 -10
  133. package/hooks/git-hooks/post_commit.py +3 -4
  134. package/hooks/hooks_constants/CLAUDE.md +4 -2
  135. package/hooks/hooks_constants/banned_identifiers_constants.py +0 -1
  136. package/hooks/hooks_constants/code_rules_path_utils_constants.py +1 -1
  137. package/hooks/hooks_constants/harness_scratchpad_constants.py +17 -0
  138. package/hooks/hooks_constants/local_identity.py +59 -8
  139. package/hooks/hooks_constants/pii_prevention_constants.py +0 -6
  140. package/hooks/hooks_constants/plain_language_blocker_constants.py +5 -0
  141. package/hooks/hooks_constants/sensitive_file_protector_constants.py +42 -0
  142. package/hooks/hooks_constants/test_local_identity.py +105 -3
  143. package/hooks/pyproject.toml +52 -4
  144. package/hooks/session/plugin_data_dir_cleanup.py +0 -1
  145. package/hooks/validation/mypy_validator.py +2 -2
  146. package/hooks/validators/CLAUDE.md +1 -1
  147. package/hooks/validators/README.md +2 -0
  148. package/hooks/validators/health_check.py +1 -0
  149. package/hooks/validators/mypy_integration.py +2 -0
  150. package/hooks/validators/python_style_checks.py +114 -136
  151. package/hooks/validators/python_style_helpers.py +95 -0
  152. package/hooks/validators/ruff_integration.py +3 -0
  153. package/hooks/validators/test_python_style_checks.py +0 -164
  154. package/hooks/validators/test_python_style_checks_decorator_gap.py +119 -0
  155. package/hooks/validators/test_python_style_fixes.py +251 -0
  156. package/hooks/validators/test_python_style_helpers.py +125 -0
  157. package/hooks/workflow/auto_formatter.py +5 -4
  158. package/package.json +1 -1
  159. package/rules/CLAUDE.md +1 -0
  160. package/rules/anti-corollary-tests.md +69 -0
  161. package/rules/bdd.md +1 -3
  162. package/rules/code-reviews.md +1 -1
  163. package/rules/gh-paginate.md +1 -1
  164. package/rules/plain-language.md +2 -0
  165. package/scripts/CLAUDE.md +4 -0
  166. package/scripts/dev_env_scripts_constants/CLAUDE.md +6 -4
  167. package/scripts/dev_env_scripts_constants/code_review_constants.py +71 -0
  168. package/scripts/dev_env_scripts_constants/grok_worker_constants.py +435 -0
  169. package/scripts/dev_env_scripts_constants/timing.py +7 -1
  170. package/scripts/grok_headless_runner.py +294 -0
  171. package/scripts/grok_worker_preflight.py +410 -0
  172. package/scripts/invoke_code_review.py +463 -0
  173. package/scripts/resolve_worker_spawn.py +619 -0
  174. package/scripts/spawn_grok_batch.py +672 -0
  175. package/scripts/test_grok_headless_runner.py +626 -0
  176. package/scripts/test_grok_worker_preflight.py +1054 -0
  177. package/scripts/test_invoke_code_review.py +672 -0
  178. package/scripts/test_resolve_worker_spawn.py +1014 -0
  179. package/scripts/test_spawn_grok_batch.py +1017 -0
  180. package/skills/CLAUDE.md +6 -3
  181. package/skills/_shared/pr-loop/scripts/build_audit_prompt.py +72 -13
  182. package/skills/_shared/pr-loop/scripts/build_fix_prompt.py +121 -14
  183. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/path_resolver_constants.py +78 -0
  184. package/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +121 -0
  185. package/skills/_shared/pr-loop/scripts/test_build_fix_prompt.py +196 -6
  186. package/skills/autoconverge/CLAUDE.md +3 -3
  187. package/skills/autoconverge/SKILL.md +9 -3
  188. package/skills/autoconverge/reference/CLAUDE.md +2 -2
  189. package/skills/autoconverge/reference/convergence.md +33 -11
  190. package/skills/autoconverge/reference/stop-conditions.md +16 -5
  191. package/skills/autoconverge/workflow/CLAUDE.md +2 -1
  192. package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +7 -2
  193. package/skills/autoconverge/workflow/converge.codex-gate.test.mjs +300 -0
  194. package/skills/autoconverge/workflow/converge.contract.test.mjs +5 -5
  195. package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +29 -29
  196. package/skills/autoconverge/workflow/converge.fix-progress.test.mjs +1 -1
  197. package/skills/autoconverge/workflow/converge.mjs +202 -18
  198. package/skills/bugteam/CLAUDE.md +2 -2
  199. package/skills/bugteam/CONSTRAINTS.md +3 -2
  200. package/skills/bugteam/PROMPTS.md +7 -6
  201. package/skills/bugteam/SKILL.md +18 -13
  202. package/skills/bugteam/reference/README.md +2 -3
  203. package/skills/bugteam/reference/audit-and-teammates.md +215 -35
  204. package/skills/bugteam/reference/design-rationale.md +1 -1
  205. package/skills/bugteam/reference/obstacles/CLAUDE.md +1 -1
  206. package/skills/bugteam/reference/team-setup.md +8 -2
  207. package/skills/closeout/SKILL.md +153 -0
  208. package/skills/closeout/reference/handoff-prompt-template.md +72 -0
  209. package/skills/closeout/reference/issue-body-templates.md +108 -0
  210. package/skills/closeout/reference/pii-redaction-checklist.md +36 -0
  211. package/skills/codex-review/CLAUDE.md +46 -0
  212. package/skills/codex-review/SKILL.md +181 -0
  213. package/skills/codex-review/reference/CLAUDE.md +15 -0
  214. package/skills/codex-review/reference/cli-contract.md +253 -0
  215. package/skills/codex-review/reference/loop-integration.md +118 -0
  216. package/skills/codex-review/scripts/codex_down_classifier.py +98 -0
  217. package/skills/codex-review/scripts/codex_review_scripts_constants/CLAUDE.md +18 -0
  218. package/skills/codex-review/scripts/codex_review_scripts_constants/__init__.py +1 -0
  219. package/skills/codex-review/scripts/codex_review_scripts_constants/classifier_constants.py +35 -0
  220. package/skills/codex-review/scripts/codex_review_scripts_constants/codex_usage_probe_constants.py +86 -0
  221. package/skills/codex-review/scripts/codex_review_scripts_constants/findings_constants.py +18 -0
  222. package/skills/codex-review/scripts/codex_review_scripts_constants/run_constants.py +45 -0
  223. package/skills/codex-review/scripts/codex_usage_probe.py +573 -0
  224. package/skills/codex-review/scripts/fixtures/auth_failure_synthetic.txt +1 -0
  225. package/skills/codex-review/scripts/fixtures/config_load_failure_v0.125.0.txt +1 -0
  226. package/skills/codex-review/scripts/fixtures/freeform_findings_v0.144.3.txt +6 -0
  227. package/skills/codex-review/scripts/fixtures/model_rejection_v0.125.0.jsonl +5 -0
  228. package/skills/codex-review/scripts/fixtures/structured_findings.txt +13 -0
  229. package/skills/codex-review/scripts/fixtures/success_stream_v0.144.3.jsonl +6 -0
  230. package/skills/codex-review/scripts/fixtures/unknown_failure_synthetic.txt +1 -0
  231. package/skills/codex-review/scripts/fixtures/usage_limit_synthetic.txt +1 -0
  232. package/skills/codex-review/scripts/parse_codex_findings.py +207 -0
  233. package/skills/codex-review/scripts/run_codex_review.py +415 -0
  234. package/skills/codex-review/scripts/test_codex_down_classifier.py +143 -0
  235. package/skills/codex-review/scripts/test_codex_usage_probe.py +678 -0
  236. package/skills/codex-review/scripts/test_parse_codex_findings.py +130 -0
  237. package/skills/codex-review/scripts/test_run_codex_review.py +812 -0
  238. package/skills/codex-review/test_skill_scaffold.py +192 -0
  239. package/skills/grok-spawn/CLAUDE.md +28 -0
  240. package/skills/grok-spawn/SKILL.md +226 -0
  241. package/skills/grok-spawn/reference/flag-profiles.md +132 -0
  242. package/skills/grok-spawn/reference/worker-briefs.md +152 -0
  243. package/skills/grokify/SKILL.md +9 -1
  244. package/skills/grokify/capability-claims.test.mjs +28 -0
  245. package/skills/grokify/evals/README.md +72 -0
  246. package/skills/grokify/evals/parse-payload.test.mjs +171 -0
  247. package/skills/grokify/evals/run-capability-evals.mjs +545 -0
  248. package/skills/orchestrator/SKILL.md +32 -22
  249. package/skills/orchestrator-refresh/SKILL.md +12 -8
  250. package/skills/pr-converge/CLAUDE.md +1 -1
  251. package/skills/pr-converge/SKILL.md +34 -13
  252. package/skills/pr-converge/reference/convergence-gates.md +42 -15
  253. package/skills/pr-converge/reference/fix-protocol.md +1 -1
  254. package/skills/pr-converge/reference/ground-rules.md +1 -1
  255. package/skills/pr-converge/reference/per-tick.md +130 -42
  256. package/skills/pr-converge/reference/state-schema.md +10 -0
  257. package/skills/pr-converge/scripts/CLAUDE.md +2 -0
  258. package/skills/pr-converge/scripts/_pr_converge_path_setup.py +5 -1
  259. package/skills/pr-converge/scripts/check_convergence.py +605 -29
  260. package/skills/pr-converge/scripts/check_convergence_availability.py +232 -0
  261. package/skills/pr-converge/scripts/check_convergence_gates.py +279 -235
  262. package/skills/pr-converge/scripts/check_convergence_thread_gates.py +1 -1
  263. package/skills/pr-converge/scripts/pr_converge_scripts_constants/convergence_gate_constants.py +36 -2
  264. package/skills/pr-converge/scripts/test__pr_converge_path_setup.py +4 -0
  265. package/skills/pr-converge/scripts/test_check_convergence.py +71 -3
  266. package/skills/pr-converge/scripts/test_check_convergence_availability.py +326 -0
  267. package/skills/pr-converge/scripts/test_check_convergence_codex.py +507 -0
  268. package/skills/pr-converge/scripts/test_check_convergence_contract.py +89 -17
  269. package/skills/pr-converge/scripts/test_check_convergence_fixture.py +179 -0
  270. package/skills/pr-converge/scripts/test_check_convergence_gates.py +84 -68
  271. package/skills/pr-converge/scripts/test_check_convergence_thread_gates.py +24 -0
  272. package/skills/pr-converge/test_step5_host_branch.py +106 -0
  273. package/skills/pr-fix-protocol/SKILL.md +65 -0
  274. package/skills/pr-loop-cloud-transport/SKILL.md +2 -0
  275. package/skills/reviewer-gates/SKILL.md +7 -5
  276. package/skills/skill-builder/references/skill-modularity.md +1 -1
  277. package/skills/team-advisor/SKILL.md +16 -12
  278. package/system-prompts/software-engineer.xml +7 -6
  279. package/hooks/validators/test_verify_paths.py +0 -32
  280. package/hooks/validators/verify_paths.py +0 -57
@@ -0,0 +1,49 @@
1
+ """Behavioral tests for the wrapper_plumb_check parts module."""
2
+
3
+ from pathlib import Path
4
+
5
+ from code_rules_gate_parts import wrapper_plumb_check
6
+
7
+
8
+ def test_is_code_path_recognizes_python() -> None:
9
+ assert wrapper_plumb_check.is_code_path(Path("module.py"))
10
+ assert not wrapper_plumb_check.is_code_path(Path("notes.txt"))
11
+
12
+
13
+ def test_is_test_path_matches_the_documented_patterns() -> None:
14
+ assert wrapper_plumb_check.is_test_path("pkg/test_bar.py")
15
+ assert wrapper_plumb_check.is_test_path("pkg/bar_test.py")
16
+ assert wrapper_plumb_check.is_test_path("pkg/conftest.py")
17
+ assert wrapper_plumb_check.is_test_path("pkg/tests/thing.py")
18
+ assert not wrapper_plumb_check.is_test_path("pkg/regular.py")
19
+
20
+
21
+ def test_check_wrapper_plumb_through_flags_dropped_kwarg() -> None:
22
+ content = (
23
+ "def build(name, verbose=False):\n"
24
+ " return name\n\n\n"
25
+ "def wrap(name):\n"
26
+ " return build(name)\n"
27
+ )
28
+ findings = wrapper_plumb_check.check_wrapper_plumb_through(content, "module.py")
29
+ assert any("verbose" in each_finding for each_finding in findings)
30
+
31
+
32
+ def test_check_wrapper_plumb_through_is_quiet_for_test_files() -> None:
33
+ content = (
34
+ "def build(name, verbose=False):\n"
35
+ " return name\n\n\n"
36
+ "def wrap(name):\n"
37
+ " return build(name)\n"
38
+ )
39
+ assert wrapper_plumb_check.check_wrapper_plumb_through(content, "test_module.py") == []
40
+
41
+
42
+ def test_check_wrapper_plumb_through_is_quiet_when_kwarg_is_plumbed() -> None:
43
+ content = (
44
+ "def build(name, verbose=False):\n"
45
+ " return name\n\n\n"
46
+ "def wrap(name, verbose=False):\n"
47
+ " return build(name, verbose=verbose)\n"
48
+ )
49
+ assert wrapper_plumb_check.check_wrapper_plumb_through(content, "module.py") == []
@@ -0,0 +1,328 @@
1
+ """Partition enforcer violations into blocking versus advisory by touched line.
2
+
3
+ Every diff-scoped enforcer message carries the line span of the unit it flags.
4
+ These extractors recover that span from the message text, and
5
+ ``split_violations_by_scope`` marks a violation blocking when its span meets a
6
+ line the current diff added, advisory otherwise.
7
+ """
8
+
9
+ import re
10
+ from collections.abc import Callable
11
+
12
+ from pr_loop_shared_constants.code_rules_gate_constants import (
13
+ BANNED_NOUN_DEFINITION_LINE_GROUP_INDEX,
14
+ BANNED_NOUN_SPAN_GROUP_INDEX,
15
+ BANNED_NOUN_VIOLATION_PATTERN,
16
+ DUPLICATE_BODY_DEFINITION_LINE_GROUP_INDEX,
17
+ DUPLICATE_BODY_SPAN_GROUP_INDEX,
18
+ DUPLICATE_BODY_VIOLATION_PATTERN,
19
+ FUNCTION_LENGTH_DEFINITION_LINE_GROUP_INDEX,
20
+ FUNCTION_LENGTH_SPAN_GROUP_INDEX,
21
+ FUNCTION_LENGTH_VIOLATION_PATTERN,
22
+ ISOLATION_DEFINITION_LINE_GROUP_INDEX,
23
+ ISOLATION_SPAN_GROUP_INDEX,
24
+ ISOLATION_VIOLATION_PATTERN,
25
+ )
26
+ from pr_loop_shared_constants.inline_duplicate_body_span_constants import (
27
+ INLINE_DUPLICATE_BODY_ENCLOSING_LINE_GROUP_INDEX,
28
+ INLINE_DUPLICATE_BODY_ENCLOSING_SPAN_GROUP_INDEX,
29
+ INLINE_DUPLICATE_BODY_HELPER_LINE_GROUP_INDEX,
30
+ INLINE_DUPLICATE_BODY_HELPER_SPAN_GROUP_INDEX,
31
+ INLINE_DUPLICATE_BODY_VIOLATION_PATTERN,
32
+ )
33
+
34
+
35
+ def _span_lines(span_match: re.Match[str], line_group_index: int, span_group_index: int) -> range:
36
+ """Build the line range from a match's start-line and span capture groups.
37
+
38
+ Args:
39
+ span_match: A regex match carrying a start line and a span length.
40
+ line_group_index: The capture-group index of the start line.
41
+ span_group_index: The capture-group index of the span length.
42
+
43
+ Returns:
44
+ The range covering the declared span.
45
+ """
46
+ start_line = int(span_match.group(line_group_index))
47
+ line_span = int(span_match.group(span_group_index))
48
+ return range(start_line, start_line + line_span)
49
+
50
+
51
+ def hunk_header_pattern() -> re.Pattern[str]:
52
+ """Return the regex matching a unified-diff hunk header.
53
+
54
+ Returns:
55
+ A pattern capturing the new-side start line and optional count.
56
+ """
57
+ return re.compile(r"^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@")
58
+
59
+
60
+ def violation_line_pattern() -> re.Pattern[str]:
61
+ """Return the regex matching a violation's ``Line N:`` prefix.
62
+
63
+ Returns:
64
+ A pattern capturing the line number named in the prefix.
65
+ """
66
+ return re.compile(r"^Line (\d+):")
67
+
68
+
69
+ def parse_added_line_numbers(unified_diff_text: str) -> set[int]:
70
+ """Extract added line numbers from unified-diff text.
71
+
72
+ Args:
73
+ unified_diff_text: Output from ``git diff --unified=0``.
74
+
75
+ Returns:
76
+ The set of newly-added line numbers named by the hunk headers.
77
+ """
78
+ header_regex = hunk_header_pattern()
79
+ added_line_numbers: set[int] = set()
80
+ for each_line in unified_diff_text.splitlines():
81
+ header_match = header_regex.match(each_line)
82
+ if header_match is None:
83
+ continue
84
+ new_start_text, new_count_text = header_match.groups()
85
+ new_start = int(new_start_text)
86
+ new_count = 1 if new_count_text is None else int(new_count_text)
87
+ if new_count <= 0:
88
+ continue
89
+ added_line_numbers.update(range(new_start, new_start + new_count))
90
+ return added_line_numbers
91
+
92
+
93
+ def extract_violation_line_number(violation_text: str) -> int | None:
94
+ """Return the line number captured by the violation-line prefix regex.
95
+
96
+ Args:
97
+ violation_text: A single violation string of the form ``Line N: ...``.
98
+
99
+ Returns:
100
+ The integer line number in the prefix, or None when it does not match.
101
+ """
102
+ prefix_match = violation_line_pattern().match(violation_text)
103
+ if prefix_match is None:
104
+ return None
105
+ return int(prefix_match.group(1))
106
+
107
+
108
+ def function_length_span_range(violation_text: str) -> range | None:
109
+ """Return the declared line span of a function-length violation, or None.
110
+
111
+ ::
112
+
113
+ message: Function 'f' (defined at line X) is Y lines - ...
114
+ ok: a function-length message -> range covering the function
115
+ flag: another message -> None
116
+
117
+ Args:
118
+ violation_text: A single violation string emitted by the enforcer.
119
+
120
+ Returns:
121
+ A range over the function's declared span, or None for another message.
122
+ """
123
+ span_match = FUNCTION_LENGTH_VIOLATION_PATTERN.search(violation_text)
124
+ if span_match is None:
125
+ return None
126
+ return _span_lines(
127
+ span_match,
128
+ FUNCTION_LENGTH_DEFINITION_LINE_GROUP_INDEX,
129
+ FUNCTION_LENGTH_SPAN_GROUP_INDEX,
130
+ )
131
+
132
+
133
+ def isolation_span_range(violation_text: str) -> range | None:
134
+ """Return the enclosing test-function span of an isolation violation, or None.
135
+
136
+ ::
137
+
138
+ message: Line N: Test 'f' (defined at line X, spanning Y lines) probes ...
139
+ ok: an isolation message -> range covering the test function
140
+ flag: another message -> None
141
+
142
+ Args:
143
+ violation_text: A single violation string emitted by the enforcer.
144
+
145
+ Returns:
146
+ A range over the enclosing test span, or None for another message.
147
+ """
148
+ span_match = ISOLATION_VIOLATION_PATTERN.search(violation_text)
149
+ if span_match is None:
150
+ return None
151
+ return _span_lines(
152
+ span_match,
153
+ ISOLATION_DEFINITION_LINE_GROUP_INDEX,
154
+ ISOLATION_SPAN_GROUP_INDEX,
155
+ )
156
+
157
+
158
+ def banned_noun_span_range(violation_text: str) -> range | None:
159
+ """Return the one-line binding span of a banned-noun violation, or None.
160
+
161
+ ::
162
+
163
+ message: Line N: Identifier 'x' ... (binding span at line X, spanning 1 lines)
164
+ ok: a banned-noun message -> range covering the binding line
165
+ flag: another message -> None
166
+
167
+ The span is the binding line alone, so a pre-existing binding stays out of
168
+ scope when an unrelated line of its enclosing function is edited.
169
+
170
+ Args:
171
+ violation_text: A single violation string emitted by the enforcer.
172
+
173
+ Returns:
174
+ A range over the binding's one-line span, or None for another message.
175
+ """
176
+ span_match = BANNED_NOUN_VIOLATION_PATTERN.search(violation_text)
177
+ if span_match is None:
178
+ return None
179
+ return _span_lines(
180
+ span_match,
181
+ BANNED_NOUN_DEFINITION_LINE_GROUP_INDEX,
182
+ BANNED_NOUN_SPAN_GROUP_INDEX,
183
+ )
184
+
185
+
186
+ def duplicate_body_span_range(violation_text: str) -> range | None:
187
+ """Return the copied function's source line span of a duplicate-body issue.
188
+
189
+ ::
190
+
191
+ message: Function 'f' duplicates a.py::f — (duplicate body span at line X ...)
192
+ ok: a duplicate-body message -> range covering the copy
193
+ flag: another message -> None
194
+
195
+ A duplicate blocks only when the diff touches the copied function; an
196
+ unrelated edit that leaves a pre-existing copy alone keeps it advisory.
197
+
198
+ Args:
199
+ violation_text: A single violation string emitted by the enforcer.
200
+
201
+ Returns:
202
+ A range over the copied function's span, or None for another message.
203
+ """
204
+ span_match = DUPLICATE_BODY_VIOLATION_PATTERN.search(violation_text)
205
+ if span_match is None:
206
+ return None
207
+ return _span_lines(
208
+ span_match,
209
+ DUPLICATE_BODY_DEFINITION_LINE_GROUP_INDEX,
210
+ DUPLICATE_BODY_SPAN_GROUP_INDEX,
211
+ )
212
+
213
+
214
+ def inline_duplicate_body_span_lines(violation_text: str) -> frozenset[int] | None:
215
+ """Return the union of both spans of a same-file inline-duplicate issue.
216
+
217
+ ::
218
+
219
+ message: ... helper at line H spanning P, enclosing at line E spanning Q
220
+ ok: an inline-duplicate message -> frozenset of both spans
221
+ flag: another message -> None
222
+
223
+ Args:
224
+ violation_text: A single violation string emitted by the enforcer.
225
+
226
+ Returns:
227
+ The frozenset of every line in both spans, or None for another message.
228
+ """
229
+ span_match = INLINE_DUPLICATE_BODY_VIOLATION_PATTERN.search(violation_text)
230
+ if span_match is None:
231
+ return None
232
+ helper_lines = _span_lines(
233
+ span_match,
234
+ INLINE_DUPLICATE_BODY_HELPER_LINE_GROUP_INDEX,
235
+ INLINE_DUPLICATE_BODY_HELPER_SPAN_GROUP_INDEX,
236
+ )
237
+ enclosing_lines = _span_lines(
238
+ span_match,
239
+ INLINE_DUPLICATE_BODY_ENCLOSING_LINE_GROUP_INDEX,
240
+ INLINE_DUPLICATE_BODY_ENCLOSING_SPAN_GROUP_INDEX,
241
+ )
242
+ return frozenset(helper_lines) | frozenset(enclosing_lines)
243
+
244
+
245
+ def _all_span_range_extractors() -> tuple[Callable[[str], range | None], ...]:
246
+ """Return every enclosing-unit span extractor, tried in order."""
247
+ return (
248
+ function_length_span_range,
249
+ isolation_span_range,
250
+ banned_noun_span_range,
251
+ duplicate_body_span_range,
252
+ )
253
+
254
+
255
+ def enclosing_span_range(violation_text: str) -> range | None:
256
+ """Return the enclosing-unit span of a span-tagged violation, or None.
257
+
258
+ ::
259
+
260
+ each extractor in the registry is tried in turn
261
+ ok: a span-tagged message -> the first range an extractor recovers
262
+ flag: an untagged message -> None
263
+
264
+ Args:
265
+ violation_text: A single violation string emitted by the enforcer.
266
+
267
+ Returns:
268
+ The first non-None span any extractor recovers, or None.
269
+ """
270
+ for each_extractor in _all_span_range_extractors():
271
+ span_range = each_extractor(violation_text)
272
+ if span_range is not None:
273
+ return span_range
274
+ return None
275
+
276
+
277
+ def _issue_is_blocking(each_issue: str, all_added_line_numbers: set[int]) -> bool:
278
+ """Return True when *each_issue* is blocking for the given added lines.
279
+
280
+ Args:
281
+ each_issue: A single violation string emitted by the enforcer.
282
+ all_added_line_numbers: Lines the current diff added.
283
+
284
+ Returns:
285
+ True when the issue's span or line meets an added line.
286
+ """
287
+ inline_duplicate_lines = inline_duplicate_body_span_lines(each_issue)
288
+ if inline_duplicate_lines is not None:
289
+ return bool(inline_duplicate_lines & all_added_line_numbers)
290
+ span_range = enclosing_span_range(each_issue)
291
+ if span_range is not None:
292
+ return any(each_line in all_added_line_numbers for each_line in span_range)
293
+ violation_line = extract_violation_line_number(each_issue)
294
+ if violation_line is None:
295
+ return True
296
+ return violation_line in all_added_line_numbers
297
+
298
+
299
+ def split_violations_by_scope(
300
+ all_issues: list[str],
301
+ all_added_line_numbers: set[int] | None,
302
+ ) -> tuple[list[str], list[str]]:
303
+ """Partition issues into blocking versus advisory based on touched lines.
304
+
305
+ ::
306
+
307
+ all_added_line_numbers is None -> every issue is blocking
308
+ an issue whose span meets an added line -> blocking
309
+ an issue on a pre-existing untouched unit -> advisory
310
+
311
+ Args:
312
+ all_issues: Violation strings emitted by the enforcer.
313
+ all_added_line_numbers: Lines added in the current diff, or None to
314
+ treat every violation as blocking.
315
+
316
+ Returns:
317
+ The tuple ``(blocking, advisory)``.
318
+ """
319
+ if all_added_line_numbers is None:
320
+ return list(all_issues), []
321
+ blocking: list[str] = []
322
+ advisory: list[str] = []
323
+ for each_issue in all_issues:
324
+ if _issue_is_blocking(each_issue, all_added_line_numbers):
325
+ blocking.append(each_issue)
326
+ else:
327
+ advisory.append(each_issue)
328
+ return blocking, advisory
@@ -0,0 +1,206 @@
1
+ """Flag public calls that drop a same-file delegate's optional keyword arguments.
2
+
3
+ A thin public wrapper that forwards to a module-level helper should keep the
4
+ helper's optional keyword surface. When the wrapper narrows it, a caller varying
5
+ that keyword hits the wrapper's default instead. This check walks the AST and
6
+ reports each such dropped keyword.
7
+ """
8
+
9
+ import ast
10
+ from collections.abc import Iterator
11
+ from pathlib import Path
12
+
13
+ from pr_loop_shared_constants.code_rules_gate_constants import (
14
+ ALL_CODE_FILE_EXTENSIONS,
15
+ ALL_TEST_FILENAME_GLOB_SUFFIXES,
16
+ ALL_TEST_FILENAME_SUFFIXES,
17
+ MAX_VIOLATIONS_PER_CHECK,
18
+ PYTHON_FILE_EXTENSION,
19
+ TEST_CONFTEST_FILENAME,
20
+ TEST_FILENAME_PREFIX,
21
+ TESTS_PATH_SEGMENT,
22
+ )
23
+
24
+
25
+ def is_code_path(file_path: Path) -> bool:
26
+ """Return True when *file_path* carries a recognized code extension."""
27
+ return file_path.suffix.lower() in ALL_CODE_FILE_EXTENSIONS
28
+
29
+
30
+ def is_test_path(file_path: str) -> bool:
31
+ """Return True when *file_path* matches a test-file detection pattern.
32
+
33
+ ::
34
+
35
+ ok(test): test_x.py, x_test.py, x.spec.ts, conftest.py, a/tests/x.py
36
+ ok(not): regular_module.py
37
+
38
+ Args:
39
+ file_path: Path string to classify; backslashes are normalized first.
40
+
41
+ Returns:
42
+ True when the path matches any test-file pattern; False otherwise.
43
+ """
44
+ normalized_posix = file_path.replace("\\", "/")
45
+ filename_only = normalized_posix.rsplit("/", maxsplit=1)[-1]
46
+ if TESTS_PATH_SEGMENT in normalized_posix:
47
+ return True
48
+ if filename_only == TEST_CONFTEST_FILENAME:
49
+ return True
50
+ if filename_only.startswith(TEST_FILENAME_PREFIX) and filename_only.endswith(
51
+ PYTHON_FILE_EXTENSION
52
+ ):
53
+ return True
54
+ if any(filename_only.endswith(each) for each in ALL_TEST_FILENAME_SUFFIXES):
55
+ return True
56
+ return any(each in filename_only for each in ALL_TEST_FILENAME_GLOB_SUFFIXES)
57
+
58
+
59
+ def _iter_calls_excluding_nested_functions(node: ast.AST) -> Iterator[ast.Call]:
60
+ """Yield calls under *node*, skipping bodies of nested function definitions."""
61
+ for each_child in ast.iter_child_nodes(node):
62
+ if isinstance(each_child, (ast.FunctionDef, ast.AsyncFunctionDef)):
63
+ continue
64
+ if isinstance(each_child, ast.Call):
65
+ yield each_child
66
+ continue
67
+ yield from _iter_calls_excluding_nested_functions(each_child)
68
+
69
+
70
+ def _optional_kwargs_of(
71
+ function_node: ast.FunctionDef | ast.AsyncFunctionDef,
72
+ ) -> set[str]:
73
+ """Return the names of every optional keyword the function accepts."""
74
+ optional_kwargs: set[str] = set()
75
+ for each_kwonly, each_default in zip(
76
+ function_node.args.kwonlyargs, function_node.args.kw_defaults, strict=False
77
+ ):
78
+ if each_default is not None:
79
+ optional_kwargs.add(each_kwonly.arg)
80
+ positional_defaults = function_node.args.defaults
81
+ if positional_defaults:
82
+ for each_arg in function_node.args.args[-len(positional_defaults) :]:
83
+ optional_kwargs.add(each_arg.arg)
84
+ return optional_kwargs
85
+
86
+
87
+ def _module_level_optional_kwargs_by_name(tree: ast.Module) -> dict[str, set[str]]:
88
+ """Return a map from module-level function name to its optional kwargs."""
89
+ all_function_signatures: dict[str, set[str]] = {}
90
+ for each_node in ast.iter_child_nodes(tree):
91
+ if isinstance(each_node, (ast.FunctionDef, ast.AsyncFunctionDef)):
92
+ all_function_signatures[each_node.name] = _optional_kwargs_of(each_node)
93
+ return all_function_signatures
94
+
95
+
96
+ def _method_ids_in_class(class_def: ast.ClassDef) -> set[int]:
97
+ """Return the object ids of every method defined directly in *class_def*."""
98
+ return {
99
+ id(each_node)
100
+ for each_node in class_def.body
101
+ if isinstance(each_node, (ast.FunctionDef, ast.AsyncFunctionDef))
102
+ }
103
+
104
+
105
+ def _class_method_node_ids(tree: ast.Module) -> set[int]:
106
+ """Return the object ids of every method defined in any class in the tree."""
107
+ class_method_node_ids: set[int] = set()
108
+ for each_node in ast.walk(tree):
109
+ if isinstance(each_node, ast.ClassDef):
110
+ class_method_node_ids.update(_method_ids_in_class(each_node))
111
+ return class_method_node_ids
112
+
113
+
114
+ def _delegate_name_of(call_node: ast.Call) -> str | None:
115
+ """Return the callee name of *call_node*, or None for a non-name callee."""
116
+ if isinstance(call_node.func, ast.Name):
117
+ return call_node.func.id
118
+ if isinstance(call_node.func, ast.Attribute):
119
+ return call_node.func.attr
120
+ return None
121
+
122
+
123
+ def _wrapper_dropped_kwarg_findings(
124
+ wrapper_node: ast.FunctionDef | ast.AsyncFunctionDef,
125
+ all_kwargs_by_function_name: dict[str, set[str]],
126
+ ) -> Iterator[str]:
127
+ """Yield one finding per optional kwarg *wrapper_node* drops from a delegate."""
128
+ wrapper_kwargs = all_kwargs_by_function_name.get(wrapper_node.name, set())
129
+ for each_call in _iter_calls_excluding_nested_functions(wrapper_node):
130
+ delegate_name = _delegate_name_of(each_call)
131
+ if delegate_name is None:
132
+ continue
133
+ delegate_kwargs = all_kwargs_by_function_name.get(delegate_name)
134
+ if delegate_kwargs is None:
135
+ continue
136
+ missing = delegate_kwargs - wrapper_kwargs
137
+ if missing:
138
+ yield (
139
+ f"Line {wrapper_node.lineno}: Wrapper {wrapper_node.name!r} drops "
140
+ f"optional kwargs {sorted(missing)!r} of delegate {delegate_name!r}"
141
+ )
142
+
143
+
144
+ def _wrapper_candidate_nodes(
145
+ tree: ast.Module, all_class_method_node_ids: set[int]
146
+ ) -> Iterator[ast.FunctionDef | ast.AsyncFunctionDef]:
147
+ """Yield each public module function that is not a class method."""
148
+ for each_node in ast.walk(tree):
149
+ if not isinstance(each_node, (ast.FunctionDef, ast.AsyncFunctionDef)):
150
+ continue
151
+ if id(each_node) in all_class_method_node_ids:
152
+ continue
153
+ if not each_node.name.startswith("_"):
154
+ yield each_node
155
+
156
+
157
+ def _wrapper_findings_from_tree(tree: ast.Module) -> list[str]:
158
+ """Collect capped wrapper-plumb-through findings from a parsed module."""
159
+ all_signatures = _module_level_optional_kwargs_by_name(tree)
160
+ class_method_node_ids = _class_method_node_ids(tree)
161
+ issues: list[str] = []
162
+ for each_node in _wrapper_candidate_nodes(tree, class_method_node_ids):
163
+ issues.extend(_wrapper_dropped_kwarg_findings(each_node, all_signatures))
164
+ if len(issues) >= MAX_VIOLATIONS_PER_CHECK:
165
+ return issues[:MAX_VIOLATIONS_PER_CHECK]
166
+ return issues
167
+
168
+
169
+ def _is_non_python_or_test(file_path: str) -> bool:
170
+ """Return True for a non-Python code file or a test file."""
171
+ non_python_code_extensions = ALL_CODE_FILE_EXTENSIONS - {PYTHON_FILE_EXTENSION}
172
+ lowercase_file_path = file_path.lower()
173
+ if any(lowercase_file_path.endswith(each) for each in non_python_code_extensions):
174
+ return True
175
+ return is_test_path(file_path)
176
+
177
+
178
+ def check_wrapper_plumb_through(content: str, file_path: str) -> list[str]:
179
+ """Flag public calls that drop a same-file delegate's optional kwargs.
180
+
181
+ ::
182
+
183
+ def build(name, verbose=False): ... def wrap(name): return build(name)
184
+ flag: wrap drops optional kwarg 'verbose' of delegate 'build'
185
+ ok: def wrap(name, verbose=False): return build(name, verbose=verbose)
186
+
187
+ Only module-level functions contribute signatures; class methods are skipped
188
+ as signature sources and as wrapper candidates. Emission caps at
189
+ ``MAX_VIOLATIONS_PER_CHECK`` findings.
190
+
191
+ Args:
192
+ content: File content as a single string for AST parsing.
193
+ file_path: Repository-relative POSIX path, for skipping non-Python and
194
+ test files early.
195
+
196
+ Returns:
197
+ Violation strings, one per dropped optional kwarg; empty for a
198
+ non-Python file, a test file, or a syntax error.
199
+ """
200
+ if _is_non_python_or_test(file_path):
201
+ return []
202
+ try:
203
+ tree = ast.parse(content)
204
+ except SyntaxError:
205
+ return []
206
+ return _wrapper_findings_from_tree(tree)
@@ -3,7 +3,16 @@
3
3
  import re
4
4
 
5
5
  MAX_VIOLATIONS_PER_CHECK: int = 3
6
- EXPECTED_TUPLE_PAIR_LENGTH: int = 2
6
+
7
+ GATE_ERROR_EXIT_CODE: int = 2
8
+
9
+ EMPTY_FILE_SET_EXIT_CODE: int = 3
10
+
11
+ EMPTY_FILE_SET_MESSAGE: str = (
12
+ "code_rules_gate: the resolved file set is empty; nothing was inspected."
13
+ )
14
+
15
+ INSPECTED_COUNT_MESSAGE: str = "code_rules_gate: inspected {inspected_count} file(s)."
7
16
 
8
17
  FUNCTION_LENGTH_VIOLATION_PATTERN: re.Pattern[str] = re.compile(
9
18
  r"\(defined at line (\d+)\) is (\d+) lines"
@@ -29,15 +38,7 @@ DUPLICATE_BODY_VIOLATION_PATTERN: re.Pattern[str] = re.compile(
29
38
  DUPLICATE_BODY_DEFINITION_LINE_GROUP_INDEX: int = 1
30
39
  DUPLICATE_BODY_SPAN_GROUP_INDEX: int = 2
31
40
 
32
- ALL_CODE_FILE_EXTENSIONS: frozenset[str] = frozenset(
33
- {".py", ".js", ".ts", ".tsx", ".jsx"}
34
- )
35
-
36
- ALL_LITERAL_KEYWORD_EXEMPTIONS: frozenset[str] = frozenset(
37
- {"true", "false", "none", "null"}
38
- )
39
-
40
- CONFIG_PATH_SEGMENT: str = "/config/"
41
+ ALL_CODE_FILE_EXTENSIONS: frozenset[str] = frozenset({".py", ".js", ".ts", ".tsx", ".jsx"})
41
42
 
42
43
  TESTS_PATH_SEGMENT: str = "/tests/"
43
44
 
@@ -52,10 +53,6 @@ TEST_CONFTEST_FILENAME: str = "conftest.py"
52
53
 
53
54
  TEST_FILENAME_PREFIX: str = "test_"
54
55
 
55
- MINIMUM_COLUMN_NAME_LENGTH_AFTER_FIRST_CHAR: int = 2
56
-
57
- COLUMN_KEY_PATTERN_TEMPLATE: str = r"^[a-z][a-z0-9_]{{{minimum_length},}}$"
58
-
59
56
  GIT_NAME_STATUS_ADDED_PREFIX: str = "A"
60
57
 
61
58
  GIT_NAME_STATUS_RENAMED_PREFIX: str = "R"
@@ -81,6 +78,14 @@ ALL_GIT_DIFF_NAME_ONLY_NULL_TERMINATED_COMMAND_PREFIX: tuple[str, ...] = (
81
78
  "-z",
82
79
  )
83
80
 
81
+ ALL_GIT_LS_FILES_UNTRACKED_NULL_TERMINATED_COMMAND: tuple[str, ...] = (
82
+ "git",
83
+ "ls-files",
84
+ "--others",
85
+ "--exclude-standard",
86
+ "-z",
87
+ )
88
+
84
89
 
85
90
  ALL_PYTEST_MODULE_INVOCATION: tuple[str, ...] = (
86
91
  "-m",
@@ -96,7 +101,10 @@ PYTHONPATH_ENV_VAR: str = "PYTHONPATH"
96
101
 
97
102
  ALL_VENV_DIRECTORY_NAMES: tuple[str, ...] = (".venv", "venv")
98
103
 
99
- ALL_WINDOWS_VENV_PYTHON_RELATIVE_PATH_SEGMENTS: tuple[str, ...] = ("Scripts", "python.exe")
104
+ ALL_WINDOWS_VENV_PYTHON_RELATIVE_PATH_SEGMENTS: tuple[str, ...] = (
105
+ "Scripts",
106
+ "python.exe",
107
+ )
100
108
 
101
109
  ALL_POSIX_VENV_PYTHON_RELATIVE_PATH_SEGMENTS: tuple[str, ...] = ("bin", "python")
102
110
 
@@ -132,8 +140,7 @@ ALL_PYTEST_CONFIG_FILE_SECTIONS: tuple[tuple[str, str | None], ...] = (
132
140
  )
133
141
 
134
142
  STAGED_TEST_GROUP_FAILURE_MESSAGE: str = (
135
- "code_rules_gate: staged test group rooted at {group_root} "
136
- "failed under pytest; commit blocked."
143
+ "code_rules_gate: staged test group rooted at {group_root} failed under pytest; commit blocked."
137
144
  )
138
145
 
139
146
  MINIMUM_STAGED_PYTEST_PYTHON_MAJOR: int = 3
@@ -8,4 +8,5 @@ CLAUDE_REVIEWS_DISABLED_TOKEN_SEPARATOR: str = ","
8
8
  CLAUDE_REVIEWS_DISABLED_BUGTEAM_TOKEN: str = "bugteam"
9
9
  CLAUDE_REVIEWS_DISABLED_BUGBOT_TOKEN: str = "bugbot"
10
10
  CLAUDE_REVIEWS_DISABLED_COPILOT_TOKEN: str = "copilot"
11
+ CLAUDE_REVIEWS_DISABLED_CODEX_TOKEN: str = "codex"
11
12
  EXIT_CODE_BUGTEAM_DISABLED_VIA_ENV: int = 7