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,317 @@
1
+ """Tests for md_to_html_blocker hook."""
2
+
3
+ import importlib
4
+ import json
5
+ import os
6
+ import subprocess
7
+ import sys
8
+
9
+
10
+ HOOK_SCRIPT_PATH = os.path.join(os.path.dirname(__file__), "md_to_html_blocker.py")
11
+
12
+
13
+ class _RunHook:
14
+ def __call__(self, tool_name: str, tool_input: dict) -> subprocess.CompletedProcess:
15
+ payload = json.dumps({"tool_name": tool_name, "tool_input": tool_input})
16
+ return subprocess.run(
17
+ [sys.executable, HOOK_SCRIPT_PATH],
18
+ input=payload,
19
+ capture_output=True,
20
+ text=True,
21
+ check=False,
22
+ )
23
+
24
+
25
+ _run_hook = _RunHook()
26
+
27
+
28
+ def test_exempt_root_filenames_are_module_constant():
29
+ """Exempt root filenames should be a module-level constant, not inline in the function body."""
30
+ hook_dir = os.path.dirname(HOOK_SCRIPT_PATH)
31
+ if hook_dir not in sys.path:
32
+ sys.path.insert(0, hook_dir)
33
+
34
+ blocker_module = importlib.import_module("md_to_html_blocker")
35
+ importlib.reload(blocker_module)
36
+
37
+ assert hasattr(blocker_module, "_exempt_root_filenames")
38
+ assert "readme.md" in blocker_module._exempt_root_filenames
39
+ assert "changelog.md" in blocker_module._exempt_root_filenames
40
+
41
+
42
+ def test_blocks_write_md_file():
43
+ result = _run_hook(
44
+ "Write",
45
+ {"file_path": "docs/guide.md", "content": "# Hello"},
46
+ )
47
+ assert result.returncode == 0
48
+ output = json.loads(result.stdout)
49
+ assert output["hookSpecificOutput"]["permissionDecision"] == "deny"
50
+
51
+
52
+ def test_blocks_edit_md_file():
53
+ result = _run_hook(
54
+ "Edit",
55
+ {"file_path": "docs/guide.md", "old_string": "a", "new_string": "b"},
56
+ )
57
+ assert result.returncode == 0
58
+ output = json.loads(result.stdout)
59
+ assert output["hookSpecificOutput"]["permissionDecision"] == "deny"
60
+
61
+
62
+ def test_blocks_uppercase_md_extension():
63
+ result = _run_hook(
64
+ "Write",
65
+ {"file_path": "DOCS/GUIDE.MD", "content": "# Hello"},
66
+ )
67
+ assert result.returncode == 0
68
+ output = json.loads(result.stdout)
69
+ assert output["hookSpecificOutput"]["permissionDecision"] == "deny"
70
+
71
+
72
+ def test_passes_html_file():
73
+ result = _run_hook(
74
+ "Write",
75
+ {"file_path": "docs/guide.html", "content": "<h1>Hello</h1>"},
76
+ )
77
+ assert result.returncode == 0
78
+ assert result.stdout == ""
79
+
80
+
81
+ def test_passes_non_markdown_extension():
82
+ result = _run_hook(
83
+ "Write",
84
+ {"file_path": "src/main.py", "content": "x = 1"},
85
+ )
86
+ assert result.returncode == 0
87
+ assert result.stdout == ""
88
+
89
+
90
+ def test_passes_claude_dir():
91
+ result = _run_hook(
92
+ "Write",
93
+ {"file_path": ".claude/rules/foo.md", "content": "# Rule"},
94
+ )
95
+ assert result.returncode == 0
96
+ assert result.stdout == ""
97
+
98
+
99
+ def test_passes_nested_claude_dir():
100
+ result = _run_hook(
101
+ "Write",
102
+ {"file_path": "notes/.claude/plans/plan.md", "content": "# Plan"},
103
+ )
104
+ assert result.returncode == 0
105
+ assert result.stdout == ""
106
+
107
+
108
+ def test_passes_readme_at_root():
109
+ result = _run_hook(
110
+ "Write",
111
+ {"file_path": "README.md", "content": "# README"},
112
+ )
113
+ assert result.returncode == 0
114
+ assert result.stdout == ""
115
+
116
+
117
+ def test_passes_changelog_at_root():
118
+ result = _run_hook(
119
+ "Write",
120
+ {"file_path": "CHANGELOG.md", "content": "# Changelog"},
121
+ )
122
+ assert result.returncode == 0
123
+ assert result.stdout == ""
124
+
125
+
126
+ def test_blocks_readme_not_at_root():
127
+ result = _run_hook(
128
+ "Write",
129
+ {"file_path": "docs/README.md", "content": "# README"},
130
+ )
131
+ assert result.returncode == 0
132
+ output = json.loads(result.stdout)
133
+ assert output["hookSpecificOutput"]["permissionDecision"] == "deny"
134
+
135
+
136
+ def test_blocks_changelog_not_at_root():
137
+ result = _run_hook(
138
+ "Write",
139
+ {"file_path": "sub/CHANGELOG.md", "content": "# Log"},
140
+ )
141
+ assert result.returncode == 0
142
+ output = json.loads(result.stdout)
143
+ assert output["hookSpecificOutput"]["permissionDecision"] == "deny"
144
+
145
+
146
+ def test_unknown_tool_passes():
147
+ result = _run_hook(
148
+ "Grep",
149
+ {"pattern": "foo", "path": "."},
150
+ )
151
+ assert result.returncode == 0
152
+ assert result.stdout == ""
153
+
154
+
155
+ def test_empty_file_path_passes():
156
+ result = _run_hook(
157
+ "Write",
158
+ {"file_path": "", "content": "# Hello"},
159
+ )
160
+ assert result.returncode == 0
161
+ assert result.stdout == ""
162
+
163
+
164
+ def test_non_dict_stdin_passes():
165
+ payload = json.dumps(["not", "a", "dict"])
166
+ result = subprocess.run(
167
+ [sys.executable, HOOK_SCRIPT_PATH],
168
+ input=payload,
169
+ capture_output=True,
170
+ text=True,
171
+ check=False,
172
+ )
173
+ assert result.returncode == 0
174
+ assert result.stdout == ""
175
+
176
+
177
+ def test_non_string_tool_name_passes():
178
+ payload = json.dumps(
179
+ {"tool_name": 123, "tool_input": {"file_path": "docs/guide.md"}}
180
+ )
181
+ result = subprocess.run(
182
+ [sys.executable, HOOK_SCRIPT_PATH],
183
+ input=payload,
184
+ capture_output=True,
185
+ text=True,
186
+ check=False,
187
+ )
188
+ assert result.returncode == 0
189
+ assert result.stdout == ""
190
+
191
+
192
+ def test_non_dict_tool_input_passes():
193
+ payload = json.dumps({"tool_name": "Write", "tool_input": "not_a_dict"})
194
+ result = subprocess.run(
195
+ [sys.executable, HOOK_SCRIPT_PATH],
196
+ input=payload,
197
+ capture_output=True,
198
+ text=True,
199
+ check=False,
200
+ )
201
+ assert result.returncode == 0
202
+ assert result.stdout == ""
203
+
204
+
205
+ def test_denial_has_system_message():
206
+ result = _run_hook(
207
+ "Write",
208
+ {"file_path": "docs/guide.md", "content": "# Hello"},
209
+ )
210
+ assert result.returncode == 0
211
+ output = json.loads(result.stdout)
212
+ assert output["suppressOutput"] is True
213
+ assert isinstance(output["systemMessage"], str)
214
+ assert len(output["systemMessage"]) > 0
215
+
216
+
217
+ def test_denial_has_additional_context():
218
+ result = _run_hook(
219
+ "Write",
220
+ {"file_path": "docs/guide.md", "content": "# Hello"},
221
+ )
222
+ assert result.returncode == 0
223
+ output = json.loads(result.stdout)
224
+ ctx = output["hookSpecificOutput"].get("additionalContext", "")
225
+ assert "HTML" in ctx
226
+ assert (
227
+ "thariqs.github.io" in output["hookSpecificOutput"]["permissionDecisionReason"]
228
+ )
229
+
230
+
231
+ def test_denial_reason_mentions_html_redirect():
232
+ result = _run_hook(
233
+ "Write",
234
+ {"file_path": "docs/guide.md", "content": "# Hello"},
235
+ )
236
+ assert result.returncode == 0
237
+ output = json.loads(result.stdout)
238
+ reason = output["hookSpecificOutput"]["permissionDecisionReason"]
239
+ assert ".html" in reason.lower()
240
+
241
+
242
+ def test_passes_claude_md_file():
243
+ result = _run_hook(
244
+ "Write",
245
+ {"file_path": ".claude/CLAUDE.md", "content": "# CLAUDE.md"},
246
+ )
247
+ assert result.returncode == 0
248
+ assert result.stdout == ""
249
+
250
+
251
+ def test_blocks_windows_path_with_backslash():
252
+ result = _run_hook(
253
+ "Write",
254
+ {"file_path": "docs\\guide.md", "content": "# Hello"},
255
+ )
256
+ assert result.returncode == 0
257
+ output = json.loads(result.stdout)
258
+ assert output["hookSpecificOutput"]["permissionDecision"] == "deny"
259
+
260
+
261
+ def test_passes_windows_path_claude_exempt():
262
+ result = _run_hook(
263
+ "Write",
264
+ {"file_path": "project\\.claude\\rules\\foo.md", "content": "# Rule"},
265
+ )
266
+ assert result.returncode == 0
267
+ assert result.stdout == ""
268
+
269
+
270
+ def test_passes_claude_dir_case_insensitive():
271
+ result = _run_hook(
272
+ "Write",
273
+ {"file_path": ".Claude/rules/foo.md", "content": "# Rule"},
274
+ )
275
+ assert result.returncode == 0
276
+ assert result.stdout == ""
277
+
278
+
279
+ def test_passes_readme_lowercase_at_root():
280
+ result = _run_hook(
281
+ "Write",
282
+ {"file_path": "readme.md", "content": "# readme"},
283
+ )
284
+ assert result.returncode == 0
285
+ assert result.stdout == ""
286
+
287
+
288
+ def test_json_decode_error_passes():
289
+ result = subprocess.run(
290
+ [sys.executable, HOOK_SCRIPT_PATH],
291
+ input="not json",
292
+ capture_output=True,
293
+ text=True,
294
+ check=False,
295
+ )
296
+ assert result.returncode == 0
297
+ assert result.stdout == ""
298
+
299
+
300
+ def test_blocks_claude_path_traversal_bypass():
301
+ result = _run_hook(
302
+ "Write",
303
+ {"file_path": ".claude/../docs/guide.md", "content": "# Bypass"},
304
+ )
305
+ assert result.returncode == 0
306
+ output = json.loads(result.stdout)
307
+ assert output["hookSpecificOutput"]["permissionDecision"] == "deny"
308
+
309
+
310
+ def test_blocks_md_with_curly_braces_in_path():
311
+ result = _run_hook(
312
+ "Write",
313
+ {"file_path": "docs/{template}.md", "content": "# Template"},
314
+ )
315
+ assert result.returncode == 0
316
+ output = json.loads(result.stdout)
317
+ assert output["hookSpecificOutput"]["permissionDecision"] == "deny"
@@ -0,0 +1,7 @@
1
+ """Configuration constants for the Any/cast escape-hatch check."""
2
+
3
+ ALL_ANY_ALLOWED_PATTERNS: tuple[str, ...] = (
4
+ "__init__.py",
5
+ "protocols.py",
6
+ "types.py",
7
+ )
@@ -9,10 +9,21 @@ ALL_BANNED_IDENTIFIERS: frozenset[str] = frozenset(
9
9
  "value",
10
10
  "item",
11
11
  "temp",
12
+ "tmp",
12
13
  "argv",
13
14
  "args",
14
15
  "kwargs",
15
16
  "argc",
17
+ "rc",
18
+ "cfg",
19
+ "ctx",
20
+ "cnt",
21
+ "btn",
22
+ "idx",
23
+ "tmp",
24
+ "msg",
25
+ "elem",
26
+ "val",
16
27
  }
