claude-dev-env 1.89.0 → 1.92.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 (77) hide show
  1. package/CLAUDE.md +0 -13
  2. package/_shared/pr-loop/scripts/code_rules_gate.py +106 -14
  3. package/_shared/pr-loop/scripts/pr_loop_shared_constants/code_rules_gate_constants.py +26 -0
  4. package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +160 -0
  5. package/agents/clean-coder.md +2 -2
  6. package/agents/code-verifier.md +0 -1
  7. package/agents/test_agent_frontmatter.py +78 -0
  8. package/audit-rubrics/category_rubrics/category-j-code-rules-compliance.md +1 -1
  9. package/audit-rubrics/prompts/category-j-code-rules-compliance.md +1 -1
  10. package/bin/install.mjs +1 -0
  11. package/docs/CODE_RULES.md +2 -2
  12. package/hooks/blocking/CLAUDE.md +6 -2
  13. package/hooks/blocking/code_rules_comments.py +2 -2
  14. package/hooks/blocking/code_verifier_spawn_preflight_gate.py +44 -0
  15. package/hooks/blocking/config/verified_commit_constants.py +2 -0
  16. package/hooks/blocking/conftest.py +115 -0
  17. package/hooks/blocking/nas_ssh_binary_enforcer.py +191 -0
  18. package/hooks/blocking/pr_description_enforcer.py +46 -22
  19. package/hooks/blocking/pr_description_pr_number.py +5 -3
  20. package/hooks/blocking/pr_description_proof_of_work.py +367 -0
  21. package/hooks/blocking/precommit_code_rules_gate.py +5 -1
  22. package/hooks/blocking/test_code_rules_enforcer_comment_string_awareness.py +8 -2
  23. package/hooks/blocking/test_code_verifier_spawn_preflight_gate.py +71 -0
  24. package/hooks/blocking/test_nas_ssh_binary_enforcer.py +168 -0
  25. package/hooks/blocking/test_pr_description_enforcer_proof_gate.py +175 -0
  26. package/hooks/blocking/test_pr_description_proof_of_work.py +162 -0
  27. package/hooks/blocking/test_precommit_code_rules_gate.py +89 -0
  28. package/hooks/blocking/test_verdict_directory_write_blocker.py +4 -0
  29. package/hooks/blocking/test_verification_verdict_store.py +11 -0
  30. package/hooks/blocking/test_verified_commit_config_bootstrap.py +49 -0
  31. package/hooks/blocking/test_verified_commit_gate.py +11 -0
  32. package/hooks/blocking/test_verifier_verdict_minter.py +11 -0
  33. package/hooks/blocking/test_volatile_path_in_post_blocker.py +210 -0
  34. package/hooks/blocking/verdict_directory_write_blocker.py +6 -0
  35. package/hooks/blocking/verification_verdict_store.py +73 -5
  36. package/hooks/blocking/verified_commit_config_bootstrap.py +51 -0
  37. package/hooks/blocking/verified_commit_gate.py +6 -0
  38. package/hooks/blocking/verifier_verdict_minter.py +6 -0
  39. package/hooks/blocking/volatile_path_in_post_blocker.py +351 -0
  40. package/hooks/hooks.json +32 -2
  41. package/hooks/hooks_constants/CLAUDE.md +4 -0
  42. package/hooks/hooks_constants/code_rules_path_utils_constants.py +1 -0
  43. package/hooks/hooks_constants/code_verifier_spawn_preflight_gate_constants.py +3 -0
  44. package/hooks/hooks_constants/enter_worktree_prefetch_constants.py +18 -0
  45. package/hooks/hooks_constants/nas_ssh_binary_enforcer_constants.py +59 -0
  46. package/hooks/hooks_constants/pr_description_enforcer_constants.py +2 -0
  47. package/hooks/hooks_constants/pr_description_proof_of_work_constants.py +111 -0
  48. package/hooks/hooks_constants/volatile_path_in_post_blocker_constants.py +48 -0
  49. package/hooks/lifecycle/CLAUDE.md +3 -1
  50. package/hooks/lifecycle/enter_worktree_origin_prefetch.py +146 -0
  51. package/hooks/lifecycle/test_enter_worktree_origin_prefetch.py +178 -0
  52. package/hooks/validators/run_all_validators.py +216 -4
  53. package/hooks/validators/test_run_all_validators_pretooluse.py +102 -0
  54. package/package.json +1 -1
  55. package/rules/CLAUDE.md +3 -0
  56. package/rules/durable-post-artifacts.md +35 -0
  57. package/rules/nas-ssh-invocation.md +21 -0
  58. package/rules/proof-of-work-pr-comments.md +26 -0
  59. package/scripts/CLAUDE.md +2 -0
  60. package/scripts/claude-chain.example.json +8 -0
  61. package/scripts/claude_chain_runner.py +400 -0
  62. package/scripts/dev_env_scripts_constants/CLAUDE.md +2 -0
  63. package/scripts/dev_env_scripts_constants/claude_chain_constants.py +124 -0
  64. package/scripts/dev_env_scripts_constants/gh_artifact_upload_constants.py +43 -0
  65. package/scripts/gh_artifact_upload.py +256 -0
  66. package/scripts/sync_to_cursor/rules.py +1 -1
  67. package/scripts/test_claude_chain_runner.py +472 -0
  68. package/scripts/tests/test_gh_artifact_upload.py +205 -0
  69. package/skills/CLAUDE.md +3 -0
  70. package/skills/team-advisor/SKILL.md +188 -0
  71. package/skills/team-advisor-refresh/SKILL.md +25 -0
  72. package/skills/usage-pause/SKILL.md +108 -0
  73. package/skills/usage-pause/scripts/resolve_usage_window.py +462 -0
  74. package/skills/usage-pause/scripts/test_resolve_usage_window.py +278 -0
  75. package/skills/usage-pause/scripts/usage_pause_constants/__init__.py +1 -0
  76. package/skills/usage-pause/scripts/usage_pause_constants/resolve_usage_window_constants.py +65 -0
  77. package/system-prompts/software-engineer.xml +3 -2
