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
|
@@ -1,14 +1,8 @@
|
|
|
1
1
|
"""Golden differential and failure-mode tests for the PreToolUse dispatcher.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
dispatcher on the same payload and asserts equal decision and equal message
|
|
7
|
-
union.
|
|
8
|
-
|
|
9
|
-
The failure-mode tests cover one row each from spec/failure-modes.md:
|
|
10
|
-
early-exit-then-later-deny, multi-deny, context-survival, blocking-hook crash,
|
|
11
|
-
fail-open malformed input.
|
|
3
|
+
The live roster carries only nonblocking edit advisors. These tests keep the
|
|
4
|
+
aggregator contract and prove retired policy registrations fail open while
|
|
5
|
+
explicit standalone checks remain callable.
|
|
12
6
|
"""
|
|
13
7
|
|
|
14
8
|
from __future__ import annotations
|
|
@@ -31,7 +25,6 @@ if str(_HOOKS_ROOT) not in sys.path:
|
|
|
31
25
|
|
|
32
26
|
from hooks_constants.pre_tool_use_dispatcher_constants import ( # noqa: E402, I001
|
|
33
27
|
ALL_HOSTED_HOOK_ENTRIES,
|
|
34
|
-
ALL_IMMEDIATE_HARM_SCRIPT_PATHS,
|
|
35
28
|
APPLY_PATCH_TOOL_NAME,
|
|
36
29
|
BLOCKING_CRASH_DENY_REASON,
|
|
37
30
|
BLOCKING_CRASH_EXIT_CODE,
|
|
@@ -407,67 +400,6 @@ def test_write_existing_file_blocker_allows_multi_edit_to_an_existing_path(
|
|
|
407
400
|
_assert_dispatcher_matches_individual_hooks(payload_text, MULTI_EDIT_TOOL_NAME)
|
|
408
401
|
|
|
409
402
|
|
|
410
|
-
def test_context_survives_alongside_deny_reason(
|
|
411
|
-
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
412
|
-
) -> None:
|
|
413
|
-
"""A non-denying hook's additional context survives in the dispatcher output.
|
|
414
|
-
|
|
415
|
-
This tests that hooks whose output is additional-context (not a deny) still
|
|
416
|
-
have their output preserved when another hook denies.
|
|
417
|
-
"""
|
|
418
|
-
monkeypatch.setenv("HOME", str(tmp_path))
|
|
419
|
-
monkeypatch.setenv("USERPROFILE", str(tmp_path))
|
|
420
|
-
sensitive_path = str(Path.home() / ".env")
|
|
421
|
-
payload_text = _write_payload(sensitive_path, "SECRET=abc")
|
|
422
|
-
dispatcher_result = _run_dispatcher(payload_text)
|
|
423
|
-
is_deny, _reason = _parse_hook_decision(dispatcher_result)
|
|
424
|
-
assert is_deny, (
|
|
425
|
-
"sensitive_file_protector should deny a write to .env — "
|
|
426
|
-
"if it did not, check whether the path is on the sensitive list"
|
|
427
|
-
)
|
|
428
|
-
assert dispatcher_result.stdout.strip(), "Dispatcher must emit output when denying"
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
def test_all_deny_reasons_present_when_multiple_hooks_deny() -> None:
|
|
432
|
-
"""When two or more hooks deny, all their reasons appear in the dispatcher output.
|
|
433
|
-
|
|
434
|
-
Uses a Write to an existing markdown path with a historical phrase
|
|
435
|
-
("previously") so write_existing_file_blocker and state_description_blocker
|
|
436
|
-
both deny.
|
|
437
|
-
"""
|
|
438
|
-
existing_markdown_path = str(Path(__file__).resolve().parent / "CLAUDE.md")
|
|
439
|
-
multi_deny_content = (
|
|
440
|
-
"# Guide\n\n"
|
|
441
|
-
"Previously the system used a different mechanism.\n"
|
|
442
|
-
)
|
|
443
|
-
payload_text = _write_payload(existing_markdown_path, multi_deny_content)
|
|
444
|
-
_assert_dispatcher_matches_individual_hooks(payload_text, WRITE_TOOL_NAME)
|
|
445
|
-
|
|
446
|
-
dispatcher_result = _run_dispatcher(payload_text)
|
|
447
|
-
dispatcher_is_deny, dispatcher_reason = _parse_hook_decision(dispatcher_result)
|
|
448
|
-
assert dispatcher_is_deny, "Dispatcher must deny when any hook denies"
|
|
449
|
-
|
|
450
|
-
applicable_entries = _applicable_entries_for_tool(WRITE_TOOL_NAME)
|
|
451
|
-
all_expected_deny_reasons: list[str] = []
|
|
452
|
-
for each_entry in applicable_entries:
|
|
453
|
-
completed_process = _run_hook_subprocess(each_entry.script_relative_path, payload_text)
|
|
454
|
-
is_deny, reason_text = _parse_hook_decision(completed_process)
|
|
455
|
-
if is_deny and reason_text:
|
|
456
|
-
all_expected_deny_reasons.append(reason_text)
|
|
457
|
-
|
|
458
|
-
assert len(all_expected_deny_reasons) >= 2, (
|
|
459
|
-
f"Test payload must trip at least two hooks — got {len(all_expected_deny_reasons)}. "
|
|
460
|
-
"Check that an existing path triggers write_existing_file_blocker and "
|
|
461
|
-
"'previously' triggers state_description_blocker on a Write."
|
|
462
|
-
)
|
|
463
|
-
for each_reason in all_expected_deny_reasons:
|
|
464
|
-
assert each_reason in dispatcher_reason, (
|
|
465
|
-
f"Missing deny reason in dispatcher output.\n"
|
|
466
|
-
f"Expected reason: {each_reason!r}\n"
|
|
467
|
-
f"Dispatcher reason: {dispatcher_reason!r}"
|
|
468
|
-
)
|
|
469
|
-
|
|
470
|
-
|
|
471
403
|
def test_aggregate_exit_code_two_signals_deny() -> None:
|
|
472
404
|
"""A HostedHookResult with exit_code 2 and did_crash False signals deny.
|
|
473
405
|
|
|
@@ -686,87 +618,6 @@ def test_emit_allow_preserves_system_message_and_additional_context(
|
|
|
686
618
|
assert parsed["systemMessage"] == "system-a\nsystem-b"
|
|
687
619
|
|
|
688
620
|
|
|
689
|
-
def test_later_hook_deny_survives_early_hook_exit() -> None:
|
|
690
|
-
"""Dispatcher denies even when an earlier hook exits cleanly before a later hook denies.
|
|
691
|
-
|
|
692
|
-
state_description_blocker denies a markdown write with a historical phrase.
|
|
693
|
-
Earlier hooks exit 0 (allow). The dispatcher must catch each hook's
|
|
694
|
-
SystemExit and continue, so the later denial reaches the aggregator.
|
|
695
|
-
"""
|
|
696
|
-
payload_text = _write_payload(
|
|
697
|
-
_MARKDOWN_FILE_PATH,
|
|
698
|
-
"# Doc\n\nPreviously this section used a different mechanism.\n",
|
|
699
|
-
)
|
|
700
|
-
_assert_dispatcher_matches_individual_hooks(payload_text, WRITE_TOOL_NAME)
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
def test_dispatcher_write_applies_both_groups() -> None:
|
|
704
|
-
"""Write tool triggers both Group A and Group B hooks through the dispatcher.
|
|
705
|
-
|
|
706
|
-
Verifies that the set of applicable entries for Write includes entries from
|
|
707
|
-
both ALL_WRITE_AND_EDIT_TOOL_NAMES (Group A) and ALL_WRITE_EDIT_MULTI_EDIT_TOOL_NAMES
|
|
708
|
-
(Group B) in the constants.
|
|
709
|
-
"""
|
|
710
|
-
all_write_entries = _applicable_entries_for_tool(WRITE_TOOL_NAME)
|
|
711
|
-
all_write_script_paths = {each_entry.script_relative_path for each_entry in all_write_entries}
|
|
712
|
-
assert "blocking/write_existing_file_blocker.py" in all_write_script_paths, (
|
|
713
|
-
"write_existing_file_blocker (Group A) must be in Write applicable set"
|
|
714
|
-
)
|
|
715
|
-
assert len(all_write_entries) == 18, (
|
|
716
|
-
f"Write tool must apply to all 18 hosted hooks, got {len(all_write_entries)}"
|
|
717
|
-
)
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
def test_dispatcher_edit_applies_both_groups() -> None:
|
|
721
|
-
"""Edit triggers Group A, Group B, and the Edit-scoped entry through the
|
|
722
|
-
dispatcher.
|
|
723
|
-
"""
|
|
724
|
-
all_edit_entries = _applicable_entries_for_tool(EDIT_TOOL_NAME)
|
|
725
|
-
all_edit_script_paths = {each_entry.script_relative_path for each_entry in all_edit_entries}
|
|
726
|
-
assert "blocking/stale_comment_reference_blocker.py" not in all_edit_script_paths
|
|
727
|
-
assert "advisory/refactor_guard.py" in all_edit_script_paths, (
|
|
728
|
-
"refactor_guard is Edit-scoped and hosted, so it belongs in the Edit applicable set"
|
|
729
|
-
)
|
|
730
|
-
assert len(all_edit_entries) == 20, (
|
|
731
|
-
f"expected 20 Edit entries, got {len(all_edit_entries)}"
|
|
732
|
-
)
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
def test_dispatcher_multi_edit_reaches_the_sensitive_protector() -> None:
|
|
736
|
-
"""A MultiEdit onto a sensitive path meets the same gate a Write does.
|
|
737
|
-
|
|
738
|
-
test_edit_and_multi_edit_applicable_sets_are_equal in the constants suite
|
|
739
|
-
holds the Edit and MultiEdit rosters equal, so this pins the one hook whose
|
|
740
|
-
absence would let a secret through a MultiEdit.
|
|
741
|
-
"""
|
|
742
|
-
all_multi_edit_entries = _applicable_entries_for_tool(MULTI_EDIT_TOOL_NAME)
|
|
743
|
-
all_multi_edit_script_paths = {
|
|
744
|
-
each_entry.script_relative_path for each_entry in all_multi_edit_entries
|
|
745
|
-
}
|
|
746
|
-
assert "blocking/sensitive_file_protector.py" in all_multi_edit_script_paths, (
|
|
747
|
-
"sensitive_file_protector belongs in the MultiEdit applicable set"
|
|
748
|
-
)
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
def test_dispatcher_apply_patch_applies_immediate_harm_hooks() -> None:
|
|
752
|
-
"""apply_patch applies to exactly the immediate-harm roster.
|
|
753
|
-
|
|
754
|
-
apply_patch reaches only the immediate-harm-plus-TDD roster the constants
|
|
755
|
-
module names, not the full Write/Edit/MultiEdit lint surface.
|
|
756
|
-
"""
|
|
757
|
-
all_apply_patch_entries = _applicable_entries_for_tool(APPLY_PATCH_TOOL_NAME)
|
|
758
|
-
all_apply_patch_script_paths = {
|
|
759
|
-
each_entry.script_relative_path for each_entry in all_apply_patch_entries
|
|
760
|
-
}
|
|
761
|
-
assert "blocking/sensitive_file_protector.py" in all_apply_patch_script_paths, (
|
|
762
|
-
"sensitive_file_protector belongs in the apply_patch applicable set"
|
|
763
|
-
)
|
|
764
|
-
assert all_apply_patch_script_paths == set(ALL_IMMEDIATE_HARM_SCRIPT_PATHS), (
|
|
765
|
-
"apply_patch must apply to exactly the immediate-harm roster, got: "
|
|
766
|
-
f"{sorted(all_apply_patch_script_paths)}"
|
|
767
|
-
)
|
|
768
|
-
|
|
769
|
-
|
|
770
621
|
def _assert_roster_names_no_run_all_validators_entry() -> None:
|
|
771
622
|
"""Assert no hosted-roster ``script_relative_path`` names run_all_validators."""
|
|
772
623
|
all_roster_script_paths = {
|
|
@@ -829,40 +680,6 @@ def _orphan_claude_md_payload(tmp_path: Path) -> str:
|
|
|
829
680
|
return _write_payload(claude_md_path, orphan_table)
|
|
830
681
|
|
|
831
682
|
|
|
832
|
-
def test_runpy_deny_preserves_additional_context_and_suppress_output(tmp_path: Path) -> None:
|
|
833
|
-
"""A runpy-hosted deny carries its additionalContext and suppressOutput through the dispatcher.
|
|
834
|
-
|
|
835
|
-
claude_md_orphan_file_blocker emits hookSpecificOutput.additionalContext and a
|
|
836
|
-
top-level suppressOutput flag on a deny. The dispatcher must preserve both so
|
|
837
|
-
the dispatched denial matches the standalone hook's deny shape.
|
|
838
|
-
|
|
839
|
-
Args:
|
|
840
|
-
tmp_path: Pytest temp directory hosting the throwaway CLAUDE.md.
|
|
841
|
-
"""
|
|
842
|
-
payload_text = _orphan_claude_md_payload(tmp_path)
|
|
843
|
-
|
|
844
|
-
standalone_result = _run_hook_subprocess(
|
|
845
|
-
"blocking/claude_md_orphan_file_blocker.py", payload_text
|
|
846
|
-
)
|
|
847
|
-
standalone_payload = json.loads(standalone_result.stdout.strip())
|
|
848
|
-
standalone_hook_specific = standalone_payload["hookSpecificOutput"]
|
|
849
|
-
expected_additional_context = standalone_hook_specific["additionalContext"]
|
|
850
|
-
|
|
851
|
-
dispatcher_result = _run_dispatcher(payload_text)
|
|
852
|
-
dispatcher_payload = json.loads(dispatcher_result.stdout.strip())
|
|
853
|
-
dispatcher_hook_specific = dispatcher_payload.get("hookSpecificOutput", {})
|
|
854
|
-
assert isinstance(dispatcher_hook_specific, dict)
|
|
855
|
-
assert dispatcher_hook_specific.get("additionalContext") == expected_additional_context, (
|
|
856
|
-
"Dispatcher must preserve the runpy hook's additionalContext.\n"
|
|
857
|
-
f"Expected: {expected_additional_context!r}\n"
|
|
858
|
-
f"Got: {dispatcher_hook_specific.get('additionalContext')!r}"
|
|
859
|
-
)
|
|
860
|
-
assert dispatcher_payload.get("suppressOutput") is True, (
|
|
861
|
-
"Dispatcher must preserve the runpy hook's suppressOutput flag.\n"
|
|
862
|
-
f"Got: {dispatcher_payload.get('suppressOutput')!r}"
|
|
863
|
-
)
|
|
864
|
-
|
|
865
|
-
|
|
866
683
|
def _parse_hook_allow(completed_process: subprocess.CompletedProcess[str]) -> bool:
|
|
867
684
|
"""Parse one hook's subprocess result for an explicit permissionDecision allow.
|
|
868
685
|
|
|
@@ -885,45 +702,19 @@ def _parse_hook_allow(completed_process: subprocess.CompletedProcess[str]) -> bo
|
|
|
885
702
|
return hook_specific.get("permissionDecision") == "allow"
|
|
886
703
|
|
|
887
704
|
|
|
888
|
-
def
|
|
889
|
-
"""
|
|
890
|
-
|
|
891
|
-
tdd_enforcer writes an explicit allow payload for a constants-only Python
|
|
892
|
-
Write, which auto-approves the write standalone. Run against the dispatcher,
|
|
893
|
-
the same payload must produce an explicit allow decision identical to the
|
|
894
|
-
standalone tdd_enforcer output, rather than a silent fall-back to the default
|
|
895
|
-
permission flow.
|
|
896
|
-
|
|
897
|
-
Args:
|
|
898
|
-
tmp_path: Pytest temp directory hosting the fresh config target path.
|
|
899
|
-
"""
|
|
705
|
+
def test_retired_tdd_hook_cannot_auto_approve_the_write(tmp_path: Path) -> None:
|
|
706
|
+
"""Pairing belongs to staged lint; its edit-time auto-approval is absent."""
|
|
900
707
|
config_target_path = str(tmp_path / "config" / "timing.py")
|
|
901
708
|
constants_only_content = (
|
|
902
709
|
'"""Timing constants."""\n\nMAXIMUM_RETRIES = 3\nRETRY_DELAY_SECONDS = 5\n'
|
|
903
710
|
)
|
|
904
711
|
payload_text = _write_payload(config_target_path, constants_only_content)
|
|
905
|
-
|
|
906
712
|
standalone_result = _run_hook_subprocess("blocking/tdd_enforcer.py", payload_text)
|
|
907
|
-
assert _parse_hook_allow(standalone_result)
|
|
908
|
-
"tdd_enforcer must emit an explicit allow for a constants-only Python Write — "
|
|
909
|
-
"if it does not, this fixture no longer exercises the allow branch"
|
|
910
|
-
)
|
|
911
|
-
|
|
713
|
+
assert _parse_hook_allow(standalone_result)
|
|
912
714
|
dispatcher_result = _run_dispatcher(payload_text)
|
|
913
|
-
dispatcher_is_deny,
|
|
914
|
-
assert not dispatcher_is_deny,
|
|
915
|
-
assert _parse_hook_allow(dispatcher_result)
|
|
916
|
-
"dispatcher must re-emit an explicit allow when a hosted hook allows explicitly "
|
|
917
|
-
"and no hook denies, matching the standalone tdd_enforcer behavior — "
|
|
918
|
-
f"got stdout {dispatcher_result.stdout.strip()!r}"
|
|
919
|
-
)
|
|
920
|
-
dispatcher_payload = json.loads(dispatcher_result.stdout.strip())
|
|
921
|
-
standalone_payload = json.loads(standalone_result.stdout.strip())
|
|
922
|
-
assert dispatcher_payload == standalone_payload, (
|
|
923
|
-
"dispatcher allow payload must match the standalone tdd_enforcer allow payload.\n"
|
|
924
|
-
f"Standalone: {standalone_payload!r}\n"
|
|
925
|
-
f"Dispatcher: {dispatcher_payload!r}"
|
|
926
|
-
)
|
|
715
|
+
dispatcher_is_deny, reason = _parse_hook_decision(dispatcher_result)
|
|
716
|
+
assert not dispatcher_is_deny, reason
|
|
717
|
+
assert not _parse_hook_allow(dispatcher_result)
|
|
927
718
|
|
|
928
719
|
|
|
929
720
|
def test_runpy_hosted_hook_sees_its_own_argv_not_the_dispatchers(tmp_path: Path) -> None:
|
|
@@ -1007,13 +798,7 @@ def _dispatcher_matchers(all_pre_tool_use_groups: list[dict]) -> list[str]:
|
|
|
1007
798
|
|
|
1008
799
|
|
|
1009
800
|
def test_apply_patch_has_no_standalone_code_rules_enforcer_hooks_json_entry() -> None:
|
|
1010
|
-
"""
|
|
1011
|
-
|
|
1012
|
-
hooks.json once registered a standalone apply_patch to code_rules_enforcer.py
|
|
1013
|
-
PreToolUse command. That entry is now folded into the dispatcher's own
|
|
1014
|
-
Write|Edit|MultiEdit|apply_patch matcher, so no separate command may still
|
|
1015
|
-
spawn code_rules_enforcer.py for apply_patch on its own.
|
|
1016
|
-
"""
|
|
801
|
+
"""The retired code-rules hook has no direct apply_patch registration."""
|
|
1017
802
|
hooks_json_text = (_HOOKS_ROOT / "hooks.json").read_text(encoding="utf-8")
|
|
1018
803
|
all_pre_tool_use_groups = json.loads(hooks_json_text)["hooks"]["PreToolUse"]
|
|
1019
804
|
|
|
@@ -1048,127 +833,43 @@ def _codex_add_patch(relative_file_path: str, file_body: str) -> str:
|
|
|
1048
833
|
)
|
|
1049
834
|
|
|
1050
835
|
|
|
1051
|
-
def test_dispatcher_denies_apply_patch_add_with_hardcoded_secret(
|
|
1052
|
-
tmp_path: Path,
|
|
1053
|
-
init_bare_git_repo: Callable[[Path], None],
|
|
1054
|
-
synthetic_github_token: str,
|
|
1055
|
-
) -> None:
|
|
1056
|
-
"""The dispatcher denies an apply_patch "add" that writes a hardcoded token.
|
|
1057
|
-
|
|
1058
|
-
Proves Done-when scenario 1 (hardcoded secret/PII denied) reaches apply_patch
|
|
1059
|
-
through the real dispatcher subprocess, not only the standalone
|
|
1060
|
-
pii_prevention_blocker test.
|
|
1061
|
-
"""
|
|
1062
|
-
repository_root = tmp_path / "repo"
|
|
1063
|
-
init_bare_git_repo(repository_root)
|
|
1064
|
-
patch_command = _codex_add_patch("leaked.py", f"token is {synthetic_github_token}\n")
|
|
1065
|
-
payload_text = _apply_patch_payload(str(repository_root), patch_command)
|
|
1066
|
-
|
|
1067
|
-
dispatcher_result = _run_dispatcher(payload_text)
|
|
1068
|
-
is_deny, reason_text = _parse_hook_decision(dispatcher_result)
|
|
1069
|
-
assert is_deny, (
|
|
1070
|
-
"dispatcher must deny an apply_patch add carrying a hardcoded secret, "
|
|
1071
|
-
f"got stdout {dispatcher_result.stdout.strip()!r}"
|
|
1072
|
-
)
|
|
1073
|
-
assert reason_text, "a deny must carry a non-empty reason"
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
def test_dispatcher_denies_apply_patch_targeting_a_sensitive_path(tmp_path: Path) -> None:
|
|
1077
|
-
"""The dispatcher denies an apply_patch "add" that targets a sensitive file.
|
|
1078
|
-
|
|
1079
|
-
Proves Done-when scenario 2 (sensitive path denied) reaches apply_patch
|
|
1080
|
-
through the real dispatcher subprocess.
|
|
1081
|
-
"""
|
|
1082
|
-
patch_command = _codex_add_patch(".env", "SECRET_KEY=irrelevant\n")
|
|
1083
|
-
payload_text = _apply_patch_payload(str(tmp_path), patch_command)
|
|
1084
|
-
|
|
1085
|
-
dispatcher_result = _run_dispatcher(payload_text)
|
|
1086
|
-
is_deny, reason_text = _parse_hook_decision(dispatcher_result)
|
|
1087
|
-
assert is_deny, (
|
|
1088
|
-
"dispatcher must deny an apply_patch add targeting a sensitive path, "
|
|
1089
|
-
f"got stdout {dispatcher_result.stdout.strip()!r}"
|
|
1090
|
-
)
|
|
1091
|
-
assert reason_text, "a deny must carry a non-empty reason"
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
def test_dispatcher_denies_apply_patch_add_onto_an_existing_path(tmp_path: Path) -> None:
|
|
1095
|
-
"""The dispatcher denies an apply_patch "add" that targets a path already on disk.
|
|
1096
|
-
|
|
1097
|
-
Proves Done-when scenario 3 (overwrite-without-reading denied) reaches
|
|
1098
|
-
apply_patch: a Codex "add" operation onto an existing path is caught by
|
|
1099
|
-
code_rules_enforcer's codex-patch reading, blocking/codex_apply_patch.py's
|
|
1100
|
-
_codex_read_patch_file, which raises CodexPatchError for an add already on
|
|
1101
|
-
disk, reported through the dispatcher's aggregate.
|
|
1102
|
-
"""
|
|
1103
|
-
existing_target = tmp_path / "already_here.py"
|
|
1104
|
-
existing_target.write_text("value = 1\n", encoding="utf-8")
|
|
1105
|
-
patch_command = _codex_add_patch("already_here.py", "value = 2\n")
|
|
1106
|
-
payload_text = _apply_patch_payload(str(tmp_path), patch_command)
|
|
1107
|
-
|
|
1108
|
-
dispatcher_result = _run_dispatcher(payload_text)
|
|
1109
|
-
is_deny, reason_text = _parse_hook_decision(dispatcher_result)
|
|
1110
|
-
assert is_deny, (
|
|
1111
|
-
"dispatcher must deny an apply_patch add onto an already-existing path, "
|
|
1112
|
-
f"got stdout {dispatcher_result.stdout.strip()!r}"
|
|
1113
|
-
)
|
|
1114
|
-
assert reason_text, "a deny must carry a non-empty reason"
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
836
|
def test_dispatcher_allows_clean_apply_patch_add(tmp_path: Path) -> None:
|
|
1118
|
-
"""
|
|
1119
|
-
|
|
1120
|
-
tdd_enforcer now reaches apply_patch the same way it reaches Write: an
|
|
1121
|
-
untested production file is exactly the immediate-harm case the apply_patch
|
|
1122
|
-
boundary rule names, so "clean" here means test-first was honored before
|
|
1123
|
-
the tool call, not that TDD is skipped for this tool. The paired test file
|
|
1124
|
-
is written to disk first, matching how test_dispatcher_surfaces_migration_
|
|
1125
|
-
warning_for_edit proves a fresh Edit passes tdd_enforcer.
|
|
1126
|
-
"""
|
|
1127
|
-
(tmp_path / "test_services.py").write_text(
|
|
1128
|
-
"def test_add_one():\n assert True\n", encoding="utf-8"
|
|
1129
|
-
)
|
|
837
|
+
"""A clean patch can proceed before the staged linter evaluates test pairing."""
|
|
1130
838
|
patch_command = _codex_add_patch(
|
|
1131
|
-
"services.py",
|
|
1132
|
-
"def add_one(value: int) -> int:\n return value + 1\n",
|
|
839
|
+
"services.py", "def add_one(value: int) -> int:\n return value + 1\n",
|
|
1133
840
|
)
|
|
1134
841
|
payload_text = _apply_patch_payload(str(tmp_path), patch_command)
|
|
1135
|
-
|
|
1136
842
|
dispatcher_result = _run_dispatcher(payload_text)
|
|
1137
843
|
is_deny, reason_text = _parse_hook_decision(dispatcher_result)
|
|
1138
|
-
assert not is_deny,
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
""
|
|
1151
|
-
|
|
844
|
+
assert not is_deny, reason_text
|
|
845
|
+
|
|
846
|
+
|
|
847
|
+
ALL_RETAINED_PATHS = {
|
|
848
|
+
"advisory/refactor_guard.py",
|
|
849
|
+
"advisory/migration_safety_advisor.py",
|
|
850
|
+
}
|
|
851
|
+
ALL_RETIRED_PATHS = {
|
|
852
|
+
"validation/hook_format_validator.py",
|
|
853
|
+
"blocking/code_rules_enforcer.py",
|
|
854
|
+
"blocking/tdd_enforcer.py",
|
|
855
|
+
"blocking/windows_rmtree_blocker.py",
|
|
856
|
+
"blocking/state_description_blocker.py",
|
|
857
|
+
"blocking/subprocess_budget_completeness.py",
|
|
858
|
+
"blocking/hook_prose_detector_consistency.py",
|
|
859
|
+
"blocking/workflow_substitution_slot_blocker.py",
|
|
860
|
+
"blocking/open_questions_in_plans_blocker.py",
|
|
861
|
+
"blocking/docstring_rule_gate_count_blocker.py",
|
|
862
|
+
"blocking/duplicate_rmtree_helper_blocker.py",
|
|
863
|
+
"blocking/env_var_table_code_drift_blocker.py",
|
|
864
|
+
"blocking/pytest_testpaths_orphan_blocker.py",
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
def test_hosted_hook_set_retains_boundaries_and_excludes_replaced_file_checks() -> None:
|
|
868
|
+
"""Keep boundary controls and exclude every registry-backed policy hook."""
|
|
869
|
+
all_hosted_script_paths = {
|
|
1152
870
|
each_entry.script_relative_path for each_entry in ALL_HOSTED_HOOK_ENTRIES
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
"blocking/code_rules_enforcer.py",
|
|
1159
|
-
"blocking/tdd_enforcer.py",
|
|
1160
|
-
"blocking/windows_rmtree_blocker.py",
|
|
1161
|
-
"blocking/state_description_blocker.py",
|
|
1162
|
-
"blocking/subprocess_budget_completeness.py",
|
|
1163
|
-
"blocking/hook_prose_detector_consistency.py",
|
|
1164
|
-
"blocking/workflow_substitution_slot_blocker.py",
|
|
1165
|
-
"blocking/claude_md_orphan_file_blocker.py",
|
|
1166
|
-
"blocking/pytest_testpaths_orphan_blocker.py",
|
|
1167
|
-
"blocking/open_questions_in_plans_blocker.py",
|
|
1168
|
-
})
|
|
1169
|
-
for each_script_path in previously_registered_blocking_hooks:
|
|
1170
|
-
assert each_script_path in all_hosted_script_paths, (
|
|
1171
|
-
f"Previously-registered blocking hook {each_script_path!r} is missing "
|
|
1172
|
-
"from the dispatcher's hosted hook set — coverage was lost when the "
|
|
1173
|
-
"standalone entry was removed from hooks.json"
|
|
1174
|
-
)
|
|
871
|
+
}
|
|
872
|
+
assert all_hosted_script_paths == ALL_RETAINED_PATHS
|
|
873
|
+
assert not all_hosted_script_paths.intersection(ALL_RETIRED_PATHS)
|
|
874
|
+
for each_relative_path in ALL_RETIRED_PATHS:
|
|
875
|
+
assert (_HOOKS_ROOT / each_relative_path).is_file()
|
|
@@ -1,16 +1,8 @@
|
|
|
1
|
-
"""
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
text. It also asserts the dispatcher reaches the same decision through its native
|
|
7
|
-
path.
|
|
8
|
-
|
|
9
|
-
The corpus pairs allowing payloads with denying payloads for each hook so the
|
|
10
|
-
equivalence holds across both outcomes. Each payload names an absolute file
|
|
11
|
-
under this repository and carries the repository root as its ``cwd``, so the
|
|
12
|
-
decision belongs to this checkout rather than to the directory pytest runs
|
|
13
|
-
from.
|
|
1
|
+
"""Preserve detector equivalence while retiring its dispatcher registration.
|
|
2
|
+
|
|
3
|
+
The standalone state-description detector remains callable by the policy linter.
|
|
4
|
+
Its function and script agree on valid and invalid inputs; the write dispatcher
|
|
5
|
+
allows these policy findings to reach the staged linter.
|
|
14
6
|
"""
|
|
15
7
|
|
|
16
8
|
from __future__ import annotations
|
|
@@ -195,28 +187,6 @@ def _deny_reason_from_dispatcher(payload_dictionary: dict[str, object]) -> str |
|
|
|
195
187
|
)
|
|
196
188
|
return _deny_reason_from_script_stdout(completed_process.stdout.strip())
|
|
197
189
|
|
|
198
|
-
def _deny_payload_from_dispatcher(payload_dictionary: dict[str, object]) -> dict[str, object]:
|
|
199
|
-
"""Run the dispatcher as a subprocess and return its parsed deny payload.
|
|
200
|
-
|
|
201
|
-
Args:
|
|
202
|
-
payload_dictionary: The payload dict to send as JSON on stdin.
|
|
203
|
-
|
|
204
|
-
Returns:
|
|
205
|
-
The dispatcher's emitted deny JSON parsed into a dict.
|
|
206
|
-
"""
|
|
207
|
-
completed_process = subprocess.run(
|
|
208
|
-
[sys.executable, _DISPATCHER_SCRIPT],
|
|
209
|
-
check=False,
|
|
210
|
-
input=json.dumps(payload_dictionary),
|
|
211
|
-
capture_output=True,
|
|
212
|
-
text=True,
|
|
213
|
-
encoding="utf-8",
|
|
214
|
-
env=_subprocess_environment(),
|
|
215
|
-
)
|
|
216
|
-
parsed_payload = json.loads(completed_process.stdout.strip())
|
|
217
|
-
assert isinstance(parsed_payload, dict)
|
|
218
|
-
return parsed_payload
|
|
219
|
-
|
|
220
190
|
def test_state_description_native_allows_match_script() -> None:
|
|
221
191
|
"""state_description_blocker native allow matches the script's allow."""
|
|
222
192
|
payload_dictionary = _write_payload_dictionary(_MARKDOWN_PATH, _STATE_DESCRIPTION_ALLOW_CONTENT)
|
|
@@ -262,18 +232,17 @@ def test_state_description_native_multi_edit_deny_matches_script() -> None:
|
|
|
262
232
|
assert native_reason == script_reason
|
|
263
233
|
|
|
264
234
|
|
|
265
|
-
def
|
|
266
|
-
"""The
|
|
235
|
+
def test_dispatcher_allows_the_retired_state_description_rule() -> None:
|
|
236
|
+
"""The detector still finds the violation, but the write dispatcher allows it."""
|
|
267
237
|
payload_dictionary = _write_payload_dictionary(_MARKDOWN_PATH, _STATE_DESCRIPTION_DENY_CONTENT)
|
|
268
238
|
native_reason = _evaluate_with_prose_style_opt_in(state_description_blocker.evaluate, payload_dictionary)
|
|
269
239
|
dispatcher_reason = _deny_reason_from_dispatcher(payload_dictionary)
|
|
270
240
|
assert native_reason is not None
|
|
271
|
-
assert dispatcher_reason is
|
|
272
|
-
assert native_reason in dispatcher_reason
|
|
241
|
+
assert dispatcher_reason is None
|
|
273
242
|
|
|
274
243
|
|
|
275
|
-
def
|
|
276
|
-
"""The
|
|
244
|
+
def test_standalone_state_description_retains_additional_context() -> None:
|
|
245
|
+
"""The retained standalone interface still supplies its corrective context."""
|
|
277
246
|
payload_dictionary = _write_payload_dictionary(_MARKDOWN_PATH, _STATE_DESCRIPTION_DENY_CONTENT)
|
|
278
247
|
deny_reason = _evaluate_with_prose_style_opt_in(state_description_blocker.evaluate, payload_dictionary)
|
|
279
248
|
assert deny_reason is not None
|
|
@@ -281,20 +250,20 @@ def test_dispatcher_native_state_description_carries_additional_context() -> Non
|
|
|
281
250
|
standalone_hook_specific = standalone_payload["hookSpecificOutput"]
|
|
282
251
|
assert isinstance(standalone_hook_specific, dict)
|
|
283
252
|
expected_additional_context = standalone_hook_specific["additionalContext"]
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
assert isinstance(
|
|
287
|
-
assert
|
|
253
|
+
script_payload = json.loads(_run_script_subprocess(_STATE_DESCRIPTION_SCRIPT, payload_dictionary))
|
|
254
|
+
script_hook_specific = script_payload.get("hookSpecificOutput", {})
|
|
255
|
+
assert isinstance(script_hook_specific, dict)
|
|
256
|
+
assert script_hook_specific.get("additionalContext") == expected_additional_context
|
|
288
257
|
|
|
289
|
-
def
|
|
290
|
-
"""The
|
|
258
|
+
def test_standalone_state_description_retains_system_message() -> None:
|
|
259
|
+
"""The retained standalone interface still supplies its system message."""
|
|
291
260
|
payload_dictionary = _write_payload_dictionary(_MARKDOWN_PATH, _STATE_DESCRIPTION_DENY_CONTENT)
|
|
292
261
|
deny_reason = _evaluate_with_prose_style_opt_in(state_description_blocker.evaluate, payload_dictionary)
|
|
293
262
|
assert deny_reason is not None
|
|
294
263
|
standalone_payload = state_description_blocker.build_deny_payload(deny_reason)
|
|
295
264
|
expected_system_message = standalone_payload["systemMessage"]
|
|
296
265
|
assert isinstance(expected_system_message, str)
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
assert isinstance(
|
|
300
|
-
assert expected_system_message in
|
|
266
|
+
script_payload = json.loads(_run_script_subprocess(_STATE_DESCRIPTION_SCRIPT, payload_dictionary))
|
|
267
|
+
script_system_message = script_payload.get("systemMessage")
|
|
268
|
+
assert isinstance(script_system_message, str)
|
|
269
|
+
assert expected_system_message in script_system_message
|
|
@@ -34,11 +34,7 @@ from test_hook_subprocess_support import ( # noqa: E402
|
|
|
34
34
|
run_hook_as_subprocess,
|
|
35
35
|
)
|
|
36
36
|
|
|
37
|
-
ALL_CONVERTED_HOOK_FILENAMES = (
|
|
38
|
-
"open_questions_in_plans_blocker.py",
|
|
39
|
-
"claude_md_orphan_file_blocker.py",
|
|
40
|
-
"package_inventory_stale_blocker.py",
|
|
41
|
-
)
|
|
37
|
+
ALL_CONVERTED_HOOK_FILENAMES = ("open_questions_in_plans_blocker.py",)
|
|
42
38
|
|
|
43
39
|
EMPTY_STDIN_PAYLOAD = ""
|
|
44
40
|
WHITESPACE_STDIN_PAYLOAD = " \n\t "
|
|
@@ -126,48 +122,6 @@ def test_open_questions_blocker_still_allows_plan_without_open_questions(
|
|
|
126
122
|
assert _decision_from_stdout(completed) is None
|
|
127
123
|
|
|
128
124
|
|
|
129
|
-
def test_package_inventory_blocker_still_denies_uninventoried_new_file(
|
|
130
|
-
tmp_path: Path,
|
|
131
|
-
) -> None:
|
|
132
|
-
inventory_body = "# package\n\n| File | Role |\n|---|---|\n| `alpha.py` | A |\n| `beta.py` | B |\n"
|
|
133
|
-
(tmp_path / "README.md").write_text(inventory_body, encoding="utf-8")
|
|
134
|
-
(tmp_path / "alpha.py").write_text("x = 1\n", encoding="utf-8")
|
|
135
|
-
(tmp_path / "beta.py").write_text("x = 1\n", encoding="utf-8")
|
|
136
|
-
new_file_path = tmp_path / "gamma.py"
|
|
137
|
-
payload = json.dumps(
|
|
138
|
-
{
|
|
139
|
-
"tool_name": "Write",
|
|
140
|
-
"tool_input": {"file_path": str(new_file_path), "content": "x = 1\n"},
|
|
141
|
-
}
|
|
142
|
-
)
|
|
143
|
-
completed = _run_hook_script("package_inventory_stale_blocker.py", payload, tmp_path)
|
|
144
|
-
assert completed.returncode == 0, completed.stderr
|
|
145
|
-
assert _decision_from_stdout(completed) == "deny"
|
|
146
|
-
assert_hook_deny_log_contains(tmp_path, "package_inventory_stale_blocker.py")
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
def test_package_inventory_blocker_still_allows_inventoried_new_file(
|
|
150
|
-
tmp_path: Path,
|
|
151
|
-
) -> None:
|
|
152
|
-
inventory_body = (
|
|
153
|
-
"# package\n\n| File | Role |\n|---|---|\n"
|
|
154
|
-
"| `alpha.py` | A |\n| `beta.py` | B |\n| `gamma.py` | G |\n"
|
|
155
|
-
)
|
|
156
|
-
(tmp_path / "README.md").write_text(inventory_body, encoding="utf-8")
|
|
157
|
-
(tmp_path / "alpha.py").write_text("x = 1\n", encoding="utf-8")
|
|
158
|
-
(tmp_path / "beta.py").write_text("x = 1\n", encoding="utf-8")
|
|
159
|
-
new_file_path = tmp_path / "gamma.py"
|
|
160
|
-
payload = json.dumps(
|
|
161
|
-
{
|
|
162
|
-
"tool_name": "Write",
|
|
163
|
-
"tool_input": {"file_path": str(new_file_path), "content": "x = 1\n"},
|
|
164
|
-
}
|
|
165
|
-
)
|
|
166
|
-
completed = _run_hook_script("package_inventory_stale_blocker.py", payload, tmp_path)
|
|
167
|
-
assert completed.returncode == 0, completed.stderr
|
|
168
|
-
assert _decision_from_stdout(completed) is None
|
|
169
|
-
|
|
170
|
-
|
|
171
125
|
def test_converted_hooks_allow_unrelated_tool_name(tmp_path: Path) -> None:
|
|
172
126
|
payload = json.dumps({"tool_name": "Bash", "tool_input": {"command": "ls"}})
|
|
173
127
|
for each_hook_filename in ALL_CONVERTED_HOOK_FILENAMES:
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Constants for the native pre-commit policy-lint step."""
|
|
2
|
+
|
|
3
|
+
POLICY_LINT_PACKAGE_PARENT_INDEX = 2
|
|
4
|
+
POLICY_LINT_SCRIPT_RELATIVE_PATH = "scripts/cde_lint.py"
|
|
5
|
+
POLICY_LINT_STAGED_ARGUMENT = "--staged"
|
|
6
|
+
POLICY_LINT_TIMEOUT_SECONDS = 240
|
|
7
|
+
POLICY_LINT_INFRASTRUCTURE_EXIT_CODE = 2
|
|
8
|
+
POLICY_LINT_UNAVAILABLE_MESSAGE = (
|
|
9
|
+
"cde lint is missing from this hook installation; reinstall claude-dev-env "
|
|
10
|
+
"before committing"
|
|
11
|
+
)
|
|
12
|
+
POLICY_LINT_FAILED_MESSAGE = "cde lint could not complete: {error}"
|