17
28
  )
18
29
  MAX_BANNED_IDENTIFIER_ISSUES: int = 3
@@ -0,0 +1,38 @@
1
+ """Caps and lookup sets for the new B-series blocking checks in code_rules_enforcer.py.
2
+
3
+ Each constant is consumed by exactly one check function in the enforcer. They
4
+ live here (not at module scope of the enforcer) so the enforcer file stays
5
+ under the file-global-constants use-count rule (CODE_RULES §file-global-constants).
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+
11
+ MAX_BANNED_PREFIX_ISSUES: int = 3
12
+ MAX_STUB_IMPLEMENTATION_ISSUES: int = 3
13
+ MAX_TYPED_DICT_PAIR_ISSUES: int = 3
14
+ MAX_TEST_BRANCHING_ISSUES: int = 3
15
+ MAX_BARE_EXCEPT_ISSUES: int = 3
16
+ MAX_BOUNDARY_TYPE_ISSUES: int = 5
17
+ ALL_BANNED_PREFIX_NAMES: tuple[str, ...] = ("handle_", "process_", "manage_", "do_")
18
+ MAX_DOCSTRING_FORMAT_ISSUES: int = 5
19
+ MAX_TYPE_ESCAPE_HATCH_ISSUES: int = 5
20
+ MAX_THIN_WRAPPER_ISSUES: int = 1
21
+ DOCSTRING_TRIVIAL_FUNCTION_BODY_LINE_LIMIT: int = 3
22
+
23
+ ALL_BARE_EXCEPT_BANNED_HANDLER_NAMES: frozenset[str] = frozenset({"Exception", "BaseException"})
24
+ ALL_BOUNDARY_TYPE_EXEMPT_FILENAMES: frozenset[str] = frozenset({"protocols.py", "types.py"})
25
+ ALL_DOCSTRING_IMPLICIT_INSTANCE_PARAMETER_NAMES: frozenset[str] = frozenset({"self", "cls"})
26
+ ALL_DOCSTRING_EXEMPT_DECORATOR_NAMES: frozenset[str] = frozenset(
27
+ {"property", "abstractmethod", "abstractproperty", "abc.abstractmethod", "overload"}
28
+ )
29
+ ALL_TEST_INDICATING_ENVIRONMENT_VARIABLE_NAMES: frozenset[str] = frozenset(
30
+ {
31
+ "TESTING",
32
+ "PYTEST_CURRENT_TEST",
33
+ "TEST_MODE",
34
+ "IS_TEST",
35
+ "IS_TESTING",
36
+ "UNIT_TEST",
37
+ }
38
+ )
@@ -0,0 +1,20 @@
1
+ """Configuration constants for the bot_mention_comment_blocker PreToolUse hook."""
2
+
3
+ TOOL_NAME: str = "mcp__plugin_github_github__add_issue_comment"
4
+
5
+ CURSOR_MENTION_TOKEN: str = "@cursor"
6
+ COPILOT_MENTION_TOKEN: str = "@copilot"
7
+
8
+ CORRECTIVE_MESSAGE_CURSOR: str = (
9
+ "BLOCKED [bot-mention]: Invalid comment. "
10
+ "Post exactly ``bugbot run`` with no other text as your issue comment "
11
+ "to trigger Bugbot."
12
+ )
13
+
14
+ CORRECTIVE_MESSAGE_COPILOT: str = (
15
+ "BLOCKED [bot-mention]: Invalid comment. "
16
+ "To request a Copilot review, use the GitHub REST API:\n"
17
+ " gh api --method POST repos/<owner>/<repo>/pulls/<number>/requested_reviewers \\\n"
18
+ " -f 'reviewers[]=copilot-pull-request-reviewer[bot]'\n"
19
+ "See ~/.claude/skills/pr-converge/reference/convergence-gates.md."
20
+ )
@@ -0,0 +1,53 @@
1
+ """Constants for code_rules_enforcer.py.
2
+
3
+ Extracted from code_rules_enforcer.py to satisfy the constants-location rule.
4
+ """
5
+
6
+ import re
7
+
8
+ ALL_PYTHON_EXTENSIONS = {".py"}
9
+ ALL_JAVASCRIPT_EXTENSIONS = {".js", ".ts", ".tsx", ".jsx"}
10
+ ALL_CODE_EXTENSIONS = ALL_PYTHON_EXTENSIONS | ALL_JAVASCRIPT_EXTENSIONS
11
+
12
+ ALL_TEST_PATH_PATTERNS = {"test_", "_test.", ".test.", ".spec.", "/tests/", "\\tests\\", "/tests.py", "\\tests.py"}
13
+ ALL_HOOK_INFRASTRUCTURE_PATTERNS = {"/.claude/hooks/", "\\.claude\\hooks\\", "\\.claude/hooks/", "/packages/claude-dev-env/hooks/", "\\packages\\claude-dev-env\\hooks\\"}
14
+ ALL_WORKFLOW_REGISTRY_PATTERNS = {"/workflow/", "\\workflow\\", "_tab.py", "/states.py", "\\states.py", "/modules.py", "\\modules.py"}
15
+ ALL_MIGRATION_PATH_PATTERNS = {"/migrations/", "\\migrations\\"}
16
+
17
+ ADVISORY_LINE_THRESHOLD_SOFT = 400
18
+ ADVISORY_LINE_THRESHOLD_HARD = 1000
19
+
20
+ ALL_BOOLEAN_NAME_PREFIXES: tuple[str, ...] = ("is_", "has_", "should_", "can_")
21
+ UPPER_SNAKE_CONSTANT_PATTERN = re.compile(r"^[A-Z][A-Z0-9_]*$")
22
+
23
+
24
+ TYPE_CHECKING_BLOCK_PATTERN = re.compile(r"^(?P<indent>\s*)if\s+(typing\.)?TYPE_CHECKING\s*:\s*$")
25
+ ALL_IMPORT_STATEMENT_PREFIXES: tuple[str, ...] = ("import ", "from ")
26
+ NOT_INSIDE_TYPE_CHECKING_BLOCK = -1
27
+ FILE_GLOBAL_UPPER_SNAKE_PATTERN = re.compile(r"^_?[A-Z][A-Z0-9_]*$")
28
+
29
+ ALL_COLLECTION_TYPE_NAMES: frozenset[str] = frozenset({
30
+ "list", "tuple", "set", "frozenset",
31
+ "Iterable", "Sequence", "Mapping", "MutableMapping", "FrozenSet",
32
+ })
33
+ ALL_SUBSCRIPT_ONLY_COLLECTION_TYPE_NAMES: frozenset[str] = frozenset({"dict"})
34
+ COLLECTION_BY_NAME_PATTERN: re.Pattern[str] = re.compile(r"^[a-z][a-z0-9]*_by_[a-z][a-z0-9_]*$")
35
+ ALL_CLI_FILE_PATH_MARKERS: tuple[str, ...] = ("/scripts/", "\\scripts\\", "_cli.py", "/cli.py", "\\cli.py")
36
+
37
+ LOGGING_FSTRING_PATTERN = re.compile(
38
+ r'\b(?:log_(?:debug|info|warning|error|critical|exception)'
39
+ r'|(?:logger|logging|log)\.(?:debug|info|warning|error|critical|exception))'
40
+ r'\s*\(\s*(?:[rR][fF]|[fF][rR]?)["\']'
41
+ )
42
+ ALL_BUILTIN_DICT_METHOD_NAMES: frozenset[str] = frozenset({
43
+ "get", "items", "keys", "values", "update", "pop",
44
+ "setdefault", "copy", "clear",
45
+ })
46
+ ALL_UNION_TYPING_NAMES: frozenset[str] = frozenset({"Optional", "Union"})
47
+ ALL_SELF_AND_CLS_PARAMETER_NAMES: frozenset[str] = frozenset({"self", "cls"})
48
+ ALL_LOOP_INDEX_LETTER_EXEMPTIONS: frozenset[str] = frozenset({"i", "j", "k", "_"})
49
+ EACH_PREFIX = "each_"
50
+ BARE_EACH_TOKEN = "each"
51
+ INLINE_COLLECTION_MIN_LENGTH = 3
52
+ ALL_CAPS_WITH_UNDERSCORE_PATTERN = re.compile(r"^[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)+$")
53
+ DOTTED_SEGMENT_PATTERN = re.compile(r"^\.[a-z][a-z0-9_]*$")
@@ -0,0 +1,9 @@
1
+ """Convergence branch naming policy constants.
2
+
3
+ Shared by destructive_command_blocker convergence-branch exemptions
4
+ and convergence_gate_blocker pre-flight checks.
5
+ """
6
+
7
+ ALL_CONVERGENCE_BRANCH_PREFIXES: tuple[str, ...] = ("claude/", "worktree-")
8
+ CONVERGENCE_BRANCH_SUFFIX_PATTERN: str = r"pr-.*-converge$"
9
+ CONVERGENCE_FORCE_PUSH_DETECTION_PATTERN: str = r"\bgit\s+push\b\s+(.*(?:--force|-f)\b.*)"
@@ -0,0 +1,18 @@
1
+ """Constants for the doc-gist auto-publish PostToolUse hook.
2
+
3
+ PUBLISH_SENTINEL: HTML comment marker. Claude includes it in HTML it intends to share;
4
+ absent in HTML that is part of code, tests, or fixtures. Hook is a no-op without it.
5
+
6
+ HTML_FILE_EXTENSION: only files ending in this extension are candidates for the hook;
7
+ Markdown, source files, etc. are skipped.
8
+
9
+ ALL_TARGET_TOOL_NAMES: tool names the hook fires after. The hook is a PostToolUse
10
+ listener; only Write and Edit produce a writable file path that the marker check
11
+ can be applied to.
12
+ """
13
+
14
+ PUBLISH_SENTINEL = "<!-- @publish-as-gist -->"
15
+ HTML_FILE_EXTENSION = ".html"
16
+ ALL_TARGET_TOOL_NAMES = ("Write", "Edit")
17
+ HOOK_SUBPROCESS_TIMEOUT_SECONDS = 50
18
+ UPLOAD_SCRIPT_RELATIVE_PATH = "skills/doc-gist/scripts/gist_upload.py"
@@ -0,0 +1,20 @@
1
+ """CSS theme constants and URL scheme denylist for md-to-html companion hook."""
2
+
3
+ CSS_BG_COLOR = "13, 17, 23"
4
+ CSS_FG_COLOR = "201, 209, 217"
5
+ CSS_BORDER_COLOR = "48, 54, 61"
6
+ CSS_ACCENT_COLOR = "88, 166, 255"
7
+ CSS_MUTED_COLOR = "139, 148, 158"
8
+ CSS_SURFACE_COLOR = "22, 27, 34"
9
+ CSS_STRONG_COLOR = "240, 246, 252"
10
+ CSS_LINE_HEIGHT = "1.6"
11
+ CSS_BODY_PADDING = "2rem"
12
+ CSS_MAX_WIDTH = "960px"
13
+ CSS_H1_SIZE = "1.6rem"
14
+ CSS_H2_SIZE = "1.25rem"
15
+ CSS_H3_SIZE = "1.1rem"
16
+ CSS_CODE_SIZE = "0.85rem"
17
+ CSS_TABLE_WIDTH = "100%"
18
+ CSS_TH_WEIGHT = "600"
19
+
20
+ BLOCKED_URL_SCHEMES = frozenset({"javascript", "data"})
@@ -0,0 +1,22 @@
1
+ """Constants for the inline-tuple snake_case-string-magic check in code_rules_enforcer.
2
+
3
+ Mirrors the column-name/key heuristic previously held only in
4
+ ``_shared/pr-loop/scripts/code_rules_gate.py`` so the Write/Edit hook can
5
+ catch the same pattern that the commit-time gate caught.
6
+ """
7
+
8
+ EXPECTED_TUPLE_PAIR_LENGTH: int = 2
9
+
10
+ MAX_INLINE_TUPLE_STRING_MAGIC_ISSUES: int = 3
11
+
12
+ MINIMUM_SNAKE_CASE_LENGTH_AFTER_FIRST_CHAR: int = 2
13
+
14
+ SNAKE_CASE_LITERAL_PATTERN: str = (
15
+ r"^[a-z][a-z0-9_]{" + str(MINIMUM_SNAKE_CASE_LENGTH_AFTER_FIRST_CHAR) + r",}$"
16
+ )
17
+
18
+ ALL_SNAKE_CASE_KEYWORD_EXEMPTIONS: frozenset[str] = frozenset(
19
+ {"true", "false", "none", "null"}
20
+ )
21
+
22
+ INLINE_TUPLE_STRING_MAGIC_MESSAGE_SUFFIX: str = "extract to config"
@@ -34,6 +34,23 @@ def test_all_banned_identifiers_includes_canonical_offenders() -> None:
34
34
  assert canonical_offenders <= ALL_BANNED_IDENTIFIERS
35
35
 
36
36
 
37
+ def test_all_banned_identifiers_includes_abbreviation_offenders() -> None:
38
+ abbreviation_offenders = {
39
+ "rc",
40
+ "cfg",
41
+ "ctx",
42
+ "cnt",
43
+ "btn",
44
+ "idx",
45
+ "cnt",
46
+ "tmp",
47
+ "msg",
48
+ "elem",
49
+ "val",
50
+ }
51
+ assert abbreviation_offenders <= ALL_BANNED_IDENTIFIERS
52
+
53
+
37
54
  def test_max_banned_identifier_issues_is_positive_cap() -> None:
38
55
  assert MAX_BANNED_IDENTIFIER_ISSUES > 0
39
56
 
package/hooks/hooks.json CHANGED
@@ -15,6 +15,11 @@
15
15
  {
16
16
  "matcher": "Write|Edit",
17
17
  "hooks": [
18
+ {
19
+ "type": "command",
20
+ "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/blocking/md_to_html_blocker.py",
21
+ "timeout": 10
22
+ },
18
23
  {
19
24
  "type": "command",
20
25
  "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/blocking/write_existing_file_blocker.py",
@@ -85,6 +90,11 @@
85
90
  "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/blocking/destructive_command_blocker.py",
86
91
  "timeout": 10
87
92
  },
93
+ {
94
+ "type": "command",
95
+ "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/blocking/gh_body_arg_blocker.py",
96
+ "timeout": 10
97
+ },
88
98
  {
89
99
  "type": "command",
90
100
  "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/blocking/block_main_commit.py",
@@ -100,6 +110,11 @@
100
110
  "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/blocking/test_preflight_check.py",
101
111
  "timeout": 10
102
112
  },
113
+ {
114
+ "type": "command",
115
+ "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/blocking/convergence_gate_blocker.py",
116
+ "timeout": 15
117
+ },
103
118
  {
104
119
  "type": "command",
105
120
  "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/blocking/content_search_to_zoekt_redirector.py",
@@ -113,12 +128,12 @@
113
128
  ]
114
129
  },
115
130
  {
116
- "matcher": "AskUserQuestion",
131
+ "matcher": "mcp__plugin_github_github__add_issue_comment",
117
132
  "hooks": [
118
133
  {
119
134
  "type": "command",
120
- "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/notification/attention_needed_notify.py",
121
- "timeout": 15
135
+ "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/blocking/bot_mention_comment_blocker.py",
136
+ "timeout": 10
122
137
  }
123
138
  ]
124
139
  }
@@ -149,11 +164,6 @@
149
164
  {
150
165
  "matcher": "",
151
166
  "hooks": [
152
- {
153
- "type": "command",
154
- "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/notification/attention_needed_notify.py",
155
- "timeout": 15
156
- },
157
167
  {
158
168
  "type": "command",
159
169
  "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/blocking/hedging_language_blocker.py",
@@ -200,6 +210,11 @@
200
210
  {
201
211
  "matcher": "Write|Edit",
202
212
  "hooks": [
213
+ {
214
+ "type": "command",
215
+ "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/workflow/md_to_html_companion.py",
216
+ "timeout": 10
217
+ },
203
218
  {
204
219
  "type": "command",
205
220
  "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/validation/mypy_validator.py",
@@ -209,6 +224,11 @@
209
224
  "type": "command",
210
225
  "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/workflow/auto_formatter.py",
211
226
  "timeout": 30
227
+ },
228
+ {
229
+ "type": "command",
230
+ "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/workflow/doc_gist_auto_publish.py ${CLAUDE_PLUGIN_ROOT}",
231
+ "timeout": 60
212
232
  }
213
233
  ]
214
234
  },
@@ -223,18 +243,6 @@
223
243
  ]
224
244
  }
225
245
  ],
226
- "Notification": [
227
- {
228
- "matcher": "",
229
- "hooks": [
230
- {
231
- "type": "command",
232
- "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/notification/claude_notification_handler.py",
233
- "timeout": 15
234
- }
235
- ]
236
- }
237
- ],
238
246
  "InstructionsLoaded": [
239
247
  {
240
248
  "matcher": "session_start|nested_traversal|path_glob_match|include|compact",