claude-dev-env 2.12.0 → 2.13.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/code_rules_gate.py +2 -3
- package/_shared/pr-loop/scripts/test_code_rules_gate.py +0 -26
- package/agents/CLAUDE.md +1 -2
- package/agents/pr-description-writer.md +11 -81
- package/bin/install.mjs +48 -9
- package/bin/install.prune.test.mjs +26 -0
- package/bin/install.settings-defaults.test.mjs +60 -33
- package/bin/install.test.mjs +2 -4
- package/commands/sr-loop.md +9 -5
- package/docs/CLAUDE.md +0 -1
- package/docs/references/CLAUDE.md +0 -1
- package/hooks/CLAUDE.md +1 -1
- package/hooks/blocking/CLAUDE.md +2 -27
- package/hooks/blocking/config/CLAUDE.md +1 -13
- package/hooks/blocking/conventional_pr_title_gate.py +1 -2
- package/hooks/blocking/send_user_file_open_locally_blocker.py +1 -1
- package/hooks/blocking/stop_dispatcher.py +4 -4
- package/hooks/blocking/test_bash_pre_tool_use_dispatcher.py +0 -3
- package/hooks/blocking/test_pre_tool_use_dispatcher.py +6 -7
- package/hooks/blocking/test_send_user_file_open_locally_blocker.py +4 -3
- package/hooks/blocking/test_shared_stdin_adoption.py +0 -2
- package/hooks/diagnostic/CLAUDE.md +3 -3
- package/hooks/diagnostic/hook_log_extractor.py +2 -36
- package/hooks/diagnostic/hook_log_stop_wrapper.py +6 -155
- package/hooks/diagnostic/test_hook_log_extractor.py +8 -21
- package/hooks/diagnostic/test_hook_log_stop_wrapper.py +3 -331
- package/hooks/git-hooks/git_hooks_constants/__init__.py +0 -6
- package/hooks/git-hooks/pre_push.py +3 -85
- package/hooks/git-hooks/test_pre_push.py +0 -130
- package/hooks/hooks.json +0 -35
- package/hooks/hooks_constants/CLAUDE.md +0 -9
- package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +0 -11
- package/hooks/hooks_constants/convergence_branch_constants.py +0 -1
- package/hooks/hooks_constants/fable_spawn_gate_constants.py +3 -4
- package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +0 -8
- package/hooks/hooks_constants/send_user_file_open_locally_blocker_constants.py +1 -6
- package/hooks/hooks_constants/stop_dispatcher_constants.py +0 -1
- package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +1 -26
- package/hooks/hooks_constants/test_pre_tool_use_dispatcher_constants.py +0 -5
- package/hooks/hooks_constants/test_stop_dispatcher_constants.py +0 -1
- package/hooks/lifecycle/CLAUDE.md +1 -3
- package/package.json +1 -1
- package/rules/CLAUDE.md +0 -2
- package/rules/gh-cli-conventions.md +0 -1
- package/rules/git-workflow.md +1 -3
- package/scripts/CLAUDE.md +0 -1
- package/scripts/_code_review_test_support.py +0 -95
- package/scripts/dev_env_scripts_constants/code_review_constants.py +9 -98
- package/scripts/dev_env_scripts_constants/test_code_review_constants.py +1 -44
- package/scripts/invoke_code_review.py +8 -451
- package/scripts/test_invoke_code_review.py +5 -61
- package/scripts/test_invoke_code_review_cli.py +1 -45
- package/scripts/test_invoke_code_review_contract.py +1 -118
- package/settings.json +1 -10
- package/skills/CLAUDE.md +8 -0
- package/skills/autoconverge/reference/CLAUDE.md +1 -1
- package/skills/autoconverge/reference/convergence.md +7 -7
- package/skills/autoconverge/reference/gotchas.md +3 -10
- package/skills/autoconverge/workflow/converge.contract.test.mjs +53 -1388
- package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +4 -3
- package/skills/autoconverge/workflow/converge.fix-recovery.test.mjs +33 -493
- package/skills/autoconverge/workflow/converge.merge-conflict.test.mjs +15 -97
- package/skills/autoconverge/workflow/converge.mjs +201 -238
- package/skills/autoconverge/workflow/converge.precatch.test.mjs +1 -1
- package/skills/bugteam/CONSTRAINTS.md +1 -1
- package/skills/bugteam/SKILL.md +9 -0
- package/skills/bugteam/reference/audit-and-teammates.md +10 -6
- package/skills/bugteam/test_skill_additions.py +9 -0
- package/skills/comments/SKILL.md +65 -0
- package/skills/descriptions/SKILL.md +51 -0
- package/skills/emergencies/SKILL.md +42 -0
- package/skills/imagegen/SKILL.md +21 -0
- package/skills/imagegen/scripts/config/__init__.py +1 -0
- package/skills/imagegen/scripts/config/constants.py +28 -0
- package/skills/imagegen/scripts/imagegen.py +45 -0
- package/skills/imagegen/scripts/imagegen_core.py +528 -0
- package/skills/imagegen/scripts/test_imagegen.py +204 -0
- package/skills/orchestrator/SKILL.md +2 -4
- package/skills/plan-to-pr/SKILL.md +6 -7
- package/skills/plan-to-pr/reference/final-validation-tasks.md +1 -1
- package/skills/plan-to-pr/reference/process-inventory.md +2 -2
- package/skills/plan-to-pr/reference/review-loop.md +2 -2
- package/skills/plan-to-pr/reference/run-record.schema.json +1 -1
- package/skills/plan-to-pr/reference/task-seeds.md +1 -1
- package/skills/plan-to-pr/reference/task-ticket.md +3 -4
- package/skills/plan-to-pr/scripts/config/constants.py +1 -2
- package/skills/plan-to-pr/scripts/test_validate_protocol.py +1 -1
- package/skills/plan-to-pr/test_skill_contract.py +2 -2
- package/skills/pr-converge/SKILL.md +13 -16
- package/skills/pr-converge/reference/progress-checklist.md +6 -5
- package/skills/pr-converge/reference/state-schema.md +1 -27
- package/skills/pr-loop-cloud-transport/SKILL.md +1 -1
- package/skills/pr-loop-cloud-transport/reference/identity-and-hooks.md +3 -3
- package/skills/pr-loop-lifecycle/SKILL.md +1 -1
- package/skills/pr-loop-lifecycle/reference/teardown-publish-permissions.md +1 -1
- package/skills/prototype/SKILL.md +4 -4
- package/skills/prototype/reference/honest-limitations.md +4 -4
- package/skills/prototype/reference/promotion-tasks.md +2 -2
- package/skills/prototype/workflows/promotion.md +4 -6
- package/skills/reviews/SKILL.md +85 -0
- package/skills/small-cl/SKILL.md +52 -0
- package/agents/code-verifier.md +0 -74
- package/docs/PR_DESCRIPTION_GUIDE.md +0 -157
- package/docs/references/code-review-enforcement.md +0 -132
- package/hooks/blocking/code_review_enforcement_config_bootstrap.py +0 -53
- package/hooks/blocking/code_review_gate_deny.py +0 -74
- package/hooks/blocking/code_review_pr_create_gate.py +0 -198
- package/hooks/blocking/code_review_push_gate.py +0 -145
- package/hooks/blocking/code_review_stamp_directory_write_blocker.py +0 -348
- package/hooks/blocking/code_review_stamp_store.py +0 -233
- package/hooks/blocking/code_review_stamp_write_blocker_parts/__init__.py +0 -7
- package/hooks/blocking/code_review_stamp_write_blocker_parts/conftest.py +0 -15
- package/hooks/blocking/code_review_stamp_write_blocker_parts/obfuscated_stamp_path_reference.py +0 -212
- package/hooks/blocking/code_review_stamp_write_blocker_parts/split_directory_change_into_stamp.py +0 -138
- package/hooks/blocking/code_review_stamp_write_blocker_parts/test_obfuscated_stamp_path_reference.py +0 -49
- package/hooks/blocking/code_review_stamp_write_blocker_parts/test_split_directory_change_into_stamp.py +0 -38
- package/hooks/blocking/code_verifier_spawn_preflight_gate.py +0 -483
- package/hooks/blocking/config/code_review_enforcement_constants.py +0 -143
- package/hooks/blocking/config/test_code_review_enforcement_constants.py +0 -166
- package/hooks/blocking/config/verified_commit_constants.py +0 -160
- package/hooks/blocking/config/verified_commit_context_constants.py +0 -21
- package/hooks/blocking/config/verified_commit_gate_output_constants.py +0 -14
- package/hooks/blocking/conftest.py +0 -123
- package/hooks/blocking/convergence_gate_blocker.py +0 -310
- package/hooks/blocking/pr_converge_bugteam_enforcer.py +0 -170
- package/hooks/blocking/pr_description_body_audit.py +0 -148
- package/hooks/blocking/pr_description_command_parser.py +0 -237
- package/hooks/blocking/pr_description_enforcer.py +0 -227
- package/hooks/blocking/pr_description_pr_number.py +0 -155
- package/hooks/blocking/pr_description_proof_of_work.py +0 -385
- package/hooks/blocking/pr_description_readability.py +0 -364
- package/hooks/blocking/reviewer_spawn_gate.py +0 -182
- package/hooks/blocking/test_code_review_enforcement_config_bootstrap.py +0 -62
- package/hooks/blocking/test_code_review_gate_deny.py +0 -54
- package/hooks/blocking/test_code_review_pr_create_gate.py +0 -199
- package/hooks/blocking/test_code_review_push_gate.py +0 -205
- package/hooks/blocking/test_code_review_stamp_directory_write_blocker.py +0 -199
- package/hooks/blocking/test_code_review_stamp_store.py +0 -205
- package/hooks/blocking/test_code_verifier_spawn_preflight_gate.py +0 -710
- package/hooks/blocking/test_code_verifier_tools_contract.py +0 -28
- package/hooks/blocking/test_convergence_gate_blocker.py +0 -428
- package/hooks/blocking/test_pr_converge_bugteam_enforcer.py +0 -311
- package/hooks/blocking/test_pr_converge_bugteam_enforcer_state_tolerance.py +0 -184
- package/hooks/blocking/test_pr_description_enforcer.py +0 -221
- package/hooks/blocking/test_pr_description_enforcer_body_audit.py +0 -247
- package/hooks/blocking/test_pr_description_enforcer_body_rules.py +0 -493
- package/hooks/blocking/test_pr_description_enforcer_command_parser.py +0 -366
- package/hooks/blocking/test_pr_description_enforcer_pr_number.py +0 -159
- package/hooks/blocking/test_pr_description_enforcer_proof_gate.py +0 -175
- package/hooks/blocking/test_pr_description_enforcer_readability.py +0 -443
- package/hooks/blocking/test_pr_description_proof_of_work.py +0 -313
- package/hooks/blocking/test_reviewer_spawn_gate.py +0 -230
- package/hooks/blocking/test_verdict_directory_write_blocker.py +0 -804
- package/hooks/blocking/test_verification_verdict_store.py +0 -974
- package/hooks/blocking/test_verified_commit_config_bootstrap.py +0 -67
- package/hooks/blocking/test_verified_commit_docs_delta.py +0 -176
- package/hooks/blocking/test_verified_commit_gate.py +0 -581
- package/hooks/blocking/test_verified_commit_gate_additional_context.py +0 -134
- package/hooks/blocking/test_verified_commit_message_accuracy_blocker.py +0 -131
- package/hooks/blocking/test_verifier_verdict_minter.py +0 -299
- package/hooks/blocking/tests/test_verified_commit_gate.py +0 -41
- package/hooks/blocking/verdict_directory_write_blocker.py +0 -687
- package/hooks/blocking/verification_verdict_store.py +0 -1039
- package/hooks/blocking/verified_commit_config_bootstrap.py +0 -63
- package/hooks/blocking/verified_commit_gate.py +0 -113
- package/hooks/blocking/verified_commit_gate_parts/CLAUDE.md +0 -28
- package/hooks/blocking/verified_commit_gate_parts/__init__.py +0 -1
- package/hooks/blocking/verified_commit_gate_parts/command_tokenization.py +0 -174
- package/hooks/blocking/verified_commit_gate_parts/deny_payload.py +0 -53
- package/hooks/blocking/verified_commit_gate_parts/deny_reason.py +0 -80
- package/hooks/blocking/verified_commit_gate_parts/directory_resolution.py +0 -170
- package/hooks/blocking/verified_commit_gate_parts/gated_invocations.py +0 -217
- package/hooks/blocking/verified_commit_gate_parts/tests/conftest.py +0 -10
- package/hooks/blocking/verified_commit_gate_parts/tests/test_command_tokenization.py +0 -94
- package/hooks/blocking/verified_commit_gate_parts/tests/test_deny_payload.py +0 -17
- package/hooks/blocking/verified_commit_gate_parts/tests/test_deny_reason.py +0 -38
- package/hooks/blocking/verified_commit_gate_parts/tests/test_directory_resolution.py +0 -71
- package/hooks/blocking/verified_commit_gate_parts/tests/test_gated_invocations.py +0 -96
- package/hooks/blocking/verified_commit_message_accuracy_blocker.py +0 -167
- package/hooks/blocking/verifier_verdict_minter.py +0 -280
- package/hooks/hooks_constants/code_verifier_spawn_preflight_gate_constants.py +0 -64
- package/hooks/hooks_constants/convergence_gate_blocker_constants.py +0 -53
- package/hooks/hooks_constants/pr_converge_bugteam_enforcer_constants.py +0 -55
- package/hooks/hooks_constants/pr_converge_bugteam_enforcer_state.py +0 -67
- package/hooks/hooks_constants/pr_description_enforcer_constants.py +0 -167
- package/hooks/hooks_constants/pr_description_proof_of_work_constants.py +0 -107
- package/hooks/hooks_constants/reviewer_spawn_gate_constants.py +0 -41
- package/hooks/hooks_constants/test_pr_description_enforcer_constants.py +0 -292
- package/hooks/lifecycle/pr_converge_bugteam_skill_tracker.py +0 -198
- package/hooks/lifecycle/test_pr_converge_bugteam_skill_tracker.py +0 -283
- package/rules/proof-of-work-pr-comments.md +0 -22
- package/rules/verified-commit-gate-skip.md +0 -4
- package/scripts/Show-Asset.ps1 +0 -106
package/hooks/blocking/CLAUDE.md
CHANGED
|
@@ -6,10 +6,7 @@ PreToolUse hooks that deny (block) tool calls when a rule is violated. The main
|
|
|
6
6
|
|
|
7
7
|
| Directory | Role |
|
|
8
8
|
|---|---|
|
|
9
|
-
| `config/` | Shared constants for the verified-commit gate family (`verified_commit_constants.py`) |
|
|
10
9
|
| `tdd_enforcer_parts/` | Concern modules the `tdd_enforcer.py` entry hook wires together: path classification, content analysis, candidate-path resolution, freshness, git-tracking restore detection, decisions, and constants |
|
|
11
|
-
| `verified_commit_gate_parts/` | Concern modules the `verified_commit_gate.py` entry hook wires together: command tokenization, directory-change resolution, gated git-invocation resolution, deny-reason resolution, and deny-payload assembly |
|
|
12
|
-
| `code_review_stamp_write_blocker_parts/` | Concern modules the `code_review_stamp_directory_write_blocker.py` entry hook wires together: the split directory-change-into-stamp matcher and the obfuscated-stamp-path-write matcher |
|
|
13
10
|
| `claude_md_orphan_file_blocker_parts/` | Concern modules the `claude_md_orphan_file_blocker.py` entry hook wires together: reference extraction, subtree scan, scan plan, decision, and constants |
|
|
14
11
|
| `package_inventory_stale_blocker_parts/` | Concern modules the `package_inventory_stale_blocker.py` entry hook wires together: inventory detection, decision, and constants |
|
|
15
12
|
| `inventory_intent_records/` | The shared per-session pending-intent store both inventory blockers read to break the file/row add-order deadlock |
|
|
@@ -73,11 +70,6 @@ The check modules it calls are the `code_rules_<concern>.py` files below.
|
|
|
73
70
|
| `block_main_commit.py` | PreToolUse (Bash) | `git commit`/`git push` directly to `main` |
|
|
74
71
|
| `bot_mention_comment_blocker.py` | PreToolUse (Write/Edit) | PR review comments that @-mention a bot |
|
|
75
72
|
| `claude_md_orphan_file_blocker.py` | PreToolUse (Write/Edit/MultiEdit) | Per-directory `CLAUDE.md` table cells naming a bare filename absent from the directory subtree |
|
|
76
|
-
| `code_review_pr_create_gate.py` | PreToolUse (Bash/MCP GitHub) | `gh pr create` or the MCP `create_pull_request` tool without a clean `xhigh` code-review stamp covering the branch surface |
|
|
77
|
-
| `code_review_push_gate.py` | PreToolUse (Bash/PowerShell) | `git push` without a clean `low` code-review stamp covering the branch surface |
|
|
78
|
-
| `code_review_stamp_directory_write_blocker.py` | PreToolUse (Bash/PowerShell/Write/Edit/MultiEdit) | Shell or file-tool writes into `~/.claude/code-review-stamps/`, and shell references to the stamp store module or its mint call, outside the sanctioned invoker |
|
|
79
|
-
| `code_verifier_spawn_preflight_gate.py` | PreToolUse (Agent) | Spawning the `code-verifier` subagent when the branch has a merge conflict vs its base or a CODE_RULES violation on a working-tree-added line, or the CODE_RULES engine fails to load |
|
|
80
|
-
| `convergence_gate_blocker.py` | PreToolUse (Bash) | Convergence workflow actions on a conflicting PR |
|
|
81
73
|
| `conventional_pr_title_gate.py` | PreToolUse (Bash) | `gh pr create`/`gh pr edit` with a `--title` that is not a Conventional Commit, in a repo whose CI runs a semantic-pull-request title check |
|
|
82
74
|
| `destructive_command_blocker.py` | PreToolUse (Bash/PowerShell) | Shell commands with destructive literals (`rm -rf`, `git reset --hard`, etc.) |
|
|
83
75
|
| `docstring_rule_gate_count_blocker.py` | PreToolUse (Write/Edit/MultiEdit) | A stale spelled-out gate-validator count in `docstring-prose-matches-implementation.md` — the "N more gate validators" / "M gated slices" count drifting from the `check_docstring_*` validators the prose names |
|
|
@@ -101,13 +93,10 @@ The check modules it calls are the `code_rules_<concern>.py` files below.
|
|
|
101
93
|
| `pii_scanner.py` | library | Pure text scanners shared by `pii_prevention_blocker.py` |
|
|
102
94
|
| `piped_pytest_blocker.py` | PreToolUse (Bash) | A pytest run whose output feeds a pipe, where the pipeline reports the exit code of the command on the right |
|
|
103
95
|
| `plain_language_blocker.py` | PreToolUse (Write/Edit/AskUserQuestion) | Heavy or jargon words in user-facing prose when `CLAUDE_PROSE_STYLE_ENFORCEMENT` is on (default off); AskUserQuestion lean-block structure stays always on |
|
|
104
|
-
| `pr_converge_bugteam_enforcer.py` | PreToolUse | Enforces that bugteam runs in parallel with bugbot in pr-converge loops |
|
|
105
|
-
| `pr_description_enforcer.py` | PreToolUse (Bash) | `gh pr create`/`edit`/`comment` bodies that fail the Anthropic claude-code style audit, proof-shaped `gh pr comment` bodies missing proof-of-work parts, and `gh pr ready` while the PR carries no passing proof comment |
|
|
106
96
|
| `precommit_code_rules_gate.py` | PreToolUse (Bash) | Staged changes that fail the CODE_RULES gate at commit time |
|
|
107
97
|
| `pytest_testpaths_orphan_blocker.py` | PreToolUse (Write/Edit/MultiEdit) | New `test_*.py` files created under a directory absent from a package's explicit pytest `testpaths` allowlist |
|
|
108
98
|
| `question_to_user_enforcer.py` | Stop | User-directed questions not routed through `AskUserQuestion` |
|
|
109
|
-
| `
|
|
110
|
-
| `send_user_file_open_locally_blocker.py` | PreToolUse (SendUserFile) | A desk-side file attach (`SendUserFile` with `status` not `proactive`); points to opening the file locally via `Show-Asset.ps1` |
|
|
99
|
+
| `send_user_file_open_locally_blocker.py` | PreToolUse (SendUserFile) | A desk-side file attach (`SendUserFile` with `status` not `proactive`); points to opening the file with its native Windows app |
|
|
111
100
|
| `sensitive_file_protector.py` | PreToolUse (Write/Edit) | Writes to sensitive credential or config files |
|
|
112
101
|
| `session_edit_stage_gate.py` | PreToolUse (Bash) | A `git commit` that would drop files edited this session because they are tracked but left unstaged |
|
|
113
102
|
| `session_handoff_blocker.py` | Stop | Responses suggesting a new session mid-task |
|
|
@@ -117,10 +106,6 @@ The check modules it calls are the `code_rules_<concern>.py` files below.
|
|
|
117
106
|
| `subprocess_budget_completeness.py` | PreToolUse | Subprocess calls missing required budget arguments |
|
|
118
107
|
| `tdd_enforcer.py` | PreToolUse (Write/Edit) | Production code written without a matching failing test |
|
|
119
108
|
| `unscoped_search_blocker.py` | PreToolUse (Bash/PowerShell) | A `find` or recursive listing that walks from the filesystem root, a drive root, bare home, or a network share root |
|
|
120
|
-
| `verdict_directory_write_blocker.py` | PreToolUse (Bash/PowerShell) | Shell writes into `~/.claude/verification/` |
|
|
121
|
-
| `verified_commit_gate.py` | PreToolUse (Bash/PowerShell) | `git commit`/`git push` without a passing verifier verdict |
|
|
122
|
-
| `verified_commit_message_accuracy_blocker.py` | PreToolUse | Commit messages that misstate what the diff has |
|
|
123
|
-
| `verifier_verdict_minter.py` | SubagentStop | Mints a passing verdict file when a `code-verifier` agent finishes cleanly |
|
|
124
109
|
| `volatile_path_in_post_blocker.py` | PreToolUse (Bash/MCP GitHub) | `gh` post commands and GitHub MCP post tools whose body references a volatile path (job scratch dir, worktree, or system temp) that outlives the durable post |
|
|
125
110
|
| `windows_rmtree_blocker.py` | PreToolUse (Write/Edit) | `shutil.rmtree` with `ignore_errors=True` on Windows |
|
|
126
111
|
| `workflow_substitution_slot_blocker.py` | PreToolUse (Write/Edit) | Workflow templates with bare per-iteration tokens missing angle-bracket slots |
|
|
@@ -131,18 +116,8 @@ The check modules it calls are the `code_rules_<concern>.py` files below.
|
|
|
131
116
|
| File | Role |
|
|
132
117
|
|---|---|
|
|
133
118
|
| `_gh_body_arg_utils.py` | Parsing helpers for `gh_body_arg_blocker.py` |
|
|
134
|
-
| `code_review_enforcement_config_bootstrap.py` | Binds `config.code_review_enforcement_constants` to the sibling `config/` file by explicit location, so the code-review gate family resolves its constants regardless of a foreign `config` package's `sys.path` order |
|
|
135
|
-
| `code_review_gate_deny.py` | Shared deny scaffold for the push and PR-create code-review gates: the `hookSpecificOutput` deny-payload builder and the log-and-emit helper, so both gates share one deny shape |
|
|
136
|
-
| `code_review_stamp_store.py` | Reads and writes the per-work-tree code-review stamp files under `~/.claude/code-review-stamps/`, and decides whether a clean stamp at the needed effort covers the live branch surface |
|
|
137
|
-
| `pr_description_body_audit.py` | Body audit logic for `pr_description_enforcer.py` |
|
|
138
|
-
| `pr_description_command_parser.py` | `gh` command parsing for `pr_description_enforcer.py` |
|
|
139
|
-
| `pr_description_pr_number.py` | PR number extraction logic |
|
|
140
|
-
| `pr_description_proof_of_work.py` | Proof-of-work comment audit and `gh pr ready` gate logic for `pr_description_enforcer.py` |
|
|
141
|
-
| `pr_description_readability.py` | Readability checks on PR description bodies |
|
|
142
|
-
| `verification_verdict_store.py` | Reads and writes verdict files under `~/.claude/verification/` |
|
|
143
|
-
| `verified_commit_config_bootstrap.py` | Binds `config.verified_commit_constants` to the sibling `config/` file by explicit location, so the gate family resolves its constants regardless of a foreign `config` package's `sys.path` order |
|
|
144
119
|
|
|
145
120
|
## Conventions
|
|
146
121
|
|
|
147
122
|
- Tests live beside each hook as `test_<hookname>.py` or `test_<hookname>_<suffix>.py`. Run with `python -m pytest <test_file>`.
|
|
148
|
-
- Tunable constants live in `hooks_constants/<hook_name>_constants.py
|
|
123
|
+
- Tunable constants live in `hooks_constants/<hook_name>_constants.py`.
|
|
@@ -1,22 +1,10 @@
|
|
|
1
1
|
# hooks/blocking/config
|
|
2
2
|
|
|
3
|
-
A Python package that holds shared constants for blocking hooks.
|
|
3
|
+
A Python package that holds shared constants for blocking hooks. Opinionated prose-style gates import from here.
|
|
4
4
|
|
|
5
5
|
## Key files
|
|
6
6
|
|
|
7
7
|
| File | Contents |
|
|
8
8
|
|---|---|
|
|
9
9
|
| `__init__.py` | Declares this as a regular package (not a namespace package) so it resolves first on `sys.path` |
|
|
10
|
-
| `code_review_enforcement_constants.py` | Stamp and effort tunables for the code-review enforcement gates |
|
|
11
10
|
| `prose_style_enforcement_constants.py` | `CLAUDE_PROSE_STYLE_ENFORCEMENT` opt-in (default off) for opinionated prose gates |
|
|
12
|
-
| `verified_commit_constants.py` | All tunables for the gate: directory names, regex patterns for detecting verdict paths and obfuscation attempts, timeout values, git subcommand sets, bypass marker, and corrective messages |
|
|
13
|
-
| `verified_commit_context_constants.py` | `VERIFY_SKIP_ADDITIONAL_CONTEXT`, the ``# verify-skip`` usage rule the gate attaches to a deny payload's `additionalContext` |
|
|
14
|
-
| `verified_commit_gate_output_constants.py` | `PRE_TOOL_USE_HOOK_EVENT_NAME` and `DENY_PERMISSION_DECISION` (the deny payload's event name and decision string), `GATE_HOOK_MODULE_NAME` (the gate's own module name for block logging), and `REGEX_ALTERNATION_SEPARATOR` (the `\|` join for the directory-change verb alternation in `gated_invocations.py`) |
|
|
15
|
-
|
|
16
|
-
## Key constants in `verified_commit_constants.py`
|
|
17
|
-
|
|
18
|
-
- `VERIFICATION_BYPASS_MARKER` — the `# verify-skip` comment that exempts a single commit/push from the gate
|
|
19
|
-
- `MINTING_AGENT_TYPE` — `"code-verifier"`, the agent type whose SubagentStop hook mints verdicts
|
|
20
|
-
- `VERDICT_DIRECTORY_NAME` — `"verification"`, the directory under `~/.claude/` that holds verdict JSON files
|
|
21
|
-
- `DOCS_ONLY_EXTENSIONS` — extensions (`.md`, `.txt`, images) whose changes are mechanically exempt from the gate
|
|
22
|
-
- `CORRECTIVE_MESSAGE` / `VERDICT_DIRECTORY_GUARD_MESSAGE` — user-facing block messages
|
|
@@ -36,8 +36,6 @@ _hooks_dir = str(Path(__file__).resolve().parent.parent)
|
|
|
36
36
|
if _hooks_dir not in sys.path:
|
|
37
37
|
sys.path.insert(0, _hooks_dir)
|
|
38
38
|
|
|
39
|
-
from verification_verdict_store import resolve_repo_root # noqa: E402
|
|
40
|
-
|
|
41
39
|
from blocking._gh_body_arg_utils import ( # noqa: E402
|
|
42
40
|
all_value_flags,
|
|
43
41
|
count_extra_tokens_to_skip_for_split_quoted_value,
|
|
@@ -68,6 +66,7 @@ from hooks_constants.conventional_pr_title_gate_constants import ( # noqa: E402
|
|
|
68
66
|
YAML_LIST_ITEM_PREFIX,
|
|
69
67
|
)
|
|
70
68
|
from hooks_constants.hook_block_logger import log_hook_block # noqa: E402
|
|
69
|
+
from hooks_constants.project_paths_reader import find_git_root as resolve_repo_root # noqa: E402
|
|
71
70
|
|
|
72
71
|
|
|
73
72
|
def _matches_gh_pr_title_subcommand(command: str) -> bool:
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
SendUserFile attaches a file to the session. While the user is at the terminal
|
|
5
5
|
(status "normal" or unset) an attach does not let them see the file — it must
|
|
6
|
-
open on screen in its
|
|
6
|
+
open on screen in its native Windows app. The one attach allowed
|
|
7
7
|
through is an away-from-desk phone push (status "proactive").
|
|
8
8
|
"""
|
|
9
9
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""Stop-hook dispatcher that hosts the
|
|
2
|
+
"""Stop-hook dispatcher that hosts the Stop-chain blockers in one process.
|
|
3
3
|
|
|
4
4
|
Reads the Stop payload from stdin once, runs each hosted hook in registration
|
|
5
5
|
order via the shared hosted-hook runner, and emits the first block decision the
|
|
6
|
-
chain produced. Later hooks still run
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
chain produced. Later hooks still run after an earlier block so every hosted
|
|
7
|
+
hook gets a chance to run. A single hosted hook crash fails open and does not
|
|
8
|
+
suppress the remaining hooks.
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
11
|
from __future__ import annotations
|
|
@@ -221,9 +221,6 @@ def test_powershell_selects_the_shared_hooks_in_registration_order() -> None:
|
|
|
221
221
|
assert powershell_paths == [
|
|
222
222
|
"blocking/unscoped_search_blocker.py",
|
|
223
223
|
"blocking/pii_prevention_blocker.py",
|
|
224
|
-
"blocking/code_review_push_gate.py",
|
|
225
|
-
"blocking/code_review_pr_create_gate.py",
|
|
226
|
-
"blocking/code_review_stamp_directory_write_blocker.py",
|
|
227
224
|
]
|
|
228
225
|
|
|
229
226
|
|
|
@@ -715,8 +715,8 @@ def test_dispatcher_write_applies_both_groups() -> None:
|
|
|
715
715
|
assert "blocking/plain_language_blocker.py" in all_write_script_paths, (
|
|
716
716
|
"plain_language_blocker (Group B) must be in Write applicable set"
|
|
717
717
|
)
|
|
718
|
-
assert len(all_write_entries) ==
|
|
719
|
-
f"Write tool must apply to all
|
|
718
|
+
assert len(all_write_entries) == 19, (
|
|
719
|
+
f"Write tool must apply to all 19 hosted hooks, got {len(all_write_entries)}"
|
|
720
720
|
)
|
|
721
721
|
|
|
722
722
|
|
|
@@ -732,16 +732,16 @@ def test_dispatcher_edit_applies_both_groups() -> None:
|
|
|
732
732
|
assert "advisory/refactor_guard.py" in all_edit_script_paths, (
|
|
733
733
|
"refactor_guard is Edit-scoped and hosted, so it belongs in the Edit applicable set"
|
|
734
734
|
)
|
|
735
|
-
assert len(all_edit_entries) ==
|
|
736
|
-
f"expected
|
|
735
|
+
assert len(all_edit_entries) == 22, (
|
|
736
|
+
f"expected 22 Edit entries, got {len(all_edit_entries)}"
|
|
737
737
|
)
|
|
738
738
|
|
|
739
739
|
|
|
740
740
|
def test_dispatcher_multi_edit_applies_only_group_b() -> None:
|
|
741
741
|
"""MultiEdit tool triggers only Group B (10 hooks), not Group A."""
|
|
742
742
|
all_multi_edit_entries = _applicable_entries_for_tool(MULTI_EDIT_TOOL_NAME)
|
|
743
|
-
assert len(all_multi_edit_entries) ==
|
|
744
|
-
f"MultiEdit tool must apply to exactly
|
|
743
|
+
assert len(all_multi_edit_entries) == 9, (
|
|
744
|
+
f"MultiEdit tool must apply to exactly 9 Group-B hooks, got {len(all_multi_edit_entries)}"
|
|
745
745
|
)
|
|
746
746
|
|
|
747
747
|
|
|
@@ -969,7 +969,6 @@ def test_hosted_hook_set_covers_all_write_edit_blocking_hooks() -> None:
|
|
|
969
969
|
"blocking/state_description_blocker.py",
|
|
970
970
|
"blocking/subprocess_budget_completeness.py",
|
|
971
971
|
"blocking/hook_prose_detector_consistency.py",
|
|
972
|
-
"blocking/verified_commit_message_accuracy_blocker.py",
|
|
973
972
|
"blocking/workflow_substitution_slot_blocker.py",
|
|
974
973
|
"blocking/claude_md_orphan_file_blocker.py",
|
|
975
974
|
"blocking/pytest_testpaths_orphan_blocker.py",
|
|
@@ -45,8 +45,9 @@ def test_allows_proactive_status() -> None:
|
|
|
45
45
|
assert _should_block(PROACTIVE_STATUS) is False
|
|
46
46
|
|
|
47
47
|
|
|
48
|
-
def
|
|
49
|
-
assert "
|
|
48
|
+
def test_corrective_message_points_to_native_windows_app() -> None:
|
|
49
|
+
assert "native Windows app" in CORRECTIVE_MESSAGE
|
|
50
|
+
assert "Show-Asset.ps1" not in CORRECTIVE_MESSAGE
|
|
50
51
|
|
|
51
52
|
|
|
52
53
|
def test_corrective_message_names_proactive_escape_hatch() -> None:
|
|
@@ -71,7 +72,7 @@ def test_main_blocks_normal_attach() -> None:
|
|
|
71
72
|
output_text = _run_main_with_io(json.dumps(hook_input))
|
|
72
73
|
output = json.loads(output_text)
|
|
73
74
|
assert output["hookSpecificOutput"]["permissionDecision"] == "deny"
|
|
74
|
-
assert "
|
|
75
|
+
assert "native Windows app" in output["hookSpecificOutput"]["permissionDecisionReason"]
|
|
75
76
|
|
|
76
77
|
|
|
77
78
|
def test_main_allows_proactive_attach() -> None:
|
|
@@ -30,8 +30,6 @@ _BLOCKING_DIRECTORY = Path(__file__).resolve().parent
|
|
|
30
30
|
ALL_CONVERTED_HOOK_FILENAMES = (
|
|
31
31
|
"open_questions_in_plans_blocker.py",
|
|
32
32
|
"claude_md_orphan_file_blocker.py",
|
|
33
|
-
"pr_converge_bugteam_enforcer.py",
|
|
34
|
-
"verdict_directory_write_blocker.py",
|
|
35
33
|
"package_inventory_stale_blocker.py",
|
|
36
34
|
)
|
|
37
35
|
|
|
@@ -14,8 +14,8 @@ Hooks and scripts that collect, store, and query hook-firing records. The pipeli
|
|
|
14
14
|
| File | What it does |
|
|
15
15
|
|---|---|
|
|
16
16
|
| `hook_log_init.py` | One-time setup: creates the Neon schema (runs `schema.sql`), then verifies read-write parity with a sentinel round-trip |
|
|
17
|
-
| `hook_log_extractor.py` |
|
|
18
|
-
| `hook_log_stop_wrapper.py` |
|
|
17
|
+
| `hook_log_extractor.py` | Disabled CLI: exits 0 without reading transcripts or rewriting offset state; body remains for a re-enable path that ingests `hook_*` attachments into Neon |
|
|
18
|
+
| `hook_log_stop_wrapper.py` | Disabled Stop wrapper stub: exits 0; not on the Stop dispatcher roster |
|
|
19
19
|
| `schema.sql` | DDL for the `hook_events` table, `blocked_commands` view, and supporting indexes |
|
|
20
20
|
| `requirements-hook-logs.txt` | Runtime dependencies (`psycopg`) for the extractor |
|
|
21
21
|
| `requirements-hook-logs-dev.txt` | Dev/test dependencies |
|
|
@@ -38,6 +38,6 @@ The `blocked_commands` view filters to `outcome = 'blocked'`.
|
|
|
38
38
|
|
|
39
39
|
## Conventions
|
|
40
40
|
|
|
41
|
-
-
|
|
41
|
+
- Extractor and Stop wrapper mains are disabled and exit 0 with no work.
|
|
42
42
|
- Constants for the extractor (table name, offset state file, timeout) live in `hooks_constants/hook_log_extractor_constants.py`.
|
|
43
43
|
- Tests run with `python -m pytest diagnostic/test_hook_log_*.py`.
|
|
@@ -67,15 +67,10 @@ from hooks_constants.hook_log_extractor_constants import (
|
|
|
67
67
|
CATEGORY_PATH_MINIMUM_PARTS,
|
|
68
68
|
COMMAND_EXCERPT_MAX_CHARACTERS,
|
|
69
69
|
CONNECT_TIMEOUT_SECONDS,
|
|
70
|
-
DEFAULT_QUERY_FOR_SUMMARY,
|
|
71
70
|
EMPTY_STRING,
|
|
72
71
|
EXIT_CODE_EXTRACTOR_ENVIRONMENT_MISSING,
|
|
73
72
|
EXIT_CODE_SUCCESS,
|
|
74
73
|
EXIT_CODE_UNKNOWN_QUERY,
|
|
75
|
-
FLAG_FULL_REBUILD,
|
|
76
|
-
FLAG_INCREMENTAL,
|
|
77
|
-
FLAG_QUERY,
|
|
78
|
-
FLAG_SUMMARY,
|
|
79
74
|
HOOK_CATEGORY_UNCATEGORIZED,
|
|
80
75
|
HOOK_EVENTS_INSERT_SQL,
|
|
81
76
|
HOOK_EVENTS_TRUNCATE_SQL,
|
|
@@ -94,10 +89,8 @@ from hooks_constants.hook_log_extractor_constants import (
|
|
|
94
89
|
NEON_DATABASE_URL_ENVIRONMENT_VARIABLE,
|
|
95
90
|
NEWLINE_JOINER,
|
|
96
91
|
OFFLINE_WARNING_LOG,
|
|
97
|
-
OFFSET_STATE_FILE,
|
|
98
92
|
OFFSETS_JSON_INDENT,
|
|
99
93
|
OUTCOME_BY_ATTACHMENT_TYPE,
|
|
100
|
-
PROJECTS_TRANSCRIPT_ROOT,
|
|
101
94
|
QUERIES_DIRECTORY_NAME,
|
|
102
95
|
QUERY_NAME_PATTERN,
|
|
103
96
|
QUERY_NO_ROWS_RETURNED_MESSAGE,
|
|
@@ -874,35 +867,8 @@ def run_query(named_query: str) -> int:
|
|
|
874
867
|
|
|
875
868
|
|
|
876
869
|
def main() -> int:
|
|
877
|
-
"""
|
|
878
|
-
|
|
879
|
-
Supported flags:
|
|
880
|
-
|
|
881
|
-
* ``--summary`` prints the top blockers of the last twenty-four hours.
|
|
882
|
-
* ``--query <name>`` runs a pre-baked SQL file under ``queries/``.
|
|
883
|
-
* ``--full-rebuild`` truncates ``hook_events`` and re-reads every
|
|
884
|
-
JSONL from byte zero.
|
|
885
|
-
* ``--incremental`` is a documented no-op; it selects the default
|
|
886
|
-
byte-offset resumption path that the Stop hook also uses when no
|
|
887
|
-
flags are passed.
|
|
888
|
-
"""
|
|
889
|
-
all_cli_arguments = list(sys.argv[1:])
|
|
890
|
-
if FLAG_SUMMARY in all_cli_arguments:
|
|
891
|
-
return run_summary()
|
|
892
|
-
if FLAG_QUERY in all_cli_arguments:
|
|
893
|
-
flag_index = all_cli_arguments.index(FLAG_QUERY)
|
|
894
|
-
if flag_index + 1 >= len(all_cli_arguments):
|
|
895
|
-
return run_query(DEFAULT_QUERY_FOR_SUMMARY)
|
|
896
|
-
return run_query(all_cli_arguments[flag_index + 1])
|
|
897
|
-
is_full_rebuild_requested = FLAG_FULL_REBUILD in all_cli_arguments
|
|
898
|
-
is_incremental_requested = FLAG_INCREMENTAL in all_cli_arguments
|
|
899
|
-
if is_incremental_requested and is_full_rebuild_requested:
|
|
900
|
-
is_full_rebuild_requested = False
|
|
901
|
-
return run_full_extraction(
|
|
902
|
-
transcripts_root=PROJECTS_TRANSCRIPT_ROOT,
|
|
903
|
-
state_file_path=OFFSET_STATE_FILE,
|
|
904
|
-
full_rebuild=is_full_rebuild_requested,
|
|
905
|
-
)
|
|
870
|
+
"""Disabled extractor entry point: exit success with no work."""
|
|
871
|
+
return EXIT_CODE_SUCCESS
|
|
906
872
|
|
|
907
873
|
|
|
908
874
|
if __name__ == "__main__":
|
|
@@ -1,173 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""Stop-hook wrapper for
|
|
2
|
+
"""Disabled Stop-hook wrapper for the hook-log extractor.
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
1. Reads the last-spawn timestamp; if it falls within the debounce
|
|
8
|
-
window, exits 0 immediately without spawning anything (typical fast
|
|
9
|
-
path: a small file read, well under 10ms).
|
|
10
|
-
2. Otherwise records the current timestamp, then launches the extractor
|
|
11
|
-
as a fully detached background process (no stdio, separate process
|
|
12
|
-
group on POSIX or CREATE_NO_WINDOW|CREATE_NEW_PROCESS_GROUP on
|
|
13
|
-
Windows) and returns without waiting for it. CREATE_NO_WINDOW
|
|
14
|
-
prevents a console flash on Windows even when the wrapper goes
|
|
15
|
-
through ``bws run`` first; DETACHED_PROCESS would let the
|
|
16
|
-
grandchild python.exe allocate a fresh console.
|
|
17
|
-
|
|
18
|
-
Bitwarden injection: when both ``bws`` is on PATH and
|
|
19
|
-
``BWS_ACCESS_TOKEN`` is set, the extractor is launched via
|
|
20
|
-
``bws run --`` so the Neon URL never hits disk; otherwise it is
|
|
21
|
-
launched directly. The extractor itself exits 0 when
|
|
22
|
-
``NEON_HOOK_LOGS_DATABASE_URL`` is unset, so missing dependencies
|
|
23
|
-
cannot block session shutdown.
|
|
24
|
-
|
|
25
|
-
This wrapper always exits 0 so the Stop hook never surfaces a failure.
|
|
4
|
+
The wrapper is off the Stop dispatcher roster and exits 0 without spawning
|
|
5
|
+
the extractor or writing debounce state. Git history holds the prior
|
|
6
|
+
debounce-and-spawn implementation if Neon-backed extraction returns.
|
|
26
7
|
"""
|
|
27
8
|
|
|
28
9
|
from __future__ import annotations
|
|
29
10
|
|
|
30
|
-
import os
|
|
31
|
-
import shutil
|
|
32
|
-
import subprocess
|
|
33
11
|
import sys
|
|
34
|
-
import time
|
|
35
12
|
from pathlib import Path
|
|
36
13
|
|
|
37
14
|
if str(Path(__file__).resolve().parent.parent) not in sys.path:
|
|
38
15
|
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
39
16
|
|
|
40
|
-
from hooks_constants.hook_log_extractor_constants import
|
|
41
|
-
BWS_ACCESS_TOKEN_ENV_VAR,
|
|
42
|
-
BWS_EXECUTABLE_NAME,
|
|
43
|
-
BWS_RUN_SEPARATOR,
|
|
44
|
-
BWS_RUN_SUBCOMMAND,
|
|
45
|
-
EXIT_CODE_SUCCESS,
|
|
46
|
-
FLAG_INCREMENTAL,
|
|
47
|
-
STOP_WRAPPER_DEBOUNCE_SECONDS,
|
|
48
|
-
STOP_WRAPPER_EXTRACTOR_SCRIPT_NAME,
|
|
49
|
-
STOP_WRAPPER_LAST_RUN_TIMESTAMP_FILE,
|
|
50
|
-
WINDOWS_CREATE_NEW_PROCESS_GROUP_FLAG,
|
|
51
|
-
WINDOWS_CREATE_NO_WINDOW_FLAG,
|
|
52
|
-
WINDOWS_OS_NAME,
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def _extractor_script_path() -> str:
|
|
57
|
-
return str(
|
|
58
|
-
Path(__file__).resolve().parent / STOP_WRAPPER_EXTRACTOR_SCRIPT_NAME
|
|
59
|
-
)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
def _last_run_timestamp_path() -> Path:
|
|
63
|
-
return Path(STOP_WRAPPER_LAST_RUN_TIMESTAMP_FILE)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
def _is_within_debounce_window() -> bool:
|
|
67
|
-
timestamp_path = _last_run_timestamp_path()
|
|
68
|
-
if not timestamp_path.exists():
|
|
69
|
-
return False
|
|
70
|
-
try:
|
|
71
|
-
previous_timestamp = float(timestamp_path.read_text().strip())
|
|
72
|
-
except (OSError, ValueError):
|
|
73
|
-
return False
|
|
74
|
-
seconds_since_previous_spawn = time.time() - previous_timestamp
|
|
75
|
-
if seconds_since_previous_spawn < 0:
|
|
76
|
-
return False
|
|
77
|
-
return seconds_since_previous_spawn < STOP_WRAPPER_DEBOUNCE_SECONDS
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def _record_current_timestamp() -> None:
|
|
81
|
-
timestamp_path = _last_run_timestamp_path()
|
|
82
|
-
timestamp_path.parent.mkdir(parents=True, exist_ok=True)
|
|
83
|
-
timestamp_path.write_text(str(time.time()))
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
def _clear_recorded_timestamp() -> None:
|
|
87
|
-
"""Remove the debounce timestamp regardless of which process wrote it.
|
|
88
|
-
|
|
89
|
-
The unlink is unconditional: if process A writes a timestamp, process
|
|
90
|
-
B observes it and debounces, then A's spawn fails, A's rollback here
|
|
91
|
-
deletes the timestamp B already saw. The next Stop hook then spawns
|
|
92
|
-
a fresh extractor instead of debouncing the remainder of the window.
|
|
93
|
-
Consequence is benign because the extractor's own offset-file lock
|
|
94
|
-
(LOCK_MAXIMUM_RETRY_COUNT in hook_log_extractor_constants) serializes
|
|
95
|
-
concurrent extractor runs, so at most one extra extractor briefly
|
|
96
|
-
waits on the lock. Scoping the rollback to A's own write would
|
|
97
|
-
require a per-process sentinel and tighter atomicity than the
|
|
98
|
-
benefit warrants for this Stop-hook fast path.
|
|
99
|
-
"""
|
|
100
|
-
timestamp_path = _last_run_timestamp_path()
|
|
101
|
-
timestamp_path.unlink(missing_ok=True)
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
def _can_use_bws() -> bool:
|
|
105
|
-
if not os.environ.get(BWS_ACCESS_TOKEN_ENV_VAR):
|
|
106
|
-
return False
|
|
107
|
-
return shutil.which(BWS_EXECUTABLE_NAME) is not None
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
def _detached_spawn_keyword_arguments() -> dict[str, object]:
|
|
111
|
-
spawn_arguments: dict[str, object] = {
|
|
112
|
-
"stdin": subprocess.DEVNULL,
|
|
113
|
-
"stdout": subprocess.DEVNULL,
|
|
114
|
-
"stderr": subprocess.DEVNULL,
|
|
115
|
-
"close_fds": True,
|
|
116
|
-
}
|
|
117
|
-
if os.name == WINDOWS_OS_NAME:
|
|
118
|
-
spawn_arguments["creationflags"] = (
|
|
119
|
-
WINDOWS_CREATE_NO_WINDOW_FLAG
|
|
120
|
-
| WINDOWS_CREATE_NEW_PROCESS_GROUP_FLAG
|
|
121
|
-
)
|
|
122
|
-
startup_info = subprocess.STARTUPINFO()
|
|
123
|
-
startup_info.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
|
124
|
-
startup_info.wShowWindow = subprocess.SW_HIDE
|
|
125
|
-
spawn_arguments["startupinfo"] = startup_info
|
|
126
|
-
else:
|
|
127
|
-
spawn_arguments["start_new_session"] = True
|
|
128
|
-
return spawn_arguments
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
def _spawn_with_bws() -> None:
|
|
132
|
-
subprocess.Popen(
|
|
133
|
-
[
|
|
134
|
-
BWS_EXECUTABLE_NAME,
|
|
135
|
-
BWS_RUN_SUBCOMMAND,
|
|
136
|
-
BWS_RUN_SEPARATOR,
|
|
137
|
-
sys.executable,
|
|
138
|
-
_extractor_script_path(),
|
|
139
|
-
FLAG_INCREMENTAL,
|
|
140
|
-
],
|
|
141
|
-
**_detached_spawn_keyword_arguments(),
|
|
142
|
-
)
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
def _spawn_without_bws() -> None:
|
|
146
|
-
subprocess.Popen(
|
|
147
|
-
[
|
|
148
|
-
sys.executable,
|
|
149
|
-
_extractor_script_path(),
|
|
150
|
-
FLAG_INCREMENTAL,
|
|
151
|
-
],
|
|
152
|
-
**_detached_spawn_keyword_arguments(),
|
|
153
|
-
)
|
|
17
|
+
from hooks_constants.hook_log_extractor_constants import EXIT_CODE_SUCCESS
|
|
154
18
|
|
|
155
19
|
|
|
156
20
|
def main() -> int:
|
|
157
|
-
"""
|
|
158
|
-
try:
|
|
159
|
-
if _is_within_debounce_window():
|
|
160
|
-
return EXIT_CODE_SUCCESS
|
|
161
|
-
_record_current_timestamp()
|
|
162
|
-
try:
|
|
163
|
-
if _can_use_bws():
|
|
164
|
-
_spawn_with_bws()
|
|
165
|
-
else:
|
|
166
|
-
_spawn_without_bws()
|
|
167
|
-
except Exception:
|
|
168
|
-
_clear_recorded_timestamp()
|
|
169
|
-
except Exception:
|
|
170
|
-
pass
|
|
21
|
+
"""Exit success without spawning the extractor."""
|
|
171
22
|
return EXIT_CODE_SUCCESS
|
|
172
23
|
|
|
173
24
|
|
|
@@ -805,32 +805,19 @@ def test_offline_fallback_still_exits_zero_when_warning_log_write_raises(
|
|
|
805
805
|
assert exit_code == 0
|
|
806
806
|
|
|
807
807
|
|
|
808
|
-
def
|
|
809
|
-
tmp_path: Path,
|
|
808
|
+
def test_main_is_disabled_and_skips_extraction(
|
|
810
809
|
monkeypatch: pytest.MonkeyPatch,
|
|
811
810
|
) -> None:
|
|
812
|
-
"""
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
state_file_path: str,
|
|
818
|
-
full_rebuild: bool,
|
|
819
|
-
) -> int:
|
|
820
|
-
captured_arguments["transcripts_root"] = transcripts_root
|
|
821
|
-
captured_arguments["state_file_path"] = state_file_path
|
|
822
|
-
captured_arguments["full_rebuild"] = full_rebuild
|
|
823
|
-
return 0
|
|
824
|
-
|
|
825
|
-
monkeypatch.setattr(sys, "argv", ["hook_log_extractor.py", "--incremental"])
|
|
811
|
+
"""Disabled main exits success without running extraction."""
|
|
812
|
+
|
|
813
|
+
def _fail_if_called(*_args: object, **_kwargs: object) -> int:
|
|
814
|
+
raise AssertionError("run_full_extraction must not run while disabled")
|
|
815
|
+
|
|
826
816
|
monkeypatch.setattr(
|
|
827
|
-
hook_log_extractor, "run_full_extraction",
|
|
817
|
+
hook_log_extractor, "run_full_extraction", _fail_if_called
|
|
828
818
|
)
|
|
829
819
|
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
assert exit_code == 0
|
|
833
|
-
assert captured_arguments["full_rebuild"] is False
|
|
820
|
+
assert hook_log_extractor.main() == 0
|
|
834
821
|
|
|
835
822
|
|
|
836
823
|
def test_run_query_returns_nonzero_for_unknown_query(
|