claude-dev-env 1.28.1 → 1.29.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 (54) hide show
  1. package/agents/caveman.md +74 -0
  2. package/hooks/blocking/code_rules_enforcer.py +82 -7
  3. package/hooks/blocking/code_rules_path_utils.py +31 -0
  4. package/hooks/blocking/es_exe_path_rewriter.py +159 -0
  5. package/hooks/blocking/hedging_language_blocker.py +12 -2
  6. package/hooks/blocking/test_code_rules_enforcer.py +148 -0
  7. package/hooks/blocking/test_code_rules_enforcer_config_path.py +123 -0
  8. package/hooks/blocking/test_code_rules_enforcer_magic_allowlist.py +1 -1
  9. package/hooks/blocking/test_code_rules_path_utils.py +52 -0
  10. package/hooks/blocking/test_es_exe_path_rewriter.py +369 -0
  11. package/hooks/blocking/test_hedging_language_blocker.py +7 -6
  12. package/hooks/config/dynamic_stderr_handler.py +22 -0
  13. package/hooks/config/path_rewriter_constants.py +13 -0
  14. package/hooks/config/project_paths_reader.py +78 -0
  15. package/hooks/config/setup_project_paths_constants.py +41 -0
  16. package/hooks/config/test_dynamic_stderr_handler.py +48 -0
  17. package/hooks/config/test_messages.py +5 -1
  18. package/hooks/config/test_path_rewriter_constants.py +57 -0
  19. package/hooks/config/test_project_paths_reader.py +149 -0
  20. package/hooks/config/test_setup_project_paths_constants.py +74 -0
  21. package/hooks/git-hooks/test_config.py +1 -0
  22. package/hooks/git-hooks/test_gate_utils.py +1 -0
  23. package/hooks/git-hooks/test_pre_commit.py +1 -0
  24. package/hooks/git-hooks/test_pre_push.py +1 -0
  25. package/hooks/hooks.json +10 -0
  26. package/hooks/session/test_untracked_repo_detector.py +192 -0
  27. package/hooks/session/untracked_repo_detector.py +103 -0
  28. package/hooks/validators/exempt_paths.py +17 -14
  29. package/hooks/validators/test_exempt_paths.py +65 -0
  30. package/hooks/validators/test_git_checks.py +17 -17
  31. package/package.json +1 -1
  32. package/scripts/config/__init__.py +1 -0
  33. package/scripts/config/groq_bugteam_config.py +118 -0
  34. package/scripts/config/test_groq_bugteam_config.py +72 -0
  35. package/scripts/groq_bugteam.README.md +129 -0
  36. package/scripts/groq_bugteam.py +586 -0
  37. package/scripts/setup_project_paths.py +347 -0
  38. package/scripts/test_groq_bugteam.py +391 -0
  39. package/scripts/test_setup_project_paths.py +532 -0
  40. package/scripts/test_setup_project_paths_config.py +6 -0
  41. package/skills/bugteam/CONSTRAINTS.md +1 -1
  42. package/skills/bugteam/PROMPTS.md +1 -1
  43. package/skills/bugteam/SKILL.md +5 -5
  44. package/skills/bugteam/SKILL_EVALS.md +5 -5
  45. package/skills/bugteam/reference/audit-and-teammates.md +3 -3
  46. package/skills/bugteam/reference/audit-contract.md +159 -0
  47. package/skills/bugteam/reference/team-setup.md +2 -2
  48. package/skills/bugteam/scripts/bugteam_preflight.py +66 -0
  49. package/skills/bugteam/scripts/test_bugteam_preflight.py +189 -0
  50. package/skills/copilot-review/SKILL.md +145 -0
  51. package/skills/findbugs/SKILL.md +14 -22
  52. package/skills/qbug/SKILL.md +56 -13
  53. package/skills/qbug/test_qbug_skill_audit_schema.py +156 -0
  54. package/skills/qbug/test_qbug_skill_post_fix_audit.py +103 -0
