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,69 @@
1
+ # Centralized configuration for hook tooling.
2
+ #
3
+ # Replaces the standalone hooks/mypy.ini and provides a single config surface
4
+ # for mypy, ruff, and pytest invocations rooted under packages/claude-dev-env/hooks/.
5
+ # C3 (mypy_integration walk-up) finds this file by walking parents from each
6
+ # checked .py file and matching on the [tool.mypy] table.
7
+
8
+ [tool.mypy]
9
+ mypy_path = "."
10
+ python_version = "3.13"
11
+ ignore_missing_imports = true
12
+ warn_unused_ignores = true
13
+ warn_redundant_casts = true
14
+ no_implicit_optional = true
15
+ strict_equality = true
16
+
17
+ [[tool.mypy.overrides]]
18
+ module = "tests.*"
19
+ disallow_untyped_defs = false
20
+
21
+ [tool.ruff]
22
+ line-length = 100
23
+ target-version = "py313"
24
+ extend-exclude = [
25
+ ".audit-trail",
26
+ ".claude",
27
+ "node_modules",
28
+ "__pycache__",
29
+ ]
30
+
31
+ [tool.ruff.lint]
32
+ select = [
33
+ "E",
34
+ "F",
35
+ "W",
36
+ "I",
37
+ "B",
38
+ "UP",
39
+ "SIM",
40
+ "PL",
41
+ ]
42
+ ignore = [
43
+ "E501",
44
+ "PLR0913",
45
+ "PLR2004",
46
+ ]
47
+
48
+ [tool.ruff.lint.per-file-ignores]
49
+ "test_*.py" = ["B011", "PLR2004"]
50
+ "*_test.py" = ["B011", "PLR2004"]
51
+ "conftest.py" = ["B011"]
52
+
53
+ [tool.coverage.run]
54
+ branch = true
55
+ omit = [
56
+ "test_*.py",
57
+ "*_test.py",
58
+ "conftest.py",
59
+ "__pycache__/*",
60
+ ".audit-trail/*",
61
+ ]
62
+
63
+ [tool.coverage.report]
64
+ exclude_lines = [
65
+ "pragma: no cover",
66
+ "raise NotImplementedError",
67
+ "if TYPE_CHECKING:",
68
+ "if __name__ == .__main__.:",
69
+ ]
@@ -1,6 +1,7 @@
1
1
  """Mypy integration for static type checking."""
2
2
 
3
3
  import subprocess
4
+ import tomllib
4
5
  from dataclasses import dataclass
5
6
  from pathlib import Path
6
7
 
@@ -25,6 +26,43 @@ def check_mypy_available() -> bool:
25
26
  return False
26
27
 
27
28
 
29
+ def _pyproject_contains_tool_mypy(pyproject_path: Path) -> bool:
30
+ try:
31
+ with pyproject_path.open("rb") as readable_handle:
32
+ parsed_toml = tomllib.load(readable_handle)
33
+ except (OSError, tomllib.TOMLDecodeError):
34
+ return False
35
+ tool_table = parsed_toml.get("tool", {})
36
+ return isinstance(tool_table, dict) and "mypy" in tool_table
37
+
38
+
39
+ def find_pyproject_with_mypy_config(starting_file: Path) -> Path | None:
40
+ """Walk up from a starting file to locate a pyproject.toml that configures mypy.
41
+
42
+ The walk skips pyproject.toml files that do not declare a [tool.mypy]
43
+ table so that an unrelated package config (for example, a project root
44
+ pyproject.toml) does not shadow a hook-tree pyproject.toml that
45
+ actually configures the type checker.
46
+
47
+ Args:
48
+ starting_file: The file (or directory) the walk begins from. The walk
49
+ climbs through every parent directory in order.
50
+
51
+ Returns:
52
+ The first ``pyproject.toml`` Path that declares a ``[tool.mypy]``
53
+ table, or ``None`` when no such file exists between ``starting_file``
54
+ and the filesystem root.
55
+ """
56
+ pyproject_filename_for_lookup = "pyproject.toml"
57
+ resolved_starting_file = starting_file.resolve()
58
+ current_directory = resolved_starting_file.parent if resolved_starting_file.is_file() else resolved_starting_file
59
+ for each_candidate_directory in [current_directory, *current_directory.parents]:
60
+ candidate_pyproject = each_candidate_directory / pyproject_filename_for_lookup
61
+ if candidate_pyproject.is_file() and _pyproject_contains_tool_mypy(candidate_pyproject):
62
+ return candidate_pyproject
63
+ return None
64
+
65
+
28
66
  def run_mypy_check(files: list[Path]) -> MypyResult:
