claude-dev-env 4.3.0 → 7.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/agents/pr-description-writer.md +2 -2
- package/.agents/agents/test_agent_frontmatter.py +2 -2
- package/.agents/agents/test_pr_description_writer_style.py +1 -3
- package/.agents/skills/e-code-review/SKILL.md +12 -12
- package/.agents/skills/e-code-review/preflight-proposal.contract.test.mjs +20 -0
- package/.agents/skills/e-code-review/reference/fix.md +15 -17
- package/.agents/skills/e-code-review/reference/loop.md +35 -39
- package/.agents/skills/e-code-review/reference/mode-contract.test.mjs +2 -1
- package/.agents/skills/e-code-review/reference/preflight-proposal.md +44 -9
- package/.agents/skills/e-code-review/reference/runner-selection.md +13 -14
- package/.agents/skills/issue-tracker/SKILL.md +5 -4
- package/.agents/skills/pull-request/SKILL.md +11 -13
- package/.agents/skills/pull-request/reference/publication-tasks.md +3 -3
- package/.agents/skills/pull-request/scripts/github_pr_command_constants/config/constants.py +9 -1
- package/.agents/skills/pull-request/scripts/pull_request.py +35 -44
- package/.agents/skills/pull-request/scripts/test_github_pr_command_constants.py +9 -0
- package/.agents/skills/pull-request/scripts/test_pull_request.py +35 -3
- package/.agents/skills/session-continuity/README.md +189 -0
- package/.agents/skills/session-continuity/SKILL.md +114 -0
- package/.agents/skills/session-continuity/continuity.mjs +375 -0
- package/.agents/skills/session-continuity/continuity.test.mjs +303 -0
- package/.agents/skills/skill-builder/SKILL.md +3 -196
- package/.agents/skills/syncing-submodules/scripts/test_sync_parent_pointer.py +0 -7
- package/.agents/skills/windows-scheduled-task/SKILL.md +153 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/terminology_sweep_constants.py +6 -0
- package/_shared/pr-loop/scripts/terminology_sweep.py +64 -19
- package/_shared/pr-loop/scripts/tests/test_terminology_sweep.py +59 -0
- package/bin/cde.mjs +36 -8
- package/bin/cde.test.mjs +48 -0
- package/bin/ever-shipped-skills.mjs +2 -20
- package/bin/ever-shipped-skills.test.mjs +8 -0
- package/bin/expand_home_directory_tokens.mjs +4 -5
- package/bin/expand_home_directory_tokens.test.mjs +128 -0
- package/bin/git_hooks_installer.mjs +22 -3
- package/bin/git_hooks_installer.test.mjs +159 -5
- package/bin/install-session-continuity.mjs +78 -0
- package/bin/install-session-continuity.test.mjs +42 -0
- package/bin/install.agents-home.test.mjs +15 -13
- package/bin/install.codex-rules.test.mjs +5 -2
- package/bin/install.cursor-rules.test.mjs +5 -2
- package/bin/install.mjs +506 -63
- package/bin/install.policy-lint.test.mjs +130 -0
- package/bin/install.profiles.test.mjs +10 -0
- package/bin/install.prune.test.mjs +286 -37
- package/bin/install.pstack-plugin.test.mjs +114 -0
- package/bin/install.test.mjs +537 -120
- package/bin/install.transaction.test.mjs +3 -3
- package/bin/refresh-pstack-plugin-skills.test.mjs +211 -0
- package/docs/CODE_RULES.md +1 -1
- package/hooks/advisory/pr_done_reminder.py +342 -0
- package/hooks/advisory/test_pr_done_reminder.py +249 -0
- package/hooks/blocking/bash_post_call_dispatcher.py +53 -6
- package/hooks/blocking/code_rules_banned_identifiers.py +3 -0
- package/hooks/blocking/code_rules_paired_test.py +22 -15
- package/hooks/blocking/precommit_code_rules_gate.py +5 -8
- package/hooks/blocking/test_bash_dispatcher_interpreter_starts.py +7 -52
- package/hooks/blocking/test_bash_post_call_dispatcher.py +65 -4
- package/hooks/blocking/test_bash_pre_tool_use_dispatcher.py +0 -69
- package/hooks/blocking/test_code_rules_banned_identifiers.py +64 -0
- package/hooks/blocking/test_code_rules_enforcer_paired_test.py +36 -29
- package/hooks/blocking/test_code_rules_js_resume_task_enumeration.py +4 -4
- package/hooks/blocking/test_code_rules_js_returns_object_schemaless.py +5 -5
- package/hooks/blocking/test_pre_tool_use_dispatcher.py +43 -342
- package/hooks/blocking/test_pre_tool_use_dispatcher_native.py +20 -51
- package/hooks/blocking/test_shared_stdin_adoption.py +1 -47
- package/hooks/git-hooks/git_hooks_constants/staged_policy_lint.py +12 -0
- package/hooks/git-hooks/git_hooks_constants/verification_notice_constants.py +116 -0
- package/hooks/git-hooks/post_commit.py +33 -0
- package/hooks/git-hooks/pre_commit.py +18 -49
- package/hooks/git-hooks/pre_push.py +45 -541
- package/hooks/git-hooks/test_installed_policy_lint.py +208 -0
- package/hooks/git-hooks/test_native_hook_support.py +129 -0
- package/hooks/git-hooks/test_native_policy_commit.py +120 -0
- package/hooks/git-hooks/test_post_commit.py +43 -0
- package/hooks/git-hooks/test_pre_commit.py +38 -148
- package/hooks/git-hooks/test_pre_push.py +48 -1631
- package/hooks/git-hooks/test_verification_notice.py +60 -0
- package/hooks/git-hooks/test_verification_start.py +173 -0
- package/hooks/git-hooks/verification_notice.py +192 -0
- package/hooks/git-hooks/verification_notice_context.py +253 -0
- package/hooks/git-hooks/verification_notice_state.py +237 -0
- package/hooks/git-hooks/verification_start.py +100 -0
- package/hooks/hooks.json +1 -120
- package/hooks/hooks_constants/banned_identifiers_constants.py +4 -0
- package/hooks/hooks_constants/bash_post_call_dispatcher_constants.py +14 -1
- package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +1 -15
- package/hooks/hooks_constants/post_tool_use_dispatcher_constants.py +0 -4
- package/hooks/hooks_constants/pr_done_reminder_constants.py +98 -0
- package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +10 -93
- package/hooks/hooks_constants/stop_dispatcher_constants.py +1 -4
- package/hooks/hooks_constants/test_bash_post_call_dispatcher_constants.py +19 -2
- package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +3 -66
- package/hooks/hooks_constants/test_pr_done_reminder_constants.py +40 -0
- package/hooks/hooks_constants/test_pre_tool_use_dispatcher_constants.py +46 -201
- package/hooks/hooks_constants/test_stop_dispatcher_constants.py +3 -25
- package/hooks/validation/test_post_tool_use_dispatcher.py +35 -298
- package/hooks/validators/run_all_validators.py +5 -1
- package/hooks/validators/security_checks.py +57 -2
- package/hooks/validators/test_hook_timing_harness.py +2 -5
- package/hooks/validators/test_run_all_validators_mutation_tool_parity.py +3 -11
- package/hooks/validators/test_run_all_validators_policy_lint.py +36 -0
- package/hooks/validators/test_security_checks.py +57 -0
- package/package.json +2 -2
- package/rules/confirm-implementation-forks.md +2 -0
- package/rules/durable-post-artifacts.md +2 -1
- package/rules/git-workflow.md +18 -6
- package/rules/pstack-host-mapping.md +48 -0
- package/rules/vault-context.md +3 -5
- package/rules/verify-runtime-state.md +7 -0
- package/scripts/audit-hooks-cli.mjs +5 -0
- package/scripts/audit-hooks.mjs +72 -16
- package/scripts/automatic_advisory/__init__.py +1 -0
- package/scripts/automatic_advisory/checkout.py +51 -0
- package/scripts/automatic_advisory/cli.py +334 -0
- package/scripts/automatic_advisory/config/__init__.py +1 -0
- package/scripts/automatic_advisory/config/constants.py +93 -0
- package/scripts/automatic_advisory/config/timing.py +4 -0
- package/scripts/automatic_advisory/configuration.py +197 -0
- package/scripts/automatic_advisory/execution.py +350 -0
- package/scripts/automatic_advisory/git.py +69 -0
- package/scripts/automatic_advisory/model.py +107 -0
- package/scripts/automatic_advisory/process_host.py +32 -0
- package/scripts/automatic_advisory/publisher.py +84 -0
- package/scripts/automatic_advisory/runner.py +399 -0
- package/scripts/automatic_advisory/state.py +116 -0
- package/scripts/automatic_advisory/window_flags.py +41 -0
- package/scripts/automatic_advisory/windows_job.py +122 -0
- package/scripts/check.ps1 +0 -1
- package/{.agents/skills/run-claude-dev-env/driver.mjs → scripts/ci/installer-lifecycle.mjs} +3 -28
- package/scripts/ci/installer-lifecycle.test.mjs +26 -0
- package/scripts/ci/windows-installer-lifecycle.ps1 +1 -11
- package/scripts/codex_compat_materializer.py +99 -13
- package/scripts/dev_env_scripts_constants/shared_tree_constants.py +4 -5
- package/{.agents/skills/_shared/pr-loop/scripts → scripts}/durable_post_lint.py +35 -43
- package/{.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/durable_post_lint_constants.py → scripts/durable_post_lint_config/config/constants.py} +10 -10
- package/{.agents/skills/_shared/pr-loop/scripts/test_durable_post_lint_constants.py → scripts/durable_post_lint_config/config/test_constants.py} +14 -1
- package/scripts/local_report_cli.py +184 -0
- package/scripts/local_report_core.py +247 -0
- package/scripts/local_report_publisher.py +12 -0
- package/scripts/local_report_validation.py +357 -0
- package/scripts/local_verification/__init__.py +23 -0
- package/scripts/local_verification/check_runner.py +364 -0
- package/scripts/local_verification/check_support.py +99 -0
- package/scripts/local_verification/cli.py +158 -0
- package/scripts/local_verification/command_runner.py +84 -0
- package/scripts/local_verification/config/__init__.py +1 -0
- package/scripts/local_verification/config/constants.py +89 -0
- package/scripts/local_verification/config/timing.py +1 -0
- package/scripts/local_verification/git_state.py +146 -0
- package/scripts/local_verification/manifest.py +224 -0
- package/scripts/local_verification/model.py +174 -0
- package/scripts/local_verification/runner.py +234 -0
- package/scripts/policy_lint/adapter_detectors.py +13 -6
- package/scripts/policy_lint/adapter_pairing.py +58 -3
- package/scripts/policy_lint/adapters.py +7 -4
- package/scripts/policy_lint/config/approved_test_pairs.py +323 -0
- package/scripts/policy_lint/config/constants.py +5 -0
- package/scripts/policy_lint/engine.py +48 -4
- package/scripts/policy_lint/model.py +1 -0
- package/scripts/policy_lint/registry.py +1 -1
- package/scripts/policy_lint/selection.py +2 -1
- package/scripts/policy_lint/selection_git.py +2 -0
- package/scripts/pr_verification/__init__.py +1 -0
- package/scripts/pr_verification/branch_refs.py +114 -0
- package/scripts/pr_verification/config/__init__.py +1 -0
- package/scripts/pr_verification/config/constants.py +84 -0
- package/scripts/pr_verification/config/timing.py +5 -0
- package/scripts/pr_verification/github.py +137 -0
- package/scripts/pr_verification/github_api.py +376 -0
- package/scripts/pr_verification/github_labels.py +88 -0
- package/scripts/pr_verification/github_parsing.py +182 -0
- package/scripts/pr_verification/github_transport.py +91 -0
- package/scripts/pr_verification/lock.py +122 -0
- package/scripts/pr_verification/model.py +43 -0
- package/scripts/pr_verification/requirements.txt +1 -0
- package/scripts/refresh_pstack_plugin_skills.mjs +200 -0
- package/scripts/shared_tree_paths.py +32 -3
- package/{.agents/skills/_shared/pr-loop/scripts → scripts}/test_durable_post_lint.py +97 -0
- package/scripts/test_shared_tree_paths.py +33 -0
- package/scripts/tests/test-audit-hooks-cli.mjs +150 -0
- package/scripts/tests/test-audit-hooks.mjs +94 -2
- package/scripts/tests/test_automatic_advisory.py +376 -0
- package/scripts/tests/test_automatic_advisory_job_cleanup.py +71 -0
- package/scripts/tests/test_automatic_advisory_polling.py +216 -0
- package/scripts/tests/test_automatic_advisory_recovery.py +320 -0
- package/scripts/tests/test_automatic_advisory_repairs.py +400 -0
- package/scripts/tests/test_automatic_advisory_settings.py +48 -0
- package/scripts/tests/test_automatic_advisory_window_flags.py +163 -0
- package/scripts/tests/test_closed_pr_label.py +157 -0
- package/scripts/tests/test_codex_compat_materializer.py +189 -6
- package/scripts/tests/test_github_labels.py +101 -0
- package/scripts/tests/test_github_transport.py +125 -0
- package/scripts/tests/test_local_report_publisher.py +396 -0
- package/scripts/tests/test_local_report_publisher_cli.py +85 -0
- package/scripts/tests/test_local_verification.py +348 -0
- package/scripts/tests/test_local_verification_cli.py +98 -0
- package/scripts/tests/test_policy_lint_rules_archive.py +120 -0
- package/scripts/tests/test_policy_lint_rules_engine.py +25 -4
- package/scripts/tests/test_policy_lint_rules_installed_loader.py +29 -0
- package/scripts/tests/test_policy_lint_rules_pairing.py +163 -0
- package/scripts/tests/test_policy_lint_rules_registry.py +227 -13
- package/scripts/tests/test_policy_lint_selection.py +25 -2
- package/scripts/tests/test_policy_lint_selection_native_index.py +30 -0
- package/scripts/tests/test_pr_verification_github.py +392 -0
- package/scripts/tests/test_pr_verification_lock.py +168 -0
- package/scripts/tests/test_rules.py +3 -7
- package/scripts/tests/test_verification_notice.py +347 -0
- package/.agents/skills/_shared/pr-loop/.claude/CLAUDE.md +0 -1
- package/.agents/skills/_shared/pr-loop/audit-contract.md +0 -5
- package/.agents/skills/_shared/pr-loop/audit-reply-template.md +0 -5
- package/.agents/skills/_shared/pr-loop/code-rules-gate.md +0 -5
- package/.agents/skills/_shared/pr-loop/fix-protocol.md +0 -5
- package/.agents/skills/_shared/pr-loop/gh-payloads.md +0 -5
- package/.agents/skills/_shared/pr-loop/portable-driver.md +0 -149
- package/.agents/skills/_shared/pr-loop/post-audit-thread-contract.md +0 -5
- package/.agents/skills/_shared/pr-loop/precatch-rubric.md +0 -5
- package/.agents/skills/_shared/pr-loop/preflight-proposal.contract.test.mjs +0 -119
- package/.agents/skills/_shared/pr-loop/preflight-proposal.md +0 -5
- package/.agents/skills/_shared/pr-loop/prompts/.claude/CLAUDE.md +0 -1
- package/.agents/skills/_shared/pr-loop/prompts/AGENTS.md +0 -1
- package/.agents/skills/_shared/pr-loop/prompts/pr-consistency-audit.xml +0 -324
- package/.agents/skills/_shared/pr-loop/scripts/.claude/CLAUDE.md +0 -1
- package/.agents/skills/_shared/pr-loop/scripts/AGENTS.md +0 -1
- package/.agents/skills/_shared/pr-loop/scripts/RUNTIME_SCRIPTS.md +0 -29
- package/.agents/skills/_shared/pr-loop/scripts/_cli_utils.py +0 -22
- package/.agents/skills/_shared/pr-loop/scripts/_path_resolver.py +0 -186
- package/.agents/skills/_shared/pr-loop/scripts/_xml_utils.py +0 -20
- package/.agents/skills/_shared/pr-loop/scripts/audit_category_schema.py +0 -355
- package/.agents/skills/_shared/pr-loop/scripts/build_audit_prompt.py +0 -289
- package/.agents/skills/_shared/pr-loop/scripts/build_converge_task_list.py +0 -310
- package/.agents/skills/_shared/pr-loop/scripts/build_fix_prompt.py +0 -292
- package/.agents/skills/_shared/pr-loop/scripts/init_loop_state.py +0 -134
- package/.agents/skills/_shared/pr-loop/scripts/portable_converge_driver.py +0 -1646
- package/.agents/skills/_shared/pr-loop/scripts/preflight_worktree.py +0 -392
- package/.agents/skills/_shared/pr-loop/scripts/select_converge_pacer.py +0 -215
- package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/.claude/CLAUDE.md +0 -1
- package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/AGENTS.md +0 -1
- package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/__init__.py +0 -0
- package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/audit_category_schema_constants.py +0 -37
- package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/converge_task_list_constants.py +0 -56
- package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/handoff_constants.py +0 -45
- package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/pacer_constants.py +0 -47
- package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/path_resolver_constants.py +0 -157
- package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/portable_driver_constants.py +0 -177
- package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/preflight_constants.py +0 -70
- package/.agents/skills/_shared/pr-loop/scripts/teardown_worktrees.py +0 -172
- package/.agents/skills/_shared/pr-loop/scripts/test__path_resolver.py +0 -106
- package/.agents/skills/_shared/pr-loop/scripts/test_audit_category_schema.py +0 -94
- package/.agents/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +0 -499
- package/.agents/skills/_shared/pr-loop/scripts/test_build_converge_task_list.py +0 -140
- package/.agents/skills/_shared/pr-loop/scripts/test_build_fix_prompt.py +0 -262
- package/.agents/skills/_shared/pr-loop/scripts/test_init_loop_state.py +0 -71
- package/.agents/skills/_shared/pr-loop/scripts/test_portable_converge_driver.py +0 -1119
- package/.agents/skills/_shared/pr-loop/scripts/test_preflight_worktree.py +0 -287
- package/.agents/skills/_shared/pr-loop/scripts/test_select_converge_pacer.py +0 -207
- package/.agents/skills/_shared/pr-loop/scripts/test_teardown_worktrees.py +0 -59
- package/.agents/skills/_shared/pr-loop/scripts/test_write_handoff.py +0 -201
- package/.agents/skills/_shared/pr-loop/scripts/write_audit_outcomes.py +0 -182
- package/.agents/skills/_shared/pr-loop/scripts/write_fix_outcomes.py +0 -206
- package/.agents/skills/_shared/pr-loop/scripts/write_handoff.py +0 -309
- package/.agents/skills/_shared/pr-loop/state-schema.md +0 -5
- package/.agents/skills/_shared/pr-loop/worker-spawn.md +0 -5
- package/.agents/skills/autoconverge/.claude/CLAUDE.md +0 -1
- package/.agents/skills/autoconverge/AGENTS.md +0 -1
- package/.agents/skills/autoconverge/SKILL.md +0 -431
- package/.agents/skills/autoconverge/reference/.claude/CLAUDE.md +0 -1
- package/.agents/skills/autoconverge/reference/AGENTS.md +0 -1
- package/.agents/skills/autoconverge/reference/closing-report.md +0 -93
- package/.agents/skills/autoconverge/reference/convergence-gates.md +0 -31
- package/.agents/skills/autoconverge/reference/convergence.md +0 -219
- package/.agents/skills/autoconverge/reference/copilot-findings.md +0 -51
- package/.agents/skills/autoconverge/reference/gotchas.md +0 -66
- package/.agents/skills/autoconverge/reference/headless-safety.md +0 -44
- package/.agents/skills/autoconverge/reference/multi-pr.md +0 -128
- package/.agents/skills/autoconverge/reference/self-closing-loop.md +0 -84
- package/.agents/skills/autoconverge/reference/stop-conditions.md +0 -127
- package/.agents/skills/autoconverge/test_portable_pacer_gate.py +0 -54
- package/.agents/skills/autoconverge/workflow/.claude/CLAUDE.md +0 -1
- package/.agents/skills/autoconverge/workflow/AGENTS.md +0 -1
- package/.agents/skills/autoconverge/workflow/aggregate_runs.py +0 -371
- package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/.claude/CLAUDE.md +0 -1
- package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/AGENTS.md +0 -1
- package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/__init__.py +0 -0
- package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/render_report_constants.py +0 -250
- package/.agents/skills/autoconverge/workflow/converge.clean-audit.test.mjs +0 -665
- package/.agents/skills/autoconverge/workflow/converge.codex-gate.test.mjs +0 -472
- package/.agents/skills/autoconverge/workflow/converge.contract.test.mjs +0 -64
- package/.agents/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +0 -1009
- package/.agents/skills/autoconverge/workflow/converge.fix-progress.test.mjs +0 -107
- package/.agents/skills/autoconverge/workflow/converge.fix-recovery.test.mjs +0 -33
- package/.agents/skills/autoconverge/workflow/converge.merge-conflict.test.mjs +0 -15
- package/.agents/skills/autoconverge/workflow/converge.mjs +0 -3132
- package/.agents/skills/autoconverge/workflow/converge.p2-advance.test.mjs +0 -202
- package/.agents/skills/autoconverge/workflow/converge.path-aware.test.mjs +0 -47
- package/.agents/skills/autoconverge/workflow/converge.precatch.test.mjs +0 -152
- package/.agents/skills/autoconverge/workflow/converge.run-input.test.mjs +0 -116
- package/.agents/skills/autoconverge/workflow/converge_multi.mjs +0 -169
- package/.agents/skills/autoconverge/workflow/converge_multi.run-input.test.mjs +0 -173
- package/.agents/skills/autoconverge/workflow/convergence_summary.py +0 -110
- package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a11d903476b803493.jsonl +0 -2
- package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a26213978adeef6fb.jsonl +0 -2
- package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a3def0d15ed9d9110.jsonl +0 -2
- package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a41f41b1b708ee3b7.jsonl +0 -2
- package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a758b880abecc3ff7.jsonl +0 -2
- package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a8897b89656b1bd16.jsonl +0 -2
- package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-ab1c2d3e4f5a6b7c8.jsonl +0 -2
- package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-abd463d744a1437bc.jsonl +0 -2
- package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-ad19d027ae8ee1816.jsonl +0 -2
- package/.agents/skills/autoconverge/workflow/fixtures/wf_run/workflows/wf_881252e6-700.json +0 -266
- package/.agents/skills/autoconverge/workflow/render_report.py +0 -1024
- package/.agents/skills/autoconverge/workflow/test_aggregate_runs.py +0 -134
- package/.agents/skills/autoconverge/workflow/test_convergence_summary.py +0 -149
- package/.agents/skills/autoconverge/workflow/test_render_report.py +0 -812
- package/.agents/skills/hitl/SKILL.md +0 -45
- package/.agents/skills/pr-cleanup/SKILL.md +0 -140
- package/.agents/skills/pr-cleanup/reference/process-inventory.md +0 -17
- package/.agents/skills/pr-cleanup/reference/task-seeds.md +0 -22
- package/.agents/skills/pr-name-by-capability/SKILL.md +0 -73
- package/.agents/skills/pr-name-by-capability/reference/fetch-commands.md +0 -29
- package/.agents/skills/pr-name-by-capability/reference/offense-examples.md +0 -32
- package/.agents/skills/pr-name-by-capability/reference/report-template.md +0 -29
- package/.agents/skills/pr-name-by-capability/reference/rule-checklist.md +0 -38
- package/.agents/skills/pr-name-by-capability/reference/task-seeds.md +0 -10
- package/.agents/skills/pr-plain-language-cleanup/SKILL.md +0 -93
- package/.agents/skills/pr-refinement/SKILL.md +0 -31
- package/.agents/skills/pr-shared-extraction/SKILL.md +0 -177
- package/.agents/skills/pr-shared-extraction/reference/examples.md +0 -46
- package/.agents/skills/pr-shared-extraction/reference/offense-taxonomy.md +0 -100
- package/.agents/skills/pr-small-cl/SKILL.md +0 -52
- package/.agents/skills/pr-title-description/SKILL.md +0 -96
- package/.agents/skills/prototype/SKILL.md +0 -85
- package/.agents/skills/prototype/reference/honest-limitations.md +0 -23
- package/.agents/skills/prototype/reference/promotion-tasks.md +0 -23
- package/.agents/skills/prototype/scripts/build_sandbox_settings.py +0 -249
- package/.agents/skills/prototype/scripts/conftest.py +0 -15
- package/.agents/skills/prototype/scripts/launch_sandbox.py +0 -205
- package/.agents/skills/prototype/scripts/probe_sandbox_safety.py +0 -311
- package/.agents/skills/prototype/scripts/prototype_scripts_constants/__init__.py +0 -1
- package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/__init__.py +0 -0
- package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/build_sandbox_settings_constants.py +0 -41
- package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/launch_sandbox_constants.py +0 -23
- package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/probe_sandbox_safety_constants.py +0 -45
- package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/prototype_common_constants.py +0 -10
- package/.agents/skills/prototype/scripts/test_build_sandbox_settings.py +0 -275
- package/.agents/skills/prototype/scripts/test_launch_sandbox.py +0 -303
- package/.agents/skills/prototype/scripts/test_probe_sandbox_safety.py +0 -312
- package/.agents/skills/prototype/workflows/promotion.md +0 -25
- package/.agents/skills/prototype/workflows/sandbox.md +0 -35
- package/.agents/skills/rebase/.claude/CLAUDE.md +0 -1
- package/.agents/skills/rebase/AGENTS.md +0 -1
- package/.agents/skills/rebase/SKILL.md +0 -178
- package/.agents/skills/review-router/SKILL.md +0 -51
- package/.agents/skills/review-router/reference/route-policy.json +0 -7
- package/.agents/skills/review-router/reference/task-seeds.md +0 -1
- package/.agents/skills/review-router/scripts/conftest.py +0 -4
- package/.agents/skills/review-router/scripts/review_router.py +0 -50
- package/.agents/skills/review-router/scripts/review_router_cli.py +0 -211
- package/.agents/skills/review-router/scripts/review_router_constants/__init__.py +0 -1
- package/.agents/skills/review-router/scripts/review_router_constants/config/__init__.py +0 -0
- package/.agents/skills/review-router/scripts/review_router_constants/config/constants.py +0 -13
- package/.agents/skills/review-router/scripts/test_review_router.py +0 -23
- package/.agents/skills/review-router/scripts/test_review_router_cli.py +0 -47
- package/.agents/skills/review-tier/SKILL.md +0 -56
- package/.agents/skills/review-tier/reference/task-seeds.md +0 -1
- package/.agents/skills/review-tier/reference/tier-policy.json +0 -9
- package/.agents/skills/review-tier/scripts/conftest.py +0 -4
- package/.agents/skills/review-tier/scripts/review_tier.py +0 -359
- package/.agents/skills/review-tier/scripts/review_tier_constants/__init__.py +0 -1
- package/.agents/skills/review-tier/scripts/review_tier_constants/config/__init__.py +0 -0
- package/.agents/skills/review-tier/scripts/review_tier_constants/config/constants.py +0 -29
- package/.agents/skills/review-tier/scripts/test_generation.py +0 -159
- package/.agents/skills/review-tier/scripts/test_review_tier.py +0 -33
- package/.agents/skills/run-claude-dev-env/SKILL.md +0 -84
- package/.agents/skills/session-log/.claude/CLAUDE.md +0 -1
- package/.agents/skills/session-log/AGENTS.md +0 -1
- package/.agents/skills/session-log/SKILL.md +0 -170
- package/.agents/skills/session-log/templates/frontmatter.md +0 -40
- package/.agents/skills/session-tidy/.claude/CLAUDE.md +0 -1
- package/.agents/skills/session-tidy/AGENTS.md +0 -1
- package/.agents/skills/session-tidy/SKILL.md +0 -191
- package/.agents/skills/skill-builder/references/.claude/CLAUDE.md +0 -1
- package/.agents/skills/skill-builder/references/AGENTS.md +0 -1
- package/.agents/skills/skill-builder/references/delegation-map.md +0 -130
- package/.agents/skills/skill-builder/references/description-field.md +0 -111
- package/.agents/skills/skill-builder/references/deterministic-elements.md +0 -218
- package/.agents/skills/skill-builder/references/progressive-disclosure.md +0 -122
- package/.agents/skills/skill-builder/references/self-audit-checklist.md +0 -126
- package/.agents/skills/skill-builder/references/skill-modularity.md +0 -111
- package/.agents/skills/skill-builder/references/skill-types.md +0 -232
- package/.agents/skills/skill-builder/references/thariq-x-post-skills.json +0 -33
- package/.agents/skills/skill-builder/templates/.claude/CLAUDE.md +0 -1
- package/.agents/skills/skill-builder/templates/AGENTS.md +0 -1
- package/.agents/skills/skill-builder/templates/gap-analysis.md +0 -87
- package/.agents/skills/skill-builder/workflows/.claude/CLAUDE.md +0 -1
- package/.agents/skills/skill-builder/workflows/AGENTS.md +0 -1
- package/.agents/skills/skill-builder/workflows/improve-skill.md +0 -136
- package/.agents/skills/skill-builder/workflows/new-skill.md +0 -179
- package/.agents/skills/skill-builder/workflows/polish-skill.md +0 -139
- package/.agents/skills/source-command-sr-loop/SKILL.md +0 -82
- package/.agents/skills/update/.claude/CLAUDE.md +0 -1
- package/.agents/skills/update/AGENTS.md +0 -1
- package/.agents/skills/update/SKILL.md +0 -190
- package/_shared/pr-loop/AGENTS.md +0 -1
- package/_shared/pr-loop/audit-contract.md +0 -183
- package/_shared/pr-loop/audit-reply-template.md +0 -151
- package/_shared/pr-loop/code-rules-gate.md +0 -65
- package/_shared/pr-loop/fix-protocol.md +0 -60
- package/_shared/pr-loop/gh-payloads.md +0 -75
- package/_shared/pr-loop/post-audit-thread-contract.md +0 -51
- package/_shared/pr-loop/precatch-rubric.md +0 -66
- package/_shared/pr-loop/preflight-proposal.md +0 -56
- package/_shared/pr-loop/scripts/post_audit_thread.py +0 -1242
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/post_audit_thread_constants.py +0 -199
- package/_shared/pr-loop/scripts/tests/test_post_audit_thread.py +0 -1115
- package/_shared/pr-loop/scripts/tests/test_post_audit_thread_constants.py +0 -129
- package/_shared/pr-loop/state-schema.md +0 -45
- package/_shared/pr-loop/worker-spawn.md +0 -189
- package/hooks/blocking/ask_user_question_shape_blocker.py +0 -83
- package/hooks/blocking/bot_mention_comment_blocker.py +0 -76
- package/hooks/blocking/claude_md_orphan_file_blocker.py +0 -129
- package/hooks/blocking/cursor_cli_python_misfire_blocker.py +0 -108
- package/hooks/blocking/destructive_command_blocker.py +0 -2073
- package/hooks/blocking/fable_spawn_gate.py +0 -187
- package/hooks/blocking/luna_fast_mode_gate.py +0 -160
- package/hooks/blocking/nas_ssh_binary_enforcer.py +0 -163
- package/hooks/blocking/orchestrator_refresh_reschedule_gate.py +0 -260
- package/hooks/blocking/package_inventory_stale_blocker.py +0 -107
- package/hooks/blocking/pii_prevention_blocker.py +0 -365
- package/hooks/blocking/piped_pytest_blocker.py +0 -1223
- package/hooks/blocking/question_to_user_enforcer.py +0 -130
- package/hooks/blocking/send_user_file_open_locally_blocker.py +0 -71
- package/hooks/blocking/sensitive_file_protector.py +0 -167
- package/hooks/blocking/session_edit_stage_gate.py +0 -684
- package/hooks/blocking/session_handoff_blocker.py +0 -182
- package/hooks/blocking/shell_substitution_blocker.py +0 -350
- package/hooks/blocking/test_ask_user_question_shape.py +0 -69
- package/hooks/blocking/test_bot_mention_comment_blocker.py +0 -131
- package/hooks/blocking/test_claude_md_orphan_file_blocker.py +0 -1277
- package/hooks/blocking/test_cursor_cli_python_misfire_blocker.py +0 -208
- package/hooks/blocking/test_destructive_command_blocker.py +0 -2024
- package/hooks/blocking/test_destructive_command_blocker_deny_mode.py +0 -105
- package/hooks/blocking/test_destructive_command_blocker_no_verify.py +0 -122
- package/hooks/blocking/test_destructive_command_blocker_patterns.py +0 -133
- package/hooks/blocking/test_fable_spawn_gate.py +0 -381
- package/hooks/blocking/test_inventory_deadlock_resolution.py +0 -154
- package/hooks/blocking/test_luna_fast_mode_gate.py +0 -211
- package/hooks/blocking/test_nas_ssh_binary_enforcer.py +0 -225
- package/hooks/blocking/test_orchestrator_refresh_reschedule_gate.py +0 -231
- package/hooks/blocking/test_package_inventory_stale_blocker.py +0 -382
- package/hooks/blocking/test_pii_prevention_blocker.py +0 -1222
- package/hooks/blocking/test_pii_prevention_windows_git_path.py +0 -41
- package/hooks/blocking/test_pii_write_surface_ephemeral.py +0 -221
- package/hooks/blocking/test_piped_pytest_blocker.py +0 -587
- package/hooks/blocking/test_precommit_code_rules_gate_native_owner.py +0 -233
- package/hooks/blocking/test_question_to_user_enforcer.py +0 -169
- package/hooks/blocking/test_send_user_file_open_locally_blocker.py +0 -118
- package/hooks/blocking/test_sensitive_file_protector.py +0 -365
- package/hooks/blocking/test_session_edit_stage_gate.py +0 -537
- package/hooks/blocking/test_session_edit_stage_gate_staging.py +0 -64
- package/hooks/blocking/test_session_edit_stage_gate_support.py +0 -204
- package/hooks/blocking/test_session_edit_stage_gate_tracker.py +0 -116
- package/hooks/blocking/test_session_handoff_blocker.py +0 -318
- package/hooks/blocking/test_shell_substitution_blocker.py +0 -298
- package/hooks/blocking/test_unscoped_search_blocker.py +0 -102
- package/hooks/blocking/test_write_existing_file_blocker.py +0 -122
- package/hooks/blocking/tests/test_pii_prevention_blocker.py +0 -259
- package/hooks/blocking/unscoped_search_blocker.py +0 -391
- package/hooks/blocking/write_existing_file_blocker.py +0 -124
- package/hooks/hooks_constants/bot_mention_comment_blocker_constants.py +0 -19
- package/hooks/hooks_constants/cursor_cli_python_misfire_blocker_constants.py +0 -79
- package/hooks/hooks_constants/fable_spawn_gate_constants.py +0 -61
- package/hooks/hooks_constants/luna_fast_mode_gate_constants.py +0 -45
- package/hooks/hooks_constants/nas_ssh_binary_enforcer_constants.py +0 -20
- package/hooks/hooks_constants/orchestrator_refresh_reschedule_gate_constants.py +0 -57
- package/hooks/hooks_constants/send_user_file_open_locally_blocker_constants.py +0 -17
- package/hooks/hooks_constants/sensitive_file_protector_constants.py +0 -40
- package/hooks/hooks_constants/session_handoff_blocker_constants.py +0 -10
- package/hooks/hooks_constants/shell_substitution_blocker_constants.py +0 -80
- /package/.agents/skills/{_shared/pr-loop → session-continuity}/AGENTS.md +0 -0
- /package/{_shared/pr-loop → .agents/skills/session-continuity}/CLAUDE.md +0 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
"""Behavior tests for the PR done-checklist context reminder.
|
|
2
|
+
|
|
3
|
+
The hook is a PostToolUse observer on Bash. It never blocks. These tests pin
|
|
4
|
+
three things: which commands wake it, what the checklist says for each live
|
|
5
|
+
PR state, and that a probe failure stays quiet. The ``gh`` probe is faked at
|
|
6
|
+
the subprocess boundary so no test touches the network.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import io
|
|
12
|
+
import json
|
|
13
|
+
import subprocess
|
|
14
|
+
import sys
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
import pytest
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
advisory_directory = str(Path(__file__).resolve().parent)
|
|
21
|
+
if advisory_directory not in sys.path:
|
|
22
|
+
sys.path.insert(0, advisory_directory)
|
|
23
|
+
import pr_done_reminder
|
|
24
|
+
except ImportError as import_error:
|
|
25
|
+
raise ImportError(
|
|
26
|
+
"test_pr_done_reminder: cannot import its sibling modules; "
|
|
27
|
+
"ensure the advisory directory is importable."
|
|
28
|
+
) from import_error
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
_CLEAN_PR_OBJECT: dict[str, object] = {
|
|
32
|
+
"number": 42,
|
|
33
|
+
"url": "https://github.com/acme/widgets/pull/42",
|
|
34
|
+
"isDraft": True,
|
|
35
|
+
"mergeable": "MERGEABLE",
|
|
36
|
+
"mergeStateStatus": "CLEAN",
|
|
37
|
+
"statusCheckRollup": [
|
|
38
|
+
{"name": "pytest", "status": "COMPLETED", "conclusion": "SUCCESS"},
|
|
39
|
+
{"context": "lint", "state": "SUCCESS"},
|
|
40
|
+
],
|
|
41
|
+
"labels": [],
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _payload(command: str, tool_response: object = None) -> str:
|
|
46
|
+
response: object = {"stdout": "", "stderr": ""} if tool_response is None else tool_response
|
|
47
|
+
return json.dumps(
|
|
48
|
+
{
|
|
49
|
+
"session_id": "reminder-session",
|
|
50
|
+
"cwd": "C:/repo",
|
|
51
|
+
"tool_name": "Bash",
|
|
52
|
+
"tool_input": {"command": command},
|
|
53
|
+
"tool_response": response,
|
|
54
|
+
}
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _fake_gh(pr_object: dict[str, object] | None, stderr: str = "") -> object:
|
|
59
|
+
def _run(*args: object, **kwargs: object) -> subprocess.CompletedProcess[str]:
|
|
60
|
+
del args, kwargs
|
|
61
|
+
if pr_object is None:
|
|
62
|
+
return subprocess.CompletedProcess([], 1, stdout="", stderr=stderr)
|
|
63
|
+
return subprocess.CompletedProcess([], 0, stdout=json.dumps(pr_object), stderr="")
|
|
64
|
+
|
|
65
|
+
return _run
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _run_main(
|
|
69
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
70
|
+
capsys: pytest.CaptureFixture[str],
|
|
71
|
+
payload_text: str,
|
|
72
|
+
fake_gh: object,
|
|
73
|
+
) -> str:
|
|
74
|
+
monkeypatch.setattr(pr_done_reminder.subprocess, "run", fake_gh)
|
|
75
|
+
monkeypatch.setattr(sys, "stdin", io.StringIO(payload_text))
|
|
76
|
+
pr_done_reminder.main()
|
|
77
|
+
return capsys.readouterr().out
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _additional_context(stdout_text: str) -> str:
|
|
81
|
+
return str(json.loads(stdout_text)["hookSpecificOutput"]["additionalContext"])
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@pytest.mark.parametrize(
|
|
85
|
+
"command",
|
|
86
|
+
[
|
|
87
|
+
"git push",
|
|
88
|
+
"git push -u origin feat/x",
|
|
89
|
+
"git -C C:/repo push origin HEAD",
|
|
90
|
+
"gh pr create --draft --title t --body-file b.md",
|
|
91
|
+
"git add -A && git commit -F m.txt && git push",
|
|
92
|
+
"git -c push.default=current push",
|
|
93
|
+
"git --git-dir=C:/repo/.git push",
|
|
94
|
+
'pwsh -NoProfile -Command "git push -u origin feat/x"',
|
|
95
|
+
"pwsh -NoProfile -Command 'gh pr create --draft --fill'",
|
|
96
|
+
'powershell -Command "git add -A; git push"',
|
|
97
|
+
],
|
|
98
|
+
)
|
|
99
|
+
def test_should_wake_on_git_push_and_gh_pr_create(command: str) -> None:
|
|
100
|
+
assert pr_done_reminder.command_triggers_reminder(command) is True
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@pytest.mark.parametrize(
|
|
104
|
+
"command",
|
|
105
|
+
[
|
|
106
|
+
"git status",
|
|
107
|
+
"git pull",
|
|
108
|
+
"git stash push -u -q -- packages",
|
|
109
|
+
"git log --grep push",
|
|
110
|
+
"git commit -m push",
|
|
111
|
+
"gh pr view 42",
|
|
112
|
+
"gh pr ready --undo",
|
|
113
|
+
"echo git push",
|
|
114
|
+
'pwsh -NoProfile -Command "git status"',
|
|
115
|
+
"pytest tests/",
|
|
116
|
+
],
|
|
117
|
+
)
|
|
118
|
+
def test_should_stay_quiet_for_other_commands(command: str) -> None:
|
|
119
|
+
assert pr_done_reminder.command_triggers_reminder(command) is False
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def test_should_say_done_when_mergeable_and_checks_pass() -> None:
|
|
123
|
+
context = pr_done_reminder.build_reminder_context(_CLEAN_PR_OBJECT)
|
|
124
|
+
|
|
125
|
+
assert context.startswith("=== PR DONE CHECKLIST")
|
|
126
|
+
assert "never a block" in context
|
|
127
|
+
assert "DONE. Add the label now:" in context
|
|
128
|
+
assert "gh pr edit 42 --add-label done" in context
|
|
129
|
+
assert "NOT DONE" not in context
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def test_should_keep_the_checklist_order_verdict_then_recheck_then_footer() -> None:
|
|
133
|
+
all_lines = pr_done_reminder.build_reminder_context(_CLEAN_PR_OBJECT).split("\n")
|
|
134
|
+
|
|
135
|
+
all_labels = [each_line.split(":")[0] for each_line in all_lines[1:-1]]
|
|
136
|
+
assert all_labels == [
|
|
137
|
+
"PR #42 https",
|
|
138
|
+
"Mergeable",
|
|
139
|
+
"CI checks",
|
|
140
|
+
"Draft",
|
|
141
|
+
"Label done",
|
|
142
|
+
"Verdict",
|
|
143
|
+
"Add label",
|
|
144
|
+
"Re-check",
|
|
145
|
+
]
|
|
146
|
+
assert all_lines[-1] == "A PR is done only when every line above is clean."
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def test_should_say_not_done_and_name_the_conflict_fix() -> None:
|
|
150
|
+
conflicted = {**_CLEAN_PR_OBJECT, "mergeable": "CONFLICTING", "mergeStateStatus": "DIRTY"}
|
|
151
|
+
|
|
152
|
+
context = pr_done_reminder.build_reminder_context(conflicted)
|
|
153
|
+
|
|
154
|
+
assert "NOT DONE" in context
|
|
155
|
+
assert "CONFLICTS with the base branch" in context
|
|
156
|
+
assert "gh pr view 42 --json mergeable" in context
|
|
157
|
+
assert "gh pr edit 42 --add-label" not in context
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def test_should_count_failing_and_pending_checks() -> None:
|
|
161
|
+
mixed_checks = {
|
|
162
|
+
**_CLEAN_PR_OBJECT,
|
|
163
|
+
"statusCheckRollup": [
|
|
164
|
+
{"name": "pytest", "status": "COMPLETED", "conclusion": "FAILURE"},
|
|
165
|
+
{"name": "mypy", "status": "IN_PROGRESS", "conclusion": ""},
|
|
166
|
+
{"context": "lint", "state": "PENDING"},
|
|
167
|
+
{"context": "docs", "state": "SUCCESS"},
|
|
168
|
+
],
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
context = pr_done_reminder.build_reminder_context(mixed_checks)
|
|
172
|
+
|
|
173
|
+
assert "1 failing" in context
|
|
174
|
+
assert "2 pending" in context
|
|
175
|
+
assert "4 total" in context
|
|
176
|
+
assert "NOT DONE" in context
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def test_should_tell_the_agent_to_wait_when_github_is_still_computing() -> None:
|
|
180
|
+
unknown = {**_CLEAN_PR_OBJECT, "mergeable": "UNKNOWN", "mergeStateStatus": "UNKNOWN"}
|
|
181
|
+
|
|
182
|
+
context = pr_done_reminder.build_reminder_context(unknown)
|
|
183
|
+
|
|
184
|
+
assert "still computing" in context
|
|
185
|
+
assert "NOT DONE" in context
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def test_should_report_the_done_label_when_already_set() -> None:
|
|
189
|
+
labeled = {**_CLEAN_PR_OBJECT, "labels": [{"name": "done"}]}
|
|
190
|
+
|
|
191
|
+
context = pr_done_reminder.build_reminder_context(labeled)
|
|
192
|
+
|
|
193
|
+
assert "Label done: set" in context
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def test_main_should_emit_additional_context_after_a_successful_push(
|
|
197
|
+
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
|
198
|
+
) -> None:
|
|
199
|
+
stdout_text = _run_main(monkeypatch, capsys, _payload("git push"), _fake_gh(_CLEAN_PR_OBJECT))
|
|
200
|
+
|
|
201
|
+
payload = json.loads(stdout_text)
|
|
202
|
+
assert payload["hookSpecificOutput"]["hookEventName"] == "PostToolUse"
|
|
203
|
+
assert "permissionDecision" not in payload["hookSpecificOutput"]
|
|
204
|
+
assert "decision" not in payload
|
|
205
|
+
assert "PR #42" in _additional_context(stdout_text)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def test_main_should_also_serve_the_powershell_tool(
|
|
209
|
+
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
|
210
|
+
) -> None:
|
|
211
|
+
payload = json.loads(_payload("git push"))
|
|
212
|
+
payload["tool_name"] = "PowerShell"
|
|
213
|
+
|
|
214
|
+
stdout_text = _run_main(monkeypatch, capsys, json.dumps(payload), _fake_gh(_CLEAN_PR_OBJECT))
|
|
215
|
+
|
|
216
|
+
assert "PR #42" in _additional_context(stdout_text)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def test_main_should_stay_quiet_when_the_push_failed(
|
|
220
|
+
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
|
221
|
+
) -> None:
|
|
222
|
+
failed_push = _payload("git push", "Error: Exit code 1\nrejected")
|
|
223
|
+
|
|
224
|
+
assert _run_main(monkeypatch, capsys, failed_push, _fake_gh(_CLEAN_PR_OBJECT)) == ""
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def test_main_should_stay_quiet_for_a_non_trigger_command(
|
|
228
|
+
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
|
229
|
+
) -> None:
|
|
230
|
+
assert _run_main(monkeypatch, capsys, _payload("git status"), _fake_gh(_CLEAN_PR_OBJECT)) == ""
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def test_main_should_remind_to_open_a_pr_when_the_branch_has_none(
|
|
234
|
+
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
|
235
|
+
) -> None:
|
|
236
|
+
no_pr = _fake_gh(None, stderr='no pull requests found for branch "feat/x"')
|
|
237
|
+
|
|
238
|
+
context = _additional_context(_run_main(monkeypatch, capsys, _payload("git push"), no_pr))
|
|
239
|
+
|
|
240
|
+
assert "No open pull request found" in context
|
|
241
|
+
assert "gh pr create --draft" in context
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def test_main_should_stay_quiet_on_any_other_gh_failure(
|
|
245
|
+
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
|
246
|
+
) -> None:
|
|
247
|
+
gh_down = _fake_gh(None, stderr="error connecting to api.github.com")
|
|
248
|
+
|
|
249
|
+
assert _run_main(monkeypatch, capsys, _payload("git push"), gh_down) == ""
|
|
@@ -14,9 +14,11 @@ collision, and needs no change to that shared, out-of-scope test file.
|
|
|
14
14
|
Reads the tool payload from stdin once, selects the hosted hooks applicable to
|
|
15
15
|
the payload's tool name, and runs each one in-process via the shared
|
|
16
16
|
hosted-hook runner. A hosted PostToolUse hook here carries no permission
|
|
17
|
-
decision -- it is
|
|
18
|
-
|
|
19
|
-
hosted hook
|
|
17
|
+
decision -- it is an observer, not a gate -- so this dispatcher runs the whole
|
|
18
|
+
roster unconditionally and never blocks. The one thing it forwards is context:
|
|
19
|
+
a hosted hook may print a ``hookSpecificOutput.additionalContext`` string (the
|
|
20
|
+
PR done reminder does), and the dispatcher joins every such string into one
|
|
21
|
+
PostToolUse payload. A hook that prints nothing adds nothing.
|
|
20
22
|
|
|
21
23
|
A single hosted hook crash fails open: ``run_hook_capturing_output`` isolates
|
|
22
24
|
it, so it contributes nothing and does not stop the remaining hosted hooks.
|
|
@@ -27,11 +29,17 @@ imports load once into this process rather than once per hook.
|
|
|
27
29
|
|
|
28
30
|
from __future__ import annotations
|
|
29
31
|
|
|
32
|
+
import json
|
|
33
|
+
import sys
|
|
30
34
|
|
|
31
35
|
import _path_setup # noqa: F401
|
|
32
36
|
|
|
33
37
|
from hooks_constants.bash_post_call_dispatcher_constants import (
|
|
38
|
+
ADDITIONAL_CONTEXT_JOIN_SEPARATOR,
|
|
39
|
+
ADDITIONAL_CONTEXT_KEY,
|
|
34
40
|
ALL_BASH_POST_TOOL_USE_HOSTED_HOOK_ENTRIES,
|
|
41
|
+
HOOK_SPECIFIC_OUTPUT_KEY,
|
|
42
|
+
POST_TOOL_USE_HOOK_EVENT_NAME,
|
|
35
43
|
)
|
|
36
44
|
from hooks_constants.bash_pre_tool_use_dispatcher_constants import BashHostedHookEntry
|
|
37
45
|
from hooks_constants.hosted_hook_runner import (
|
|
@@ -50,15 +58,54 @@ def select_applicable_entries(tool_name: str) -> list[BashHostedHookEntry]:
|
|
|
50
58
|
]
|
|
51
59
|
|
|
52
60
|
|
|
61
|
+
def additional_context_from_hook_output(captured_stdout: str) -> str | None:
|
|
62
|
+
"""Return the additionalContext string a hosted hook printed, or None.
|
|
63
|
+
|
|
64
|
+
Empty output, non-JSON output, and JSON without a non-empty
|
|
65
|
+
``hookSpecificOutput.additionalContext`` string all return None.
|
|
66
|
+
"""
|
|
67
|
+
if not captured_stdout.strip():
|
|
68
|
+
return None
|
|
69
|
+
try:
|
|
70
|
+
hook_output = json.loads(captured_stdout)
|
|
71
|
+
except json.JSONDecodeError:
|
|
72
|
+
return None
|
|
73
|
+
if not isinstance(hook_output, dict):
|
|
74
|
+
return None
|
|
75
|
+
hook_specific_output = hook_output.get(HOOK_SPECIFIC_OUTPUT_KEY)
|
|
76
|
+
if not isinstance(hook_specific_output, dict):
|
|
77
|
+
return None
|
|
78
|
+
additional_context = hook_specific_output.get(ADDITIONAL_CONTEXT_KEY)
|
|
79
|
+
if not isinstance(additional_context, str) or not additional_context.strip():
|
|
80
|
+
return None
|
|
81
|
+
return additional_context
|
|
82
|
+
|
|
83
|
+
|
|
53
84
|
def dispatch(payload_text: str, tool_name: str) -> None:
|
|
54
|
-
"""Run every hosted hook applicable to tool_name,
|
|
85
|
+
"""Run every hosted hook applicable to tool_name, then forward their context.
|
|
55
86
|
|
|
56
87
|
Each hosted hook's crash is isolated by ``run_hook_capturing_output``, so
|
|
57
|
-
one hook failing never stops a later one in the same roster.
|
|
88
|
+
one hook failing never stops a later one in the same roster. Every
|
|
89
|
+
additionalContext string the hooks printed is joined into one PostToolUse
|
|
90
|
+
payload on stdout; when no hook printed one, nothing is written.
|
|
58
91
|
"""
|
|
92
|
+
all_additional_context: list[str] = []
|
|
59
93
|
for each_entry in select_applicable_entries(tool_name):
|
|
60
94
|
script_path = resolved_hook_script_path(each_entry.script_relative_path)
|
|
61
|
-
run_hook_capturing_output(script_path, payload_text)
|
|
95
|
+
hook_run = run_hook_capturing_output(script_path, payload_text)
|
|
96
|
+
additional_context = additional_context_from_hook_output(hook_run.captured_stdout)
|
|
97
|
+
if additional_context is not None:
|
|
98
|
+
all_additional_context.append(additional_context)
|
|
99
|
+
if not all_additional_context:
|
|
100
|
+
return
|
|
101
|
+
payload = {
|
|
102
|
+
HOOK_SPECIFIC_OUTPUT_KEY: {
|
|
103
|
+
"hookEventName": POST_TOOL_USE_HOOK_EVENT_NAME,
|
|
104
|
+
ADDITIONAL_CONTEXT_KEY: ADDITIONAL_CONTEXT_JOIN_SEPARATOR.join(all_additional_context),
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
sys.stdout.write(json.dumps(payload))
|
|
108
|
+
sys.stdout.flush()
|
|
62
109
|
|
|
63
110
|
|
|
64
111
|
def main() -> None:
|
|
@@ -30,6 +30,7 @@ from code_rules_shared import ( # noqa: E402
|
|
|
30
30
|
from hooks_constants.banned_identifiers_constants import ( # noqa: E402
|
|
31
31
|
ALL_BANNED_IDENTIFIERS,
|
|
32
32
|
ALL_BANNED_NOUN_WORDS,
|
|
33
|
+
ALL_BLAST_RADIUS_EXCEPTION_SUFFIXES,
|
|
33
34
|
BANNED_IDENTIFIER_MESSAGE_SUFFIX,
|
|
34
35
|
BANNED_IDENTIFIER_SKIP_ADVISORY,
|
|
35
36
|
BANNED_NOUN_COMPATIBILITY_COMMENT,
|
|
@@ -228,6 +229,8 @@ def _find_banned_noun_word(identifier: str) -> str | None:
|
|
|
228
229
|
identifier (e.g., ``'result'`` for ``'HolidayPeakResult'``). Returns
|
|
229
230
|
``None`` when no banned noun word is present.
|
|
230
231
|
"""
|
|
232
|
+
if identifier.endswith(ALL_BLAST_RADIUS_EXCEPTION_SUFFIXES):
|
|
233
|
+
return None
|
|
231
234
|
for each_word in _identifier_word_parts(identifier):
|
|
232
235
|
if each_word in ALL_BANNED_NOUN_WORDS:
|
|
233
236
|
return each_word
|
|
@@ -215,12 +215,27 @@ def _collect_referenced_identifiers(source: str) -> set[str]:
|
|
|
215
215
|
return all_referenced_identifiers
|
|
216
216
|
|
|
217
217
|
|
|
218
|
+
def _sibling_test_paths(stem_test_path: Path) -> list[Path]:
|
|
219
|
+
"""Return the capped, sorted test files beside the stem test, stem excluded."""
|
|
220
|
+
suite_directory = stem_test_path.parent
|
|
221
|
+
stem_path_key = os.path.normcase(str(stem_test_path.resolve()))
|
|
222
|
+
all_sibling_paths: list[Path] = []
|
|
223
|
+
for each_glob in ALL_TEST_FILENAME_GLOBS:
|
|
224
|
+
all_sibling_paths.extend(
|
|
225
|
+
each_path
|
|
226
|
+
for each_path in sorted(suite_directory.glob(each_glob))
|
|
227
|
+
if os.path.normcase(str(each_path.resolve())) != stem_path_key
|
|
228
|
+
)
|
|
229
|
+
return all_sibling_paths[:MAX_TEST_FILES_SCANNED]
|
|
230
|
+
|
|
231
|
+
|
|
218
232
|
def _suite_referenced_identifiers(stem_test_path: Path) -> set[str]:
|
|
219
233
|
"""Return identifiers referenced across every test file beside the stem test.
|
|
220
234
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
each one references.
|
|
235
|
+
Reads the stem-matched test file first, then scans every other ``test_*.py``
|
|
236
|
+
and ``*_test.py`` file in its directory, bounded by the scan cap, and unions
|
|
237
|
+
the identifiers each one references. The stem test always counts, so a
|
|
238
|
+
directory with more test files than the cap cannot hide it.
|
|
224
239
|
|
|
225
240
|
Args:
|
|
226
241
|
stem_test_path: The stem-matched test file whose directory is scanned.
|
|
@@ -228,12 +243,9 @@ def _suite_referenced_identifiers(stem_test_path: Path) -> set[str]:
|
|
|
228
243
|
Returns:
|
|
229
244
|
The union of referenced identifiers across the scanned test files.
|
|
230
245
|
"""
|
|
231
|
-
|
|
232
|
-
all_test_file_paths: list[Path] = []
|
|
233
|
-
for each_glob in ALL_TEST_FILENAME_GLOBS:
|
|
234
|
-
all_test_file_paths.extend(sorted(suite_directory.glob(each_glob)))
|
|
246
|
+
all_test_file_paths = [stem_test_path, *_sibling_test_paths(stem_test_path)]
|
|
235
247
|
all_referenced_identifiers: set[str] = set()
|
|
236
|
-
for each_test_file_path in all_test_file_paths
|
|
248
|
+
for each_test_file_path in all_test_file_paths:
|
|
237
249
|
try:
|
|
238
250
|
test_source = each_test_file_path.read_text(encoding="utf-8")
|
|
239
251
|
except (OSError, UnicodeDecodeError):
|
|
@@ -364,16 +376,12 @@ def _production_stem_from_test_filename(test_filename: str) -> str | None:
|
|
|
364
376
|
if test_filename.startswith(STEM_TEST_FILENAME_PREFIX) and test_filename.endswith(
|
|
365
377
|
PYTHON_SOURCE_SUFFIX
|
|
366
378
|
):
|
|
367
|
-
production_stem = test_filename[
|
|
368
|
-
len(STEM_TEST_FILENAME_PREFIX) : -len(PYTHON_SOURCE_SUFFIX)
|
|
369
|
-
]
|
|
379
|
+
production_stem = test_filename[len(STEM_TEST_FILENAME_PREFIX) : -len(PYTHON_SOURCE_SUFFIX)]
|
|
370
380
|
return production_stem or None
|
|
371
381
|
return None
|
|
372
382
|
|
|
373
383
|
|
|
374
|
-
def _paired_production_module_path(
|
|
375
|
-
test_path: Path, production_stem: str
|
|
376
|
-
) -> Path | None:
|
|
384
|
+
def _paired_production_module_path(test_path: Path, production_stem: str) -> Path | None:
|
|
377
385
|
"""Return the production module a stem-matched test file pairs with, or None.
|
|
378
386
|
|
|
379
387
|
Mirrors ``_stem_matched_test_path`` in reverse for the two common layouts: a
|
|
@@ -515,4 +523,3 @@ def check_test_file_omits_module_public_function(
|
|
|
515
523
|
if len(all_omission_violations) >= MAX_PAIRED_TEST_COVERAGE_ISSUES:
|
|
516
524
|
break
|
|
517
525
|
return all_omission_violations
|
|
518
|
-
|
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
"""Library helper
|
|
2
|
-
|
|
3
|
-
``
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
repository a commit's unstaged-edit check targets. This module carries no
|
|
7
|
-
hook entry point of its own; the native ``git-hooks/pre_commit.py`` runs
|
|
8
|
-
the real commit-time CODE_RULES enforcement.
|
|
1
|
+
"""Library helper that resolves a directory to its Git repository root.
|
|
2
|
+
|
|
3
|
+
``pii_payload_scan.py`` imports ``resolve_repository_root`` for staged scans.
|
|
4
|
+
This module has no hook entry point. Native ``git-hooks/pre_commit.py`` shows
|
|
5
|
+
the local verification advisory and does not enforce CODE_RULES.
|
|
9
6
|
"""
|
|
10
7
|
|
|
11
8
|
import subprocess
|
|
@@ -1,61 +1,16 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Tests for the retired Bash and PowerShell blocking roster."""
|
|
2
2
|
|
|
3
|
-
Standalone registration starts one Python process per hosted hook. The
|
|
4
|
-
dispatcher replaces that chain with a single process that hosts every hook
|
|
5
|
-
in-process. This test pins the before/after interpreter-start counts so a
|
|
6
|
-
regression that re-splits the chain fails CI.
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
from __future__ import annotations
|
|
10
|
-
|
|
11
|
-
import json
|
|
12
|
-
import subprocess
|
|
13
3
|
import sys
|
|
14
4
|
from pathlib import Path
|
|
15
5
|
|
|
16
|
-
|
|
17
|
-
if
|
|
18
|
-
sys.path.insert(0,
|
|
6
|
+
_HOOKS_ROOT = Path(__file__).resolve().parent.parent
|
|
7
|
+
if str(_HOOKS_ROOT) not in sys.path:
|
|
8
|
+
sys.path.insert(0, str(_HOOKS_ROOT))
|
|
19
9
|
|
|
20
|
-
from hooks_constants.bash_pre_tool_use_dispatcher_constants import (
|
|
10
|
+
from hooks_constants.bash_pre_tool_use_dispatcher_constants import (
|
|
21
11
|
ALL_BASH_HOSTED_HOOK_ENTRIES,
|
|
22
|
-
BASH_TOOL_NAME,
|
|
23
12
|
)
|
|
24
13
|
|
|
25
|
-
_BLOCKING_DIR = Path(__file__).resolve().parent
|
|
26
|
-
_HOOKS_ROOT = _BLOCKING_DIR.parent
|
|
27
|
-
_DISPATCHER_SCRIPT = str(_BLOCKING_DIR / "bash_pre_tool_use_dispatcher.py")
|
|
28
|
-
|
|
29
|
-
STANDALONE_INTERPRETER_STARTS_PER_BASH_CALL = len(ALL_BASH_HOSTED_HOOK_ENTRIES)
|
|
30
|
-
DISPATCHER_INTERPRETER_STARTS_PER_BASH_CALL = 1
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def test_roster_is_large_enough_that_consolidation_saves_starts() -> None:
|
|
34
|
-
"""Consolidation only pays off when the chain hosts more than one hook."""
|
|
35
|
-
assert STANDALONE_INTERPRETER_STARTS_PER_BASH_CALL >= 10
|
|
36
|
-
assert DISPATCHER_INTERPRETER_STARTS_PER_BASH_CALL == 1
|
|
37
|
-
assert STANDALONE_INTERPRETER_STARTS_PER_BASH_CALL > DISPATCHER_INTERPRETER_STARTS_PER_BASH_CALL
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def test_dispatcher_handles_a_bash_call_in_one_process() -> None:
|
|
41
|
-
"""One subprocess run of the dispatcher covers the full Bash hosted chain."""
|
|
42
|
-
payload_text = json.dumps(
|
|
43
|
-
{"tool_name": BASH_TOOL_NAME, "tool_input": {"command": "echo interpreter-start-probe"}}
|
|
44
|
-
)
|
|
45
|
-
completed = subprocess.run(
|
|
46
|
-
[sys.executable, _DISPATCHER_SCRIPT],
|
|
47
|
-
check=False,
|
|
48
|
-
input=payload_text,
|
|
49
|
-
capture_output=True,
|
|
50
|
-
text=True,
|
|
51
|
-
encoding="utf-8",
|
|
52
|
-
)
|
|
53
|
-
assert completed.returncode == 0
|
|
54
|
-
assert DISPATCHER_INTERPRETER_STARTS_PER_BASH_CALL == 1
|
|
55
|
-
|
|
56
14
|
|
|
57
|
-
def
|
|
58
|
-
|
|
59
|
-
for each_entry in ALL_BASH_HOSTED_HOOK_ENTRIES:
|
|
60
|
-
assert (_HOOKS_ROOT / each_entry.script_relative_path).is_file()
|
|
61
|
-
assert len(ALL_BASH_HOSTED_HOOK_ENTRIES) == STANDALONE_INTERPRETER_STARTS_PER_BASH_CALL
|
|
15
|
+
def test_retired_bash_roster_has_no_effective_blocking_hooks() -> None:
|
|
16
|
+
assert ALL_BASH_HOSTED_HOOK_ENTRIES == ()
|
|
@@ -15,16 +15,26 @@ from pathlib import Path
|
|
|
15
15
|
|
|
16
16
|
import pytest
|
|
17
17
|
|
|
18
|
-
from bash_post_call_dispatcher import
|
|
18
|
+
from bash_post_call_dispatcher import (
|
|
19
|
+
additional_context_from_hook_output,
|
|
20
|
+
dispatch,
|
|
21
|
+
main,
|
|
22
|
+
select_applicable_entries,
|
|
23
|
+
)
|
|
19
24
|
from hooks_constants.bash_pre_tool_use_dispatcher_constants import BASH_TOOL_NAME
|
|
20
25
|
from hooks_constants.hosted_hook_runner import HostedHookRun
|
|
21
26
|
from tdd_enforcer_parts import content_hash_store
|
|
22
27
|
|
|
28
|
+
_ALL_EXPECTED_ROSTER_PATHS = [
|
|
29
|
+
"observability/test_failure_recorder.py",
|
|
30
|
+
"advisory/pr_done_reminder.py",
|
|
31
|
+
]
|
|
23
32
|
|
|
24
|
-
|
|
33
|
+
|
|
34
|
+
def test_select_applicable_entries_returns_the_hosted_hooks_for_bash() -> None:
|
|
25
35
|
all_entries = select_applicable_entries(BASH_TOOL_NAME)
|
|
26
36
|
all_script_paths = [each_entry.script_relative_path for each_entry in all_entries]
|
|
27
|
-
assert all_script_paths ==
|
|
37
|
+
assert all_script_paths == _ALL_EXPECTED_ROSTER_PATHS
|
|
28
38
|
|
|
29
39
|
|
|
30
40
|
def test_select_applicable_entries_returns_none_for_a_write_tool() -> None:
|
|
@@ -45,7 +55,58 @@ def test_dispatch_runs_every_hosted_hook_in_roster_order(
|
|
|
45
55
|
|
|
46
56
|
dispatch('{"tool_name": "Bash"}', BASH_TOOL_NAME)
|
|
47
57
|
|
|
48
|
-
assert all_call_paths == [
|
|
58
|
+
assert all_call_paths == [Path(each_path).name for each_path in _ALL_EXPECTED_ROSTER_PATHS]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_dispatch_forwards_hosted_hook_additional_context_as_one_payload(
|
|
62
|
+
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
|
63
|
+
) -> None:
|
|
64
|
+
context_by_hook = {
|
|
65
|
+
"test_failure_recorder.py": "",
|
|
66
|
+
"pr_done_reminder.py": json.dumps(
|
|
67
|
+
{"hookSpecificOutput": {"hookEventName": "PostToolUse", "additionalContext": "PR #42"}}
|
|
68
|
+
),
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
def _fake_run_hook(script_path: str, payload_text: str) -> HostedHookRun:
|
|
72
|
+
del payload_text
|
|
73
|
+
return HostedHookRun(
|
|
74
|
+
captured_stdout=context_by_hook[Path(script_path).name], did_crash=False
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
monkeypatch.setattr("bash_post_call_dispatcher.run_hook_capturing_output", _fake_run_hook)
|
|
78
|
+
|
|
79
|
+
dispatch('{"tool_name": "Bash"}', BASH_TOOL_NAME)
|
|
80
|
+
|
|
81
|
+
emitted = json.loads(capsys.readouterr().out)
|
|
82
|
+
assert emitted == {
|
|
83
|
+
"hookSpecificOutput": {"hookEventName": "PostToolUse", "additionalContext": "PR #42"}
|
|
84
|
+
}
|
|
85
|
+
assert "decision" not in emitted
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def test_dispatch_writes_nothing_when_no_hosted_hook_printed_context(
|
|
89
|
+
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
|
90
|
+
) -> None:
|
|
91
|
+
def _fake_run_hook(script_path: str, payload_text: str) -> HostedHookRun:
|
|
92
|
+
del script_path, payload_text
|
|
93
|
+
return HostedHookRun(captured_stdout="", did_crash=False)
|
|
94
|
+
|
|
95
|
+
monkeypatch.setattr("bash_post_call_dispatcher.run_hook_capturing_output", _fake_run_hook)
|
|
96
|
+
|
|
97
|
+
dispatch('{"tool_name": "Bash"}', BASH_TOOL_NAME)
|
|
98
|
+
|
|
99
|
+
assert capsys.readouterr().out == ""
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@pytest.mark.parametrize(
|
|
103
|
+
"captured_stdout",
|
|
104
|
+
["", " ", "not json", "[]", '{"decision": "block"}', '{"hookSpecificOutput": {}}'],
|
|
105
|
+
)
|
|
106
|
+
def test_additional_context_from_hook_output_ignores_non_context_output(
|
|
107
|
+
captured_stdout: str,
|
|
108
|
+
) -> None:
|
|
109
|
+
assert additional_context_from_hook_output(captured_stdout) is None
|
|
49
110
|
|
|
50
111
|
|
|
51
112
|
def test_main_exits_zero_and_writes_nothing_to_stdout(
|