claude-dev-env 1.38.0 → 1.39.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 (271) hide show
  1. package/CLAUDE.md +10 -36
  2. package/_shared/pr-loop/audit-reply-template.md +147 -0
  3. package/_shared/pr-loop/fix-protocol.md +25 -4
  4. package/_shared/pr-loop/gh-payloads.md +37 -50
  5. package/_shared/pr-loop/scripts/code_rules_gate.py +0 -60
  6. package/_shared/pr-loop/scripts/config/post_audit_thread_constants.py +189 -0
  7. package/_shared/pr-loop/scripts/post_audit_thread.py +947 -0
  8. package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +0 -19
  9. package/_shared/pr-loop/scripts/tests/test_post_audit_thread.py +923 -0
  10. package/_shared/pr-loop/scripts/tests/test_post_audit_thread_constants.py +127 -0
  11. package/_shared/pr-loop/state-schema.md +1 -1
  12. package/agents/clean-coder.md +2 -2
  13. package/bin/install.mjs +6 -7
  14. package/bin/install.test.mjs +8 -0
  15. package/commands/doc-gist.md +16 -0
  16. package/commands/plan.md +0 -2
  17. package/commands/review-plan.md +1 -1
  18. package/docs/CODE_RULES.md +122 -2
  19. package/hooks/blocking/bot_mention_comment_blocker.py +75 -0
  20. package/hooks/blocking/code_rules_enforcer.py +1236 -161
  21. package/hooks/blocking/convergence_gate_blocker.py +130 -0
  22. package/hooks/blocking/destructive_command_blocker.py +74 -0
  23. package/hooks/blocking/gh_body_arg_blocker.py +30 -0
  24. package/hooks/blocking/md_to_html_blocker.py +119 -0
  25. package/hooks/blocking/test_bot_mention_comment_blocker.py +131 -0
  26. package/hooks/blocking/test_code_rules_enforcer.py +21 -0
  27. package/hooks/blocking/test_code_rules_enforcer_any_exempt_files.py +70 -0
  28. package/hooks/blocking/test_code_rules_enforcer_any_imports_and_cast.py +92 -0
  29. package/hooks/blocking/test_code_rules_enforcer_banned_import_alias.py +143 -0
  30. package/hooks/blocking/test_code_rules_enforcer_banned_prefixes.py +152 -0
  31. package/hooks/blocking/test_code_rules_enforcer_bare_except.py +120 -0
  32. package/hooks/blocking/test_code_rules_enforcer_boundary_types.py +175 -0
  33. package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +0 -1
  34. package/hooks/blocking/test_code_rules_enforcer_collection_prefix.py +50 -0
  35. package/hooks/blocking/test_code_rules_enforcer_docstring_format.py +255 -0
  36. package/hooks/blocking/test_code_rules_enforcer_inline_tuple_string_magic.py +130 -0
  37. package/hooks/blocking/test_code_rules_enforcer_stub_implementations.py +141 -0
  38. package/hooks/blocking/test_code_rules_enforcer_test_branching.py +143 -0
  39. package/hooks/blocking/test_code_rules_enforcer_thin_wrapper_files.py +169 -0
  40. package/hooks/blocking/test_code_rules_enforcer_todo_markers.py +99 -0
  41. package/hooks/blocking/test_code_rules_enforcer_typed_dict_pairs.py +141 -0
  42. package/hooks/blocking/test_code_rules_enforcer_unused_imports.py +158 -0
  43. package/hooks/blocking/test_convergence_gate_blocker.py +63 -0
  44. package/hooks/blocking/test_destructive_command_blocker.py +146 -0
  45. package/hooks/blocking/test_destructive_command_blocker_no_verify.py +102 -0
  46. package/hooks/blocking/test_gh_body_arg_blocker.py +45 -0
  47. package/hooks/blocking/test_md_to_html_blocker.py +317 -0
  48. package/hooks/config/any_type_config.py +7 -0
  49. package/hooks/config/banned_identifiers_constants.py +11 -0
  50. package/hooks/config/blocking_check_limits.py +38 -0
  51. package/hooks/config/bot_mention_comment_blocker_constants.py +20 -0
  52. package/hooks/config/code_rules_enforcer_constants.py +53 -0
  53. package/hooks/config/convergence_branch_constants.py +9 -0
  54. package/hooks/config/doc_gist_auto_publish_constants.py +18 -0
  55. package/hooks/config/html_companion_constants.py +20 -0
  56. package/hooks/config/inline_tuple_string_magic_constants.py +22 -0
  57. package/hooks/config/test_banned_identifiers_constants.py +17 -0
  58. package/hooks/hooks.json +28 -20
  59. package/hooks/pyproject.toml +69 -0
  60. package/hooks/validators/mypy_integration.py +47 -1
  61. package/hooks/validators/run_all_validators.py +3 -3
  62. package/hooks/validators/test_mypy_integration.py +50 -1
  63. package/hooks/workflow/doc_gist_auto_publish.py +144 -0
  64. package/hooks/workflow/md_to_html_companion.py +365 -0
  65. package/hooks/workflow/test_doc_gist_auto_publish.py +117 -0
  66. package/hooks/workflow/test_md_to_html_companion.py +452 -0
  67. package/package.json +1 -1
  68. package/rules/gh-body-file.md +2 -0
  69. package/scripts/Install-SweepEmptyDirs.ps1 +111 -0
  70. package/scripts/check.ps1 +106 -0
  71. package/scripts/config/timing.py +11 -0
  72. package/scripts/sweep_empty_dirs.py +138 -0
  73. package/scripts/sync_to_cursor/rules.py +1 -1
  74. package/scripts/test_sweep_empty_dirs.py +183 -0
  75. package/skills/_shared/pr-loop/prompts/pr-consistency-audit.xml +323 -0
  76. package/skills/_shared/pr-loop/scripts/_cli_utils.py +22 -0
  77. package/skills/_shared/pr-loop/scripts/_path_resolver.py +165 -0
  78. package/skills/_shared/pr-loop/scripts/_xml_utils.py +20 -0
  79. package/skills/_shared/pr-loop/scripts/build_audit_prompt.py +182 -0
  80. package/skills/_shared/pr-loop/scripts/build_fix_prompt.py +185 -0
  81. package/skills/_shared/pr-loop/scripts/config/__init__.py +0 -0
  82. package/skills/_shared/pr-loop/scripts/config/path_resolver_constants.py +78 -0
  83. package/skills/_shared/pr-loop/scripts/init_loop_state.py +135 -0
  84. package/skills/_shared/pr-loop/scripts/teardown_worktrees.py +175 -0
  85. package/skills/_shared/pr-loop/scripts/write_audit_outcomes.py +182 -0
  86. package/skills/_shared/pr-loop/scripts/write_fix_outcomes.py +206 -0
  87. package/skills/bugteam/CONSTRAINTS.md +21 -22
  88. package/skills/bugteam/EXAMPLES.md +3 -3
  89. package/skills/bugteam/PROMPTS.md +227 -67
  90. package/skills/bugteam/SKILL.md +114 -455
  91. package/skills/bugteam/reference/README.md +1 -1
  92. package/skills/bugteam/reference/audit-and-teammates.md +112 -39
  93. package/skills/bugteam/reference/audit-contract.md +4 -22
  94. package/skills/bugteam/reference/copilot-gap-analysis.md +8 -5
  95. package/skills/bugteam/reference/design-rationale.md +2 -2
  96. package/skills/bugteam/reference/github-pr-reviews.md +50 -57
  97. package/skills/bugteam/reference/obstacles/audit-assign-ids.md +13 -0
  98. package/skills/bugteam/reference/obstacles/audit-capture-excerpts.md +13 -0
  99. package/skills/bugteam/reference/obstacles/audit-walk-categories.md +13 -0
  100. package/skills/bugteam/reference/obstacles/audit-write-xml.md +13 -0
  101. package/skills/bugteam/reference/obstacles/fix-append-summary.md +13 -0
  102. package/skills/bugteam/reference/obstacles/fix-apply-fixes.md +13 -0
  103. package/skills/bugteam/reference/obstacles/fix-git-add-commit.md +13 -0
  104. package/skills/bugteam/reference/obstacles/fix-git-push.md +13 -0
  105. package/skills/bugteam/reference/obstacles/fix-post-reply.md +13 -0
  106. package/skills/bugteam/reference/obstacles/fix-publish-summary.md +13 -0
  107. package/skills/bugteam/reference/obstacles/fix-py-compile.md +13 -0
  108. package/skills/bugteam/reference/obstacles/fix-read-files.md +13 -0
  109. package/skills/bugteam/reference/obstacles/fix-resolve-thread.md +13 -0
  110. package/skills/bugteam/reference/obstacles/fix-test-suite.md +13 -0
  111. package/skills/bugteam/reference/obstacles/fix-violation-count.md +13 -0
  112. package/skills/bugteam/reference/obstacles/fix-write-xml.md +13 -0
  113. package/skills/bugteam/reference/team-setup.md +106 -9
  114. package/skills/bugteam/reference/teardown-publish-permissions.md +39 -8
  115. package/skills/bugteam/scripts/README.md +60 -0
  116. package/skills/bugteam/scripts/_claude_permissions_common.py +358 -0
  117. package/skills/bugteam/scripts/bugteam_code_rules_gate.py +976 -0
  118. package/skills/bugteam/scripts/bugteam_fix_hookspath.py +375 -0
  119. package/skills/bugteam/scripts/bugteam_preflight.py +294 -0
  120. package/skills/bugteam/scripts/config/bugteam_code_rules_gate_constants.py +25 -0
  121. package/skills/bugteam/scripts/config/bugteam_fix_hookspath_constants.py +26 -0
  122. package/skills/bugteam/scripts/config/bugteam_preflight_constants.py +35 -0
  123. package/skills/bugteam/scripts/config/claude_permissions_common_constants.py +20 -0
  124. package/skills/bugteam/scripts/config/probe_code_rules_enforcer_check_constants.py +12 -0
  125. package/skills/bugteam/scripts/config/windows_safe_rmtree_constants.py +7 -0
  126. package/skills/bugteam/scripts/grant_project_claude_permissions.py +175 -0
  127. package/skills/bugteam/scripts/probe_code_rules_enforcer_check.py +107 -0
  128. package/skills/bugteam/scripts/revoke_project_claude_permissions.py +220 -0
  129. package/skills/bugteam/scripts/test__claude_permissions_common.py +112 -0
  130. package/skills/bugteam/scripts/test_bugteam_code_rules_gate.py +400 -0
  131. package/skills/bugteam/scripts/test_bugteam_fix_hookspath.py +384 -0
  132. package/skills/bugteam/scripts/test_bugteam_preflight.py +268 -0
  133. package/skills/bugteam/scripts/test_claude_permissions_common.py +195 -0
  134. package/skills/bugteam/scripts/test_grant_project_claude_permissions.py +55 -0
  135. package/skills/bugteam/scripts/test_probe_code_rules_enforcer_check.py +76 -0
  136. package/skills/bugteam/scripts/test_revoke_project_claude_permissions.py +55 -0
  137. package/skills/bugteam/scripts/test_windows_safe_rmtree.py +108 -0
  138. package/skills/bugteam/scripts/windows_safe_rmtree.py +100 -0
  139. package/skills/bugteam/test_skill_additions.py +1 -11
  140. package/skills/code/SKILL.md +176 -0
  141. package/skills/doc-gist/SKILL.md +99 -0
  142. package/skills/doc-gist/references/examples/01-exploration-code-approaches.html +453 -0
  143. package/skills/doc-gist/references/examples/02-exploration-visual-designs.html +515 -0
  144. package/skills/doc-gist/references/examples/03-code-review-pr.html +638 -0
  145. package/skills/doc-gist/references/examples/04-code-understanding.html +491 -0
  146. package/skills/doc-gist/references/examples/05-design-system.html +629 -0
  147. package/skills/doc-gist/references/examples/06-component-variants.html +605 -0
  148. package/skills/doc-gist/references/examples/07-prototype-animation.html +455 -0
  149. package/skills/doc-gist/references/examples/08-prototype-interaction.html +396 -0
  150. package/skills/doc-gist/references/examples/09-slide-deck.html +592 -0
  151. package/skills/doc-gist/references/examples/10-svg-illustrations.html +492 -0
  152. package/skills/doc-gist/references/examples/11-status-report.html +528 -0
  153. package/skills/doc-gist/references/examples/12-incident-report.html +596 -0
  154. package/skills/doc-gist/references/examples/13-flowchart-diagram.html +395 -0
  155. package/skills/doc-gist/references/examples/14-research-feature-explainer.html +381 -0
  156. package/skills/doc-gist/references/examples/15-research-concept-explainer.html +368 -0
  157. package/skills/doc-gist/references/examples/16-implementation-plan.html +702 -0
  158. package/skills/doc-gist/references/examples/17-pr-writeup.html +595 -0
  159. package/skills/doc-gist/references/examples/18-editor-triage-board.html +573 -0
  160. package/skills/doc-gist/references/examples/19-editor-feature-flags.html +663 -0
  161. package/skills/doc-gist/references/examples/20-editor-prompt-tuner.html +722 -0
  162. package/skills/doc-gist/references/examples/README.md +5 -0
  163. package/skills/doc-gist/scripts/config/__init__.py +0 -0
  164. package/skills/doc-gist/scripts/config/gist_upload_constants.py +16 -0
  165. package/skills/doc-gist/scripts/gist_upload.py +177 -0
  166. package/skills/doc-gist/scripts/test_gist_upload.py +51 -0
  167. package/skills/findbugs/SKILL.md +68 -2
  168. package/skills/monitor-open-prs/SKILL.md +13 -32
  169. package/skills/monitor-open-prs/test_skill_contract.py +0 -11
  170. package/skills/pr-consistency-audit/SKILL.md +112 -0
  171. package/skills/pr-consistency-audit/reference/detection-rules.md +96 -0
  172. package/skills/pr-consistency-audit/reference/illustrations.md +78 -0
  173. package/skills/pr-converge/SKILL.md +227 -23
  174. package/skills/pr-converge/config/__init__.py +0 -0
  175. package/skills/pr-converge/config/constants.py +62 -0
  176. package/skills/pr-converge/reference/convergence-gates.md +138 -44
  177. package/skills/pr-converge/reference/examples.md +43 -11
  178. package/skills/pr-converge/reference/fix-protocol.md +6 -5
  179. package/skills/pr-converge/reference/ground-rules.md +5 -3
  180. package/skills/pr-converge/reference/multi-pr-orchestration.md +44 -19
  181. package/skills/pr-converge/reference/obstacles/fix-post-replies.md +13 -0
  182. package/skills/pr-converge/reference/obstacles/fix-publish-summary.md +13 -0
  183. package/skills/pr-converge/reference/obstacles/fix-push.md +13 -0
  184. package/skills/pr-converge/reference/obstacles/fix-read-filelines.md +13 -0
  185. package/skills/pr-converge/reference/obstacles/fix-reset-state.md +13 -0
  186. package/skills/pr-converge/reference/obstacles/fix-resolve-threads.md +13 -0
  187. package/skills/pr-converge/reference/obstacles/fix-spawn-clean-coder.md +13 -0
  188. package/skills/pr-converge/reference/obstacles/fix-stage-commit.md +13 -0
  189. package/skills/pr-converge/reference/obstacles/fix-trigger-bugbot.md +13 -0
  190. package/skills/pr-converge/reference/obstacles/fix-write-test.md +13 -0
  191. package/skills/pr-converge/reference/per-tick.md +90 -31
  192. package/skills/pr-converge/reference/state-schema.md +22 -1
  193. package/skills/pr-converge/reference/stop-conditions.md +9 -7
  194. package/skills/pr-converge/scripts/README.md +34 -46
  195. package/skills/pr-converge/scripts/check_bugbot_ci.py +174 -0
  196. package/skills/pr-converge/scripts/check_convergence.py +497 -0
  197. package/skills/pr-converge/scripts/check_pending_reviews.py +154 -0
  198. package/skills/pr-converge/scripts/config/pr_converge_constants.py +118 -0
  199. package/skills/pr-converge/scripts/fetch_copilot_reviews.py +134 -0
  200. package/skills/pr-converge/scripts/post_fix_reply.py +168 -0
  201. package/skills/pr-converge/workflows/schedule-wakeup-loop.md +5 -12
  202. package/skills/qbug/SKILL.md +132 -27
  203. package/skills/session-log/SKILL.md +216 -114
  204. package/skills/session-tidy/SKILL.md +1 -1
  205. package/skills/skill-builder/SKILL.md +138 -56
  206. package/skills/skill-builder/references/delegation-map.md +72 -113
  207. package/skills/skill-builder/references/progressive-disclosure.md +122 -0
  208. package/skills/skill-builder/references/self-audit-checklist.md +92 -0
  209. package/skills/skill-builder/references/skill-types.md +228 -0
  210. package/skills/skill-builder/references/thariq-x-post-skills.json +33 -0
  211. package/skills/skill-builder/templates/gap-analysis.md +15 -8
  212. package/skills/skill-builder/workflows/improve-skill.md +86 -57
  213. package/skills/skill-builder/workflows/new-skill.md +80 -168
  214. package/skills/skill-builder/workflows/polish-skill.md +78 -54
  215. package/skills/structure-prompt/SKILL.md +50 -0
  216. package/skills/structure-prompt/reference/adversarial-tuning.md +62 -0
  217. package/skills/structure-prompt/reference/block-classification.md +27 -0
  218. package/skills/structure-prompt/reference/canonical-case.md +48 -0
  219. package/skills/structure-prompt/reference/citation-depth.md +70 -0
  220. package/skills/structure-prompt/reference/cleanup.md +33 -0
  221. package/skills/structure-prompt/reference/constraints.md +33 -0
  222. package/skills/structure-prompt/reference/directives.md +37 -0
  223. package/skills/structure-prompt/reference/examples.md +72 -0
  224. package/skills/structure-prompt/reference/instantiation.md +51 -0
  225. package/skills/structure-prompt/reference/output-contract.md +72 -0
  226. package/skills/structure-prompt/reference/per-category.md +23 -0
  227. package/skills/structure-prompt/reference/persona.md +38 -0
  228. package/skills/structure-prompt/reference/research.md +33 -0
  229. package/skills/structure-prompt/reference/structure.md +28 -0
  230. package/agents/code-standards-agent.md +0 -93
  231. package/agents/groq-coder.md +0 -113
  232. package/agents/plan-executor.md +0 -226
  233. package/agents/project-docs-analyzer.md +0 -53
  234. package/agents/project-structure-organizer-agent.md +0 -72
  235. package/agents/skill-to-agent-converter.md +0 -370
  236. package/agents/skill-writer-agent.md +0 -470
  237. package/agents/user-docs-writer.md +0 -67
  238. package/agents/workflow-visual-documenter.md +0 -82
  239. package/commands/readability-review.md +0 -20
  240. package/hooks/mypy.ini +0 -2
  241. package/hooks/notification/attention_needed_notify.py +0 -71
  242. package/hooks/notification/claude_notification_handler.py +0 -67
  243. package/hooks/notification/notification_utils.py +0 -267
  244. package/hooks/notification/subagent_complete_notify.py +0 -381
  245. package/hooks/notification/test_attention_needed_notify.py +0 -47
  246. package/hooks/notification/test_claude_notification_handler.py +0 -54
  247. package/hooks/notification/test_notification_utils.py +0 -91
  248. package/hooks/notification/test_subagent_complete_notify.py +0 -79
  249. package/scripts/config/groq_bugteam_config.py +0 -230
  250. package/scripts/config/test_groq_bugteam_config.py +0 -83
  251. package/scripts/config/test_spec_implementer_prompt.py +0 -32
  252. package/scripts/groq_bugteam.README.md +0 -131
  253. package/scripts/groq_bugteam.py +0 -647
  254. package/scripts/groq_bugteam_dotenv.py +0 -40
  255. package/scripts/groq_bugteam_spec.py +0 -226
  256. package/scripts/test_groq_bugteam.py +0 -529
  257. package/scripts/test_groq_bugteam_apply_fix_from_spec.py +0 -426
  258. package/scripts/test_groq_bugteam_dotenv.py +0 -66
  259. package/scripts/test_groq_bugteam_spec.py +0 -338
  260. package/skills/bugteam/SKILL_EVALS.md +0 -309
  261. package/skills/dream/SKILL.md +0 -118
  262. package/skills/ingest/SKILL.md +0 -40
  263. package/skills/npm-creator/SKILL.md +0 -187
  264. package/skills/readability-review/SKILL.md +0 -127
  265. package/skills/resume-review/SKILL.md +0 -261
  266. package/skills/rule-audit/SKILL.md +0 -307
  267. package/skills/rule-creator/SKILL.md +0 -150
  268. package/skills/searching-obsidian-vault/SKILL.md +0 -131
  269. package/skills/skill-writer/REFERENCE.md +0 -284
  270. package/skills/skill-writer/SKILL.md +0 -222
  271. package/skills/tdd-team/SKILL.md +0 -128
