claude-dev-env 2.7.0 → 2.8.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 (88) hide show
  1. package/_shared/CLAUDE.md +1 -0
  2. package/_shared/advisor/advisor-protocol.md +19 -9
  3. package/_shared/pr-loop/audit-contract.md +4 -4
  4. package/_shared/pr-loop/precatch-rubric.md +2 -2
  5. package/_shared/pr-loop/worker-spawn.md +3 -1
  6. package/_shared/process-tree/CLAUDE.md +41 -0
  7. package/_shared/process-tree/scripts/config/process_tree_scripts_constants/__init__.py +1 -0
  8. package/_shared/process-tree/scripts/config/process_tree_scripts_constants/process_tree_kill_constants.py +27 -0
  9. package/_shared/process-tree/scripts/process_tree_kill.py +141 -0
  10. package/_shared/process-tree/scripts/pyproject.toml +16 -0
  11. package/_shared/process-tree/scripts/test_process_tree_kill.py +278 -0
  12. package/agents/code-quality-agent.md +6 -5
  13. package/agents/deep-research.md +7 -24
  14. package/agents/docs-agent.md +1 -27
  15. package/agents/issue-tracker.md +1 -7
  16. package/agents/skill-writer-agent.md +1 -2
  17. package/agents/test_agent_frontmatter.py +309 -12
  18. package/hooks/blocking/CLAUDE.md +2 -0
  19. package/hooks/blocking/fable_spawn_gate.py +187 -0
  20. package/hooks/blocking/piped_pytest_blocker.py +1223 -0
  21. package/hooks/blocking/plain_language_blocker.py +287 -15
  22. package/hooks/blocking/test_fable_spawn_gate.py +374 -0
  23. package/hooks/blocking/test_piped_pytest_blocker.py +587 -0
  24. package/hooks/blocking/test_plain_language_blocker.py +277 -2
  25. package/hooks/blocking/test_pre_tool_use_dispatcher.py +34 -2
  26. package/hooks/git-hooks/CLAUDE.md +2 -2
  27. package/hooks/git-hooks/git_hooks_constants/__init__.py +28 -0
  28. package/hooks/git-hooks/pre_push.py +343 -54
  29. package/hooks/git-hooks/test_pre_push.py +852 -6
  30. package/hooks/hooks.json +9 -19
  31. package/hooks/hooks_constants/CLAUDE.md +2 -0
  32. package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +1 -0
  33. package/hooks/hooks_constants/fable_spawn_gate_constants.py +62 -0
  34. package/hooks/hooks_constants/piped_pytest_blocker_constants.py +360 -0
  35. package/hooks/hooks_constants/plain_language_blocker_constants.py +64 -1
  36. package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +10 -0
  37. package/hooks/hooks_constants/shell_command_segments.py +1 -1
  38. package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +1 -0
  39. package/hooks/hooks_constants/test_pre_tool_use_dispatcher_constants.py +27 -0
  40. package/hooks/hooks_constants/test_prose_metrics_parity.py +124 -0
  41. package/package.json +1 -1
  42. package/rules/CLAUDE.md +1 -0
  43. package/rules/ask-user-question-required.md +26 -0
  44. package/rules/claims-as-quotes.md +65 -0
  45. package/scripts/CLAUDE.md +4 -4
  46. package/scripts/_code_review_test_support.py +6 -0
  47. package/scripts/check.ps1 +18 -5
  48. package/scripts/claude_chain_runner.py +203 -31
  49. package/scripts/codec_forwarding_test_support.py +2 -0
  50. package/scripts/dev_env_scripts_constants/CLAUDE.md +4 -4
  51. package/scripts/dev_env_scripts_constants/claude_chain_constants.py +38 -0
  52. package/scripts/dev_env_scripts_constants/code_review_constants.py +403 -2
  53. package/scripts/dev_env_scripts_constants/grok_worker_constants.py +83 -13
  54. package/scripts/grok_headless_runner.py +148 -18
  55. package/scripts/resolve_worker_spawn.py +56 -10
  56. package/scripts/spawn_grok_batch.py +81 -23
  57. package/scripts/test_claude_chain_runner.py +358 -0
  58. package/scripts/test_grok_headless_runner.py +547 -10
  59. package/scripts/test_invoke_code_review.py +298 -0
  60. package/scripts/test_resolve_worker_spawn.py +185 -15
  61. package/scripts/test_spawn_grok_batch.py +326 -22
  62. package/scripts/tests/CLAUDE.md +1 -0
  63. package/scripts/tests/test_grok_worker_constants.py +59 -0
  64. package/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +46 -0
  65. package/skills/autoconverge/workflow/converge.contract.test.mjs +133 -8
  66. package/skills/autoconverge/workflow/converge.fix-recovery.test.mjs +107 -1
  67. package/skills/autoconverge/workflow/converge.mjs +113 -31
  68. package/skills/codex-review/scripts/codex_review_scripts_constants/codex_usage_probe_constants.py +0 -4
  69. package/skills/codex-review/scripts/codex_usage_probe.py +20 -33
  70. package/skills/codex-review/scripts/run_codex_review.py +16 -64
  71. package/skills/codex-review/scripts/test_codex_usage_probe.py +46 -41
  72. package/skills/codex-review/scripts/test_run_codex_review.py +1 -33
  73. package/skills/e-code-review/SKILL.md +9 -8
  74. package/skills/e-code-review/reference/fix.md +29 -7
  75. package/skills/e-code-review/reference/loop.md +230 -14
  76. package/skills/e-code-review/reference/low.md +33 -15
  77. package/skills/e-code-review/reference/medium.md +55 -21
  78. package/skills/e-code-review/reference/xhigh.md +30 -12
  79. package/skills/fresh-branch/CLAUDE.md +5 -5
  80. package/skills/fresh-branch/SKILL.md +14 -6
  81. package/skills/fresh-branch/scripts/create_fresh_branch.py +122 -39
  82. package/skills/fresh-branch/scripts/fresh_branch_scripts_constants/fresh_branch_cli_constants.py +10 -3
  83. package/skills/fresh-branch/scripts/test_create_fresh_branch.py +251 -0
  84. package/skills/grok-spawn/SKILL.md +10 -3
  85. package/skills/grok-spawn/reference/flag-profiles.md +3 -1
  86. package/skills/orchestrator/SKILL.md +4 -1
  87. package/skills/orchestrator-refresh/SKILL.md +5 -1
  88. package/skills/team-advisor/SKILL.md +4 -1
