claude-dev-env 2.12.0 → 2.14.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.md +61 -0
- package/CLAUDE.md +1 -57
- package/_shared/AGENTS.md +15 -0
- package/_shared/CLAUDE.md +1 -15
- package/_shared/advisor/AGENTS.md +22 -0
- package/_shared/advisor/CLAUDE.md +1 -22
- package/_shared/pr-loop/AGENTS.md +27 -0
- package/_shared/pr-loop/CLAUDE.md +1 -27
- package/_shared/pr-loop/scripts/AGENTS.md +35 -0
- package/_shared/pr-loop/scripts/CLAUDE.md +1 -35
- package/_shared/pr-loop/scripts/code_rules_gate.py +2 -3
- package/_shared/pr-loop/scripts/code_rules_gate_parts/AGENTS.md +42 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/CLAUDE.md +1 -42
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/AGENTS.md +26 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/CLAUDE.md +1 -26
- package/_shared/pr-loop/scripts/test_code_rules_gate.py +0 -26
- package/_shared/pr-loop/scripts/tests/AGENTS.md +44 -0
- package/_shared/pr-loop/scripts/tests/CLAUDE.md +1 -44
- package/_shared/process-tree/AGENTS.md +41 -0
- package/_shared/process-tree/CLAUDE.md +1 -41
- package/agents/AGENTS.md +31 -0
- package/agents/CLAUDE.md +1 -32
- package/agents/clean-coder.md +6 -6
- package/agents/pr-description-writer.md +11 -81
- package/agents/test_agent_frontmatter.py +4 -4
- package/audit-rubrics/AGENTS.md +43 -0
- package/audit-rubrics/CLAUDE.md +1 -43
- package/audit-rubrics/category_rubrics/AGENTS.md +37 -0
- package/audit-rubrics/category_rubrics/CLAUDE.md +1 -37
- package/audit-rubrics/prompts/AGENTS.md +37 -0
- package/audit-rubrics/prompts/CLAUDE.md +1 -37
- package/bin/AGENTS.md +112 -0
- package/bin/CLAUDE.md +1 -112
- package/bin/ever-shipped-skills.mjs +1 -0
- package/bin/install-constants.mjs +6 -7
- package/bin/install.mjs +71 -13
- package/bin/install.prune.test.mjs +26 -0
- package/bin/install.settings-defaults.test.mjs +60 -33
- package/bin/install.test.mjs +36 -4
- package/commands/AGENTS.md +23 -0
- package/commands/CLAUDE.md +1 -23
- package/commands/hook-log-extract.md +3 -3
- package/commands/sr-loop.md +9 -5
- package/docs/AGENTS.md +32 -0
- package/docs/CLAUDE.md +1 -33
- package/docs/CODE_RULES.md +1 -1
- package/docs/imagegen.md +22 -0
- package/docs/references/AGENTS.md +17 -0
- package/docs/references/CLAUDE.md +1 -18
- package/hooks/AGENTS.md +31 -0
- package/hooks/CLAUDE.md +1 -31
- package/hooks/_gh_pr_author_swap_utils.py +14 -0
- package/hooks/advisory/AGENTS.md +16 -0
- package/hooks/advisory/CLAUDE.md +1 -16
- package/hooks/blocking/AGENTS.md +123 -0
- package/hooks/blocking/CLAUDE.md +1 -148
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/AGENTS.md +28 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/CLAUDE.md +1 -28
- package/hooks/blocking/code_rules_banned_identifiers.py +1 -1
- package/hooks/blocking/code_rules_dead_module_constant.py +149 -37
- package/hooks/blocking/code_rules_probe_chains.py +6 -2
- package/hooks/blocking/config/AGENTS.md +10 -0
- package/hooks/blocking/config/CLAUDE.md +1 -22
- package/hooks/blocking/conventional_pr_title_gate.py +1 -2
- package/hooks/blocking/gh_pr_author_restore.py +7 -2
- package/hooks/blocking/intent_only_ending_blocker.py +11 -16
- package/hooks/blocking/inventory_intent_records/AGENTS.md +26 -0
- package/hooks/blocking/inventory_intent_records/CLAUDE.md +1 -26
- package/hooks/blocking/package_inventory_stale_blocker_parts/AGENTS.md +26 -0
- package/hooks/blocking/package_inventory_stale_blocker_parts/CLAUDE.md +1 -26
- package/hooks/blocking/pii_prevention_blocker_parts/AGENTS.md +24 -0
- package/hooks/blocking/pii_prevention_blocker_parts/CLAUDE.md +1 -24
- package/hooks/blocking/send_user_file_open_locally_blocker.py +1 -1
- package/hooks/blocking/stop_dispatcher.py +4 -4
- package/hooks/blocking/tdd_enforcer_parts/AGENTS.md +30 -0
- package/hooks/blocking/tdd_enforcer_parts/CLAUDE.md +1 -30
- package/hooks/blocking/test_bash_pre_tool_use_dispatcher.py +0 -3
- package/hooks/blocking/test_code_rules_enforcer_banned_prefixes.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_dead_module_constant.py +171 -0
- package/hooks/blocking/test_code_rules_enforcer_magic_allowlist.py +1 -1
- package/hooks/blocking/test_gh_pr_author_restore.py +2 -1
- package/hooks/blocking/test_intent_only_ending_blocker.py +4 -0
- 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/AGENTS.md +43 -0
- package/hooks/diagnostic/CLAUDE.md +1 -43
- package/hooks/diagnostic/hook_log_extractor.py +4 -38
- package/hooks/diagnostic/hook_log_stop_wrapper.py +6 -155
- package/hooks/diagnostic/migrations/AGENTS.md +16 -0
- package/hooks/diagnostic/migrations/CLAUDE.md +1 -16
- package/hooks/diagnostic/queries/AGENTS.md +19 -0
- package/hooks/diagnostic/queries/CLAUDE.md +1 -19
- package/hooks/diagnostic/test_hook_log_extractor.py +10 -23
- package/hooks/diagnostic/test_hook_log_stop_wrapper.py +3 -331
- package/hooks/git-hooks/AGENTS.md +31 -0
- package/hooks/git-hooks/CLAUDE.md +1 -31
- package/hooks/git-hooks/git_hooks_constants/AGENTS.md +21 -0
- package/hooks/git-hooks/git_hooks_constants/CLAUDE.md +1 -21
- package/hooks/git-hooks/git_hooks_constants/__init__.py +4 -12
- package/hooks/git-hooks/pre_push.py +49 -118
- package/hooks/git-hooks/test_pre_push.py +111 -137
- package/hooks/hooks.json +0 -35
- package/hooks/hooks_constants/AGENTS.md +104 -0
- package/hooks/hooks_constants/CLAUDE.md +1 -113
- 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/hook_log_extractor_constants.py +1 -2
- 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/task_list_loop_starter_constants.py +4 -3
- 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/hooks_constants/working_style_prompt_constants.py +29 -20
- package/hooks/lifecycle/AGENTS.md +18 -0
- package/hooks/lifecycle/CLAUDE.md +1 -20
- package/hooks/observability/AGENTS.md +20 -0
- package/hooks/observability/CLAUDE.md +1 -20
- package/hooks/session/AGENTS.md +33 -0
- package/hooks/session/CLAUDE.md +1 -33
- package/hooks/session/gh_pr_author_session_cleanup.py +7 -2
- package/hooks/session/task_list_loop_starter.py +3 -3
- package/hooks/session/test_gh_pr_author_session_cleanup.py +2 -1
- package/hooks/session/test_task_list_loop_starter.py +10 -2
- package/hooks/session/test_working_style_prompt.py +9 -4
- package/hooks/validation/AGENTS.md +20 -0
- package/hooks/validation/CLAUDE.md +1 -20
- package/hooks/validators/AGENTS.md +51 -0
- package/hooks/validators/CLAUDE.md +1 -51
- package/hooks/validators/health_check.py +52 -19
- package/hooks/validators/python_style_checks.py +1 -1
- package/hooks/validators/test_health_check.py +112 -27
- package/hooks/workflow/AGENTS.md +16 -0
- package/hooks/workflow/CLAUDE.md +1 -16
- package/installable-surfaces.manifest.json +1 -0
- package/output-styles/AGENTS.md +15 -0
- package/output-styles/CLAUDE.md +1 -15
- package/package.json +2 -1
- package/rules/AGENTS.md +61 -0
- package/rules/CLAUDE.md +1 -63
- package/rules/code-standards.md +5 -9
- package/rules/doc-prose-cuts.md +1 -1
- package/rules/gh-cli-conventions.md +0 -1
- package/rules/git-workflow.md +1 -3
- package/scripts/AGENTS.md +62 -0
- package/scripts/CLAUDE.md +1 -63
- package/scripts/_code_review_test_support.py +0 -95
- package/scripts/codex_compat_materializer.py +4 -0
- package/scripts/dev_env_scripts_constants/AGENTS.md +21 -0
- package/scripts/dev_env_scripts_constants/CLAUDE.md +1 -21
- 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/sync_to_cursor/AGENTS.md +23 -0
- package/scripts/sync_to_cursor/CLAUDE.md +1 -23
- 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/scripts/test_validate_instruction_pairs.py +90 -0
- package/scripts/test_verify_installable_package.py +5 -1
- package/scripts/tests/AGENTS.md +33 -0
- package/scripts/tests/CLAUDE.md +1 -33
- package/scripts/tests/test_codex_compat_materializer.py +36 -1
- package/scripts/validate_instruction_pairs.py +183 -0
- package/settings.json +1 -10
- package/skills/AGENTS.md +72 -0
- package/skills/CLAUDE.md +1 -64
- package/skills/_shared/AGENTS.md +44 -0
- package/skills/_shared/CLAUDE.md +1 -44
- package/skills/_shared/advisor/AGENTS.md +9 -0
- package/skills/_shared/advisor/CLAUDE.md +1 -9
- package/skills/_shared/pr-loop/AGENTS.md +50 -0
- package/skills/_shared/pr-loop/CLAUDE.md +1 -50
- package/skills/_shared/pr-loop/prompts/AGENTS.md +9 -0
- package/skills/_shared/pr-loop/prompts/CLAUDE.md +1 -9
- package/skills/_shared/pr-loop/scripts/AGENTS.md +35 -0
- package/skills/_shared/pr-loop/scripts/CLAUDE.md +1 -35
- package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/AGENTS.md +24 -0
- package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/CLAUDE.md +1 -24
- package/skills/anthropic-plan/AGENTS.md +34 -0
- package/skills/anthropic-plan/CLAUDE.md +1 -34
- package/skills/anthropic-plan/scripts/AGENTS.md +11 -0
- package/skills/anthropic-plan/scripts/CLAUDE.md +1 -11
- package/skills/anthropic-plan/scripts/anthropic_plan_scripts_constants/AGENTS.md +16 -0
- package/skills/anthropic-plan/scripts/anthropic_plan_scripts_constants/CLAUDE.md +1 -16
- package/skills/anthropic-plan/templates/AGENTS.md +13 -0
- package/skills/anthropic-plan/templates/CLAUDE.md +1 -13
- package/skills/anthropic-plan/workflow/AGENTS.md +14 -0
- package/skills/anthropic-plan/workflow/CLAUDE.md +1 -14
- package/skills/auditing-claude-config/AGENTS.md +21 -0
- package/skills/auditing-claude-config/CLAUDE.md +1 -21
- package/skills/autoconverge/AGENTS.md +36 -0
- package/skills/autoconverge/CLAUDE.md +1 -36
- package/skills/autoconverge/reference/AGENTS.md +16 -0
- package/skills/autoconverge/reference/CLAUDE.md +1 -16
- package/skills/autoconverge/reference/convergence.md +7 -7
- package/skills/autoconverge/reference/gotchas.md +3 -10
- package/skills/autoconverge/workflow/AGENTS.md +26 -0
- package/skills/autoconverge/workflow/CLAUDE.md +1 -26
- package/skills/autoconverge/workflow/autoconverge_report_constants/AGENTS.md +16 -0
- package/skills/autoconverge/workflow/autoconverge_report_constants/CLAUDE.md +1 -16
- 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/AGENTS.md +30 -0
- package/skills/bugteam/CLAUDE.md +1 -30
- package/skills/bugteam/CONSTRAINTS.md +1 -1
- package/skills/bugteam/SKILL.md +9 -0
- package/skills/bugteam/reference/AGENTS.md +20 -0
- package/skills/bugteam/reference/CLAUDE.md +1 -20
- package/skills/bugteam/reference/audit-and-teammates.md +10 -6
- package/skills/bugteam/reference/obstacles/AGENTS.md +24 -0
- package/skills/bugteam/reference/obstacles/CLAUDE.md +1 -24
- package/skills/bugteam/scripts/AGENTS.md +30 -0
- package/skills/bugteam/scripts/CLAUDE.md +1 -30
- package/skills/bugteam/scripts/bugteam_scripts_constants/AGENTS.md +18 -0
- package/skills/bugteam/scripts/bugteam_scripts_constants/CLAUDE.md +1 -18
- package/skills/bugteam/test_skill_additions.py +9 -0
- package/skills/closeout/reference/handoff-prompt-template.md +1 -1
- package/skills/codex-review/AGENTS.md +46 -0
- package/skills/codex-review/CLAUDE.md +1 -46
- package/skills/codex-review/reference/AGENTS.md +15 -0
- package/skills/codex-review/reference/CLAUDE.md +1 -15
- package/skills/codex-review/scripts/codex_review_scripts_constants/AGENTS.md +18 -0
- package/skills/codex-review/scripts/codex_review_scripts_constants/CLAUDE.md +1 -18
- package/skills/codex-review/test_skill_scaffold.py +3 -3
- package/skills/comments/SKILL.md +65 -0
- package/skills/copilot-review/AGENTS.md +18 -0
- package/skills/copilot-review/CLAUDE.md +1 -18
- package/skills/descriptions/SKILL.md +51 -0
- package/skills/emergencies/SKILL.md +42 -0
- package/skills/everything-search/AGENTS.md +17 -0
- package/skills/everything-search/CLAUDE.md +1 -17
- package/skills/fresh-branch/AGENTS.md +14 -0
- package/skills/fresh-branch/CLAUDE.md +1 -14
- package/skills/grok-spawn/AGENTS.md +28 -0
- package/skills/grok-spawn/CLAUDE.md +1 -28
- 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/AGENTS.md +32 -0
- package/skills/pr-converge/CLAUDE.md +1 -32
- package/skills/pr-converge/SKILL.md +13 -16
- package/skills/pr-converge/pr_converge_skill_constants/AGENTS.md +26 -0
- package/skills/pr-converge/pr_converge_skill_constants/CLAUDE.md +1 -26
- package/skills/pr-converge/reference/AGENTS.md +28 -0
- package/skills/pr-converge/reference/CLAUDE.md +1 -28
- package/skills/pr-converge/reference/obstacles/AGENTS.md +23 -0
- package/skills/pr-converge/reference/obstacles/CLAUDE.md +1 -23
- package/skills/pr-converge/reference/progress-checklist.md +6 -5
- package/skills/pr-converge/reference/state-schema.md +1 -27
- package/skills/pr-converge/scripts/AGENTS.md +46 -0
- package/skills/pr-converge/scripts/CLAUDE.md +1 -46
- package/skills/pr-converge/scripts/pr_converge_scripts_constants/AGENTS.md +18 -0
- package/skills/pr-converge/scripts/pr_converge_scripts_constants/CLAUDE.md +1 -18
- package/skills/pr-converge/workflows/AGENTS.md +16 -0
- package/skills/pr-converge/workflows/CLAUDE.md +1 -16
- 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 +5 -5
- package/skills/prototype/reference/promotion-tasks.md +2 -2
- package/skills/prototype/workflows/promotion.md +4 -6
- package/skills/rebase/AGENTS.md +32 -0
- package/skills/rebase/CLAUDE.md +1 -32
- package/skills/recall/AGENTS.md +30 -0
- package/skills/recall/CLAUDE.md +1 -30
- package/skills/remember/AGENTS.md +31 -0
- package/skills/remember/CLAUDE.md +1 -31
- package/skills/reviews/SKILL.md +85 -0
- package/skills/session-log/AGENTS.md +32 -0
- package/skills/session-log/CLAUDE.md +1 -32
- package/skills/session-tidy/AGENTS.md +36 -0
- package/skills/session-tidy/CLAUDE.md +1 -36
- package/skills/skill-builder/AGENTS.md +49 -0
- package/skills/skill-builder/CLAUDE.md +1 -49
- package/skills/skill-builder/references/AGENTS.md +25 -0
- package/skills/skill-builder/references/CLAUDE.md +1 -25
- package/skills/skill-builder/references/deterministic-elements.md +1 -1
- package/skills/skill-builder/templates/AGENTS.md +14 -0
- package/skills/skill-builder/templates/CLAUDE.md +1 -14
- package/skills/skill-builder/workflows/AGENTS.md +19 -0
- package/skills/skill-builder/workflows/CLAUDE.md +1 -19
- package/skills/small-cl/SKILL.md +52 -0
- package/skills/task-build/AGENTS.md +29 -0
- package/skills/task-build/CLAUDE.md +1 -29
- package/skills/update/AGENTS.md +38 -0
- package/skills/update/CLAUDE.md +1 -38
- package/system-prompts/AGENTS.md +17 -0
- package/system-prompts/CLAUDE.md +1 -17
- package/system-prompts/software-engineer.xml +3 -3
- 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
|
@@ -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
|
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# hooks/diagnostic
|
|
2
|
+
|
|
3
|
+
Hooks and scripts that collect, store, and query hook-firing records. The pipeline reads session JSONL transcripts, extracts hook attachment records, and writes them as rows into a Neon (Postgres) `hook_events` table.
|
|
4
|
+
|
|
5
|
+
## Subdirectories
|
|
6
|
+
|
|
7
|
+
| Directory | Role |
|
|
8
|
+
|---|---|
|
|
9
|
+
| `migrations/` | SQL migration files for the `hook_events` schema |
|
|
10
|
+
| `queries/` | Parameterized SQL queries for inspecting blocked commands |
|
|
11
|
+
|
|
12
|
+
## Key files
|
|
13
|
+
|
|
14
|
+
| File | What it does |
|
|
15
|
+
|---|---|
|
|
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` | 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
|
+
| `schema.sql` | DDL for the `hook_events` table, `blocked_commands` view, and supporting indexes |
|
|
20
|
+
| `requirements-hook-logs.txt` | Runtime dependencies (`psycopg`) for the extractor |
|
|
21
|
+
| `requirements-hook-logs-dev.txt` | Dev/test dependencies |
|
|
22
|
+
| `test_hook_log_extractor.py` | Tests for the extractor |
|
|
23
|
+
| `test_hook_log_init.py` | Tests for the schema-init script |
|
|
24
|
+
| `test_hook_log_stop_wrapper.py` | Tests for the Stop wrapper |
|
|
25
|
+
|
|
26
|
+
## Schema overview (`schema.sql`)
|
|
27
|
+
|
|
28
|
+
The `hook_events` table captures one row per hook firing:
|
|
29
|
+
|
|
30
|
+
- `hook_event`, `hook_name`, `hook_category` — what fired
|
|
31
|
+
- `outcome` — `allowed`, `blocked`, or `ask`
|
|
32
|
+
- `tool_name`, `command_excerpt` — what tool was called
|
|
33
|
+
- `session_id`, `git_branch`, `cwd` — context
|
|
34
|
+
- `duration_ms`, `exit_code` — timing and result
|
|
35
|
+
- `source_jsonl_path`, `source_line_number` — idempotency key
|
|
36
|
+
|
|
37
|
+
The `blocked_commands` view filters to `outcome = 'blocked'`.
|
|
38
|
+
|
|
39
|
+
## Conventions
|
|
40
|
+
|
|
41
|
+
- Extractor and Stop wrapper mains are disabled and exit 0 with no work.
|
|
42
|
+
- Constants for the extractor (table name, offset state file, timeout) live in `hooks_constants/hook_log_extractor_constants.py`.
|
|
43
|
+
- Tests run with `python -m pytest diagnostic/test_hook_log_*.py`.
|
|
@@ -1,43 +1 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
Hooks and scripts that collect, store, and query hook-firing records. The pipeline reads session JSONL transcripts, extracts hook attachment records, and writes them as rows into a Neon (Postgres) `hook_events` table.
|
|
4
|
-
|
|
5
|
-
## Subdirectories
|
|
6
|
-
|
|
7
|
-
| Directory | Role |
|
|
8
|
-
|---|---|
|
|
9
|
-
| `migrations/` | SQL migration files for the `hook_events` schema |
|
|
10
|
-
| `queries/` | Parameterized SQL queries for inspecting blocked commands |
|
|
11
|
-
|
|
12
|
-
## Key files
|
|
13
|
-
|
|
14
|
-
| File | What it does |
|
|
15
|
-
|---|---|
|
|
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` | Stop hook — reads per-session JSONL transcripts and ingests new `hook_*` attachment records into the `hook_events` table; idempotent via a UNIQUE constraint on `(source_jsonl_path, source_line_number)` |
|
|
18
|
-
| `hook_log_stop_wrapper.py` | Thin wrapper that invokes `hook_log_extractor.py` from the Stop lifecycle event |
|
|
19
|
-
| `schema.sql` | DDL for the `hook_events` table, `blocked_commands` view, and supporting indexes |
|
|
20
|
-
| `requirements-hook-logs.txt` | Runtime dependencies (`psycopg`) for the extractor |
|
|
21
|
-
| `requirements-hook-logs-dev.txt` | Dev/test dependencies |
|
|
22
|
-
| `test_hook_log_extractor.py` | Tests for the extractor |
|
|
23
|
-
| `test_hook_log_init.py` | Tests for the schema-init script |
|
|
24
|
-
| `test_hook_log_stop_wrapper.py` | Tests for the Stop wrapper |
|
|
25
|
-
|
|
26
|
-
## Schema overview (`schema.sql`)
|
|
27
|
-
|
|
28
|
-
The `hook_events` table captures one row per hook firing:
|
|
29
|
-
|
|
30
|
-
- `hook_event`, `hook_name`, `hook_category` — what fired
|
|
31
|
-
- `outcome` — `allowed`, `blocked`, or `ask`
|
|
32
|
-
- `tool_name`, `command_excerpt` — what tool was called
|
|
33
|
-
- `session_id`, `git_branch`, `cwd` — context
|
|
34
|
-
- `duration_ms`, `exit_code` — timing and result
|
|
35
|
-
- `source_jsonl_path`, `source_line_number` — idempotency key
|
|
36
|
-
|
|
37
|
-
The `blocked_commands` view filters to `outcome = 'blocked'`.
|
|
38
|
-
|
|
39
|
-
## Conventions
|
|
40
|
-
|
|
41
|
-
- The extractor exits 0 even when Neon is unreachable (offline-graceful); it logs to `OFFLINE_WARNING_LOG` and does not block session end.
|
|
42
|
-
- Constants for the extractor (table name, offset state file, timeout) live in `hooks_constants/hook_log_extractor_constants.py`.
|
|
43
|
-
- Tests run with `python -m pytest diagnostic/test_hook_log_*.py`.
|
|
1
|
+
@AGENTS.md
|
|
@@ -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,
|
|
@@ -106,7 +99,7 @@ from hooks_constants.hook_log_extractor_constants import (
|
|
|
106
99
|
STDERR_EXCERPT_MAX_CHARACTERS,
|
|
107
100
|
STDOUT_EXCERPT_MAX_CHARACTERS,
|
|
108
101
|
SUMMARY_COLUMN_HEADINGS,
|
|
109
|
-
|
|
102
|
+
SUMMARY_ZERO_BLOCKED_EVENTS_MESSAGE,
|
|
110
103
|
SUMMARY_TABLE_COLUMN_GAP,
|
|
111
104
|
TOP_BLOCKED_COMMAND_PREVIEW_MAX_CHARACTERS,
|
|
112
105
|
TOP_BLOCKERS_LAST_24_HOURS_SQL,
|
|
@@ -787,7 +780,7 @@ def run_summary() -> int:
|
|
|
787
780
|
except Exception:
|
|
788
781
|
pass
|
|
789
782
|
if not all_result_rows:
|
|
790
|
-
print(
|
|
783
|
+
print(SUMMARY_ZERO_BLOCKED_EVENTS_MESSAGE)
|
|
791
784
|
return EXIT_CODE_SUCCESS
|
|
792
785
|
_print_summary_table(all_result_rows)
|
|
793
786
|
return EXIT_CODE_SUCCESS
|
|
@@ -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
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# hooks/diagnostic/migrations
|
|
2
|
+
|
|
3
|
+
SQL migration files for the `hook_events` Neon schema. Each file applies a schema change to the `hook_events` table or its indexes.
|
|
4
|
+
|
|
5
|
+
## Files
|
|
6
|
+
|
|
7
|
+
| File | What it does |
|
|
8
|
+
|---|---|
|
|
9
|
+
| `2026-04-25-drop-themes-hook-events.sql` | Drops the `themes` hook-events table variant |
|
|
10
|
+
| `README.md` | Notes on the migration approach |
|
|
11
|
+
|
|
12
|
+
## Conventions
|
|
13
|
+
|
|
14
|
+
- Run migrations manually against the Neon database using `psql` or the Neon console.
|
|
15
|
+
- The baseline schema lives in `diagnostic/schema.sql`.
|
|
16
|
+
- File names follow `YYYY-MM-DD-<description>.sql` for chronological ordering.
|
|
@@ -1,16 +1 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
SQL migration files for the `hook_events` Neon schema. Each file applies a schema change to the `hook_events` table or its indexes.
|
|
4
|
-
|
|
5
|
-
## Files
|
|
6
|
-
|
|
7
|
-
| File | What it does |
|
|
8
|
-
|---|---|
|
|
9
|
-
| `2026-04-25-drop-themes-hook-events.sql` | Drops the `themes` hook-events table variant |
|
|
10
|
-
| `README.md` | Notes on the migration approach |
|
|
11
|
-
|
|
12
|
-
## Conventions
|
|
13
|
-
|
|
14
|
-
- Run migrations manually against the Neon database using `psql` or the Neon console.
|
|
15
|
-
- The baseline schema lives in `diagnostic/schema.sql`.
|
|
16
|
-
- File names follow `YYYY-MM-DD-<description>.sql` for chronological ordering.
|
|
1
|
+
@AGENTS.md
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# hooks/diagnostic/queries
|
|
2
|
+
|
|
3
|
+
Parameterized SQL queries for inspecting the `hook_events` Neon table. Run these directly against the Neon database to analyze hook-firing patterns.
|
|
4
|
+
|
|
5
|
+
## Files
|
|
6
|
+
|
|
7
|
+
| File | What it returns |
|
|
8
|
+
|---|---|
|
|
9
|
+
| `block_details_for_hook.sql` | Full details for all blocked events matching a given hook name |
|
|
10
|
+
| `blocks_by_category.sql` | Count of blocks grouped by hook category |
|
|
11
|
+
| `blocks_by_tool.sql` | Count of blocks grouped by tool name |
|
|
12
|
+
| `blocks_last_7_days.sql` | All blocked events from the last 7 days |
|
|
13
|
+
| `top_blockers_last_24_hours.sql` | Hook names with the most blocks in the last 24 hours |
|
|
14
|
+
| `top_blockers_overall.sql` | Hook names with the most blocks across all time |
|
|
15
|
+
|
|
16
|
+
## Conventions
|
|
17
|
+
|
|
18
|
+
- Queries target the `hook_events` table and `blocked_commands` view defined in `diagnostic/schema.sql`.
|
|
19
|
+
- Run with `psql $DATABASE_URL -f <query>.sql` or paste into the Neon console.
|
|
@@ -1,19 +1 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
Parameterized SQL queries for inspecting the `hook_events` Neon table. Run these directly against the Neon database to analyze hook-firing patterns.
|
|
4
|
-
|
|
5
|
-
## Files
|
|
6
|
-
|
|
7
|
-
| File | What it returns |
|
|
8
|
-
|---|---|
|
|
9
|
-
| `block_details_for_hook.sql` | Full details for all blocked events matching a given hook name |
|
|
10
|
-
| `blocks_by_category.sql` | Count of blocks grouped by hook category |
|
|
11
|
-
| `blocks_by_tool.sql` | Count of blocks grouped by tool name |
|
|
12
|
-
| `blocks_last_7_days.sql` | All blocked events from the last 7 days |
|
|
13
|
-
| `top_blockers_last_24_hours.sql` | Hook names with the most blocks in the last 24 hours |
|
|
14
|
-
| `top_blockers_overall.sql` | Hook names with the most blocks across all time |
|
|
15
|
-
|
|
16
|
-
## Conventions
|
|
17
|
-
|
|
18
|
-
- Queries target the `hook_events` table and `blocked_commands` view defined in `diagnostic/schema.sql`.
|
|
19
|
-
- Run with `psql $DATABASE_URL -f <query>.sql` or paste into the Neon console.
|
|
1
|
+
@AGENTS.md
|
|
@@ -617,7 +617,7 @@ def test_tool_name_extracted_from_hook_name_prefix() -> None:
|
|
|
617
617
|
assert hook_log_extractor.extract_tool_name("UserPromptSubmit") is None
|
|
618
618
|
|
|
619
619
|
|
|
620
|
-
def
|
|
620
|
+
def test_run_summary_prints_zero_blocked_events_when_cursor_empty(
|
|
621
621
|
capsys: pytest.CaptureFixture[str],
|
|
622
622
|
) -> None:
|
|
623
623
|
fake_cursor = MagicMock()
|
|
@@ -632,7 +632,7 @@ def test_run_summary_prints_no_new_blocks_when_cursor_empty(
|
|
|
632
632
|
|
|
633
633
|
captured = capsys.readouterr()
|
|
634
634
|
assert exit_code == 0
|
|
635
|
-
assert "
|
|
635
|
+
assert "Blocked events recorded in the last 24 hours: 0." in captured.out
|
|
636
636
|
|
|
637
637
|
|
|
638
638
|
def test_run_summary_prints_table_when_rows_returned(
|
|
@@ -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(
|