claude-dev-env 4.3.0 → 7.0.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 +33 -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 +1 -3
- package/.agents/skills/skill-builder/SKILL.md +3 -196
- 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/install.agents-home.test.mjs +15 -13
- package/bin/install.mjs +28 -0
- package/bin/install.policy-lint.test.mjs +78 -0
- package/bin/install.prune.test.mjs +59 -20
- package/bin/install.pstack-plugin.test.mjs +114 -0
- package/bin/install.test.mjs +37 -0
- package/bin/refresh-pstack-plugin-skills.test.mjs +211 -0
- 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_paired_test.py +22 -15
- package/hooks/blocking/test_bash_post_call_dispatcher.py +65 -4
- 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 +68 -133
- package/hooks/blocking/test_pre_tool_use_dispatcher_native.py +20 -51
- package/hooks/blocking/test_precommit_code_rules_gate_native_owner.py +18 -18
- package/hooks/git-hooks/git_hooks_constants/staged_policy_lint.py +12 -0
- package/hooks/git-hooks/pre_commit.py +52 -11
- package/hooks/git-hooks/test_installed_policy_lint.py +159 -0
- package/hooks/git-hooks/test_native_policy_commit.py +87 -0
- package/hooks/git-hooks/test_pre_commit.py +246 -0
- package/hooks/hooks.json +1 -1
- package/hooks/hooks_constants/bash_post_call_dispatcher_constants.py +14 -1
- package/hooks/hooks_constants/pr_done_reminder_constants.py +98 -0
- package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +10 -43
- package/hooks/hooks_constants/test_bash_post_call_dispatcher_constants.py +19 -2
- package/hooks/hooks_constants/test_pr_done_reminder_constants.py +40 -0
- package/hooks/hooks_constants/test_pre_tool_use_dispatcher_constants.py +20 -32
- package/package.json +2 -2
- package/rules/durable-post-artifacts.md +2 -1
- package/rules/git-workflow.md +6 -6
- 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/dev_env_scripts_constants/shared_tree_constants.py +4 -5
- package/{.agents/skills/_shared/pr-loop/scripts → scripts}/durable_post_lint.py +5 -41
- package/{.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/durable_post_lint_constants.py → scripts/durable_post_lint_config/config/constants.py} +0 -10
- package/{.agents/skills/_shared/pr-loop/scripts/test_durable_post_lint_constants.py → scripts/durable_post_lint_config/config/test_constants.py} +1 -1
- package/scripts/policy_lint/adapter_detectors.py +13 -6
- package/scripts/policy_lint/adapters.py +7 -4
- 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/refresh_pstack_plugin_skills.mjs +200 -0
- package/scripts/shared_tree_paths.py +32 -3
- package/scripts/test_shared_tree_paths.py +33 -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_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_rules.py +3 -7
- package/.agents/skills/_shared/pr-loop/.claude/CLAUDE.md +0 -1
- package/.agents/skills/_shared/pr-loop/AGENTS.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/CLAUDE.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/{.agents/skills/_shared/pr-loop/scripts → scripts}/test_durable_post_lint.py +0 -0
|
@@ -34,8 +34,8 @@ check_js_returns_object_schemaless_branch = (
|
|
|
34
34
|
|
|
35
35
|
_MJS_PATH = "skills/autoconverge/workflow/converge.mjs"
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
_HOOK_DIRECTORY.parents[
|
|
37
|
+
_ARCHIVED_CONVERGE_MJS = (
|
|
38
|
+
_HOOK_DIRECTORY.parents[3] / "skill-archive" / "autoconverge" / "workflow" / "converge.mjs"
|
|
39
39
|
)
|
|
40
40
|
|
|
41
41
|
|
|
@@ -161,7 +161,7 @@ def test_skips_python_files() -> None:
|
|
|
161
161
|
assert issues == []
|
|
162
162
|
|
|
163
163
|
|
|
164
|
-
def
|
|
165
|
-
|
|
166
|
-
issues = check_js_returns_object_schemaless_branch(
|
|
164
|
+
def test_archived_converge_regression_fixture_passes_the_check() -> None:
|
|
165
|
+
archived_source = _ARCHIVED_CONVERGE_MJS.read_text(encoding="utf-8")
|
|
166
|
+
issues = check_js_returns_object_schemaless_branch(archived_source, _MJS_PATH)
|
|
167
167
|
assert issues == []
|
|
@@ -428,44 +428,30 @@ def test_context_survives_alongside_deny_reason(
|
|
|
428
428
|
assert dispatcher_result.stdout.strip(), "Dispatcher must emit output when denying"
|
|
429
429
|
|
|
430
430
|
|
|
431
|
-
def test_all_deny_reasons_present_when_multiple_hooks_deny(
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
("
|
|
436
|
-
|
|
437
|
-
""
|
|
438
|
-
|
|
439
|
-
|
|
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)
|
|
431
|
+
def test_all_deny_reasons_present_when_multiple_hooks_deny(
|
|
432
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: Path,
|
|
433
|
+
) -> None:
|
|
434
|
+
"""Preserve every deny reason from the retained file-boundary checks."""
|
|
435
|
+
monkeypatch.setenv("HOME", str(tmp_path))
|
|
436
|
+
monkeypatch.setenv("USERPROFILE", str(tmp_path))
|
|
437
|
+
existing_sensitive_path = tmp_path / ".env"
|
|
438
|
+
existing_sensitive_path.write_text("VALUE=fixture\n", encoding="utf-8")
|
|
439
|
+
payload_text = _write_payload(str(existing_sensitive_path), "VALUE=changed\n")
|
|
444
440
|
_assert_dispatcher_matches_individual_hooks(payload_text, WRITE_TOOL_NAME)
|
|
445
|
-
|
|
446
441
|
dispatcher_result = _run_dispatcher(payload_text)
|
|
447
442
|
dispatcher_is_deny, dispatcher_reason = _parse_hook_decision(dispatcher_result)
|
|
448
|
-
assert dispatcher_is_deny
|
|
449
|
-
|
|
450
|
-
applicable_entries = _applicable_entries_for_tool(WRITE_TOOL_NAME)
|
|
443
|
+
assert dispatcher_is_deny
|
|
451
444
|
all_expected_deny_reasons: list[str] = []
|
|
452
|
-
for each_entry in
|
|
445
|
+
for each_entry in _applicable_entries_for_tool(WRITE_TOOL_NAME):
|
|
453
446
|
completed_process = _run_hook_subprocess(each_entry.script_relative_path, payload_text)
|
|
454
447
|
is_deny, reason_text = _parse_hook_decision(completed_process)
|
|
455
448
|
if is_deny and reason_text:
|
|
456
449
|
all_expected_deny_reasons.append(reason_text)
|
|
457
|
-
|
|
458
450
|
assert len(all_expected_deny_reasons) >= 2, (
|
|
459
|
-
|
|
460
|
-
"Check that an existing path triggers write_existing_file_blocker and "
|
|
461
|
-
"'previously' triggers state_description_blocker on a Write."
|
|
451
|
+
"Existing-file and sensitive-file checks must both deny the fixture"
|
|
462
452
|
)
|
|
463
453
|
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
|
-
)
|
|
454
|
+
assert each_reason in dispatcher_reason
|
|
469
455
|
|
|
470
456
|
|
|
471
457
|
def test_aggregate_exit_code_two_signals_deny() -> None:
|
|
@@ -686,18 +672,12 @@ def test_emit_allow_preserves_system_message_and_additional_context(
|
|
|
686
672
|
assert parsed["systemMessage"] == "system-a\nsystem-b"
|
|
687
673
|
|
|
688
674
|
|
|
689
|
-
def test_later_hook_deny_survives_early_hook_exit() -> None:
|
|
690
|
-
"""
|
|
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
|
-
)
|
|
675
|
+
def test_later_hook_deny_survives_early_hook_exit(tmp_path: Path) -> None:
|
|
676
|
+
"""A retained document-reference denial survives earlier successful checks."""
|
|
677
|
+
payload_text = _orphan_claude_md_payload(tmp_path)
|
|
700
678
|
_assert_dispatcher_matches_individual_hooks(payload_text, WRITE_TOOL_NAME)
|
|
679
|
+
is_deny, reason = _parse_hook_decision(_run_dispatcher(payload_text))
|
|
680
|
+
assert is_deny, reason
|
|
701
681
|
|
|
702
682
|
|
|
703
683
|
def test_dispatcher_write_applies_both_groups() -> None:
|
|
@@ -712,8 +692,8 @@ def test_dispatcher_write_applies_both_groups() -> None:
|
|
|
712
692
|
assert "blocking/write_existing_file_blocker.py" in all_write_script_paths, (
|
|
713
693
|
"write_existing_file_blocker (Group A) must be in Write applicable set"
|
|
714
694
|
)
|
|
715
|
-
assert len(all_write_entries) ==
|
|
716
|
-
f"Write tool must apply to
|
|
695
|
+
assert len(all_write_entries) == 11, (
|
|
696
|
+
f"Write tool must apply to the 11 retained write hooks, got {len(all_write_entries)}"
|
|
717
697
|
)
|
|
718
698
|
|
|
719
699
|
|
|
@@ -727,8 +707,8 @@ def test_dispatcher_edit_applies_both_groups() -> None:
|
|
|
727
707
|
assert "advisory/refactor_guard.py" in all_edit_script_paths, (
|
|
728
708
|
"refactor_guard is Edit-scoped and hosted, so it belongs in the Edit applicable set"
|
|
729
709
|
)
|
|
730
|
-
assert len(all_edit_entries) ==
|
|
731
|
-
f"expected
|
|
710
|
+
assert len(all_edit_entries) == 13, (
|
|
711
|
+
f"expected 13 retained Edit entries, got {len(all_edit_entries)}"
|
|
732
712
|
)
|
|
733
713
|
|
|
734
714
|
|
|
@@ -751,7 +731,7 @@ def test_dispatcher_multi_edit_reaches_the_sensitive_protector() -> None:
|
|
|
751
731
|
def test_dispatcher_apply_patch_applies_immediate_harm_hooks() -> None:
|
|
752
732
|
"""apply_patch applies to exactly the immediate-harm roster.
|
|
753
733
|
|
|
754
|
-
apply_patch reaches only the immediate-harm
|
|
734
|
+
apply_patch reaches only the retained immediate-harm roster the constants
|
|
755
735
|
module names, not the full Write/Edit/MultiEdit lint surface.
|
|
756
736
|
"""
|
|
757
737
|
all_apply_patch_entries = _applicable_entries_for_tool(APPLY_PATCH_TOOL_NAME)
|
|
@@ -885,45 +865,19 @@ def _parse_hook_allow(completed_process: subprocess.CompletedProcess[str]) -> bo
|
|
|
885
865
|
return hook_specific.get("permissionDecision") == "allow"
|
|
886
866
|
|
|
887
867
|
|
|
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
|
-
"""
|
|
868
|
+
def test_retired_tdd_hook_cannot_auto_approve_the_write(tmp_path: Path) -> None:
|
|
869
|
+
"""Pairing belongs to staged lint; its edit-time auto-approval is absent."""
|
|
900
870
|
config_target_path = str(tmp_path / "config" / "timing.py")
|
|
901
871
|
constants_only_content = (
|
|
902
872
|
'"""Timing constants."""\n\nMAXIMUM_RETRIES = 3\nRETRY_DELAY_SECONDS = 5\n'
|
|
903
873
|
)
|
|
904
874
|
payload_text = _write_payload(config_target_path, constants_only_content)
|
|
905
|
-
|
|
906
875
|
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
|
-
|
|
876
|
+
assert _parse_hook_allow(standalone_result)
|
|
912
877
|
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
|
-
)
|
|
878
|
+
dispatcher_is_deny, reason = _parse_hook_decision(dispatcher_result)
|
|
879
|
+
assert not dispatcher_is_deny, reason
|
|
880
|
+
assert not _parse_hook_allow(dispatcher_result)
|
|
927
881
|
|
|
928
882
|
|
|
929
883
|
def test_runpy_hosted_hook_sees_its_own_argv_not_the_dispatchers(tmp_path: Path) -> None:
|
|
@@ -1007,13 +961,7 @@ def _dispatcher_matchers(all_pre_tool_use_groups: list[dict]) -> list[str]:
|
|
|
1007
961
|
|
|
1008
962
|
|
|
1009
963
|
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
|
-
"""
|
|
964
|
+
"""The retired code-rules hook has no direct apply_patch registration."""
|
|
1017
965
|
hooks_json_text = (_HOOKS_ROOT / "hooks.json").read_text(encoding="utf-8")
|
|
1018
966
|
all_pre_tool_use_groups = json.loads(hooks_json_text)["hooks"]["PreToolUse"]
|
|
1019
967
|
|
|
@@ -1115,60 +1063,47 @@ def test_dispatcher_denies_apply_patch_add_onto_an_existing_path(tmp_path: Path)
|
|
|
1115
1063
|
|
|
1116
1064
|
|
|
1117
1065
|
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
|
-
)
|
|
1066
|
+
"""A clean patch can proceed before the staged linter evaluates test pairing."""
|
|
1130
1067
|
patch_command = _codex_add_patch(
|
|
1131
|
-
"services.py",
|
|
1132
|
-
"def add_one(value: int) -> int:\n return value + 1\n",
|
|
1068
|
+
"services.py", "def add_one(value: int) -> int:\n return value + 1\n",
|
|
1133
1069
|
)
|
|
1134
1070
|
payload_text = _apply_patch_payload(str(tmp_path), patch_command)
|
|
1135
|
-
|
|
1136
1071
|
dispatcher_result = _run_dispatcher(payload_text)
|
|
1137
1072
|
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
|
-
|
|
1073
|
+
assert not is_deny, reason_text
|
|
1074
|
+
|
|
1075
|
+
|
|
1076
|
+
ALL_RETAINED_PATHS = {
|
|
1077
|
+
"blocking/write_existing_file_blocker.py",
|
|
1078
|
+
"blocking/sensitive_file_protector.py",
|
|
1079
|
+
"blocking/pii_prevention_blocker.py",
|
|
1080
|
+
"validation/hook_format_validator.py",
|
|
1081
|
+
"blocking/duplicate_rmtree_helper_blocker.py",
|
|
1082
|
+
"blocking/claude_md_orphan_file_blocker.py",
|
|
1083
|
+
"blocking/package_inventory_stale_blocker.py",
|
|
1084
|
+
"blocking/env_var_table_code_drift_blocker.py",
|
|
1085
|
+
"blocking/pytest_testpaths_orphan_blocker.py",
|
|
1086
|
+
"blocking/open_questions_in_plans_blocker.py",
|
|
1087
|
+
"blocking/docstring_rule_gate_count_blocker.py",
|
|
1088
|
+
"advisory/refactor_guard.py",
|
|
1089
|
+
"advisory/migration_safety_advisor.py",
|
|
1090
|
+
}
|
|
1091
|
+
ALL_RETIRED_PATHS = {
|
|
1092
|
+
"blocking/code_rules_enforcer.py",
|
|
1093
|
+
"blocking/tdd_enforcer.py",
|
|
1094
|
+
"blocking/windows_rmtree_blocker.py",
|
|
1095
|
+
"blocking/state_description_blocker.py",
|
|
1096
|
+
"blocking/subprocess_budget_completeness.py",
|
|
1097
|
+
"blocking/hook_prose_detector_consistency.py",
|
|
1098
|
+
"blocking/workflow_substitution_slot_blocker.py",
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
def test_hosted_hook_set_retains_boundaries_and_excludes_replaced_file_checks() -> None:
|
|
1102
|
+
"""Keep unresolved controls and exclude the seven staged-linter replacements."""
|
|
1103
|
+
all_hosted_script_paths = {
|
|
1152
1104
|
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
|
-
)
|
|
1105
|
+
}
|
|
1106
|
+
assert all_hosted_script_paths == ALL_RETAINED_PATHS
|
|
1107
|
+
assert not all_hosted_script_paths.intersection(ALL_RETIRED_PATHS)
|
|
1108
|
+
for each_relative_path in ALL_RETIRED_PATHS:
|
|
1109
|
+
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
|
|
@@ -10,7 +10,6 @@ from __future__ import annotations
|
|
|
10
10
|
from collections.abc import Iterator
|
|
11
11
|
from contextlib import contextmanager
|
|
12
12
|
import json
|
|
13
|
-
import shutil
|
|
14
13
|
import stat
|
|
15
14
|
import subprocess
|
|
16
15
|
import sys
|
|
@@ -21,12 +20,20 @@ import pytest
|
|
|
21
20
|
HOOKS_ROOT = Path(__file__).resolve().parent.parent
|
|
22
21
|
BLOCKING_ROOT = HOOKS_ROOT / "blocking"
|
|
23
22
|
GIT_HOOKS_ROOT = HOOKS_ROOT / "git-hooks"
|
|
24
|
-
PRE_COMMIT_SOURCE_PATH = GIT_HOOKS_ROOT / "pre_commit.py"
|
|
25
|
-
GATE_UTILS_SOURCE_PATH = GIT_HOOKS_ROOT / "gate_utils.py"
|
|
26
|
-
GIT_HOOKS_CONSTANTS_SOURCE_PATH = GIT_HOOKS_ROOT / "git_hooks_constants"
|
|
27
23
|
DISPATCHER_SOURCE_PATH = BLOCKING_ROOT / "bash_pre_tool_use_dispatcher.py"
|
|
28
24
|
GIT_COMMAND_TIMEOUT_SECONDS = 60
|
|
29
|
-
CLEAN_MODULE_SOURCE =
|
|
25
|
+
CLEAN_MODULE_SOURCE = (
|
|
26
|
+
"def add_one(number: int) -> int:\n"
|
|
27
|
+
' """Increase the number.\n\n'
|
|
28
|
+
" Args:\n number: The input number.\n\n"
|
|
29
|
+
" Returns:\n The next number.\n"
|
|
30
|
+
' """\n'
|
|
31
|
+
" return number + 1\n"
|
|
32
|
+
)
|
|
33
|
+
PAIRED_TEST_SOURCE = (
|
|
34
|
+
"from widget import add_one\n\n"
|
|
35
|
+
"def test_add_one() -> None:\n assert add_one(1) == 2\n"
|
|
36
|
+
)
|
|
30
37
|
|
|
31
38
|
|
|
32
39
|
def run_git(
|
|
@@ -69,21 +76,13 @@ def write_gate_script(gate_path: Path, marker_path: Path, exit_code: int) -> Non
|
|
|
69
76
|
|
|
70
77
|
|
|
71
78
|
def install_native_pre_commit(hooks_path: Path) -> None:
|
|
72
|
-
"""
|
|
79
|
+
"""Register the actual package owner with its complete linter dependency tree."""
|
|
73
80
|
hooks_path.mkdir()
|
|
74
|
-
shutil.copyfile(PRE_COMMIT_SOURCE_PATH, hooks_path / "pre_commit.py")
|
|
75
|
-
shutil.copyfile(GATE_UTILS_SOURCE_PATH, hooks_path / "gate_utils.py")
|
|
76
|
-
shutil.copytree(
|
|
77
|
-
GIT_HOOKS_CONSTANTS_SOURCE_PATH,
|
|
78
|
-
hooks_path / "git_hooks_constants",
|
|
79
|
-
)
|
|
80
81
|
pre_commit_path = hooks_path / "pre-commit"
|
|
81
82
|
pre_commit_path.write_text(
|
|
82
83
|
"#!/usr/bin/env python3\n"
|
|
83
84
|
"import sys\n"
|
|
84
|
-
"
|
|
85
|
-
"shim_directory = Path(__file__).resolve().parent\n"
|
|
86
|
-
"sys.path.insert(0, str(shim_directory))\n"
|
|
85
|
+
f"sys.path.insert(0, {str(GIT_HOOKS_ROOT)!r})\n"
|
|
87
86
|
"import pre_commit\n"
|
|
88
87
|
"sys.exit(pre_commit.main())\n",
|
|
89
88
|
encoding="utf-8",
|
|
@@ -133,9 +132,10 @@ def temporary_hooks_path(repository_root: Path, benchmark_hooks_path: Path) -> I
|
|
|
133
132
|
|
|
134
133
|
|
|
135
134
|
def stage_module(repository_root: Path) -> None:
|
|
136
|
-
"""
|
|
135
|
+
"""Stage a valid module and matching test before exercising the legacy gate."""
|
|
137
136
|
(repository_root / "widget.py").write_text(CLEAN_MODULE_SOURCE, encoding="utf-8")
|
|
138
|
-
|
|
137
|
+
(repository_root / "test_widget.py").write_text(PAIRED_TEST_SOURCE, encoding="utf-8")
|
|
138
|
+
run_git(repository_root, "add", "widget.py", "test_widget.py")
|
|
139
139
|
|
|
140
140
|
|
|
141
141
|
def build_bash_payload(repository_root: Path) -> str:
|
|
@@ -216,7 +216,7 @@ def test_temporary_hooks_path_restores_shared_worktree_config_on_timeout(
|
|
|
216
216
|
initialize_repository(tmp_path)
|
|
217
217
|
supported_hooks_path = tmp_path / "supported_hooks"
|
|
218
218
|
supported_hooks_path.mkdir()
|
|
219
|
-
run_git(tmp_path, "config", "
|
|
219
|
+
run_git(tmp_path, "config", "core.hooksPath", str(supported_hooks_path))
|
|
220
220
|
unrelated_worktree = tmp_path.parent / "unrelated_worktree"
|
|
221
221
|
run_git(tmp_path, "worktree", "add", "--detach", str(unrelated_worktree))
|
|
222
222
|
benchmark_hooks_path = tmp_path / "benchmark_hooks"
|
|
@@ -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}"
|
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
2
|
+
"""Run staged policy lint, then the existing staged CODE_RULES gate.
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
Git invokes this module through the installed native pre-commit shim. Policy
|
|
5
|
+
lint reads the Git index, including GIT_INDEX_FILE when Git supplies an
|
|
6
|
+
alternate index. The existing gate still covers checks whose replacement
|
|
7
|
+
coverage is unproven.
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
Exit codes:
|
|
12
|
-
0 - staged changes pass the gate (or the gate is not installed locally).
|
|
13
|
-
1 - staged changes introduce one or more blocking violations.
|
|
14
|
-
2 - unexpected invocation failure (e.g., subprocess could not launch).
|
|
9
|
+
A missing linter, launch failure, or timeout is an infrastructure failure.
|
|
10
|
+
Linter diagnostics and failed-rule statuses reach Git.
|
|
15
11
|
"""
|
|
16
12
|
|
|
17
13
|
from __future__ import annotations
|
|
@@ -27,6 +23,43 @@ from git_hooks_constants import (
|
|
|
27
23
|
IMMEDIATE_SCOPE_ARGUMENT,
|
|
28
24
|
INVOKE_GATE_FAILURE_MESSAGE,
|
|
29
25
|
)
|
|
26
|
+
from git_hooks_constants.staged_policy_lint import (
|
|
27
|
+
POLICY_LINT_FAILED_MESSAGE,
|
|
28
|
+
POLICY_LINT_INFRASTRUCTURE_EXIT_CODE,
|
|
29
|
+
POLICY_LINT_PACKAGE_PARENT_INDEX,
|
|
30
|
+
POLICY_LINT_SCRIPT_RELATIVE_PATH,
|
|
31
|
+
POLICY_LINT_STAGED_ARGUMENT,
|
|
32
|
+
POLICY_LINT_TIMEOUT_SECONDS,
|
|
33
|
+
POLICY_LINT_UNAVAILABLE_MESSAGE,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def resolve_policy_lint_script_path() -> Path:
|
|
38
|
+
"""Return the policy-linter entry point in this package or managed home."""
|
|
39
|
+
package_root = Path(__file__).resolve().parents[POLICY_LINT_PACKAGE_PARENT_INDEX]
|
|
40
|
+
return package_root / POLICY_LINT_SCRIPT_RELATIVE_PATH
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def run_staged_policy_lint() -> int:
|
|
44
|
+
"""Run the staged linter with Git's environment and working directory.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
The linter status, or the infrastructure status when it cannot run.
|
|
48
|
+
"""
|
|
49
|
+
try:
|
|
50
|
+
script_path = resolve_policy_lint_script_path()
|
|
51
|
+
if not script_path.is_file():
|
|
52
|
+
sys.stderr.write(POLICY_LINT_UNAVAILABLE_MESSAGE + "\n")
|
|
53
|
+
return POLICY_LINT_INFRASTRUCTURE_EXIT_CODE
|
|
54
|
+
completion = subprocess.run(
|
|
55
|
+
[sys.executable, str(script_path.resolve(strict=True)), POLICY_LINT_STAGED_ARGUMENT],
|
|
56
|
+
check=False,
|
|
57
|
+
timeout=POLICY_LINT_TIMEOUT_SECONDS,
|
|
58
|
+
)
|
|
59
|
+
except (OSError, subprocess.TimeoutExpired) as launch_error:
|
|
60
|
+
sys.stderr.write(POLICY_LINT_FAILED_MESSAGE.format(error=launch_error) + "\n")
|
|
61
|
+
return POLICY_LINT_INFRASTRUCTURE_EXIT_CODE
|
|
62
|
+
return completion.returncode
|
|
30
63
|
|
|
31
64
|
|
|
32
65
|
def invoke_gate(gate_script_path: Path) -> int:
|
|
@@ -50,6 +83,14 @@ def invoke_gate(gate_script_path: Path) -> int:
|
|
|
50
83
|
|
|
51
84
|
|
|
52
85
|
def main() -> int:
|
|
86
|
+
"""Run local staged policy lint before the existing native commit checks.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
The first failing check's status, or zero when the checks pass.
|
|
90
|
+
"""
|
|
91
|
+
lint_exit_code = run_staged_policy_lint()
|
|
92
|
+
if lint_exit_code != 0:
|
|
93
|
+
return lint_exit_code
|
|
53
94
|
gate_script_not_found_message = GATE_SCRIPT_NOT_FOUND_MESSAGE
|
|
54
95
|
gate_script_path, exact_allowed_path = resolve_gate_script_path()
|
|
55
96
|
if not is_safe_regular_file(gate_script_path, exact_allowed_path):
|