29
67
  """Run mypy on files."""
30
68
  if not files:
@@ -37,8 +75,16 @@ def run_mypy_check(files: list[Path]) -> MypyResult:
37
75
  if not py_files:
38
76
  return MypyResult(passed=True, output="No Python files", error_count=0)
39
77
 
78
+ config_argument: list[str] = []
79
+ for each_py_file in py_files:
80
+ discovered_pyproject = find_pyproject_with_mypy_config(Path(each_py_file))
81
+ if discovered_pyproject is not None:
82
+ config_argument = ["--config-file", str(discovered_pyproject)]
83
+ break
84
+
40
85
  result = subprocess.run(
41
- ["mypy", "--ignore-missing-imports", "--no-error-summary"] + py_files,
86
+ ["mypy", *config_argument, "--ignore-missing-imports", "--no-error-summary"]
87
+ + py_files,
42
88
  capture_output=True,
43
89
  text=True,
44
90
  )
@@ -14,7 +14,7 @@ import time
14
14
  from dataclasses import dataclass
15
15
  from datetime import datetime
16
16
  from pathlib import Path
17
- from typing import Any, Callable, Dict, List, Optional, Tuple
17
+ from typing import Callable, Dict, List, Optional, Tuple
18
18
 
19
19
  from .health_check import get_system_health, get_validator_version, print_health_report
20
20
  from .mypy_integration import check_mypy_available, run_mypy_check
@@ -179,10 +179,10 @@ def print_header() -> None:
179
179
 
180
180
 
