claude-dev-env 2.28.0 → 2.29.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 (212) hide show
  1. package/.agents/agents/AGENTS.md +0 -31
  2. package/.agents/agents/clean-coder.md +1 -1
  3. package/.agents/agents/test_agent_frontmatter.py +3 -1
  4. package/.agents/skills/AGENTS.md +0 -70
  5. package/.agents/skills/_shared/AGENTS.md +0 -44
  6. package/.agents/skills/_shared/advisor/AGENTS.md +0 -8
  7. package/.agents/skills/_shared/pr-loop/AGENTS.md +0 -57
  8. package/.agents/skills/_shared/pr-loop/prompts/AGENTS.md +0 -8
  9. package/.agents/skills/_shared/pr-loop/scripts/AGENTS.md +0 -34
  10. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/AGENTS.md +0 -23
  11. package/.agents/skills/autoconverge/AGENTS.md +0 -35
  12. package/.agents/skills/autoconverge/reference/AGENTS.md +0 -15
  13. package/.agents/skills/autoconverge/workflow/AGENTS.md +0 -25
  14. package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/AGENTS.md +0 -15
  15. package/.agents/skills/everything-search/AGENTS.md +0 -16
  16. package/.agents/skills/fresh-branch/AGENTS.md +0 -13
  17. package/.agents/skills/grok-spawn/AGENTS.md +0 -27
  18. package/.agents/skills/orchestrator/.claude/CLAUDE.md +1 -0
  19. package/.agents/skills/orchestrator/AGENTS.md +1 -0
  20. package/.agents/skills/orchestrator/SKILL.md +44 -53
  21. package/.agents/skills/orchestrator/reference/.claude/CLAUDE.md +1 -0
  22. package/.agents/skills/orchestrator/reference/AGENTS.md +1 -0
  23. package/.agents/skills/orchestrator/reference/consult-the-orchestrator.md +70 -0
  24. package/.agents/skills/orchestrator/reference/executor-consult-block.md +62 -0
  25. package/.agents/skills/orchestrator/reference/host-detect.md +15 -0
  26. package/.agents/skills/orchestrator/test_orchestrator_skill_contract.py +62 -0
  27. package/.agents/skills/orchestrator-refresh/SKILL.md +16 -34
  28. package/.agents/skills/rebase/AGENTS.md +0 -31
  29. package/.agents/skills/session-log/AGENTS.md +0 -31
  30. package/.agents/skills/session-tidy/AGENTS.md +0 -35
  31. package/.agents/skills/skill-builder/AGENTS.md +0 -48
  32. package/.agents/skills/skill-builder/references/AGENTS.md +0 -24
  33. package/.agents/skills/skill-builder/templates/AGENTS.md +0 -13
  34. package/.agents/skills/skill-builder/workflows/AGENTS.md +0 -18
  35. package/.agents/skills/task-build/AGENTS.md +0 -28
  36. package/.agents/skills/update/AGENTS.md +0 -37
  37. package/.agents/skills-archived/AGENTS.md +0 -44
  38. package/.agents/skills-archived/anthropic-plan/AGENTS.md +0 -33
  39. package/.agents/skills-archived/anthropic-plan/scripts/AGENTS.md +0 -10
  40. package/.agents/skills-archived/anthropic-plan/scripts/anthropic_plan_scripts_constants/AGENTS.md +0 -15
  41. package/.agents/skills-archived/anthropic-plan/templates/AGENTS.md +0 -12
  42. package/.agents/skills-archived/anthropic-plan/workflow/AGENTS.md +0 -13
  43. package/.agents/skills-archived/auditing-claude-config/AGENTS.md +0 -20
  44. package/.agents/skills-archived/bugteam/AGENTS.md +0 -29
  45. package/.agents/skills-archived/bugteam/reference/AGENTS.md +0 -19
  46. package/.agents/skills-archived/bugteam/reference/obstacles/AGENTS.md +0 -23
  47. package/.agents/skills-archived/bugteam/scripts/AGENTS.md +0 -29
  48. package/.agents/skills-archived/bugteam/scripts/bugteam_scripts_constants/AGENTS.md +0 -17
  49. package/.agents/skills-archived/codex-review/AGENTS.md +0 -45
  50. package/.agents/skills-archived/codex-review/reference/AGENTS.md +0 -14
  51. package/.agents/skills-archived/codex-review/scripts/codex_review_scripts_constants/AGENTS.md +0 -17
  52. package/.agents/skills-archived/codex-review/test_skill_scaffold.py +13 -3
  53. package/.agents/skills-archived/copilot-review/AGENTS.md +0 -17
  54. package/.agents/skills-archived/pr-converge/AGENTS.md +0 -31
  55. package/.agents/skills-archived/pr-converge/pr_converge_skill_constants/AGENTS.md +0 -25
  56. package/.agents/skills-archived/pr-converge/reference/AGENTS.md +0 -27
  57. package/.agents/skills-archived/pr-converge/reference/obstacles/AGENTS.md +0 -22
  58. package/.agents/skills-archived/pr-converge/scripts/AGENTS.md +0 -45
  59. package/.agents/skills-archived/pr-converge/scripts/pr_converge_scripts_constants/AGENTS.md +0 -17
  60. package/.agents/skills-archived/pr-converge/workflows/AGENTS.md +0 -15
  61. package/.agents/skills-archived/pr-loop-cloud-transport/reference/identity-and-hooks.md +1 -1
  62. package/.agents/skills-archived/recall/AGENTS.md +0 -29
  63. package/.agents/skills-archived/remember/AGENTS.md +0 -30
  64. package/AGENTS.md +0 -112
  65. package/_shared/AGENTS.md +0 -16
  66. package/_shared/advisor/AGENTS.md +0 -21
  67. package/_shared/advisor/advisor-protocol.md +4 -1
  68. package/_shared/advisor/reference/consult-format.md +4 -2
  69. package/_shared/pr-loop/AGENTS.md +0 -27
  70. package/_shared/pr-loop/scripts/AGENTS.md +0 -49
  71. package/_shared/pr-loop/scripts/code_rules_gate_parts/AGENTS.md +0 -41
  72. package/_shared/pr-loop/scripts/codex_review_scripts_constants/AGENTS.md +0 -17
  73. package/_shared/pr-loop/scripts/pr_converge_scripts_constants/AGENTS.md +0 -17
  74. package/_shared/pr-loop/scripts/pr_converge_skill_constants/AGENTS.md +0 -25
  75. package/_shared/pr-loop/scripts/pr_loop_shared_constants/AGENTS.md +0 -25
  76. package/_shared/pr-loop/scripts/tests/AGENTS.md +0 -43
  77. package/_shared/process-tree/AGENTS.md +0 -40
  78. package/audit-rubrics/AGENTS.md +0 -42
  79. package/audit-rubrics/category_rubrics/AGENTS.md +0 -36
  80. package/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md +16 -29
  81. package/audit-rubrics/prompts/AGENTS.md +0 -36
  82. package/audit-rubrics/prompts/category-o-docstring-vs-impl-drift.md +2 -2
  83. package/bin/AGENTS.md +0 -118
  84. package/bin/install.test.mjs +1 -1
  85. package/commands/AGENTS.md +0 -14
  86. package/docs/AGENTS.md +0 -31
  87. package/docs/CODE_RULES.md +3 -3
  88. package/docs/agent-spawn-protocol.md +1 -1
  89. package/docs/references/AGENTS.md +0 -16
  90. package/docs/references/prose-style-enforcement.md +5 -9
  91. package/hooks/AGENTS.md +0 -27
  92. package/hooks/advisory/AGENTS.md +0 -15
  93. package/hooks/blocking/AGENTS.md +0 -122
  94. package/hooks/blocking/claude_md_orphan_file_blocker_parts/AGENTS.md +0 -27
  95. package/hooks/blocking/code_rules_docstrings.py +10 -2138
  96. package/hooks/blocking/code_rules_enforcer.py +0 -121
  97. package/hooks/blocking/code_rules_imports_logging.py +1 -236
  98. package/hooks/blocking/code_rules_shared.py +23 -0
  99. package/hooks/blocking/code_rules_test_layout.py +8 -8
  100. package/hooks/blocking/config/AGENTS.md +0 -9
  101. package/hooks/blocking/config/prose_style_enforcement_constants.py +4 -2
  102. package/hooks/blocking/config/test_prose_style_enforcement_constants.py +5 -1
  103. package/hooks/blocking/inventory_intent_records/AGENTS.md +0 -25
  104. package/hooks/blocking/package_inventory_stale_blocker_parts/AGENTS.md +0 -25
  105. package/hooks/blocking/pii_prevention_blocker_parts/AGENTS.md +0 -23
  106. package/hooks/blocking/precommit_code_rules_gate.py +8 -43
  107. package/hooks/blocking/state_description_blocker.py +1 -7
  108. package/hooks/blocking/tdd_enforcer_parts/AGENTS.md +0 -29
  109. package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +0 -1
  110. package/hooks/blocking/test_code_rules_enforcer_dispatch_wiring.py +0 -8
  111. package/hooks/blocking/test_code_rules_enforcer_module_docstring_roster.py +11 -112
  112. package/hooks/blocking/test_code_rules_enforcer_narrow_edit.py +0 -1
  113. package/hooks/blocking/test_code_rules_enforcer_split_entry_1.py +1 -18
  114. package/hooks/blocking/test_code_rules_shared.py +12 -0
  115. package/hooks/blocking/test_precommit_code_rules_gate.py +32 -179
  116. package/hooks/blocking/test_precommit_code_rules_gate_native_owner.py +0 -1
  117. package/hooks/blocking/test_state_description_blocker.py +6 -4
  118. package/hooks/blocking/test_stop_dispatcher.py +5 -29
  119. package/hooks/git-hooks/AGENTS.md +0 -31
  120. package/hooks/git-hooks/git_hooks_constants/AGENTS.md +0 -20
  121. package/hooks/hooks_constants/AGENTS.md +0 -104
  122. package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +0 -1
  123. package/hooks/hooks_constants/code_rules_enforcer_constants.py +3 -0
  124. package/hooks/hooks_constants/messages.py +0 -2
  125. package/hooks/hooks_constants/precommit_code_rules_gate_constants.py +3 -17
  126. package/hooks/hooks_constants/stop_dispatcher_constants.py +0 -2
  127. package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +0 -1
  128. package/hooks/hooks_constants/test_code_rules_enforcer_constants.py +7 -0
  129. package/hooks/hooks_constants/test_messages.py +5 -3
  130. package/hooks/hooks_constants/test_stop_dispatcher_constants.py +0 -2
  131. package/hooks/lifecycle/AGENTS.md +0 -17
  132. package/hooks/observability/AGENTS.md +0 -19
  133. package/hooks/session/AGENTS.md +0 -37
  134. package/hooks/validation/AGENTS.md +0 -19
  135. package/hooks/validators/AGENTS.md +0 -51
  136. package/hooks/workflow/AGENTS.md +0 -15
  137. package/output-styles/AGENTS.md +0 -14
  138. package/package.json +1 -1
  139. package/rules/AGENTS.md +0 -58
  140. package/rules/claims-as-quotes.md +0 -10
  141. package/rules/code-standards.md +6 -6
  142. package/rules/explore-thoroughly.md +0 -1
  143. package/rules/failure-blast-radius.md +0 -8
  144. package/rules/falsify-before-green.md +0 -8
  145. package/rules/file-global-constants.md +2 -2
  146. package/rules/filesystem-search.md +1 -1
  147. package/rules/git-workflow.md +1 -9
  148. package/rules/hedging-claims.md +2 -6
  149. package/rules/long-horizon-autonomy.md +1 -1
  150. package/rules/measurement-denominators.md +0 -9
  151. package/rules/research-mode.md +0 -6
  152. package/rules/verify-before-asking.md +0 -5
  153. package/rules/verify-runtime-state.md +0 -5
  154. package/scripts/AGENTS.md +0 -55
  155. package/scripts/codex_compat_materializer.py +0 -12
  156. package/scripts/dev_env_scripts_constants/AGENTS.md +0 -20
  157. package/scripts/sync_to_cursor/AGENTS.md +0 -22
  158. package/scripts/sync_to_cursor/rules.py +0 -10
  159. package/scripts/tests/AGENTS.md +0 -34
  160. package/scripts/tests/test_engine.py +0 -1
  161. package/scripts/tests/test_rules.py +0 -1
  162. package/scripts/tests/test_sync_to_cursor.py +0 -1
  163. package/system-prompts/AGENTS.md +0 -24
  164. package/system-prompts/software-engineer.xml +3 -3
  165. package/hooks/blocking/code_rules_dead_argparse_argument.py +0 -554
  166. package/hooks/blocking/code_rules_dead_config_field.py +0 -568
  167. package/hooks/blocking/code_rules_dead_dataclass_field.py +0 -348
  168. package/hooks/blocking/code_rules_dead_module_constant.py +0 -757
  169. package/hooks/blocking/code_rules_dead_split_branch.py +0 -225
  170. package/hooks/blocking/code_rules_mock_completeness.py +0 -295
  171. package/hooks/blocking/code_rules_scope_binding.py +0 -151
  172. package/hooks/blocking/code_rules_unused_imports.py +0 -197
  173. package/hooks/blocking/hedging_language_blocker.py +0 -221
  174. package/hooks/blocking/intent_only_ending_blocker.py +0 -148
  175. package/hooks/blocking/test_code_rules_enforcer_dead_argparse_argument.py +0 -534
  176. package/hooks/blocking/test_code_rules_enforcer_dead_config_field.py +0 -846
  177. package/hooks/blocking/test_code_rules_enforcer_dead_dataclass_field.py +0 -507
  178. package/hooks/blocking/test_code_rules_enforcer_dead_module_constant.py +0 -679
  179. package/hooks/blocking/test_code_rules_enforcer_dead_module_constant_alias.py +0 -133
  180. package/hooks/blocking/test_code_rules_enforcer_dead_module_constant_read_cap.py +0 -103
  181. package/hooks/blocking/test_code_rules_enforcer_dead_split_branch.py +0 -105
  182. package/hooks/blocking/test_code_rules_enforcer_docstring_args_span_scope.py +0 -425
  183. package/hooks/blocking/test_code_rules_enforcer_docstring_cardinal_family.py +0 -176
  184. package/hooks/blocking/test_code_rules_enforcer_docstring_delegation_summary.py +0 -385
  185. package/hooks/blocking/test_code_rules_enforcer_docstring_fallback_branch.py +0 -398
  186. package/hooks/blocking/test_code_rules_enforcer_docstring_field_runmode_outcome.py +0 -129
  187. package/hooks/blocking/test_code_rules_enforcer_docstring_inline_literal_claim.py +0 -93
  188. package/hooks/blocking/test_code_rules_enforcer_docstring_length_constant_superlative.py +0 -198
  189. package/hooks/blocking/test_code_rules_enforcer_docstring_mark_glyph_enumeration.py +0 -262
  190. package/hooks/blocking/test_code_rules_enforcer_docstring_no_consumer.py +0 -93
  191. package/hooks/blocking/test_code_rules_enforcer_docstring_no_network.py +0 -115
  192. package/hooks/blocking/test_code_rules_enforcer_docstring_raises_largezipfile.py +0 -226
  193. package/hooks/blocking/test_code_rules_enforcer_docstring_returns_plural_cardinality.py +0 -207
  194. package/hooks/blocking/test_code_rules_enforcer_docstring_step_dispatch.py +0 -262
  195. package/hooks/blocking/test_code_rules_enforcer_docstring_type_checking_gate.py +0 -164
  196. package/hooks/blocking/test_code_rules_enforcer_docstring_unguarded_payload.py +0 -188
  197. package/hooks/blocking/test_code_rules_enforcer_import_block_sort.py +0 -157
  198. package/hooks/blocking/test_code_rules_enforcer_split_mocks_1.py +0 -303
  199. package/hooks/blocking/test_code_rules_enforcer_split_mocks_2.py +0 -111
  200. package/hooks/blocking/test_code_rules_enforcer_unused_imports.py +0 -656
  201. package/hooks/blocking/test_hedging_language_blocker.py +0 -261
  202. package/hooks/blocking/test_intent_only_ending_blocker.py +0 -209
  203. package/hooks/hooks_constants/dead_argparse_argument_constants.py +0 -28
  204. package/hooks/hooks_constants/dead_config_field_constants.py +0 -39
  205. package/hooks/hooks_constants/dead_dataclass_field_constants.py +0 -25
  206. package/hooks/hooks_constants/dead_module_constant_constants.py +0 -30
  207. package/hooks/hooks_constants/hedging_uncertainty_constants.py +0 -42
  208. package/hooks/hooks_constants/test_dispatcher_constants_docstrings.py +0 -44
  209. package/rules/conservative-action.md +0 -17
  210. package/rules/context7.md +0 -8
  211. package/rules/few-words.md +0 -3
  212. package/rules/parallel-tools.md +0 -23
