claude-dev-env 2.13.0 → 2.14.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 (241) hide show
  1. package/AGENTS.md +61 -0
  2. package/CLAUDE.md +1 -57
  3. package/_shared/AGENTS.md +15 -0
  4. package/_shared/CLAUDE.md +1 -15
  5. package/_shared/advisor/AGENTS.md +22 -0
  6. package/_shared/advisor/CLAUDE.md +1 -22
  7. package/_shared/pr-loop/AGENTS.md +27 -0
  8. package/_shared/pr-loop/CLAUDE.md +1 -27
  9. package/_shared/pr-loop/scripts/AGENTS.md +35 -0
  10. package/_shared/pr-loop/scripts/CLAUDE.md +1 -35
  11. package/_shared/pr-loop/scripts/code_rules_gate_parts/AGENTS.md +42 -0
  12. package/_shared/pr-loop/scripts/code_rules_gate_parts/CLAUDE.md +1 -42
  13. package/_shared/pr-loop/scripts/pr_loop_shared_constants/AGENTS.md +26 -0
  14. package/_shared/pr-loop/scripts/pr_loop_shared_constants/CLAUDE.md +1 -26
  15. package/_shared/pr-loop/scripts/tests/AGENTS.md +44 -0
  16. package/_shared/pr-loop/scripts/tests/CLAUDE.md +1 -44
  17. package/_shared/process-tree/AGENTS.md +41 -0
  18. package/_shared/process-tree/CLAUDE.md +1 -41
  19. package/agents/AGENTS.md +31 -0
  20. package/agents/CLAUDE.md +1 -31
  21. package/agents/clean-coder.md +6 -6
  22. package/agents/test_agent_frontmatter.py +4 -4
  23. package/audit-rubrics/AGENTS.md +43 -0
  24. package/audit-rubrics/CLAUDE.md +1 -43
  25. package/audit-rubrics/category_rubrics/AGENTS.md +37 -0
  26. package/audit-rubrics/category_rubrics/CLAUDE.md +1 -37
  27. package/audit-rubrics/prompts/AGENTS.md +37 -0
  28. package/audit-rubrics/prompts/CLAUDE.md +1 -37
  29. package/bin/AGENTS.md +112 -0
  30. package/bin/CLAUDE.md +1 -112
  31. package/bin/ever-shipped-skills.mjs +1 -0
  32. package/bin/install-constants.mjs +6 -7
  33. package/bin/install.mjs +24 -5
  34. package/bin/install.test.mjs +34 -0
  35. package/commands/AGENTS.md +23 -0
  36. package/commands/CLAUDE.md +1 -23
  37. package/commands/hook-log-extract.md +3 -3
  38. package/docs/AGENTS.md +32 -0
  39. package/docs/CLAUDE.md +1 -32
  40. package/docs/CODE_RULES.md +1 -1
  41. package/docs/imagegen.md +22 -0
  42. package/docs/references/AGENTS.md +17 -0
  43. package/docs/references/CLAUDE.md +1 -17
  44. package/hooks/AGENTS.md +31 -0
  45. package/hooks/CLAUDE.md +1 -31
  46. package/hooks/_gh_pr_author_swap_utils.py +14 -0
  47. package/hooks/advisory/AGENTS.md +16 -0
  48. package/hooks/advisory/CLAUDE.md +1 -16
  49. package/hooks/blocking/AGENTS.md +123 -0
  50. package/hooks/blocking/CLAUDE.md +1 -123
  51. package/hooks/blocking/claude_md_orphan_file_blocker_parts/AGENTS.md +28 -0
  52. package/hooks/blocking/claude_md_orphan_file_blocker_parts/CLAUDE.md +1 -28
  53. package/hooks/blocking/code_rules_banned_identifiers.py +1 -1
  54. package/hooks/blocking/code_rules_dead_module_constant.py +149 -37
  55. package/hooks/blocking/code_rules_probe_chains.py +6 -2
  56. package/hooks/blocking/config/AGENTS.md +10 -0
  57. package/hooks/blocking/config/CLAUDE.md +1 -10
  58. package/hooks/blocking/gh_pr_author_restore.py +7 -2
  59. package/hooks/blocking/intent_only_ending_blocker.py +11 -16
  60. package/hooks/blocking/inventory_intent_records/AGENTS.md +26 -0
  61. package/hooks/blocking/inventory_intent_records/CLAUDE.md +1 -26
  62. package/hooks/blocking/package_inventory_stale_blocker_parts/AGENTS.md +26 -0
  63. package/hooks/blocking/package_inventory_stale_blocker_parts/CLAUDE.md +1 -26
  64. package/hooks/blocking/pii_prevention_blocker_parts/AGENTS.md +24 -0
  65. package/hooks/blocking/pii_prevention_blocker_parts/CLAUDE.md +1 -24
  66. package/hooks/blocking/tdd_enforcer_parts/AGENTS.md +30 -0
  67. package/hooks/blocking/tdd_enforcer_parts/CLAUDE.md +1 -30
  68. package/hooks/blocking/test_code_rules_enforcer_banned_prefixes.py +1 -1
  69. package/hooks/blocking/test_code_rules_enforcer_dead_module_constant.py +171 -0
  70. package/hooks/blocking/test_code_rules_enforcer_magic_allowlist.py +1 -1
  71. package/hooks/blocking/test_gh_pr_author_restore.py +2 -1
  72. package/hooks/blocking/test_intent_only_ending_blocker.py +4 -0
  73. package/hooks/diagnostic/AGENTS.md +43 -0
  74. package/hooks/diagnostic/CLAUDE.md +1 -43
  75. package/hooks/diagnostic/hook_log_extractor.py +2 -2
  76. package/hooks/diagnostic/migrations/AGENTS.md +16 -0
  77. package/hooks/diagnostic/migrations/CLAUDE.md +1 -16
  78. package/hooks/diagnostic/queries/AGENTS.md +19 -0
  79. package/hooks/diagnostic/queries/CLAUDE.md +1 -19
  80. package/hooks/diagnostic/test_hook_log_extractor.py +2 -2
  81. package/hooks/git-hooks/AGENTS.md +31 -0
  82. package/hooks/git-hooks/CLAUDE.md +1 -31
  83. package/hooks/git-hooks/git_hooks_constants/AGENTS.md +21 -0
  84. package/hooks/git-hooks/git_hooks_constants/CLAUDE.md +1 -21
  85. package/hooks/git-hooks/git_hooks_constants/__init__.py +4 -6
  86. package/hooks/git-hooks/pre_push.py +47 -34
  87. package/hooks/git-hooks/test_pre_push.py +111 -7
  88. package/hooks/hooks_constants/AGENTS.md +104 -0
  89. package/hooks/hooks_constants/CLAUDE.md +1 -104
  90. package/hooks/hooks_constants/hook_log_extractor_constants.py +1 -2
  91. package/hooks/hooks_constants/task_list_loop_starter_constants.py +4 -3
  92. package/hooks/hooks_constants/working_style_prompt_constants.py +29 -20
  93. package/hooks/lifecycle/AGENTS.md +18 -0
  94. package/hooks/lifecycle/CLAUDE.md +1 -18
  95. package/hooks/observability/AGENTS.md +20 -0
  96. package/hooks/observability/CLAUDE.md +1 -20
  97. package/hooks/session/AGENTS.md +33 -0
  98. package/hooks/session/CLAUDE.md +1 -33
  99. package/hooks/session/gh_pr_author_session_cleanup.py +7 -2
  100. package/hooks/session/task_list_loop_starter.py +3 -3
  101. package/hooks/session/test_gh_pr_author_session_cleanup.py +2 -1
  102. package/hooks/session/test_task_list_loop_starter.py +10 -2
  103. package/hooks/session/test_working_style_prompt.py +9 -4
  104. package/hooks/validation/AGENTS.md +20 -0
  105. package/hooks/validation/CLAUDE.md +1 -20
  106. package/hooks/validators/AGENTS.md +51 -0
  107. package/hooks/validators/CLAUDE.md +1 -51
  108. package/hooks/validators/health_check.py +52 -19
  109. package/hooks/validators/python_style_checks.py +1 -1
  110. package/hooks/validators/test_health_check.py +112 -27
  111. package/hooks/workflow/AGENTS.md +16 -0
  112. package/hooks/workflow/CLAUDE.md +1 -16
  113. package/installable-surfaces.manifest.json +1 -0
  114. package/output-styles/AGENTS.md +15 -0
  115. package/output-styles/CLAUDE.md +1 -15
  116. package/package.json +2 -1
  117. package/rules/AGENTS.md +61 -0
  118. package/rules/CLAUDE.md +1 -61
  119. package/rules/code-standards.md +5 -9
  120. package/rules/doc-prose-cuts.md +1 -1
  121. package/scripts/AGENTS.md +62 -0
  122. package/scripts/CLAUDE.md +1 -62
  123. package/scripts/codex_compat_materializer.py +4 -0
  124. package/scripts/dev_env_scripts_constants/AGENTS.md +21 -0
  125. package/scripts/dev_env_scripts_constants/CLAUDE.md +1 -21
  126. package/scripts/sync_to_cursor/AGENTS.md +23 -0
  127. package/scripts/sync_to_cursor/CLAUDE.md +1 -23
  128. package/scripts/test_validate_instruction_pairs.py +90 -0
  129. package/scripts/test_verify_installable_package.py +5 -1
  130. package/scripts/tests/AGENTS.md +33 -0
  131. package/scripts/tests/CLAUDE.md +1 -33
  132. package/scripts/tests/test_codex_compat_materializer.py +36 -1
  133. package/scripts/validate_instruction_pairs.py +183 -0
  134. package/skills/AGENTS.md +72 -0
  135. package/skills/CLAUDE.md +1 -72
  136. package/skills/_shared/AGENTS.md +44 -0
  137. package/skills/_shared/CLAUDE.md +1 -44
  138. package/skills/_shared/advisor/AGENTS.md +9 -0
  139. package/skills/_shared/advisor/CLAUDE.md +1 -9
  140. package/skills/_shared/pr-loop/AGENTS.md +50 -0
  141. package/skills/_shared/pr-loop/CLAUDE.md +1 -50
  142. package/skills/_shared/pr-loop/prompts/AGENTS.md +9 -0
  143. package/skills/_shared/pr-loop/prompts/CLAUDE.md +1 -9
  144. package/skills/_shared/pr-loop/scripts/AGENTS.md +35 -0
  145. package/skills/_shared/pr-loop/scripts/CLAUDE.md +1 -35
  146. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/AGENTS.md +24 -0
  147. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/CLAUDE.md +1 -24
  148. package/skills/anthropic-plan/AGENTS.md +34 -0
  149. package/skills/anthropic-plan/CLAUDE.md +1 -34
  150. package/skills/anthropic-plan/scripts/AGENTS.md +11 -0
  151. package/skills/anthropic-plan/scripts/CLAUDE.md +1 -11
  152. package/skills/anthropic-plan/scripts/anthropic_plan_scripts_constants/AGENTS.md +16 -0
  153. package/skills/anthropic-plan/scripts/anthropic_plan_scripts_constants/CLAUDE.md +1 -16
  154. package/skills/anthropic-plan/templates/AGENTS.md +13 -0
  155. package/skills/anthropic-plan/templates/CLAUDE.md +1 -13
  156. package/skills/anthropic-plan/workflow/AGENTS.md +14 -0
  157. package/skills/anthropic-plan/workflow/CLAUDE.md +1 -14
  158. package/skills/auditing-claude-config/AGENTS.md +21 -0
  159. package/skills/auditing-claude-config/CLAUDE.md +1 -21
  160. package/skills/autoconverge/AGENTS.md +36 -0
  161. package/skills/autoconverge/CLAUDE.md +1 -36
  162. package/skills/autoconverge/reference/AGENTS.md +16 -0
  163. package/skills/autoconverge/reference/CLAUDE.md +1 -16
  164. package/skills/autoconverge/workflow/AGENTS.md +26 -0
  165. package/skills/autoconverge/workflow/CLAUDE.md +1 -26
  166. package/skills/autoconverge/workflow/autoconverge_report_constants/AGENTS.md +16 -0
  167. package/skills/autoconverge/workflow/autoconverge_report_constants/CLAUDE.md +1 -16
  168. package/skills/bugteam/AGENTS.md +30 -0
  169. package/skills/bugteam/CLAUDE.md +1 -30
  170. package/skills/bugteam/reference/AGENTS.md +20 -0
  171. package/skills/bugteam/reference/CLAUDE.md +1 -20
  172. package/skills/bugteam/reference/obstacles/AGENTS.md +24 -0
  173. package/skills/bugteam/reference/obstacles/CLAUDE.md +1 -24
  174. package/skills/bugteam/scripts/AGENTS.md +30 -0
  175. package/skills/bugteam/scripts/CLAUDE.md +1 -30
  176. package/skills/bugteam/scripts/bugteam_scripts_constants/AGENTS.md +18 -0
  177. package/skills/bugteam/scripts/bugteam_scripts_constants/CLAUDE.md +1 -18
  178. package/skills/closeout/reference/handoff-prompt-template.md +1 -1
  179. package/skills/codex-review/AGENTS.md +46 -0
  180. package/skills/codex-review/CLAUDE.md +1 -46
  181. package/skills/codex-review/reference/AGENTS.md +15 -0
  182. package/skills/codex-review/reference/CLAUDE.md +1 -15
  183. package/skills/codex-review/scripts/codex_review_scripts_constants/AGENTS.md +18 -0
  184. package/skills/codex-review/scripts/codex_review_scripts_constants/CLAUDE.md +1 -18
  185. package/skills/codex-review/test_skill_scaffold.py +3 -3
  186. package/skills/copilot-review/AGENTS.md +18 -0
  187. package/skills/copilot-review/CLAUDE.md +1 -18
  188. package/skills/everything-search/AGENTS.md +17 -0
  189. package/skills/everything-search/CLAUDE.md +1 -17
  190. package/skills/fresh-branch/AGENTS.md +14 -0
  191. package/skills/fresh-branch/CLAUDE.md +1 -14
  192. package/skills/grok-spawn/AGENTS.md +28 -0
  193. package/skills/grok-spawn/CLAUDE.md +1 -28
  194. package/skills/pr-converge/AGENTS.md +32 -0
  195. package/skills/pr-converge/CLAUDE.md +1 -32
  196. package/skills/pr-converge/pr_converge_skill_constants/AGENTS.md +26 -0
  197. package/skills/pr-converge/pr_converge_skill_constants/CLAUDE.md +1 -26
  198. package/skills/pr-converge/reference/AGENTS.md +28 -0
  199. package/skills/pr-converge/reference/CLAUDE.md +1 -28
  200. package/skills/pr-converge/reference/obstacles/AGENTS.md +23 -0
  201. package/skills/pr-converge/reference/obstacles/CLAUDE.md +1 -23
  202. package/skills/pr-converge/scripts/AGENTS.md +46 -0
  203. package/skills/pr-converge/scripts/CLAUDE.md +1 -46
  204. package/skills/pr-converge/scripts/pr_converge_scripts_constants/AGENTS.md +18 -0
  205. package/skills/pr-converge/scripts/pr_converge_scripts_constants/CLAUDE.md +1 -18
  206. package/skills/pr-converge/workflows/AGENTS.md +16 -0
  207. package/skills/pr-converge/workflows/CLAUDE.md +1 -16
  208. package/skills/prototype/reference/honest-limitations.md +1 -1
  209. package/skills/rebase/AGENTS.md +32 -0
  210. package/skills/rebase/CLAUDE.md +1 -32
  211. package/skills/recall/AGENTS.md +30 -0
  212. package/skills/recall/CLAUDE.md +1 -30
  213. package/skills/remember/AGENTS.md +31 -0
  214. package/skills/remember/CLAUDE.md +1 -31
  215. package/skills/reviews/SKILL.md +1 -1
  216. package/skills/session-log/AGENTS.md +32 -0
  217. package/skills/session-log/CLAUDE.md +1 -32
  218. package/skills/session-tidy/AGENTS.md +36 -0
  219. package/skills/session-tidy/CLAUDE.md +1 -36
  220. package/skills/skill-builder/AGENTS.md +49 -0
  221. package/skills/skill-builder/CLAUDE.md +1 -49
  222. package/skills/skill-builder/references/AGENTS.md +25 -0
  223. package/skills/skill-builder/references/CLAUDE.md +1 -25
  224. package/skills/skill-builder/references/deterministic-elements.md +1 -1
  225. package/skills/skill-builder/templates/AGENTS.md +14 -0
  226. package/skills/skill-builder/templates/CLAUDE.md +1 -14
  227. package/skills/skill-builder/workflows/AGENTS.md +19 -0
  228. package/skills/skill-builder/workflows/CLAUDE.md +1 -19
  229. package/skills/task-build/AGENTS.md +29 -0
  230. package/skills/task-build/CLAUDE.md +1 -29
  231. package/skills/update/AGENTS.md +38 -0
  232. package/skills/update/CLAUDE.md +1 -38
  233. package/system-prompts/AGENTS.md +17 -0
  234. package/system-prompts/CLAUDE.md +1 -17
  235. package/system-prompts/software-engineer.xml +3 -3
  236. package/skills/imagegen/SKILL.md +0 -21
  237. package/skills/imagegen/scripts/config/__init__.py +0 -1
  238. package/skills/imagegen/scripts/config/constants.py +0 -28
  239. package/skills/imagegen/scripts/imagegen.py +0 -45
  240. package/skills/imagegen/scripts/imagegen_core.py +0 -528
  241. package/skills/imagegen/scripts/test_imagegen.py +0 -204