@@ -0,0 +1,78 @@
1
+ """Load the per-user project-path registry from ~/.claude/project-paths.json."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import logging
7
+ import os
8
+ from pathlib import Path
9
+
10
+ from config.dynamic_stderr_handler import DynamicStderrHandler
11
+ from config.setup_project_paths_constants import META_KEY, UTF8_ENCODING
12
+
13
+
14
+ _logger = logging.getLogger("project_paths_reader")
15
+ if not _logger.handlers:
16
+ _stderr_handler = DynamicStderrHandler()
17
+ _stderr_handler.setFormatter(logging.Formatter("%(name)s: %(message)s"))
18
+ _logger.addHandler(_stderr_handler)
19
+ _logger.setLevel(logging.INFO)
20
+ _logger.propagate = False
21
+
22
+
23
+ def registry_file_path() -> Path:
24
+ """Return the canonical path to ~/.claude/project-paths.json."""
25
+ return Path.home() / ".claude" / "project-paths.json"
26
+
27
+
28
+ def _default_config_path() -> Path:
29
+ return registry_file_path()
30
+
31
+
32
+ def _normalize_path_separators(raw_path: str) -> str:
33
+ forward_slash_form = raw_path.replace("\\", "/")
34
+ return os.path.normcase(os.path.normpath(forward_slash_form)).replace("\\", "/")
35
+
36
+
37
+ def load_registry(config_path: Path | None = None) -> dict[str, str]:
38
+ """Return the name-to-absolute-path mapping with the _meta key stripped.
39
+
40
+ Returns an empty dict when the file is missing, unreadable, malformed,
41
+ or otherwise invalid. Logs one line to stderr when the file cannot be
42
+ read or contains malformed JSON.
43
+ """
44
+ resolved_path = config_path if config_path is not None else _default_config_path()
45
+ if not resolved_path.is_file():
46
+ return {}
47
+ try:
48
+ raw_text = resolved_path.read_text(encoding=UTF8_ENCODING)
49
+ except OSError as e:
50
+ _logger.error("cannot read %s: %s", resolved_path, e)
51
+ return {}
52
+ try:
53
+ parsed = json.loads(raw_text)
54
+ except json.JSONDecodeError as e:
55
+ _logger.error("malformed JSON in %s: %s", resolved_path, e)
56
+ return {}
57
+ if not isinstance(parsed, dict):
58
+ return {}
59
+ return {
60
+ each_key: each_value
61
+ for each_key, each_value in parsed.items()
62
+ if each_key != META_KEY
63
+ and isinstance(each_key, str)
64
+ and isinstance(each_value, str)
65
+ }
66
+
67
+
68
+ def registry_contains_path(known_registry: dict[str, str], path_to_find: str) -> bool:
69
+ """Return True when the given path appears as any registry value.
70
+
71
+ Normalizes both sides before comparing so Windows and POSIX separator
72
+ forms of the same path compare equal.
73
+ """
74
+ normalized_target = _normalize_path_separators(path_to_find)
75
+ for each_registered_path in known_registry.values():
76
+ if _normalize_path_separators(each_registered_path) == normalized_target:
77
+ return True
78
+ return False
@@ -0,0 +1,41 @@
1
+ """Configuration constants for the setup_project_paths bootstrap script.
2
+
3
+ Shared constants consumed by two or more modules across the hook subsystem.
4
+ Single-use values are inlined into their consuming functions per the
5
+ file-global-constants use-count rule.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ ES_EXE_FOLDERS_ONLY_QUERY_ARGUMENTS = ("/ad", "folder:.git")
11
+
12
+ EXCLUDED_PATH_SEGMENTS = frozenset(
13
+ {
14
+ "temp",
15
+ "tmp",
16
+ "worktree",
17
+ "node_modules",
18
+ ".cache",
19
+ "$recycle.bin",
20
+ }
21
+ )
22
+
23
+ JSON_INDENT_SPACES = 2
24
+
25
+ GIT_DIRECTORY_SEGMENT_NAME = ".git"
26
+
27
+ ES_EXE_BINARY_NAME = "es.exe"
28
+
29
+ SUPPORTED_SCHEMA_VERSION = 1
30
+
31
+ META_KEY = "_meta"
32
+
33
+ UTF8_ENCODING = "utf-8"
34
+
35
+ CONFIRMATION_PROMPT_TEXT = "Write this mapping to the config file? (yes/no): "
36
+
37
+ ABORTED_NOTHING_WRITTEN_MESSAGE = "Aborted. Nothing written."
38
+
39
+ WROTE_ENTRIES_STATUS_TEMPLATE = "Wrote {entry_count} entries to {save_path}."
40
+
41
+ STDERR_TRUNCATION_LENGTH = 200
@@ -0,0 +1,48 @@
1
+ """Tests for DynamicStderrHandler — resolves sys.stderr at emit time."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ import sys
7
+ from io import StringIO
8
+ from pathlib import Path
9
+ from unittest.mock import patch
10
+
11
+ _HOOKS_ROOT = Path(__file__).resolve().parent.parent
12
+ if str(_HOOKS_ROOT) not in sys.path:
13
+ sys.path.insert(0, str(_HOOKS_ROOT))
14
+
15
+ from config.dynamic_stderr_handler import DynamicStderrHandler
16
+
17
+
18
+ def _make_logger_with_handler() -> tuple[logging.Logger, DynamicStderrHandler]:
19
+ handler_instance = DynamicStderrHandler()
20
+ handler_instance.setFormatter(logging.Formatter("%(name)s: %(message)s"))
21
+ test_logger = logging.getLogger("test_dynamic_stderr_handler_logger")
22
+ for each_existing_handler in list(test_logger.handlers):
23
+ test_logger.removeHandler(each_existing_handler)
24
+ test_logger.addHandler(handler_instance)
25
+ test_logger.setLevel(logging.INFO)
26
+ test_logger.propagate = False
27
+ return test_logger, handler_instance
28
+
29
+
30
+ def test_emit_writes_to_current_sys_stderr() -> None:
31
+ test_logger, _ = _make_logger_with_handler()
32
+ captured_stderr = StringIO()
33
+ with patch("sys.stderr", captured_stderr):
34
+ test_logger.error("hello from handler")
35
+ assert "hello from handler" in captured_stderr.getvalue()
36
+
37
+
38
+ def test_emit_resolves_stderr_at_emit_time_not_construction_time() -> None:
39
+ test_logger, _ = _make_logger_with_handler()
40
+ first_captured_stderr = StringIO()
41
+ second_captured_stderr = StringIO()
42
+ with patch("sys.stderr", first_captured_stderr):
43
+ test_logger.error("first message")
44
+ with patch("sys.stderr", second_captured_stderr):
45
+ test_logger.error("second message")
46
+ assert "first message" in first_captured_stderr.getvalue()
47
+ assert "second message" in second_captured_stderr.getvalue()
48
+ assert "second message" not in first_captured_stderr.getvalue()
@@ -1,4 +1,8 @@
1
- """Smoke tests for hooks.config.messages — verify user-facing notice constants exist."""
1
+ """Smoke tests for config.messages — verify user-facing notice constants exist."""
2
+
3
+ import sys
4
+
5
+ sys.modules.pop("config", None)
2
6
 
3
7
  from config import messages
4
8
 
@@ -0,0 +1,57 @@
1
+ """Pin tests for path_rewriter_constants — values consumed by es_exe_path_rewriter."""
2
+
3
+ import sys
4
+ from pathlib import Path
5
+
6
+ _HOOKS_ROOT = Path(__file__).resolve().parent.parent
7
+ if str(_HOOKS_ROOT) not in sys.path:
8
+ sys.path.insert(0, str(_HOOKS_ROOT))
9
+
10
+ from config.path_rewriter_constants import (
11
+ BASH_TOOL_NAME,
12
+ HOOK_EVENT_NAME,
13
+ PERMISSION_ALLOW,
14
+ PLACEHOLDER_TOKEN_PATTERN,
15
+ )
16
+
17
+
18
+ def test_bash_tool_name_is_bash() -> None:
19
+ assert BASH_TOOL_NAME == "Bash"
20
+
21
+
22
+ def test_hook_event_name_is_pre_tool_use() -> None:
23
+ assert HOOK_EVENT_NAME == "PreToolUse"
24
+
25
+
26
+ def test_permission_allow_is_allow() -> None:
27
+ assert PERMISSION_ALLOW == "allow"
28
+
29
+
30
+ def test_placeholder_token_pattern_matches_curly_brace_form() -> None:
31
+ match = PLACEHOLDER_TOKEN_PATTERN.match("{my-repo}")
32
+ assert match is not None
33
+ assert match.group(1) == "my-repo"
34
+
35
+
36
+ def test_placeholder_token_pattern_matches_double_quoted_form() -> None:
37
+ match = PLACEHOLDER_TOKEN_PATTERN.match('"{my-repo}"')
38
+ assert match is not None
39
+ assert match.group(1) == "my-repo"
40
+
41
+
42
+ def test_placeholder_token_pattern_matches_single_quoted_form() -> None:
43
+ match = PLACEHOLDER_TOKEN_PATTERN.match("'{my-repo}'")
44
+ assert match is not None
45
+ assert match.group(1) == "my-repo"
46
+
47
+
48
+ def test_placeholder_token_pattern_does_not_match_shell_parameter_expansion() -> None:
49
+ assert PLACEHOLDER_TOKEN_PATTERN.search("${myrepo}") is None
50
+
51
+
52
+ def test_placeholder_token_pattern_does_not_match_embedded_in_flag() -> None:
53
+ assert PLACEHOLDER_TOKEN_PATTERN.search("--flag={my-repo}") is None
54
+
55
+
56
+ def test_placeholder_token_pattern_does_not_match_embedded_in_token() -> None:
57
+ assert PLACEHOLDER_TOKEN_PATTERN.search("foo{my-repo}bar") is None
@@ -0,0 +1,149 @@
1
+ """Tests for project_paths_reader — config reader for ~/.claude/project-paths.json."""
2
+
3
+ import inspect
4
+ import json
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ import pytest
9
+
10
+ _HOOKS_ROOT = Path(__file__).resolve().parent.parent
11
+ if str(_HOOKS_ROOT) not in sys.path:
12
+ sys.path.insert(0, str(_HOOKS_ROOT))
13
+
14
+ from config import project_paths_reader
15
+ from config.project_paths_reader import (
16
+ load_registry,
17
+ registry_contains_path,
18
+ registry_file_path,
19
+ )
20
+ from config.setup_project_paths_constants import META_KEY
21
+
22
+
23
+ def test_reader_does_not_redefine_dynamic_stderr_handler_locally() -> None:
24
+ """Pin PR #230 round 3 DRY fix: handler is imported from the shared module.
25
+
26
+ Both project_paths_reader and es_exe_path_rewriter previously defined
27
+ identical `_DynamicStderrHandler` classes. This test fails if the
28
+ duplicate class reappears in project_paths_reader.
29
+ """
30
+ assert not hasattr(project_paths_reader, "_DynamicStderrHandler")
31
+
32
+
33
+ def test_load_registry_returns_empty_dict_when_file_missing(tmp_path: Path) -> None:
34
+ missing_path = tmp_path / "nonexistent.json"
35
+ loaded_registry = load_registry(config_path=missing_path)
36
+ assert loaded_registry == {}
37
+
38
+
39
+ def test_load_registry_returns_empty_dict_when_json_is_malformed(
40
+ tmp_path: Path,
41
+ ) -> None:
42
+ malformed_file = tmp_path / "project-paths.json"
43
+ malformed_file.write_text("{ not valid json", encoding="utf-8")
44
+ loaded_registry = load_registry(config_path=malformed_file)
45
+ assert loaded_registry == {}
46
+
47
+
48
+ def test_load_registry_strips_meta_key(tmp_path: Path) -> None:
49
+ registry_file = tmp_path / "project-paths.json"
50
+ registry_file.write_text(
51
+ json.dumps(
52
+ {
53
+ "_meta": {"schema_version": 1, "last_scan": "2026-01-01T00:00:00Z"},
54
+ "my-repo": "Y:\\Projects\\my-repo",
55
+ }
56
+ ),
57
+ encoding="utf-8",
58
+ )
59
+ loaded_registry = load_registry(config_path=registry_file)
60
+ assert "_meta" not in loaded_registry
61
+ assert loaded_registry["my-repo"] == "Y:\\Projects\\my-repo"
62
+
63
+
64
+ def test_load_registry_returns_name_to_path_mapping(tmp_path: Path) -> None:
65
+ registry_file = tmp_path / "project-paths.json"
66
+ registry_file.write_text(
67
+ json.dumps(
68
+ {
69
+ "repo-alpha": "Y:\\Projects\\repo-alpha",
70
+ "repo-beta": "C:\\Dev\\repo-beta",
71
+ }
72
+ ),
73
+ encoding="utf-8",
74
+ )
75
+ loaded_registry = load_registry(config_path=registry_file)
76
+ assert loaded_registry == {
77
+ "repo-alpha": "Y:\\Projects\\repo-alpha",
78
+ "repo-beta": "C:\\Dev\\repo-beta",
79
+ }
80
+
81
+
82
+ def test_load_registry_returns_empty_dict_when_top_level_is_not_object(
83
+ tmp_path: Path,
84
+ ) -> None:
85
+ registry_file = tmp_path / "project-paths.json"
86
+ registry_file.write_text(json.dumps(["a", "b"]), encoding="utf-8")
87
+ loaded_registry = load_registry(config_path=registry_file)
88
+ assert loaded_registry == {}
89
+
90
+
91
+ def test_registry_contains_path_returns_true_when_path_present(tmp_path: Path) -> None:
92
+ known_registry = {"my-repo": str(tmp_path)}
93
+ assert registry_contains_path(known_registry, str(tmp_path)) is True
94
+
95
+
96
+ def test_registry_contains_path_returns_false_when_path_absent(tmp_path: Path) -> None:
97
+ known_registry = {"other-repo": "C:\\Other\\Path"}
98
+ assert registry_contains_path(known_registry, str(tmp_path)) is False
99
+
100
+
101
+ def test_registry_contains_path_normalizes_separators(tmp_path: Path) -> None:
102
+ forward_slash_path = str(tmp_path).replace("\\", "/")
103
+ known_registry = {"my-repo": str(tmp_path)}
104
+ assert registry_contains_path(known_registry, forward_slash_path) is True
105
+
106
+
107
+ def test_registry_contains_path_treats_backslash_and_forward_slash_as_equal() -> None:
108
+ backslash_path = "C:\\foo\\bar"
109
+ forward_slash_path = "C:/foo/bar"
110
+ known_registry = {"my-repo": backslash_path}
111
+ assert registry_contains_path(known_registry, forward_slash_path) is True
112
+ known_registry_forward = {"my-repo": forward_slash_path}
113
+ assert registry_contains_path(known_registry_forward, backslash_path) is True
114
+
115
+
116
+ def test_registry_file_path_returns_dot_claude_project_paths_json() -> None:
117
+ resolved_path = registry_file_path()
118
+ assert resolved_path.name == "project-paths.json"
119
+ assert resolved_path.parent.name == ".claude"
120
+ assert resolved_path.parent.parent == Path.home()
121
+
122
+
123
+ def test_registry_file_path_is_absolute() -> None:
124
+ resolved_path = registry_file_path()
125
+ assert resolved_path.is_absolute()
126
+
127
+
128
+ def test_load_registry_uses_shared_meta_key_constant() -> None:
129
+ """Pin: load_registry must import META_KEY from setup_project_paths_constants.
130
+
131
+ The bare string literal "_meta" must not appear in load_registry;
132
+ it must use the shared META_KEY constant as the single source of truth.
133
+ """
134
+ source = inspect.getsource(project_paths_reader.load_registry)
135
+ assert '"_meta"' not in source, (
136
+ 'load_registry must use META_KEY constant, not the bare literal "_meta"'
137
+ )
138
+
139
+
140
+ def test_load_registry_uses_shared_utf8_encoding_constant() -> None:
141
+ """Pin fix_1: load_registry must import UTF8_ENCODING from setup_project_paths_constants.
142
+
143
+ The bare string literal "utf-8" in the read_text call must be replaced
144
+ with the shared constant so there is one source of truth for the encoding name.
145
+ """
146
+ source = inspect.getsource(project_paths_reader)
147
+ assert 'encoding="utf-8"' not in source, (
148
+ 'load_registry must use UTF8_ENCODING constant, not the bare literal "utf-8"'
149
+ )
@@ -0,0 +1,74 @@
1
+ """Regression-guard tests for setup_project_paths_constants module."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ _HOOKS_ROOT = Path(__file__).resolve().parent.parent
9
+ if str(_HOOKS_ROOT) not in sys.path:
10
+ sys.path.insert(0, str(_HOOKS_ROOT))
11
+
12
+ import config.setup_project_paths_constants as constants_module
13
+ from config.setup_project_paths_constants import (
14
+ ABORTED_NOTHING_WRITTEN_MESSAGE,
15
+ CONFIRMATION_PROMPT_TEXT,
16
+ ES_EXE_FOLDERS_ONLY_QUERY_ARGUMENTS,
17
+ STDERR_TRUNCATION_LENGTH,
18
+ WROTE_ENTRIES_STATUS_TEMPLATE,
19
+ )
20
+
21
+
22
+ def test_es_exe_arguments_is_immutable_tuple() -> None:
23
+ """Pin PR #230 round 6: constant must be a tuple, not a mutable list.
24
+
25
+ Tuples unpack identically into subprocess.run([...]) args and prevent
26
+ accidental mutation of the shared constant at call sites.
27
+ """
28
+ assert isinstance(ES_EXE_FOLDERS_ONLY_QUERY_ARGUMENTS, tuple)
29
+
30
+
31
+ def test_es_exe_arguments_contains_folders_only_flag() -> None:
32
+ assert "/ad" in ES_EXE_FOLDERS_ONLY_QUERY_ARGUMENTS
33
+
34
+
35
+ def test_es_exe_arguments_contains_git_folder_query() -> None:
36
+ assert "folder:.git" in ES_EXE_FOLDERS_ONLY_QUERY_ARGUMENTS
37
+
38
+
39
+ def test_es_exe_arguments_do_not_include_name_flag() -> None:
40
+ assert "-name" not in ES_EXE_FOLDERS_ONLY_QUERY_ARGUMENTS
41
+
42
+
43
+ def test_user_config_file_relative_parts_is_removed() -> None:
44
+ """Pin PR #230 round 7: dead constant USER_CONFIG_FILE_RELATIVE_PARTS removed.
45
+
46
+ This constant had zero references after round 6 moved the canonical
47
+ source to registry_file_path() in project_paths_reader.py.
48
+ """
49
+ assert not hasattr(constants_module, "USER_CONFIG_FILE_RELATIVE_PARTS")
50
+
51
+
52
+ def test_confirmation_prompt_text_constant_exists() -> None:
53
+ """Pin PR #230 round 7: prompt string extracted from prompt_and_write magic values."""
54
+ assert isinstance(CONFIRMATION_PROMPT_TEXT, str)
55
+ assert len(CONFIRMATION_PROMPT_TEXT) > 0
56
+
57
+
58
+ def test_aborted_nothing_written_message_constant_exists() -> None:
59
+ """Pin PR #230 round 7: abort message extracted from prompt_and_write magic values."""
60
+ assert isinstance(ABORTED_NOTHING_WRITTEN_MESSAGE, str)
61
+ assert len(ABORTED_NOTHING_WRITTEN_MESSAGE) > 0
62
+
63
+
64
+ def test_wrote_entries_status_template_constant_exists() -> None:
65
+ """Pin PR #230 round 7: success message template extracted from prompt_and_write magic values."""
66
+ assert isinstance(WROTE_ENTRIES_STATUS_TEMPLATE, str)
67
+ assert "{entry_count}" in WROTE_ENTRIES_STATUS_TEMPLATE
68
+ assert "{save_path}" in WROTE_ENTRIES_STATUS_TEMPLATE
69
+
70
+
71
+ def test_stderr_truncation_length_constant_exists() -> None:
72
+ """Pin PR #230 round 9: STDERR_TRUNCATION_LENGTH required for EverythingScanError messages."""
73
+ assert isinstance(STDERR_TRUNCATION_LENGTH, int)
74
+ assert STDERR_TRUNCATION_LENGTH > 0
@@ -7,6 +7,7 @@ from pathlib import Path
7
7
  SCRIPT_DIRECTORY = Path(__file__).resolve().parent
