claude-dev-env 1.38.1 → 1.40.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 (282) 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 +199 -0
  7. package/_shared/pr-loop/scripts/config/reviews_disabled_constants.py +8 -0
  8. package/_shared/pr-loop/scripts/post_audit_thread.py +1242 -0
  9. package/_shared/pr-loop/scripts/preflight.py +129 -2
  10. package/_shared/pr-loop/scripts/reviews_disabled.py +59 -0
  11. package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +0 -19
  12. package/_shared/pr-loop/scripts/tests/test_post_audit_thread.py +1116 -0
  13. package/_shared/pr-loop/scripts/tests/test_post_audit_thread_constants.py +127 -0
  14. package/_shared/pr-loop/scripts/tests/test_preflight.py +41 -0
  15. package/_shared/pr-loop/scripts/tests/test_reviews_disabled.py +36 -0
  16. package/_shared/pr-loop/state-schema.md +1 -1
  17. package/agents/clean-coder.md +2 -2
  18. package/agents/pr-description-writer.md +150 -52
  19. package/bin/install.mjs +6 -7
  20. package/bin/install.test.mjs +8 -0
  21. package/commands/doc-gist.md +16 -0
  22. package/commands/plan.md +0 -2
  23. package/commands/review-plan.md +1 -1
  24. package/docs/CODE_RULES.md +122 -2
  25. package/docs/PR_DESCRIPTION_GUIDE.md +127 -64
  26. package/hooks/blocking/bot_mention_comment_blocker.py +75 -0
  27. package/hooks/blocking/code_rules_enforcer.py +1143 -129
  28. package/hooks/blocking/convergence_gate_blocker.py +130 -0
  29. package/hooks/blocking/destructive_command_blocker.py +74 -0
  30. package/hooks/blocking/gh_body_arg_blocker.py +30 -0
  31. package/hooks/blocking/md_to_html_blocker.py +119 -0
  32. package/hooks/blocking/pr_description_enforcer.py +57 -22
  33. package/hooks/blocking/test_bot_mention_comment_blocker.py +131 -0
  34. package/hooks/blocking/test_code_rules_enforcer.py +21 -0
  35. package/hooks/blocking/test_code_rules_enforcer_any_exempt_files.py +70 -0
  36. package/hooks/blocking/test_code_rules_enforcer_any_imports_and_cast.py +92 -0
  37. package/hooks/blocking/test_code_rules_enforcer_banned_import_alias.py +143 -0
  38. package/hooks/blocking/test_code_rules_enforcer_banned_prefixes.py +152 -0
  39. package/hooks/blocking/test_code_rules_enforcer_bare_except.py +120 -0
  40. package/hooks/blocking/test_code_rules_enforcer_boundary_types.py +175 -0
  41. package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +0 -1
  42. package/hooks/blocking/test_code_rules_enforcer_collection_prefix.py +50 -0
  43. package/hooks/blocking/test_code_rules_enforcer_docstring_format.py +255 -0
  44. package/hooks/blocking/test_code_rules_enforcer_inline_tuple_string_magic.py +130 -0
  45. package/hooks/blocking/test_code_rules_enforcer_stub_implementations.py +141 -0
  46. package/hooks/blocking/test_code_rules_enforcer_test_branching.py +143 -0
  47. package/hooks/blocking/test_code_rules_enforcer_thin_wrapper_files.py +169 -0
  48. package/hooks/blocking/test_code_rules_enforcer_todo_markers.py +99 -0
  49. package/hooks/blocking/test_code_rules_enforcer_typed_dict_pairs.py +141 -0
  50. package/hooks/blocking/test_convergence_gate_blocker.py +63 -0
  51. package/hooks/blocking/test_destructive_command_blocker.py +146 -0
  52. package/hooks/blocking/test_destructive_command_blocker_no_verify.py +102 -0
  53. package/hooks/blocking/test_gh_body_arg_blocker.py +45 -0
  54. package/hooks/blocking/test_md_to_html_blocker.py +317 -0
  55. package/hooks/blocking/test_pr_description_enforcer.py +69 -8
  56. package/hooks/config/any_type_config.py +7 -0
  57. package/hooks/config/banned_identifiers_constants.py +11 -0
  58. package/hooks/config/blocking_check_limits.py +38 -0
  59. package/hooks/config/bot_mention_comment_blocker_constants.py +20 -0
  60. package/hooks/config/code_rules_enforcer_constants.py +53 -0
  61. package/hooks/config/convergence_branch_constants.py +9 -0
  62. package/hooks/config/doc_gist_auto_publish_constants.py +18 -0
  63. package/hooks/config/html_companion_constants.py +20 -0
  64. package/hooks/config/inline_tuple_string_magic_constants.py +22 -0
  65. package/hooks/config/pr_description_enforcer_constants.py +14 -0
  66. package/hooks/config/test_banned_identifiers_constants.py +17 -0
  67. package/hooks/hooks.json +28 -20
  68. package/hooks/pyproject.toml +69 -0
  69. package/hooks/validators/mypy_integration.py +47 -1
  70. package/hooks/validators/run_all_validators.py +3 -3
  71. package/hooks/validators/test_mypy_integration.py +50 -1
  72. package/hooks/workflow/doc_gist_auto_publish.py +144 -0
  73. package/hooks/workflow/md_to_html_companion.py +365 -0
  74. package/hooks/workflow/test_doc_gist_auto_publish.py +117 -0
  75. package/hooks/workflow/test_md_to_html_companion.py +452 -0
  76. package/package.json +1 -1
  77. package/rules/gh-body-file.md +2 -0
  78. package/scripts/Install-SweepEmptyDirs.ps1 +111 -0
  79. package/scripts/check.ps1 +106 -0
  80. package/scripts/config/timing.py +11 -0
  81. package/scripts/sweep_empty_dirs.py +138 -0
  82. package/scripts/sync_to_cursor/rules.py +1 -1
  83. package/scripts/test_sweep_empty_dirs.py +183 -0
  84. package/skills/_shared/pr-loop/prompts/pr-consistency-audit.xml +323 -0
  85. package/skills/_shared/pr-loop/scripts/_cli_utils.py +22 -0
  86. package/skills/_shared/pr-loop/scripts/_path_resolver.py +165 -0
  87. package/skills/_shared/pr-loop/scripts/_xml_utils.py +20 -0
  88. package/skills/_shared/pr-loop/scripts/build_audit_prompt.py +182 -0
  89. package/skills/_shared/pr-loop/scripts/build_fix_prompt.py +185 -0
  90. package/skills/_shared/pr-loop/scripts/config/__init__.py +0 -0
  91. package/skills/_shared/pr-loop/scripts/config/path_resolver_constants.py +78 -0
  92. package/skills/_shared/pr-loop/scripts/init_loop_state.py +135 -0
  93. package/skills/_shared/pr-loop/scripts/teardown_worktrees.py +175 -0
  94. package/skills/_shared/pr-loop/scripts/write_audit_outcomes.py +182 -0
  95. package/skills/_shared/pr-loop/scripts/write_fix_outcomes.py +206 -0
  96. package/skills/bugteam/CONSTRAINTS.md +21 -22
  97. package/skills/bugteam/EXAMPLES.md +3 -3
  98. package/skills/bugteam/PROMPTS.md +227 -67
  99. package/skills/bugteam/SKILL.md +132 -455
  100. package/skills/bugteam/reference/README.md +1 -1
  101. package/skills/bugteam/reference/audit-and-teammates.md +112 -39
  102. package/skills/bugteam/reference/audit-contract.md +4 -22
  103. package/skills/bugteam/reference/copilot-gap-analysis.md +8 -5
  104. package/skills/bugteam/reference/design-rationale.md +2 -2
  105. package/skills/bugteam/reference/github-pr-reviews.md +50 -57
  106. package/skills/bugteam/reference/obstacles/audit-assign-ids.md +13 -0
  107. package/skills/bugteam/reference/obstacles/audit-capture-excerpts.md +13 -0
  108. package/skills/bugteam/reference/obstacles/audit-walk-categories.md +13 -0
  109. package/skills/bugteam/reference/obstacles/audit-write-xml.md +13 -0
  110. package/skills/bugteam/reference/obstacles/fix-append-summary.md +13 -0
  111. package/skills/bugteam/reference/obstacles/fix-apply-fixes.md +13 -0
  112. package/skills/bugteam/reference/obstacles/fix-git-add-commit.md +13 -0
  113. package/skills/bugteam/reference/obstacles/fix-git-push.md +13 -0
  114. package/skills/bugteam/reference/obstacles/fix-post-reply.md +13 -0
  115. package/skills/bugteam/reference/obstacles/fix-publish-summary.md +13 -0
  116. package/skills/bugteam/reference/obstacles/fix-py-compile.md +13 -0
  117. package/skills/bugteam/reference/obstacles/fix-read-files.md +13 -0
  118. package/skills/bugteam/reference/obstacles/fix-resolve-thread.md +13 -0
  119. package/skills/bugteam/reference/obstacles/fix-test-suite.md +13 -0
  120. package/skills/bugteam/reference/obstacles/fix-violation-count.md +13 -0
  121. package/skills/bugteam/reference/obstacles/fix-write-xml.md +13 -0
  122. package/skills/bugteam/reference/team-setup.md +111 -9
  123. package/skills/bugteam/reference/teardown-publish-permissions.md +39 -8
  124. package/skills/bugteam/scripts/README.md +60 -0
  125. package/skills/bugteam/scripts/_claude_permissions_common.py +358 -0
  126. package/skills/bugteam/scripts/bugteam_code_rules_gate.py +976 -0
  127. package/skills/bugteam/scripts/bugteam_fix_hookspath.py +375 -0
  128. package/skills/bugteam/scripts/bugteam_preflight.py +328 -0
  129. package/skills/bugteam/scripts/config/bugteam_code_rules_gate_constants.py +25 -0
  130. package/skills/bugteam/scripts/config/bugteam_fix_hookspath_constants.py +26 -0
  131. package/skills/bugteam/scripts/config/bugteam_preflight_constants.py +35 -0
  132. package/skills/bugteam/scripts/config/claude_permissions_common_constants.py +20 -0
  133. package/skills/bugteam/scripts/config/probe_code_rules_enforcer_check_constants.py +12 -0
  134. package/skills/bugteam/scripts/config/windows_safe_rmtree_constants.py +7 -0
  135. package/skills/bugteam/scripts/grant_project_claude_permissions.py +175 -0
  136. package/skills/bugteam/scripts/probe_code_rules_enforcer_check.py +107 -0
  137. package/skills/bugteam/scripts/revoke_project_claude_permissions.py +220 -0
  138. package/skills/bugteam/scripts/test__claude_permissions_common.py +112 -0
  139. package/skills/bugteam/scripts/test_bugteam_code_rules_gate.py +400 -0
  140. package/skills/bugteam/scripts/test_bugteam_fix_hookspath.py +384 -0
  141. package/skills/bugteam/scripts/test_bugteam_preflight.py +309 -0
  142. package/skills/bugteam/scripts/test_claude_permissions_common.py +195 -0
  143. package/skills/bugteam/scripts/test_grant_project_claude_permissions.py +55 -0
  144. package/skills/bugteam/scripts/test_probe_code_rules_enforcer_check.py +76 -0
  145. package/skills/bugteam/scripts/test_revoke_project_claude_permissions.py +55 -0
  146. package/skills/bugteam/scripts/test_windows_safe_rmtree.py +108 -0
  147. package/skills/bugteam/scripts/windows_safe_rmtree.py +100 -0
  148. package/skills/bugteam/test_skill_additions.py +1 -11
  149. package/skills/code/SKILL.md +176 -0
  150. package/skills/copilot-review/SKILL.md +16 -0
  151. package/skills/doc-gist/SKILL.md +99 -0
  152. package/skills/doc-gist/references/examples/01-exploration-code-approaches.html +453 -0
  153. package/skills/doc-gist/references/examples/02-exploration-visual-designs.html +515 -0
  154. package/skills/doc-gist/references/examples/03-code-review-pr.html +638 -0
  155. package/skills/doc-gist/references/examples/04-code-understanding.html +491 -0
  156. package/skills/doc-gist/references/examples/05-design-system.html +629 -0
  157. package/skills/doc-gist/references/examples/06-component-variants.html +605 -0
  158. package/skills/doc-gist/references/examples/07-prototype-animation.html +455 -0
  159. package/skills/doc-gist/references/examples/08-prototype-interaction.html +396 -0
  160. package/skills/doc-gist/references/examples/09-slide-deck.html +592 -0
  161. package/skills/doc-gist/references/examples/10-svg-illustrations.html +492 -0
  162. package/skills/doc-gist/references/examples/11-status-report.html +528 -0
  163. package/skills/doc-gist/references/examples/12-incident-report.html +596 -0
  164. package/skills/doc-gist/references/examples/13-flowchart-diagram.html +395 -0
  165. package/skills/doc-gist/references/examples/14-research-feature-explainer.html +381 -0
  166. package/skills/doc-gist/references/examples/15-research-concept-explainer.html +368 -0
  167. package/skills/doc-gist/references/examples/16-implementation-plan.html +702 -0
  168. package/skills/doc-gist/references/examples/17-pr-writeup.html +595 -0
  169. package/skills/doc-gist/references/examples/18-editor-triage-board.html +573 -0
  170. package/skills/doc-gist/references/examples/19-editor-feature-flags.html +663 -0
  171. package/skills/doc-gist/references/examples/20-editor-prompt-tuner.html +722 -0
  172. package/skills/doc-gist/references/examples/README.md +5 -0
  173. package/skills/doc-gist/scripts/config/__init__.py +0 -0
  174. package/skills/doc-gist/scripts/config/gist_upload_constants.py +16 -0
  175. package/skills/doc-gist/scripts/gist_upload.py +177 -0
  176. package/skills/doc-gist/scripts/test_gist_upload.py +51 -0
  177. package/skills/findbugs/SKILL.md +96 -2
  178. package/skills/monitor-open-prs/SKILL.md +14 -32
  179. package/skills/monitor-open-prs/test_skill_contract.py +0 -11
  180. package/skills/pr-consistency-audit/SKILL.md +112 -0
  181. package/skills/pr-consistency-audit/reference/detection-rules.md +96 -0
  182. package/skills/pr-consistency-audit/reference/illustrations.md +78 -0
  183. package/skills/pr-converge/SKILL.md +229 -23
  184. package/skills/pr-converge/config/__init__.py +0 -0
  185. package/skills/pr-converge/config/constants.py +63 -0
  186. package/skills/pr-converge/reference/convergence-gates.md +138 -44
  187. package/skills/pr-converge/reference/examples.md +43 -11
  188. package/skills/pr-converge/reference/fix-protocol.md +6 -5
  189. package/skills/pr-converge/reference/ground-rules.md +5 -3
  190. package/skills/pr-converge/reference/multi-pr-orchestration.md +44 -19
  191. package/skills/pr-converge/reference/obstacles/fix-post-replies.md +13 -0
  192. package/skills/pr-converge/reference/obstacles/fix-publish-summary.md +13 -0
  193. package/skills/pr-converge/reference/obstacles/fix-push.md +13 -0
  194. package/skills/pr-converge/reference/obstacles/fix-read-filelines.md +13 -0
  195. package/skills/pr-converge/reference/obstacles/fix-reset-state.md +13 -0
  196. package/skills/pr-converge/reference/obstacles/fix-resolve-threads.md +13 -0
  197. package/skills/pr-converge/reference/obstacles/fix-spawn-clean-coder.md +13 -0
  198. package/skills/pr-converge/reference/obstacles/fix-stage-commit.md +13 -0
  199. package/skills/pr-converge/reference/obstacles/fix-trigger-bugbot.md +13 -0
  200. package/skills/pr-converge/reference/obstacles/fix-write-test.md +13 -0
  201. package/skills/pr-converge/reference/per-tick.md +107 -31
  202. package/skills/pr-converge/reference/state-schema.md +22 -1
  203. package/skills/pr-converge/reference/stop-conditions.md +9 -7
  204. package/skills/pr-converge/scripts/README.md +34 -46
  205. package/skills/pr-converge/scripts/check_bugbot_ci.py +279 -0
  206. package/skills/pr-converge/scripts/check_convergence.py +497 -0
  207. package/skills/pr-converge/scripts/check_pending_reviews.py +154 -0
  208. package/skills/pr-converge/scripts/config/pr_converge_constants.py +118 -0
  209. package/skills/pr-converge/scripts/fetch_copilot_reviews.py +134 -0
  210. package/skills/pr-converge/scripts/post_fix_reply.py +168 -0
  211. package/skills/pr-converge/scripts/test_check_bugbot_ci.py +312 -0
  212. package/skills/pr-converge/workflows/schedule-wakeup-loop.md +5 -12
  213. package/skills/qbug/SKILL.md +157 -27
  214. package/skills/session-log/SKILL.md +216 -114
  215. package/skills/session-tidy/SKILL.md +1 -1
  216. package/skills/skill-builder/SKILL.md +138 -56
  217. package/skills/skill-builder/references/delegation-map.md +72 -113
  218. package/skills/skill-builder/references/progressive-disclosure.md +122 -0
  219. package/skills/skill-builder/references/self-audit-checklist.md +92 -0
  220. package/skills/skill-builder/references/skill-types.md +228 -0
  221. package/skills/skill-builder/references/thariq-x-post-skills.json +33 -0
  222. package/skills/skill-builder/templates/gap-analysis.md +15 -8
  223. package/skills/skill-builder/workflows/improve-skill.md +86 -57
  224. package/skills/skill-builder/workflows/new-skill.md +80 -168
  225. package/skills/skill-builder/workflows/polish-skill.md +78 -54
  226. package/skills/structure-prompt/SKILL.md +50 -0
  227. package/skills/structure-prompt/reference/adversarial-tuning.md +62 -0
  228. package/skills/structure-prompt/reference/block-classification.md +27 -0
  229. package/skills/structure-prompt/reference/canonical-case.md +48 -0
  230. package/skills/structure-prompt/reference/citation-depth.md +70 -0
  231. package/skills/structure-prompt/reference/cleanup.md +33 -0
  232. package/skills/structure-prompt/reference/constraints.md +33 -0
  233. package/skills/structure-prompt/reference/directives.md +37 -0
  234. package/skills/structure-prompt/reference/examples.md +72 -0
  235. package/skills/structure-prompt/reference/instantiation.md +51 -0
  236. package/skills/structure-prompt/reference/output-contract.md +72 -0
  237. package/skills/structure-prompt/reference/per-category.md +23 -0
  238. package/skills/structure-prompt/reference/persona.md +38 -0
  239. package/skills/structure-prompt/reference/research.md +33 -0
  240. package/skills/structure-prompt/reference/structure.md +28 -0
  241. package/agents/code-standards-agent.md +0 -93
  242. package/agents/groq-coder.md +0 -113
  243. package/agents/plan-executor.md +0 -226
  244. package/agents/project-docs-analyzer.md +0 -53
  245. package/agents/project-structure-organizer-agent.md +0 -72
  246. package/agents/skill-to-agent-converter.md +0 -370
  247. package/agents/skill-writer-agent.md +0 -470
  248. package/agents/user-docs-writer.md +0 -67
  249. package/agents/workflow-visual-documenter.md +0 -82
  250. package/commands/readability-review.md +0 -20
  251. package/hooks/mypy.ini +0 -2
  252. package/hooks/notification/attention_needed_notify.py +0 -71
  253. package/hooks/notification/claude_notification_handler.py +0 -67
  254. package/hooks/notification/notification_utils.py +0 -267
  255. package/hooks/notification/subagent_complete_notify.py +0 -381
  256. package/hooks/notification/test_attention_needed_notify.py +0 -47
  257. package/hooks/notification/test_claude_notification_handler.py +0 -54
  258. package/hooks/notification/test_notification_utils.py +0 -91
  259. package/hooks/notification/test_subagent_complete_notify.py +0 -79
  260. package/scripts/config/groq_bugteam_config.py +0 -230
  261. package/scripts/config/test_groq_bugteam_config.py +0 -83
  262. package/scripts/config/test_spec_implementer_prompt.py +0 -32
  263. package/scripts/groq_bugteam.README.md +0 -131
  264. package/scripts/groq_bugteam.py +0 -647
  265. package/scripts/groq_bugteam_dotenv.py +0 -40
  266. package/scripts/groq_bugteam_spec.py +0 -226
  267. package/scripts/test_groq_bugteam.py +0 -529
  268. package/scripts/test_groq_bugteam_apply_fix_from_spec.py +0 -426
  269. package/scripts/test_groq_bugteam_dotenv.py +0 -66
  270. package/scripts/test_groq_bugteam_spec.py +0 -338
  271. package/skills/bugteam/SKILL_EVALS.md +0 -309
  272. package/skills/dream/SKILL.md +0 -118
  273. package/skills/ingest/SKILL.md +0 -40
  274. package/skills/npm-creator/SKILL.md +0 -187
  275. package/skills/readability-review/SKILL.md +0 -127
  276. package/skills/resume-review/SKILL.md +0 -261
  277. package/skills/rule-audit/SKILL.md +0 -307
  278. package/skills/rule-creator/SKILL.md +0 -150
  279. package/skills/searching-obsidian-vault/SKILL.md +0 -131
  280. package/skills/skill-writer/REFERENCE.md +0 -284
  281. package/skills/skill-writer/SKILL.md +0 -222
  282. package/skills/tdd-team/SKILL.md +0 -128
