claude-dev-env 1.19.0 → 1.19.1

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.
@@ -8,7 +8,10 @@ import sys
8
8
  from content_search_zoekt_bash_block_reason import block_reason_for_bash_command
9
9
  from content_search_zoekt_block_payload import build_block_payload
10
10
  from content_search_zoekt_indexed_paths import is_in_indexed_repo, is_specific_file
11
- from content_search_zoekt_redirect_guidance import get_zoekt_redirect_guidance
11
+ from content_search_zoekt_redirect_guidance import (
12
+ get_zoekt_redirect_guidance,
13
+ get_zoekt_redirect_reason_brief,
14
+ )
12
15
 
13
16
 
14
17
  def main() -> None:
@@ -45,7 +48,8 @@ def main() -> None:
45
48
  short_label = f"blocked {block_reason}; use Zoekt MCP"
46
49
  payload = build_block_payload(
47
50
  brief_label=short_label,
48
- permission_decision_reason=get_zoekt_redirect_guidance(),
51
+ permission_decision_reason=get_zoekt_redirect_reason_brief(),
52
+ additional_context=get_zoekt_redirect_guidance(),
49
53
  )
50
54
  print(json.dumps(payload))
51
55
  sys.exit(0)
@@ -4,14 +4,18 @@
4
4
  def build_block_payload(
5
5
  brief_label: str,
6
6
  permission_decision_reason: str,
7
+ additional_context: str | None = None,
7
8
  ) -> dict:
8
9
  destructive_gate_label_prefix = "[destructive-gate]"
10
+ hook_specific_output: dict = {
11
+ "hookEventName": "PreToolUse",
12
+ "permissionDecision": "deny",
13
+ "permissionDecisionReason": permission_decision_reason,
14
+ }
15
+ if additional_context is not None:
16
+ hook_specific_output["additionalContext"] = additional_context
9
17
  return {
10
- "hookSpecificOutput": {
11
- "hookEventName": "PreToolUse",
12
- "permissionDecision": "deny",
13
- "permissionDecisionReason": permission_decision_reason,
14
- },
18
+ "hookSpecificOutput": hook_specific_output,
15
19
  "systemMessage": f"{destructive_gate_label_prefix} {brief_label}",
16
20
  "suppressOutput": True,
17
21
  }
@@ -1,4 +1,10 @@
1
- """Zoekt MCP usage and repo-to-disk path mapping for PreToolUse permissionDecisionReason."""
1
+ """Zoekt MCP usage and repo-to-disk path mapping for PreToolUse outputs."""
2
+
3
+
4
+ def get_zoekt_redirect_reason_brief() -> str:
5
+ return (
6
+ "Use Zoekt MCP (e.g. mcp__zoekt__search) instead of Grep/Search in Zoekt-indexed trees."
7
+ )
2
8
 
3
9
 
4
10
  def get_zoekt_redirect_guidance() -> str:
@@ -13,7 +13,10 @@ class ContentSearchHookIntegrationTests(unittest.TestCase):
13
13
  hook_directory = pathlib.Path(__file__).resolve().parent
14
14
  if str(hook_directory) not in sys.path:
15
15
  sys.path.insert(0, str(hook_directory))
16
- from content_search_zoekt_redirect_guidance import get_zoekt_redirect_guidance
16
+ from content_search_zoekt_redirect_guidance import (
17
+ get_zoekt_redirect_guidance,
18
+ get_zoekt_redirect_reason_brief,
19
+ )
17
20
 
18
21
  hook_path = hook_directory / "content-search-to-zoekt-redirector.py"
19
22
  destructive_gate_label_prefix = "[destructive-gate]"
@@ -38,6 +41,10 @@ class ContentSearchHookIntegrationTests(unittest.TestCase):
38
41
  )
39
42
  self.assertEqual(
40
43
  payload["hookSpecificOutput"]["permissionDecisionReason"],
44
+ get_zoekt_redirect_reason_brief(),
45
+ )
46
+ self.assertEqual(
47
+ payload["hookSpecificOutput"]["additionalContext"],
41
48
  get_zoekt_redirect_guidance(),
42
49
  )
43
50
  self.assertEqual(
@@ -11,7 +11,10 @@ if str(HOOK_DIRECTORY) not in sys.path:
11
11
  sys.path.insert(0, str(HOOK_DIRECTORY))
12
12
 
13
13
  from content_search_zoekt_block_payload import build_block_payload
14
- from content_search_zoekt_redirect_guidance import get_zoekt_redirect_guidance
14
+ from content_search_zoekt_redirect_guidance import (
15
+ get_zoekt_redirect_guidance,
16
+ get_zoekt_redirect_reason_brief,
17
+ )
15
18
 
16
19
 
17
20
  class BuildBlockPayloadTests(unittest.TestCase):
@@ -32,12 +35,14 @@ class BuildBlockPayloadTests(unittest.TestCase):
32
35
  self.assertEqual(payload["suppressOutput"], True)
33
36
  self.assertNotIn("decision", payload)
34
37
  self.assertNotIn("reason", payload)
38
+ self.assertNotIn("additionalContext", payload["hookSpecificOutput"])
35
39
 
36
40
  def test_serialized_payload_under_documented_context_cap(self) -> None:
37
41
  cap_characters = 10_000
38
42
  payload = build_block_payload(
39
43
  brief_label="blocked Bash(grep); use Zoekt MCP",
40
- permission_decision_reason=get_zoekt_redirect_guidance(),
44
+ permission_decision_reason=get_zoekt_redirect_reason_brief(),
45
+ additional_context=get_zoekt_redirect_guidance(),
41
46
  )
42
47
  serialized = json.dumps(payload)
43
48
  self.assertLessEqual(
package/hooks/hooks.json CHANGED
@@ -2,6 +2,16 @@
2
2
  "description": "Code standards enforcement, safety guards, and development workflow hooks",
3
3
  "hooks": {
4
4
  "PreToolUse": [
5
+ {
6
+ "matcher": "Grep|Search",
7
+ "hooks": [
8
+ {
9
+ "type": "command",
10
+ "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/blocking/content-search-to-zoekt-redirector.py",
11
+ "timeout": 10
12
+ }
13
+ ]
14
+ },
5
15
  {
6
16
  "matcher": "Write|Edit",
7
17
  "hooks": [
@@ -84,6 +94,11 @@
84
94
  "type": "command",
85
95
  "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/blocking/test-preflight-check.py",
86
96
  "timeout": 10
97
+ },
98
+ {
99
+ "type": "command",
100
+ "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/blocking/content-search-to-zoekt-redirector.py",
101
+ "timeout": 10
87
102
  }
88
103
  ]
89
104
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-dev-env",
3
- "version": "1.19.0",
3
+ "version": "1.19.1",
4
4
  "description": "Claude Code development standards — rules, hooks, agents, commands, and skills",
5
5
  "type": "module",
6
6
  "bin": {