@@ -0,0 +1,187 @@
1
+ #!/usr/bin/env python3
2
+ """PreToolUse gate: deny a fable-tier subagent spawn that carries no marker.
3
+
4
+ One exact token in the spawn prompt authorizes a spawn at the fable tier::
5
+
6
+ model: fable, no FABLE-SPAWN-AUTHORIZED flag: deny
7
+ model: claude-fable-5, no FABLE-SPAWN-AUTHORIZED flag: deny
8
+ model: fable, FABLE-SPAWN-AUTHORIZED ok: allow
9
+ model: claude-sonnet-4, prompt either way ok: allow
10
+ no model field, prompt either way ok: allow
11
+
12
+ The gate reads an ``Agent`` or ``Task`` call and finds the tier in the model
13
+ field whether that field carries the bare alias or a full model id, in any
14
+ letter case. Its deny reason points at the advisor-protocol document rather
15
+ than quoting the token, so a denial pasted into a retry authorizes nothing.
16
+ A deny also sets ``additionalContext`` so the spawner sees the recovery path
17
+ (re-spawn at opus / an opus-equivalent tier, or authorize a fable bind).
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import json
23
+ import sys
24
+ from pathlib import Path
25
+ from typing import Mapping, TextIO
26
+
27
+ _hooks_dir = str(Path(__file__).resolve().parent.parent)
28
+ if _hooks_dir not in sys.path:
29
+ sys.path.insert(0, _hooks_dir)
30
+
31
+ from hooks_constants.fable_spawn_gate_constants import ( # noqa: E402
32
+ ALL_SPAWN_TOOL_NAMES,
33
+ CALLING_HOOK_NAME,
34
+ DENY_ADDITIONAL_CONTEXT,
35
+ DENY_PREVIEW_TEMPLATE,
36
+ DENY_REASON,
37
+ FABLE_MODEL_ALIAS,
38
+ FABLE_SPAWN_AUTHORIZATION_MARKER,
39
+ HOOK_EVENT_NAME,
40
+ MAXIMUM_PREVIEW_MODEL_LENGTH,
41
+ MODEL_FIELD_NAME,
42
+ MODEL_SEGMENT_SPLIT_PATTERN,
43
+ PROMPT_FIELD_NAME,
44
+ TOOL_INPUT_FIELD_NAME,
45
+ TOOL_NAME_FIELD_NAME,
46
+ )
47
+ from hooks_constants.hook_block_logger import log_hook_block # noqa: E402
48
+ from hooks_constants.pre_tool_use_stdin import ( # noqa: E402
49
+ read_hook_input_dictionary_from_stdin,
50
+ )
51
+
52
+
53
+ def _model_names_the_fable_tier(model_identifier: str) -> bool:
54
+ """Report whether a model string names the fable tier.
55
+
56
+ The field carries either the bare alias or a full model id, so the
57
+ lowercased string is cut on its delimiters and the tier is looked for as
58
+ a whole segment::
59
+
60
+ fable -> ['fable'] flag: fable tier
61
+ claude-fable-5 -> ['claude', 'fable', '5'] flag: fable tier
62
+ claude-sonnet-4 -> ['claude', 'sonnet', '4'] ok: another tier
63
+ affable -> ['affable'] ok: another tier
64
+
65
+ Args:
66
+ model_identifier: The spawn's ``model`` string.
67
+
68
+ Returns:
69
+ True when a model segment reads ``fable`` in any letter case; False
70
+ for another tier.
71
+ """
72
+ all_segments = MODEL_SEGMENT_SPLIT_PATTERN.split(model_identifier.strip().lower())
73
+ return FABLE_MODEL_ALIAS in all_segments
74
+
75
+
76
+ def _prompt_carries_authorization_marker(all_tool_input: Mapping[str, object]) -> bool:
77
+ """Report whether the spawn prompt holds the exact authorization token.
78
+
79
+ Args:
80
+ all_tool_input: The spawn's ``tool_input`` mapping.
81
+
82
+ Returns:
83
+ True when the prompt is a string holding the marker token; False for
84
+ an unmarked prompt and for an absent or non-string field.
85
+ """
86
+ spawn_prompt = all_tool_input.get(PROMPT_FIELD_NAME)
87
+ if not isinstance(spawn_prompt, str):
88
+ return False
89
+ return FABLE_SPAWN_AUTHORIZATION_MARKER in spawn_prompt
90
+
91
+
92
+ def _denied_spawn_model(all_payload_by_field: Mapping[str, object]) -> str | None:
93
+ """Return the model string of an unauthorized fable spawn, or None.
94
+
95
+ The model string travels back as the decision, so the deny path holds a
96
+ proven fable string and never re-derives one.
97
+
98
+ Args:
99
+ all_payload_by_field: The parsed PreToolUse payload, keyed by
100
+ top-level field name.
101
+
102
+ Returns:
103
+ The ``model`` string for a spawn tool call at the fable tier whose
104
+ prompt lacks the authorization marker; None for every allowed call.
105
+ """
106
+ if all_payload_by_field.get(TOOL_NAME_FIELD_NAME, "") not in ALL_SPAWN_TOOL_NAMES:
107
+ return None
108
+ tool_input = all_payload_by_field.get(TOOL_INPUT_FIELD_NAME, {})
109
+ if not isinstance(tool_input, dict):
110
+ return None
111
+ model_identifier = tool_input.get(MODEL_FIELD_NAME)
112
+ if not isinstance(model_identifier, str):
113
+ return None
114
+ if not _model_names_the_fable_tier(model_identifier):
115
+ return None
116
+ if _prompt_carries_authorization_marker(tool_input):
117
+ return None
118
+ return model_identifier
119
+
120
+
121
+ def _build_denial_preview(model_identifier: str) -> str:
122
+ """Build the bounded preview the hook-blocks log records for a denial.
123
+
124
+ The preview names the model field that tripped the gate, so its length
125
+ holds steady whatever the spawn prompt carries::
126
+
127
+ fable -> model=fable marker_present=False
128
+ claude-fable-5 -> model=claude-fable-5 marker_present=False
129
+
130
+ Args:
131
+ model_identifier: The fable model string the denial was decided on.
132
+
133
+ Returns:
134
+ One preview line naming the model text and the marker state.
135
+ """
136
+ return DENY_PREVIEW_TEMPLATE.format(
137
+ model_text=model_identifier[:MAXIMUM_PREVIEW_MODEL_LENGTH]
138
+ )
139
+
140
+
141
+ def _emit_denial(
142
+ decision_stream: TextIO,
143
+ all_payload_by_field: Mapping[str, object],
144
+ model_identifier: str,
145
+ ) -> None:
146
+ """Log the denied spawn and write the PreToolUse deny payload.
147
+
148
+ Args:
149
+ decision_stream: Writable text stream — production code passes
150
+ ``sys.stdout``; tests pass a ``StringIO`` to capture the JSON.
151
+ all_payload_by_field: The parsed PreToolUse payload, whose tool name
152
+ names the denied spawn in the hook-blocks log.
153
+ model_identifier: The fable model string the denial was decided on.
154
+ """
155
+ denial = {
156
+ "hookSpecificOutput": {
157
+ "hookEventName": HOOK_EVENT_NAME,
158
+ "permissionDecision": "deny",
159
+ "permissionDecisionReason": DENY_REASON,
160
+ "additionalContext": DENY_ADDITIONAL_CONTEXT,
161
+ }
162
+ }
163
+ log_hook_block(
164
+ calling_hook_name=CALLING_HOOK_NAME,
165
+ hook_event=HOOK_EVENT_NAME,
166
+ block_reason=DENY_REASON,
167
+ tool_name=str(all_payload_by_field.get(TOOL_NAME_FIELD_NAME, "")),
168
+ offending_input_preview=_build_denial_preview(model_identifier),
169
+ )
170
+ decision_stream.write(json.dumps(denial) + "\n")
171
+ decision_stream.flush()
172
+
173
+
174
+ def main() -> None:
175
+ """Read the PreToolUse payload and deny an unmarked fable spawn."""
176
+ hook_payload = read_hook_input_dictionary_from_stdin()
177
+ if hook_payload is None:
178
+ sys.exit(0)
179
+ denied_model = _denied_spawn_model(hook_payload)
180
+ if denied_model is None:
181
+ sys.exit(0)
182
+ _emit_denial(sys.stdout, hook_payload, denied_model)
183
+ sys.exit(0)
184
+
185
+
186
+ if __name__ == "__main__":
187
+ main()