@@ -0,0 +1,141 @@
1
+ """Tests for check_stub_implementations — flags placeholder function bodies.
2
+
3
+ Per Plan 1c.stub_detector / Phase B1: production functions whose body is
4
+ only `pass`, `...` (Ellipsis), or `raise NotImplementedError` are stubs.
5
+ Exemptions: ABC methods, Protocol methods, abstractmethod-decorated
6
+ functions, test files, hook infrastructure.
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_stub_implementations(content: str, file_path: str) -> list[str]:
30
+ return code_rules_enforcer.check_stub_implementations(content, file_path)
31
+
32
+ PRODUCTION_FILE_PATH = "/project/src/services.py"
33
+ TEST_FILE_PATH = "/project/src/test_services.py"
34
+ HOOK_INFRASTRUCTURE_PATH = "/home/user/.claude/hooks/blocking/example.py"
35
+
36
+
37
+ def test_should_flag_pass_only_function() -> None:
38
+ source = "def parse_invoice(payload: str) -> int:\n pass\n"
39
+ issues = check_stub_implementations(source, PRODUCTION_FILE_PATH)
40
+ assert any("parse_invoice" in each for each in issues), (
41
+ f"Expected pass-only function to be flagged, got: {issues!r}"
42
+ )
43
+
44
+
45
+ def test_should_flag_ellipsis_only_function() -> None:
46
+ source = "def parse_invoice(payload: str) -> int:\n ...\n"
47
+ issues = check_stub_implementations(source, PRODUCTION_FILE_PATH)
48
+ assert any("parse_invoice" in each for each in issues), (
49
+ f"Expected ellipsis-only function to be flagged, got: {issues!r}"
50
+ )
51
+
52
+
53
+ def test_should_flag_raise_not_implemented_function() -> None:
54
+ source = "def parse_invoice(payload: str) -> int:\n raise NotImplementedError\n"
55
+ issues = check_stub_implementations(source, PRODUCTION_FILE_PATH)
56
+ assert any("parse_invoice" in each for each in issues), (
57
+ f"Expected NotImplementedError stub to be flagged, got: {issues!r}"
58
+ )
59
+
60
+
61
+ def test_should_flag_raise_not_implemented_with_message() -> None:
62
+ source = (
63
+ "def parse_invoice(payload: str) -> int:\n"
64
+ " raise NotImplementedError('coming soon')\n"
65
+ )
66
+ issues = check_stub_implementations(source, PRODUCTION_FILE_PATH)
67
+ assert any("parse_invoice" in each for each in issues), (
68
+ f"Expected NotImplementedError(...) stub to be flagged, got: {issues!r}"
69
+ )
70
+
71
+
72
+ def test_should_flag_function_with_docstring_then_pass() -> None:
73
+ source = (
74
+ "def parse_invoice(payload: str) -> int:\n"
75
+ ' """Parse the invoice."""\n'
76
+ " pass\n"
77
+ )
78
+ issues = check_stub_implementations(source, PRODUCTION_FILE_PATH)
79
+ assert any("parse_invoice" in each for each in issues), (
80
+ f"Docstring + pass is still a stub; should be flagged. Got: {issues!r}"
81
+ )
82
+
83
+
84
+ def test_should_not_flag_real_implementation() -> None:
85
+ source = "def parse_invoice(payload: str) -> int:\n return len(payload)\n"
86
+ issues = check_stub_implementations(source, PRODUCTION_FILE_PATH)
87
+ assert issues == [], f"Real impl must not be flagged, got: {issues!r}"
88
+
89
+
90
+ def test_should_exempt_abstractmethod_decorated() -> None:
91
+ source = (
92
+ "from abc import ABC, abstractmethod\n"
93
+ "class InvoiceParser(ABC):\n"
94
+ " @abstractmethod\n"
95
+ " def parse(self, payload: str) -> int:\n"
96
+ " pass\n"
97
+ )
98
+ issues = check_stub_implementations(source, PRODUCTION_FILE_PATH)
99
+ assert issues == [], f"@abstractmethod must be exempt, got: {issues!r}"
100
+
101
+
102
+ def test_should_exempt_protocol_methods() -> None:
103
+ source = (
104
+ "from typing import Protocol\n"
105
+ "class InvoiceParser(Protocol):\n"
106
+ " def parse(self, payload: str) -> int:\n"
107
+ " ...\n"
108
+ )
109
+ issues = check_stub_implementations(source, PRODUCTION_FILE_PATH)
110
+ assert issues == [], f"Protocol methods must be exempt, got: {issues!r}"
111
+
112
+
113
+ def test_should_skip_test_file() -> None:
114
+ source = "def stub_helper():\n pass\n"
115
+ issues = check_stub_implementations(source, TEST_FILE_PATH)
116
+ assert issues == [], f"Test files must be exempt, got: {issues!r}"
117
+
118
+
119
+ def test_should_skip_hook_infrastructure() -> None:
120
+ source = "def stub_helper():\n pass\n"
121
+ issues = check_stub_implementations(source, HOOK_INFRASTRUCTURE_PATH)
122
+ assert issues == [], f"Hook infrastructure must be exempt, got: {issues!r}"
123
+
124
+
125
+ def test_should_handle_syntax_error_gracefully() -> None:
126
+ source = "def parse_invoice(\n"
127
+ issues = check_stub_implementations(source, PRODUCTION_FILE_PATH)
128
+ assert issues == [], f"Syntax errors must yield no issues, got: {issues!r}"
129
+
130
+
131
+ def test_should_include_line_number_in_issue() -> None:
132
+ source = "x = 1\n\ndef parse_invoice():\n pass\n"
133
+ issues = check_stub_implementations(source, PRODUCTION_FILE_PATH)
134
+ assert len(issues) == 1
135
+ assert "Line 3" in issues[0], f"Issue must include line number, got: {issues[0]!r}"
136
+
137
+
138
+ def test_should_cap_at_three_issues() -> None:
139
+ source = "\n\n".join(f"def stub_{i}():\n pass" for i in range(5)) + "\n"
140
+ issues = check_stub_implementations(source, PRODUCTION_FILE_PATH)
141
+ assert len(issues) <= 3, f"Issue count must be capped at 3, got: {len(issues)}"
@@ -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
+ )