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,143 @@
1
+ """Tests for check_test_branching_in_production — flags prod-vs-test branching.
2
+
3
+ Per Plan 1c.di_pattern_check / Phase B3: production code that branches on
4
+ TESTING / PYTEST_CURRENT_TEST via os.environ creates two parallel
5
+ implementations the wrong way. The correct pattern is dependency injection
6
+ (`_test_hooks.py` sibling) so production code is single-path and tests
7
+ override the dependency.
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
+
29
+
30
+ def check_test_branching_in_production(content: str, file_path: str) -> list[str]:
31
+ return code_rules_enforcer.check_test_branching_in_production(content, file_path)
32
+
33
+
34
+ PRODUCTION_FILE_PATH = "/project/src/services.py"
35
+ TEST_FILE_PATH = "/project/src/test_services.py"
36
+ HOOK_INFRASTRUCTURE_PATH = "/home/user/.claude/hooks/blocking/example.py"
37
+
38
+
39
+ def test_should_flag_os_environ_get_testing_branch() -> None:
40
+ source = (
41
+ "import os\n"
42
+ "def fetch_user():\n"
43
+ " if os.environ.get('TESTING'):\n"
44
+ " return None\n"
45
+ " return real_fetch()\n"
46
+ )
47
+ issues = check_test_branching_in_production(source, PRODUCTION_FILE_PATH)
48
+ assert any("TESTING" in each for each in issues), (
49
+ f"Expected TESTING env branch flagged, got: {issues!r}"
50
+ )
51
+
52
+
53
+ def test_should_flag_pytest_current_test_branch() -> None:
54
+ source = (
55
+ "import os\n"
56
+ "def fetch_user():\n"
57
+ " if 'PYTEST_CURRENT_TEST' in os.environ:\n"
58
+ " return None\n"
59
+ " return real_fetch()\n"
60
+ )
61
+ issues = check_test_branching_in_production(source, PRODUCTION_FILE_PATH)
62
+ assert any("PYTEST_CURRENT_TEST" in each for each in issues), (
63
+ f"Expected PYTEST_CURRENT_TEST branch flagged, got: {issues!r}"
64
+ )
65
+
66
+
67
+ def test_should_flag_env_testing_check() -> None:
68
+ source = (
69
+ "import os\n"
70
+ "TESTING_FLAG = os.environ.get('TESTING') == '1'\n"
71
+ "def get_db():\n"
72
+ " if TESTING_FLAG:\n"
73
+ " return MockDB()\n"
74
+ " return RealDB()\n"
75
+ )
76
+ issues = check_test_branching_in_production(source, PRODUCTION_FILE_PATH)
77
+ assert any("TESTING" in each for each in issues), (
78
+ f"Expected TESTING env access flagged, got: {issues!r}"
79
+ )
80
+
81
+
82
+ def test_should_flag_environ_subscript_testing() -> None:
83
+ source = (
84
+ "import os\n"
85
+ "def get_db():\n"
86
+ " if os.environ['TESTING']:\n"
87
+ " return None\n"
88
+ " return RealDB()\n"
89
+ )
90
+ issues = check_test_branching_in_production(source, PRODUCTION_FILE_PATH)
91
+ assert any("TESTING" in each for each in issues), (
92
+ f"Expected os.environ['TESTING'] flagged, got: {issues!r}"
93
+ )
94
+
95
+
96
+ def test_should_not_flag_other_env_var_access() -> None:
97
+ source = (
98
+ "import os\n"
99
+ "def get_db():\n"
100
+ " db_url = os.environ.get('DATABASE_URL')\n"
101
+ " return RealDB(db_url)\n"
102
+ )
103
+ issues = check_test_branching_in_production(source, PRODUCTION_FILE_PATH)
104
+ assert issues == [], f"Non-test env vars must not trigger, got: {issues!r}"
105
+
106
+
107
+ def test_should_skip_test_file() -> None:
108
+ source = (
109
+ "import os\n"
110
+ "def fetch_user():\n"
111
+ " if os.environ.get('TESTING'):\n"
112
+ " return None\n"
113
+ )
114
+ issues = check_test_branching_in_production(source, TEST_FILE_PATH)
115
+ assert issues == [], f"Test files exempt, got: {issues!r}"
116
+
117
+
118
+ def test_should_skip_hook_infrastructure() -> None:
119
+ source = (
120
+ "import os\n"
121
+ "def fetch_user():\n"
122
+ " if os.environ.get('TESTING'):\n"
123
+ " return None\n"
124
+ )
125
+ issues = check_test_branching_in_production(source, HOOK_INFRASTRUCTURE_PATH)
126
+ assert issues == [], f"Hook infrastructure exempt, got: {issues!r}"
127
+
128
+
129
+ def test_should_handle_syntax_error_gracefully() -> None:
130
+ source = "def fetch_user(\n"
131
+ issues = check_test_branching_in_production(source, PRODUCTION_FILE_PATH)
132
+ assert issues == [], f"Syntax error must yield no issues, got: {issues!r}"
133
+
134
+
135
+ def test_should_include_line_number_in_issue() -> None:
136
+ source = (
137
+ "import os\n\ndef fetch():\n if os.environ.get('TESTING'):\n pass\n"
138
+ )
139
+ issues = check_test_branching_in_production(source, PRODUCTION_FILE_PATH)
140
+ assert len(issues) >= 1
141
+ assert any("Line 4" in each for each in issues), (
142
+ f"Issue must include line number, got: {issues!r}"
143
+ )
@@ -0,0 +1,169 @@
1
+ """Tests for check_thin_wrapper_files — flag re-export-only modules.
2
+
3
+ Per Plan / Phase B5: a non-`__init__.py` module whose entire body is
4
+ `import` statements plus an `__all__` assignment is a thin wrapper that
5
+ forces callers through an indirection layer with no payload. Callers
6
+ should import from the real module. `__init__.py` is the canonical
7
+ re-export surface and is 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
+
29
+
30
+ def check_thin_wrapper_files(content: str, file_path: str) -> list[str]:
31
+ return code_rules_enforcer.check_thin_wrapper_files(content, file_path)
32
+
33
+
34
+ PRODUCTION_FILE_PATH = "/project/src/aliases.py"
35
+ INIT_FILE_PATH = "/project/src/__init__.py"
36
+ TEST_FILE_PATH = "/project/src/test_aliases.py"
37
+ HOOK_INFRASTRUCTURE_PATH = "/home/user/.claude/hooks/blocking/example.py"
38
+ CONFIG_FILE_PATH = "/project/config/aliases.py"
39
+
40
+
41
+ def test_should_flag_thin_wrapper_with_imports_and_all() -> None:
42
+ source = (
43
+ "from real_module import do_thing, other_thing\n"
44
+ "\n"
45
+ '__all__ = ["do_thing", "other_thing"]\n'
46
+ )
47
+ issues = check_thin_wrapper_files(source, PRODUCTION_FILE_PATH)
48
+ assert any("thin wrapper" in each.lower() for each in issues), (
49
+ f"Expected thin-wrapper flag, got: {issues!r}"
50
+ )
51
+
52
+
53
+ def test_should_flag_thin_wrapper_imports_only_no_all() -> None:
54
+ source = "from real_module import do_thing\nfrom other_module import other_thing\n"
55
+ issues = check_thin_wrapper_files(source, PRODUCTION_FILE_PATH)
56
+ assert any("thin wrapper" in each.lower() for each in issues), (
57
+ f"Expected import-only thin-wrapper flag, got: {issues!r}"
58
+ )
59
+
60
+
61
+ def test_should_not_flag_init_file() -> None:
62
+ source = 'from real_module import do_thing\n\n__all__ = ["do_thing"]\n'
63
+ issues = check_thin_wrapper_files(source, INIT_FILE_PATH)
64
+ assert issues == [], (
65
+ f"__init__.py is the canonical re-export surface, got: {issues!r}"
66
+ )
67
+
68
+
69
+ def test_should_not_flag_file_with_function_definition() -> None:
70
+ source = (
71
+ "from real_module import dependency\n"
72
+ "\n"
73
+ '__all__ = ["public_helper"]\n'
74
+ "\n"
75
+ "def public_helper(value: int) -> int:\n"
76
+ " return dependency(value)\n"
77
+ )
78
+ issues = check_thin_wrapper_files(source, PRODUCTION_FILE_PATH)
79
+ assert issues == [], f"File with real code must not be flagged, got: {issues!r}"
80
+
81
+
82
+ def test_should_not_flag_file_with_class_definition() -> None:
83
+ source = "from real_module import Base\n\nclass Subtype(Base):\n pass\n"
84
+ issues = check_thin_wrapper_files(source, PRODUCTION_FILE_PATH)
85
+ assert issues == [], f"File with class must not be flagged, got: {issues!r}"
86
+
87
+
88
+ def test_should_not_flag_file_with_constant_assignment() -> None:
89
+ source = (
90
+ "from real_module import constant_value\n\nDERIVED_VALUE = constant_value * 2\n"
91
+ )
92
+ issues = check_thin_wrapper_files(source, PRODUCTION_FILE_PATH)
93
+ assert issues == [], (
94
+ f"File with derived constant must not be flagged, got: {issues!r}"
95
+ )
96
+
97
+
98
+ def test_should_skip_test_file() -> None:
99
+ source = "from real_module import do_thing\n\n__all__ = ['do_thing']\n"
100
+ issues = check_thin_wrapper_files(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 = "from real_module import do_thing\n\n__all__ = ['do_thing']\n"
106
+ issues = check_thin_wrapper_files(source, HOOK_INFRASTRUCTURE_PATH)
107
+ assert issues == [], f"Hook infrastructure exempt, got: {issues!r}"
108
+
109
+
110
+ def test_should_skip_config_file() -> None:
111
+ source = "from real_module import do_thing\n\n__all__ = ['do_thing']\n"
112
+ issues = check_thin_wrapper_files(source, CONFIG_FILE_PATH)
113
+ assert issues == [], f"config/ files exempt, got: {issues!r}"
114
+
115
+
116
+ def test_should_handle_syntax_error_gracefully() -> None:
117
+ source = "from real_module import (\n"
118
+ issues = check_thin_wrapper_files(source, PRODUCTION_FILE_PATH)
119
+ assert issues == [], f"Syntax error must yield no issues, got: {issues!r}"
120
+
121
+
122
+ def test_should_not_flag_empty_file() -> None:
123
+ issues = check_thin_wrapper_files("", PRODUCTION_FILE_PATH)
124
+ assert issues == [], f"Empty file must not be flagged, got: {issues!r}"
125
+
126
+
127
+ def test_should_not_flag_module_docstring_with_real_code() -> None:
128
+ source = (
129
+ '"""Module docstring."""\n'
130
+ "from real_module import dependency\n"
131
+ "\n"
132
+ "def helper() -> int:\n"
133
+ " return dependency()\n"
134
+ )
135
+ issues = check_thin_wrapper_files(source, PRODUCTION_FILE_PATH)
136
+ assert issues == [], f"Docstring + real code must not be flagged, got: {issues!r}"
137
+
138
+
139
+ def test_should_flag_thin_wrapper_with_module_docstring() -> None:
140
+ source = (
141
+ '"""Module docstring."""\n'
142
+ "from real_module import do_thing\n"
143
+ "\n"
144
+ '__all__ = ["do_thing"]\n'
145
+ )
146
+ issues = check_thin_wrapper_files(source, PRODUCTION_FILE_PATH)
147
+ assert any("thin wrapper" in each.lower() for each in issues), (
148
+ f"Docstring + import + __all__ is still a thin wrapper, got: {issues!r}"
149
+ )
150
+
151
+
152
+ def test_validate_content_uses_empty_full_file_content_over_pre_edit_fragment() -> None:
153
+ """An empty-string `full_file_content` must be honored, not silently replaced with `content`.
154
+
155
+ Regression for loop1-8: the `or` short-circuit at line 3775 collapsed
156
+ empty-string and None, so an Edit that produced an empty post-edit file
157
+ was scanned against the pre-edit fragment instead of the empty file.
158
+ The thin-wrapper check uses the same idiom — an empty post-edit file is
159
+ not a thin wrapper, but a pre-edit fragment with imports + __all__ is.
160
+ """
161
+ pre_edit_fragment = "from real_module import do_thing\n__all__ = ['do_thing']\n"
162
+ issues = code_rules_enforcer.validate_content(
163
+ pre_edit_fragment,
164
+ PRODUCTION_FILE_PATH,
165
+ full_file_content="",
166
+ )
167
+ assert not any("thin wrapper" in each.lower() for each in issues), (
168
+ f"empty post-edit file must not be flagged as a thin wrapper, got: {issues!r}"
169
+ )
@@ -0,0 +1,99 @@
1
+ """Tests covering TODO/FIXME/HACK/XXX exemption from the comment blocker.
2
+
3
+ CODE_RULES.md requires scaffolding/placeholder code to carry TODO comments
4
+ naming what replaces it and why. Without an exemption, the no-NEW-comments
5
+ rule blocks every authored TODO. These tests pin the exemption.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import importlib.util
11
+ from pathlib import Path
12
+ from types import ModuleType
13
+
14
+
15
+ def _load_enforcer_module() -> ModuleType:
16
+ module_path = Path(__file__).parent / "code_rules_enforcer.py"
17
+ spec = importlib.util.spec_from_file_location("code_rules_enforcer", module_path)
18
+ assert spec is not None
19
+ assert spec.loader is not None
20
+ module = importlib.util.module_from_spec(spec)
21
+ spec.loader.exec_module(module)
22
+ return module
23
+
24
+
25
+ code_rules_enforcer = _load_enforcer_module()
26
+
27
+
28
+ def test_python_check_should_exempt_standalone_todo_comment() -> None:
29
+ content = "# TODO: replace stub with real implementation\nx = 1\n"
30
+ issues = code_rules_enforcer.check_comments_python(content)
31
+ assert issues == [], f"Expected no issues for '# TODO:' but got: {issues!r}"
32
+
33
+
34
+ def test_python_check_should_exempt_standalone_fixme_comment() -> None:
35
+ content = "# FIXME: handle the empty list case\nx = 1\n"
36
+ issues = code_rules_enforcer.check_comments_python(content)
37
+ assert issues == [], f"Expected no issues for '# FIXME:' but got: {issues!r}"
38
+
39
+
40
+ def test_python_check_should_exempt_standalone_hack_comment() -> None:
41
+ content = "# HACK: working around upstream bug #1234\nx = 1\n"
42
+ issues = code_rules_enforcer.check_comments_python(content)
43
+ assert issues == [], f"Expected no issues for '# HACK:' but got: {issues!r}"
44
+
45
+
46
+ def test_python_check_should_exempt_standalone_xxx_comment() -> None:
47
+ content = "# XXX revisit when API stabilizes\nx = 1\n"
48
+ issues = code_rules_enforcer.check_comments_python(content)
49
+ assert issues == [], f"Expected no issues for '# XXX' but got: {issues!r}"
50
+
51
+
52
+ def test_python_check_should_exempt_inline_todo_comment() -> None:
53
+ content = "x = 1 # TODO: extract to config\n"
54
+ issues = code_rules_enforcer.check_comments_python(content)
55
+ assert issues == [], f"Expected no issues for inline TODO but got: {issues!r}"
56
+
57
+
58
+ def test_python_check_should_exempt_inline_fixme_comment() -> None:
59
+ content = "x = 1 # FIXME: race condition\n"
60
+ issues = code_rules_enforcer.check_comments_python(content)
61
+ assert issues == [], f"Expected no issues for inline FIXME but got: {issues!r}"
62
+
63
+
64
+ def test_javascript_check_should_exempt_inline_todo_comment() -> None:
65
+ content = "const x = 1; // TODO: replace with config\n"
66
+ issues = code_rules_enforcer.check_comments_javascript(content)
67
+ assert issues == [], f"Expected no issues for JS inline TODO but got: {issues!r}"
68
+
69
+
70
+ def test_javascript_check_should_exempt_inline_fixme_comment() -> None:
71
+ content = "const x = 1; // FIXME: handle null case\n"
72
+ issues = code_rules_enforcer.check_comments_javascript(content)
73
+ assert issues == [], f"Expected no issues for JS inline FIXME but got: {issues!r}"
74
+
75
+
76
+ def test_extract_comment_texts_should_skip_standalone_todo() -> None:
77
+ content = "# TODO: replace stub\nx = 1\n"
78
+ inline, standalone = code_rules_enforcer.extract_comment_texts(content, "foo.py")
79
+ assert "# TODO: replace stub" not in standalone, (
80
+ "Standalone TODO must not be tracked as a comment subject to "
81
+ f"deletion-blocking, got standalone={standalone!r}"
82
+ )
83
+
84
+
85
+ def test_extract_comment_texts_should_skip_inline_todo() -> None:
86
+ content = "x = 1 # TODO: extract to config\n"
87
+ inline, standalone = code_rules_enforcer.extract_comment_texts(content, "foo.py")
88
+ assert all("TODO" not in each_comment for each_comment in inline), (
89
+ "Inline TODO must not be tracked as a comment subject to "
90
+ f"deletion-blocking, got inline={inline!r}"
91
+ )
92
+
93
+
94
+ def test_existing_non_todo_comment_still_flagged() -> None:
95
+ content = "x = 1 # this is just a comment\n"
96
+ issues = code_rules_enforcer.check_comments_python(content)
97
+ assert len(issues) == 1, (
98
+ f"Expected non-TODO inline comment to still be flagged, got: {issues!r}"
99
+ )
@@ -0,0 +1,141 @@
1
+ """Tests for check_typed_dict_encode_decode — flags TypedDicts missing companion encoders.
2
+
3
+ Per Plan 1c.typed_dict_validator / Phase B2: every TypedDict declaration in
4
+ production code must have a companion `_encode_<snake_name>` and
5
+ `_decode_<snake_name>` function so untyped dicts cannot leak across module
6
+ boundaries without explicit validation.
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_typed_dict_encode_decode(content: str, file_path: str) -> list[str]:
30
+ return code_rules_enforcer.check_typed_dict_encode_decode(content, file_path)
31
+
32
+
33
+ PRODUCTION_FILE_PATH = "/project/src/contracts.py"
34
+ TEST_FILE_PATH = "/project/src/test_contracts.py"
35
+ HOOK_INFRASTRUCTURE_PATH = "/home/user/.claude/hooks/blocking/example.py"
36
+
37
+
38
+ def test_should_flag_typed_dict_without_encode_or_decode() -> None:
39
+ source = (
40
+ "from typing import TypedDict\n"
41
+ "class InvoicePayload(TypedDict):\n"
42
+ " amount: int\n"
43
+ )
44
+ issues = check_typed_dict_encode_decode(source, PRODUCTION_FILE_PATH)
45
+ assert any("InvoicePayload" in each for each in issues), (
46
+ f"Expected InvoicePayload to be flagged, got: {issues!r}"
47
+ )
48
+
49
+
50
+ def test_should_flag_typed_dict_with_only_encode() -> None:
51
+ source = (
52
+ "from typing import TypedDict\n"
53
+ "class InvoicePayload(TypedDict):\n"
54
+ " amount: int\n"
55
+ "def _encode_invoice_payload(value: InvoicePayload) -> bytes:\n"
56
+ " return b''\n"
57
+ )
58
+ issues = check_typed_dict_encode_decode(source, PRODUCTION_FILE_PATH)
59
+ assert any(
60
+ "InvoicePayload" in each and "decode" in each.lower() for each in issues
61
+ ), f"Expected missing _decode_ to be flagged, got: {issues!r}"
62
+
63
+
64
+ def test_should_flag_typed_dict_with_only_decode() -> None:
65
+ source = (
66
+ "from typing import TypedDict\n"
67
+ "class InvoicePayload(TypedDict):\n"
68
+ " amount: int\n"
69
+ "def _decode_invoice_payload(raw: bytes) -> InvoicePayload:\n"
70
+ " return {'amount': 0}\n"
71
+ )
72
+ issues = check_typed_dict_encode_decode(source, PRODUCTION_FILE_PATH)
73
+ assert any(
74
+ "InvoicePayload" in each and "encode" in each.lower() for each in issues
75
+ ), f"Expected missing _encode_ to be flagged, got: {issues!r}"
76
+
77
+
78
+ def test_should_not_flag_typed_dict_with_both_companions() -> None:
79
+ source = (
80
+ "from typing import TypedDict\n"
81
+ "class InvoicePayload(TypedDict):\n"
82
+ " amount: int\n"
83
+ "def _encode_invoice_payload(value: InvoicePayload) -> bytes:\n"
84
+ " return b''\n"
85
+ "def _decode_invoice_payload(raw: bytes) -> InvoicePayload:\n"
86
+ " return {'amount': 0}\n"
87
+ )
88
+ issues = check_typed_dict_encode_decode(source, PRODUCTION_FILE_PATH)
89
+ assert issues == [], f"Both companions present, got: {issues!r}"
90
+
91
+
92
+ def test_should_handle_pascal_to_snake_conversion() -> None:
93
+ source = (
94
+ "from typing import TypedDict\n"
95
+ "class TypedAuthRequest(TypedDict):\n"
96
+ " token: str\n"
97
+ )
98
+ issues = check_typed_dict_encode_decode(source, PRODUCTION_FILE_PATH)
99
+ assert any("TypedAuthRequest" in each for each in issues), (
100
+ f"PascalCase conversion expected; got: {issues!r}"
101
+ )
102
+
103
+
104
+ def test_should_skip_test_file() -> None:
105
+ source = "from typing import TypedDict\nclass MockPayload(TypedDict):\n x: int\n"
106
+ issues = check_typed_dict_encode_decode(source, TEST_FILE_PATH)
107
+ assert issues == [], f"Test files exempt, got: {issues!r}"
108
+
109
+
110
+ def test_should_skip_hook_infrastructure() -> None:
111
+ source = "from typing import TypedDict\nclass HookPayload(TypedDict):\n x: int\n"
112
+ issues = check_typed_dict_encode_decode(source, HOOK_INFRASTRUCTURE_PATH)
113
+ assert issues == [], f"Hook infrastructure exempt, got: {issues!r}"
114
+
115
+
116
+ def test_should_handle_syntax_error_gracefully() -> None:
117
+ source = "class InvoicePayload(TypedDict\n"
118
+ issues = check_typed_dict_encode_decode(source, PRODUCTION_FILE_PATH)
119
+ assert issues == [], f"Syntax error must yield no issues, got: {issues!r}"
120
+
121
+
122
+ def test_should_not_flag_typed_dict_nested_inside_class() -> None:
123
+ source = (
124
+ "from typing import TypedDict\n"
125
+ "class Service:\n"
126
+ " class RequestPayload(TypedDict):\n"
127
+ " token: str\n"
128
+ )
129
+ issues = check_typed_dict_encode_decode(source, PRODUCTION_FILE_PATH)
130
+ assert issues == [], f"Nested TypedDict must not be flagged, got: {issues!r}"
131
+
132
+
133
+ def test_should_not_flag_non_typed_dict_class() -> None:
134
+ source = (
135
+ "from dataclasses import dataclass\n"
136
+ "@dataclass\n"
137
+ "class Invoice:\n"
138
+ " amount: int\n"
139
+ )
140
+ issues = check_typed_dict_encode_decode(source, PRODUCTION_FILE_PATH)
141
+ assert issues == [], f"Regular dataclass must not be flagged, got: {issues!r}"