@@ -0,0 +1,92 @@
1
+ """Tests for `from typing import Any` and `cast()` detection in production.
2
+
3
+ CODE_RULES.md §6 (no `Any`) and Plan Phase 1a require:
4
+ - `from typing import Any` (and re-export forms) flagged in production
5
+ - `from typing import *` flagged (introduces Any access)
6
+ - `cast()` calls flagged in production
7
+ - Test files exempt; hook infrastructure exempt
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import importlib.util
13
+ from pathlib import Path
14
+ from types import ModuleType
15
+
16
+
17
+ def _load_enforcer_module() -> ModuleType:
18
+ module_path = Path(__file__).parent / "code_rules_enforcer.py"
19
+ spec = importlib.util.spec_from_file_location("code_rules_enforcer", module_path)
20
+ assert spec is not None
21
+ assert spec.loader is not None
22
+ module = importlib.util.module_from_spec(spec)
23
+ spec.loader.exec_module(module)
24
+ return module
25
+
26
+
27
+ code_rules_enforcer = _load_enforcer_module()
28
+ check_type_escape_hatches = code_rules_enforcer.check_type_escape_hatches
29
+
30
+ PRODUCTION_FILE_PATH = "/project/src/module.py"
31
+ TEST_FILE_PATH = "/project/src/test_module.py"
32
+
33
+
34
+ def test_should_flag_from_typing_import_any() -> None:
35
+ source = "from typing import Any\n"
36
+ issues = check_type_escape_hatches(source, PRODUCTION_FILE_PATH)
37
+ assert any(
38
+ "Any" in each_issue and "import" in each_issue.lower() for each_issue in issues
39
+ ), f"Expected 'from typing import Any' to be flagged, got: {issues!r}"
40
+
41
+
42
+ def test_should_flag_from_typing_import_any_among_others() -> None:
43
+ source = "from typing import Optional, Any, Dict\n"
44
+ issues = check_type_escape_hatches(source, PRODUCTION_FILE_PATH)
45
+ assert any(
46
+ "Any" in each_issue and "import" in each_issue.lower() for each_issue in issues
47
+ ), f"Expected 'Any' inside multi-import to be flagged, got: {issues!r}"
48
+
49
+
50
+ def test_should_not_flag_from_typing_import_without_any() -> None:
51
+ source = "from typing import Optional, Dict, List\n"
52
+ issues = check_type_escape_hatches(source, PRODUCTION_FILE_PATH)
53
+ assert issues == [], (
54
+ f"Expected no issues for typing import without Any, got: {issues!r}"
55
+ )
56
+
57
+
58
+ def test_should_flag_from_typing_import_star() -> None:
59
+ source = "from typing import *\n"
60
+ issues = check_type_escape_hatches(source, PRODUCTION_FILE_PATH)
61
+ assert any(
62
+ "import *" in each_issue or "wildcard" in each_issue.lower()
63
+ for each_issue in issues
64
+ ), f"Expected 'from typing import *' to be flagged, got: {issues!r}"
65
+
66
+
67
+ def test_should_flag_cast_call_in_production() -> None:
68
+ source = "from typing import cast\n\nx = cast(str, value)\n"
69
+ issues = check_type_escape_hatches(source, PRODUCTION_FILE_PATH)
70
+ assert any("cast" in each_issue for each_issue in issues), (
71
+ f"Expected cast() call to be flagged, got: {issues!r}"
72
+ )
73
+
74
+
75
+ def test_should_flag_typing_cast_call() -> None:
76
+ source = "import typing\n\nx = typing.cast(int, value)\n"
77
+ issues = check_type_escape_hatches(source, PRODUCTION_FILE_PATH)
78
+ assert any("cast" in each_issue for each_issue in issues), (
79
+ f"Expected typing.cast() call to be flagged, got: {issues!r}"
80
+ )
81
+
82
+
83
+ def test_should_not_flag_cast_in_test_file() -> None:
84
+ source = "from typing import cast\nx = cast(str, value)\n"
85
+ issues = check_type_escape_hatches(source, TEST_FILE_PATH)
86
+ assert issues == [], f"Test files must be exempt, got: {issues!r}"
87
+
88
+
89
+ def test_should_not_flag_typing_import_any_in_test_file() -> None:
90
+ source = "from typing import Any\nx: Any = 1\n"
91
+ issues = check_type_escape_hatches(source, TEST_FILE_PATH)
92
+ assert issues == [], f"Test files must be exempt, got: {issues!r}"
@@ -0,0 +1,143 @@
1
+ """Unit tests for banned-identifier coverage of import aliases.
2
+
3
+ Covers the gap surfaced during PR #419: ``from config import rebase_constants as rc``
4
+ slipped past both the Write/Edit hook and the commit-time gate because the
5
+ banned-identifier check inspected only assignment targets, never import-alias
6
+ binding targets. Also covers the expanded abbreviation list (rc, cfg, ctx,
7
+ cnt, btn, idx, tmp, msg, elem, val) added per CODE_RULES.md §5.
8
+ """
9
+
10
+ import importlib.util
11
+ import pathlib
12
+ import sys
13
+
14
+ _HOOK_DIR = pathlib.Path(__file__).parent
15
+ if str(_HOOK_DIR) not in sys.path:
16
+ sys.path.insert(0, str(_HOOK_DIR))
17
+
18
+ hook_spec = importlib.util.spec_from_file_location(
19
+ "code_rules_enforcer",
20
+ _HOOK_DIR / "code_rules_enforcer.py",
21
+ )
22
+ assert hook_spec is not None
23
+ assert hook_spec.loader is not None
24
+ hook_module = importlib.util.module_from_spec(hook_spec)
25
+ hook_spec.loader.exec_module(hook_module)
26
+ check_banned_identifiers = hook_module.check_banned_identifiers
27
+
28
+ PRODUCTION_FILE_PATH = "packages/app/services/loader.py"
29
+ TEST_FILE_PATH = "packages/app/services/test_loader.py"
30
+
31
+
32
+ def test_should_flag_from_import_alias_rc() -> None:
33
+ content = "from config import rebase_constants as rc\n"
34
+ issues = check_banned_identifiers(content, PRODUCTION_FILE_PATH)
35
+ assert any("'rc'" in each_issue for each_issue in issues), (
36
+ f"Expected 'rc' import alias flagged, got: {issues}"
37
+ )
38
+
39
+
40
+ def test_should_flag_import_alias_rc() -> None:
41
+ content = "import rebase_constants as rc\n"
42
+ issues = check_banned_identifiers(content, PRODUCTION_FILE_PATH)
43
+ assert any("'rc'" in each_issue for each_issue in issues), (
44
+ f"Expected 'rc' bare-import alias flagged, got: {issues}"
45
+ )
46
+
47
+
48
+ def test_should_flag_from_import_alias_cfg() -> None:
49
+ content = "from app import configuration as cfg\n"
50
+ issues = check_banned_identifiers(content, PRODUCTION_FILE_PATH)
51
+ assert any("'cfg'" in each_issue for each_issue in issues), (
52
+ f"Expected 'cfg' import alias flagged, got: {issues}"
53
+ )
54
+
55
+
56
+ def test_should_flag_from_import_alias_ctx() -> None:
57
+ content = "from app import context as ctx\n"
58
+ issues = check_banned_identifiers(content, PRODUCTION_FILE_PATH)
59
+ assert any("'ctx'" in each_issue for each_issue in issues), (
60
+ f"Expected 'ctx' import alias flagged, got: {issues}"
61
+ )
62
+
63
+
64
+ def test_should_flag_idx_assignment() -> None:
65
+ content = "def pick(items):\n idx = 0\n return items[idx]\n"
66
+ issues = check_banned_identifiers(content, PRODUCTION_FILE_PATH)
67
+ assert any("'idx'" in each_issue for each_issue in issues), (
68
+ f"Expected 'idx' assignment flagged — use index, got: {issues}"
69
+ )
70
+
71
+
72
+ def test_should_flag_tmp_assignment() -> None:
73
+ content = "def swap(a, b):\n tmp = a\n return tmp\n"
74
+ issues = check_banned_identifiers(content, PRODUCTION_FILE_PATH)
75
+ assert any("'tmp'" in each_issue for each_issue in issues), (
76
+ f"Expected 'tmp' assignment flagged — use temporary_value, got: {issues}"
77
+ )
78
+
79
+
80
+ def test_should_flag_msg_assignment() -> None:
81
+ content = "def notify():\n msg = build_message()\n return msg\n"
82
+ issues = check_banned_identifiers(content, PRODUCTION_FILE_PATH)
83
+ assert any("'msg'" in each_issue for each_issue in issues), (
84
+ f"Expected 'msg' assignment flagged — use message, got: {issues}"
85
+ )
86
+
87
+
88
+ def test_should_flag_elem_assignment() -> None:
89
+ content = "def first(items):\n elem = items[0]\n return elem\n"
90
+ issues = check_banned_identifiers(content, PRODUCTION_FILE_PATH)
91
+ assert any("'elem'" in each_issue for each_issue in issues), (
92
+ f"Expected 'elem' assignment flagged — use element, got: {issues}"
93
+ )
94
+
95
+
96
+ def test_should_flag_val_assignment() -> None:
97
+ content = "def read():\n val = compute()\n return val\n"
98
+ issues = check_banned_identifiers(content, PRODUCTION_FILE_PATH)
99
+ assert any("'val'" in each_issue for each_issue in issues), (
100
+ f"Expected 'val' assignment flagged — use value, got: {issues}"
101
+ )
102
+
103
+
104
+ def test_should_flag_cnt_assignment() -> None:
105
+ content = "def tally(items):\n cnt = len(items)\n return cnt\n"
106
+ issues = check_banned_identifiers(content, PRODUCTION_FILE_PATH)
107
+ assert any("'cnt'" in each_issue for each_issue in issues), (
108
+ f"Expected 'cnt' assignment flagged — use count, got: {issues}"
109
+ )
110
+
111
+
112
+ def test_should_flag_btn_assignment() -> None:
113
+ content = "def click_handler():\n btn = locate_button()\n return btn\n"
114
+ issues = check_banned_identifiers(content, PRODUCTION_FILE_PATH)
115
+ assert any("'btn'" in each_issue for each_issue in issues), (
116
+ f"Expected 'btn' assignment flagged — use button, got: {issues}"
117
+ )
118
+
119
+
120
+ def test_should_not_flag_import_alias_in_test_file() -> None:
121
+ content = "from config import rebase_constants as rc\n"
122
+ issues = check_banned_identifiers(content, TEST_FILE_PATH)
123
+ assert issues == [], (
124
+ f"Test files are exempt from banned-identifier checks, got: {issues}"
125
+ )
126
+
127
+
128
+ def test_should_not_flag_import_alias_with_descriptive_name() -> None:
129
+ content = (
130
+ "from config import rebase_constants\n"
131
+ "import json as json_module\n"
132
+ "from typing import Optional as OptionalType\n"
133
+ )
134
+ issues = check_banned_identifiers(content, PRODUCTION_FILE_PATH)
135
+ assert issues == [], f"Descriptive import aliases must not flag, got: {issues}"
136
+
137
+
138
+ def test_should_include_line_number_for_import_alias() -> None:
139
+ content = "import json\nfrom config import rebase_constants as rc\n"
140
+ issues = check_banned_identifiers(content, PRODUCTION_FILE_PATH)
141
+ assert len(issues) >= 1
142
+ assert "Line 2" in issues[0]
143
+ assert "'rc'" in issues[0]
@@ -0,0 +1,152 @@
1
+ """Tests for check_banned_prefixes — flags function names with generic prefixes.
2
+
3
+ CODE_RULES.md §5 / AGENTS.md "Naming → Function names use specific verbs:
4
+ parse_invoice, dispatch_event, migrate_schema. Generic prefixes to replace:
5
+ handle_, process_, manage_, do_."
6
+
7
+ The check fires on def / async def names (functions and methods) in
8
+ production code, exempting test files and hook infrastructure.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import importlib.util
14
+ from pathlib import Path
15
+ from types import ModuleType
16
+
17
+
18
+ def _load_enforcer_module() -> ModuleType:
19
+ module_path = Path(__file__).parent / "code_rules_enforcer.py"
20
+ spec = importlib.util.spec_from_file_location("code_rules_enforcer", module_path)
21
+ assert spec is not None
22
+ assert spec.loader is not None
23
+ module = importlib.util.module_from_spec(spec)
24
+ spec.loader.exec_module(module)
25
+ return module
26
+
27
+
28
+ code_rules_enforcer = _load_enforcer_module()
29
+
30
+ PRODUCTION_FILE_PATH = "src/example_production.py"
31
+ TEST_FILE_PATH = "src/test_example.py"
32
+ HOOK_INFRASTRUCTURE_FILE_PATH = "/home/user/.claude/hooks/blocking/example_hook.py"
33
+
34
+
35
+ def test_should_flag_handle_prefixed_function() -> None:
36
+ content = "def handle_request(payload):\n return payload\n"
37
+ issues = code_rules_enforcer.check_banned_prefixes(content, PRODUCTION_FILE_PATH)
38
+ assert any("handle_request" in each_issue for each_issue in issues), (
39
+ f"Expected 'handle_request' to be flagged, got: {issues!r}"
40
+ )
41
+
42
+
43
+ def test_should_flag_process_prefixed_function() -> None:
44
+ content = "def process_data(items):\n return items\n"
45
+ issues = code_rules_enforcer.check_banned_prefixes(content, PRODUCTION_FILE_PATH)
46
+ assert any("process_data" in each_issue for each_issue in issues), (
47
+ f"Expected 'process_data' to be flagged, got: {issues!r}"
48
+ )
49
+
50
+
51
+ def test_should_flag_manage_prefixed_function() -> None:
52
+ content = "def manage_session(session_id):\n return session_id\n"
53
+ issues = code_rules_enforcer.check_banned_prefixes(content, PRODUCTION_FILE_PATH)
54
+ assert any("manage_session" in each_issue for each_issue in issues), (
55
+ f"Expected 'manage_session' to be flagged, got: {issues!r}"
56
+ )
57
+
58
+
59
+ def test_should_flag_do_prefixed_function() -> None:
60
+ content = "def do_thing(argument):\n return argument\n"
61
+ issues = code_rules_enforcer.check_banned_prefixes(content, PRODUCTION_FILE_PATH)
62
+ assert any("do_thing" in each_issue for each_issue in issues), (
63
+ f"Expected 'do_thing' to be flagged, got: {issues!r}"
64
+ )
65
+
66
+
67
+ def test_should_flag_async_function_with_banned_prefix() -> None:
68
+ content = "async def handle_event(event):\n return event\n"
69
+ issues = code_rules_enforcer.check_banned_prefixes(content, PRODUCTION_FILE_PATH)
70
+ assert any("handle_event" in each_issue for each_issue in issues), (
71
+ f"Expected async 'handle_event' to be flagged, got: {issues!r}"
72
+ )
73
+
74
+
75
+ def test_should_flag_method_with_banned_prefix() -> None:
76
+ content = (
77
+ "class OrderService:\n"
78
+ " def process_order(self, order):\n"
79
+ " return order\n"
80
+ )
81
+ issues = code_rules_enforcer.check_banned_prefixes(content, PRODUCTION_FILE_PATH)
82
+ assert any("process_order" in each_issue for each_issue in issues), (
83
+ f"Expected method 'process_order' to be flagged, got: {issues!r}"
84
+ )
85
+
86
+
87
+ def test_should_not_flag_function_without_banned_prefix() -> None:
88
+ content = "def parse_invoice(payload):\n return payload\n"
89
+ issues = code_rules_enforcer.check_banned_prefixes(content, PRODUCTION_FILE_PATH)
90
+ assert issues == [], f"Expected no issues for 'parse_invoice', got: {issues!r}"
91
+
92
+
93
+ def test_should_not_flag_function_with_handle_substring_but_no_underscore() -> None:
94
+ content = "def handler(payload):\n return payload\n"
95
+ issues = code_rules_enforcer.check_banned_prefixes(content, PRODUCTION_FILE_PATH)
96
+ assert issues == [], (
97
+ f"'handler' is a noun, not a banned 'handle_' prefix; got: {issues!r}"
98
+ )
99
+
100
+
101
+ def test_should_not_flag_function_with_doctor_prefix() -> None:
102
+ content = "def doctor_visit(patient):\n return patient\n"
103
+ issues = code_rules_enforcer.check_banned_prefixes(content, PRODUCTION_FILE_PATH)
104
+ assert issues == [], (
105
+ f"'doctor_visit' starts with 'do' but not 'do_'; got: {issues!r}"
106
+ )
107
+
108
+
109
+ def test_should_skip_test_file() -> None:
110
+ content = "def handle_request(payload):\n return payload\n"
111
+ issues = code_rules_enforcer.check_banned_prefixes(content, TEST_FILE_PATH)
112
+ assert issues == [], f"Test files must be exempt, got: {issues!r}"
113
+
114
+
115
+ def test_should_skip_hook_infrastructure() -> None:
116
+ content = "def handle_request(payload):\n return payload\n"
117
+ issues = code_rules_enforcer.check_banned_prefixes(
118
+ content, HOOK_INFRASTRUCTURE_FILE_PATH
119
+ )
120
+ assert issues == [], f"Hook infrastructure must be exempt, got: {issues!r}"
121
+
122
+
123
+ def test_should_handle_syntax_error_gracefully() -> None:
124
+ content = "def handle_request(\n"
125
+ issues = code_rules_enforcer.check_banned_prefixes(content, PRODUCTION_FILE_PATH)
126
+ assert issues == [], f"Syntax errors must yield no issues, got: {issues!r}"
127
+
128
+
129
+ def test_should_include_line_number_and_function_name() -> None:
130
+ content = "x = 1\n\ndef handle_event(event):\n return event\n"
131
+ issues = code_rules_enforcer.check_banned_prefixes(content, PRODUCTION_FILE_PATH)
132
+ assert len(issues) == 1, f"Expected exactly one issue, got: {issues!r}"
133
+ assert "Line 3" in issues[0], (
134
+ f"Issue must include the line number, got: {issues[0]!r}"
135
+ )
136
+ assert "handle_event" in issues[0], (
137
+ f"Issue must include the function name, got: {issues[0]!r}"
138
+ )
139
+
140
+
141
+ def test_should_cap_at_three_issues() -> None:
142
+ content = (
143
+ "def handle_one():\n pass\n"
144
+ "def handle_two():\n pass\n"
145
+ "def handle_three():\n pass\n"
146
+ "def handle_four():\n pass\n"
147
+ "def handle_five():\n pass\n"
148
+ )
149
+ issues = code_rules_enforcer.check_banned_prefixes(content, PRODUCTION_FILE_PATH)
150
+ assert len(issues) <= 3, (
151
+ f"Issue count must be capped at 3, got {len(issues)}: {issues!r}"
152
+ )
@@ -0,0 +1,120 @@
1
+ """Tests for check_bare_except — flags except: / except Exception: / except BaseException:.
2
+
3
+ Per Plan 1c.bare_except_detector / Phase B4: bare exceptions swallow every
4
+ error including KeyboardInterrupt and SystemExit. They hide bugs and make
5
+ debugging impossible. Production code names the specific exception class
6
+ it intends to catch.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import importlib.util
12
+ from pathlib import Path
13
+ from types import ModuleType
14
+
15
+
16
+ def _load_enforcer_module() -> ModuleType:
17
+ module_path = Path(__file__).parent / "code_rules_enforcer.py"
18
+ spec = importlib.util.spec_from_file_location("code_rules_enforcer", module_path)
19
+ assert spec is not None
20
+ assert spec.loader is not None
21
+ module = importlib.util.module_from_spec(spec)
22
+ spec.loader.exec_module(module)
23
+ return module
24
+
25
+
26
+ code_rules_enforcer = _load_enforcer_module()
27
+
28
+
29
+ def check_bare_except(content: str, file_path: str) -> list[str]:
30
+ return code_rules_enforcer.check_bare_except(content, file_path)
31
+
32
+
33
+ PRODUCTION_FILE_PATH = "/project/src/services.py"
34
+ TEST_FILE_PATH = "/project/src/test_services.py"
35
+ HOOK_INFRASTRUCTURE_PATH = "/home/user/.claude/hooks/blocking/example.py"
36
+
37
+
38
+ def test_should_flag_bare_except() -> None:
39
+ source = "def fetch():\n try:\n do_thing()\n except:\n pass\n"
40
+ issues = check_bare_except(source, PRODUCTION_FILE_PATH)
41
+ assert any("bare except" in each.lower() for each in issues), (
42
+ f"Expected bare except: flagged, got: {issues!r}"
43
+ )
44
+
45
+
46
+ def test_should_flag_except_exception() -> None:
47
+ source = (
48
+ "def fetch():\n"
49
+ " try:\n"
50
+ " do_thing()\n"
51
+ " except Exception:\n"
52
+ " pass\n"
53
+ )
54
+ issues = check_bare_except(source, PRODUCTION_FILE_PATH)
55
+ assert any("Exception" in each for each in issues), (
56
+ f"Expected 'except Exception:' flagged, got: {issues!r}"
57
+ )
58
+
59
+
60
+ def test_should_flag_except_base_exception() -> None:
61
+ source = (
62
+ "def fetch():\n"
63
+ " try:\n"
64
+ " do_thing()\n"
65
+ " except BaseException:\n"
66
+ " pass\n"
67
+ )
68
+ issues = check_bare_except(source, PRODUCTION_FILE_PATH)
69
+ assert any("BaseException" in each for each in issues), (
70
+ f"Expected 'except BaseException:' flagged, got: {issues!r}"
71
+ )
72
+
73
+
74
+ def test_should_not_flag_specific_exception() -> None:
75
+ source = (
76
+ "def fetch():\n"
77
+ " try:\n"
78
+ " do_thing()\n"
79
+ " except ValueError:\n"
80
+ " pass\n"
81
+ )
82
+ issues = check_bare_except(source, PRODUCTION_FILE_PATH)
83
+ assert issues == [], f"Specific exception must not be flagged, got: {issues!r}"
84
+
85
+
86
+ def test_should_not_flag_tuple_of_exceptions() -> None:
87
+ source = (
88
+ "def fetch():\n"
89
+ " try:\n"
90
+ " do_thing()\n"
91
+ " except (ValueError, KeyError):\n"
92
+ " pass\n"
93
+ )
94
+ issues = check_bare_except(source, PRODUCTION_FILE_PATH)
95
+ assert issues == [], f"Tuple of exceptions must not be flagged, got: {issues!r}"
96
+
97
+
98
+ def test_should_skip_test_file() -> None:
99
+ source = "try:\n do_thing()\nexcept:\n pass\n"
100
+ issues = check_bare_except(source, TEST_FILE_PATH)
101
+ assert issues == [], f"Test files exempt, got: {issues!r}"
102
+
103
+
104
+ def test_should_skip_hook_infrastructure() -> None:
105
+ source = "try:\n do_thing()\nexcept:\n pass\n"
106
+ issues = check_bare_except(source, HOOK_INFRASTRUCTURE_PATH)
107
+ assert issues == [], f"Hook infrastructure exempt, got: {issues!r}"
108
+
109
+
110
+ def test_should_handle_syntax_error_gracefully() -> None:
111
+ source = "try:\n do(\n"
112
+ issues = check_bare_except(source, PRODUCTION_FILE_PATH)
113
+ assert issues == [], f"Syntax error must yield no issues, got: {issues!r}"
114
+
115
+
116
+ def test_should_include_line_number() -> None:
117
+ source = "def fetch():\n try:\n do_thing()\n except:\n pass\n"
118
+ issues = check_bare_except(source, PRODUCTION_FILE_PATH)
119
+ assert len(issues) >= 1
120
+ assert "Line 4" in issues[0], f"Issue must include line number, got: {issues[0]!r}"
@@ -0,0 +1,175 @@
1
+ """Tests for check_boundary_types — flags Any at module boundaries.
2
+
3
+ Per Plan 1c.boundary_type_check / Phase B6: a function signature or class
4
+ attribute typed with `Any` (directly or nested inside a generic) makes
5
+ no type promise to callers. Production code at boundaries should name
6
+ the concrete shape it accepts and returns. Local variables are exempt
7
+ (they are private to the function); `if TYPE_CHECKING:` blocks are
8
+ exempt (those imports never reach runtime); `protocols.py` and
9
+ `types.py` are exempt (interface declaration files).
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import importlib.util
15
+ from pathlib import Path
16
+ from types import ModuleType
17
+
18
+
19
+ def _load_enforcer_module() -> ModuleType:
20
+ module_path = Path(__file__).parent / "code_rules_enforcer.py"
21
+ spec = importlib.util.spec_from_file_location("code_rules_enforcer", module_path)
22
+ assert spec is not None
23
+ assert spec.loader is not None
24
+ module = importlib.util.module_from_spec(spec)
25
+ spec.loader.exec_module(module)
26
+ return module
27
+
28
+
29
+ code_rules_enforcer = _load_enforcer_module()
30
+
31
+
32
+ def check_boundary_types(content: str, file_path: str) -> list[str]:
33
+ return code_rules_enforcer.check_boundary_types(content, file_path)
34
+
35
+
36
+ PRODUCTION_FILE_PATH = "/project/src/services.py"
37
+ PROTOCOLS_FILE_PATH = "/project/src/protocols.py"
38
+ TYPES_FILE_PATH = "/project/src/types.py"
39
+ TEST_FILE_PATH = "/project/src/test_services.py"
40
+ HOOK_INFRASTRUCTURE_PATH = "/home/user/.claude/hooks/blocking/example.py"
41
+
42
+
43
+ def test_should_flag_any_as_direct_param_annotation() -> None:
44
+ source = (
45
+ "from typing import Any\n\ndef fetch(payload: Any) -> None:\n return None\n"
46
+ )
47
+ issues = check_boundary_types(source, PRODUCTION_FILE_PATH)
48
+ assert any("Any" in each for each in issues), (
49
+ f"Expected Any-in-signature flag, got: {issues!r}"
50
+ )
51
+
52
+
53
+ def test_should_flag_any_in_dict_param() -> None:
54
+ source = (
55
+ "from typing import Any\n"
56
+ "\n"
57
+ "def fetch(payload: dict[str, Any]) -> None:\n"
58
+ " return None\n"
59
+ )
60
+ issues = check_boundary_types(source, PRODUCTION_FILE_PATH)
61
+ assert any("Any" in each for each in issues), (
62
+ f"Expected dict[str, Any] flagged, got: {issues!r}"
63
+ )
64
+
65
+
66
+ def test_should_flag_any_as_return_type() -> None:
67
+ source = (
68
+ "from typing import Any, List\n\ndef fetch() -> List[Any]:\n return []\n"
69
+ )
70
+ issues = check_boundary_types(source, PRODUCTION_FILE_PATH)
71
+ assert any("Any" in each for each in issues), (
72
+ f"Expected List[Any] return flagged, got: {issues!r}"
73
+ )
74
+
75
+
76
+ def test_should_flag_any_nested_two_levels() -> None:
77
+ source = (
78
+ "from typing import Any\n"
79
+ "\n"
80
+ "def fetch(payload: dict[str, list[Any]]) -> None:\n"
81
+ " return None\n"
82
+ )
83
+ issues = check_boundary_types(source, PRODUCTION_FILE_PATH)
84
+ assert any("Any" in each for each in issues), (
85
+ f"Expected nested Any flagged, got: {issues!r}"
86
+ )
87
+
88
+
89
+ def test_should_flag_callable_returning_any() -> None:
90
+ source = (
91
+ "from typing import Any, Callable\n"
92
+ "\n"
93
+ "def fetch() -> Callable[..., Any]:\n"
94
+ " return lambda: 1\n"
95
+ )
96
+ issues = check_boundary_types(source, PRODUCTION_FILE_PATH)
97
+ assert any("Any" in each for each in issues), (
98
+ f"Expected Callable[..., Any] flagged, got: {issues!r}"
99
+ )
100
+
101
+
102
+ def test_should_flag_any_in_class_attribute_annotation() -> None:
103
+ source = "from typing import Any\n\nclass Cache:\n storage: dict[str, Any]\n"
104
+ issues = check_boundary_types(source, PRODUCTION_FILE_PATH)
105
+ assert any("Any" in each for each in issues), (
106
+ f"Expected class-attribute Any flagged, got: {issues!r}"
107
+ )
108
+
109
+
110
+ def test_should_not_flag_specific_dict_value_type() -> None:
111
+ source = "def fetch(payload: dict[str, int]) -> dict[str, str]:\n return {}\n"
112
+ issues = check_boundary_types(source, PRODUCTION_FILE_PATH)
113
+ assert issues == [], f"Specific types must not be flagged, got: {issues!r}"
114
+
115
+
116
+ def test_should_not_flag_local_variable_annotation() -> None:
117
+ source = (
118
+ "from typing import Any\n"
119
+ "\n"
120
+ "def fetch() -> None:\n"
121
+ " cache: dict[str, Any] = {}\n"
122
+ " return None\n"
123
+ )
124
+ issues = check_boundary_types(source, PRODUCTION_FILE_PATH)
125
+ assert issues == [], f"Local var annotations must not be flagged, got: {issues!r}"
126
+
127
+
128
+ def test_should_skip_protocols_file() -> None:
129
+ source = (
130
+ "from typing import Any, Protocol\n"
131
+ "\n"
132
+ "class Storage(Protocol):\n"
133
+ " def get(self, key: str) -> Any: ...\n"
134
+ )
135
+ issues = check_boundary_types(source, PROTOCOLS_FILE_PATH)
136
+ assert issues == [], f"protocols.py exempt, got: {issues!r}"
137
+
138
+
139
+ def test_should_skip_types_file() -> None:
140
+ source = (
141
+ "from typing import Any\n\ndef coerce(value: Any) -> Any:\n return value\n"
142
+ )
143
+ issues = check_boundary_types(source, TYPES_FILE_PATH)
144
+ assert issues == [], f"types.py exempt, got: {issues!r}"
145
+
146
+
147
+ def test_should_skip_test_file() -> None:
148
+ source = (
149
+ "from typing import Any\n\ndef fetch(payload: Any) -> None:\n return None\n"
150
+ )
151
+ issues = check_boundary_types(source, TEST_FILE_PATH)
152
+ assert issues == [], f"Test files exempt, got: {issues!r}"
153
+
154
+
155
+ def test_should_skip_hook_infrastructure() -> None:
156
+ source = (
157
+ "from typing import Any\n\ndef fetch(payload: Any) -> None:\n return None\n"
158
+ )
159
+ issues = check_boundary_types(source, HOOK_INFRASTRUCTURE_PATH)
160
+ assert issues == [], f"Hook infrastructure exempt, got: {issues!r}"
161
+
162
+
163
+ def test_should_handle_syntax_error_gracefully() -> None:
164
+ source = "def fetch(\n"
165
+ issues = check_boundary_types(source, PRODUCTION_FILE_PATH)
166
+ assert issues == [], f"Syntax error must yield no issues, got: {issues!r}"
167
+
168
+
169
+ def test_should_include_line_number() -> None:
170
+ source = (
171
+ "from typing import Any\n\ndef fetch(payload: Any) -> None:\n return None\n"
172
+ )
173
+ issues = check_boundary_types(source, PRODUCTION_FILE_PATH)
174
+ assert len(issues) >= 1
175
+ assert "Line 3" in issues[0], f"Issue must include line number, got: {issues[0]!r}"
@@ -67,7 +67,6 @@ KNOWN_UNCAPPED_CHECKS_PENDING_REVIEW: frozenset[str] = frozenset(
67
67
  "check_return_annotations",
68
68
  "check_skip_decorators_in_tests",
69
69
  "check_string_literal_magic",
70
- "check_type_escape_hatches",
71
70
  "check_unused_optional_parameters",
72
71
  "check_windows_api_none",
73
72
  }