@@ -1,11 +1,13 @@
1
- """PreToolUse hook that passes Git commit validation to Git hooks.
2
-
3
- Intercepts Bash `git commit` invocations (including `git -C <path> commit`),
4
- emits neutral evidence, and returns control to Git. Configured Git hooks
5
- receive the commit at the commit boundary.
1
+ """Library helper: resolve a directory's Git repository root.
2
+
3
+ ``pii_prevention_blocker.py`` and ``pii_payload_scan.py`` import
4
+ ``resolve_repository_root`` to find the repository a staged-commit scan
5
+ targets, and ``session_edit_stage_gate.py`` imports it to find the
6
+ repository a commit's unstaged-edit check targets. This module carries no
7
+ hook entry point of its own; the native ``git-hooks/pre_commit.py`` runs
8
+ the real commit-time CODE_RULES enforcement.
6
9
  """
7
10
 
8
- import re
9
11
  import subprocess
10
12
  import sys
11
13
  from pathlib import Path
@@ -14,36 +16,12 @@ _hooks_directory = str(Path(__file__).resolve().parent.parent)
14
16
  if _hooks_directory not in sys.path:
15
17
  sys.path.insert(0, _hooks_directory)
16
18
 
17
- from block_main_commit import ( # noqa: E402
18
- is_commit_command,
19
- parse_bash_command_from_stdin,
20
- )
21
-
22
19
  from hooks_constants.precommit_code_rules_gate_constants import ( # noqa: E402
23
20
  ALL_GIT_REPOSITORY_ROOT_COMMAND,
24
21
  GIT_COMMAND_TIMEOUT_SECONDS,
25
- GIT_DASH_C_COMMIT_PATTERN,
26
22
  )
27
23
 
28
24
 
29
- def is_git_commit_invocation(bash_command: str) -> bool:
30
- """Report whether *bash_command* runs a git commit.
31
-
32
- Matches both the plain ``git commit`` substring form and the
33
- ``git -C <path> commit`` form, where the directory flag sits between
34
- the two words.
35
-
36
- Args:
37
- bash_command: The Bash tool command string from the hook payload.
38
-
39
- Returns:
40
- True when the command invokes git commit; False otherwise.
41
- """
42
- if is_commit_command(bash_command):
43
- return True
44
- return re.search(GIT_DASH_C_COMMIT_PATTERN, bash_command) is not None
45
-
46
-
47
25
  def resolve_repository_root(working_directory: str | None) -> Path | None:
48
26
  """Resolve the Git repository root for a directory.