@@ -0,0 +1,210 @@
1
+ """Unit and integration tests for the volatile_path_in_post_blocker PreToolUse hook."""
2
+
3
+ import importlib.util
4
+ import json
5
+ import pathlib
6
+ import subprocess
7
+ import sys
8
+
9
+ _HOOK_DIR = pathlib.Path(__file__).parent
10
+ if str(_HOOK_DIR) not in sys.path:
11
+ sys.path.insert(0, str(_HOOK_DIR))
12
+
13
+ hook_spec = importlib.util.spec_from_file_location(
14
+ "volatile_path_in_post_blocker",
15
+ _HOOK_DIR / "volatile_path_in_post_blocker.py",
16
+ )
17
+ assert hook_spec is not None
18
+ assert hook_spec.loader is not None
19
+ hook_module = importlib.util.module_from_spec(hook_spec)
20
+ hook_spec.loader.exec_module(hook_module)
21
+
22
+ scan_text_for_volatile_marker = hook_module.scan_text_for_volatile_marker
23
+ extract_gh_post_body_texts = hook_module.extract_gh_post_body_texts
24
+ extract_mcp_body_texts = hook_module.extract_mcp_body_texts
25
+ _collect_body_texts = hook_module._collect_body_texts
26
+
27
+
28
+ def _body_names_volatile_path(tool_name: str, tool_input: dict[str, object]) -> bool:
29
+ all_body_texts = _collect_body_texts(tool_name, tool_input)
30
+ return hook_module._first_volatile_marker(all_body_texts) is not None
31
+
32
+
33
+ def test_scan_detects_job_scratch_path_backslash() -> None:
34
+ text = r"See C:\Users\jon\.claude-editor\jobs\95762cea\tmp\staging\contact_sheet.png"
35
+ assert scan_text_for_volatile_marker(text) == ".claude-editor/jobs/"
36
+
37
+
38
+ def test_scan_detects_job_scratch_path_forward_slash() -> None:
39
+ text = "artifact at /home/user/.claude-editor/jobs/abc/tmp/out.png"
40
+ assert scan_text_for_volatile_marker(text) == ".claude-editor/jobs/"
41
+
42
+
43
+ def test_scan_detects_worktree_path() -> None:
44
+ text = r"edited .claude\worktrees\feature\file.py"
45
+ assert scan_text_for_volatile_marker(text) == ".claude/worktrees/"
46
+
47
+
48
+ def test_scan_detects_appdata_temp_case_insensitive() -> None:
49
+ text = r"C:\Users\jon\AppData\Local\Temp\bugteam\worktree"
50
+ assert scan_text_for_volatile_marker(text) == "appdata/local/temp"
51
+
52
+
53
+ def test_scan_detects_unix_tmp() -> None:
54
+ assert scan_text_for_volatile_marker("output written to /tmp/run/out.log") == "/tmp/"
55
+
56
+
57
+ def test_scan_detects_percent_temp_token() -> None:
58
+ assert scan_text_for_volatile_marker("saved to %TEMP%\\out.txt") == "%temp%"
59
+
60
+
61
+ def test_scan_detects_env_temp_token() -> None:
62
+ assert scan_text_for_volatile_marker("path is $env:TEMP\\out.txt") == "$env:temp"
63
+
64
+
65
+ def test_scan_detects_claude_job_dir_token() -> None:
66
+ assert scan_text_for_volatile_marker("see $CLAUDE_JOB_DIR/staging/report.md") == "$claude_job_dir"
67
+
68
+
69
+ def test_scan_clean_body_returns_none() -> None:
70
+ text = "The failing table is pasted below:\n| case | result |\n| a | pass |"
71
+ assert scan_text_for_volatile_marker(text) is None
72
+
73
+
74
+ def test_gh_comment_with_job_scratch_path_is_blocked() -> None:
75
+ command = (
76
+ 'gh pr comment 669 --body "Contact sheet at '
77
+ r'C:\Users\jon\.claude-editor\jobs\95762cea\tmp\staging\contact_sheet.png"'
78
+ )
79
+ assert _body_names_volatile_path("Bash", {"command": command})
80
+
81
+
82
+ def test_gh_comment_with_clean_body_is_allowed() -> None:
83
+ command = 'gh pr comment 669 --body "All checks pass. Table pasted inline above."'
84
+ assert not _body_names_volatile_path("Bash", {"command": command})
85
+
86
+
87
+ def test_gh_pr_create_short_flag_blocked() -> None:
88
+ command = 'gh pr create --title "T" -b "logs under /tmp/run/out.log"'
89
+ assert _body_names_volatile_path("Bash", {"command": command})
90
+
91
+
92
+ def test_gh_issue_create_body_equals_form_blocked() -> None:
93
+ command = 'gh issue create --title "T" --body="dump in %TEMP%\\dump.json"'
94
+ assert _body_names_volatile_path("Bash", {"command": command})
95
+
96
+
97
+ def test_body_file_content_is_scanned_and_blocked(tmp_path: pathlib.Path) -> None:
98
+ body_file = tmp_path / "body.md"
99
+ body_file.write_text(
100
+ "Artifact: $CLAUDE_JOB_DIR/tmp/contact_sheet.png",
101
+ encoding="utf-8",
102
+ )
103
+ command = f"gh pr comment 669 --body-file {body_file}"
104
+ assert _body_names_volatile_path("Bash", {"command": command})
105
+
106
+
107
+ def test_body_file_clean_content_is_allowed(tmp_path: pathlib.Path) -> None:
108
+ body_file = tmp_path / "body.md"
109
+ body_file.write_text("Everything is green. Results inline above.", encoding="utf-8")
110
+ command = f"gh pr comment 669 --body-file {body_file}"
111
+ assert not _body_names_volatile_path("Bash", {"command": command})
112
+
113
+
114
+ def test_non_post_gh_command_is_untouched() -> None:
115
+ assert not _body_names_volatile_path("Bash", {"command": "gh pr list --repo owner/repo"})
116
+
117
+
118
+ def test_gh_pr_view_with_tmp_in_flag_is_untouched() -> None:
119
+ command = "gh pr view 10 --json body --jq .body > /tmp/out.json"
120
+ assert not _body_names_volatile_path("Bash", {"command": command})
121
+
122
+
123
+ def test_substring_mention_of_gh_post_does_not_classify() -> None:
124
+ command = 'echo "example: gh pr comment 42 --body \\"see /tmp/x\\"" >> notes.txt'
125
+ assert not _body_names_volatile_path("Bash", {"command": command})
126
+
127
+
128
+ def test_gh_word_not_first_token_does_not_classify() -> None:
129
+ command = r'echo gh pr comment 42 --body "see C:\.claude-editor\jobs\x"'
130
+ assert not _body_names_volatile_path("Bash", {"command": command})
131
+
132
+
133
+ def test_env_assignment_prefix_still_classifies() -> None:
134
+ command = 'GH_TOKEN=abc gh pr comment 669 --body "log at /tmp/run.log"'
135
+ assert _body_names_volatile_path("Bash", {"command": command})
136
+
137
+
138
+ def test_unparseable_command_is_allowed() -> None:
139
+ assert not _body_names_volatile_path(
140
+ "Bash", {"command": "gh pr comment 1 --body 'unterminated"}
141
+ )
142
+
143
+
144
+ def test_mcp_issue_comment_body_blocked() -> None:
145
+ tool_input: dict[str, object] = {"body": "artifact at $CLAUDE_JOB_DIR/tmp/out.png"}
146
+ assert _body_names_volatile_path("mcp__plugin_github_github__add_issue_comment", tool_input)
147
+
148
+
149
+ def test_mcp_review_comment_param_blocked() -> None:
150
+ tool_input: dict[str, object] = {"comment": r"see .claude\worktrees\x\file"}
151
+ assert _body_names_volatile_path(
152
+ "mcp__plugin_github_github__add_reply_to_pull_request_comment", tool_input
153
+ )
154
+
155
+
156
+ def test_mcp_clean_body_allowed() -> None:
157
+ tool_input: dict[str, object] = {"body": "LGTM, results pasted inline."}
158
+ assert not _body_names_volatile_path("mcp__plugin_github_github__add_issue_comment", tool_input)
159
+
160
+
161
+ def test_mcp_read_tool_without_body_allowed() -> None:
162
+ tool_input: dict[str, object] = {"pullNumber": 10}
163
+ assert not _body_names_volatile_path("mcp__plugin_github_github__pull_request_read", tool_input)
164
+
165
+
166
+ def test_extract_mcp_body_texts_skips_non_string_values() -> None:
167
+ tool_input: dict[str, object] = {"body": None, "comment": 42}
168
+ assert extract_mcp_body_texts(tool_input) == []
169
+
170
+
171
+ def test_extract_gh_post_body_texts_returns_inline_and_file(tmp_path: pathlib.Path) -> None:
172
+ body_file = tmp_path / "b.md"
173
+ body_file.write_text("file body text", encoding="utf-8")
174
+ command = f'gh pr create --title "T" --body "inline body" --body-file {body_file}'
175
+ all_texts = extract_gh_post_body_texts(command)
176
+ assert "inline body" in all_texts
177
+ assert "file body text" in all_texts
178
+
179
+
180
+ def test_hook_subprocess_denies_volatile_gh_comment() -> None:
181
+ payload = {
182
+ "tool_name": "Bash",
183
+ "tool_input": {
184
+ "command": 'gh pr comment 669 --body "art at $CLAUDE_JOB_DIR/tmp/x.png"',
185
+ },
186
+ }
187
+ completion = subprocess.run(
188
+ [sys.executable, str(_HOOK_DIR / "volatile_path_in_post_blocker.py")],
189
+ input=json.dumps(payload),
190
+ capture_output=True,
191
+ text=True,
192
+ check=False,
193
+ )
194
+ decision = json.loads(completion.stdout)
195
+ assert decision["hookSpecificOutput"]["permissionDecision"] == "deny"
196
+
197
+
198
+ def test_hook_subprocess_allows_clean_gh_comment() -> None:
199
+ payload = {
200
+ "tool_name": "Bash",
201
+ "tool_input": {"command": 'gh pr comment 669 --body "all green, results inline"'},
202
+ }
203
+ completion = subprocess.run(
204
+ [sys.executable, str(_HOOK_DIR / "volatile_path_in_post_blocker.py")],
205
+ input=json.dumps(payload),
206
+ capture_output=True,
207
+ text=True,
208
+ check=False,
209
+ )
210
+ assert completion.stdout.strip() == ""
@@ -41,6 +41,12 @@ hooks_directory = str(Path(__file__).resolve().parent.parent)
41
41
  if hooks_directory not in sys.path:
