claude-dev-env 2.28.0 → 2.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.
- package/.agents/agents/AGENTS.md +0 -31
- package/.agents/agents/clean-coder.md +1 -1
- package/.agents/agents/test_agent_frontmatter.py +3 -1
- package/.agents/skills/AGENTS.md +0 -70
- package/.agents/skills/_shared/AGENTS.md +0 -44
- package/.agents/skills/_shared/advisor/AGENTS.md +0 -8
- package/.agents/skills/_shared/pr-loop/AGENTS.md +0 -57
- package/.agents/skills/_shared/pr-loop/prompts/AGENTS.md +0 -8
- package/.agents/skills/_shared/pr-loop/scripts/AGENTS.md +0 -34
- package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/AGENTS.md +0 -23
- package/.agents/skills/autoconverge/AGENTS.md +0 -35
- package/.agents/skills/autoconverge/reference/AGENTS.md +0 -15
- package/.agents/skills/autoconverge/workflow/AGENTS.md +0 -25
- package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/AGENTS.md +0 -15
- package/.agents/skills/everything-search/AGENTS.md +0 -16
- package/.agents/skills/fresh-branch/AGENTS.md +0 -13
- package/.agents/skills/grok-spawn/AGENTS.md +0 -27
- package/.agents/skills/orchestrator/.claude/CLAUDE.md +1 -0
- package/.agents/skills/orchestrator/AGENTS.md +1 -0
- package/.agents/skills/orchestrator/SKILL.md +44 -53
- package/.agents/skills/orchestrator/reference/.claude/CLAUDE.md +1 -0
- package/.agents/skills/orchestrator/reference/AGENTS.md +1 -0
- package/.agents/skills/orchestrator/reference/consult-the-orchestrator.md +70 -0
- package/.agents/skills/orchestrator/reference/executor-consult-block.md +62 -0
- package/.agents/skills/orchestrator/reference/host-detect.md +15 -0
- package/.agents/skills/orchestrator/test_orchestrator_skill_contract.py +62 -0
- package/.agents/skills/orchestrator-refresh/SKILL.md +16 -34
- package/.agents/skills/rebase/AGENTS.md +0 -31
- package/.agents/skills/session-log/AGENTS.md +0 -31
- package/.agents/skills/session-tidy/AGENTS.md +0 -35
- package/.agents/skills/skill-builder/AGENTS.md +0 -48
- package/.agents/skills/skill-builder/references/AGENTS.md +0 -24
- package/.agents/skills/skill-builder/templates/AGENTS.md +0 -13
- package/.agents/skills/skill-builder/workflows/AGENTS.md +0 -18
- package/.agents/skills/task-build/AGENTS.md +0 -28
- package/.agents/skills/update/AGENTS.md +0 -37
- package/.agents/skills-archived/AGENTS.md +0 -44
- package/.agents/skills-archived/anthropic-plan/AGENTS.md +0 -33
- package/.agents/skills-archived/anthropic-plan/scripts/AGENTS.md +0 -10
- package/.agents/skills-archived/anthropic-plan/scripts/anthropic_plan_scripts_constants/AGENTS.md +0 -15
- package/.agents/skills-archived/anthropic-plan/templates/AGENTS.md +0 -12
- package/.agents/skills-archived/anthropic-plan/workflow/AGENTS.md +0 -13
- package/.agents/skills-archived/auditing-claude-config/AGENTS.md +0 -20
- package/.agents/skills-archived/bugteam/AGENTS.md +0 -29
- package/.agents/skills-archived/bugteam/reference/AGENTS.md +0 -19
- package/.agents/skills-archived/bugteam/reference/obstacles/AGENTS.md +0 -23
- package/.agents/skills-archived/bugteam/scripts/AGENTS.md +0 -29
- package/.agents/skills-archived/bugteam/scripts/bugteam_scripts_constants/AGENTS.md +0 -17
- package/.agents/skills-archived/codex-review/AGENTS.md +0 -45
- package/.agents/skills-archived/codex-review/reference/AGENTS.md +0 -14
- package/.agents/skills-archived/codex-review/scripts/codex_review_scripts_constants/AGENTS.md +0 -17
- package/.agents/skills-archived/codex-review/test_skill_scaffold.py +13 -3
- package/.agents/skills-archived/copilot-review/AGENTS.md +0 -17
- package/.agents/skills-archived/pr-converge/AGENTS.md +0 -31
- package/.agents/skills-archived/pr-converge/pr_converge_skill_constants/AGENTS.md +0 -25
- package/.agents/skills-archived/pr-converge/reference/AGENTS.md +0 -27
- package/.agents/skills-archived/pr-converge/reference/obstacles/AGENTS.md +0 -22
- package/.agents/skills-archived/pr-converge/scripts/AGENTS.md +0 -45
- package/.agents/skills-archived/pr-converge/scripts/pr_converge_scripts_constants/AGENTS.md +0 -17
- package/.agents/skills-archived/pr-converge/workflows/AGENTS.md +0 -15
- package/.agents/skills-archived/pr-loop-cloud-transport/reference/identity-and-hooks.md +1 -1
- package/.agents/skills-archived/recall/AGENTS.md +0 -29
- package/.agents/skills-archived/remember/AGENTS.md +0 -30
- package/AGENTS.md +0 -112
- package/_shared/AGENTS.md +0 -16
- package/_shared/advisor/AGENTS.md +0 -21
- package/_shared/advisor/advisor-protocol.md +4 -1
- package/_shared/advisor/reference/consult-format.md +4 -2
- package/_shared/pr-loop/AGENTS.md +0 -27
- package/_shared/pr-loop/scripts/AGENTS.md +0 -49
- package/_shared/pr-loop/scripts/code_rules_gate_parts/AGENTS.md +0 -41
- package/_shared/pr-loop/scripts/codex_review_scripts_constants/AGENTS.md +0 -17
- package/_shared/pr-loop/scripts/pr_converge_scripts_constants/AGENTS.md +0 -17
- package/_shared/pr-loop/scripts/pr_converge_skill_constants/AGENTS.md +0 -25
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/AGENTS.md +0 -25
- package/_shared/pr-loop/scripts/tests/AGENTS.md +0 -43
- package/_shared/process-tree/AGENTS.md +0 -40
- package/audit-rubrics/AGENTS.md +0 -42
- package/audit-rubrics/category_rubrics/AGENTS.md +0 -36
- package/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md +16 -29
- package/audit-rubrics/prompts/AGENTS.md +0 -36
- package/audit-rubrics/prompts/category-o-docstring-vs-impl-drift.md +2 -2
- package/bin/AGENTS.md +0 -118
- package/bin/install.test.mjs +1 -1
- package/commands/AGENTS.md +0 -14
- package/docs/AGENTS.md +0 -31
- package/docs/CODE_RULES.md +3 -3
- package/docs/agent-spawn-protocol.md +1 -1
- package/docs/references/AGENTS.md +0 -16
- package/docs/references/prose-style-enforcement.md +5 -9
- package/hooks/AGENTS.md +0 -27
- package/hooks/advisory/AGENTS.md +0 -15
- package/hooks/blocking/AGENTS.md +0 -122
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/AGENTS.md +0 -27
- package/hooks/blocking/code_rules_docstrings.py +10 -2138
- package/hooks/blocking/code_rules_enforcer.py +0 -121
- package/hooks/blocking/code_rules_imports_logging.py +1 -236
- package/hooks/blocking/code_rules_shared.py +23 -0
- package/hooks/blocking/code_rules_test_layout.py +8 -8
- package/hooks/blocking/config/AGENTS.md +0 -9
- package/hooks/blocking/config/prose_style_enforcement_constants.py +4 -2
- package/hooks/blocking/config/test_prose_style_enforcement_constants.py +5 -1
- package/hooks/blocking/inventory_intent_records/AGENTS.md +0 -25
- package/hooks/blocking/package_inventory_stale_blocker_parts/AGENTS.md +0 -25
- package/hooks/blocking/pii_prevention_blocker_parts/AGENTS.md +0 -23
- package/hooks/blocking/precommit_code_rules_gate.py +8 -43
- package/hooks/blocking/state_description_blocker.py +1 -7
- package/hooks/blocking/tdd_enforcer_parts/AGENTS.md +0 -29
- package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +0 -1
- package/hooks/blocking/test_code_rules_enforcer_dispatch_wiring.py +0 -8
- package/hooks/blocking/test_code_rules_enforcer_module_docstring_roster.py +11 -112
- package/hooks/blocking/test_code_rules_enforcer_narrow_edit.py +0 -1
- package/hooks/blocking/test_code_rules_enforcer_split_entry_1.py +1 -18
- package/hooks/blocking/test_code_rules_shared.py +12 -0
- package/hooks/blocking/test_precommit_code_rules_gate.py +32 -179
- package/hooks/blocking/test_precommit_code_rules_gate_native_owner.py +0 -1
- package/hooks/blocking/test_state_description_blocker.py +6 -4
- package/hooks/blocking/test_stop_dispatcher.py +5 -29
- package/hooks/git-hooks/AGENTS.md +0 -31
- package/hooks/git-hooks/git_hooks_constants/AGENTS.md +0 -20
- package/hooks/hooks_constants/AGENTS.md +0 -104
- package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +0 -1
- package/hooks/hooks_constants/code_rules_enforcer_constants.py +3 -0
- package/hooks/hooks_constants/messages.py +0 -2
- package/hooks/hooks_constants/precommit_code_rules_gate_constants.py +3 -17
- package/hooks/hooks_constants/stop_dispatcher_constants.py +0 -2
- package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +0 -1
- package/hooks/hooks_constants/test_code_rules_enforcer_constants.py +7 -0
- package/hooks/hooks_constants/test_messages.py +5 -3
- package/hooks/hooks_constants/test_stop_dispatcher_constants.py +0 -2
- package/hooks/lifecycle/AGENTS.md +0 -17
- package/hooks/observability/AGENTS.md +0 -19
- package/hooks/session/AGENTS.md +0 -37
- package/hooks/validation/AGENTS.md +0 -19
- package/hooks/validators/AGENTS.md +0 -51
- package/hooks/workflow/AGENTS.md +0 -15
- package/output-styles/AGENTS.md +0 -14
- package/package.json +1 -1
- package/rules/AGENTS.md +0 -58
- package/rules/claims-as-quotes.md +0 -10
- package/rules/code-standards.md +6 -6
- package/rules/explore-thoroughly.md +0 -1
- package/rules/failure-blast-radius.md +0 -8
- package/rules/falsify-before-green.md +0 -8
- package/rules/file-global-constants.md +2 -2
- package/rules/filesystem-search.md +1 -1
- package/rules/git-workflow.md +1 -9
- package/rules/hedging-claims.md +2 -6
- package/rules/long-horizon-autonomy.md +1 -1
- package/rules/measurement-denominators.md +0 -9
- package/rules/research-mode.md +0 -6
- package/rules/verify-before-asking.md +0 -5
- package/rules/verify-runtime-state.md +0 -5
- package/scripts/AGENTS.md +0 -55
- package/scripts/codex_compat_materializer.py +0 -12
- package/scripts/dev_env_scripts_constants/AGENTS.md +0 -20
- package/scripts/sync_to_cursor/AGENTS.md +0 -22
- package/scripts/sync_to_cursor/rules.py +0 -10
- package/scripts/tests/AGENTS.md +0 -34
- package/scripts/tests/test_engine.py +0 -1
- package/scripts/tests/test_rules.py +0 -1
- package/scripts/tests/test_sync_to_cursor.py +0 -1
- package/system-prompts/AGENTS.md +0 -24
- package/system-prompts/software-engineer.xml +3 -3
- package/hooks/blocking/code_rules_dead_argparse_argument.py +0 -554
- package/hooks/blocking/code_rules_dead_config_field.py +0 -568
- package/hooks/blocking/code_rules_dead_dataclass_field.py +0 -348
- package/hooks/blocking/code_rules_dead_module_constant.py +0 -757
- package/hooks/blocking/code_rules_dead_split_branch.py +0 -225
- package/hooks/blocking/code_rules_mock_completeness.py +0 -295
- package/hooks/blocking/code_rules_scope_binding.py +0 -151
- package/hooks/blocking/code_rules_unused_imports.py +0 -197
- package/hooks/blocking/hedging_language_blocker.py +0 -221
- package/hooks/blocking/intent_only_ending_blocker.py +0 -148
- package/hooks/blocking/test_code_rules_enforcer_dead_argparse_argument.py +0 -534
- package/hooks/blocking/test_code_rules_enforcer_dead_config_field.py +0 -846
- package/hooks/blocking/test_code_rules_enforcer_dead_dataclass_field.py +0 -507
- package/hooks/blocking/test_code_rules_enforcer_dead_module_constant.py +0 -679
- package/hooks/blocking/test_code_rules_enforcer_dead_module_constant_alias.py +0 -133
- package/hooks/blocking/test_code_rules_enforcer_dead_module_constant_read_cap.py +0 -103
- package/hooks/blocking/test_code_rules_enforcer_dead_split_branch.py +0 -105
- package/hooks/blocking/test_code_rules_enforcer_docstring_args_span_scope.py +0 -425
- package/hooks/blocking/test_code_rules_enforcer_docstring_cardinal_family.py +0 -176
- package/hooks/blocking/test_code_rules_enforcer_docstring_delegation_summary.py +0 -385
- package/hooks/blocking/test_code_rules_enforcer_docstring_fallback_branch.py +0 -398
- package/hooks/blocking/test_code_rules_enforcer_docstring_field_runmode_outcome.py +0 -129
- package/hooks/blocking/test_code_rules_enforcer_docstring_inline_literal_claim.py +0 -93
- package/hooks/blocking/test_code_rules_enforcer_docstring_length_constant_superlative.py +0 -198
- package/hooks/blocking/test_code_rules_enforcer_docstring_mark_glyph_enumeration.py +0 -262
- package/hooks/blocking/test_code_rules_enforcer_docstring_no_consumer.py +0 -93
- package/hooks/blocking/test_code_rules_enforcer_docstring_no_network.py +0 -115
- package/hooks/blocking/test_code_rules_enforcer_docstring_raises_largezipfile.py +0 -226
- package/hooks/blocking/test_code_rules_enforcer_docstring_returns_plural_cardinality.py +0 -207
- package/hooks/blocking/test_code_rules_enforcer_docstring_step_dispatch.py +0 -262
- package/hooks/blocking/test_code_rules_enforcer_docstring_type_checking_gate.py +0 -164
- package/hooks/blocking/test_code_rules_enforcer_docstring_unguarded_payload.py +0 -188
- package/hooks/blocking/test_code_rules_enforcer_import_block_sort.py +0 -157
- package/hooks/blocking/test_code_rules_enforcer_split_mocks_1.py +0 -303
- package/hooks/blocking/test_code_rules_enforcer_split_mocks_2.py +0 -111
- package/hooks/blocking/test_code_rules_enforcer_unused_imports.py +0 -656
- package/hooks/blocking/test_hedging_language_blocker.py +0 -261
- package/hooks/blocking/test_intent_only_ending_blocker.py +0 -209
- package/hooks/hooks_constants/dead_argparse_argument_constants.py +0 -28
- package/hooks/hooks_constants/dead_config_field_constants.py +0 -39
- package/hooks/hooks_constants/dead_dataclass_field_constants.py +0 -25
- package/hooks/hooks_constants/dead_module_constant_constants.py +0 -30
- package/hooks/hooks_constants/hedging_uncertainty_constants.py +0 -42
- package/hooks/hooks_constants/test_dispatcher_constants_docstrings.py +0 -44
- package/rules/conservative-action.md +0 -17
- package/rules/context7.md +0 -8
- package/rules/few-words.md +0 -3
- package/rules/parallel-tools.md +0 -23
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
"""
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
"""Library helper: resolve a directory's Git repository root.
|
|
2
|
+
|
|
3
|
+
``pii_prevention_blocker.py`` and ``pii_payload_scan.py`` import
|
|
4
|
+
``resolve_repository_root`` to find the repository a staged-commit scan
|
|
5
|
+
targets, and ``session_edit_stage_gate.py`` imports it to find the
|
|
6
|
+
repository a commit's unstaged-edit check targets. This module carries no
|
|
7
|
+
hook entry point of its own; the native ``git-hooks/pre_commit.py`` runs
|
|
8
|
+
the real commit-time CODE_RULES enforcement.
|
|
6
9
|
"""
|
|
7
10
|
|
|
8
|
-
import re
|
|
9
11
|
import subprocess
|
|
10
12
|
import sys
|
|
11
13
|
from pathlib import Path
|
|
@@ -14,36 +16,12 @@ _hooks_directory = str(Path(__file__).resolve().parent.parent)
|
|
|
14
16
|
if _hooks_directory not in sys.path:
|
|
15
17
|
sys.path.insert(0, _hooks_directory)
|
|
16
18
|
|
|
17
|
-
from block_main_commit import ( # noqa: E402
|
|
18
|
-
is_commit_command,
|
|
19
|
-
parse_bash_command_from_stdin,
|
|
20
|
-
)
|
|
21
|
-
|
|
22
19
|
from hooks_constants.precommit_code_rules_gate_constants import ( # noqa: E402
|
|
23
20
|
ALL_GIT_REPOSITORY_ROOT_COMMAND,
|
|
24
21
|
GIT_COMMAND_TIMEOUT_SECONDS,
|
|
25
|
-
GIT_DASH_C_COMMIT_PATTERN,
|
|
26
22
|
)
|
|
27
23
|
|
|
28
24
|
|
|
29
|
-
def is_git_commit_invocation(bash_command: str) -> bool:
|
|
30
|
-
"""Report whether *bash_command* runs a git commit.
|
|
31
|
-
|
|
32
|
-
Matches both the plain ``git commit`` substring form and the
|
|
33
|
-
``git -C <path> commit`` form, where the directory flag sits between
|
|
34
|
-
the two words.
|
|
35
|
-
|
|
36
|
-
Args:
|
|
37
|
-
bash_command: The Bash tool command string from the hook payload.
|
|
38
|
-
|
|
39
|
-
Returns:
|
|
40
|
-
True when the command invokes git commit; False otherwise.
|
|
41
|
-
"""
|
|
42
|
-
if is_commit_command(bash_command):
|
|
43
|
-
return True
|
|
44
|
-
return re.search(GIT_DASH_C_COMMIT_PATTERN, bash_command) is not None
|
|
45
|
-
|
|
46
|
-
|
|
47
25
|
def resolve_repository_root(working_directory: str | None) -> Path | None:
|
|
48
26
|
"""Resolve the Git repository root for a directory.
|
|
49
27
|
|
|
@@ -70,16 +48,3 @@ def resolve_repository_root(working_directory: str | None) -> Path | None:
|
|
|
70
48
|
if not top_level_text:
|
|
71
49
|
return None
|
|
72
50
|
return Path(top_level_text)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
def main() -> None:
|
|
76
|
-
"""Pass Git commit validation through to configured Git hooks."""
|
|
77
|
-
bash_command = parse_bash_command_from_stdin()
|
|
78
|
-
if not is_git_commit_invocation(bash_command):
|
|
79
|
-
sys.exit(0)
|
|
80
|
-
sys.stderr.write("Git commit proceeds to configured Git hooks.\n")
|
|
81
|
-
sys.exit(0)
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if __name__ == "__main__":
|
|
85
|
-
main()
|
|
@@ -20,9 +20,6 @@ if _hooks_dir not in sys.path:
|
|
|
20
20
|
sys.path.insert(0, _hooks_dir)
|
|
21
21
|
|
|
22
22
|
from blocking.code_rules_shared import is_ephemeral_path # noqa: E402
|
|
23
|
-
from blocking.config.prose_style_enforcement_constants import ( # noqa: E402
|
|
24
|
-
prose_style_enforcement_enabled_in_environment,
|
|
25
|
-
)
|
|
26
23
|
from hooks_constants.hook_block_logger import log_hook_block # noqa: E402
|
|
27
24
|
from hooks_constants.pre_tool_use_stdin import read_hook_input_dictionary_from_stdin # noqa: E402
|
|
28
25
|
from hooks_constants.state_description_blocker_constants import ( # noqa: E402
|
|
@@ -281,8 +278,7 @@ def evaluate(payload_by_key: dict[str, object]) -> str | None:
|
|
|
281
278
|
|
|
282
279
|
Applies the same tool-name gate, file-extension gate, content selection, and
|
|
283
280
|
pattern scan the standalone hook applies. Returns the deny-reason text when a
|
|
284
|
-
historical phrase is found, or None to allow.
|
|
285
|
-
is default-off via ``prose_style_enforcement_enabled_in_environment()``.
|
|
281
|
+
historical phrase is found, or None to allow. The check is unconditional.
|
|
286
282
|
|
|
287
283
|
Args:
|
|
288
284
|
payload_by_key: The PreToolUse payload with tool_name and tool_input.
|
|
@@ -291,8 +287,6 @@ def evaluate(payload_by_key: dict[str, object]) -> str | None:
|
|
|
291
287
|
The permissionDecisionReason text when the write is denied, or None when
|
|
292
288
|
the write is allowed.
|
|
293
289
|
"""
|
|
294
|
-
if not prose_style_enforcement_enabled_in_environment():
|
|
295
|
-
return None
|
|
296
290
|
raw_tool_name = payload_by_key.get("tool_name", "")
|
|
297
291
|
tool_name = raw_tool_name if isinstance(raw_tool_name, str) else ""
|
|
298
292
|
if tool_name not in ("Write", "Edit"):
|
|
@@ -1,30 +1 @@
|
|
|
1
|
-
# tdd_enforcer_parts
|
|
2
1
|
|
|
3
|
-
The concern modules `tdd_enforcer.py` wires together to run the TDD gate. Each
|
|
4
|
-
module owns one concern; the entry hook imports them and re-exports their
|
|
5
|
-
surface for the test suite.
|
|
6
|
-
|
|
7
|
-
## Modules
|
|
8
|
-
|
|
9
|
-
| File | Purpose |
|
|
10
|
-
|---|---|
|
|
11
|
-
| `path_classification.py` | Classifies a write target (docs, tests, `.claude` trees) the gate skips, and extracts the written text from a Write, Edit, or MultiEdit payload |
|
|
12
|
-
| `content_analysis.py` | Decides whether a payload is constants-only, and whether an Edit or MultiEdit merely removes or reorders imports |
|
|
13
|
-
| `candidate_paths.py` | Resolves the candidate test files whose freshness can satisfy the gate for a production file |
|
|
14
|
-
| `freshness.py` | Checks whether a candidate test was modified within the window and holds a real test function |
|
|
15
|
-
| `git_tracking.py` | Detects a write that restores a git-tracked file absent on disk, so a remove-then-Write rewrite is exempt from the gate |
|
|
16
|
-
| `decisions.py` | Builds the deny reason and writes the allow or deny decision JSON to stdout |
|
|
17
|
-
| `__init__.py` | Package marker |
|
|
18
|
-
|
|
19
|
-
## Subdirectories
|
|
20
|
-
|
|
21
|
-
| Entry | Description |
|
|
22
|
-
|---|---|
|
|
23
|
-
| `config/` | The freshness window, the ancestor-walk limit, the git-tracking command tokens, the source-file extension sets, and the join separator (`tdd_enforcer_constants.py`) |
|
|
24
|
-
| `tests/` | pytest suite with one test module per module above |
|
|
25
|
-
|
|
26
|
-
## Running tests
|
|
27
|
-
|
|
28
|
-
```bash
|
|
29
|
-
python -m pytest packages/claude-dev-env/hooks/blocking/tdd_enforcer_parts/tests/
|
|
30
|
-
```
|
|
@@ -71,14 +71,6 @@ def test_every_check_function_is_called_in_validate_content() -> None:
|
|
|
71
71
|
)
|
|
72
72
|
|
|
73
73
|
|
|
74
|
-
def test_dead_module_constant_check_stays_wired() -> None:
|
|
75
|
-
validate_content_source = _validate_content_source()
|
|
76
|
-
assert "check_dead_module_constants" in validate_content_source, (
|
|
77
|
-
"check_dead_module_constants must stay dispatched from validate_content so a "
|
|
78
|
-
"dead exported constant (the MEDIUM_TEXT class) is blocked at Write/Edit time."
|
|
79
|
-
)
|
|
80
|
-
|
|
81
|
-
|
|
82
74
|
def test_known_undispatched_set_lists_only_existing_checks() -> None:
|
|
83
75
|
all_check_names = set(_all_check_function_names())
|
|
84
76
|
stale_names = KNOWN_UNDISPATCHED_CHECKS - all_check_names
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
"""Tests for the module-docstring check roster
|
|
1
|
+
"""Tests for the module-docstring check roster.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
module-level tuple of literal attribute names. The drift the
|
|
3
|
+
Catches docstring-prose-vs-implementation drift in a check-registry module — a
|
|
4
|
+
hook module that exposes several public ``check_*`` functions. The drift the
|
|
6
5
|
``code_rules_test_assertions.py`` module hit at PR #713 HEAD: a one-line module
|
|
7
|
-
docstring that names four of its five public checks
|
|
8
|
-
that enumerates three plumbing attributes while the tuple it reads holds four.
|
|
6
|
+
docstring that names four of its five public checks.
|
|
9
7
|
"""
|
|
10
8
|
|
|
11
9
|
from __future__ import annotations
|
|
@@ -32,10 +30,6 @@ def check_module_docstring_names_public_checks(content: str, file_path: str) ->
|
|
|
32
30
|
return code_rules_enforcer.check_module_docstring_names_public_checks(content, file_path)
|
|
33
31
|
|
|
34
32
|
|
|
35
|
-
def check_docstring_tuple_enumeration_match(content: str, file_path: str) -> list[str]:
|
|
36
|
-
return code_rules_enforcer.check_docstring_tuple_enumeration_match(content, file_path)
|
|
37
|
-
|
|
38
|
-
|
|
39
33
|
def validate_content(content: str, file_path: str, old_content: str) -> list[str]:
|
|
40
34
|
return code_rules_enforcer.validate_content(content, file_path, old_content)
|
|
41
35
|
|
|
@@ -202,101 +196,6 @@ def test_should_handle_module_roster_syntax_error_gracefully() -> None:
|
|
|
202
196
|
assert issues == [], f"Syntax error must yield no issues, got: {issues!r}"
|
|
203
197
|
|
|
204
198
|
|
|
205
|
-
def _module_with_drifted_tuple_enumeration() -> str:
|
|
206
|
-
return (
|
|
207
|
-
'"""Call-args plumbing detection."""\n'
|
|
208
|
-
"\n"
|
|
209
|
-
'_CALL_ARGS_PLUMBING_ATTRIBUTES = ("call_args", "call_args_list", "called", "call_count")\n'
|
|
210
|
-
"\n"
|
|
211
|
-
"def check_plumbing(content: str, file_path: str) -> list[str]:\n"
|
|
212
|
-
' """Advise when a test asserts only on call-args plumbing.\n'
|
|
213
|
-
"\n"
|
|
214
|
-
" A body asserting on call-args plumbing (``call_args``, ``call_args_list``,\n"
|
|
215
|
-
" ``.kwargs``) reaches into mock internals rather than observed behavior.\n"
|
|
216
|
-
' """\n'
|
|
217
|
-
" for each_name in _CALL_ARGS_PLUMBING_ATTRIBUTES:\n"
|
|
218
|
-
" if each_name in content:\n"
|
|
219
|
-
" return [each_name]\n"
|
|
220
|
-
" return []\n"
|
|
221
|
-
)
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
def test_should_flag_docstring_tuple_enumeration_drift() -> None:
|
|
225
|
-
issues = check_docstring_tuple_enumeration_match(
|
|
226
|
-
_module_with_drifted_tuple_enumeration(), HOOK_INFRASTRUCTURE_PATH
|
|
227
|
-
)
|
|
228
|
-
assert any("check_plumbing" in each for each in issues), (
|
|
229
|
-
f"Expected the drifted enumeration to flag, got: {issues!r}"
|
|
230
|
-
)
|
|
231
|
-
assert any("kwargs" in each for each in issues), (
|
|
232
|
-
f"Expected the docstring-only token named in the message, got: {issues!r}"
|
|
233
|
-
)
|
|
234
|
-
assert any("called" in each for each in issues), (
|
|
235
|
-
f"Expected a tuple-only member named in the message, got: {issues!r}"
|
|
236
|
-
)
|
|
237
|
-
assert len(issues) == 1
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
def test_should_not_flag_docstring_tuple_enumeration_match() -> None:
|
|
241
|
-
source = (
|
|
242
|
-
'"""Call-args plumbing detection."""\n'
|
|
243
|
-
"\n"
|
|
244
|
-
'_CALL_ARGS_PLUMBING_ATTRIBUTES = ("call_args", "call_args_list", "called", "call_count")\n'
|
|
245
|
-
"\n"
|
|
246
|
-
"def check_plumbing(content: str, file_path: str) -> list[str]:\n"
|
|
247
|
-
' """Advise when a test asserts only on call-args plumbing.\n'
|
|
248
|
-
"\n"
|
|
249
|
-
" A body asserting on call-args plumbing (``call_args``, ``call_args_list``,\n"
|
|
250
|
-
" ``called``, ``call_count``) reaches into mock internals.\n"
|
|
251
|
-
' """\n'
|
|
252
|
-
" for each_name in _CALL_ARGS_PLUMBING_ATTRIBUTES:\n"
|
|
253
|
-
" if each_name in content:\n"
|
|
254
|
-
" return [each_name]\n"
|
|
255
|
-
" return []\n"
|
|
256
|
-
)
|
|
257
|
-
issues = check_docstring_tuple_enumeration_match(source, HOOK_INFRASTRUCTURE_PATH)
|
|
258
|
-
assert issues == [], f"Matching enumeration must not flag, got: {issues!r}"
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
def test_should_not_flag_docstring_without_tuple_overlap() -> None:
|
|
262
|
-
source = (
|
|
263
|
-
'"""Call-args plumbing detection."""\n'
|
|
264
|
-
"\n"
|
|
265
|
-
'_CALL_ARGS_PLUMBING_ATTRIBUTES = ("call_args", "call_args_list", "called", "call_count")\n'
|
|
266
|
-
"\n"
|
|
267
|
-
"def check_plumbing(content: str, file_path: str) -> list[str]:\n"
|
|
268
|
-
' """Advise when a test asserts only on mock internals.\n'
|
|
269
|
-
"\n"
|
|
270
|
-
" A body reading ``foo``, ``bar``, ``baz`` is unrelated to the tuple.\n"
|
|
271
|
-
' """\n'
|
|
272
|
-
" for each_name in _CALL_ARGS_PLUMBING_ATTRIBUTES:\n"
|
|
273
|
-
" if each_name in content:\n"
|
|
274
|
-
" return [each_name]\n"
|
|
275
|
-
" return []\n"
|
|
276
|
-
)
|
|
277
|
-
issues = check_docstring_tuple_enumeration_match(source, HOOK_INFRASTRUCTURE_PATH)
|
|
278
|
-
assert issues == [], f"A docstring not naming the tuple must not flag, got: {issues!r}"
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
def test_should_not_flag_when_function_does_not_reference_the_tuple() -> None:
|
|
282
|
-
source = (
|
|
283
|
-
'"""Call-args plumbing detection."""\n'
|
|
284
|
-
"\n"
|
|
285
|
-
'_CALL_ARGS_PLUMBING_ATTRIBUTES = ("call_args", "call_args_list", "called", "call_count")\n'
|
|
286
|
-
"\n"
|
|
287
|
-
"def check_unrelated(content: str, file_path: str) -> list[str]:\n"
|
|
288
|
-
' """Advise on ``call_args`` and ``call_args_list`` usage in tests."""\n'
|
|
289
|
-
" return [content[:0]]\n"
|
|
290
|
-
)
|
|
291
|
-
issues = check_docstring_tuple_enumeration_match(source, HOOK_INFRASTRUCTURE_PATH)
|
|
292
|
-
assert issues == [], f"A function not reading the tuple must not flag, got: {issues!r}"
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
def test_should_handle_tuple_enumeration_syntax_error_gracefully() -> None:
|
|
296
|
-
issues = check_docstring_tuple_enumeration_match("def broken(\n", HOOK_INFRASTRUCTURE_PATH)
|
|
297
|
-
assert issues == [], f"Syntax error must yield no issues, got: {issues!r}"
|
|
298
|
-
|
|
299
|
-
|
|
300
199
|
def test_validate_content_surfaces_module_roster_drift() -> None:
|
|
301
200
|
issues = validate_content(
|
|
302
201
|
_registry_module_omitting_a_check(), HOOK_INFRASTRUCTURE_PATH, old_content=""
|
|
@@ -309,11 +208,11 @@ def test_validate_content_surfaces_module_roster_drift() -> None:
|
|
|
309
208
|
)
|
|
310
209
|
|
|
311
210
|
|
|
312
|
-
def
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
)
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
f"Expected validate_content to surface the tuple-enumeration drift, got: {issues!r}"
|
|
211
|
+
def test_imports_logging_module_docstring_names_every_public_check() -> None:
|
|
212
|
+
"""The shipped imports-logging module's own roster stays complete after trims."""
|
|
213
|
+
module_path = Path(__file__).resolve().parent / "code_rules_imports_logging.py"
|
|
214
|
+
module_source = module_path.read_text(encoding="utf-8")
|
|
215
|
+
issues = check_module_docstring_names_public_checks(
|
|
216
|
+
module_source, HOOK_INFRASTRUCTURE_PATH
|
|
319
217
|
)
|
|
218
|
+
assert issues == [], f"Roster drift in code_rules_imports_logging.py: {issues!r}"
|
|
@@ -28,8 +28,6 @@ code_rules_enforcer = SimpleNamespace(
|
|
|
28
28
|
|
|
29
29
|
DUPLICATED_FORMAT_PRODUCTION_FILE_PATH = "packages/app/services/api_client.py"
|
|
30
30
|
|
|
31
|
-
INCOMPLETE_MOCK_TEST_FILE_PATH = "packages/app/tests/test_orders.py"
|
|
32
|
-
|
|
33
31
|
|
|
34
32
|
def _oversized_function_source(name: str) -> str:
|
|
35
33
|
body_line_count = code_rules_enforcer.FUNCTION_LENGTH_BLOCKING_THRESHOLD - 1
|
|
@@ -39,24 +37,9 @@ def _oversized_function_source(name: str) -> str:
|
|
|
39
37
|
return f"def {name}() -> None:\n" + "\n".join(body_lines) + "\n"
|
|
40
38
|
|
|
41
39
|
|
|
42
|
-
def
|
|
40
|
+
def test_should_emit_advisory_for_duplicated_format_patterns_via_validate_content(
|
|
43
41
|
capsys: object,
|
|
44
42
|
) -> None:
|
|
45
|
-
incomplete_mock_source = (
|
|
46
|
-
"mock_order = {'id': 1}\n"
|
|
47
|
-
"\n"
|
|
48
|
-
"def test_order_total() -> None:\n"
|
|
49
|
-
" total = mock_order['total']\n"
|
|
50
|
-
" assert total > 0\n"
|
|
51
|
-
)
|
|
52
|
-
code_rules_enforcer.validate_content(
|
|
53
|
-
incomplete_mock_source, INCOMPLETE_MOCK_TEST_FILE_PATH
|
|
54
|
-
)
|
|
55
|
-
captured = getattr(capsys, "readouterr")()
|
|
56
|
-
assert "mock_order" in captured.err and "total" in captured.err, (
|
|
57
|
-
f"Expected incomplete-mock advisory from validate_content, got: {captured.err!r}"
|
|
58
|
-
)
|
|
59
|
-
|
|
60
43
|
repeated_pattern_source = (
|
|
61
44
|
"def get_user(user_id: str) -> str:\n"
|
|
62
45
|
" return f'/api/{user_id}'\n"
|
|
@@ -279,3 +279,15 @@ def test_is_ephemeral_path_reads_session_id_from_environment(
|
|
|
279
279
|
throwaway_script = scratchpad_directory / "probe.py"
|
|
280
280
|
|
|
281
281
|
assert _SHARED_MODULE.is_ephemeral_path(str(throwaway_script)) is True
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def test_is_dedicated_constants_module_true_for_constants_suffix() -> None:
|
|
285
|
+
assert _SHARED_MODULE._is_dedicated_constants_module("hooks_constants/foo_constants.py") is True
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def test_is_dedicated_constants_module_true_for_config_directory() -> None:
|
|
289
|
+
assert _SHARED_MODULE._is_dedicated_constants_module("app/config/timing.py") is True
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def test_is_dedicated_constants_module_false_for_ordinary_module() -> None:
|
|
293
|
+
assert _SHARED_MODULE._is_dedicated_constants_module("app/services/orders.py") is False
|
|
@@ -1,111 +1,23 @@
|
|
|
1
|
-
"""Behavior tests for the
|
|
1
|
+
"""Behavior tests for the precommit_code_rules_gate library module.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
The module carries no hook entry point of its own; it exports
|
|
4
|
+
``resolve_repository_root`` for its importers (``pii_prevention_blocker.py``,
|
|
5
|
+
``pii_payload_scan.py``, ``session_edit_stage_gate.py``).
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
import json
|
|
9
8
|
import subprocess
|
|
10
9
|
import sys
|
|
11
10
|
from pathlib import Path
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
number: The integer to increment.
|
|
23
|
-
|
|
24
|
-
Returns:
|
|
25
|
-
The incremented integer.
|
|
26
|
-
"""
|
|
27
|
-
return number + 1
|
|
28
|
-
'''
|
|
29
|
-
|
|
30
|
-
VIOLATING_MODULE_SOURCE = '''"""Module carrying a banned identifier for the precommit gate tests."""
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def compute_total() -> int:
|
|
34
|
-
"""Return a fixed total.
|
|
35
|
-
|
|
36
|
-
Returns:
|
|
37
|
-
The fixed total.
|
|
38
|
-
"""
|
|
39
|
-
result = 1
|
|
40
|
-
return result
|
|
41
|
-
'''
|
|
42
|
-
|
|
43
|
-
WIDGET_MODULE_BASE_SOURCE = '''"""Arithmetic helpers used by the precommit gate worktree tests."""
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
def add_one(number: int) -> int:
|
|
47
|
-
"""Return *number* plus one.
|
|
48
|
-
|
|
49
|
-
Args:
|
|
50
|
-
number: The integer to increment.
|
|
51
|
-
|
|
52
|
-
Returns:
|
|
53
|
-
The incremented integer.
|
|
54
|
-
"""
|
|
55
|
-
return number + 1
|
|
56
|
-
'''
|
|
57
|
-
|
|
58
|
-
WIDGET_MODULE_EXPANDED_SOURCE = '''"""Arithmetic helpers used by the precommit gate worktree tests."""
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
def add_one(number: int) -> int:
|
|
62
|
-
"""Return *number* plus one.
|
|
63
|
-
|
|
64
|
-
Args:
|
|
65
|
-
number: The integer to increment.
|
|
66
|
-
|
|
67
|
-
Returns:
|
|
68
|
-
The incremented integer.
|
|
69
|
-
"""
|
|
70
|
-
return number + 1
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
def subtract_one(number: int) -> int:
|
|
74
|
-
"""Return *number* minus one.
|
|
75
|
-
|
|
76
|
-
Args:
|
|
77
|
-
number: The integer to decrement.
|
|
78
|
-
|
|
79
|
-
Returns:
|
|
80
|
-
The decremented integer.
|
|
81
|
-
"""
|
|
82
|
-
return number - 1
|
|
83
|
-
'''
|
|
84
|
-
|
|
85
|
-
WIDGET_TEST_BASE_SOURCE = '''"""Behavior tests for the widget arithmetic helpers."""
|
|
86
|
-
|
|
87
|
-
from widget import add_one
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
def test_add_one_increments() -> None:
|
|
91
|
-
"""add_one returns its argument plus one."""
|
|
92
|
-
assert add_one(1) == 2
|
|
93
|
-
'''
|
|
94
|
-
|
|
95
|
-
WIDGET_TEST_EXPANDED_SOURCE = '''"""Behavior tests for the widget arithmetic helpers."""
|
|
96
|
-
|
|
97
|
-
from widget import add_one, subtract_one
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
def test_add_one_increments() -> None:
|
|
101
|
-
"""add_one returns its argument plus one."""
|
|
102
|
-
assert add_one(1) == 2
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
def test_subtract_one_decrements() -> None:
|
|
106
|
-
"""subtract_one returns its argument minus one."""
|
|
107
|
-
assert subtract_one(2) == 1
|
|
108
|
-
'''
|
|
12
|
+
try:
|
|
13
|
+
import precommit_code_rules_gate as gate_module
|
|
14
|
+
from precommit_code_rules_gate import resolve_repository_root
|
|
15
|
+
except ModuleNotFoundError:
|
|
16
|
+
_BLOCKING_DIR = str(Path(__file__).resolve().parent)
|
|
17
|
+
if _BLOCKING_DIR not in sys.path:
|
|
18
|
+
sys.path.insert(0, _BLOCKING_DIR)
|
|
19
|
+
import precommit_code_rules_gate as gate_module
|
|
20
|
+
from precommit_code_rules_gate import resolve_repository_root
|
|
109
21
|
|
|
110
22
|
|
|
111
23
|
def run_git(repository_root: Path, *git_arguments: str) -> None:
|
|
@@ -123,88 +35,29 @@ def initialize_repository(repository_root: Path) -> None:
|
|
|
123
35
|
run_git(repository_root, "commit", "--allow-empty", "-m", "initial")
|
|
124
36
|
|
|
125
37
|
|
|
126
|
-
def
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
def run_hook(bash_command: str, working_directory: Path) -> subprocess.CompletedProcess[str]:
|
|
132
|
-
payload = json.dumps({"tool_input": {"command": bash_command}})
|
|
133
|
-
return subprocess.run(
|
|
134
|
-
[sys.executable, str(HOOK_PATH)],
|
|
135
|
-
input=payload,
|
|
136
|
-
capture_output=True,
|
|
137
|
-
text=True,
|
|
138
|
-
cwd=str(working_directory),
|
|
139
|
-
timeout=120,
|
|
140
|
-
)
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
def test_non_commit_command_passes_through(tmp_path: Path) -> None:
|
|
144
|
-
initialize_repository(tmp_path)
|
|
145
|
-
completed_hook = run_hook("git status", tmp_path)
|
|
146
|
-
assert completed_hook.returncode == 0
|
|
147
|
-
assert completed_hook.stdout.strip() == ""
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
def test_commit_with_clean_staged_python_file_is_allowed(tmp_path: Path) -> None:
|
|
151
|
-
initialize_repository(tmp_path)
|
|
152
|
-
stage_file(tmp_path, "incrementer.py", CLEAN_MODULE_SOURCE)
|
|
153
|
-
completed_hook = run_hook("git commit -m add", tmp_path)
|
|
154
|
-
assert completed_hook.returncode == 0
|
|
155
|
-
assert completed_hook.stdout.strip() == ""
|
|
156
|
-
assert "Git commit proceeds to configured Git hooks." in completed_hook.stderr
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
def test_commit_with_staged_python_file_passes_through(tmp_path: Path) -> None:
|
|
38
|
+
def test_resolve_repository_root_returns_root_for_directory_inside_repository(
|
|
39
|
+
tmp_path: Path,
|
|
40
|
+
) -> None:
|
|
160
41
|
initialize_repository(tmp_path)
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
assert
|
|
165
|
-
assert "Git commit proceeds to configured Git hooks." in completed_hook.stderr
|
|
42
|
+
nested_directory = tmp_path / "nested"
|
|
43
|
+
nested_directory.mkdir()
|
|
44
|
+
resolved_root = resolve_repository_root(str(nested_directory))
|
|
45
|
+
assert resolved_root == tmp_path.resolve()
|
|
166
46
|
|
|
167
47
|
|
|
168
|
-
def
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
elsewhere.mkdir()
|
|
175
|
-
quoted_root = str(repository_root)
|
|
176
|
-
completed_hook = run_hook(f'git -C "{quoted_root}" commit -m add', elsewhere)
|
|
177
|
-
assert completed_hook.returncode == 0
|
|
178
|
-
assert completed_hook.stdout.strip() == ""
|
|
179
|
-
assert "Git commit proceeds to configured Git hooks." in completed_hook.stderr
|
|
48
|
+
def test_resolve_repository_root_returns_none_outside_a_repository(
|
|
49
|
+
tmp_path: Path,
|
|
50
|
+
) -> None:
|
|
51
|
+
non_repository_directory = tmp_path / "not_a_repo"
|
|
52
|
+
non_repository_directory.mkdir()
|
|
53
|
+
assert resolve_repository_root(str(non_repository_directory)) is None
|
|
180
54
|
|
|
181
55
|
|
|
182
|
-
def
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
completed_hook = run_hook("git commit -m docs", tmp_path)
|
|
186
|
-
assert completed_hook.returncode == 0
|
|
187
|
-
assert completed_hook.stdout.strip() == ""
|
|
56
|
+
def test_module_carries_no_main_entry_point() -> None:
|
|
57
|
+
"""The module has no dispatcher-facing hook entry point."""
|
|
58
|
+
assert not hasattr(gate_module, "main")
|
|
188
59
|
|
|
189
60
|
|
|
190
|
-
def
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
main_checkout = tmp_path / "main_checkout"
|
|
194
|
-
main_checkout.mkdir()
|
|
195
|
-
initialize_repository(main_checkout)
|
|
196
|
-
(main_checkout / "widget.py").write_text(WIDGET_MODULE_BASE_SOURCE, encoding="utf-8")
|
|
197
|
-
(main_checkout / "test_widget.py").write_text(WIDGET_TEST_BASE_SOURCE, encoding="utf-8")
|
|
198
|
-
run_git(main_checkout, "add", "widget.py", "test_widget.py")
|
|
199
|
-
run_git(main_checkout, "commit", "-m", "base")
|
|
200
|
-
worktree_root = tmp_path / "sibling_worktree"
|
|
201
|
-
run_git(main_checkout, "worktree", "add", str(worktree_root), "-b", "feature")
|
|
202
|
-
stage_file(worktree_root, "widget.py", WIDGET_MODULE_EXPANDED_SOURCE)
|
|
203
|
-
stage_file(worktree_root, "test_widget.py", WIDGET_TEST_EXPANDED_SOURCE)
|
|
204
|
-
quoted_worktree = str(worktree_root)
|
|
205
|
-
completed_hook = run_hook(
|
|
206
|
-
f'git -C "{quoted_worktree}" commit -m expand', main_checkout
|
|
207
|
-
)
|
|
208
|
-
assert completed_hook.returncode == 0
|
|
209
|
-
assert completed_hook.stdout.strip() == ""
|
|
210
|
-
assert "Git commit proceeds to configured Git hooks." in completed_hook.stderr
|
|
61
|
+
def test_module_carries_no_commit_invocation_detector() -> None:
|
|
62
|
+
"""The commit-detection helper lives in block_main_commit, not duplicated here."""
|
|
63
|
+
assert not hasattr(gate_module, "is_git_commit_invocation")
|
|
@@ -183,7 +183,6 @@ def test_agent_dispatcher_passes_commit_through_without_running_gate(
|
|
|
183
183
|
|
|
184
184
|
assert completed_dispatcher.returncode == 0, completed_dispatcher.stderr
|
|
185
185
|
assert completed_dispatcher.stdout.strip() == ""
|
|
186
|
-
assert "Git commit proceeds to configured Git hooks." in completed_dispatcher.stderr
|
|
187
186
|
assert not gate_marker_path.exists()
|
|
188
187
|
|
|
189
188
|
|
|
@@ -74,7 +74,7 @@ class _RunHook:
|
|
|
74
74
|
_run_hook = _RunHook()
|
|
75
75
|
|
|
76
76
|
|
|
77
|
-
def
|
|
77
|
+
def test_historical_phrase_scan_runs_with_the_env_flag_unset(
|
|
78
78
|
monkeypatch: pytest.MonkeyPatch,
|
|
79
79
|
) -> None:
|
|
80
80
|
monkeypatch.delenv("CLAUDE_PROSE_STYLE_ENFORCEMENT", raising=False)
|
|
@@ -87,8 +87,8 @@ def test_historical_phrase_scan_is_default_off(
|
|
|
87
87
|
is_prose_style_enabled=False,
|
|
88
88
|
)
|
|
89
89
|
assert result.returncode == 0
|
|
90
|
-
assert result.stdout
|
|
91
|
-
|
|
90
|
+
assert result.stdout.strip() != ""
|
|
91
|
+
deny_reason = _evaluate_without_opt_in(
|
|
92
92
|
{
|
|
93
93
|
"tool_name": "Write",
|
|
94
94
|
"tool_input": {
|
|
@@ -96,7 +96,9 @@ def test_historical_phrase_scan_is_default_off(
|
|
|
96
96
|
"content": VIOLATION_INSTEAD_OF_COMMENT,
|
|
97
97
|
},
|
|
98
98
|
}
|
|
99
|
-
)
|
|
99
|
+
)
|
|
100
|
+
assert deny_reason is not None
|
|
101
|
+
assert "instead of" in deny_reason
|
|
100
102
|
|
|
101
103
|
|
|
102
104
|
def test_block_clean_python_comment_passes():
|