49
27
 
@@ -70,16 +48,3 @@ def resolve_repository_root(working_directory: str | None) -> Path | None:
70
48
  if not top_level_text:
71
49
  return None
72
50
  return Path(top_level_text)
73
-
74
-
75
- def main() -> None:
76
- """Pass Git commit validation through to configured Git hooks."""
77
- bash_command = parse_bash_command_from_stdin()
78
- if not is_git_commit_invocation(bash_command):
79
- sys.exit(0)
80
- sys.stderr.write("Git commit proceeds to configured Git hooks.\n")
81
- sys.exit(0)
82
-
83
-
84
- if __name__ == "__main__":
85
- main()
@@ -20,9 +20,6 @@ if _hooks_dir not in sys.path:
20
20
  sys.path.insert(0, _hooks_dir)
21
21
 
22
22
  from blocking.code_rules_shared import is_ephemeral_path # noqa: E402
23
- from blocking.config.prose_style_enforcement_constants import ( # noqa: E402
24
- prose_style_enforcement_enabled_in_environment,
25
- )
26
23
  from hooks_constants.hook_block_logger import log_hook_block # noqa: E402
27
24
  from hooks_constants.pre_tool_use_stdin import read_hook_input_dictionary_from_stdin # noqa: E402
28
25
  from hooks_constants.state_description_blocker_constants import ( # noqa: E402
@@ -281,8 +278,7 @@ def evaluate(payload_by_key: dict[str, object]) -> str | None:
281
278
 
282
279
  Applies the same tool-name gate, file-extension gate, content selection, and
283
280
  pattern scan the standalone hook applies. Returns the deny-reason text when a
284
- historical phrase is found, or None to allow. Opinionated prose enforcement
285
- is default-off via ``prose_style_enforcement_enabled_in_environment()``.
281
+ historical phrase is found, or None to allow. The check is unconditional.
286
282
 
287
283
  Args:
288
284
  payload_by_key: The PreToolUse payload with tool_name and tool_input.
@@ -291,8 +287,6 @@ def evaluate(payload_by_key: dict[str, object]) -> str | None:
291
287
  The permissionDecisionReason text when the write is denied, or None when
292
288
  the write is allowed.
293
289
  """
294
- if not prose_style_enforcement_enabled_in_environment():
295
- return None
296
290
  raw_tool_name = payload_by_key.get("tool_name", "")
297
291
  tool_name = raw_tool_name if isinstance(raw_tool_name, str) else ""
298
292
  if tool_name not in ("Write", "Edit"):
@@ -1,30 +1 @@
1
- # tdd_enforcer_parts
2
1
 
3
- The concern modules `tdd_enforcer.py` wires together to run the TDD gate. Each
4
- module owns one concern; the entry hook imports them and re-exports their
5
- surface for the test suite.
6
-
7
- ## Modules
8
-
9
- | File | Purpose |
10
- |---|---|
11
- | `path_classification.py` | Classifies a write target (docs, tests, `.claude` trees) the gate skips, and extracts the written text from a Write, Edit, or MultiEdit payload |
12
- | `content_analysis.py` | Decides whether a payload is constants-only, and whether an Edit or MultiEdit merely removes or reorders imports |
13
- | `candidate_paths.py` | Resolves the candidate test files whose freshness can satisfy the gate for a production file |
14
- | `freshness.py` | Checks whether a candidate test was modified within the window and holds a real test function |
15
- | `git_tracking.py` | Detects a write that restores a git-tracked file absent on disk, so a remove-then-Write rewrite is exempt from the gate |
16
- | `decisions.py` | Builds the deny reason and writes the allow or deny decision JSON to stdout |
17
- | `__init__.py` | Package marker |
18
-
19
- ## Subdirectories
20
-
21
- | Entry | Description |
22
- |---|---|
23
- | `config/` | The freshness window, the ancestor-walk limit, the git-tracking command tokens, the source-file extension sets, and the join separator (`tdd_enforcer_constants.py`) |
24
- | `tests/` | pytest suite with one test module per module above |
25
-
26
- ## Running tests
27
-
28
- ```bash
29
- python -m pytest packages/claude-dev-env/hooks/blocking/tdd_enforcer_parts/tests/
30
- ```
@@ -47,7 +47,6 @@ VOID_ADVISORY_CHECK_FUNCTION_NAMES: frozenset[str] = frozenset(
47
47
  {
48
48
  "check_duplicated_format_patterns",
49
49
  "check_flag_gated_scenario_test_naming",
50
- "check_incomplete_mocks",
51
50
  }
52
51
  )
53
52
 
@@ -71,14 +71,6 @@ def test_every_check_function_is_called_in_validate_content() -> None:
71
71
  )
72
72
 
73
73
 
74
- def test_dead_module_constant_check_stays_wired() -> None:
75
- validate_content_source = _validate_content_source()
76
- assert "check_dead_module_constants" in validate_content_source, (
77
- "check_dead_module_constants must stay dispatched from validate_content so a "
78
- "dead exported constant (the MEDIUM_TEXT class) is blocked at Write/Edit time."
79
- )
80
-
81
-
82
74
  def test_known_undispatched_set_lists_only_existing_checks() -> None:
83
75
  all_check_names = set(_all_check_function_names())
84
76
  stale_names = KNOWN_UNDISPATCHED_CHECKS - all_check_names
@@ -1,11 +1,9 @@
1
- """Tests for the module-docstring check roster and docstring tuple-enumeration checks.
1
+ """Tests for the module-docstring check roster.
2
2
 
3
- Both checks catch docstring-prose-vs-implementation drift in a check-registry
4
- module — a hook module that exposes several public ``check_*`` functions and a
5
- module-level tuple of literal attribute names. The drift the
3
+ Catches docstring-prose-vs-implementation drift in a check-registry module — a
4
+ hook module that exposes several public ``check_*`` functions. The drift the
6
5
  ``code_rules_test_assertions.py`` module hit at PR #713 HEAD: a one-line module
7
- docstring that names four of its five public checks, and a function docstring
8
- that enumerates three plumbing attributes while the tuple it reads holds four.
6
+ docstring that names four of its five public checks.
9
7
  """
10
8
 
11
9
  from __future__ import annotations
@@ -32,10 +30,6 @@ def check_module_docstring_names_public_checks(content: str, file_path: str) ->
32
30
  return code_rules_enforcer.check_module_docstring_names_public_checks(content, file_path)
33
31
 
34
32
 
35
- def check_docstring_tuple_enumeration_match(content: str, file_path: str) -> list[str]:
36
- return code_rules_enforcer.check_docstring_tuple_enumeration_match(content, file_path)
37
-
38
-
39
33
  def validate_content(content: str, file_path: str, old_content: str) -> list[str]:
40
34
  return code_rules_enforcer.validate_content(content, file_path, old_content)
41
35
 
@@ -202,101 +196,6 @@ def test_should_handle_module_roster_syntax_error_gracefully() -> None:
202
196
  assert issues == [], f"Syntax error must yield no issues, got: {issues!r}"
203
197
 
204
198
 
205
- def _module_with_drifted_tuple_enumeration() -> str:
206
- return (
207
- '"""Call-args plumbing detection."""\n'
208
- "\n"
209
- '_CALL_ARGS_PLUMBING_ATTRIBUTES = ("call_args", "call_args_list", "called", "call_count")\n'
210
- "\n"
211
- "def check_plumbing(content: str, file_path: str) -> list[str]:\n"
212
- ' """Advise when a test asserts only on call-args plumbing.\n'
213
- "\n"
214
- " A body asserting on call-args plumbing (``call_args``, ``call_args_list``,\n"
215
- " ``.kwargs``) reaches into mock internals rather than observed behavior.\n"
216
- ' """\n'
217
- " for each_name in _CALL_ARGS_PLUMBING_ATTRIBUTES:\n"
218
- " if each_name in content:\n"
219
- " return [each_name]\n"
220
- " return []\n"
221
- )
222
-
223
-
224
- def test_should_flag_docstring_tuple_enumeration_drift() -> None:
225
- issues = check_docstring_tuple_enumeration_match(
226
- _module_with_drifted_tuple_enumeration(), HOOK_INFRASTRUCTURE_PATH
227
- )
228
- assert any("check_plumbing" in each for each in issues), (
229
- f"Expected the drifted enumeration to flag, got: {issues!r}"
230
- )
231
- assert any("kwargs" in each for each in issues), (
232
- f"Expected the docstring-only token named in the message, got: {issues!r}"
233
- )
234
- assert any("called" in each for each in issues), (
235
- f"Expected a tuple-only member named in the message, got: {issues!r}"
236
- )
237
- assert len(issues) == 1
238
-
239
-
240
- def test_should_not_flag_docstring_tuple_enumeration_match() -> None:
241
- source = (
242
- '"""Call-args plumbing detection."""\n'
243
- "\n"
244
- '_CALL_ARGS_PLUMBING_ATTRIBUTES = ("call_args", "call_args_list", "called", "call_count")\n'
245
- "\n"
246
- "def check_plumbing(content: str, file_path: str) -> list[str]:\n"
247
- ' """Advise when a test asserts only on call-args plumbing.\n'
248
- "\n"
249
- " A body asserting on call-args plumbing (``call_args``, ``call_args_list``,\n"
250
- " ``called``, ``call_count``) reaches into mock internals.\n"
251
- ' """\n'
252
- " for each_name in _CALL_ARGS_PLUMBING_ATTRIBUTES:\n"
253
- " if each_name in content:\n"
254
- " return [each_name]\n"
255
- " return []\n"
256
- )
257
- issues = check_docstring_tuple_enumeration_match(source, HOOK_INFRASTRUCTURE_PATH)
258
- assert issues == [], f"Matching enumeration must not flag, got: {issues!r}"
259
-
260
-
261
- def test_should_not_flag_docstring_without_tuple_overlap() -> None:
262
- source = (
263
- '"""Call-args plumbing detection."""\n'
264
- "\n"
265
- '_CALL_ARGS_PLUMBING_ATTRIBUTES = ("call_args", "call_args_list", "called", "call_count")\n'
266
- "\n"
267
- "def check_plumbing(content: str, file_path: str) -> list[str]:\n"
268
- ' """Advise when a test asserts only on mock internals.\n'
269
- "\n"
270
- " A body reading ``foo``, ``bar``, ``baz`` is unrelated to the tuple.\n"
271
- ' """\n'
272
- " for each_name in _CALL_ARGS_PLUMBING_ATTRIBUTES:\n"
273
- " if each_name in content:\n"
274
- " return [each_name]\n"
275
- " return []\n"
276
- )
277
- issues = check_docstring_tuple_enumeration_match(source, HOOK_INFRASTRUCTURE_PATH)
278
- assert issues == [], f"A docstring not naming the tuple must not flag, got: {issues!r}"
279
-
280
-
281
- def test_should_not_flag_when_function_does_not_reference_the_tuple() -> None:
282
- source = (
283
- '"""Call-args plumbing detection."""\n'
284
- "\n"
285
- '_CALL_ARGS_PLUMBING_ATTRIBUTES = ("call_args", "call_args_list", "called", "call_count")\n'
286
- "\n"
287
- "def check_unrelated(content: str, file_path: str) -> list[str]:\n"
288
- ' """Advise on ``call_args`` and ``call_args_list`` usage in tests."""\n'
289
- " return [content[:0]]\n"
290
- )
291
- issues = check_docstring_tuple_enumeration_match(source, HOOK_INFRASTRUCTURE_PATH)
292
- assert issues == [], f"A function not reading the tuple must not flag, got: {issues!r}"
293
-
294
-
295
- def test_should_handle_tuple_enumeration_syntax_error_gracefully() -> None:
296
- issues = check_docstring_tuple_enumeration_match("def broken(\n", HOOK_INFRASTRUCTURE_PATH)
297
- assert issues == [], f"Syntax error must yield no issues, got: {issues!r}"
298
-
299
-
300
199
  def test_validate_content_surfaces_module_roster_drift() -> None:
301
200
  issues = validate_content(
302
201
  _registry_module_omitting_a_check(), HOOK_INFRASTRUCTURE_PATH, old_content=""
@@ -309,11 +208,11 @@ def test_validate_content_surfaces_module_roster_drift() -> None:
309
208
  )
310
209
 
311
210
 
312
- def test_validate_content_surfaces_tuple_enumeration_drift() -> None:
313
- issues = validate_content(
314
- _module_with_drifted_tuple_enumeration(), HOOK_INFRASTRUCTURE_PATH, old_content=""
315
- )
316
- matching_issues = [each for each in issues if "check_plumbing" in each and "enumerat" in each]
317
- assert matching_issues, (
318
- f"Expected validate_content to surface the tuple-enumeration drift, got: {issues!r}"
211
+ def test_imports_logging_module_docstring_names_every_public_check() -> None:
212
+ """The shipped imports-logging module's own roster stays complete after trims."""
213
+ module_path = Path(__file__).resolve().parent / "code_rules_imports_logging.py"
214
+ module_source = module_path.read_text(encoding="utf-8")
215
+ issues = check_module_docstring_names_public_checks(
216
+ module_source, HOOK_INFRASTRUCTURE_PATH
319
217
  )
218
+ assert issues == [], f"Roster drift in code_rules_imports_logging.py: {issues!r}"
@@ -39,7 +39,6 @@ class NarrowEditFixture:
39
39
  ALL_SCOPE_AWARE_RULE_NAMES = frozenset(
40
40
  {
41
41
  "check_blast_radius_declared",
42
- "check_import_block_sorted",
43
42
  "check_magic_values",
44
43
  "check_duplicate_function_body_across_files",
45
44
  "check_same_file_inline_duplicate_body",
@@ -28,8 +28,6 @@ code_rules_enforcer = SimpleNamespace(
28
28
 
29
29
  DUPLICATED_FORMAT_PRODUCTION_FILE_PATH = "packages/app/services/api_client.py"
30
30
 
31
- INCOMPLETE_MOCK_TEST_FILE_PATH = "packages/app/tests/test_orders.py"
32
-
33
31
 
34
32
  def _oversized_function_source(name: str) -> str:
35
33
  body_line_count = code_rules_enforcer.FUNCTION_LENGTH_BLOCKING_THRESHOLD - 1
@@ -39,24 +37,9 @@ def _oversized_function_source(name: str) -> str:
39
37
  return f"def {name}() -> None:\n" + "\n".join(body_lines) + "\n"
40
38
 
41
39
 
42
- def test_should_emit_advisories_for_incomplete_mocks_and_format_patterns_via_validate_content(
40
+ def test_should_emit_advisory_for_duplicated_format_patterns_via_validate_content(
43
41
  capsys: object,
44
42
  ) -> None:
45
- incomplete_mock_source = (
46
- "mock_order = {'id': 1}\n"
47
- "\n"
48
- "def test_order_total() -> None:\n"
49
- " total = mock_order['total']\n"
50
- " assert total > 0\n"
51
- )
52
- code_rules_enforcer.validate_content(
53
- incomplete_mock_source, INCOMPLETE_MOCK_TEST_FILE_PATH
54
- )
55
- captured = getattr(capsys, "readouterr")()
56
- assert "mock_order" in captured.err and "total" in captured.err, (
57
- f"Expected incomplete-mock advisory from validate_content, got: {captured.err!r}"
58
- )
59
-
60
43
  repeated_pattern_source = (
61
44
  "def get_user(user_id: str) -> str:\n"
62
45
  " return f'/api/{user_id}'\n"
@@ -279,3 +279,15 @@ def test_is_ephemeral_path_reads_session_id_from_environment(
279
279
  throwaway_script = scratchpad_directory / "probe.py"
280
280
 
281
281
  assert _SHARED_MODULE.is_ephemeral_path(str(throwaway_script)) is True
282
+
283
+
284
+ def test_is_dedicated_constants_module_true_for_constants_suffix() -> None:
285
+ assert _SHARED_MODULE._is_dedicated_constants_module("hooks_constants/foo_constants.py") is True
286
+
287
+
288
+ def test_is_dedicated_constants_module_true_for_config_directory() -> None:
289
+ assert _SHARED_MODULE._is_dedicated_constants_module("app/config/timing.py") is True
290
+
291
+
292
+ def test_is_dedicated_constants_module_false_for_ordinary_module() -> None:
293
+ assert _SHARED_MODULE._is_dedicated_constants_module("app/services/orders.py") is False
@@ -1,111 +1,23 @@
1
- """Behavior tests for the pass-through precommit_code_rules_gate hook.
1
+ """Behavior tests for the precommit_code_rules_gate library module.
2
2
 
3
- Each test builds a real git repository in a temporary directory, stages
4
- real files, and runs the hook script as a subprocess with a PreToolUse
5
- JSON payload on stdin through the exact production invocation path.
3
+ The module carries no hook entry point of its own; it exports
4
+ ``resolve_repository_root`` for its importers (``pii_prevention_blocker.py``,
5
+ ``pii_payload_scan.py``, ``session_edit_stage_gate.py``).
6
6
  """
7
7
 
8
- import json
9
8
  import subprocess
10
9
  import sys
11
10
  from pathlib import Path
12
11
 
13
- HOOK_PATH = Path(__file__).resolve().parent / "precommit_code_rules_gate.py"
14
-
15
- CLEAN_MODULE_SOURCE = '''"""Increment helper used by the precommit gate tests."""
16
-
17
-
18
- def add_one(number: int) -> int:
19
- """Return *number* plus one.
20
-
21
- Args:
22
- number: The integer to increment.
23
-
24
- Returns:
25
- The incremented integer.
26
- """
27
- return number + 1
28
- '''
29
-
30
- VIOLATING_MODULE_SOURCE = '''"""Module carrying a banned identifier for the precommit gate tests."""
31
-
32
-
33
- def compute_total() -> int:
34
- """Return a fixed total.
35
-
36
- Returns:
37
- The fixed total.
38
- """
39
- result = 1
40
- return result
41
- '''
42
-
43
- WIDGET_MODULE_BASE_SOURCE = '''"""Arithmetic helpers used by the precommit gate worktree tests."""
44
-
45
-
46
- def add_one(number: int) -> int:
47
- """Return *number* plus one.
48
-
49
- Args:
50
- number: The integer to increment.
51
-
52
- Returns:
53
- The incremented integer.
54
- """
55
- return number + 1
56
- '''
57
-
58
- WIDGET_MODULE_EXPANDED_SOURCE = '''"""Arithmetic helpers used by the precommit gate worktree tests."""
59
-
60
-
61
- def add_one(number: int) -> int:
62
- """Return *number* plus one.
63
-
64
- Args:
65
- number: The integer to increment.
66
-
67
- Returns:
68
- The incremented integer.
69
- """
70
- return number + 1
71
-
72
-
73
- def subtract_one(number: int) -> int:
74
- """Return *number* minus one.
75
-
76
- Args:
77
- number: The integer to decrement.
78
-
79
- Returns:
80
- The decremented integer.
81
- """
82
- return number - 1
83
- '''
84
-
85
- WIDGET_TEST_BASE_SOURCE = '''"""Behavior tests for the widget arithmetic helpers."""
86
-
87
- from widget import add_one
88
-
89
-
90
- def test_add_one_increments() -> None:
91
- """add_one returns its argument plus one."""
92
- assert add_one(1) == 2
93
- '''
94
-
95
- WIDGET_TEST_EXPANDED_SOURCE = '''"""Behavior tests for the widget arithmetic helpers."""
96
-
97
- from widget import add_one, subtract_one
98
-
99
-
100
- def test_add_one_increments() -> None:
101
- """add_one returns its argument plus one."""
102
- assert add_one(1) == 2
103
-
104
-
105
- def test_subtract_one_decrements() -> None:
106
- """subtract_one returns its argument minus one."""
107
- assert subtract_one(2) == 1
108
- '''
12
+ try:
13
+ import precommit_code_rules_gate as gate_module
14
+ from precommit_code_rules_gate import resolve_repository_root
15
+ except ModuleNotFoundError:
16
+ _BLOCKING_DIR = str(Path(__file__).resolve().parent)
17
+ if _BLOCKING_DIR not in sys.path:
18
+ sys.path.insert(0, _BLOCKING_DIR)
19
+ import precommit_code_rules_gate as gate_module
20
+ from precommit_code_rules_gate import resolve_repository_root
109
21
 
110
22
 
111
23
  def run_git(repository_root: Path, *git_arguments: str) -> None:
@@ -123,88 +35,29 @@ def initialize_repository(repository_root: Path) -> None:
123
35
  run_git(repository_root, "commit", "--allow-empty", "-m", "initial")
124
36
 
125
37
 
126
- def stage_file(repository_root: Path, relative_name: str, source_text: str) -> None:
127
- (repository_root / relative_name).write_text(source_text, encoding="utf-8")
128
- run_git(repository_root, "add", relative_name)
129
-
130
-
131
- def run_hook(bash_command: str, working_directory: Path) -> subprocess.CompletedProcess[str]:
132
- payload = json.dumps({"tool_input": {"command": bash_command}})
133
- return subprocess.run(
134
- [sys.executable, str(HOOK_PATH)],
135
- input=payload,
136
- capture_output=True,
137
- text=True,
138
- cwd=str(working_directory),
139
- timeout=120,
140
- )
141
-
142
-
143
- def test_non_commit_command_passes_through(tmp_path: Path) -> None:
144
- initialize_repository(tmp_path)
145
- completed_hook = run_hook("git status", tmp_path)
146
- assert completed_hook.returncode == 0
147
- assert completed_hook.stdout.strip() == ""
148
-
149
-
150
- def test_commit_with_clean_staged_python_file_is_allowed(tmp_path: Path) -> None:
151
- initialize_repository(tmp_path)
152
- stage_file(tmp_path, "incrementer.py", CLEAN_MODULE_SOURCE)
153
- completed_hook = run_hook("git commit -m add", tmp_path)
154
- assert completed_hook.returncode == 0
155
- assert completed_hook.stdout.strip() == ""
156
- assert "Git commit proceeds to configured Git hooks." in completed_hook.stderr
157
-
158
-
159
- def test_commit_with_staged_python_file_passes_through(tmp_path: Path) -> None:
38
+ def test_resolve_repository_root_returns_root_for_directory_inside_repository(
39
+ tmp_path: Path,
40
+ ) -> None:
160
41
  initialize_repository(tmp_path)
161
- stage_file(tmp_path, "totals.py", VIOLATING_MODULE_SOURCE)
162
- completed_hook = run_hook("git commit -m add", tmp_path)
163
- assert completed_hook.returncode == 0
164
- assert completed_hook.stdout.strip() == ""
165
- assert "Git commit proceeds to configured Git hooks." in completed_hook.stderr
42
+ nested_directory = tmp_path / "nested"
43
+ nested_directory.mkdir()
44
+ resolved_root = resolve_repository_root(str(nested_directory))
45
+ assert resolved_root == tmp_path.resolve()
166
46
 
167
47
 
168
- def test_git_dash_c_commit_form_passes_through(tmp_path: Path) -> None:
169
- repository_root = tmp_path / "repo"
170
- repository_root.mkdir()
171
- initialize_repository(repository_root)
172
- stage_file(repository_root, "totals.py", VIOLATING_MODULE_SOURCE)
173
- elsewhere = tmp_path / "elsewhere"
174
- elsewhere.mkdir()
175
- quoted_root = str(repository_root)
176
- completed_hook = run_hook(f'git -C "{quoted_root}" commit -m add', elsewhere)
177
- assert completed_hook.returncode == 0
178
- assert completed_hook.stdout.strip() == ""
179
- assert "Git commit proceeds to configured Git hooks." in completed_hook.stderr
48
+ def test_resolve_repository_root_returns_none_outside_a_repository(
49
+ tmp_path: Path,
50
+ ) -> None:
51
+ non_repository_directory = tmp_path / "not_a_repo"
52
+ non_repository_directory.mkdir()
53
+ assert resolve_repository_root(str(non_repository_directory)) is None
180
54
 
181
55
 
182
- def test_commit_with_no_staged_python_files_is_allowed(tmp_path: Path) -> None:
183
- initialize_repository(tmp_path)
184
- stage_file(tmp_path, "notes.md", "# Notes\n")
185
- completed_hook = run_hook("git commit -m docs", tmp_path)
186
- assert completed_hook.returncode == 0
187
- assert completed_hook.stdout.strip() == ""
56
+ def test_module_carries_no_main_entry_point() -> None:
57
+ """The module has no dispatcher-facing hook entry point."""
58
+ assert not hasattr(gate_module, "main")
188
59
 
189
60
 
190
- def test_worktree_commit_passes_through(
191
- tmp_path: Path,
192
- ) -> None:
193
- main_checkout = tmp_path / "main_checkout"
194
- main_checkout.mkdir()
195
- initialize_repository(main_checkout)
196
- (main_checkout / "widget.py").write_text(WIDGET_MODULE_BASE_SOURCE, encoding="utf-8")
197
- (main_checkout / "test_widget.py").write_text(WIDGET_TEST_BASE_SOURCE, encoding="utf-8")
198
- run_git(main_checkout, "add", "widget.py", "test_widget.py")
199
- run_git(main_checkout, "commit", "-m", "base")
200
- worktree_root = tmp_path / "sibling_worktree"
201
- run_git(main_checkout, "worktree", "add", str(worktree_root), "-b", "feature")
202
- stage_file(worktree_root, "widget.py", WIDGET_MODULE_EXPANDED_SOURCE)
203
- stage_file(worktree_root, "test_widget.py", WIDGET_TEST_EXPANDED_SOURCE)
204
- quoted_worktree = str(worktree_root)
205
- completed_hook = run_hook(
206
- f'git -C "{quoted_worktree}" commit -m expand', main_checkout
207
- )
208
- assert completed_hook.returncode == 0
209
- assert completed_hook.stdout.strip() == ""
210
- assert "Git commit proceeds to configured Git hooks." in completed_hook.stderr
61
+ def test_module_carries_no_commit_invocation_detector() -> None:
62
+ """The commit-detection helper lives in block_main_commit, not duplicated here."""
63
+ assert not hasattr(gate_module, "is_git_commit_invocation")
@@ -183,7 +183,6 @@ def test_agent_dispatcher_passes_commit_through_without_running_gate(
183
183
 
184
184
  assert completed_dispatcher.returncode == 0, completed_dispatcher.stderr
185
185
  assert completed_dispatcher.stdout.strip() == ""
186
- assert "Git commit proceeds to configured Git hooks." in completed_dispatcher.stderr
187
186
  assert not gate_marker_path.exists()
188
187
 
189
188
 
@@ -74,7 +74,7 @@ class _RunHook:
74
74
  _run_hook = _RunHook()
75
75
 
76
76
 
77
- def test_historical_phrase_scan_is_default_off(
77
+ def test_historical_phrase_scan_runs_with_the_env_flag_unset(
78
78
  monkeypatch: pytest.MonkeyPatch,
79
79
  ) -> None:
80
80
  monkeypatch.delenv("CLAUDE_PROSE_STYLE_ENFORCEMENT", raising=False)
@@ -87,8 +87,8 @@ def test_historical_phrase_scan_is_default_off(
87
87
  is_prose_style_enabled=False,
88
88
  )
89
89
  assert result.returncode == 0
90
- assert result.stdout == ""
91
- assert _evaluate_without_opt_in(
90
+ assert result.stdout.strip() != ""
91
+ deny_reason = _evaluate_without_opt_in(
92
92
  {
93
93
  "tool_name": "Write",
94
94
  "tool_input": {
@@ -96,7 +96,9 @@ def test_historical_phrase_scan_is_default_off(
96
96
  "content": VIOLATION_INSTEAD_OF_COMMENT,
97
97
  },
98
98
  }
99
- ) is None
99
+ )
100
+ assert deny_reason is not None
101
+ assert "instead of" in deny_reason
100
102
 
101
103
 
102
104
  def test_block_clean_python_comment_passes():