42
42
  sys.path.append(hooks_directory)
43
43
 
44
+ from verified_commit_config_bootstrap import ( # noqa: E402
45
+ register_verified_commit_constants,
46
+ )
47
+
48
+ register_verified_commit_constants()
49
+
44
50
  from config.verified_commit_constants import ( # noqa: E402
45
51
  ALL_GATED_TOOL_NAMES,
46
52
  ALL_VERDICT_PATH_SEGMENT_BODIES,
@@ -25,14 +25,22 @@ blocking_directory = str(Path(__file__).resolve().parent)
25
25
  if blocking_directory not in sys.path:
26
26
  sys.path.insert(0, blocking_directory)
27
27
 
28
+ from verified_commit_config_bootstrap import ( # noqa: E402
29
+ register_verified_commit_constants,
30
+ )
31
+
32
+ register_verified_commit_constants()
33
+
28
34
  from config.verified_commit_constants import (
29
35
  AGENT_META_SIDECAR_SUFFIX,
30
36
  AGENT_META_TYPE_KEY,
31
37
  AGENT_TRANSCRIPT_GLOB,
32
38
  BRANCH_REFERENCE_PREFIX,
39
+ BRANCH_REMOTE_CONFIG_KEY_TEMPLATE,
33
40
  BRANCH_WORKTREE_ABSENT_MESSAGE,
34
41
  CLAUDE_HOME_DIRECTORY_NAME,
35
42
  CONFTEST_FILE_NAME,
43
+ DETACHED_HEAD_LABEL,
36
44
  DOCS_ONLY_EXTENSIONS,
37
45
  ALL_FALLBACK_BASE_REFERENCES,
38
46
  EMPTY_SURFACE_GUARD_MESSAGE,
@@ -122,13 +130,65 @@ def _tracked_upstream_reference(repo_root: str) -> str | None:
122
130
  )
123
131
 
124
132
 
133
+ def _head_branch_name(repo_root: str) -> str | None:
134
+ """Read the short name of the branch HEAD points at.
135
+
136
+ Args:
137
+ repo_root: The repository top-level directory.
138
+
139
+ Returns:
140
+ The short branch name, or None when HEAD is detached or git fails.
141
+ """
142
+ branch_name = run_git(repo_root, "rev-parse", "--abbrev-ref", "HEAD")
143
+ if not branch_name or branch_name == DETACHED_HEAD_LABEL:
144
+ return None
145
+ return branch_name
146
+
147
+
148
+ def _upstream_is_own_remote_counterpart(repo_root: str, tracked_upstream: str) -> bool:
149
+ """Decide whether HEAD's upstream is the current branch's own remote ref.
150
+
151
+ ::
152
+
153
+ branch feature tracks origin/feature -> True (own counterpart)
154
+ branch feature tracks origin/develop -> False (different branch)
155
+
156
+ A branch tracking its own remote counterpart makes the merge base against
157
+ that ref resolve to the branch tip itself, masking the branch's real state
158
+ versus the default branch — so the caller ranks that ref below the
159
+ default-branch fallbacks.
160
+
161
+ Args:
162
+ repo_root: The repository top-level directory.
163
+ tracked_upstream: HEAD's configured upstream tracking reference.
164
+
165
+ Returns:
166
+ True when the upstream names ``<remote>/<current-branch>``; False when
167
+ it names a different branch, HEAD is detached, or git fails.
168
+ """
169
+ branch_name = _head_branch_name(repo_root)
170
+ if branch_name is None:
171
+ return False
172
+ configured_remote = run_git(
173
+ repo_root, "config", BRANCH_REMOTE_CONFIG_KEY_TEMPLATE.format(branch_name=branch_name)
174
+ )
175
+ if not configured_remote:
176
+ return False
177
+ return tracked_upstream == f"{configured_remote}/{branch_name}"
178
+
179
+
125
180
  def candidate_base_references(repo_root: str) -> tuple[str, ...]:
126
181
  """Collect the upstream references to probe for the merge base, in order.
127
182
 
128
- Probes ``origin/HEAD`` first, then HEAD's configured upstream tracking
129
- reference (so a non-standard default branch like ``origin/develop`` is
130
- found regardless of its name), then the fixed ``origin/main`` /
131
- ``origin/master`` fallbacks for checkouts with no tracking ref set.
183
+ Probes ``origin/HEAD`` first, then a tracked upstream that names a
184
+ different branch (so a differently-named default like ``origin/develop`` is
185
+ found), then the fixed ``origin/main`` / ``origin/master`` fallbacks. A
186
+ tracked upstream that is the current branch's own remote counterpart
187
+ (``origin/<current-branch>``) ranks last, below the fallbacks: on a PR
188
+ branch whose fork carries ``origin/main`` the merge base comes from
189
+ ``origin/main`` rather than the branch's stale pushed ref, while a repo
190
+ whose default branch is the tracked branch itself (no ``origin/main`` or
191
+ ``origin/master`` present) still falls through to that own-counterpart ref.
132
192
 
133
193
  Args:
134
194
  repo_root: The repository top-level directory.
@@ -138,10 +198,18 @@ def candidate_base_references(repo_root: str) -> tuple[str, ...]:
138
198
  """
139
199
  upstream_head = run_git(repo_root, "symbolic-ref", "--quiet", "refs/remotes/origin/HEAD")
140
200
  tracked_upstream = _tracked_upstream_reference(repo_root)
201
+ high_priority_upstream: tuple[str, ...] = ()
202
+ demoted_upstream: tuple[str, ...] = ()
203
+ if tracked_upstream:
204
+ if _upstream_is_own_remote_counterpart(repo_root, tracked_upstream):
205
+ demoted_upstream = (tracked_upstream,)
206
+ else:
207
+ high_priority_upstream = (tracked_upstream,)
141
208
  ordered_references = (
142
209
  ((upstream_head,) if upstream_head else ())
143
- + ((tracked_upstream,) if tracked_upstream else ())
210
+ + high_priority_upstream
144
211
  + ALL_FALLBACK_BASE_REFERENCES
212
+ + demoted_upstream
145
213
  )
146
214
  return tuple(dict.fromkeys(ordered_references))
147
215
 
@@ -0,0 +1,51 @@
1
+ """Deterministic loader for the verified-commit constants module.
2
+
3
+ The blocking hooks import their shared constants as
4
+ ``from config.verified_commit_constants import ...``. In the installed hook
5
+ tree a second, unrelated ``config`` package can sit ahead of this package on
6
+ ``sys.path`` and win that dotted name by path order, so the import binds to the
7
+ wrong file and raises ImportError on any constant the stale copy lacks. This
8
+ module binds the dotted name to the sibling ``config/verified_commit_constants``
9
+ file by explicit location, so resolution never depends on ``sys.path`` order.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import importlib.util
15
+ import sys
16
+ from pathlib import Path
17
+
18
+
19
+ def register_verified_commit_constants() -> None:
20
+ """Bind ``config.verified_commit_constants`` to the sibling config file.
21
+
22
+ ::
23
+
24
+ sys.path = ["<installed hooks>", "<blocking>"] # foreign config first
25
+ register_verified_commit_constants()
26
+ from config.verified_commit_constants import DETACHED_HEAD_LABEL
27
+ ok: bound to <blocking>/config/verified_commit_constants.py
28
+
29
+ A ``from config.verified_commit_constants import`` that follows this call
30
+ reads the entry straight from ``sys.modules``, so it resolves to this file
31
+ whatever else owns the ``config`` name. An entry already cached is left in
32
+ place, so the call is idempotent and never displaces a module a caller
33
+ loaded on purpose.
34
+
35
+ Returns:
36
+ None. The effect is the ``sys.modules`` registration.
37
+ """
38
+ config_module_dotted_name = "config.verified_commit_constants"
39
+ if config_module_dotted_name in sys.modules:
40
+ return
41
+ constants_file_path = (
42
+ Path(__file__).resolve().parent / "config" / "verified_commit_constants.py"
43
+ )
44
+ module_spec = importlib.util.spec_from_file_location(
45
+ config_module_dotted_name, constants_file_path
46
+ )
47
+ if module_spec is None or module_spec.loader is None:
48
+ return
49
+ constants_module = importlib.util.module_from_spec(module_spec)
50
+ sys.modules[config_module_dotted_name] = constants_module
51
+ module_spec.loader.exec_module(constants_module)
@@ -42,6 +42,12 @@ _hooks_dir = str(Path(__file__).resolve().parent.parent)
42
42
  if _hooks_dir not in sys.path:
43
43
  sys.path.insert(0, _hooks_dir)
44
44
 
45
+ from verified_commit_config_bootstrap import ( # noqa: E402
46
+ register_verified_commit_constants,
47
+ )
48
+
49
+ register_verified_commit_constants()
50
+
45
51
  from config.verified_commit_constants import (
46
52
  ALL_GIT_BINARY_NAMES,
47
53
  CORRECTIVE_MESSAGE,
@@ -35,6 +35,12 @@ blocking_directory = str(Path(__file__).resolve().parent)
35
35
  if blocking_directory not in sys.path:
36
36
  sys.path.insert(0, blocking_directory)
37
37
 
38
+ from verified_commit_config_bootstrap import ( # noqa: E402
39
+ register_verified_commit_constants,
40
+ )
41
+
42
+ register_verified_commit_constants()
43
+
38
44
  from config.verified_commit_constants import (
39
45
  MINTING_AGENT_TYPE,
40
46
  VERDICT_KEY_MANIFEST_SHA256,