8
8
  if str(SCRIPT_DIRECTORY) not in sys.path:
9
9
  sys.path.insert(0, str(SCRIPT_DIRECTORY))
10
+ sys.modules.pop("config", None)
10
11
 
11
12
  import config
12
13
 
@@ -9,6 +9,7 @@ import pytest
9
9
  SCRIPT_DIRECTORY = Path(__file__).resolve().parent
10
10
  if str(SCRIPT_DIRECTORY) not in sys.path:
11
11
  sys.path.insert(0, str(SCRIPT_DIRECTORY))
12
+ sys.modules.pop("config", None)
12
13
 
13
14
  import gate_utils
14
15
 
@@ -9,6 +9,7 @@ import pytest
9
9
  SCRIPT_DIRECTORY = Path(__file__).resolve().parent
10
10
  if str(SCRIPT_DIRECTORY) not in sys.path:
11
11
  sys.path.insert(0, str(SCRIPT_DIRECTORY))
12
+ sys.modules.pop("config", None)
12
13
 
13
14
  import pre_commit
14
15
 
@@ -10,6 +10,7 @@ import pytest
10
10
  SCRIPT_DIRECTORY = Path(__file__).resolve().parent
11
11
  if str(SCRIPT_DIRECTORY) not in sys.path:
12
12
  sys.path.insert(0, str(SCRIPT_DIRECTORY))
13
+ sys.modules.pop("config", None)
13
14
 
14
15
  import pre_push
15
16
  import config
package/hooks/hooks.json CHANGED
@@ -60,6 +60,11 @@
60
60
  {
61
61
  "matcher": "Bash",
62
62
  "hooks": [
63
+ {
64
+ "type": "command",
65
+ "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/blocking/es_exe_path_rewriter.py",
66
+ "timeout": 10
67
+ },
63
68
  {
64
69
  "type": "command",
65
70
  "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/blocking/destructive_command_blocker.py",
@@ -106,6 +111,11 @@
106
111
  "type": "command",
107
112
  "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/session/plugin_data_dir_cleanup.py",
108
113
  "timeout": 10
114
+ },
115
+ {
116
+ "type": "command",
117
+ "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/session/untracked_repo_detector.py",
118
+ "timeout": 10
109
119
  }
110
120
  ]
111
121
  }