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,30 +1,12 @@
|
|
|
1
|
-
"""
|
|
2
|
-
|
|
3
|
-
The golden differential test runs a payload through every hosted PostToolUse
|
|
4
|
-
hook as its own subprocess (the production path), records each hook's block
|
|
5
|
-
decision, computes the expected aggregate, then runs the dispatcher on the same
|
|
6
|
-
payload and asserts an equal block-or-allow decision and the union of reasons.
|
|
7
|
-
|
|
8
|
-
Two focused tests pin the side-effecting behavior the dispatcher must not
|
|
9
|
-
change: the formatter formats only on a Write of a file git does not yet track
|
|
10
|
-
and never blocks; and the type-checker still blocks on a real type error when
|
|
11
|
-
run through the dispatcher. Two more tests pin that non-block stdout from a
|
|
12
|
-
side-effect hook survives on both the allow and block paths.
|
|
13
|
-
|
|
14
|
-
Crash and early-exit tests exercise the aggregator directly: an early hook
|
|
15
|
-
crash before mypy does not drop mypy's block, a non-blocking hook crash leaves
|
|
16
|
-
the decision allow, and a blocking hook crash surfaces a block.
|
|
17
|
-
"""
|
|
1
|
+
"""Behavior tests for the nonblocking PostToolUse dispatcher roster."""
|
|
18
2
|
|
|
19
3
|
from __future__ import annotations
|
|
20
4
|
|
|
21
|
-
import importlib.util
|
|
22
5
|
import json
|
|
23
6
|
import os
|
|
24
7
|
import subprocess
|
|
25
8
|
import sys
|
|
26
9
|
from pathlib import Path
|
|
27
|
-
from types import ModuleType
|
|
28
10
|
|
|
29
11
|
_HOOKS_DIR = str(Path(__file__).resolve().parent.parent)
|
|
30
12
|
if _HOOKS_DIR not in sys.path:
|
|
@@ -56,34 +38,8 @@ _WRITE_TOOL_NAME = "Write"
|
|
|
56
38
|
_EDIT_TOOL_NAME = "Edit"
|
|
57
39
|
|
|
58
40
|
|
|
59
|
-
def _load_module(module_filename: str, module_directory: Path) -> ModuleType:
|
|
60
|
-
"""Load a hook module by file path so its module-level constants are readable.
|
|
61
|
-
|
|
62
|
-
Args:
|
|
63
|
-
module_filename: The module filename to load.
|
|
64
|
-
module_directory: The directory holding the module.
|
|
65
|
-
|
|
66
|
-
Returns:
|
|
67
|
-
The loaded module object.
|
|
68
|
-
"""
|
|
69
|
-
module_path = module_directory / module_filename
|
|
70
|
-
spec = importlib.util.spec_from_file_location(module_path.stem, module_path)
|
|
71
|
-
assert spec is not None and spec.loader is not None
|
|
72
|
-
loaded_module = importlib.util.module_from_spec(spec)
|
|
73
|
-
spec.loader.exec_module(loaded_module)
|
|
74
|
-
return loaded_module
|
|
75
|
-
|
|
76
|
-
|
|
77
41
|
def _resolve_extra_arguments(each_entry: PostHostedHookEntry) -> list[str]:
|
|
78
|
-
"""Resolve a hook entry's relative
|
|
79
|
-
|
|
80
|
-
Args:
|
|
81
|
-
each_entry: The hosted hook entry whose extra arguments to resolve.
|
|
82
|
-
|
|
83
|
-
Returns:
|
|
84
|
-
The resolved argument list. The plugin-root placeholder resolves to the
|
|
85
|
-
plugin root absolute path; every other entry resolves relative to it.
|
|
86
|
-
"""
|
|
42
|
+
"""Resolve a hook entry's relative arguments against the plugin root."""
|
|
87
43
|
resolved_arguments: list[str] = []
|
|
88
44
|
for each_relative_path in each_entry.extra_argument_relative_paths:
|
|
89
45
|
if each_relative_path == PLUGIN_ROOT_PLACEHOLDER:
|
|
@@ -94,17 +50,10 @@ def _resolve_extra_arguments(each_entry: PostHostedHookEntry) -> list[str]:
|
|
|
94
50
|
|
|
95
51
|
|
|
96
52
|
def _environment_without_git_hook_variables() -> dict[str, str]:
|
|
97
|
-
"""Return the
|
|
98
|
-
|
|
99
|
-
Git exports variables such as GIT_DIR and GIT_INDEX_FILE to hook
|
|
100
|
-
processes. When this suite runs under a git hook (the pre-commit gate
|
|
101
|
-
replays staged test files), those variables leak into the hook
|
|
102
|
-
subprocesses and point their project discovery at the wrong checkout.
|
|
103
|
-
Stripping them restores the cwd-based discovery the hooks use in
|
|
104
|
-
production.
|
|
53
|
+
"""Return the environment without variables Git adds to hook processes.
|
|
105
54
|
|
|
106
|
-
|
|
107
|
-
|
|
55
|
+
Removing GIT_-prefixed variables keeps subprocess project discovery rooted in
|
|
56
|
+
the current checkout.
|
|
108
57
|
"""
|
|
109
58
|
return {
|
|
110
59
|
each_name: each_value
|
|
@@ -116,15 +65,7 @@ def _environment_without_git_hook_variables() -> dict[str, str]:
|
|
|
116
65
|
def _run_hook_subprocess(
|
|
117
66
|
each_entry: PostHostedHookEntry, payload_text: str
|
|
118
67
|
) -> subprocess.CompletedProcess[str]:
|
|
119
|
-
"""Run one hosted hook
|
|
120
|
-
|
|
121
|
-
Args:
|
|
122
|
-
each_entry: The hosted hook entry naming the script and its arguments.
|
|
123
|
-
payload_text: The JSON payload to send on stdin.
|
|
124
|
-
|
|
125
|
-
Returns:
|
|
126
|
-
The completed subprocess result with stdout and stderr captured.
|
|
127
|
-
"""
|
|
68
|
+
"""Run one hosted hook subprocess and capture its streams."""
|
|
128
69
|
script_path = str(_HOOKS_ROOT / each_entry.script_relative_path)
|
|
129
70
|
command = [sys.executable, script_path, *_resolve_extra_arguments(each_entry)]
|
|
130
71
|
return subprocess.run(
|
|
@@ -139,14 +80,7 @@ def _run_hook_subprocess(
|
|
|
139
80
|
|
|
140
81
|
|
|
141
82
|
def _run_dispatcher(payload_text: str) -> subprocess.CompletedProcess[str]:
|
|
142
|
-
"""Run the PostToolUse dispatcher
|
|
143
|
-
|
|
144
|
-
Args:
|
|
145
|
-
payload_text: The JSON payload to send on stdin.
|
|
146
|
-
|
|
147
|
-
Returns:
|
|
148
|
-
The completed subprocess result with stdout and stderr captured.
|
|
149
|
-
"""
|
|
83
|
+
"""Run the PostToolUse dispatcher and capture its streams."""
|
|
150
84
|
return subprocess.run(
|
|
151
85
|
[sys.executable, _DISPATCHER_SCRIPT, str(_PLUGIN_ROOT)],
|
|
152
86
|
check=False,
|
|
@@ -159,16 +93,7 @@ def _run_dispatcher(payload_text: str) -> subprocess.CompletedProcess[str]:
|
|
|
159
93
|
|
|
160
94
|
|
|
161
95
|
def _parse_block_decision(completed_process: subprocess.CompletedProcess[str]) -> tuple[bool, str]:
|
|
162
|
-
"""Parse
|
|
163
|
-
|
|
164
|
-
Args:
|
|
165
|
-
completed_process: The completed subprocess from running a hook.
|
|
166
|
-
|
|
167
|
-
Returns:
|
|
168
|
-
A (is_block, reason_text) pair where is_block is True when the hook
|
|
169
|
-
emitted a PostToolUse block decision, and reason_text carries the
|
|
170
|
-
block reason.
|
|
171
|
-
"""
|
|
96
|
+
"""Parse a hook's subprocess output into a block flag and reason."""
|
|
172
97
|
stdout_text = completed_process.stdout.strip()
|
|
173
98
|
if not stdout_text:
|
|
174
99
|
return False, ""
|
|
@@ -184,15 +109,7 @@ def _parse_block_decision(completed_process: subprocess.CompletedProcess[str]) -
|
|
|
184
109
|
|
|
185
110
|
|
|
186
111
|
def _compute_expected_aggregate(payload_text: str) -> tuple[bool, list[str]]:
|
|
187
|
-
"""Run each hosted hook
|
|
188
|
-
|
|
189
|
-
Args:
|
|
190
|
-
payload_text: The JSON payload text to send to each hook.
|
|
191
|
-
|
|
192
|
-
Returns:
|
|
193
|
-
A (should_block, all_block_reasons) pair where should_block is True when
|
|
194
|
-
any hook blocks, and all_block_reasons collects every blocking reason.
|
|
195
|
-
"""
|
|
112
|
+
"""Run each hosted hook and return whether any blocked plus all reasons."""
|
|
196
113
|
all_block_reasons: list[str] = []
|
|
197
114
|
for each_entry in ALL_POST_HOSTED_HOOK_ENTRIES:
|
|
198
115
|
completed_process = _run_hook_subprocess(each_entry, payload_text)
|
|
@@ -203,15 +120,7 @@ def _compute_expected_aggregate(payload_text: str) -> tuple[bool, list[str]]:
|
|
|
203
120
|
|
|
204
121
|
|
|
205
122
|
def _write_payload(file_path: str, content: str) -> str:
|
|
206
|
-
"""Build a Write tool
|
|
207
|
-
|
|
208
|
-
Args:
|
|
209
|
-
file_path: The target file path.
|
|
210
|
-
content: The file content written.
|
|
211
|
-
|
|
212
|
-
Returns:
|
|
213
|
-
JSON-encoded payload string.
|
|
214
|
-
"""
|
|
123
|
+
"""Build a JSON payload for a Write tool call."""
|
|
215
124
|
return json.dumps(
|
|
216
125
|
{
|
|
217
126
|
"tool_name": _WRITE_TOOL_NAME,
|
|
@@ -221,16 +130,7 @@ def _write_payload(file_path: str, content: str) -> str:
|
|
|
221
130
|
|
|
222
131
|
|
|
223
132
|
def _edit_payload(file_path: str, old_string: str, new_string: str) -> str:
|
|
224
|
-
"""Build an Edit tool
|
|
225
|
-
|
|
226
|
-
Args:
|
|
227
|
-
file_path: The target file path.
|
|
228
|
-
old_string: The text replaced.
|
|
229
|
-
new_string: The replacement text.
|
|
230
|
-
|
|
231
|
-
Returns:
|
|
232
|
-
JSON-encoded payload string.
|
|
233
|
-
"""
|
|
133
|
+
"""Build a JSON payload for an Edit tool call."""
|
|
234
134
|
return json.dumps(
|
|
235
135
|
{
|
|
236
136
|
"tool_name": _EDIT_TOOL_NAME,
|
|
@@ -269,60 +169,6 @@ def _assert_dispatcher_matches_individual_hooks(payload_text: str) -> None:
|
|
|
269
169
|
)
|
|
270
170
|
|
|
271
171
|
|
|
272
|
-
def _post_tool_use_dispatcher_harness_timeout_seconds() -> int:
|
|
273
|
-
"""Return the PostToolUse dispatcher's harness timeout from hooks.json.
|
|
274
|
-
|
|
275
|
-
Returns:
|
|
276
|
-
The timeout the Write|Edit PostToolUse entry declares for the dispatcher.
|
|
277
|
-
"""
|
|
278
|
-
hooks_json_path = _HOOKS_ROOT / "hooks.json"
|
|
279
|
-
parsed_hooks = json.loads(hooks_json_path.read_text(encoding="utf-8"))
|
|
280
|
-
for each_entry in parsed_hooks["hooks"]["PostToolUse"]:
|
|
281
|
-
for each_hook in each_entry["hooks"]:
|
|
282
|
-
if "post_tool_use_dispatcher.py" in each_hook["command"]:
|
|
283
|
-
return int(each_hook["timeout"])
|
|
284
|
-
raise AssertionError("PostToolUse dispatcher entry not found in hooks.json")
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
def _hosted_hooks_worst_case_internal_seconds() -> int:
|
|
288
|
-
"""Return the summed worst-case internal subprocess budget of the hosted hooks.
|
|
289
|
-
|
|
290
|
-
The hosted hooks run sequentially in one dispatcher process. The
|
|
291
|
-
type-checker and the formatter (its slower JS path) each carry their own
|
|
292
|
-
internal subprocess timeout; their sum is the dispatcher process's
|
|
293
|
-
worst-case runtime when each hook runs to its own ceiling.
|
|
294
|
-
|
|
295
|
-
Returns:
|
|
296
|
-
The summed worst-case internal subprocess seconds across the hosted hooks.
|
|
297
|
-
"""
|
|
298
|
-
mypy_validator = _load_module("mypy_validator.py", _VALIDATION_DIR)
|
|
299
|
-
auto_formatter = _load_module("auto_formatter.py", _HOOKS_ROOT / "workflow")
|
|
300
|
-
slowest_formatter_seconds = max(
|
|
301
|
-
auto_formatter.PYTHON_FORMAT_TIMEOUT_SECONDS,
|
|
302
|
-
auto_formatter.JS_FORMAT_TIMEOUT_SECONDS,
|
|
303
|
-
)
|
|
304
|
-
return mypy_validator.MYPY_TIMEOUT_SECONDS + slowest_formatter_seconds
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
def test_dispatcher_harness_timeout_clears_summed_hosted_hook_budgets() -> None:
|
|
308
|
-
"""The dispatcher harness timeout exceeds the summed worst-case hosted-hook budget.
|
|
309
|
-
|
|
310
|
-
The hosted hooks run sequentially under one harness timeout. When the
|
|
311
|
-
harness timeout does not clear their summed worst-case internal budgets, a
|
|
312
|
-
near-full slow type-check run consumes the budget and the harness kills the
|
|
313
|
-
dispatcher before the formatter gets its turn. The harness timeout must
|
|
314
|
-
exceed the sum so every hosted hook runs to completion.
|
|
315
|
-
"""
|
|
316
|
-
harness_timeout_seconds = _post_tool_use_dispatcher_harness_timeout_seconds()
|
|
317
|
-
summed_internal_seconds = _hosted_hooks_worst_case_internal_seconds()
|
|
318
|
-
assert harness_timeout_seconds > summed_internal_seconds, (
|
|
319
|
-
"PostToolUse dispatcher harness timeout "
|
|
320
|
-
f"({harness_timeout_seconds}s) must exceed the summed worst-case internal "
|
|
321
|
-
f"budgets of the hosted hooks ({summed_internal_seconds}s), or a slow "
|
|
322
|
-
"type-check run starves the formatter."
|
|
323
|
-
)
|
|
324
|
-
|
|
325
|
-
|
|
326
172
|
def test_clean_edit_of_plain_text_allows() -> None:
|
|
327
173
|
"""Dispatcher allows an Edit of a non-Python plain-text file (no hook blocks)."""
|
|
328
174
|
plain_text_path = str(_VALIDATION_DIR / "CLAUDE.md")
|
|
@@ -331,7 +177,7 @@ def test_clean_edit_of_plain_text_allows() -> None:
|
|
|
331
177
|
|
|
332
178
|
|
|
333
179
|
def test_clean_write_of_nonexistent_path_allows() -> None:
|
|
334
|
-
"""Dispatcher allows a Write whose path does not exist (
|
|
180
|
+
"""Dispatcher allows a Write whose path does not exist (blocking_hook skips)."""
|
|
335
181
|
missing_path = str(_VALIDATION_DIR / "does_not_exist_dispatcher_probe.txt")
|
|
336
182
|
payload_text = _write_payload(missing_path, "hello world\n")
|
|
337
183
|
_assert_dispatcher_matches_individual_hooks(payload_text)
|
|
@@ -364,56 +210,8 @@ def test_empty_payload_allows_fail_open() -> None:
|
|
|
364
210
|
)
|
|
365
211
|
|
|
366
212
|
|
|
367
|
-
def test_type_checker_still_blocks_on_type_error_through_dispatcher() -> None:
|
|
368
|
-
"""The type-checker's block on a real type error survives through the dispatcher.
|
|
369
|
-
|
|
370
|
-
Writes a Python file with a genuine type error inside this repository so
|
|
371
|
-
mypy_validator discovers the project root and blocks, then runs the same
|
|
372
|
-
payload through the dispatcher and asserts the dispatcher emits the block.
|
|
373
|
-
"""
|
|
374
|
-
type_error_file = _VALIDATION_DIR / "dispatcher_type_error_probe.py"
|
|
375
|
-
type_error_file.write_text(
|
|
376
|
-
"def add_one(value: int) -> int:\n return value + 1\n\n\nadd_one('not an int')\n",
|
|
377
|
-
encoding="utf-8",
|
|
378
|
-
)
|
|
379
|
-
try:
|
|
380
|
-
payload_text = _write_payload(str(type_error_file), type_error_file.read_text())
|
|
381
|
-
direct_block, direct_reason = _parse_block_decision(
|
|
382
|
-
_run_hook_subprocess(ALL_POST_HOSTED_HOOK_ENTRIES[0], payload_text)
|
|
383
|
-
)
|
|
384
|
-
assert direct_block, (
|
|
385
|
-
"Precondition failed: mypy_validator did not block a real type error "
|
|
386
|
-
f"directly. Reason: {direct_reason!r}. Is mypy installed and the file "
|
|
387
|
-
"inside the git project?"
|
|
388
|
-
)
|
|
389
|
-
dispatcher_result = _run_dispatcher(payload_text)
|
|
390
|
-
dispatcher_is_block, dispatcher_reason = _parse_block_decision(dispatcher_result)
|
|
391
|
-
assert dispatcher_is_block, (
|
|
392
|
-
"Dispatcher must block when the type-checker blocks on a type error. "
|
|
393
|
-
f"Dispatcher stdout: {dispatcher_result.stdout!r}"
|
|
394
|
-
)
|
|
395
|
-
assert direct_reason in dispatcher_reason, (
|
|
396
|
-
"Dispatcher block reason must carry the type-checker's reason.\n"
|
|
397
|
-
f"Expected: {direct_reason!r}\n"
|
|
398
|
-
f"Dispatcher reason: {dispatcher_reason!r}"
|
|
399
|
-
)
|
|
400
|
-
finally:
|
|
401
|
-
type_error_file.unlink(missing_ok=True)
|
|
402
|
-
|
|
403
|
-
|
|
404
213
|
def test_formatter_formats_only_untracked_write_and_never_blocks(tmp_path: Path) -> None:
|
|
405
|
-
"""
|
|
406
|
-
|
|
407
|
-
Writes an unformatted Python file into a git repo at tmp_path so the file is
|
|
408
|
-
untracked, runs a Write payload through the dispatcher, and asserts the file
|
|
409
|
-
is formatted on disk and the dispatcher does not block. Then runs an Edit
|
|
410
|
-
payload for the same path and asserts the formatter leaves an unformatted
|
|
411
|
-
file untouched, proving the Write-untracked gate still holds through the
|
|
412
|
-
dispatcher.
|
|
413
|
-
|
|
414
|
-
Args:
|
|
415
|
-
tmp_path: Pytest temp directory hosting the throwaway git repository.
|
|
416
|
-
"""
|
|
214
|
+
"""Verify formatting applies to untracked Write payloads but not Edit payloads."""
|
|
417
215
|
subprocess.run(
|
|
418
216
|
["git", "init", str(tmp_path)],
|
|
419
217
|
check=True,
|
|
@@ -447,13 +245,7 @@ def test_formatter_formats_only_untracked_write_and_never_blocks(tmp_path: Path)
|
|
|
447
245
|
|
|
448
246
|
|
|
449
247
|
def test_non_block_stdout_preserved_in_aggregator_allow_path() -> None:
|
|
450
|
-
"""
|
|
451
|
-
|
|
452
|
-
A side-effect hook writes informational text to stdout without emitting a
|
|
453
|
-
block decision. The aggregator must carry that text into
|
|
454
|
-
all_non_block_stdout so the dispatcher can write it to the real stdout on
|
|
455
|
-
the allow path.
|
|
456
|
-
"""
|
|
248
|
+
"""Preserve informational stdout when the aggregate allows."""
|
|
457
249
|
informational_text = "https://htmlpreview.github.io/?https://gist.github.com/abc/123"
|
|
458
250
|
all_results = [
|
|
459
251
|
PostHostedHookResult(captured_stdout="", did_crash=False, is_blocking=True),
|
|
@@ -473,23 +265,18 @@ def test_non_block_stdout_preserved_in_aggregator_allow_path() -> None:
|
|
|
473
265
|
|
|
474
266
|
|
|
475
267
|
def test_non_block_stdout_preserved_in_aggregator_block_path() -> None:
|
|
476
|
-
"""
|
|
477
|
-
|
|
478
|
-
When mypy_validator blocks and a side-effect hook wrote informational
|
|
479
|
-
text, both the block reason and the informational text survive in the
|
|
480
|
-
aggregated decision so _emit_block_decision can forward both to stdout.
|
|
481
|
-
"""
|
|
482
|
-
mypy_block_json = json.dumps({"decision": BLOCK_DECISION, "reason": "[MYPY] Type errors: x"})
|
|
268
|
+
"""Preserve informational stdout when another hook blocks."""
|
|
269
|
+
blocking_hook_block_json = json.dumps({"decision": BLOCK_DECISION, "reason": "[BLOCKING_HOOK] Type errors: x"})
|
|
483
270
|
informational_text = "https://htmlpreview.github.io/?https://gist.github.com/abc/456"
|
|
484
271
|
all_results = [
|
|
485
|
-
PostHostedHookResult(captured_stdout=
|
|
272
|
+
PostHostedHookResult(captured_stdout=blocking_hook_block_json, did_crash=False, is_blocking=True),
|
|
486
273
|
PostHostedHookResult(
|
|
487
274
|
captured_stdout=informational_text, did_crash=False, is_blocking=False
|
|
488
275
|
),
|
|
489
276
|
]
|
|
490
277
|
aggregated_decision = aggregate_post_hosted_hook_results(all_results)
|
|
491
278
|
assert aggregated_decision.should_block, (
|
|
492
|
-
"Aggregator must block when
|
|
279
|
+
"Aggregator must block when blocking_hook_validator blocks"
|
|
493
280
|
)
|
|
494
281
|
assert informational_text in aggregated_decision.all_non_block_stdout, (
|
|
495
282
|
"Aggregator must preserve non-block hook stdout even on the block path.\n"
|
|
@@ -499,39 +286,29 @@ def test_non_block_stdout_preserved_in_aggregator_block_path() -> None:
|
|
|
499
286
|
|
|
500
287
|
|
|
501
288
|
def test_early_hook_crash_does_not_drop_later_blocking_hook_block() -> None:
|
|
502
|
-
"""
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
runs and blocks. The aggregated decision must still block, proving the
|
|
506
|
-
dispatcher continues past a crash to collect all results.
|
|
507
|
-
"""
|
|
508
|
-
mypy_block_json = json.dumps(
|
|
509
|
-
{"decision": BLOCK_DECISION, "reason": "[MYPY] Type errors: y"}
|
|
289
|
+
"""Keep a later blocking hook's decision after an earlier hook crashes."""
|
|
290
|
+
blocking_hook_block_json = json.dumps(
|
|
291
|
+
{"decision": BLOCK_DECISION, "reason": "[BLOCKING_HOOK] Type errors: y"}
|
|
510
292
|
)
|
|
511
293
|
all_results = [
|
|
512
294
|
PostHostedHookResult(captured_stdout="", did_crash=True, is_blocking=False),
|
|
513
295
|
PostHostedHookResult(
|
|
514
|
-
captured_stdout=
|
|
296
|
+
captured_stdout=blocking_hook_block_json, did_crash=False, is_blocking=True
|
|
515
297
|
),
|
|
516
298
|
]
|
|
517
299
|
aggregated_decision = aggregate_post_hosted_hook_results(all_results)
|
|
518
300
|
assert aggregated_decision.should_block, (
|
|
519
|
-
"An early non-blocking hook crash must not prevent
|
|
301
|
+
"An early non-blocking hook crash must not prevent blocking_hook_validator's block "
|
|
520
302
|
"from reaching the aggregated decision"
|
|
521
303
|
)
|
|
522
|
-
assert any("[
|
|
523
|
-
"The
|
|
304
|
+
assert any("[BLOCKING_HOOK]" in each_reason for each_reason in aggregated_decision.all_block_reasons), (
|
|
305
|
+
"The blocking_hook block reason must survive in the aggregated decision.\n"
|
|
524
306
|
f"Block reasons: {aggregated_decision.all_block_reasons!r}"
|
|
525
307
|
)
|
|
526
308
|
|
|
527
309
|
|
|
528
310
|
def test_non_blocking_hook_crash_leaves_decision_allow() -> None:
|
|
529
|
-
"""
|
|
530
|
-
|
|
531
|
-
A side-effect hook such as auto_formatter carries is_blocking=False. Its
|
|
532
|
-
crash must not surface a blocking signal — the aggregated decision stays
|
|
533
|
-
allow.
|
|
534
|
-
"""
|
|
311
|
+
"""Keep the aggregate allowed after a nonblocking hook crashes."""
|
|
535
312
|
all_results = [
|
|
536
313
|
PostHostedHookResult(captured_stdout="", did_crash=True, is_blocking=False),
|
|
537
314
|
]
|
|
@@ -542,12 +319,7 @@ def test_non_blocking_hook_crash_leaves_decision_allow() -> None:
|
|
|
542
319
|
|
|
543
320
|
|
|
544
321
|
def test_empty_reason_block_still_blocks_with_fallback_reason() -> None:
|
|
545
|
-
"""
|
|
546
|
-
|
|
547
|
-
A blocking hook that emits decision=block with an empty reason string must
|
|
548
|
-
still block. The aggregator substitutes EMPTY_REASON_BLOCK_FALLBACK so the
|
|
549
|
-
block is not silently downgraded to allow.
|
|
550
|
-
"""
|
|
322
|
+
"""Use a fallback reason when a block decision has no reason."""
|
|
551
323
|
empty_reason_block_json = json.dumps({"decision": BLOCK_DECISION, "reason": ""})
|
|
552
324
|
all_results = [
|
|
553
325
|
PostHostedHookResult(
|
|
@@ -567,50 +339,8 @@ def test_empty_reason_block_still_blocks_with_fallback_reason() -> None:
|
|
|
567
339
|
)
|
|
568
340
|
|
|
569
341
|
|
|
570
|
-
def test_block_path_emits_single_parseable_json_object_on_stdout() -> None:
|
|
571
|
-
"""On the block path the dispatcher emits a single parseable JSON object on stdout.
|
|
572
|
-
|
|
573
|
-
Writes a Python file with a real type error inside this repository so
|
|
574
|
-
mypy_validator discovers the project root and blocks, then asserts the
|
|
575
|
-
dispatcher's whole stdout parses as one JSON block object — no leading
|
|
576
|
-
informational text mixed onto the same stream.
|
|
577
|
-
"""
|
|
578
|
-
type_error_file = _VALIDATION_DIR / "dispatcher_block_stdout_probe.py"
|
|
579
|
-
type_error_file.write_text(
|
|
580
|
-
"def add_one(value: int) -> int:\n return value + 1\n\n\nadd_one('not an int')\n",
|
|
581
|
-
encoding="utf-8",
|
|
582
|
-
)
|
|
583
|
-
try:
|
|
584
|
-
payload_text = _write_payload(str(type_error_file), type_error_file.read_text())
|
|
585
|
-
completed_direct = _run_hook_subprocess(ALL_POST_HOSTED_HOOK_ENTRIES[0], payload_text)
|
|
586
|
-
direct_block, _direct_reason = _parse_block_decision(completed_direct)
|
|
587
|
-
assert direct_block, (
|
|
588
|
-
"Precondition failed: mypy_validator did not block a real type error directly. "
|
|
589
|
-
"Is mypy installed and the file inside the git project?\n"
|
|
590
|
-
f"stdout: {completed_direct.stdout!r}\nstderr: {completed_direct.stderr!r}"
|
|
591
|
-
)
|
|
592
|
-
|
|
593
|
-
dispatcher_result = _run_dispatcher(payload_text)
|
|
594
|
-
parsed_stdout = json.loads(dispatcher_result.stdout.strip())
|
|
595
|
-
assert isinstance(parsed_stdout, dict), (
|
|
596
|
-
"Dispatcher stdout on the block path must be a single JSON object.\n"
|
|
597
|
-
f"Got: {dispatcher_result.stdout!r}"
|
|
598
|
-
)
|
|
599
|
-
assert parsed_stdout.get("decision") == BLOCK_DECISION, (
|
|
600
|
-
"The single JSON object on stdout must carry the block decision.\n"
|
|
601
|
-
f"Got: {parsed_stdout!r}"
|
|
602
|
-
)
|
|
603
|
-
finally:
|
|
604
|
-
type_error_file.unlink(missing_ok=True)
|
|
605
|
-
|
|
606
|
-
|
|
607
342
|
def test_blocking_hook_crash_surfaces_a_block() -> None:
|
|
608
|
-
"""
|
|
609
|
-
|
|
610
|
-
When a blocking hook (such as mypy_validator) crashes before emitting any
|
|
611
|
-
output, the aggregator must still block so a bad write does not silently
|
|
612
|
-
pass. The block reason must reference the dispatcher's crash signal.
|
|
613
|
-
"""
|
|
343
|
+
"""Surface a block with a crash reason when a blocking hook crashes."""
|
|
614
344
|
all_results = [
|
|
615
345
|
PostHostedHookResult(captured_stdout="", did_crash=True, is_blocking=True),
|
|
616
346
|
]
|
|
@@ -630,3 +360,10 @@ def test_blocking_hook_crash_surfaces_a_block() -> None:
|
|
|
630
360
|
"BLOCKING_CRASH_DENY_REASON constant.\n"
|
|
631
361
|
f"Got: {aggregated_decision.all_block_reasons!r}"
|
|
632
362
|
)
|
|
363
|
+
|
|
364
|
+
def test_live_roster_contains_only_nonblocking_hooks() -> None:
|
|
365
|
+
assert ALL_POST_HOSTED_HOOK_ENTRIES
|
|
366
|
+
assert all(not each_entry.is_blocking for each_entry in ALL_POST_HOSTED_HOOK_ENTRIES)
|
|
367
|
+
assert tuple(each_entry.script_relative_path for each_entry in ALL_POST_HOSTED_HOOK_ENTRIES) == (
|
|
368
|
+
"workflow/auto_formatter.py",
|
|
369
|
+
)
|
|
@@ -914,7 +914,11 @@ def validate_proposed_file(
|
|
|
914
914
|
temporary_file = _temporary_path_preserving_directory_signal(
|
|
915
915
|
Path(temporary_directory), file_path
|
|
916
916
|
)
|
|
917
|
-
temporary_file.write_text(
|
|
917
|
+
temporary_file.write_text(
|
|
918
|
+
proposed_content,
|
|
919
|
+
encoding="utf-8",
|
|
920
|
+
newline="",
|
|
921
|
+
)
|
|
918
922
|
resolved_config_source_path = (
|
|
919
923
|
config_source_path
|
|
920
924
|
if config_source_path is not None
|
|
@@ -26,7 +26,58 @@ SECRET_PATTERNS: frozenset[str] = frozenset({
|
|
|
26
26
|
SQL_EXECUTE_PATTERN = re.compile(r"\.execute\s*\(\s*f['\"]|\.execute\s*\([^)]*\.format\(", re.IGNORECASE)
|
|
27
27
|
|
|
28
28
|
|
|
29
|
+
def _normalize_metadata_name(metadata_text: str) -> str:
|
|
30
|
+
return "".join(
|
|
31
|
+
each_character for each_character in metadata_text.lower() if each_character.isalnum()
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _is_schema_key_metadata(variable_name: str, literal_text: str) -> bool:
|
|
36
|
+
return variable_name == "token_key" and _normalize_metadata_name(literal_text) == "token"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _is_header_metadata(variable_name: str, literal_text: str) -> bool:
|
|
40
|
+
if not variable_name.endswith("_header"):
|
|
41
|
+
return False
|
|
42
|
+
header_name = variable_name.removesuffix("_header")
|
|
43
|
+
return _normalize_metadata_name(literal_text) == _normalize_metadata_name(header_name)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _is_endpoint_template_metadata(variable_name: str, literal_text: str) -> bool:
|
|
47
|
+
if not variable_name.endswith("_endpoint_template"):
|
|
48
|
+
return False
|
|
49
|
+
endpoint_template_pattern = re.compile(r"^/[A-Za-z0-9_{}./-]+$")
|
|
50
|
+
if endpoint_template_pattern.fullmatch(literal_text) is None or "{" not in literal_text:
|
|
51
|
+
return False
|
|
52
|
+
endpoint_name = variable_name.removesuffix("_endpoint_template")
|
|
53
|
+
normalized_endpoint = _normalize_metadata_name(literal_text)
|
|
54
|
+
return all(each_word in normalized_endpoint for each_word in endpoint_name.split("_"))
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _is_resource_name_metadata(variable_name: str, literal_text: str) -> bool:
|
|
58
|
+
if not variable_name.endswith("_resource_name"):
|
|
59
|
+
return False
|
|
60
|
+
all_label_words = literal_text.lower().split()
|
|
61
|
+
resource_name = variable_name.removesuffix("_resource_name")
|
|
62
|
+
return (
|
|
63
|
+
len(all_label_words) > 1
|
|
64
|
+
and all(each_word.isalpha() for each_word in all_label_words)
|
|
65
|
+
and _normalize_metadata_name(all_label_words[-1]) == _normalize_metadata_name(resource_name)
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _is_secret_metadata(variable_name: str, literal_text: str) -> bool:
|
|
70
|
+
if _is_schema_key_metadata(variable_name, literal_text):
|
|
71
|
+
return True
|
|
72
|
+
if _is_header_metadata(variable_name, literal_text):
|
|
73
|
+
return True
|
|
74
|
+
if _is_endpoint_template_metadata(variable_name, literal_text):
|
|
75
|
+
return True
|
|
76
|
+
return _is_resource_name_metadata(variable_name, literal_text)
|
|
77
|
+
|
|
78
|
+
|
|
29
79
|
def check_hardcoded_secrets(tree: ast.AST, filename: str) -> List[Violation]:
|
|
80
|
+
MINIMUM_SECRET_LENGTH = 3
|
|
30
81
|
violations: List[Violation] = []
|
|
31
82
|
|
|
32
83
|
for node in ast.walk(tree):
|
|
@@ -35,8 +86,12 @@ def check_hardcoded_secrets(tree: ast.AST, filename: str) -> List[Violation]:
|
|
|
35
86
|
if isinstance(target, ast.Name):
|
|
36
87
|
var_name = target.id.lower()
|
|
37
88
|
if any(pattern in var_name for pattern in SECRET_PATTERNS):
|
|
38
|
-
if isinstance(node.value, ast.Constant) and isinstance(
|
|
39
|
-
|
|
89
|
+
if isinstance(node.value, ast.Constant) and isinstance(
|
|
90
|
+
node.value.value, str
|
|
91
|
+
):
|
|
92
|
+
if len(node.value.value) > MINIMUM_SECRET_LENGTH and not _is_secret_metadata(
|
|
93
|
+
var_name, node.value.value
|
|
94
|
+
):
|
|
40
95
|
violations.append(
|
|
41
96
|
Violation(
|
|
42
97
|
filename,
|
|
@@ -51,7 +51,6 @@ def test_write_edit_hook_commands_reads_the_real_hooks_json_roster() -> None:
|
|
|
51
51
|
all_commands = hook_timing_harness.write_edit_hook_commands(_HOOKS_JSON_PATH)
|
|
52
52
|
all_labels = [each_label for each_label, _each_command in all_commands]
|
|
53
53
|
assert all_labels == [
|
|
54
|
-
"run_all_validators",
|
|
55
54
|
"pre_tool_use_dispatcher",
|
|
56
55
|
"post_tool_use_dispatcher",
|
|
57
56
|
"session_file_edit_tracker",
|
|
@@ -169,17 +168,15 @@ def test_run_hosted_command_once_milliseconds_drives_the_real_tracker() -> None:
|
|
|
169
168
|
|
|
170
169
|
|
|
171
170
|
def test_measure_hosted_command_wall_times_covers_the_real_roster() -> None:
|
|
172
|
-
"""A live run against the real hooks.json roster returns all
|
|
171
|
+
"""A live run against the real hooks.json roster returns all three hooks.
|
|
173
172
|
|
|
174
173
|
Runs each registered command once against a real repository file,
|
|
175
|
-
|
|
176
|
-
its full wall time to prove the roster end to end.
|
|
174
|
+
so this is the one place the suite pays its full wall time to prove the roster end to end.
|
|
177
175
|
"""
|
|
178
176
|
all_wall_times_by_label = hook_timing_harness.measure_hosted_command_wall_times(
|
|
179
177
|
_HOOKS_JSON_PATH, _PACKAGE_ROOT, _REAL_TARGET_FILE, 1
|
|
180
178
|
)
|
|
181
179
|
assert set(all_wall_times_by_label) == {
|
|
182
|
-
"run_all_validators",
|
|
183
180
|
"pre_tool_use_dispatcher",
|
|
184
181
|
"post_tool_use_dispatcher",
|
|
185
182
|
"session_file_edit_tracker",
|
|
@@ -12,17 +12,12 @@ from pathlib import Path
|
|
|
12
12
|
|
|
13
13
|
import pytest
|
|
14
14
|
|
|
15
|
-
from hooks_constants.pre_tool_use_dispatcher_constants import (
|
|
16
|
-
ALL_WRITE_EDIT_MULTI_EDIT_APPLY_PATCH_TOOL_NAMES,
|
|
17
|
-
)
|
|
18
|
-
|
|
19
15
|
from .run_all_validators import run_validators_entrypoint_subprocess
|
|
20
16
|
|
|
21
17
|
pytestmark = pytest.mark.usefixtures("ephemeral_exempt_off")
|
|
22
18
|
|
|
23
19
|
HOOKS_JSON_PATH = Path(__file__).resolve().parents[1] / "hooks.json"
|
|
24
20
|
VALIDATORS_RUNNER_COMMAND_MARKER = "validators.run_all_validators"
|
|
25
|
-
MATCHER_TOOL_NAME_SEPARATOR = "|"
|
|
26
21
|
DENY_DECISION_FRAGMENT = '"permissionDecision": "deny"'
|
|
27
22
|
MAGIC_VALUE_VALIDATOR_NAME = "Magic Values"
|
|
28
23
|
|
|
@@ -91,7 +86,7 @@ def write_clean_module(target_directory: Path, module_name: str) -> Path:
|
|
|
91
86
|
|
|
92
87
|
|
|
93
88
|
class TestMutationToolRegistration:
|
|
94
|
-
def
|
|
89
|
+
def test_validators_gate_has_no_live_mutation_registration(self) -> None:
|
|
95
90
|
hooks_document = json.loads(HOOKS_JSON_PATH.read_text(encoding="utf-8"))
|
|
96
91
|
all_matchers = [
|
|
97
92
|
each_group["matcher"]
|
|
@@ -99,11 +94,8 @@ class TestMutationToolRegistration:
|
|
|
99
94
|
for each_hook in each_group.get("hooks", [])
|
|
100
95
|
if VALIDATORS_RUNNER_COMMAND_MARKER in each_hook.get("command", "")
|
|
101
96
|
]
|
|
102
|
-
assert
|
|
103
|
-
|
|
104
|
-
set(all_matchers[0].split(MATCHER_TOOL_NAME_SEPARATOR))
|
|
105
|
-
== ALL_WRITE_EDIT_MULTI_EDIT_APPLY_PATCH_TOOL_NAMES
|
|
106
|
-
)
|
|
97
|
+
assert all_matchers == [], all_matchers
|
|
98
|
+
|
|
107
99
|
|
|
108
100
|
|
|
109
101
|
class TestMultiEditPayloadLane:
|