@@ -36,6 +36,7 @@ if hooks_parent_directory not in sys.path:
36
36
  sys.path.insert(0, hooks_parent_directory)
37
37
 
38
38
  from _gh_pr_author_swap_utils import ( # noqa: E402
39
+ _build_restore_failure_message,
39
40
  _command_invokes_gh_pr_create_in_stripped,
40
41
  _delete_state_file,
41
42
  _preprocess_command_for_matching,
@@ -89,8 +90,12 @@ def main() -> None:
89
90
  _delete_state_file(state_file)
90
91
  else:
91
92
  _write_line(
92
- f"[gh-pr-author-restore] failed to restore active gh account to {original_account!r}; "
93
- f"state file {state_file} left in place so the SessionStart cleanup hook can retry",
93
+ _build_restore_failure_message(
94
+ "gh-pr-author-restore",
95
+ original_account,
96
+ state_file,
97
+ "state file",
98
+ ),
94
99
  sys.stderr,
95
100
  )
96
101
  sys.exit(0)
@@ -1,12 +1,10 @@
1
1
  #!/usr/bin/env python3
2
- """
3
- Stop hook that blocks Claude responses ending on a promise about undone work.
2
+ """Stop hook that blocks a turn ending with a promise of future work.
4
3
 
5
- When a turn ends on a forward-looking statement of intent ("I'll now run the
6
- tests", "Let me implement the fix") instead of actually doing the work, the
7
- agent is forced to do the work now with tool calls, or - when genuinely blocked
8
- on input only the user can supply - route through AskUserQuestion and end
9
- cleanly. The rule name is long-horizon-autonomy.
4
+ When a turn ends with a forward-looking statement of intent ("I'll now run the
5
+ tests", "Let me implement the fix"), complete the work with tool calls during
6
+ the turn. Route work requiring user-only input through AskUserQuestion. The
7
+ rule name is long-horizon-autonomy.
10
8
  """
11
9
 
12
10
  import json
@@ -104,7 +102,7 @@ def find_intent_only_ending(text: str) -> bool:
104
102
 
105
103
 
106
104
  def main() -> None:
107
- """Read the stop-hook payload and block turns that end on a promise of undone work."""
105
+ """Read the stop-hook payload and block turns that promise future work."""
108
106
  try:
109
107
  hook_input = json.load(sys.stdin)
110
108
  except json.JSONDecodeError:
@@ -125,14 +123,11 @@ def main() -> None:
125
123
  sys.exit(0)
126
124
 
127
125
  block_reason = (
128
- "LONG-HORIZON-AUTONOMY GUARDRAIL: Your turn ends on a promise about work "
129
- "that is not yet done, rather than doing it. Do the work NOW with tool calls "
130
- "instead of describing what you are about to do.\n\n"
131
- "If the work is genuinely blocked on input only the user can give, route the "
132
- "ask through an AskUserQuestion tool call and end the turn cleanly. Otherwise, "
133
- "carry out the stated action this turn.\n\n"
134
- "You MUST re-output the complete response with the work actually performed, "
135
- "per the long-horizon-autonomy rule."
126
+ "LONG-HORIZON-AUTONOMY GUARDRAIL: This turn ends with a promise of future "
127
+ "work. Complete that work with tool calls during this turn. Route questions "
128
+ "requiring user-only input through AskUserQuestion, then end the turn cleanly.\n\n"
129
+ "Re-output the complete response with the work performed, per the "
130
+ "long-horizon-autonomy rule."
136
131
  )
137
132
  block_response = {
138
133
  "decision": "block",
@@ -0,0 +1,26 @@
1
+ # inventory_intent_records
2
+
3
+ The shared pending-intent store that lets `package_inventory_stale_blocker.py`
4
+ and `claude_md_orphan_file_blocker.py` add a new file and its inventory row in
5
+ one change, in either order. When one blocker denies, it records a note; the
6
+ sibling blocker reads that note to allow the matching second write.
7
+
8
+ ## Modules
9
+
10
+ | File | Purpose |
11
+ |---|---|
12
+ | `records.py` | Read, record, peek, and consume the per-session file and row intents; a missing or corrupt store reads as no notes |
13
+ | `__init__.py` | Package marker |
14
+
15
+ ## Subdirectories
16
+
17
+ | Entry | Description |
18
+ |---|---|
19
+ | `config/` | The records-file name shape, the freshness window, the list keys, and the note field names (`intent_records_constants.py`) |
20
+ | `tests/` | pytest suite for the records store |
21
+
22
+ ## Running tests
23
+
24
+ ```bash
25
+ python -m pytest packages/claude-dev-env/hooks/blocking/inventory_intent_records/tests/
26
+ ```
@@ -1,26 +1 @@
1
- # inventory_intent_records
2
-
3
- The shared pending-intent store that lets `package_inventory_stale_blocker.py`
4
- and `claude_md_orphan_file_blocker.py` add a new file and its inventory row in
5
- one change, in either order. When one blocker denies, it records a note; the
6
- sibling blocker reads that note to allow the matching second write.
7
-
8
- ## Modules
9
-
10
- | File | Purpose |
11
- |---|---|
12
- | `records.py` | Read, record, peek, and consume the per-session file and row intents; a missing or corrupt store reads as no notes |
13
- | `__init__.py` | Package marker |
14
-
15
- ## Subdirectories
16
-
17
- | Entry | Description |
18
- |---|---|
19
- | `config/` | The records-file name shape, the freshness window, the list keys, and the note field names (`intent_records_constants.py`) |
20
- | `tests/` | pytest suite for the records store |
21
-
22
- ## Running tests
23
-
24
- ```bash
25
- python -m pytest packages/claude-dev-env/hooks/blocking/inventory_intent_records/tests/
26
- ```
1
+ @AGENTS.md
@@ -0,0 +1,26 @@
1
+ # package_inventory_stale_blocker_parts
2
+
3
+ The concern modules `package_inventory_stale_blocker.py` wires together to block a
4
+ new production file its package inventory omits. The entry hook imports them and
5
+ re-exports their surface for the test suite.
6
+
7
+ ## Modules
8
+
9
+ | File | Purpose |
10
+ |---|---|
11
+ | `inventory_detection.py` | Surveys a directory's `README.md`/`CLAUDE.md`/`SKILL.md`, collects the filenames they name, and reports whether a maintained inventory omits the file being written |
12
+ | `decision.py` | Builds the deny payload naming the omitted file and closing with the retry hint, and emits the decision JSON |
13
+ | `__init__.py` | Package marker |
14
+
15
+ ## Subdirectories
16
+
17
+ | Entry | Description |
18
+ |---|---|
19
+ | `config/` | The retry hint the deny reason closes with and the inventory-name join separator (`inventory_blocker_constants.py`) |
20
+ | `tests/` | pytest suite with one test module per module above |
21
+
22
+ ## Running tests
23
+
24
+ ```bash
25
+ python -m pytest packages/claude-dev-env/hooks/blocking/package_inventory_stale_blocker_parts/tests/
26
+ ```
@@ -1,26 +1 @@
1
- # package_inventory_stale_blocker_parts
2
-
3
- The concern modules `package_inventory_stale_blocker.py` wires together to block a
4
- new production file its package inventory omits. The entry hook imports them and
5
- re-exports their surface for the test suite.
6
-
7
- ## Modules
8
-
9
- | File | Purpose |
10
- |---|---|
11
- | `inventory_detection.py` | Surveys a directory's `README.md`/`CLAUDE.md`/`SKILL.md`, collects the filenames they name, and reports whether a maintained inventory omits the file being written |
12
- | `decision.py` | Builds the deny payload naming the omitted file and closing with the retry hint, and emits the decision JSON |
13
- | `__init__.py` | Package marker |
14
-
15
- ## Subdirectories
16
-
17
- | Entry | Description |
18
- |---|---|
19
- | `config/` | The retry hint the deny reason closes with and the inventory-name join separator (`inventory_blocker_constants.py`) |
20
- | `tests/` | pytest suite with one test module per module above |
21
-
22
- ## Running tests
23
-
24
- ```bash
25
- python -m pytest packages/claude-dev-env/hooks/blocking/package_inventory_stale_blocker_parts/tests/
26
- ```
1
+ @AGENTS.md
@@ -0,0 +1,24 @@
1
+ # pii_prevention_blocker_parts
2
+
3
+ The concern modules `pii_prevention_blocker.py` wires together. The entry hook
4
+ imports them and re-exports their surface for the test suite.
5
+
6
+ ## Modules
7
+
8
+ | File | Purpose |
9
+ |---|---|
10
+ | `repository_exemption.py` | Resolves a commit's origin owner/repo slug, decides whether the repository skips the staged PII scan, and resolves the exact values that repository allows past the scan |
11
+ | `repository_resolution.py` | Reads the repository a commit command targets — composing multiple `-C` values and a leading `cd`/`pushd` — and builds the deny reason naming the path when the repository root does not resolve |
12
+ | `__init__.py` | Package marker |
13
+
14
+ ## Subdirectories
15
+
16
+ | Entry | Description |
17
+ |---|---|
18
+ | `config/` | The repository-resolution deny-message template and the session-cwd label (`repository_resolution_constants.py`) |
19
+
20
+ ## Running tests
21
+
22
+ ```bash
23
+ python -m pytest packages/claude-dev-env/hooks/blocking/tests/
24
+ ```
@@ -1,24 +1 @@
1
- # pii_prevention_blocker_parts
2
-
3
- The concern modules `pii_prevention_blocker.py` wires together. The entry hook
4
- imports them and re-exports their surface for the test suite.
5
-
6
- ## Modules
7
-
8
- | File | Purpose |
9
- |---|---|
10
- | `repository_exemption.py` | Resolves a commit's origin owner/repo slug, decides whether the repository skips the staged PII scan, and resolves the exact values that repository allows past the scan |
11
- | `repository_resolution.py` | Reads the repository a commit command targets — composing multiple `-C` values and a leading `cd`/`pushd` — and builds the deny reason naming the path when the repository root does not resolve |
12
- | `__init__.py` | Package marker |
13
-
14
- ## Subdirectories
15
-
16
- | Entry | Description |
17
- |---|---|
18
- | `config/` | The repository-resolution deny-message template and the session-cwd label (`repository_resolution_constants.py`) |
19
-
20
- ## Running tests
21
-
22
- ```bash
23
- python -m pytest packages/claude-dev-env/hooks/blocking/tests/
24
- ```
1
+ @AGENTS.md
@@ -0,0 +1,30 @@
1
+ # tdd_enforcer_parts
2
+
3
+ The concern modules `tdd_enforcer.py` wires together to run the TDD gate. Each
4
+ module owns one concern; the entry hook imports them and re-exports their
5
+ surface for the test suite.
6
+
7
+ ## Modules
8
+
9
+ | File | Purpose |
10
+ |---|---|
11
+ | `path_classification.py` | Classifies a write target (docs, tests, `.claude` trees) the gate skips, and extracts the written text from a Write, Edit, or MultiEdit payload |
12
+ | `content_analysis.py` | Decides whether a payload is constants-only, and whether an Edit or MultiEdit merely removes or reorders imports |
13
+ | `candidate_paths.py` | Resolves the candidate test files whose freshness can satisfy the gate for a production file |
14
+ | `freshness.py` | Checks whether a candidate test was modified within the window and holds a real test function |
15
+ | `git_tracking.py` | Detects a write that restores a git-tracked file absent on disk, so a remove-then-Write rewrite is exempt from the gate |
16
+ | `decisions.py` | Builds the deny reason and writes the allow or deny decision JSON to stdout |
17
+ | `__init__.py` | Package marker |
18
+
19
+ ## Subdirectories
20
+
21
+ | Entry | Description |
22
+ |---|---|
23
+ | `config/` | The freshness window, the ancestor-walk limit, the git-tracking command tokens, the source-file extension sets, and the join separator (`tdd_enforcer_constants.py`) |
24
+ | `tests/` | pytest suite with one test module per module above |
25
+
26
+ ## Running tests
27
+
28
+ ```bash
29
+ python -m pytest packages/claude-dev-env/hooks/blocking/tdd_enforcer_parts/tests/
30
+ ```
@@ -1,30 +1 @@
1
- # tdd_enforcer_parts
2
-
3
- The concern modules `tdd_enforcer.py` wires together to run the TDD gate. Each
4
- module owns one concern; the entry hook imports them and re-exports their
5
- surface for the test suite.
6
-
7
- ## Modules
8
-
9
- | File | Purpose |
10
- |---|---|
11
- | `path_classification.py` | Classifies a write target (docs, tests, `.claude` trees) the gate skips, and extracts the written text from a Write, Edit, or MultiEdit payload |
12
- | `content_analysis.py` | Decides whether a payload is constants-only, and whether an Edit or MultiEdit merely removes or reorders imports |
13
- | `candidate_paths.py` | Resolves the candidate test files whose freshness can satisfy the gate for a production file |
14
- | `freshness.py` | Checks whether a candidate test was modified within the window and holds a real test function |
15
- | `git_tracking.py` | Detects a write that restores a git-tracked file absent on disk, so a remove-then-Write rewrite is exempt from the gate |
16
- | `decisions.py` | Builds the deny reason and writes the allow or deny decision JSON to stdout |
17
- | `__init__.py` | Package marker |
18
-
19
- ## Subdirectories
20
-
21
- | Entry | Description |
22
- |---|---|
23
- | `config/` | The freshness window, the ancestor-walk limit, the git-tracking command tokens, the source-file extension sets, and the join separator (`tdd_enforcer_constants.py`) |
24
- | `tests/` | pytest suite with one test module per module above |
25
-
26
- ## Running tests
27
-
28
- ```bash
29
- python -m pytest packages/claude-dev-env/hooks/blocking/tdd_enforcer_parts/tests/
30
- ```
1
+ @AGENTS.md
@@ -1,6 +1,6 @@
1
1
  """Tests for check_banned_prefixes — flags function names with generic prefixes.
2
2
 
3
- CODE_RULES.md §5 / AGENTS.md "Naming → Function names use specific verbs:
3
+ CODE_RULES.md §5 "Naming → Function names use specific verbs:
4
4
  parse_invoice, dispatch_event, migrate_schema. Generic prefixes to replace:
5
5
  handle_, process_, manage_, do_."
6
6
 
@@ -506,3 +506,174 @@ def test_dead_constant_stays_denied_on_every_attempt(neutral_root: Path) -> None
506
506
  "re-issue this write" in each_issue for each_issue in each_attempt
507
507
  ), f"A dead constant stays denied with the retry escape, got: {each_attempt}"
508
508
 
509
+
510
+ ALIAS_CONSTANTS_BODY = 'BASE_PROMPTS_DIRECTORY_NAME = "prompts"\nSTYLE_SHEET_FILENAME = "style.css"\n'
511
+
512
+ ALIASED_IMPORT_CONSUMER_BODY = (
513
+ "from style_guide_gen_constants.config import constants as sg\n"
514
+ "\n"
515
+ "def prompts_directory() -> str:\n"
516
+ " return sg.BASE_PROMPTS_DIRECTORY_NAME\n"
517
+ "\n"
518
+ "def style_sheet() -> str:\n"
519
+ " return sg.STYLE_SHEET_FILENAME\n"
520
+ )
521
+
522
+
523
+ def _build_alias_repository(
524
+ package_parent: Path,
525
+ consumer_directory: Path,
526
+ consumer_body: str,
527
+ ) -> Path:
528
+ package_directory = package_parent / "style_guide_gen_constants"
529
+ config_directory = package_directory / "config"
530
+ config_directory.mkdir(parents=True)
531
+ (package_directory / "__init__.py").write_text("", encoding="utf-8")
532
+ (config_directory / "__init__.py").write_text("", encoding="utf-8")
533
+ constants_path = config_directory / "constants.py"
534
+ constants_path.write_text(ALIAS_CONSTANTS_BODY, encoding="utf-8")
535
+ consumer_directory.mkdir(parents=True, exist_ok=True)
536
+ (consumer_directory / "style_guide_gen.py").write_text(consumer_body, encoding="utf-8")
537
+ return constants_path
538
+
539
+
540
+ def test_does_not_flag_constant_read_through_an_aliased_module_import(
541
+ neutral_root: Path,
542
+ ) -> None:
543
+ constants_path = _build_alias_repository(
544
+ neutral_root,
545
+ neutral_root / "style_guide_gen_constants",
546
+ ALIASED_IMPORT_CONSUMER_BODY,
547
+ )
548
+ issues = _check(ALIAS_CONSTANTS_BODY, str(constants_path))
549
+ assert issues == [], (
550
+ f"A constant read as an attribute on an aliased module import stays live, got: {issues}"
551
+ )
552
+
553
+
554
+ def test_does_not_flag_constant_read_through_an_alias_outside_the_package_tree(
555
+ neutral_root: Path,
556
+ ) -> None:
557
+ constants_path = _build_alias_repository(
558
+ neutral_root / "scripts",
559
+ neutral_root / "scripts",
560
+ ALIASED_IMPORT_CONSUMER_BODY,
561
+ )
562
+ issues = _check(ALIAS_CONSTANTS_BODY, str(constants_path))
563
+ assert issues == [], (
564
+ "A constant an aliased module import reads from outside the package tree "
565
+ f"stays live through the widened scan, got: {issues}"
566
+ )
567
+
568
+
569
+ def test_does_not_flag_constant_read_through_an_unaliased_module_import_outside_the_tree(
570
+ neutral_root: Path,
571
+ ) -> None:
572
+ consumer_body = (
573
+ "from style_guide_gen_constants.config import constants\n"
574
+ "\n"
575
+ "def prompts_directory() -> str:\n"
576
+ " return constants.BASE_PROMPTS_DIRECTORY_NAME\n"
577
+ "\n"
578
+ "def style_sheet() -> str:\n"
579
+ " return constants.STYLE_SHEET_FILENAME\n"
580
+ )
581
+ constants_path = _build_alias_repository(
582
+ neutral_root / "scripts",
583
+ neutral_root / "scripts",
584
+ consumer_body,
585
+ )
586
+ issues = _check(ALIAS_CONSTANTS_BODY, str(constants_path))
587
+ assert issues == [], (
588
+ f"A constant read on an unaliased imported module object stays live, got: {issues}"
589
+ )
590
+
591
+
592
+ def test_does_not_flag_constant_read_through_a_dotted_module_path_outside_the_tree(
593
+ neutral_root: Path,
594
+ ) -> None:
595
+ consumer_body = (
596
+ "import style_guide_gen_constants.config.constants\n"
597
+ "\n"
598
+ "def prompts_directory() -> str:\n"
599
+ " return style_guide_gen_constants.config.constants.BASE_PROMPTS_DIRECTORY_NAME\n"
600
+ "\n"
601
+ "def style_sheet() -> str:\n"
602
+ " return style_guide_gen_constants.config.constants.STYLE_SHEET_FILENAME\n"
603
+ )
604
+ constants_path = _build_alias_repository(
605
+ neutral_root / "scripts",
606
+ neutral_root / "scripts",
607
+ consumer_body,
608
+ )
609
+ issues = _check(ALIAS_CONSTANTS_BODY, str(constants_path))
610
+ assert issues == [], (
611
+ f"A constant read through the full dotted module path stays live, got: {issues}"
612
+ )
613
+
614
+
615
+ def test_does_not_flag_constant_read_through_an_alias_in_a_sibling_tree(
616
+ neutral_root: Path,
617
+ ) -> None:
618
+ constants_body = 'CROSS_TREE_CONSTANT = "cross"\nLOCALLY_DEAD_CONSTANT = "dead"\n'
619
+ sibling_consumer_body = (
620
+ "from shared.theme_db.config import constants as theme_constants\n"
621
+ "\n"
622
+ "def tally() -> str:\n"
623
+ " return theme_constants.CROSS_TREE_CONSTANT\n"
624
+ )
625
+ constants_path = _build_cross_tree_repository(
626
+ neutral_root, constants_body, sibling_consumer_body
627
+ )
628
+ issues = _check(constants_body, str(constants_path))
629
+ assert not any("CROSS_TREE_CONSTANT" in each_issue for each_issue in issues), (
630
+ "A constant a sibling tree reads as an attribute on an aliased module import "
631
+ f"must not be flagged, got: {issues}"
632
+ )
633
+ assert any("LOCALLY_DEAD_CONSTANT" in each_issue for each_issue in issues), (
634
+ f"A constant referenced nowhere in the repository stays flagged, got: {issues}"
635
+ )
636
+
637
+
638
+ def _build_alias_collision_repository(
639
+ repository_root: Path,
640
+ promoter_constants_body: str,
641
+ harness_constants_body: str,
642
+ harness_consumer_body: str,
643
+ ) -> Path:
644
+ promoter_config = repository_root / "promoter" / "config"
645
+ promoter_config.mkdir(parents=True)
646
+ promoter_constants_path = promoter_config / "constants.py"
647
+ promoter_constants_path.write_text(promoter_constants_body, encoding="utf-8")
648
+ harness_config = repository_root / "harness" / "config"
649
+ harness_config.mkdir(parents=True)
650
+ (harness_config / "other_constants.py").write_text(harness_constants_body, encoding="utf-8")
651
+ (repository_root / "harness" / "lockfile.py").write_text(
652
+ harness_consumer_body, encoding="utf-8"
653
+ )
654
+ return promoter_constants_path
655
+
656
+
657
+ def test_alias_attribute_read_on_an_unrelated_module_does_not_mask_a_dead_constant(
658
+ neutral_root: Path,
659
+ ) -> None:
660
+ promoter_constants_body = 'NEVER_READ_CONSTANT = "dead"\n'
661
+ harness_constants_body = 'NEVER_READ_CONSTANT = "twin"\n'
662
+ harness_consumer_body = (
663
+ "from harness.config import other_constants as oc\n"
664
+ "\n"
665
+ "def acquire() -> str:\n"
666
+ " return oc.NEVER_READ_CONSTANT\n"
667
+ )
668
+ promoter_constants_path = _build_alias_collision_repository(
669
+ neutral_root,
670
+ promoter_constants_body,
671
+ harness_constants_body,
672
+ harness_consumer_body,
673
+ )
674
+ issues = _check(promoter_constants_body, str(promoter_constants_path))
675
+ assert any("NEVER_READ_CONSTANT" in each_issue for each_issue in issues), (
676
+ "A constant whose only same-named twin is read as an attribute on an "
677
+ f"unrelated aliased module must still be flagged dead, got: {issues}"
678
+ )
679
+
@@ -1,6 +1,6 @@
1
1
  """Tests for magic-value allowlist alignment with CODE_RULES §HOOK-ENFORCED.
2
2
 
3
- CODE_RULES.md and AGENTS.md both state that only
3
+ CODE_RULES.md states that only
4
4
  0, 1, and -1 (plus their float forms 0.0, 1.0) are exempt from the
5
5
  magic-value check. Prior to this change, the hook silently allowed 2
6
6
  and 100 as well, making the hook more permissive than the written rule.
@@ -422,7 +422,8 @@ def test_main_logs_high_level_failure_when_restore_switch_fails(
422
422
 
423
423
  assert exit_code == 0
424
424
  assert state_file.exists()
425
- assert "[gh-pr-author-restore] failed to restore" in captured_streams.err
425
+ assert "[gh-pr-author-restore] Restore the gh account" in captured_streams.err
426
+ assert "gh account switch requires attention" in captured_streams.err
426
427
  assert "'jl-cmd'" in captured_streams.err
427
428
  assert str(state_file) in captured_streams.err
428
429
 
@@ -111,6 +111,10 @@ def test_intent_ending_message_emits_block_with_short_user_notice():
111
111
  assert parsed_response["systemMessage"] == USER_FACING_INTENT_ENDING_NOTICE
112
112
  assert parsed_response["suppressOutput"] is True
113
113
  assert "long-horizon-autonomy" in parsed_response["reason"]
114
+ assert "This turn ends with a promise of future work." in parsed_response["reason"]
115
+ assert "Complete that work with tool calls during this turn." in parsed_response[
116
+ "reason"
117
+ ]
114
118
 
115
119
 
116
120
  def test_next_steps_lead_in_emits_block():
@@ -0,0 +1,43 @@
1
+ # hooks/diagnostic
2
+
3
+ Hooks and scripts that collect, store, and query hook-firing records. The pipeline reads session JSONL transcripts, extracts hook attachment records, and writes them as rows into a Neon (Postgres) `hook_events` table.
4
+
5
+ ## Subdirectories
6
+
7
+ | Directory | Role |
8
+ |---|---|
9
+ | `migrations/` | SQL migration files for the `hook_events` schema |
10
+ | `queries/` | Parameterized SQL queries for inspecting blocked commands |
11
+
12
+ ## Key files
13
+
14
+ | File | What it does |
15
+ |---|---|
16
+ | `hook_log_init.py` | One-time setup: creates the Neon schema (runs `schema.sql`), then verifies read-write parity with a sentinel round-trip |
17
+ | `hook_log_extractor.py` | Disabled CLI: exits 0 without reading transcripts or rewriting offset state; body remains for a re-enable path that ingests `hook_*` attachments into Neon |
18
+ | `hook_log_stop_wrapper.py` | Disabled Stop wrapper stub: exits 0; not on the Stop dispatcher roster |
19
+ | `schema.sql` | DDL for the `hook_events` table, `blocked_commands` view, and supporting indexes |
20
+ | `requirements-hook-logs.txt` | Runtime dependencies (`psycopg`) for the extractor |
21
+ | `requirements-hook-logs-dev.txt` | Dev/test dependencies |
22
+ | `test_hook_log_extractor.py` | Tests for the extractor |
23
+ | `test_hook_log_init.py` | Tests for the schema-init script |
24
+ | `test_hook_log_stop_wrapper.py` | Tests for the Stop wrapper |
25
+
26
+ ## Schema overview (`schema.sql`)
27
+
28
+ The `hook_events` table captures one row per hook firing:
29
+
30
+ - `hook_event`, `hook_name`, `hook_category` — what fired
31
+ - `outcome` — `allowed`, `blocked`, or `ask`
32
+ - `tool_name`, `command_excerpt` — what tool was called
33
+ - `session_id`, `git_branch`, `cwd` — context
34
+ - `duration_ms`, `exit_code` — timing and result
35
+ - `source_jsonl_path`, `source_line_number` — idempotency key
36
+
37
+ The `blocked_commands` view filters to `outcome = 'blocked'`.
38
+
39
+ ## Conventions
40
+
41
+ - Extractor and Stop wrapper mains are disabled and exit 0 with no work.
42
+ - Constants for the extractor (table name, offset state file, timeout) live in `hooks_constants/hook_log_extractor_constants.py`.
43
+ - Tests run with `python -m pytest diagnostic/test_hook_log_*.py`.
@@ -1,43 +1 @@
1
- # hooks/diagnostic
2
-
3
- Hooks and scripts that collect, store, and query hook-firing records. The pipeline reads session JSONL transcripts, extracts hook attachment records, and writes them as rows into a Neon (Postgres) `hook_events` table.
4
-
5
- ## Subdirectories
6
-
7
- | Directory | Role |
8
- |---|---|
9
- | `migrations/` | SQL migration files for the `hook_events` schema |
10
- | `queries/` | Parameterized SQL queries for inspecting blocked commands |
11
-
12
- ## Key files
13
-
14
- | File | What it does |
15
- |---|---|
16
- | `hook_log_init.py` | One-time setup: creates the Neon schema (runs `schema.sql`), then verifies read-write parity with a sentinel round-trip |
17
- | `hook_log_extractor.py` | Disabled CLI: exits 0 without reading transcripts or rewriting offset state; body remains for a re-enable path that ingests `hook_*` attachments into Neon |
18
- | `hook_log_stop_wrapper.py` | Disabled Stop wrapper stub: exits 0; not on the Stop dispatcher roster |
19
- | `schema.sql` | DDL for the `hook_events` table, `blocked_commands` view, and supporting indexes |
20
- | `requirements-hook-logs.txt` | Runtime dependencies (`psycopg`) for the extractor |
21
- | `requirements-hook-logs-dev.txt` | Dev/test dependencies |
22
- | `test_hook_log_extractor.py` | Tests for the extractor |
23
- | `test_hook_log_init.py` | Tests for the schema-init script |
24
- | `test_hook_log_stop_wrapper.py` | Tests for the Stop wrapper |
25
-
26
- ## Schema overview (`schema.sql`)
27
-
28
- The `hook_events` table captures one row per hook firing:
29
-
30
- - `hook_event`, `hook_name`, `hook_category` — what fired
31
- - `outcome` — `allowed`, `blocked`, or `ask`
32
- - `tool_name`, `command_excerpt` — what tool was called
33
- - `session_id`, `git_branch`, `cwd` — context
34
- - `duration_ms`, `exit_code` — timing and result
35
- - `source_jsonl_path`, `source_line_number` — idempotency key
36
-
37
- The `blocked_commands` view filters to `outcome = 'blocked'`.
38
-
39
- ## Conventions
40
-
41
- - Extractor and Stop wrapper mains are disabled and exit 0 with no work.
42
- - Constants for the extractor (table name, offset state file, timeout) live in `hooks_constants/hook_log_extractor_constants.py`.
43
- - Tests run with `python -m pytest diagnostic/test_hook_log_*.py`.
1
+ @AGENTS.md
@@ -99,7 +99,7 @@ from hooks_constants.hook_log_extractor_constants import (
99
99
  STDERR_EXCERPT_MAX_CHARACTERS,
100
100
  STDOUT_EXCERPT_MAX_CHARACTERS,
101
101
  SUMMARY_COLUMN_HEADINGS,
102
- SUMMARY_NO_NEW_BLOCKS_MESSAGE,
102
+ SUMMARY_ZERO_BLOCKED_EVENTS_MESSAGE,
103
103
  SUMMARY_TABLE_COLUMN_GAP,
104
104
  TOP_BLOCKED_COMMAND_PREVIEW_MAX_CHARACTERS,
105
105
  TOP_BLOCKERS_LAST_24_HOURS_SQL,
@@ -780,7 +780,7 @@ def run_summary() -> int:
780
780
  except Exception:
781
781
  pass
782
782
  if not all_result_rows:
783
- print(SUMMARY_NO_NEW_BLOCKS_MESSAGE)
783
+ print(SUMMARY_ZERO_BLOCKED_EVENTS_MESSAGE)
784
784
  return EXIT_CODE_SUCCESS
785
785
  _print_summary_table(all_result_rows)
786
786
  return EXIT_CODE_SUCCESS
@@ -0,0 +1,16 @@
1
+ # hooks/diagnostic/migrations
2
+
3
+ SQL migration files for the `hook_events` Neon schema. Each file applies a schema change to the `hook_events` table or its indexes.
4
+
5
+ ## Files
6
+
7
+ | File | What it does |
8
+ |---|---|
9
+ | `2026-04-25-drop-themes-hook-events.sql` | Drops the `themes` hook-events table variant |
10
+ | `README.md` | Notes on the migration approach |
11
+
12
+ ## Conventions
13
+
14
+ - Run migrations manually against the Neon database using `psql` or the Neon console.
15
+ - The baseline schema lives in `diagnostic/schema.sql`.
16
+ - File names follow `YYYY-MM-DD-<description>.sql` for chronological ordering.
@@ -1,16 +1 @@
1
- # hooks/diagnostic/migrations
2
-
3
- SQL migration files for the `hook_events` Neon schema. Each file applies a schema change to the `hook_events` table or its indexes.
4
-
5
- ## Files
6
-
7
- | File | What it does |
8
- |---|---|
9
- | `2026-04-25-drop-themes-hook-events.sql` | Drops the `themes` hook-events table variant |
10
- | `README.md` | Notes on the migration approach |
11
-
12
- ## Conventions
13
-
14
- - Run migrations manually against the Neon database using `psql` or the Neon console.
15
- - The baseline schema lives in `diagnostic/schema.sql`.
16
- - File names follow `YYYY-MM-DD-<description>.sql` for chronological ordering.
1
+ @AGENTS.md