claude-dev-env 1.42.0 → 1.44.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.
- package/_shared/pr-loop/scripts/_claude_permissions_common.py +1 -5
- package/_shared/pr-loop/scripts/code_rules_gate.py +293 -8
- package/_shared/pr-loop/scripts/fix_hookspath.py +96 -5
- package/_shared/pr-loop/scripts/grant_project_claude_permissions.py +3 -16
- package/_shared/pr-loop/scripts/post_audit_thread.py +4 -4
- package/_shared/pr-loop/scripts/{config → pr_loop_shared_constants}/claude_permissions_constants.py +1 -1
- package/_shared/pr-loop/scripts/preflight.py +13 -31
- package/_shared/pr-loop/scripts/reviews_disabled.py +2 -16
- package/_shared/pr-loop/scripts/revoke_project_claude_permissions.py +3 -16
- package/_shared/pr-loop/scripts/tests/conftest.py +1 -51
- package/_shared/pr-loop/scripts/tests/test_agent_config_carveout.py +4 -4
- package/_shared/pr-loop/scripts/tests/test_claude_permissions_common.py +4 -2
- package/_shared/pr-loop/scripts/tests/test_claude_permissions_constants.py +4 -2
- package/_shared/pr-loop/scripts/tests/test_claude_settings_keys_constants.py +4 -2
- package/_shared/pr-loop/scripts/tests/test_code_rules_gate_constants.py +4 -2
- package/_shared/pr-loop/scripts/tests/test_fix_hookspath_constants.py +6 -2
- package/_shared/pr-loop/scripts/tests/test_grant_project_claude_permissions.py +2 -2
- package/_shared/pr-loop/scripts/tests/test_post_audit_thread.py +1 -2
- package/_shared/pr-loop/scripts/tests/test_post_audit_thread_constants.py +4 -2
- package/_shared/pr-loop/scripts/tests/test_preflight.py +17 -52
- package/_shared/pr-loop/scripts/tests/test_preflight_constants.py +6 -2
- package/_shared/pr-loop/scripts/tests/test_revoke_project_claude_permissions.py +2 -2
- package/agents/pr-description-writer.md +50 -140
- package/docs/PR_DESCRIPTION_GUIDE.md +101 -102
- package/hooks/_gh_pr_author_swap_utils.py +1 -1
- package/hooks/blocking/bot_mention_comment_blocker.py +4 -10
- package/hooks/blocking/code_rules_enforcer.py +217 -99
- package/hooks/blocking/code_rules_path_utils.py +8 -1
- package/hooks/blocking/destructive_command_blocker.py +1 -1
- package/hooks/blocking/es_exe_path_rewriter.py +7 -13
- package/hooks/blocking/gh_body_arg_blocker.py +6 -1
- package/hooks/blocking/gh_pr_author_enforcer.py +5 -5
- package/hooks/blocking/gh_pr_author_restore.py +5 -5
- package/hooks/blocking/hedging_language_blocker.py +4 -10
- package/hooks/blocking/md_path_exemptions.py +205 -0
- package/hooks/blocking/md_to_html_blocker.py +48 -20
- package/hooks/blocking/pr_converge_bugteam_enforcer.py +5 -11
- package/hooks/blocking/pr_description_enforcer.py +626 -41
- package/hooks/blocking/question_to_user_enforcer.py +4 -10
- package/hooks/blocking/state_description_blocker.py +6 -12
- package/hooks/blocking/tdd_enforcer.py +1 -1
- package/hooks/blocking/test_bot_mention_comment_blocker.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer.py +3 -3
- package/hooks/blocking/test_code_rules_enforcer_any_exempt_files.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +0 -2
- package/hooks/blocking/test_code_rules_enforcer_comment_string_awareness.py +184 -0
- package/hooks/blocking/test_code_rules_enforcer_type_checking_scope.py +82 -0
- package/hooks/blocking/test_code_rules_enforcer_unused_imports.py +29 -29
- package/hooks/blocking/test_gh_body_arg_blocker.py +7 -8
- package/hooks/blocking/test_gh_pr_author_enforcer.py +1 -1
- package/hooks/blocking/test_gh_pr_author_restore.py +1 -1
- package/hooks/blocking/test_hedging_language_blocker.py +2 -2
- package/hooks/blocking/test_md_to_html_blocker.py +463 -8
- package/hooks/blocking/test_pr_converge_bugteam_enforcer.py +1 -1
- package/hooks/blocking/test_pr_description_enforcer.py +1210 -13
- package/hooks/blocking/test_question_to_user_enforcer.py +1 -1
- package/hooks/blocking/windows_rmtree_blocker.py +5 -11
- package/hooks/diagnostic/hook_log_extractor.py +1 -1
- package/hooks/diagnostic/hook_log_init.py +1 -1
- package/hooks/diagnostic/hook_log_stop_wrapper.py +1 -1
- package/hooks/diagnostic/test_hook_log_extractor.py +1 -1
- package/hooks/diagnostic/test_hook_log_init.py +2 -2
- package/hooks/diagnostic/test_hook_log_stop_wrapper.py +1 -1
- package/hooks/git-hooks/gate_utils.py +1 -1
- package/hooks/git-hooks/pre_commit.py +1 -1
- package/hooks/git-hooks/pre_push.py +1 -1
- package/hooks/git-hooks/test_config.py +5 -5
- package/hooks/git-hooks/test_pre_push.py +6 -6
- package/hooks/{config → hooks_constants}/code_rules_enforcer_constants.py +37 -0
- package/hooks/hooks_constants/code_rules_path_utils_constants.py +28 -0
- package/hooks/hooks_constants/md_to_html_blocker_constants.py +82 -0
- package/hooks/{config → hooks_constants}/pr_converge_bugteam_enforcer_state.py +1 -1
- package/hooks/hooks_constants/pr_description_enforcer_constants.py +154 -0
- package/hooks/{config → hooks_constants}/pre_tool_use_stdin.py +1 -1
- package/hooks/{config → hooks_constants}/project_paths_reader.py +2 -2
- package/hooks/{config → hooks_constants}/test_banned_identifiers_constants.py +1 -1
- package/hooks/{config → hooks_constants}/test_dynamic_stderr_handler.py +1 -1
- package/hooks/{config → hooks_constants}/test_hardcoded_user_path_constants.py +1 -1
- package/hooks/{config → hooks_constants}/test_hook_log_extractor_constants.py +2 -2
- package/hooks/hooks_constants/test_md_to_html_blocker_constants.py +110 -0
- package/hooks/{config → hooks_constants}/test_messages.py +2 -6
- package/hooks/{config → hooks_constants}/test_path_rewriter_constants.py +1 -1
- package/hooks/hooks_constants/test_pr_description_enforcer_constants.py +292 -0
- package/hooks/{config → hooks_constants}/test_pre_tool_use_stdin.py +2 -2
- package/hooks/{config → hooks_constants}/test_project_paths_reader.py +3 -3
- package/hooks/{config → hooks_constants}/test_session_env_cleanup_constants.py +1 -1
- package/hooks/{config → hooks_constants}/test_setup_project_paths_constants.py +2 -2
- package/hooks/{config → hooks_constants}/test_unused_module_import_constants.py +1 -1
- package/hooks/lifecycle/pr_converge_bugteam_skill_tracker.py +5 -11
- package/hooks/lifecycle/test_pr_converge_bugteam_skill_tracker.py +1 -1
- package/hooks/session/gh_pr_author_session_cleanup.py +5 -6
- package/hooks/session/session_env_cleanup.py +4 -10
- package/hooks/session/test_gh_pr_author_session_cleanup.py +1 -1
- package/hooks/session/test_untracked_repo_detector.py +2 -2
- package/hooks/session/untracked_repo_detector.py +6 -12
- package/hooks/test__gh_pr_author_swap_utils.py +1 -1
- package/hooks/validators/run_all_validators.py +16 -5
- package/hooks/validators/test_output_formatter.py +46 -0
- package/hooks/workflow/doc_gist_auto_publish.py +1 -1
- package/hooks/workflow/md_to_html_companion.py +8 -15
- package/hooks/workflow/test_md_to_html_companion.py +184 -23
- package/package.json +1 -1
- package/rules/ask-user-question-required.md +1 -1
- package/rules/vault-context.md +1 -1
- package/scripts/{config → dev_env_scripts_constants}/timing.py +1 -1
- package/scripts/setup_project_paths.py +49 -11
- package/scripts/sweep_empty_dirs.py +10 -1
- package/scripts/test_setup_project_paths.py +2 -2
- package/scripts/test_sweep_empty_dirs.py +2 -6
- package/skills/_shared/pr-loop/scripts/_path_resolver.py +1 -1
- package/skills/_shared/pr-loop/scripts/build_audit_prompt.py +1 -1
- package/skills/_shared/pr-loop/scripts/build_fix_prompt.py +1 -1
- package/skills/_shared/pr-loop/scripts/init_loop_state.py +1 -1
- package/skills/_shared/pr-loop/scripts/teardown_worktrees.py +1 -1
- package/skills/_shared/pr-loop/scripts/write_audit_outcomes.py +2 -2
- package/skills/_shared/pr-loop/scripts/write_fix_outcomes.py +2 -2
- package/skills/bugteam/PROMPTS.md +1 -1
- package/skills/bugteam/SKILL.md +1 -1
- package/skills/bugteam/reference/github-pr-reviews.md +1 -1
- package/skills/bugteam/scripts/{_claude_permissions_common.py → _bugteam_permissions_common.py} +1 -13
- package/skills/bugteam/scripts/bugteam_code_rules_gate.py +1 -13
- package/skills/bugteam/scripts/bugteam_fix_hookspath.py +1 -16
- package/skills/bugteam/scripts/bugteam_preflight.py +1 -13
- package/skills/bugteam/scripts/grant_project_claude_permissions.py +2 -8
- package/skills/bugteam/scripts/probe_code_rules_enforcer_check.py +1 -1
- package/skills/bugteam/scripts/reflow_skill_md.py +1 -1
- package/skills/bugteam/scripts/revoke_project_claude_permissions.py +2 -8
- package/skills/bugteam/scripts/{test__claude_permissions_common.py → test__bugteam_permissions_common.py} +4 -4
- package/skills/bugteam/scripts/test_agent_config_carveout.py +2 -2
- package/skills/bugteam/scripts/test_bugteam_fix_hookspath.py +0 -26
- package/skills/bugteam/scripts/{test_claude_permissions_common.py → test_bugteam_permissions_common.py} +3 -66
- package/skills/bugteam/scripts/test_bugteam_preflight.py +2 -27
- package/skills/bugteam/scripts/windows_safe_rmtree.py +1 -1
- package/skills/doc-gist/SKILL.md +1 -1
- package/skills/doc-gist/scripts/gist_upload.py +1 -1
- package/skills/implement/SKILL.md +2 -2
- package/skills/implement/scripts/append_note.py +1 -1
- package/skills/pr-converge/pr_converge_skill_constants/__init__.py +0 -0
- package/skills/pr-converge/{config → pr_converge_skill_constants}/constants.py +1 -1
- package/skills/pr-converge/scripts/check_bugbot_ci.py +1 -1
- package/skills/pr-converge/scripts/check_convergence.py +11 -4
- package/skills/pr-converge/scripts/check_pending_reviews.py +1 -1
- package/skills/pr-converge/scripts/fetch_copilot_reviews.py +1 -1
- package/skills/pr-converge/scripts/post_fix_reply.py +1 -1
- package/skills/pr-converge/scripts/pr_converge_scripts_constants/__init__.py +0 -0
- package/skills/pr-converge/scripts/{config → pr_converge_scripts_constants}/pr_converge_constants.py +1 -1
- package/skills/pr-converge/scripts/reflow_skill_md.py +90 -16
- package/skills/pr-converge/scripts/test_check_convergence.py +18 -0
- package/skills/pr-converge/scripts/test_reflow_skill_md.py +0 -31
- package/skills/pre-compact/SKILL.md +114 -0
- package/skills/session-log/SKILL.md +98 -233
- package/hooks/config/pr_description_enforcer_constants.py +0 -19
- package/hooks/config/test_pr_description_enforcer_constants.py +0 -82
- package/skills/bugteam/scripts/test_grant_project_claude_permissions.py +0 -55
- package/skills/bugteam/scripts/test_revoke_project_claude_permissions.py +0 -55
- package/skills/pr-converge/scripts/conftest.py +0 -60
- package/skills/pr-converge/scripts/evict_cached_config_modules.py +0 -20
- package/skills/pr-converge/scripts/test_evict_cached_config_modules.py +0 -22
- /package/_shared/pr-loop/scripts/{config → pr_loop_shared_constants}/__init__.py +0 -0
- /package/_shared/pr-loop/scripts/{config → pr_loop_shared_constants}/claude_settings_keys_constants.py +0 -0
- /package/_shared/pr-loop/scripts/{config → pr_loop_shared_constants}/code_rules_gate_constants.py +0 -0
- /package/_shared/pr-loop/scripts/{config → pr_loop_shared_constants}/fix_hookspath_constants.py +0 -0
- /package/_shared/pr-loop/scripts/{config → pr_loop_shared_constants}/post_audit_thread_constants.py +0 -0
- /package/_shared/pr-loop/scripts/{config → pr_loop_shared_constants}/preflight_constants.py +0 -0
- /package/_shared/pr-loop/scripts/{config → pr_loop_shared_constants}/reviews_disabled_constants.py +0 -0
- /package/hooks/git-hooks/{config.py → git_hooks_constants/__init__.py} +0 -0
- /package/hooks/{config → hooks_constants}/__init__.py +0 -0
- /package/hooks/{config → hooks_constants}/any_type_config.py +0 -0
- /package/hooks/{config → hooks_constants}/banned_identifiers_constants.py +0 -0
- /package/hooks/{config → hooks_constants}/blocking_check_limits.py +0 -0
- /package/hooks/{config → hooks_constants}/bot_mention_comment_blocker_constants.py +0 -0
- /package/hooks/{config → hooks_constants}/convergence_branch_constants.py +0 -0
- /package/hooks/{config → hooks_constants}/doc_gist_auto_publish_constants.py +0 -0
- /package/hooks/{config → hooks_constants}/dynamic_stderr_handler.py +0 -0
- /package/hooks/{config → hooks_constants}/gh_pr_author_swap_constants.py +0 -0
- /package/hooks/{config → hooks_constants}/hardcoded_user_path_constants.py +0 -0
- /package/hooks/{config → hooks_constants}/hook_log_extractor_constants.py +0 -0
- /package/hooks/{config → hooks_constants}/html_companion_constants.py +0 -0
- /package/hooks/{config → hooks_constants}/inline_tuple_string_magic_constants.py +0 -0
- /package/hooks/{config → hooks_constants}/messages.py +0 -0
- /package/hooks/{config → hooks_constants}/path_rewriter_constants.py +0 -0
- /package/hooks/{config → hooks_constants}/pr_converge_bugteam_enforcer_constants.py +0 -0
- /package/hooks/{config → hooks_constants}/session_env_cleanup_constants.py +0 -0
- /package/hooks/{config → hooks_constants}/setup_project_paths_constants.py +0 -0
- /package/hooks/{config → hooks_constants}/state_description_blocker_constants.py +0 -0
- /package/hooks/{config → hooks_constants}/stuttering_check_config.py +0 -0
- /package/hooks/{config → hooks_constants}/stuttering_import_binding_constants.py +0 -0
- /package/hooks/{config → hooks_constants}/sys_path_insert_constants.py +0 -0
- /package/hooks/{config → hooks_constants}/unused_module_import_constants.py +0 -0
- /package/hooks/{config → hooks_constants}/windows_rmtree_blocker_constants.py +0 -0
- /package/{skills/_shared/pr-loop/scripts/config → hooks/lifecycle}/__init__.py +0 -0
- /package/{skills/bugteam/scripts/config → hooks/session}/__init__.py +0 -0
- /package/scripts/{config → dev_env_scripts_constants}/__init__.py +0 -0
- /package/skills/{doc-gist/scripts/config → _shared/pr-loop/scripts/skills_pr_loop_constants}/__init__.py +0 -0
- /package/skills/_shared/pr-loop/scripts/{config → skills_pr_loop_constants}/path_resolver_constants.py +0 -0
- /package/skills/{implement/scripts/config → bugteam/scripts/bugteam_scripts_constants}/__init__.py +0 -0
- /package/skills/bugteam/scripts/{config → bugteam_scripts_constants}/bugteam_code_rules_gate_constants.py +0 -0
- /package/skills/bugteam/scripts/{config → bugteam_scripts_constants}/bugteam_fix_hookspath_constants.py +0 -0
- /package/skills/bugteam/scripts/{config → bugteam_scripts_constants}/bugteam_preflight_constants.py +0 -0
- /package/skills/bugteam/scripts/{config → bugteam_scripts_constants}/claude_permissions_common_constants.py +0 -0
- /package/skills/bugteam/scripts/{config → bugteam_scripts_constants}/probe_code_rules_enforcer_check_constants.py +0 -0
- /package/skills/bugteam/scripts/{config → bugteam_scripts_constants}/reflow_skill_md_constants.py +0 -0
- /package/skills/bugteam/scripts/{config → bugteam_scripts_constants}/windows_safe_rmtree_constants.py +0 -0
- /package/skills/{pr-converge/config → doc-gist/scripts/doc_gist_scripts_constants}/__init__.py +0 -0
- /package/skills/doc-gist/scripts/{config → doc_gist_scripts_constants}/gist_upload_constants.py +0 -0
- /package/skills/{pr-converge/scripts/config → implement/scripts/implement_scripts_constants}/__init__.py +0 -0
- /package/skills/implement/scripts/{config → implement_scripts_constants}/notes_constants.py +0 -0
- /package/skills/pr-converge/scripts/{config → pr_converge_scripts_constants}/reflow_skill_md_constants.py +0 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""Behavior tests for md_to_html_blocker_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
|
+
from hooks_constants import md_to_html_blocker_constants as constants_module
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_indicator_path_segments_are_named_constants() -> None:
|
|
16
|
+
"""The two leading segments of the Claude Code source indicator
|
|
17
|
+
(`packages/claude-dev-env`) must live as named constants so
|
|
18
|
+
`_is_exempt_path` references symbols, not inline literals. Bugbot flagged
|
|
19
|
+
these as magic strings even though the third segment was already a named
|
|
20
|
+
constant; align all three."""
|
|
21
|
+
assert constants_module.PACKAGES_TOP_LEVEL_SEGMENT == "packages"
|
|
22
|
+
assert constants_module.CLAUDE_DEV_ENV_REPO_NAME_SEGMENT == "claude-dev-env"
|
|
23
|
+
for each_name in ("PACKAGES_TOP_LEVEL_SEGMENT", "CLAUDE_DEV_ENV_REPO_NAME_SEGMENT"):
|
|
24
|
+
assert each_name in constants_module.__all__
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_claude_code_source_top_directories_enumerates_six_canonical_dirs() -> None:
|
|
28
|
+
"""The exempt top-level directory set must match the six canonical Claude
|
|
29
|
+
Code source directories (agents, docs, skills, rules, system-prompts,
|
|
30
|
+
commands). Drift in either direction silently changes the exemption
|
|
31
|
+
surface."""
|
|
32
|
+
expected_directories = frozenset(
|
|
33
|
+
{"agents", "docs", "skills", "rules", "system-prompts", "commands"}
|
|
34
|
+
)
|
|
35
|
+
assert constants_module.ALL_CLAUDE_CODE_SOURCE_TOP_DIRECTORIES == expected_directories
|
|
36
|
+
assert "ALL_CLAUDE_CODE_SOURCE_TOP_DIRECTORIES" in constants_module.__all__
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_minimum_segment_count_to_match_indicator_is_four() -> None:
|
|
40
|
+
"""Matching `packages/claude-dev-env/<dir>/<file>` requires at least 4
|
|
41
|
+
consecutive path segments from the starting index. The constant documents
|
|
42
|
+
that requirement explicitly."""
|
|
43
|
+
assert constants_module.MINIMUM_SEGMENT_COUNT_TO_MATCH_INDICATOR == 4
|
|
44
|
+
assert "MINIMUM_SEGMENT_COUNT_TO_MATCH_INDICATOR" in constants_module.__all__
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_exempt_anywhere_filenames_include_skill_md() -> None:
|
|
48
|
+
"""`SKILL.md` files are exempt anywhere in the tree. The constant stores
|
|
49
|
+
the display-case spelling; the lookup at use sites lowercases the
|
|
50
|
+
candidate basename, so casing here documents the human-facing form."""
|
|
51
|
+
assert "SKILL.md" in constants_module.ALL_EXEMPT_ANYWHERE_FILENAMES
|
|
52
|
+
assert "ALL_EXEMPT_ANYWHERE_FILENAMES" in constants_module.__all__
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_exempt_plugin_directory_segments_match_claude_code_layout() -> None:
|
|
56
|
+
"""The three plugin-layout directory names recognized anywhere in a path:
|
|
57
|
+
agents/, skills/, commands/. Drift in either direction silently changes
|
|
58
|
+
the exemption surface."""
|
|
59
|
+
expected_segments = ("agents", "skills", "commands")
|
|
60
|
+
assert constants_module.ALL_EXEMPT_PLUGIN_DIRECTORY_SEGMENTS == expected_segments
|
|
61
|
+
assert "ALL_EXEMPT_PLUGIN_DIRECTORY_SEGMENTS" in constants_module.__all__
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_exempt_plugin_segments_subset_of_claude_code_source_top_directories() -> None:
|
|
65
|
+
"""ALL_EXEMPT_PLUGIN_DIRECTORY_SEGMENTS (matched anywhere in the path)
|
|
66
|
+
must be a subset of ALL_CLAUDE_CODE_SOURCE_TOP_DIRECTORIES (matched
|
|
67
|
+
only at the anchored claude-dev-env source root). If a future change
|
|
68
|
+
adds a segment to the anywhere list that is not in the anchored set,
|
|
69
|
+
the anywhere rule would let writes through that the anchored rule
|
|
70
|
+
would still block — surprising and asymmetric."""
|
|
71
|
+
anywhere_segments = set(constants_module.ALL_EXEMPT_PLUGIN_DIRECTORY_SEGMENTS)
|
|
72
|
+
anchored_source_directories = set(
|
|
73
|
+
constants_module.ALL_CLAUDE_CODE_SOURCE_TOP_DIRECTORIES
|
|
74
|
+
)
|
|
75
|
+
assert anywhere_segments.issubset(anchored_source_directories)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_exempt_home_relative_directories_include_session_log() -> None:
|
|
79
|
+
"""SessionLog/ under the user's home directory is the canonical Obsidian
|
|
80
|
+
vault entrypoint and must be writable as .md."""
|
|
81
|
+
assert "SessionLog" in constants_module.ALL_EXEMPT_HOME_RELATIVE_DIRECTORIES
|
|
82
|
+
assert "ALL_EXEMPT_HOME_RELATIVE_DIRECTORIES" in constants_module.__all__
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def test_exempt_root_filenames_cover_readme_and_changelog() -> None:
|
|
86
|
+
"""README.md and CHANGELOG.md at a repo root are universally exempt;
|
|
87
|
+
every repo with a `.git` marker satisfies the root check."""
|
|
88
|
+
assert constants_module.ALL_EXEMPT_ROOT_FILENAMES == ("readme.md", "changelog.md")
|
|
89
|
+
assert "ALL_EXEMPT_ROOT_FILENAMES" in constants_module.__all__
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def test_repo_root_marker_name_is_dot_git() -> None:
|
|
93
|
+
"""A directory containing `.git` (file or directory) is treated as a repo
|
|
94
|
+
root for the README/CHANGELOG exemption."""
|
|
95
|
+
assert constants_module.REPO_ROOT_MARKER_NAME == ".git"
|
|
96
|
+
assert "REPO_ROOT_MARKER_NAME" in constants_module.__all__
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def test_claude_directory_name_is_dot_claude() -> None:
|
|
100
|
+
"""Any path containing a `.claude/` segment bypasses the .md block
|
|
101
|
+
(project-level Claude Code infrastructure)."""
|
|
102
|
+
assert constants_module.CLAUDE_DIRECTORY_NAME == ".claude"
|
|
103
|
+
assert "CLAUDE_DIRECTORY_NAME" in constants_module.__all__
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def test_plugin_root_marker_directory_name_is_dot_claude_plugin() -> None:
|
|
107
|
+
"""Any directory whose ancestor contains `.claude-plugin/` is treated as
|
|
108
|
+
a plugin repo root and exempted."""
|
|
109
|
+
assert constants_module.PLUGIN_ROOT_MARKER_DIRECTORY_NAME == ".claude-plugin"
|
|
110
|
+
assert "PLUGIN_ROOT_MARKER_DIRECTORY_NAME" in constants_module.__all__
|
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
"""Smoke tests for
|
|
1
|
+
"""Smoke tests for hooks_constants.messages — verify user-facing notice constants exist."""
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
sys.modules.pop("config", None)
|
|
6
|
-
|
|
7
|
-
from config import messages
|
|
3
|
+
from hooks_constants import messages
|
|
8
4
|
|
|
9
5
|
|
|
10
6
|
def test_user_facing_notice_is_nonempty_string() -> None:
|
|
@@ -7,7 +7,7 @@ _HOOKS_ROOT = Path(__file__).resolve().parent.parent
|
|
|
7
7
|
if str(_HOOKS_ROOT) not in sys.path:
|
|
8
8
|
sys.path.insert(0, str(_HOOKS_ROOT))
|
|
9
9
|
|
|
10
|
-
from
|
|
10
|
+
from hooks_constants.path_rewriter_constants import (
|
|
11
11
|
BASH_TOOL_NAME,
|
|
12
12
|
HOOK_EVENT_NAME,
|
|
13
13
|
PERMISSION_ALLOW,
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
"""Behavior tests for pr_description_enforcer_constants module."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
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 hooks_constants import pr_description_enforcer_constants as constants_module
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_plugin_root_is_private_module_attribute() -> None:
|
|
19
|
+
assert hasattr(constants_module, "_PLUGIN_ROOT")
|
|
20
|
+
assert isinstance(constants_module._PLUGIN_ROOT, str)
|
|
21
|
+
assert os.path.isabs(constants_module._PLUGIN_ROOT)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_plugin_root_public_name_is_not_exported() -> None:
|
|
25
|
+
assert not hasattr(constants_module, "PLUGIN_ROOT")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_pr_guide_path_resolves_under_plugin_root_docs() -> None:
|
|
29
|
+
expected_pr_guide_path = os.path.join(
|
|
30
|
+
constants_module._PLUGIN_ROOT, "docs", "PR_DESCRIPTION_GUIDE.md"
|
|
31
|
+
)
|
|
32
|
+
assert constants_module.PR_GUIDE_PATH == expected_pr_guide_path
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_fenced_code_block_pattern_matches_triple_backtick_block() -> None:
|
|
36
|
+
sample_markdown = "before ```python\ncode\n``` after"
|
|
37
|
+
match = constants_module.FENCED_CODE_BLOCK_PATTERN.search(sample_markdown)
|
|
38
|
+
assert match is not None
|
|
39
|
+
assert match.group(0).startswith("```")
|
|
40
|
+
assert match.group(0).endswith("```")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_inline_code_pattern_matches_single_backtick_span() -> None:
|
|
44
|
+
match = constants_module.INLINE_CODE_PATTERN.search("see `value` here")
|
|
45
|
+
assert match is not None
|
|
46
|
+
assert match.group(0) == "`value`"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_heading_line_pattern_matches_atx_heading() -> None:
|
|
50
|
+
match = constants_module.HEADING_LINE_PATTERN.search("## Description\n")
|
|
51
|
+
assert match is not None
|
|
52
|
+
assert match.group(0).strip() == "## Description"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_bold_pair_pattern_captures_inner_text() -> None:
|
|
56
|
+
match = constants_module.BOLD_PAIR_PATTERN.search("this is **bold** text")
|
|
57
|
+
assert match is not None
|
|
58
|
+
assert match.group(1) == "bold"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_bullet_marker_pattern_strips_dash_bullet_from_line() -> None:
|
|
62
|
+
stripped_line = constants_module.BULLET_MARKER_PATTERN.sub("", "- first item")
|
|
63
|
+
assert stripped_line == "first item"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_blockquote_marker_pattern_strips_quote_marker_from_line() -> None:
|
|
67
|
+
stripped_line = constants_module.BLOCKQUOTE_MARKER_PATTERN.sub("", "> quoted line")
|
|
68
|
+
assert stripped_line == "quoted line"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def test_link_text_pattern_captures_anchor_text() -> None:
|
|
72
|
+
match = constants_module.LINK_TEXT_PATTERN.search("See [the docs](https://example.com) now")
|
|
73
|
+
assert match is not None
|
|
74
|
+
assert match.group(1) == "the docs"
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def test_whitespace_run_pattern_collapses_multiple_spaces() -> None:
|
|
78
|
+
collapsed_text = constants_module.WHITESPACE_RUN_PATTERN.sub(" ", "a b\t\tc\n\nd")
|
|
79
|
+
assert collapsed_text == "a b c d"
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_this_pr_opening_pattern_matches_any_verb() -> None:
|
|
83
|
+
"""The guide says any `This PR ...` opening is a hard block. The pattern
|
|
84
|
+
must match regardless of the verb that follows, not a short allowlist."""
|
|
85
|
+
test_inputs = [
|
|
86
|
+
"This PR introduces a new feature.",
|
|
87
|
+
"This PR improves the algorithm.",
|
|
88
|
+
"This PR enables the cache.",
|
|
89
|
+
"This PR documents the contract.",
|
|
90
|
+
"This PR adds a check.",
|
|
91
|
+
"This PR fixes the bug.",
|
|
92
|
+
]
|
|
93
|
+
for each_input in test_inputs:
|
|
94
|
+
assert constants_module.THIS_PR_OPENING_PATTERN.match(each_input), (
|
|
95
|
+
f"`{each_input}` must match THIS_PR_OPENING_PATTERN regardless of verb"
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def test_this_pr_opening_pattern_does_not_match_within_prose() -> None:
|
|
100
|
+
"""The block applies to body openings, not mid-paragraph mentions."""
|
|
101
|
+
text_with_mid_mention = "Adds caching. This PR follows the playbook."
|
|
102
|
+
assert constants_module.THIS_PR_OPENING_PATTERN.match(text_with_mid_mention) is None
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def test_all_list_is_alphabetically_sorted() -> None:
|
|
106
|
+
"""`__all__` is the export surface; alphabetical order makes it easy to
|
|
107
|
+
verify completeness, spot duplicates, and bisect missing entries. Pin
|
|
108
|
+
sorted order so drift cannot reintroduce the H-before-G / appended-at-end
|
|
109
|
+
inconsistencies Bugbot flagged."""
|
|
110
|
+
actual_all_list = constants_module.__all__
|
|
111
|
+
expected_sorted = sorted(actual_all_list)
|
|
112
|
+
if actual_all_list != expected_sorted:
|
|
113
|
+
first_divergence_index = next(
|
|
114
|
+
each_index
|
|
115
|
+
for each_index, (each_actual_value, each_expected_value) in enumerate(zip(actual_all_list, expected_sorted))
|
|
116
|
+
if each_actual_value != each_expected_value
|
|
117
|
+
)
|
|
118
|
+
raise AssertionError(
|
|
119
|
+
"constants_module.__all__ must be alphabetically sorted; "
|
|
120
|
+
f"the first divergence is at index {first_divergence_index}: "
|
|
121
|
+
f"got {actual_all_list[first_divergence_index]!r}, "
|
|
122
|
+
f"expected {expected_sorted[first_divergence_index]!r}"
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def test_dead_heavy_detection_constants_are_removed() -> None:
|
|
127
|
+
"""`ALL_HEAVY_DETECTION_HEADERS` and `HEAVY_DETECTION_HEADER_COUNT_MIN`
|
|
128
|
+
were remnants of the prior two-condition shape classifier (length AND
|
|
129
|
+
detection-header count). e137dee9 simplified the classifier to use length
|
|
130
|
+
alone, leaving both names as dead exports. Pin their removal so they
|
|
131
|
+
cannot drift back as misleading vestigial constants."""
|
|
132
|
+
for each_name in ("ALL_HEAVY_DETECTION_HEADERS", "HEAVY_DETECTION_HEADER_COUNT_MIN"):
|
|
133
|
+
assert not hasattr(constants_module, each_name), (
|
|
134
|
+
f"{each_name} must be deleted; the classifier now uses "
|
|
135
|
+
"HEAVY_MIN_BODY_CHARS_FOR_CLASSIFICATION alone."
|
|
136
|
+
)
|
|
137
|
+
assert each_name not in constants_module.__all__
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def test_self_closing_reference_message_prefix_and_suffix_compose_full_message() -> None:
|
|
141
|
+
pr_number = 42
|
|
142
|
+
composed_message = (
|
|
143
|
+
f"{constants_module.SELF_CLOSING_REFERENCE_MESSAGE_PREFIX}"
|
|
144
|
+
f"{pr_number}"
|
|
145
|
+
f"{constants_module.SELF_CLOSING_REFERENCE_MESSAGE_SUFFIX}"
|
|
146
|
+
)
|
|
147
|
+
assert "#42" in composed_message
|
|
148
|
+
assert "Fixes/Closes/Resolves" in composed_message
|
|
149
|
+
assert "self-reference" in composed_message
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def test_loosen_factors_are_inverse_paired() -> None:
|
|
153
|
+
flesch_factor = constants_module.READABILITY_FLESCH_LOOSEN_FACTOR
|
|
154
|
+
sentence_factor = constants_module.READABILITY_SENTENCE_WORDS_LOOSEN_FACTOR
|
|
155
|
+
assert flesch_factor * sentence_factor == pytest.approx(1.0)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def test_unused_header_constants_are_removed_from_module() -> None:
|
|
159
|
+
"""The four header constants FIX_HEADER, CHANGES_HEADER, APPROACH_HEADER,
|
|
160
|
+
and ROOT_CAUSE_HEADER were dead exports with no call sites and must not be
|
|
161
|
+
re-introduced as either module attributes or __all__ entries."""
|
|
162
|
+
for each_dead_name in ("FIX_HEADER", "CHANGES_HEADER", "APPROACH_HEADER", "ROOT_CAUSE_HEADER"):
|
|
163
|
+
assert not hasattr(constants_module, each_dead_name), (
|
|
164
|
+
f"{each_dead_name} was re-introduced; the four header constants "
|
|
165
|
+
"had zero call sites and were deleted."
|
|
166
|
+
)
|
|
167
|
+
assert each_dead_name not in constants_module.__all__, (
|
|
168
|
+
f"{each_dead_name} re-appeared in __all__ even though it has no consumers."
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def test_module_internal_header_constants_are_not_publicly_exported() -> None:
|
|
173
|
+
"""The seven header-name constants and three default-readability-threshold scalars
|
|
174
|
+
are consumed only inside this same module (used to build ALL_HEAVY_* frozensets
|
|
175
|
+
and DEFAULT_READABILITY_THRESHOLDS). They must remain module attributes for the
|
|
176
|
+
builder expressions, but they must NOT appear in __all__ -- callers should import
|
|
177
|
+
the public aggregates (ALL_HEAVY_OPENING_HEADERS / ALL_HEAVY_TESTING_HEADERS /
|
|
178
|
+
DEFAULT_READABILITY_THRESHOLDS) instead of the individual scalars."""
|
|
179
|
+
all_internal_only_names = (
|
|
180
|
+
"SUMMARY_HEADER",
|
|
181
|
+
"PROBLEM_HEADER",
|
|
182
|
+
"TEST_PLAN_HEADER",
|
|
183
|
+
"TESTS_HEADER",
|
|
184
|
+
"TESTING_HEADER",
|
|
185
|
+
"VERIFICATION_HEADER",
|
|
186
|
+
"VALIDATION_HEADER",
|
|
187
|
+
"READABILITY_MAX_SENTENCE_WORDS",
|
|
188
|
+
"READABILITY_AVG_SENTENCE_WORDS",
|
|
189
|
+
"READABILITY_MIN_FLESCH",
|
|
190
|
+
)
|
|
191
|
+
for each_internal_name in all_internal_only_names:
|
|
192
|
+
assert hasattr(constants_module, each_internal_name), (
|
|
193
|
+
f"{each_internal_name} must remain as a module attribute -- "
|
|
194
|
+
"the aggregate builders reference it."
|
|
195
|
+
)
|
|
196
|
+
assert each_internal_name not in constants_module.__all__, (
|
|
197
|
+
f"{each_internal_name} is consumed only inside the module and "
|
|
198
|
+
"must not be advertised in __all__."
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def test_self_reference_pattern_covers_all_nine_github_closing_keywords() -> None:
|
|
203
|
+
"""GitHub recognizes nine closing keywords: close/closes/closed,
|
|
204
|
+
fix/fixes/fixed, resolve/resolves/resolved. The self-reference template
|
|
205
|
+
must enumerate all nine so no variant bypasses the self-closing block."""
|
|
206
|
+
pattern_source = constants_module.SELF_REFERENCE_PATTERN_TEMPLATE
|
|
207
|
+
for each_keyword in (
|
|
208
|
+
"Close", "Closes", "Closed",
|
|
209
|
+
"Fix", "Fixes", "Fixed",
|
|
210
|
+
"Resolve", "Resolves", "Resolved",
|
|
211
|
+
):
|
|
212
|
+
assert each_keyword in pattern_source, (
|
|
213
|
+
f"SELF_REFERENCE_PATTERN_TEMPLATE must enumerate `{each_keyword}` "
|
|
214
|
+
f"to catch every GitHub closing-keyword variant; got: {pattern_source!r}"
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def test_body_shape_string_literals_are_named_constants() -> None:
|
|
219
|
+
"""The three PR-body shape names (`trivial`, `standard`, `heavy`) are used
|
|
220
|
+
by both `_compute_pr_body_shape` (return values) and `validate_pr_body`
|
|
221
|
+
(string comparisons). Bugbot flagged the inline literals as cross-function
|
|
222
|
+
magic strings. Pin the canonical names in `hooks_constants/` so a typo in
|
|
223
|
+
either site cannot silently misclassify shape."""
|
|
224
|
+
assert constants_module.TRIVIAL_SHAPE == "trivial"
|
|
225
|
+
assert constants_module.STANDARD_SHAPE == "standard"
|
|
226
|
+
assert constants_module.HEAVY_SHAPE == "heavy"
|
|
227
|
+
for each_name in ("TRIVIAL_SHAPE", "STANDARD_SHAPE", "HEAVY_SHAPE"):
|
|
228
|
+
assert each_name in constants_module.__all__
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def test_readability_cli_flag_tokens_exposes_four_flags() -> None:
|
|
232
|
+
"""ALL_READABILITY_CLI_FLAG_TOKENS centralises the four CLI flags the
|
|
233
|
+
dispatcher recognises (loosen/reset/disable/enable). Pinning the set keeps
|
|
234
|
+
main()'s dispatcher loop and the dispatcher itself in sync; adding a flag
|
|
235
|
+
requires updating both the constant and the dispatcher in the same commit."""
|
|
236
|
+
expected_tokens = frozenset(
|
|
237
|
+
{
|
|
238
|
+
"--readability-loosen",
|
|
239
|
+
"--readability-reset",
|
|
240
|
+
"--readability-disable",
|
|
241
|
+
"--readability-enable",
|
|
242
|
+
}
|
|
243
|
+
)
|
|
244
|
+
assert constants_module.ALL_READABILITY_CLI_FLAG_TOKENS == expected_tokens
|
|
245
|
+
assert "ALL_READABILITY_CLI_FLAG_TOKENS" in constants_module.__all__
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def test_ceremony_header_pattern_is_removed() -> None:
|
|
249
|
+
"""`CEREMONY_HEADER_PATTERN` is replaced by the broader `HEADING_LINE_PATTERN`
|
|
250
|
+
check in the Trivial-ceremony violation; the constant must not linger as a
|
|
251
|
+
dead export to drift back into use."""
|
|
252
|
+
assert not hasattr(constants_module, "CEREMONY_HEADER_PATTERN"), (
|
|
253
|
+
"CEREMONY_HEADER_PATTERN must be deleted; the ceremony-on-Trivial check "
|
|
254
|
+
"now uses HEADING_LINE_PATTERN to cover every heading level."
|
|
255
|
+
)
|
|
256
|
+
assert "CEREMONY_HEADER_PATTERN" not in constants_module.__all__
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def test_flesch_formula_coefficients_are_named_constants() -> None:
|
|
260
|
+
"""The Flesch Reading Ease formula coefficients (206.835, 1.015, 84.6) and
|
|
261
|
+
the perfect-score default (100.0) must live as named UPPER_SNAKE constants
|
|
262
|
+
in `hooks_constants/pr_description_enforcer_constants.py` so the production
|
|
263
|
+
function body has zero magic numeric literals."""
|
|
264
|
+
assert constants_module.FLESCH_BASE_SCORE == 206.835
|
|
265
|
+
assert constants_module.FLESCH_WORDS_PER_SENTENCE_COEFFICIENT == 1.015
|
|
266
|
+
assert constants_module.FLESCH_SYLLABLES_PER_WORD_COEFFICIENT == 84.6
|
|
267
|
+
assert constants_module.FLESCH_PERFECT_SCORE == 100.0
|
|
268
|
+
for each_name in (
|
|
269
|
+
"FLESCH_BASE_SCORE",
|
|
270
|
+
"FLESCH_WORDS_PER_SENTENCE_COEFFICIENT",
|
|
271
|
+
"FLESCH_SYLLABLES_PER_WORD_COEFFICIENT",
|
|
272
|
+
"FLESCH_PERFECT_SCORE",
|
|
273
|
+
):
|
|
274
|
+
assert each_name in constants_module.__all__, (
|
|
275
|
+
f"{each_name} must appear in __all__ so the enforcer can import it."
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def test_all_heavy_testing_headers_enumerates_five_canonical_forms() -> None:
|
|
280
|
+
"""The frozenset ALL_HEAVY_TESTING_HEADERS drives the violation message that
|
|
281
|
+
tells the writer which headers satisfy the heavy-shape testing-category
|
|
282
|
+
requirement. The set must include all five canonical forms (Test plan,
|
|
283
|
+
Testing, Tests, Verification, Validation) so the writer's documented
|
|
284
|
+
vocabulary matches the message it receives on a violation."""
|
|
285
|
+
expected_canonical_headers = [
|
|
286
|
+
"## Test plan",
|
|
287
|
+
"## Testing",
|
|
288
|
+
"## Tests",
|
|
289
|
+
"## Validation",
|
|
290
|
+
"## Verification",
|
|
291
|
+
]
|
|
292
|
+
assert sorted(constants_module.ALL_HEAVY_TESTING_HEADERS) == expected_canonical_headers
|
|
@@ -8,8 +8,8 @@ import json
|
|
|
8
8
|
import sys
|
|
9
9
|
from unittest.mock import patch
|
|
10
10
|
|
|
11
|
-
from
|
|
12
|
-
from
|
|
11
|
+
from hooks_constants import pre_tool_use_stdin
|
|
12
|
+
from hooks_constants.pre_tool_use_stdin import read_hook_input_dictionary_from_stdin
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
def test_pre_tool_use_stdin_uses_shared_encoding_and_decode_constants() -> None:
|
|
@@ -11,13 +11,13 @@ _HOOKS_ROOT = Path(__file__).resolve().parent.parent
|
|
|
11
11
|
if str(_HOOKS_ROOT) not in sys.path:
|
|
12
12
|
sys.path.insert(0, str(_HOOKS_ROOT))
|
|
13
13
|
|
|
14
|
-
from
|
|
15
|
-
from
|
|
14
|
+
from hooks_constants import project_paths_reader
|
|
15
|
+
from hooks_constants.project_paths_reader import (
|
|
16
16
|
load_registry,
|
|
17
17
|
registry_contains_path,
|
|
18
18
|
registry_file_path,
|
|
19
19
|
)
|
|
20
|
-
from
|
|
20
|
+
from hooks_constants.setup_project_paths_constants import META_KEY
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
def test_reader_does_not_redefine_dynamic_stderr_handler_locally() -> None:
|
|
@@ -11,7 +11,7 @@ for each_sys_path_entry in (str(_CONFIG_DIRECTORY), str(_HOOKS_ROOT)):
|
|
|
11
11
|
if each_sys_path_entry not in sys.path:
|
|
12
12
|
sys.path.insert(0, each_sys_path_entry)
|
|
13
13
|
|
|
14
|
-
from
|
|
14
|
+
from hooks_constants.session_env_cleanup_constants import SESSION_ID_PATTERN, SESSION_ID_PAYLOAD_KEY
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
class TestSessionIdPatternAccepts:
|
|
@@ -9,8 +9,8 @@ _HOOKS_ROOT = Path(__file__).resolve().parent.parent
|
|
|
9
9
|
if str(_HOOKS_ROOT) not in sys.path:
|
|
10
10
|
sys.path.insert(0, str(_HOOKS_ROOT))
|
|
11
11
|
|
|
12
|
-
import
|
|
13
|
-
from
|
|
12
|
+
import hooks_constants.setup_project_paths_constants as constants_module
|
|
13
|
+
from hooks_constants.setup_project_paths_constants import (
|
|
14
14
|
ABORTED_NOTHING_WRITTEN_MESSAGE,
|
|
15
15
|
CONFIRMATION_PROMPT_TEXT,
|
|
16
16
|
ES_EXE_FOLDERS_ONLY_QUERY_ARGUMENTS,
|
|
@@ -9,7 +9,7 @@ _HOOKS_ROOT = Path(__file__).resolve().parent.parent
|
|
|
9
9
|
if str(_HOOKS_ROOT) not in sys.path:
|
|
10
10
|
sys.path.insert(0, str(_HOOKS_ROOT))
|
|
11
11
|
|
|
12
|
-
from
|
|
12
|
+
from hooks_constants.unused_module_import_constants import line_suppresses_unused_import_via_noqa
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
def test_line_suppresses_bare_noqa() -> None:
|
|
@@ -25,17 +25,11 @@ import tempfile
|
|
|
25
25
|
from pathlib import Path
|
|
26
26
|
from typing import TextIO
|
|
27
27
|
|
|
28
|
+
_hooks_dir = str(Path(__file__).resolve().parent.parent)
|
|
29
|
+
if _hooks_dir not in sys.path:
|
|
30
|
+
sys.path.insert(0, _hooks_dir)
|
|
28
31
|
|
|
29
|
-
|
|
30
|
-
hooks_tree = Path(__file__).resolve().parent.parent
|
|
31
|
-
hooks_tree_string = str(hooks_tree)
|
|
32
|
-
if hooks_tree_string not in sys.path:
|
|
33
|
-
sys.path.insert(0, hooks_tree_string)
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
_insert_hooks_tree_for_imports()
|
|
37
|
-
|
|
38
|
-
from config.pr_converge_bugteam_enforcer_constants import (
|
|
32
|
+
from hooks_constants.pr_converge_bugteam_enforcer_constants import ( # noqa: E402
|
|
39
33
|
BUGTEAM_SKILL_NAME,
|
|
40
34
|
SKILL_TOOL_NAME,
|
|
41
35
|
STATE_FIELD_BUGTEAM_SKILL_INVOKED_AT_HEAD,
|
|
@@ -45,7 +39,7 @@ from config.pr_converge_bugteam_enforcer_constants import (
|
|
|
45
39
|
STATE_FILE_ATOMIC_WRITE_SUFFIX,
|
|
46
40
|
STATE_FILE_JSON_INDENT_SPACES,
|
|
47
41
|
)
|
|
48
|
-
from
|
|
42
|
+
from hooks_constants.pr_converge_bugteam_enforcer_state import ( # noqa: E402
|
|
49
43
|
load_state_dictionary,
|
|
50
44
|
resolve_state_path,
|
|
51
45
|
)
|
|
@@ -33,7 +33,7 @@ assert hook_spec.loader is not None
|
|
|
33
33
|
hook_module = importlib.util.module_from_spec(hook_spec)
|
|
34
34
|
hook_spec.loader.exec_module(hook_module)
|
|
35
35
|
|
|
36
|
-
from
|
|
36
|
+
from hooks_constants.pr_converge_bugteam_enforcer_constants import (
|
|
37
37
|
CLAUDE_JOB_DIR_ENV_VAR,
|
|
38
38
|
PR_CONVERGE_STATE_FILENAME,
|
|
39
39
|
)
|
|
@@ -29,19 +29,18 @@ import tempfile
|
|
|
29
29
|
import time
|
|
30
30
|
from pathlib import Path
|
|
31
31
|
|
|
32
|
+
hooks_parent_directory = str(Path(__file__).resolve().parent.parent)
|
|
33
|
+
if hooks_parent_directory not in sys.path:
|
|
34
|
+
sys.path.insert(0, hooks_parent_directory)
|
|
32
35
|
|
|
33
|
-
|
|
34
|
-
if _hooks_tree_path not in sys.path:
|
|
35
|
-
sys.path.insert(0, _hooks_tree_path)
|
|
36
|
-
|
|
37
|
-
from _gh_pr_author_swap_utils import ( # noqa: E402 # sys.path shim above must run first
|
|
36
|
+
from _gh_pr_author_swap_utils import ( # noqa: E402
|
|
38
37
|
_delete_state_file,
|
|
39
38
|
_lstat_indicates_attacker_planted,
|
|
40
39
|
_read_original_account,
|
|
41
40
|
_switch_gh_account,
|
|
42
41
|
_write_line,
|
|
43
42
|
)
|
|
44
|
-
from
|
|
43
|
+
from hooks_constants.gh_pr_author_swap_constants import ( # noqa: E402
|
|
45
44
|
REQUIRED_ACCOUNT_ENV_VAR,
|
|
46
45
|
STATE_FILE_PREFIX,
|
|
47
46
|
STATE_FILE_STALE_AGE_SECONDS,
|
|
@@ -25,17 +25,11 @@ import time
|
|
|
25
25
|
from pathlib import Path
|
|
26
26
|
from typing import Callable
|
|
27
27
|
|
|
28
|
+
_hooks_dir = str(Path(__file__).resolve().parent.parent)
|
|
29
|
+
if _hooks_dir not in sys.path:
|
|
30
|
+
sys.path.insert(0, _hooks_dir)
|
|
28
31
|
|
|
29
|
-
|
|
30
|
-
hooks_tree = Path(__file__).resolve().parent.parent
|
|
31
|
-
hooks_tree_string = str(hooks_tree)
|
|
32
|
-
if hooks_tree_string not in sys.path:
|
|
33
|
-
sys.path.insert(0, hooks_tree_string)
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
_insert_hooks_tree_for_imports()
|
|
37
|
-
|
|
38
|
-
from config.session_env_cleanup_constants import (
|
|
32
|
+
from hooks_constants.session_env_cleanup_constants import ( # noqa: E402
|
|
39
33
|
ALL_RMTREE_ONEXC_PYTHON_VERSION_PARTS,
|
|
40
34
|
SESSION_ENV_DIRECTORY,
|
|
41
35
|
SESSION_ID_PATTERN,
|
|
@@ -31,7 +31,7 @@ hook_module_spec.loader.exec_module(hook_module)
|
|
|
31
31
|
|
|
32
32
|
import _gh_pr_author_swap_utils as swap_utils_module # noqa: E402
|
|
33
33
|
|
|
34
|
-
from
|
|
34
|
+
from hooks_constants.gh_pr_author_swap_constants import ( # noqa: E402
|
|
35
35
|
STATE_FILE_PERMISSION_MODE,
|
|
36
36
|
STATE_FILE_STALE_AGE_SECONDS,
|
|
37
37
|
)
|
|
@@ -15,8 +15,8 @@ for each_sys_path_entry in (str(_SESSION_DIR), str(_HOOKS_ROOT)):
|
|
|
15
15
|
sys.path.insert(0, each_sys_path_entry)
|
|
16
16
|
|
|
17
17
|
import untracked_repo_detector as detector
|
|
18
|
-
from
|
|
19
|
-
from
|
|
18
|
+
from hooks_constants.project_paths_reader import registry_file_path
|
|
19
|
+
from hooks_constants.setup_project_paths_constants import GIT_DIRECTORY_SEGMENT_NAME
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
def _run_main_with_cwd(cwd: str, known_registry: dict) -> tuple[str, str, int]:
|
|
@@ -15,23 +15,17 @@ import os
|
|
|
15
15
|
import sys
|
|
16
16
|
from pathlib import Path
|
|
17
17
|
|
|
18
|
+
_hooks_dir = str(Path(__file__).resolve().parent.parent)
|
|
19
|
+
if _hooks_dir not in sys.path:
|
|
20
|
+
sys.path.insert(0, _hooks_dir)
|
|
18
21
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
hooks_tree_string = str(hooks_tree)
|
|
22
|
-
if hooks_tree_string not in sys.path:
|
|
23
|
-
sys.path.insert(0, hooks_tree_string)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
_insert_hooks_tree_for_imports()
|
|
27
|
-
|
|
28
|
-
from config.dynamic_stderr_handler import DynamicStderrHandler
|
|
29
|
-
from config.project_paths_reader import (
|
|
22
|
+
from hooks_constants.dynamic_stderr_handler import DynamicStderrHandler # noqa: E402
|
|
23
|
+
from hooks_constants.project_paths_reader import ( # noqa: E402
|
|
30
24
|
load_registry,
|
|
31
25
|
registry_contains_path,
|
|
32
26
|
registry_file_path,
|
|
33
27
|
)
|
|
34
|
-
from
|
|
28
|
+
from hooks_constants.setup_project_paths_constants import GIT_DIRECTORY_SEGMENT_NAME # noqa: E402
|
|
35
29
|
|
|
36
30
|
|
|
37
31
|
_logger = logging.getLogger("untracked_repo_detector")
|
|
@@ -17,7 +17,7 @@ import tempfile
|
|
|
17
17
|
|
|
18
18
|
import pytest
|
|
19
19
|
|
|
20
|
-
from
|
|
20
|
+
from hooks_constants.gh_pr_author_swap_constants import STATE_FILE_PERMISSION_MODE
|
|
21
21
|
|
|
22
22
|
_HOOKS_ROOT = pathlib.Path(__file__).resolve().parent
|
|
23
23
|
if str(_HOOKS_ROOT) not in sys.path:
|