181
181
  def build_json_output(
182
- results: List[Any],
182
+ results: List["ValidatorResult"],
183
183
  metrics: TimingMetrics,
184
184
  include_timing: bool,
185
- ) -> Dict[str, Any]:
185
+ ) -> Dict[str, object]:
186
186
  """Build JSON output dictionary.
187
187
 
188
188
  Args:
@@ -3,7 +3,12 @@
3
3
  from pathlib import Path
4
4
  from unittest.mock import patch
5
5
 
6
- from .mypy_integration import MypyResult, check_mypy_available, run_mypy_check
6
+ from .mypy_integration import (
7
+ MypyResult,
8
+ check_mypy_available,
9
+ find_pyproject_with_mypy_config,
10
+ run_mypy_check,
11
+ )
7
12
 
8
13
 
9
14
  def test_mypy_result_dataclass() -> None:
@@ -25,3 +30,47 @@ def test_run_mypy_check_returns_passed_for_empty_files() -> None:
25
30
  result = run_mypy_check([])
26
31
  assert result.passed is True
27
32
  assert "No files" in result.output
33
+
34
+
35
+ def test_find_pyproject_returns_pyproject_with_tool_mypy(tmp_path: Path) -> None:
36
+ project_root = tmp_path / "myproj"
37
+ nested_dir = project_root / "src" / "deep"
38
+ nested_dir.mkdir(parents=True)
39
+ project_pyproject = project_root / "pyproject.toml"
40
+ project_pyproject.write_text("[tool.mypy]\nignore_missing_imports = true\n")
41
+ target_file = nested_dir / "module.py"
42
+ target_file.write_text("x: int = 1\n")
43
+ found_path = find_pyproject_with_mypy_config(target_file)
44
+ assert found_path == project_pyproject
45
+
46
+
47
+ def test_find_pyproject_skips_pyproject_without_tool_mypy(tmp_path: Path) -> None:
48
+ outer_root = tmp_path / "outer"
49
+ inner_root = outer_root / "inner"
50
+ inner_root.mkdir(parents=True)
51
+ outer_pyproject = outer_root / "pyproject.toml"
52
+ outer_pyproject.write_text("[tool.mypy]\nignore_missing_imports = true\n")
53
+ inner_pyproject = inner_root / "pyproject.toml"
54
+ inner_pyproject.write_text("[project]\nname = 'inner-package'\n")
55
+ target_file = inner_root / "module.py"
56
+ target_file.write_text("y: str = 'hello'\n")
57
+ found_path = find_pyproject_with_mypy_config(target_file)
58
+ assert found_path == outer_pyproject
59
+
60
+
61
+ def test_find_pyproject_returns_none_when_no_match(tmp_path: Path) -> None:
62
+ isolated_dir = tmp_path / "isolated"
63
+ isolated_dir.mkdir()
64
+ target_file = isolated_dir / "module.py"
65
+ target_file.write_text("z: float = 1.0\n")
66
+ assert find_pyproject_with_mypy_config(target_file) is None
67
+
68
+
69
+ def test_find_pyproject_handles_malformed_toml(tmp_path: Path) -> None:
70
+ project_root = tmp_path / "broken"
71
+ project_root.mkdir()
72
+ project_pyproject = project_root / "pyproject.toml"
73
+ project_pyproject.write_text("[tool.mypy\nbroken = true\n")
74
+ target_file = project_root / "module.py"
75
+ target_file.write_text("a = 1\n")
76
+ assert find_pyproject_with_mypy_config(target_file) is None
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env python3
2
+ """PostToolUse hook: auto-publish HTML files marked with the doc-gist sentinel.
3
+
4
+ When Claude writes an .html file containing the marker
5
+ `<!-- @publish-as-gist -->`, this hook invokes the doc-gist `gist_upload.py`
6
+ script against that file and prints the resulting gist + htmlpreview URLs
7
+ into the harness output so Claude can quote them back to the user.
8
+
9
+ The marker is the on-demand trigger: HTML files without it are ignored. This
10
+ keeps the hook silent for HTML that is part of code (React components, test
11
+ fixtures, scraped pages) and active only for HTML Claude intentionally
12
+ designed as a shareable artifact.
13
+
14
+ The hook does not modify the file, does not block the write, and exits 0
15
+ even on upload failure (failure is logged to stderr but does not break
16
+ Claude's flow).
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import json
22
+ import logging
23
+ import subprocess
24
+ import sys
25
+ from pathlib import Path
26
+ from typing import IO
27
+
28
+
29
+ logging.basicConfig(stream=sys.stderr, level=logging.WARNING, format="%(message)s")
30
+
31
+ _hooks_directory = str(Path(__file__).resolve().parent.parent)
32
+ if _hooks_directory not in sys.path:
33
+ sys.path.insert(0, _hooks_directory)
34
+
35
+ from config.doc_gist_auto_publish_constants import ( # noqa: E402
36
+ ALL_TARGET_TOOL_NAMES,
37
+ HOOK_SUBPROCESS_TIMEOUT_SECONDS,
38
+ HTML_FILE_EXTENSION,
39
+ PUBLISH_SENTINEL,
40
+ UPLOAD_SCRIPT_RELATIVE_PATH,
41
+ )
42
+
43
+
44
+ def _read_hook_payload() -> dict[str, object]:
45
+ """Read the PostToolUse JSON payload from stdin. Empty/invalid payload exits clean."""
46
+ try:
47
+ payload = json.load(sys.stdin)
48
+ except json.JSONDecodeError as decode_error:
49
+ logging.warning("doc_gist_auto_publish: invalid JSON payload: %s", decode_error)
50
+ sys.exit(0)
51
+ if not isinstance(payload, dict):
52
+ sys.exit(0)
53
+ return payload
54
+
55
+
56
+ def _resolve_target_path(payload: dict[str, object]) -> Path | None:
57
+ """Extract a writable .html file path from the hook payload, or None to skip."""
58
+ if payload.get("tool_name") not in ALL_TARGET_TOOL_NAMES:
59
+ return None
60
+ tool_input = payload.get("tool_input", {})
61
+ if not isinstance(tool_input, dict):
62
+ return None
63
+ raw_path = tool_input.get("file_path", "")
64
+ if not isinstance(raw_path, str) or not raw_path.lower().endswith(HTML_FILE_EXTENSION):
65
+ return None
66
+ candidate = Path(raw_path)
67
+ if not candidate.is_file():
68
+ return None
69
+ return candidate
70
+
71
+
72
+ def _has_publish_sentinel(target_path: Path) -> bool:
73
+ """Read the HTML and check for the publish marker."""
74
+ try:
75
+ contents = target_path.read_text(encoding="utf-8", errors="replace")
76
+ except OSError:
77
+ logging.warning("doc_gist_auto_publish: cannot read %s", target_path)
78
+ return False
79
+ return PUBLISH_SENTINEL in contents
80
+
81
+
82
+ def _resolve_upload_script() -> Path:
83
+ """Locate the gist_upload.py script bundled with the doc-gist skill."""
84
+ plugin_root_argument = sys.argv[1] if len(sys.argv) > 1 else None
85
+ if plugin_root_argument:
86
+ return Path(plugin_root_argument) / UPLOAD_SCRIPT_RELATIVE_PATH
87
+ plugin_root_directory = Path(__file__).resolve().parent.parent.parent
88
+ return plugin_root_directory / UPLOAD_SCRIPT_RELATIVE_PATH
89
+
90
+
91
+ def _invoke_upload(
92
+ script_path: Path,
93
+ target_path: Path,
94
+ out_stream: IO[str],
95
+ err_stream: IO[str],
96
+ ) -> None:
97
+ """Run gist_upload.py against target_path and surface its URLs to the harness."""
98
+ try:
99
+ completed = subprocess.run(
100
+ [sys.executable, str(script_path), "--input", str(target_path), "--no-open"],
101
+ check=False,
102
+ capture_output=True,
103
+ text=True,
104
+ encoding="utf-8",
105
+ errors="replace",
106
+ timeout=HOOK_SUBPROCESS_TIMEOUT_SECONDS,
107
+ )
108
+ except FileNotFoundError:
109
+ logging.warning("doc_gist_auto_publish: gist_upload script not found: %s", script_path)
110
+ return
111
+ except subprocess.TimeoutExpired:
112
+ logging.warning("doc_gist_auto_publish: gist_upload timed out after %ds", HOOK_SUBPROCESS_TIMEOUT_SECONDS)
113
+ return
114
+ if completed.stderr:
115
+ err_stream.write(completed.stderr)
116
+ if completed.returncode != 0:
117
+ logging.warning("doc_gist_auto_publish: gist_upload exited %d", completed.returncode)
118
+ return
119
+ if completed.stdout:
120
+ out_stream.write(completed.stdout)
121
+
122
+
123
+ def main() -> None:
124
+ """Entry point — process one PostToolUse event, exit 0 always.
125
+
126
+ Returns:
127
+ None — exits 0 on success or when no action is needed.
128
+ """
129
+ payload = _read_hook_payload()
130
+ target_path = _resolve_target_path(payload)
131
+ if target_path is None:
132
+ sys.exit(0)
133
+ if not _has_publish_sentinel(target_path):
134
+ sys.exit(0)
135
+ script_path = _resolve_upload_script()
136
+ if not script_path.is_file():
137
+ logging.warning("doc_gist_auto_publish: missing %s", script_path)
138
+ sys.exit(0)
139
+ _invoke_upload(script_path, target_path, sys.stdout, sys.stderr)
140
+ sys.exit(0)
141
+
142
+
143
+ if __name__ == "__main__":
144
+ main()