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,425 +0,0 @@
1
- """Span-scope tests for docstring checks.
2
-
3
- Covers two surfaces:
4
-
5
- 1. ``check_docstring_args_single_line_scope_vs_span`` (Category O6) — an Args
6
- entry that claims single-line (block-anchor) scope while the body builds a
7
- multi-line span and routes it through span intersection.
8
-
9
- 2. Changed-span grading for ``check_docstring_runon_sentence`` and
10
- ``check_docstring_prose_wall_without_illustration`` (issue #237) — far-away
11
- no-op grandfathering, introduced-violation blocking, and
12
- ``defer_scope_to_caller`` pass-through.
13
- """
14
-
15
- from __future__ import annotations
16
-
17
- import importlib.util
18
- from pathlib import Path
19
- from types import ModuleType
20
-
21
-
22
- def _load_enforcer_module() -> ModuleType:
23
- module_path = Path(__file__).parent / "code_rules_enforcer.py"
24
- spec = importlib.util.spec_from_file_location("code_rules_enforcer", module_path)
25
- assert spec is not None
26
- assert spec.loader is not None
27
- module = importlib.util.module_from_spec(spec)
28
- spec.loader.exec_module(module)
29
- return module
30
-
31
-
32
- code_rules_enforcer = _load_enforcer_module()
33
-
34
-
35
- def check_docstring_args_single_line_scope_vs_span(content: str, file_path: str) -> list[str]:
36
- return code_rules_enforcer.check_docstring_args_single_line_scope_vs_span(content, file_path)
37
-
38
-
39
- PRODUCTION_FILE_PATH = "/project/scripts/check_import_block_sorted.py"
40
- TEST_FILE_PATH = "/project/scripts/test_check_import_block_sorted.py"
41
- HOOK_INFRASTRUCTURE_PATH = "/home/user/.claude/hooks/blocking/example.py"
42
-
43
-
44
- SPAN_SCOPING_BODY = (
45
- " span_range = range(line_number, block_end_line_number + 1)\n"
46
- " all_violations.append((span_range, message))\n"
47
- " return _scope_violations_to_changed_lines(\n"
48
- " all_violations, all_changed_lines, defer_scope_to_caller\n"
49
- " )\n"
50
- )
51
-
52
-
53
- def test_flags_anchor_line_scope_against_span_intersection_body() -> None:
54
- content = (
55
- "def check_import_block_sorted(\n"
56
- " content: str, file_path: str, all_changed_lines: set[int] | None\n"
57
- ") -> list[str]:\n"
58
- ' """Flag an unsorted import block scoped to the changed lines.\n'
59
- "\n"
60
- " A finding is returned when any line in that block span is among\n"
61
- " all_changed_lines.\n"
62
- "\n"
63
- " Args:\n"
64
- " content: The full file content the write would leave on disk.\n"
65
- " file_path: The destination path used to gate by extension.\n"
66
- " all_changed_lines: Post-edit line numbers the current edit touched, or\n"
67
- " None to treat the whole file as in scope. When provided, a finding\n"
68
- " blocks only when its block-anchor line is among the changed lines.\n"
69
- "\n"
70
- " Returns:\n"
71
- " One issue string per detected finding.\n"
72
- ' """\n' + SPAN_SCOPING_BODY
73
- )
74
- issues = check_docstring_args_single_line_scope_vs_span(content, PRODUCTION_FILE_PATH)
75
- assert len(issues) == 1
76
- assert "check_import_block_sorted" in issues[0]
77
- assert "all_changed_lines" in issues[0]
78
-
79
-
80
- def test_flags_single_line_scope_against_stepped_range_span_body() -> None:
81
- content = (
82
- "def check_import_block_sorted(\n"
83
- " content: str, file_path: str, all_changed_lines: set[int] | None\n"
84
- ") -> list[str]:\n"
85
- ' """Flag an unsorted import block scoped to the changed lines.\n'
86
- "\n"
87
- " Args:\n"
88
- " content: The full file content the write would leave on disk.\n"
89
- " file_path: The destination path used to gate by extension.\n"
90
- " all_changed_lines: Post-edit line numbers, or None. When provided,\n"
91
- " a finding blocks only when its block-anchor line is among the\n"
92
- " changed lines.\n"
93
- "\n"
94
- " Returns:\n"
95
- " One issue string per detected finding.\n"
96
- ' """\n'
97
- " span_range = range(line_number, block_end_line_number + 1, 1)\n"
98
- " all_violations.append((span_range, message))\n"
99
- " return _scope_violations_to_changed_lines(\n"
100
- " all_violations, all_changed_lines, defer_scope_to_caller\n"
101
- " )\n"
102
- )
103
- issues = check_docstring_args_single_line_scope_vs_span(content, PRODUCTION_FILE_PATH)
104
- assert len(issues) == 1
105
- assert "check_import_block_sorted" in issues[0]
106
- assert "all_changed_lines" in issues[0]
107
-
108
-
109
- def test_flags_the_line_is_among_phrasing() -> None:
110
- content = (
111
- "def check_block(\n"
112
- " content: str, file_path: str, all_changed_lines: set[int] | None\n"
113
- ") -> list[str]:\n"
114
- ' """Flag an unsorted block scoped to the diff.\n'
115
- "\n"
116
- " Args:\n"
117
- " content: The file content.\n"
118
- " all_changed_lines: When provided, a finding blocks only when the\n"
119
- " anchor line is among the changed lines.\n"
120
- ' """\n' + SPAN_SCOPING_BODY
121
- )
122
- assert len(check_docstring_args_single_line_scope_vs_span(content, PRODUCTION_FILE_PATH)) == 1
123
-
124
-
125
- def test_passes_when_args_says_any_line_of_the_span() -> None:
126
- content = (
127
- "def check_import_block_sorted(\n"
128
- " content: str, file_path: str, all_changed_lines: set[int] | None\n"
129
- ") -> list[str]:\n"
130
- ' """Flag an unsorted import block scoped to the changed lines.\n'
131
- "\n"
132
- " Args:\n"
133
- " content: The full file content.\n"
134
- " all_changed_lines: Post-edit line numbers the current edit touched, or\n"
135
- " None to treat the whole file as in scope. When provided, a finding\n"
136
- " blocks only when any line of its block span is among the changed lines.\n"
137
- ' """\n' + SPAN_SCOPING_BODY
138
- )
139
- assert check_docstring_args_single_line_scope_vs_span(content, PRODUCTION_FILE_PATH) == []
140
-
141
-
142
- def test_passes_when_body_scopes_by_single_line_not_a_span() -> None:
143
- content = (
144
- "def check_one_line(\n"
145
- " content: str, file_path: str, all_changed_lines: set[int] | None\n"
146
- ") -> list[str]:\n"
147
- ' """Flag a violation on a single line scoped to the diff.\n'
148
- "\n"
149
- " Args:\n"
150
- " content: The file content.\n"
151
- " all_changed_lines: When provided, a finding blocks only when its\n"
152
- " anchor line is among the changed lines.\n"
153
- ' """\n'
154
- " if all_changed_lines is not None and line_number not in all_changed_lines:\n"
155
- " return []\n"
156
- " return [message]\n"
157
- )
158
- assert check_docstring_args_single_line_scope_vs_span(content, PRODUCTION_FILE_PATH) == []
159
-
160
-
161
- def test_passes_when_no_single_line_scope_phrase() -> None:
162
- content = (
163
- "def check_import_block_sorted(\n"
164
- " content: str, file_path: str, all_changed_lines: set[int] | None\n"
165
- ") -> list[str]:\n"
166
- ' """Flag an unsorted import block scoped to the changed lines.\n'
167
- "\n"
168
- " Args:\n"
169
- " content: The full file content.\n"
170
- " all_changed_lines: Post-edit line numbers the current edit touched.\n"
171
- ' """\n' + SPAN_SCOPING_BODY
172
- )
173
- assert check_docstring_args_single_line_scope_vs_span(content, PRODUCTION_FILE_PATH) == []
174
-
175
-
176
- def test_test_files_are_exempt() -> None:
177
- content = (
178
- "def check_import_block_sorted(\n"
179
- " content: str, file_path: str, all_changed_lines: set[int] | None\n"
180
- ") -> list[str]:\n"
181
- ' """Flag an unsorted import block.\n'
182
- "\n"
183
- " Args:\n"
184
- " all_changed_lines: A finding blocks only when its anchor line is\n"
185
- " among the changed lines.\n"
186
- ' """\n' + SPAN_SCOPING_BODY
187
- )
188
- assert check_docstring_args_single_line_scope_vs_span(content, TEST_FILE_PATH) == []
189
-
190
-
191
- def test_hook_infrastructure_is_not_exempt() -> None:
192
- content = (
193
- "def check_import_block_sorted(\n"
194
- " content: str, file_path: str, all_changed_lines: set[int] | None\n"
195
- ") -> list[str]:\n"
196
- ' """Flag an unsorted import block.\n'
197
- "\n"
198
- " Args:\n"
199
- " all_changed_lines: A finding blocks only when its block-anchor line is\n"
200
- " among the changed lines.\n"
201
- ' """\n' + SPAN_SCOPING_BODY
202
- )
203
- assert (
204
- len(check_docstring_args_single_line_scope_vs_span(content, HOOK_INFRASTRUCTURE_PATH)) == 1
205
- )
206
-
207
- # ---------------------------------------------------------------------------
208
- # Changed-span grading for the two whole-file docstring checks (issue #237 / P-41)
209
- # check_docstring_runon_sentence and check_docstring_prose_wall_without_illustration
210
- # ---------------------------------------------------------------------------
211
-
212
-
213
- def check_docstring_runon_sentence(
214
- content: str,
215
- file_path: str,
216
- all_changed_lines: set[int] | None = None,
217
- defer_scope_to_caller: bool = False,
218
- ) -> list[str]:
219
- return code_rules_enforcer.check_docstring_runon_sentence(
220
- content, file_path, all_changed_lines, defer_scope_to_caller
221
- )
222
-
223
-
224
- def check_docstring_prose_wall_without_illustration(
225
- content: str,
226
- file_path: str,
227
- all_changed_lines: set[int] | None = None,
228
- defer_scope_to_caller: bool = False,
229
- ) -> list[str]:
230
- return code_rules_enforcer.check_docstring_prose_wall_without_illustration(
231
- content, file_path, all_changed_lines, defer_scope_to_caller
232
- )
233
-
234
-
235
- _CLEAN_HELPER_TAIL = (
236
- "\n"
237
- "def clean_helper() -> str:\n"
238
- ' """Return a short status token for the board.\n'
239
- "\n"
240
- " Each call names one vessel and its final port.\n"
241
- ' """\n'
242
- ' return "ok"\n'
243
- )
244
-
245
-
246
- def _module_with_far_away_runon_and_clean_helper() -> str:
247
- return (
248
- '"""Owns the SIGINT install/restore/installability check, the atexit terminal-record\n'
249
- "registration, and the interrupted-run finalizer — the non-promoter-specific\n"
250
- "machinery that brackets a run so the JSONL artifact always carries a terminal\n"
251
- "record and an in-flight theme record on interrupt.\n"
252
- '"""\n'
253
- + _CLEAN_HELPER_TAIL
254
- )
255
-
256
-
257
- def _module_with_far_away_prose_wall_and_clean_helper() -> str:
258
- return (
259
- '"""Assemble the nightly voyage tally from the harbor scans.\n'
260
- "\n"
261
- "A scan names one vessel and where it dropped anchor.\n"
262
- "The tally walks the scans in arrival order and keeps that order.\n"
263
- "A calm voyage ends well for every vessel it carried.\n"
264
- "A halted voyage marks the vessel it was near when the storm arrived.\n"
265
- "A wrecked voyage marks the vessel that sank and stops the walk there.\n"
266
- "The tally groups the vessels by their final port for the harbor.\n"
267
- "The harbor reads the tally and sees every arrival at a glance.\n"
268
- '"""\n'
269
- + _CLEAN_HELPER_TAIL
270
- )
271
-
272
-
273
- def test_runon_far_away_no_op_is_grandfathered() -> None:
274
- content = _module_with_far_away_runon_and_clean_helper()
275
- clean_helper_line = content.splitlines().index("def clean_helper() -> str:") + 1
276
- all_changed_lines = set(range(clean_helper_line, clean_helper_line + 6))
277
- unscoped = check_docstring_runon_sentence(content, PRODUCTION_FILE_PATH)
278
- assert any("run-on" in each for each in unscoped), (
279
- f"control: unscoped must still see the far-away run-on, got {unscoped!r}"
280
- )
281
- scoped = check_docstring_runon_sentence(
282
- content, PRODUCTION_FILE_PATH, all_changed_lines=all_changed_lines
283
- )
284
- assert scoped == [], (
285
- f"far-away run-on must be grandfathered on a clean-helper edit, got {scoped!r}"
286
- )
287
-
288
-
289
- def test_runon_introduced_violation_blocks() -> None:
290
- content = _module_with_far_away_runon_and_clean_helper()
291
- unscoped = check_docstring_runon_sentence(content, PRODUCTION_FILE_PATH)
292
- assert any("run-on" in each for each in unscoped)
293
- scoped = check_docstring_runon_sentence(
294
- content, PRODUCTION_FILE_PATH, all_changed_lines={1}
295
- )
296
- assert any("run-on" in each for each in scoped), (
297
- f"introduced/touched run-on must block, got {scoped!r}"
298
- )
299
-
300
-
301
- def test_prose_wall_far_away_no_op_is_grandfathered() -> None:
302
- content = _module_with_far_away_prose_wall_and_clean_helper()
303
- clean_helper_line = content.splitlines().index("def clean_helper() -> str:") + 1
304
- all_changed_lines = set(range(clean_helper_line, clean_helper_line + 6))
305
- unscoped = check_docstring_prose_wall_without_illustration(
306
- content, PRODUCTION_FILE_PATH
307
- )
308
- assert any("worked example" in each for each in unscoped), (
309
- f"control: unscoped must still see the far-away wall, got {unscoped!r}"
310
- )
311
- scoped = check_docstring_prose_wall_without_illustration(
312
- content, PRODUCTION_FILE_PATH, all_changed_lines=all_changed_lines
313
- )
314
- assert scoped == [], (
315
- f"far-away wall must be grandfathered on a clean-helper edit, got {scoped!r}"
316
- )
317
-
318
-
319
- def test_prose_wall_introduced_violation_blocks() -> None:
320
- content = _module_with_far_away_prose_wall_and_clean_helper()
321
- unscoped = check_docstring_prose_wall_without_illustration(
322
- content, PRODUCTION_FILE_PATH
323
- )
324
- assert any("worked example" in each for each in unscoped)
325
- scoped = check_docstring_prose_wall_without_illustration(
326
- content, PRODUCTION_FILE_PATH, all_changed_lines={1}
327
- )
328
- assert any("worked example" in each for each in scoped), (
329
- f"introduced/touched wall must block, got {scoped!r}"
330
- )
331
-
332
-
333
- def test_runon_defer_scope_returns_all_violations() -> None:
334
- content = _module_with_far_away_runon_and_clean_helper()
335
- deferred = check_docstring_runon_sentence(
336
- content,
337
- PRODUCTION_FILE_PATH,
338
- all_changed_lines={999},
339
- defer_scope_to_caller=True,
340
- )
341
- assert any("run-on" in each for each in deferred)
342
-
343
-
344
- def _function_with_runon_docstring() -> str:
345
- return (
346
- "def clean_helper() -> str:\n"
347
- ' """Owns the SIGINT install/restore/installability check, the atexit terminal-record\n'
348
- " registration, and the interrupted-run finalizer — the non-promoter-specific\n"
349
- " machinery that brackets a run so the JSONL artifact always carries a terminal\n"
350
- " record and an in-flight theme record on interrupt.\n"
351
- ' """\n'
352
- ' return "ok"\n'
353
- )
354
-
355
-
356
- def test_runon_docstring_body_edit_blocks_without_def_line() -> None:
357
- content = _function_with_runon_docstring()
358
- unscoped = check_docstring_runon_sentence(content, PRODUCTION_FILE_PATH)
359
- assert any("run-on" in each for each in unscoped), (
360
- f"control: unscoped must see the function run-on, got {unscoped!r}"
361
- )
362
- docstring_body_lines = {2, 3, 4, 5}
363
- scoped = check_docstring_runon_sentence(
364
- content, PRODUCTION_FILE_PATH, all_changed_lines=docstring_body_lines
365
- )
366
- assert any("run-on" in each for each in scoped), (
367
- f"docstring-body edit must block even when the def line is untouched, got {scoped!r}"
368
- )
369
-
370
-
371
- def test_runon_post_docstring_body_edit_is_grandfathered() -> None:
372
- content = _function_with_runon_docstring()
373
- return_line = content.splitlines().index(' return "ok"') + 1
374
- scoped = check_docstring_runon_sentence(
375
- content, PRODUCTION_FILE_PATH, all_changed_lines={return_line}
376
- )
377
- assert scoped == [], (
378
- f"post-docstring body edit must grandfather the pre-existing run-on, got {scoped!r}"
379
- )
380
-
381
-
382
- def _function_with_prose_wall_docstring() -> str:
383
- return (
384
- "def clean_helper() -> str:\n"
385
- ' """Assemble the nightly voyage tally from the harbor scans.\n'
386
- "\n"
387
- " A scan names one vessel and where it dropped anchor.\n"
388
- " The tally walks the scans in arrival order and keeps that order.\n"
389
- " A calm voyage ends well for every vessel it carried.\n"
390
- " A halted voyage marks the vessel it was near when the storm arrived.\n"
391
- " A wrecked voyage marks the vessel that sank and stops the walk there.\n"
392
- " The tally groups the vessels by their final port for the harbor.\n"
393
- " The harbor reads the tally and sees every arrival at a glance.\n"
394
- ' """\n'
395
- ' return "ok"\n'
396
- )
397
-
398
-
399
- def test_prose_wall_docstring_body_edit_blocks_without_def_line() -> None:
400
- content = _function_with_prose_wall_docstring()
401
- unscoped = check_docstring_prose_wall_without_illustration(
402
- content, PRODUCTION_FILE_PATH
403
- )
404
- assert any("worked example" in each for each in unscoped), (
405
- f"control: unscoped must see the function wall, got {unscoped!r}"
406
- )
407
- docstring_body_lines = set(range(2, 12))
408
- scoped = check_docstring_prose_wall_without_illustration(
409
- content, PRODUCTION_FILE_PATH, all_changed_lines=docstring_body_lines
410
- )
411
- assert any("worked example" in each for each in scoped), (
412
- f"docstring-body edit must block even when the def line is untouched, got {scoped!r}"
413
- )
414
-
415
-
416
- def test_prose_wall_defer_scope_returns_all_violations() -> None:
417
- content = _module_with_far_away_prose_wall_and_clean_helper()
418
- deferred = check_docstring_prose_wall_without_illustration(
419
- content,
420
- PRODUCTION_FILE_PATH,
421
- all_changed_lines={999},
422
- defer_scope_to_caller=True,
423
- )
424
- assert any("worked example" in each for each in deferred)
425
-
@@ -1,176 +0,0 @@
1
- """Tests for check_docstring_cardinal_count_matches_constant_family — O6 drift.
2
-
3
- A docstring states a cardinal count of an outcome family ("Covers the four
4
- outcome branches: ...") and enumerates the family members in prose, while the
5
- module references more members of that constant family than the count names. The
6
- prose under-describes the code: a reader trusts "four" and the list to be the
7
- full set, but the module imports and exercises a fifth outcome. This is the
8
- deterministic cardinal-count slice of Category O6 docstring-prose-vs-
9
- implementation drift, the shape that appears when a producer adds an outcome
10
- branch but leaves the test-module summary at the old count. The check runs on
11
- test files because the drift class lives in a test-module docstring.
12
- """
13
-
14
- from __future__ import annotations
15
-
16
- import importlib.util
17
- from pathlib import Path
18
- from types import ModuleType
19
-
20
-
21
- def _load_enforcer_module() -> ModuleType:
22
- module_path = Path(__file__).parent / "code_rules_enforcer.py"
23
- spec = importlib.util.spec_from_file_location("code_rules_enforcer", module_path)
24
- assert spec is not None
25
- assert spec.loader is not None
26
- module = importlib.util.module_from_spec(spec)
27
- spec.loader.exec_module(module)
28
- return module
29
-
30
-
31
- code_rules_enforcer = _load_enforcer_module()
32
-
33
-
34
- def check_docstring_cardinal_count_matches_constant_family(
35
- content: str, file_path: str
36
- ) -> list[str]:
37
- return code_rules_enforcer.check_docstring_cardinal_count_matches_constant_family(
38
- content, file_path
39
- )
40
-
41
-
42
- def validate_content(content: str, file_path: str, old_content: str) -> list[str]:
43
- return code_rules_enforcer.validate_content(content, file_path, old_content)
44
-
45
-
46
- PRODUCTION_FILE_PATH = "/project/src/dropdown_fix_rule.py"
47
- TEST_FILE_PATH = "/project/src/tests/test_dropdown_fix_rule.py"
48
-
49
-
50
- def _test_module(summary_line: str, enumeration_line: str) -> str:
51
- return (
52
- '"""Behavioral tests for classify_dropdown_fix.\n'
53
- "\n"
54
- f"{summary_line}\n"
55
- f"{enumeration_line}\n"
56
- '"""\n'
57
- "\n"
58
- "from stp_dropdown_color_fix.config.status_codes import (\n"
59
- " OUTCOME_ALREADY_OK,\n"
60
- " OUTCOME_OFFENDER_LOW_CONTRAST,\n"
61
- " OUTCOME_OFFENDER_MISSING,\n"
62
- " OUTCOME_OFFENDER_UNREADABLE,\n"
63
- " OUTCOME_SKIPPED_NO_SAFE_SOURCE,\n"
64
- ")\n"
65
- "\n"
66
- "def test_skipped() -> None:\n"
67
- " assert classify() == OUTCOME_SKIPPED_NO_SAFE_SOURCE\n"
68
- "\n"
69
- "def test_missing() -> None:\n"
70
- " assert classify() == OUTCOME_OFFENDER_MISSING\n"
71
- "\n"
72
- "def test_low_contrast() -> None:\n"
73
- " assert classify() == OUTCOME_OFFENDER_LOW_CONTRAST\n"
74
- "\n"
75
- "def test_already_ok() -> None:\n"
76
- " assert classify() == OUTCOME_ALREADY_OK\n"
77
- "\n"
78
- "def test_unreadable() -> None:\n"
79
- " assert classify() == OUTCOME_OFFENDER_UNREADABLE\n"
80
- )
81
-
82
-
83
- def _drifted_module() -> str:
84
- return _test_module(
85
- "Covers the four outcome branches: skipped_no_safe_source,",
86
- "offender_missing (case A), offender_low_contrast (case B), already_ok.",
87
- )
88
-
89
-
90
- def _matching_module() -> str:
91
- return _test_module(
92
- "Covers the five outcome branches: skipped_no_safe_source, offender_missing,",
93
- "offender_low_contrast, already_ok, offender_unreadable.",
94
- )
95
-
96
-
97
- def test_should_flag_test_module_omitting_an_outcome() -> None:
98
- issues = check_docstring_cardinal_count_matches_constant_family(
99
- _drifted_module(), TEST_FILE_PATH
100
- )
101
- assert any("offender_unreadable" in each for each in issues), (
102
- "A test-module docstring claiming four outcome branches while the module "
103
- f"references five OUTCOME_ constants must be flagged, got: {issues!r}"
104
- )
105
-
106
-
107
- def test_should_report_category_o6_in_the_message() -> None:
108
- issues = check_docstring_cardinal_count_matches_constant_family(
109
- _drifted_module(), TEST_FILE_PATH
110
- )
111
- assert any("O6" in each for each in issues), (
112
- f"Expected the Category O6 label in the message, got: {issues!r}"
113
- )
114
-
115
-
116
- def test_should_also_flag_production_module() -> None:
117
- issues = check_docstring_cardinal_count_matches_constant_family(
118
- _drifted_module(), PRODUCTION_FILE_PATH
119
- )
120
- assert issues != [], (
121
- f"The cardinal drift must be flagged on a production file too, got: {issues!r}"
122
- )
123
-
124
-
125
- def test_should_not_flag_when_count_and_enumeration_match_family() -> None:
126
- issues = check_docstring_cardinal_count_matches_constant_family(
127
- _matching_module(), TEST_FILE_PATH
128
- )
129
- assert issues == [], (
130
- f"A docstring naming all five referenced outcomes must not be flagged, got: {issues!r}"
131
- )
132
-
133
-
134
- def test_should_not_flag_without_a_cardinal_phrase() -> None:
135
- source = _test_module(
136
- "Covers these outcome branches: skipped_no_safe_source,",
137
- "offender_missing, offender_low_contrast, already_ok.",
138
- )
139
- issues = check_docstring_cardinal_count_matches_constant_family(source, TEST_FILE_PATH)
140
- assert issues == [], (
141
- f"An enumeration with no cardinal count word must not bind, got: {issues!r}"
142
- )
143
-
144
-
145
- def test_should_not_flag_single_passing_mention() -> None:
146
- source = _test_module(
147
- "Asserts the four guard paths reach already_ok when the dropdown is",
148
- "absent and the dialer keeps its painted swatch.",
149
- )
150
- issues = check_docstring_cardinal_count_matches_constant_family(source, TEST_FILE_PATH)
151
- assert issues == [], f"A single overlapping token must not bind the family, got: {issues!r}"
152
-
153
-
154
- def test_should_not_flag_when_family_has_one_referenced_member() -> None:
155
- source = (
156
- '"""Covers the four outcome branches: already_ok, more, words, here."""\n'
157
- "from status_codes import OUTCOME_ALREADY_OK\n"
158
- "\n"
159
- "def test_only() -> None:\n"
160
- " assert classify() == OUTCOME_ALREADY_OK\n"
161
- )
162
- issues = check_docstring_cardinal_count_matches_constant_family(source, TEST_FILE_PATH)
163
- assert issues == [], f"A family with one referenced member must not bind, got: {issues!r}"
164
-
165
-
166
- def test_should_handle_syntax_error_gracefully() -> None:
167
- issues = check_docstring_cardinal_count_matches_constant_family("def fetch(\n", TEST_FILE_PATH)
168
- assert issues == [], f"Syntax error must yield no issues, got: {issues!r}"
169
-
170
-
171
- def test_validate_content_surfaces_cardinal_family_drift() -> None:
172
- issues = validate_content(_drifted_module(), TEST_FILE_PATH, old_content="")
173
- matching_issues = [each for each in issues if "offender_unreadable" in each and "O6" in each]
174
- assert matching_issues, (
175
- f"Expected validate_content to surface the O6 cardinal-family drift, got: {issues!r}"
176
- )