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
|
@@ -1,311 +0,0 @@
|
|
|
1
|
-
"""Unit tests for the pr_converge_bugteam_enforcer PreToolUse hook.
|
|
2
|
-
|
|
3
|
-
Covers the Step 5 BUGTEAM contract: Agent({subagent_type: "clean-coder"})
|
|
4
|
-
calls that look like audit substitutes are blocked when
|
|
5
|
-
$CLAUDE_JOB_DIR/pr-converge-state.json (named by PR_CONVERGE_STATE_FILENAME)
|
|
6
|
-
shows phase=BUGTEAM and the formal Skill({skill: "bugteam"}) has not
|
|
7
|
-
registered at the current HEAD and tick. qbug is explicitly NOT a substitute.
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
from __future__ import annotations
|
|
11
|
-
|
|
12
|
-
import importlib.util
|
|
13
|
-
import io
|
|
14
|
-
import json
|
|
15
|
-
import pathlib
|
|
16
|
-
import sys
|
|
17
|
-
from typing import Any
|
|
18
|
-
from unittest import mock
|
|
19
|
-
|
|
20
|
-
import pytest
|
|
21
|
-
|
|
22
|
-
_HOOK_DIR = pathlib.Path(__file__).parent
|
|
23
|
-
_HOOKS_TREE = _HOOK_DIR.parent
|
|
24
|
-
for each_path in (str(_HOOK_DIR), str(_HOOKS_TREE)):
|
|
25
|
-
if each_path not in sys.path:
|
|
26
|
-
sys.path.insert(0, each_path)
|
|
27
|
-
|
|
28
|
-
hook_spec = importlib.util.spec_from_file_location(
|
|
29
|
-
"pr_converge_bugteam_enforcer",
|
|
30
|
-
_HOOK_DIR / "pr_converge_bugteam_enforcer.py",
|
|
31
|
-
)
|
|
32
|
-
assert hook_spec is not None
|
|
33
|
-
assert hook_spec.loader is not None
|
|
34
|
-
hook_module = importlib.util.module_from_spec(hook_spec)
|
|
35
|
-
hook_spec.loader.exec_module(hook_module)
|
|
36
|
-
|
|
37
|
-
from hooks_constants.pr_converge_bugteam_enforcer_constants import (
|
|
38
|
-
BUGTEAM_PHASE,
|
|
39
|
-
CLAUDE_JOB_DIR_ENV_VAR,
|
|
40
|
-
PR_CONVERGE_STATE_FILENAME,
|
|
41
|
-
)
|
|
42
|
-
|
|
43
|
-
_HEAD_SHA_CURRENT = "abc123def456abc123def456abc123def456abcd"
|
|
44
|
-
_HEAD_SHA_STALE = "deadbeef0000deadbeef0000deadbeef0000dead"
|
|
45
|
-
_TICK_CURRENT = 7
|
|
46
|
-
_AUDIT_PROMPT = "Run the bugteam audit and report findings against the A-J categories."
|
|
47
|
-
_FIX_ONLY_PROMPT = "Fix the failing test in tests/test_widget.py by adding a guard clause."
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def _write_state(state_directory: pathlib.Path, state: dict[str, Any]) -> pathlib.Path:
|
|
51
|
-
state_path = state_directory / PR_CONVERGE_STATE_FILENAME
|
|
52
|
-
state_path.write_text(json.dumps(state), encoding="utf-8")
|
|
53
|
-
return state_path
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def _bugteam_phase_state(**overrides: Any) -> dict[str, Any]:
|
|
57
|
-
baseline_state: dict[str, Any] = {
|
|
58
|
-
"phase": BUGTEAM_PHASE,
|
|
59
|
-
"current_head": _HEAD_SHA_CURRENT,
|
|
60
|
-
"tick_count": _TICK_CURRENT,
|
|
61
|
-
"bugteam_skill_invoked_at_head": None,
|
|
62
|
-
"bugteam_skill_invoked_at_tick": None,
|
|
63
|
-
}
|
|
64
|
-
baseline_state.update(overrides)
|
|
65
|
-
return baseline_state
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
def _clean_coder_audit_payload(prompt: str = _AUDIT_PROMPT) -> dict[str, Any]:
|
|
69
|
-
return {
|
|
70
|
-
"tool_name": "Agent",
|
|
71
|
-
"tool_input": {
|
|
72
|
-
"subagent_type": "clean-coder",
|
|
73
|
-
"prompt": prompt,
|
|
74
|
-
"description": "Audit the PR",
|
|
75
|
-
},
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
def _run_main_with_io(input_text: str) -> str:
|
|
80
|
-
with mock.patch("sys.stdin", io.StringIO(input_text)):
|
|
81
|
-
with mock.patch("sys.stdout", new_callable=io.StringIO) as captured_stdout:
|
|
82
|
-
try:
|
|
83
|
-
hook_module.main()
|
|
84
|
-
except SystemExit:
|
|
85
|
-
pass
|
|
86
|
-
return captured_stdout.getvalue()
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
@pytest.fixture()
|
|
90
|
-
def claude_job_directory(tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch) -> pathlib.Path:
|
|
91
|
-
monkeypatch.setenv(CLAUDE_JOB_DIR_ENV_VAR, str(tmp_path))
|
|
92
|
-
return tmp_path
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
def test_should_allow_when_state_file_absent(claude_job_directory: pathlib.Path) -> None:
|
|
96
|
-
payload_text = json.dumps(_clean_coder_audit_payload())
|
|
97
|
-
captured_output = _run_main_with_io(payload_text)
|
|
98
|
-
assert captured_output == ""
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
def test_should_allow_when_phase_not_bugteam(claude_job_directory: pathlib.Path) -> None:
|
|
102
|
-
_write_state(claude_job_directory, _bugteam_phase_state(phase="BUGBOT"))
|
|
103
|
-
payload_text = json.dumps(_clean_coder_audit_payload())
|
|
104
|
-
captured_output = _run_main_with_io(payload_text)
|
|
105
|
-
assert captured_output == ""
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
def test_should_allow_when_subagent_type_not_clean_coder(
|
|
109
|
-
claude_job_directory: pathlib.Path,
|
|
110
|
-
) -> None:
|
|
111
|
-
_write_state(claude_job_directory, _bugteam_phase_state())
|
|
112
|
-
explore_payload: dict[str, Any] = {
|
|
113
|
-
"tool_name": "Agent",
|
|
114
|
-
"tool_input": {"subagent_type": "Explore", "prompt": _AUDIT_PROMPT},
|
|
115
|
-
}
|
|
116
|
-
captured_output = _run_main_with_io(json.dumps(explore_payload))
|
|
117
|
-
assert captured_output == ""
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
def test_should_allow_when_prompt_lacks_audit_keywords(
|
|
121
|
-
claude_job_directory: pathlib.Path,
|
|
122
|
-
) -> None:
|
|
123
|
-
_write_state(claude_job_directory, _bugteam_phase_state())
|
|
124
|
-
payload_text = json.dumps(_clean_coder_audit_payload(prompt=_FIX_ONLY_PROMPT))
|
|
125
|
-
captured_output = _run_main_with_io(payload_text)
|
|
126
|
-
assert captured_output == ""
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
def test_should_block_when_clean_coder_audit_without_bugteam_skill_invocation(
|
|
130
|
-
claude_job_directory: pathlib.Path,
|
|
131
|
-
) -> None:
|
|
132
|
-
_write_state(claude_job_directory, _bugteam_phase_state())
|
|
133
|
-
captured_output = _run_main_with_io(json.dumps(_clean_coder_audit_payload()))
|
|
134
|
-
deny_payload = json.loads(captured_output)
|
|
135
|
-
assert deny_payload["hookSpecificOutput"]["permissionDecision"] == "deny"
|
|
136
|
-
assert "bugteam-enforcer" in deny_payload["hookSpecificOutput"]["permissionDecisionReason"]
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
def test_should_allow_after_bugteam_skill_invoked_at_current_head_and_tick(
|
|
140
|
-
claude_job_directory: pathlib.Path,
|
|
141
|
-
) -> None:
|
|
142
|
-
_write_state(
|
|
143
|
-
claude_job_directory,
|
|
144
|
-
_bugteam_phase_state(
|
|
145
|
-
bugteam_skill_invoked_at_head=_HEAD_SHA_CURRENT,
|
|
146
|
-
bugteam_skill_invoked_at_tick=_TICK_CURRENT,
|
|
147
|
-
),
|
|
148
|
-
)
|
|
149
|
-
captured_output = _run_main_with_io(json.dumps(_clean_coder_audit_payload()))
|
|
150
|
-
assert captured_output == ""
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
def test_should_block_when_bugteam_skill_invocation_is_stale_head(
|
|
154
|
-
claude_job_directory: pathlib.Path,
|
|
155
|
-
) -> None:
|
|
156
|
-
_write_state(
|
|
157
|
-
claude_job_directory,
|
|
158
|
-
_bugteam_phase_state(
|
|
159
|
-
bugteam_skill_invoked_at_head=_HEAD_SHA_STALE,
|
|
160
|
-
bugteam_skill_invoked_at_tick=_TICK_CURRENT,
|
|
161
|
-
),
|
|
162
|
-
)
|
|
163
|
-
captured_output = _run_main_with_io(json.dumps(_clean_coder_audit_payload()))
|
|
164
|
-
deny_payload = json.loads(captured_output)
|
|
165
|
-
assert deny_payload["hookSpecificOutput"]["permissionDecision"] == "deny"
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
def test_should_block_when_qbug_was_invoked_but_not_bugteam(
|
|
169
|
-
claude_job_directory: pathlib.Path,
|
|
170
|
-
) -> None:
|
|
171
|
-
_write_state(
|
|
172
|
-
claude_job_directory,
|
|
173
|
-
_bugteam_phase_state(
|
|
174
|
-
bugteam_skill_invoked_at_head=None,
|
|
175
|
-
bugteam_skill_invoked_at_tick=None,
|
|
176
|
-
),
|
|
177
|
-
)
|
|
178
|
-
captured_output = _run_main_with_io(json.dumps(_clean_coder_audit_payload()))
|
|
179
|
-
deny_payload = json.loads(captured_output)
|
|
180
|
-
assert deny_payload["hookSpecificOutput"]["permissionDecision"] == "deny"
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
def test_should_block_when_bugteam_invoked_at_current_head_but_previous_tick(
|
|
184
|
-
claude_job_directory: pathlib.Path,
|
|
185
|
-
) -> None:
|
|
186
|
-
_write_state(
|
|
187
|
-
claude_job_directory,
|
|
188
|
-
_bugteam_phase_state(
|
|
189
|
-
bugteam_skill_invoked_at_head=_HEAD_SHA_CURRENT,
|
|
190
|
-
bugteam_skill_invoked_at_tick=_TICK_CURRENT - 1,
|
|
191
|
-
),
|
|
192
|
-
)
|
|
193
|
-
captured_output = _run_main_with_io(json.dumps(_clean_coder_audit_payload()))
|
|
194
|
-
deny_payload = json.loads(captured_output)
|
|
195
|
-
assert deny_payload["hookSpecificOutput"]["permissionDecision"] == "deny"
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
def test_should_block_when_invoked_head_is_non_string_type(
|
|
199
|
-
claude_job_directory: pathlib.Path,
|
|
200
|
-
) -> None:
|
|
201
|
-
_write_state(
|
|
202
|
-
claude_job_directory,
|
|
203
|
-
_bugteam_phase_state(
|
|
204
|
-
bugteam_skill_invoked_at_head=42,
|
|
205
|
-
bugteam_skill_invoked_at_tick=_TICK_CURRENT,
|
|
206
|
-
),
|
|
207
|
-
)
|
|
208
|
-
captured_output = _run_main_with_io(json.dumps(_clean_coder_audit_payload()))
|
|
209
|
-
deny_payload = json.loads(captured_output)
|
|
210
|
-
assert deny_payload["hookSpecificOutput"]["permissionDecision"] == "deny"
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
def test_should_block_when_current_head_is_non_string_type(
|
|
214
|
-
claude_job_directory: pathlib.Path,
|
|
215
|
-
) -> None:
|
|
216
|
-
_write_state(
|
|
217
|
-
claude_job_directory,
|
|
218
|
-
_bugteam_phase_state(
|
|
219
|
-
current_head=42,
|
|
220
|
-
bugteam_skill_invoked_at_head=_HEAD_SHA_CURRENT,
|
|
221
|
-
bugteam_skill_invoked_at_tick=_TICK_CURRENT,
|
|
222
|
-
),
|
|
223
|
-
)
|
|
224
|
-
captured_output = _run_main_with_io(json.dumps(_clean_coder_audit_payload()))
|
|
225
|
-
deny_payload = json.loads(captured_output)
|
|
226
|
-
assert deny_payload["hookSpecificOutput"]["permissionDecision"] == "deny"
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
def test_should_block_when_invoked_tick_is_string_type(
|
|
230
|
-
claude_job_directory: pathlib.Path,
|
|
231
|
-
) -> None:
|
|
232
|
-
_write_state(
|
|
233
|
-
claude_job_directory,
|
|
234
|
-
_bugteam_phase_state(
|
|
235
|
-
bugteam_skill_invoked_at_head=_HEAD_SHA_CURRENT,
|
|
236
|
-
bugteam_skill_invoked_at_tick="7",
|
|
237
|
-
),
|
|
238
|
-
)
|
|
239
|
-
captured_output = _run_main_with_io(json.dumps(_clean_coder_audit_payload()))
|
|
240
|
-
deny_payload = json.loads(captured_output)
|
|
241
|
-
assert deny_payload["hookSpecificOutput"]["permissionDecision"] == "deny"
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
def test_should_block_when_current_tick_is_string_type(
|
|
245
|
-
claude_job_directory: pathlib.Path,
|
|
246
|
-
) -> None:
|
|
247
|
-
_write_state(
|
|
248
|
-
claude_job_directory,
|
|
249
|
-
_bugteam_phase_state(
|
|
250
|
-
tick_count="7",
|
|
251
|
-
bugteam_skill_invoked_at_head=_HEAD_SHA_CURRENT,
|
|
252
|
-
bugteam_skill_invoked_at_tick=_TICK_CURRENT,
|
|
253
|
-
),
|
|
254
|
-
)
|
|
255
|
-
captured_output = _run_main_with_io(json.dumps(_clean_coder_audit_payload()))
|
|
256
|
-
deny_payload = json.loads(captured_output)
|
|
257
|
-
assert deny_payload["hookSpecificOutput"]["permissionDecision"] == "deny"
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
def test_should_block_when_invoked_tick_is_bool_type(
|
|
261
|
-
claude_job_directory: pathlib.Path,
|
|
262
|
-
) -> None:
|
|
263
|
-
_write_state(
|
|
264
|
-
claude_job_directory,
|
|
265
|
-
_bugteam_phase_state(
|
|
266
|
-
tick_count=1,
|
|
267
|
-
bugteam_skill_invoked_at_head=_HEAD_SHA_CURRENT,
|
|
268
|
-
bugteam_skill_invoked_at_tick=True,
|
|
269
|
-
),
|
|
270
|
-
)
|
|
271
|
-
captured_output = _run_main_with_io(json.dumps(_clean_coder_audit_payload()))
|
|
272
|
-
deny_payload = json.loads(captured_output)
|
|
273
|
-
assert deny_payload["hookSpecificOutput"]["permissionDecision"] == "deny"
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
def test_should_block_when_current_tick_is_bool_type(
|
|
277
|
-
claude_job_directory: pathlib.Path,
|
|
278
|
-
) -> None:
|
|
279
|
-
_write_state(
|
|
280
|
-
claude_job_directory,
|
|
281
|
-
_bugteam_phase_state(
|
|
282
|
-
tick_count=True,
|
|
283
|
-
bugteam_skill_invoked_at_head=_HEAD_SHA_CURRENT,
|
|
284
|
-
bugteam_skill_invoked_at_tick=1,
|
|
285
|
-
),
|
|
286
|
-
)
|
|
287
|
-
captured_output = _run_main_with_io(json.dumps(_clean_coder_audit_payload()))
|
|
288
|
-
deny_payload = json.loads(captured_output)
|
|
289
|
-
assert deny_payload["hookSpecificOutput"]["permissionDecision"] == "deny"
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
def test_should_allow_when_claude_job_dir_env_var_absent(
|
|
293
|
-
monkeypatch: pytest.MonkeyPatch,
|
|
294
|
-
) -> None:
|
|
295
|
-
monkeypatch.delenv(CLAUDE_JOB_DIR_ENV_VAR, raising=False)
|
|
296
|
-
captured_output = _run_main_with_io(json.dumps(_clean_coder_audit_payload()))
|
|
297
|
-
assert captured_output == ""
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
def test_should_allow_when_state_json_is_malformed(
|
|
301
|
-
claude_job_directory: pathlib.Path,
|
|
302
|
-
) -> None:
|
|
303
|
-
state_path = claude_job_directory / PR_CONVERGE_STATE_FILENAME
|
|
304
|
-
state_path.write_text("{not valid json", encoding="utf-8")
|
|
305
|
-
captured_output = _run_main_with_io(json.dumps(_clean_coder_audit_payload()))
|
|
306
|
-
assert captured_output == ""
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
def test_should_allow_when_payload_is_malformed_json() -> None:
|
|
310
|
-
captured_output = _run_main_with_io("not valid json {{{")
|
|
311
|
-
assert captured_output == ""
|
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
"""Unit tests: persistent-agent state keys leave both pr-converge hooks unchanged.
|
|
2
|
-
|
|
3
|
-
pr-converge-state.json carries `agents_session_id` and `persistent_agents`
|
|
4
|
-
(the persistent per-step agent map with a `fix_executor` entry). Neither the
|
|
5
|
-
enforcer (`pr_converge_bugteam_enforcer`) nor the tracker
|
|
6
|
-
(`pr_converge_bugteam_skill_tracker`) reads these keys, so their allow/block
|
|
7
|
-
decisions and stamped fields match a state file without them, and the tracker
|
|
8
|
-
preserves the keys unchanged on its stamp write.
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
from __future__ import annotations
|
|
12
|
-
|
|
13
|
-
import importlib.util
|
|
14
|
-
import io
|
|
15
|
-
import json
|
|
16
|
-
import pathlib
|
|
17
|
-
import sys
|
|
18
|
-
from types import ModuleType
|
|
19
|
-
from typing import Any
|
|
20
|
-
from unittest import mock
|
|
21
|
-
|
|
22
|
-
import pytest
|
|
23
|
-
|
|
24
|
-
_BLOCKING_DIR = pathlib.Path(__file__).parent
|
|
25
|
-
_HOOKS_TREE = _BLOCKING_DIR.parent
|
|
26
|
-
_LIFECYCLE_DIR = _HOOKS_TREE / "lifecycle"
|
|
27
|
-
for each_path in (str(_BLOCKING_DIR), str(_HOOKS_TREE)):
|
|
28
|
-
if each_path not in sys.path:
|
|
29
|
-
sys.path.insert(0, each_path)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def _load_hook_module(module_name: str, module_path: pathlib.Path) -> ModuleType:
|
|
33
|
-
hook_spec = importlib.util.spec_from_file_location(module_name, module_path)
|
|
34
|
-
assert hook_spec is not None
|
|
35
|
-
assert hook_spec.loader is not None
|
|
36
|
-
loaded_module = importlib.util.module_from_spec(hook_spec)
|
|
37
|
-
hook_spec.loader.exec_module(loaded_module)
|
|
38
|
-
return loaded_module
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
enforcer_module = _load_hook_module(
|
|
42
|
-
"pr_converge_bugteam_enforcer_state_tolerance_target",
|
|
43
|
-
_BLOCKING_DIR / "pr_converge_bugteam_enforcer.py",
|
|
44
|
-
)
|
|
45
|
-
tracker_module = _load_hook_module(
|
|
46
|
-
"pr_converge_bugteam_skill_tracker_state_tolerance_target",
|
|
47
|
-
_LIFECYCLE_DIR / "pr_converge_bugteam_skill_tracker.py",
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
from hooks_constants.pr_converge_bugteam_enforcer_constants import (
|
|
51
|
-
BUGTEAM_PHASE,
|
|
52
|
-
CLAUDE_JOB_DIR_ENV_VAR,
|
|
53
|
-
PR_CONVERGE_STATE_FILENAME,
|
|
54
|
-
)
|
|
55
|
-
|
|
56
|
-
_HEAD_SHA_CURRENT = "abc123def456abc123def456abc123def456abcd"
|
|
57
|
-
_TICK_CURRENT = 7
|
|
58
|
-
_AUDIT_PROMPT = "Run the bugteam audit and report findings against the A-J categories."
|
|
59
|
-
_AGENTS_SESSION_ID = "session-0123456789abcdef"
|
|
60
|
-
_FIX_EXECUTOR_AGENT_ID = "prc-fix-42"
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
def _persistent_agent_state_keys() -> dict[str, Any]:
|
|
64
|
-
return {
|
|
65
|
-
"agents_session_id": _AGENTS_SESSION_ID,
|
|
66
|
-
"persistent_agents": {
|
|
67
|
-
"fix_executor": {
|
|
68
|
-
"agent_id": _FIX_EXECUTOR_AGENT_ID,
|
|
69
|
-
"created_tick": 2,
|
|
70
|
-
"last_used_tick": 6,
|
|
71
|
-
}
|
|
72
|
-
},
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
def _bugteam_phase_state(**overrides: Any) -> dict[str, Any]:
|
|
77
|
-
baseline_state: dict[str, Any] = {
|
|
78
|
-
"phase": BUGTEAM_PHASE,
|
|
79
|
-
"current_head": _HEAD_SHA_CURRENT,
|
|
80
|
-
"tick_count": _TICK_CURRENT,
|
|
81
|
-
"bugteam_skill_invoked_at_head": None,
|
|
82
|
-
"bugteam_skill_invoked_at_tick": None,
|
|
83
|
-
}
|
|
84
|
-
baseline_state.update(_persistent_agent_state_keys())
|
|
85
|
-
baseline_state.update(overrides)
|
|
86
|
-
return baseline_state
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
def _write_state(state_directory: pathlib.Path, state: dict[str, Any]) -> pathlib.Path:
|
|
90
|
-
state_path = state_directory / PR_CONVERGE_STATE_FILENAME
|
|
91
|
-
state_path.write_text(json.dumps(state), encoding="utf-8")
|
|
92
|
-
return state_path
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
def _read_state(state_directory: pathlib.Path) -> dict[str, Any]:
|
|
96
|
-
state_path = state_directory / PR_CONVERGE_STATE_FILENAME
|
|
97
|
-
return json.loads(state_path.read_text(encoding="utf-8"))
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
def _clean_coder_audit_payload() -> dict[str, Any]:
|
|
101
|
-
return {
|
|
102
|
-
"tool_name": "Agent",
|
|
103
|
-
"tool_input": {
|
|
104
|
-
"subagent_type": "clean-coder",
|
|
105
|
-
"prompt": _AUDIT_PROMPT,
|
|
106
|
-
"description": "Audit the PR",
|
|
107
|
-
},
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
def _bugteam_skill_payload() -> dict[str, Any]:
|
|
112
|
-
return {
|
|
113
|
-
"tool_name": "Skill",
|
|
114
|
-
"tool_input": {"skill": "bugteam", "args": "https://github.com/o/r/pull/1"},
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
def _run_enforcer_main_with_io(input_text: str) -> str:
|
|
119
|
-
with mock.patch("sys.stdin", io.StringIO(input_text)):
|
|
120
|
-
with mock.patch("sys.stdout", new_callable=io.StringIO) as captured_stdout:
|
|
121
|
-
try:
|
|
122
|
-
enforcer_module.main()
|
|
123
|
-
except SystemExit:
|
|
124
|
-
pass
|
|
125
|
-
return captured_stdout.getvalue()
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
def _run_tracker_main_with_io(input_text: str) -> None:
|
|
129
|
-
with mock.patch("sys.stdin", io.StringIO(input_text)):
|
|
130
|
-
with mock.patch("sys.stdout", new_callable=io.StringIO):
|
|
131
|
-
with mock.patch("sys.stderr", new_callable=io.StringIO):
|
|
132
|
-
try:
|
|
133
|
-
tracker_module.main()
|
|
134
|
-
except SystemExit:
|
|
135
|
-
pass
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
@pytest.fixture()
|
|
139
|
-
def claude_job_directory(tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch) -> pathlib.Path:
|
|
140
|
-
monkeypatch.setenv(CLAUDE_JOB_DIR_ENV_VAR, str(tmp_path))
|
|
141
|
-
return tmp_path
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
def test_should_block_enforcer_when_persistent_agent_keys_present_without_skill(
|
|
145
|
-
claude_job_directory: pathlib.Path,
|
|
146
|
-
) -> None:
|
|
147
|
-
_write_state(claude_job_directory, _bugteam_phase_state())
|
|
148
|
-
captured_output = _run_enforcer_main_with_io(json.dumps(_clean_coder_audit_payload()))
|
|
149
|
-
deny_payload = json.loads(captured_output)
|
|
150
|
-
assert deny_payload["hookSpecificOutput"]["permissionDecision"] == "deny"
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
def test_should_allow_enforcer_when_persistent_agent_keys_present_with_skill_recorded(
|
|
154
|
-
claude_job_directory: pathlib.Path,
|
|
155
|
-
) -> None:
|
|
156
|
-
_write_state(
|
|
157
|
-
claude_job_directory,
|
|
158
|
-
_bugteam_phase_state(
|
|
159
|
-
bugteam_skill_invoked_at_head=_HEAD_SHA_CURRENT,
|
|
160
|
-
bugteam_skill_invoked_at_tick=_TICK_CURRENT,
|
|
161
|
-
),
|
|
162
|
-
)
|
|
163
|
-
captured_output = _run_enforcer_main_with_io(json.dumps(_clean_coder_audit_payload()))
|
|
164
|
-
assert captured_output == ""
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
def test_should_stamp_tracker_fields_when_persistent_agent_keys_present(
|
|
168
|
-
claude_job_directory: pathlib.Path,
|
|
169
|
-
) -> None:
|
|
170
|
-
_write_state(claude_job_directory, _bugteam_phase_state())
|
|
171
|
-
_run_tracker_main_with_io(json.dumps(_bugteam_skill_payload()))
|
|
172
|
-
updated_state = _read_state(claude_job_directory)
|
|
173
|
-
assert updated_state["bugteam_skill_invoked_at_head"] == _HEAD_SHA_CURRENT
|
|
174
|
-
assert updated_state["bugteam_skill_invoked_at_tick"] == _TICK_CURRENT
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
def test_should_preserve_persistent_agent_keys_on_tracker_stamp_write(
|
|
178
|
-
claude_job_directory: pathlib.Path,
|
|
179
|
-
) -> None:
|
|
180
|
-
_write_state(claude_job_directory, _bugteam_phase_state())
|
|
181
|
-
_run_tracker_main_with_io(json.dumps(_bugteam_skill_payload()))
|
|
182
|
-
updated_state = _read_state(claude_job_directory)
|
|
183
|
-
assert updated_state["agents_session_id"] == _AGENTS_SESSION_ID
|
|
184
|
-
assert updated_state["persistent_agents"] == _persistent_agent_state_keys()["persistent_agents"]
|