claude-dev-env 4.2.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 +7 -0
- package/.agents/skills/pull-request/SKILL.md +216 -0
- package/.agents/skills/pull-request/reference/publication-tasks.md +18 -0
- package/.agents/skills/pull-request/scripts/github_pr_command_constants/__init__.py +1 -0
- package/.agents/skills/pull-request/scripts/github_pr_command_constants/config/__init__.py +1 -0
- package/.agents/skills/pull-request/scripts/github_pr_command_constants/config/constants.py +69 -0
- package/.agents/skills/pull-request/scripts/pull_request.py +265 -0
- package/.agents/skills/pull-request/scripts/recover_legacy_author.py +251 -0
- package/.agents/skills/pull-request/scripts/test_github_pr_command_constants.py +58 -0
- package/.agents/skills/pull-request/scripts/test_pull_request.py +396 -0
- package/.agents/skills/pull-request/scripts/test_recover_legacy_author.py +221 -0
- package/.agents/skills/skill-builder/SKILL.md +3 -196
- package/.agents/skills/test-runner/SKILL.md +69 -0
- package/.agents/skills/test-runner/scripts/preflight_checks.py +365 -0
- package/.agents/skills/test-runner/scripts/run_tests.py +316 -0
- package/.agents/skills/test-runner/scripts/test_preflight_checks.py +185 -0
- package/.agents/skills/test-runner/scripts/test_run_tests.py +281 -0
- package/.agents/skills/test-runner/scripts/test_runner_constants/__init__.py +1 -0
- package/.agents/skills/test-runner/scripts/test_runner_constants/config/__init__.py +1 -0
- package/.agents/skills/test-runner/scripts/test_runner_constants/config/constants.py +96 -0
- package/.agents/skills-archived/pr-loop-cloud-transport/reference/identity-and-hooks.md +1 -1
- 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/ever-shipped-skills.mjs +2 -0
- package/bin/ever-shipped-skills.test.mjs +8 -0
- package/bin/install.agents-home.test.mjs +15 -13
- package/bin/install.mjs +37 -1
- package/bin/install.policy-lint.test.mjs +78 -0
- package/bin/install.prune.test.mjs +149 -20
- package/bin/install.pstack-plugin.test.mjs +114 -0
- package/bin/install.test.mjs +61 -7
- 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/pii_prevention_blocker.py +32 -51
- package/hooks/blocking/test_bash_post_call_dispatcher.py +55 -17
- package/hooks/blocking/test_bash_pre_tool_use_dispatcher.py +1 -4
- package/hooks/blocking/test_code_rules_command_dispatch.py +2 -2
- 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_pii_prevention_blocker.py +37 -89
- 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/gh_post_body_texts.py +195 -0
- 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 -16
- package/hooks/hooks_constants/bash_post_call_dispatcher_constants.py +14 -2
- package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +0 -8
- package/hooks/hooks_constants/gh_post_body_texts_constants.py +12 -0
- 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 +17 -7
- package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +0 -7
- package/hooks/hooks_constants/test_failure_recorder_constants.py +3 -3
- package/hooks/hooks_constants/test_gh_post_body_texts_constants.py +19 -0
- 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/hooks/observability/test_failure_recorder.py +1 -1
- package/hooks/pyproject.toml +1 -1
- package/hooks/test_gh_post_body_texts.py +79 -0
- package/package.json +2 -2
- package/rules/durable-post-artifacts.md +32 -20
- package/rules/gh-cli-conventions.md +23 -13
- package/rules/git-workflow.md +21 -8
- 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/scripts/durable_post_lint.py +206 -0
- package/scripts/durable_post_lint_config/config/constants.py +96 -0
- package/scripts/durable_post_lint_config/config/test_constants.py +25 -0
- package/scripts/policy_lint/adapter_configuration.py +53 -12
- 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/repository_checks/config/constants.py +0 -5
- package/scripts/shared_tree_paths.py +32 -3
- package/scripts/test_durable_post_lint.py +332 -0
- package/scripts/test_shared_tree_paths.py +33 -0
- package/scripts/tests/test_adapter_configuration.py +54 -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/hooks/_gh_pr_author_swap_utils.py +0 -1225
- package/hooks/blocking/conventional_pr_title_gate.py +0 -443
- package/hooks/blocking/gh_body_arg_blocker.py +0 -176
- package/hooks/blocking/gh_pr_author_enforcer.py +0 -487
- package/hooks/blocking/gh_pr_author_restore.py +0 -105
- package/hooks/blocking/pr_description_writer_gate.py +0 -256
- package/hooks/blocking/test_conventional_pr_title_gate.py +0 -640
- package/hooks/blocking/test_gh_body_arg_blocker.py +0 -441
- package/hooks/blocking/test_gh_pr_author_enforcer.py +0 -1166
- package/hooks/blocking/test_gh_pr_author_restore.py +0 -513
- package/hooks/blocking/test_gh_pr_author_swap_utils.py +0 -910
- package/hooks/blocking/test_pr_description_writer_gate.py +0 -234
- package/hooks/blocking/test_preflight_check.py +0 -343
- package/hooks/blocking/test_test_preflight_check.py +0 -80
- package/hooks/blocking/test_volatile_path_in_post_blocker.py +0 -322
- package/hooks/blocking/volatile_path_in_post_blocker.py +0 -461
- package/hooks/hooks_constants/conventional_pr_title_gate_constants.py +0 -58
- package/hooks/hooks_constants/gh_pr_author_swap_constants.py +0 -76
- package/hooks/hooks_constants/pr_description_writer_gate_constants.py +0 -73
- package/hooks/hooks_constants/volatile_path_in_post_blocker_constants.py +0 -55
- package/hooks/observability/pr_description_writer_spawn_tracker.py +0 -113
- package/hooks/observability/test_pr_description_writer_spawn_tracker.py +0 -150
- package/hooks/session/gh_pr_author_session_cleanup.py +0 -175
- package/hooks/session/test_gh_pr_author_session_cleanup.py +0 -576
- package/hooks/test__gh_pr_author_swap_utils.py +0 -333
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Incremental migration tests for the hook-configuration lint rule."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from dataclasses import replace
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from policy_lint import adapters
|
|
10
|
+
from policy_lint.model import Document
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _hook_document(all_commands: list[str], prior_text: str) -> Document:
|
|
14
|
+
current_text = json.dumps(
|
|
15
|
+
{
|
|
16
|
+
"hooks": {
|
|
17
|
+
"PreToolUse": [
|
|
18
|
+
{
|
|
19
|
+
"hooks": [
|
|
20
|
+
{"command": each_command} for each_command in all_commands
|
|
21
|
+
]
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
)
|
|
27
|
+
return replace(
|
|
28
|
+
Document.from_text("hooks/hooks.json", current_text),
|
|
29
|
+
prior_text=prior_text,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_incremental_blocker_removal_is_clean(tmp_path: Path) -> None:
|
|
34
|
+
prior_text = _hook_document(
|
|
35
|
+
["hooks/blocking/first.py", "hooks/blocking/second.py"],
|
|
36
|
+
'{"hooks": {}}',
|
|
37
|
+
).text
|
|
38
|
+
current_document = _hook_document(
|
|
39
|
+
["hooks/blocking/second.py"],
|
|
40
|
+
prior_text,
|
|
41
|
+
)
|
|
42
|
+
assert adapters.hook_configuration_diagnostics(current_document, tmp_path) == ()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_new_blocker_in_staged_change_is_rejected(tmp_path: Path) -> None:
|
|
46
|
+
current_document = _hook_document(
|
|
47
|
+
["hooks/blocking/new.py"],
|
|
48
|
+
'{"hooks": {}}',
|
|
49
|
+
)
|
|
50
|
+
all_diagnostics = adapters.hook_configuration_diagnostics(
|
|
51
|
+
current_document, tmp_path
|
|
52
|
+
)
|
|
53
|
+
assert len(all_diagnostics) == 1
|
|
54
|
+
assert all_diagnostics[0].rule_id == "hook-configuration"
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""Archive boundaries for automatic and explicitly requested policy checks."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import subprocess
|
|
7
|
+
from pathlib import Path, PurePosixPath
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
from policy_lint.engine import _runtime_document_set, lint
|
|
11
|
+
from policy_lint.model import (
|
|
12
|
+
Diagnostic,
|
|
13
|
+
Document,
|
|
14
|
+
DocumentRule,
|
|
15
|
+
DocumentSet,
|
|
16
|
+
LintRequest,
|
|
17
|
+
Location,
|
|
18
|
+
SelectionKind,
|
|
19
|
+
Severity,
|
|
20
|
+
TextDocument,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _git(repository_root: Path, *arguments: str) -> None:
|
|
25
|
+
environment = {
|
|
26
|
+
variable_name: variable_text
|
|
27
|
+
for variable_name, variable_text in os.environ.items()
|
|
28
|
+
if not variable_name.upper().startswith("GIT_")
|
|
29
|
+
}
|
|
30
|
+
subprocess.run(
|
|
31
|
+
["git", *arguments], cwd=repository_root, env=environment,
|
|
32
|
+
check=True, capture_output=True, text=True,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@pytest.fixture
|
|
37
|
+
def archive_repository(tmp_path: Path) -> Path:
|
|
38
|
+
_git(tmp_path, "init", "--quiet", "-b", "main")
|
|
39
|
+
_git(tmp_path, "config", "user.name", "Archive Tests")
|
|
40
|
+
_git(tmp_path, "config", "user.email", "archive@example.invalid")
|
|
41
|
+
_git(tmp_path, "config", "commit.gpgsign", "false")
|
|
42
|
+
for relative_path in ("active.py", "skill-archive/old.py", "src/skill-archive/live.py"):
|
|
43
|
+
file_path = tmp_path / relative_path
|
|
44
|
+
file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
45
|
+
file_path.write_text("first\n", encoding="utf-8")
|
|
46
|
+
_git(tmp_path, "add", ".")
|
|
47
|
+
_git(tmp_path, "commit", "--quiet", "-m", "archive fixture")
|
|
48
|
+
for file_path in tmp_path.rglob("*.py"):
|
|
49
|
+
file_path.write_text("second\n", encoding="utf-8")
|
|
50
|
+
_git(tmp_path, "add", ".")
|
|
51
|
+
return tmp_path
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _marker_diagnostics(document: Document, repository_root: Path) -> tuple[Diagnostic, ...]:
|
|
55
|
+
assert (repository_root / document.path).is_file()
|
|
56
|
+
return (Diagnostic("marker", Severity.ERROR, "Checked document", Location(document.path, 1)),)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _accept_document(document: Document) -> bool:
|
|
60
|
+
return bool(document.path.parts)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _marker_rule() -> DocumentRule:
|
|
64
|
+
return DocumentRule(
|
|
65
|
+
"marker", frozenset({"changed", "repository"}),
|
|
66
|
+
_accept_document, _marker_diagnostics,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@pytest.mark.parametrize("selection", (SelectionKind.STAGED, SelectionKind.BASE, SelectionKind.REPOSITORY))
|
|
71
|
+
def test_automatic_checks_keep_live_code_and_exclude_only_root_archive(
|
|
72
|
+
archive_repository: Path, selection: SelectionKind,
|
|
73
|
+
) -> None:
|
|
74
|
+
requests = {
|
|
75
|
+
SelectionKind.STAGED: LintRequest.staged(archive_repository),
|
|
76
|
+
SelectionKind.BASE: LintRequest.base(archive_repository, "HEAD"),
|
|
77
|
+
SelectionKind.REPOSITORY: LintRequest.repository(archive_repository),
|
|
78
|
+
}
|
|
79
|
+
report = lint(requests[selection], all_registry=(_marker_rule(),))
|
|
80
|
+
assert report.checked_documents == (
|
|
81
|
+
PurePosixPath("active.py"), PurePosixPath("src/skill-archive/live.py"),
|
|
82
|
+
)
|
|
83
|
+
assert len(report.diagnostics) == 2
|
|
84
|
+
assert report.executed_rules == ("marker",)
|
|
85
|
+
assert report.exit_code == 1
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@pytest.mark.parametrize("selection", (SelectionKind.FILES, SelectionKind.TEXT))
|
|
89
|
+
def test_explicit_archive_checks_still_execute_rules(
|
|
90
|
+
archive_repository: Path, selection: SelectionKind,
|
|
91
|
+
) -> None:
|
|
92
|
+
archive_path = Path("skill-archive/old.py")
|
|
93
|
+
request = (
|
|
94
|
+
LintRequest.files(archive_repository, [archive_path])
|
|
95
|
+
if selection is SelectionKind.FILES
|
|
96
|
+
else LintRequest(archive_repository, TextDocument(archive_path, "editor text\n"))
|
|
97
|
+
)
|
|
98
|
+
report = lint(request, all_registry=(_marker_rule(),))
|
|
99
|
+
assert report.checked_documents == (PurePosixPath("skill-archive/old.py"),)
|
|
100
|
+
assert len(report.diagnostics) == 1
|
|
101
|
+
assert report.exit_code == 1
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def test_retirement_and_restoration_preserve_live_change_metadata(tmp_path: Path) -> None:
|
|
105
|
+
retired_path = PurePosixPath("active/retired.py")
|
|
106
|
+
archive_path = PurePosixPath("skill-archive/retired.py")
|
|
107
|
+
restored_source = PurePosixPath("skill-archive/restored.py")
|
|
108
|
+
restored_path = PurePosixPath("active/restored.py")
|
|
109
|
+
deleted_path = PurePosixPath("active/deleted.py")
|
|
110
|
+
document_set = DocumentSet(
|
|
111
|
+
(Document.from_text(archive_path, "archived"), Document.from_text(restored_path, "restored")),
|
|
112
|
+
SelectionKind.BASE,
|
|
113
|
+
tmp_path,
|
|
114
|
+
(deleted_path, PurePosixPath("skill-archive/deleted.py")),
|
|
115
|
+
((retired_path, archive_path), (restored_source, restored_path)),
|
|
116
|
+
)
|
|
117
|
+
runtime_set = _runtime_document_set(document_set)
|
|
118
|
+
assert tuple(document.path for document in runtime_set.documents) == (restored_path,)
|
|
119
|
+
assert runtime_set.deleted_paths == (deleted_path, retired_path)
|
|
120
|
+
assert runtime_set.renamed_paths == ((restored_source, restored_path),)
|
|
@@ -10,7 +10,7 @@ from pathlib import Path, PurePosixPath
|
|
|
10
10
|
import pytest
|
|
11
11
|
from policy_lint import adapters
|
|
12
12
|
from policy_lint.config.constants import INCOMPLETE_EXIT_CODE
|
|
13
|
-
from policy_lint.engine import lint
|
|
13
|
+
from policy_lint.engine import _runtime_document_set, lint
|
|
14
14
|
from policy_lint.model import (
|
|
15
15
|
ContentOrigin,
|
|
16
16
|
Diagnostic,
|
|
@@ -309,7 +309,11 @@ def test_state_description_diagnostics_maps_and_scopes_phrases(
|
|
|
309
309
|
lint_report = _lint_documents(
|
|
310
310
|
tmp_path,
|
|
311
311
|
(source_document,),
|
|
312
|
-
(
|
|
312
|
+
(
|
|
313
|
+
_changed_document_rule(
|
|
314
|
+
"state-description", adapters.state_description_diagnostics
|
|
315
|
+
),
|
|
316
|
+
),
|
|
313
317
|
)
|
|
314
318
|
assert len(lint_report.diagnostics) == 1
|
|
315
319
|
assert lint_report.diagnostics[0].location is not None
|
|
@@ -351,8 +355,12 @@ def test_failed_adapter_rolls_back_continues_and_exits_incomplete(
|
|
|
351
355
|
_source_document("pkg/second.py"),
|
|
352
356
|
)
|
|
353
357
|
all_rules = (
|
|
354
|
-
_changed_document_rule(
|
|
355
|
-
|
|
358
|
+
_changed_document_rule(
|
|
359
|
+
"flaky", lambda document, _root: _flaky_diagnostics(document)
|
|
360
|
+
),
|
|
361
|
+
_changed_document_rule(
|
|
362
|
+
"stable", lambda document, _root: _stable_diagnostics(document)
|
|
363
|
+
),
|
|
356
364
|
)
|
|
357
365
|
lint_report = _lint_documents(repository_root, all_documents, all_rules)
|
|
358
366
|
assert lint_report.failed_rules == ("flaky",)
|
|
@@ -379,3 +387,16 @@ def test_process_interrupt_is_not_swallowed(tmp_path: Path) -> None:
|
|
|
379
387
|
(_source_document("pkg/mod.py"),),
|
|
380
388
|
(_changed_document_rule("interrupt", interrupt_diagnostics),),
|
|
381
389
|
)
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def test_runtime_document_set_keeps_base_revision_for_base_selection(
|
|
393
|
+
tmp_path: Path,
|
|
394
|
+
) -> None:
|
|
395
|
+
document_set = DocumentSet(
|
|
396
|
+
(_source_document("pkg/mod.py"),),
|
|
397
|
+
SelectionKind.BASE,
|
|
398
|
+
tmp_path,
|
|
399
|
+
base_revision="abc123def",
|
|
400
|
+
)
|
|
401
|
+
runtime_document_set = _runtime_document_set(document_set)
|
|
402
|
+
assert runtime_document_set.base_revision == "abc123def"
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Load terminology through the verified installed shared-tree resolver."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
from policy_lint import adapters
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_pr_loop_loader_uses_the_shared_tree_resolver(
|
|
13
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch,
|
|
14
|
+
) -> None:
|
|
15
|
+
module_name = "policy_fixture_terminology"
|
|
16
|
+
(tmp_path / f"{module_name}.py").write_text("IS_FIXTURE = True\n", encoding="utf-8")
|
|
17
|
+
all_calls: list[tuple[object, ...]] = []
|
|
18
|
+
|
|
19
|
+
def resolve_directory(*all_arguments: object) -> Path:
|
|
20
|
+
all_calls.append(all_arguments)
|
|
21
|
+
return tmp_path
|
|
22
|
+
|
|
23
|
+
monkeypatch.setattr(adapters, "resolve_shared_scripts_directory", resolve_directory)
|
|
24
|
+
monkeypatch.setattr(sys, "path", list(sys.path))
|
|
25
|
+
loaded = adapters._pr_loop_script_module(module_name)
|
|
26
|
+
assert loaded.IS_FIXTURE is True
|
|
27
|
+
assert Path(loaded.__file__).parent == tmp_path
|
|
28
|
+
assert all_calls[0][2:] == ("pr-loop", f"{module_name}.py", 2)
|
|
29
|
+
monkeypatch.delitem(sys.modules, module_name)
|
|
@@ -15,6 +15,7 @@ from policy_lint.model import (
|
|
|
15
15
|
Document,
|
|
16
16
|
DocumentRule,
|
|
17
17
|
DocumentSet,
|
|
18
|
+
LintReport,
|
|
18
19
|
LintRequest,
|
|
19
20
|
SelectionKind,
|
|
20
21
|
)
|
|
@@ -96,6 +97,164 @@ def _terminology_rule() -> ChangeSetRule:
|
|
|
96
97
|
return terminology_rule
|
|
97
98
|
|
|
98
99
|
|
|
100
|
+
def _head_revision(repository_root: Path) -> str:
|
|
101
|
+
completed = subprocess.run(
|
|
102
|
+
("git", "rev-parse", "HEAD"),
|
|
103
|
+
cwd=repository_root,
|
|
104
|
+
check=True,
|
|
105
|
+
capture_output=True,
|
|
106
|
+
text=True,
|
|
107
|
+
)
|
|
108
|
+
return completed.stdout.strip()
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _git_add(repository_root: Path, *all_paths: str) -> None:
|
|
112
|
+
subprocess.run(
|
|
113
|
+
("git", "add", *all_paths),
|
|
114
|
+
cwd=repository_root,
|
|
115
|
+
check=True,
|
|
116
|
+
capture_output=True,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _quota_document_paths(
|
|
121
|
+
repository_root: Path,
|
|
122
|
+
source_name: str = "quota.py",
|
|
123
|
+
documentation_name: str = "README.md",
|
|
124
|
+
) -> tuple[Path, Path]:
|
|
125
|
+
source_path = repository_root / "src" / source_name
|
|
126
|
+
documentation_path = repository_root / "docs" / documentation_name
|
|
127
|
+
source_path.parent.mkdir(parents=True, exist_ok=True)
|
|
128
|
+
documentation_path.parent.mkdir(parents=True, exist_ok=True)
|
|
129
|
+
return source_path, documentation_path
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _write_text(file_path: Path, text: str) -> None:
|
|
133
|
+
file_path.write_text(text, encoding="utf-8")
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _commit_quota_base(
|
|
137
|
+
repository_root: Path,
|
|
138
|
+
source_text: str,
|
|
139
|
+
documentation_text: str,
|
|
140
|
+
source_name: str = "quota.py",
|
|
141
|
+
documentation_name: str = "README.md",
|
|
142
|
+
) -> tuple[Path, Path, str]:
|
|
143
|
+
_ensure_git_repository(repository_root)
|
|
144
|
+
source_path, documentation_path = _quota_document_paths(
|
|
145
|
+
repository_root, source_name, documentation_name
|
|
146
|
+
)
|
|
147
|
+
_write_text(source_path, source_text)
|
|
148
|
+
_write_text(documentation_path, documentation_text)
|
|
149
|
+
_commit_repository(repository_root, "base")
|
|
150
|
+
return source_path, documentation_path, _head_revision(repository_root)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _commit_identifier_then_documentation(
|
|
154
|
+
repository_root: Path,
|
|
155
|
+
source_path: Path,
|
|
156
|
+
documentation_path: Path,
|
|
157
|
+
) -> None:
|
|
158
|
+
_write_text(source_path, "baseline = 1\npremium_request_interactions = 5\n")
|
|
159
|
+
_commit_repository(repository_root, "identifier")
|
|
160
|
+
_write_text(
|
|
161
|
+
documentation_path,
|
|
162
|
+
"The premium-request-budget field gates the run.\n",
|
|
163
|
+
)
|
|
164
|
+
_commit_repository(repository_root, "documentation")
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _committed_near_miss_from_base(
|
|
168
|
+
repository_root: Path,
|
|
169
|
+
) -> tuple[Path, Path, str]:
|
|
170
|
+
source_path, documentation_path, comparison_revision = _commit_quota_base(
|
|
171
|
+
repository_root,
|
|
172
|
+
"baseline = 1\n",
|
|
173
|
+
"Baseline quota text.\n",
|
|
174
|
+
)
|
|
175
|
+
_commit_identifier_then_documentation(
|
|
176
|
+
repository_root, source_path, documentation_path
|
|
177
|
+
)
|
|
178
|
+
return source_path, documentation_path, comparison_revision
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _stage_unrelated_index_file(repository_root: Path) -> None:
|
|
182
|
+
_write_text(repository_root / "staged.txt", "unrelated index content\n")
|
|
183
|
+
_git_add(repository_root, "--", "staged.txt")
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _worktree_and_index_near_miss_repository(repository_root: Path) -> str:
|
|
187
|
+
source_path, documentation_path, comparison_revision = (
|
|
188
|
+
_committed_near_miss_from_base(repository_root)
|
|
189
|
+
)
|
|
190
|
+
_write_text(
|
|
191
|
+
source_path,
|
|
192
|
+
"baseline = 1\npremium_request_interactions = 5\nanother_request_total = 2\n",
|
|
193
|
+
)
|
|
194
|
+
_write_text(
|
|
195
|
+
documentation_path,
|
|
196
|
+
"The another-request-budget field gates the run.\n",
|
|
197
|
+
)
|
|
198
|
+
_git_add(repository_root, "-A")
|
|
199
|
+
_write_text(
|
|
200
|
+
documentation_path,
|
|
201
|
+
"The another-request-total field gates the run.\n",
|
|
202
|
+
)
|
|
203
|
+
return comparison_revision
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _existing_identifier_from_base(repository_root: Path) -> str:
|
|
207
|
+
source_path, documentation_path, comparison_revision = _commit_quota_base(
|
|
208
|
+
repository_root,
|
|
209
|
+
"premium_request_interactions = 5\n",
|
|
210
|
+
"Baseline quota text.\n",
|
|
211
|
+
)
|
|
212
|
+
_write_text(
|
|
213
|
+
source_path,
|
|
214
|
+
"premium_request_interactions = 5\npremium_request_interactions = 6\n",
|
|
215
|
+
)
|
|
216
|
+
_write_text(
|
|
217
|
+
documentation_path,
|
|
218
|
+
"The premium-request-budget field gates the run.\n",
|
|
219
|
+
)
|
|
220
|
+
_commit_repository(repository_root, "existing identifier")
|
|
221
|
+
return comparison_revision
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def _renamed_and_deleted_near_miss_from_base(repository_root: Path) -> str:
|
|
225
|
+
old_source_path, deleted_documentation_path, comparison_revision = (
|
|
226
|
+
_commit_quota_base(
|
|
227
|
+
repository_root,
|
|
228
|
+
"baseline = 1\n",
|
|
229
|
+
"Obsolete quota text.\n",
|
|
230
|
+
"old_quota.py",
|
|
231
|
+
"obsolete.md",
|
|
232
|
+
)
|
|
233
|
+
)
|
|
234
|
+
new_source_path = repository_root / "src" / "new_quota.py"
|
|
235
|
+
subprocess.run(
|
|
236
|
+
("git", "mv", "--", str(old_source_path), str(new_source_path)),
|
|
237
|
+
cwd=repository_root,
|
|
238
|
+
check=True,
|
|
239
|
+
capture_output=True,
|
|
240
|
+
)
|
|
241
|
+
_write_text(new_source_path, "baseline = 1\npremium_request_interactions = 5\n")
|
|
242
|
+
deleted_documentation_path.unlink()
|
|
243
|
+
_write_text(
|
|
244
|
+
repository_root / "docs" / "README.md",
|
|
245
|
+
"The premium-request-budget field gates the run.\n",
|
|
246
|
+
)
|
|
247
|
+
_commit_repository(repository_root, "rename and delete")
|
|
248
|
+
return comparison_revision
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def _lint_base(repository_root: Path, comparison_revision: str) -> LintReport:
|
|
252
|
+
return lint(
|
|
253
|
+
LintRequest.base(repository_root, comparison_revision),
|
|
254
|
+
all_registry=(_terminology_rule(),),
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
|
|
99
258
|
def test_hook_format_accepts_claude_settings() -> None:
|
|
100
259
|
settings_document = Document.from_text(".claude/settings.json", "{}")
|
|
101
260
|
hooks_document = Document.from_text("hooks/hooks.json", "{}")
|
|
@@ -161,7 +320,7 @@ def test_test_pairing_diagnostics_exempts_constants_only_python_modules(
|
|
|
161
320
|
(
|
|
162
321
|
Document.from_text(
|
|
163
322
|
"hooks_constants/tool_names.py",
|
|
164
|
-
|
|
323
|
+
'TOOL_NAME: str = "tool"\n',
|
|
165
324
|
),
|
|
166
325
|
),
|
|
167
326
|
SelectionKind.STAGED,
|
|
@@ -244,9 +403,7 @@ def test_hook_configuration_diagnostics_rejects_registered_action_boundary_path(
|
|
|
244
403
|
json.dumps(
|
|
245
404
|
{
|
|
246
405
|
"hooks": {
|
|
247
|
-
"PreToolUse": [
|
|
248
|
-
{"hooks": [{"command": "hooks/blocking/deny.py"}]}
|
|
249
|
-
]
|
|
406
|
+
"PreToolUse": [{"hooks": [{"command": "hooks/blocking/deny.py"}]}]
|
|
250
407
|
}
|
|
251
408
|
}
|
|
252
409
|
),
|
|
@@ -263,7 +420,9 @@ def test_hook_configuration_diagnostics_ignore_description_text(tmp_path: Path)
|
|
|
263
420
|
"hooks/hooks.json",
|
|
264
421
|
json.dumps({"description": "blocking safety controls", "hooks": {}}),
|
|
265
422
|
)
|
|
266
|
-
assert
|
|
423
|
+
assert (
|
|
424
|
+
adapters.hook_configuration_diagnostics(configuration_document, tmp_path) == ()
|
|
425
|
+
)
|
|
267
426
|
|
|
268
427
|
|
|
269
428
|
def test_hook_configuration_diagnostics_ignore_task_path(tmp_path: Path) -> None:
|
|
@@ -271,7 +430,9 @@ def test_hook_configuration_diagnostics_ignore_task_path(tmp_path: Path) -> None
|
|
|
271
430
|
"hooks/hooks.json",
|
|
272
431
|
json.dumps({"hooks": {"PostToolUse": [{"command": "task_runner.py"}]}}),
|
|
273
432
|
)
|
|
274
|
-
assert
|
|
433
|
+
assert (
|
|
434
|
+
adapters.hook_configuration_diagnostics(configuration_document, tmp_path) == ()
|
|
435
|
+
)
|
|
275
436
|
|
|
276
437
|
|
|
277
438
|
def test_registry_omits_native_ruff_mypy_javascript_adapters() -> None:
|
|
@@ -291,7 +452,9 @@ def test_registry_registers_unique_python_validator_rule() -> None:
|
|
|
291
452
|
def test_registry_registers_terminology_change_set_rule() -> None:
|
|
292
453
|
terminology_rule = _terminology_rule()
|
|
293
454
|
assert terminology_rule.rule_sets == _CHANGED_RULE_SETS
|
|
294
|
-
assert terminology_rule.selections == frozenset(
|
|
455
|
+
assert terminology_rule.selections == frozenset(
|
|
456
|
+
{SelectionKind.STAGED, SelectionKind.BASE}
|
|
457
|
+
)
|
|
295
458
|
|
|
296
459
|
|
|
297
460
|
def test_registry_runs_test_pairing_for_staged_and_base_changes() -> None:
|
|
@@ -313,10 +476,11 @@ def test_terminology_rule_reports_cross_surface_path_and_line(
|
|
|
313
476
|
tmp_path,
|
|
314
477
|
"baseline = 1\n",
|
|
315
478
|
"premium_request_interactions = 5\n",
|
|
316
|
-
"Quota fields use one name.\n"
|
|
317
|
-
|
|
479
|
+
"Quota fields use one name.\nThe premium-request-budget field gates the run.\n",
|
|
480
|
+
)
|
|
481
|
+
lint_report = lint(
|
|
482
|
+
LintRequest.staged(tmp_path), all_registry=(_terminology_rule(),)
|
|
318
483
|
)
|
|
319
|
-
lint_report = lint(LintRequest.staged(tmp_path), all_registry=(_terminology_rule(),))
|
|
320
484
|
|
|
321
485
|
assert len(lint_report.diagnostics) == 1
|
|
322
486
|
diagnostic = lint_report.diagnostics[0]
|
|
@@ -332,15 +496,65 @@ def test_terminology_rule_suppresses_identifier_present_in_prior_tree(
|
|
|
332
496
|
_stage_terminology_change(
|
|
333
497
|
tmp_path,
|
|
334
498
|
"premium_request_interactions = 5\n",
|
|
335
|
-
"premium_request_interactions = 5\n"
|
|
336
|
-
"premium_request_interactions = 6\n",
|
|
499
|
+
"premium_request_interactions = 5\npremium_request_interactions = 6\n",
|
|
337
500
|
"The premium-request-budget field gates the run.\n",
|
|
338
501
|
)
|
|
339
|
-
lint_report = lint(
|
|
502
|
+
lint_report = lint(
|
|
503
|
+
LintRequest.staged(tmp_path), all_registry=(_terminology_rule(),)
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
assert lint_report.diagnostics == ()
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
def test_terminology_rule_reports_committed_changes_from_base(
|
|
510
|
+
tmp_path: Path,
|
|
511
|
+
) -> None:
|
|
512
|
+
_, _, comparison_revision = _committed_near_miss_from_base(tmp_path)
|
|
513
|
+
_stage_unrelated_index_file(tmp_path)
|
|
514
|
+
lint_report = _lint_base(tmp_path, comparison_revision)
|
|
515
|
+
assert lint_report.failed_rules == ()
|
|
516
|
+
assert len(lint_report.diagnostics) == 1
|
|
517
|
+
assert lint_report.diagnostics[0].location is not None
|
|
518
|
+
assert lint_report.diagnostics[0].location.path == PurePosixPath("docs/README.md")
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
def test_terminology_base_uses_worktree_while_staged_uses_index(
|
|
522
|
+
tmp_path: Path,
|
|
523
|
+
) -> None:
|
|
524
|
+
comparison_revision = _worktree_and_index_near_miss_repository(tmp_path)
|
|
525
|
+
base_report = _lint_base(tmp_path, comparison_revision)
|
|
526
|
+
staged_report = lint(
|
|
527
|
+
LintRequest.staged(tmp_path),
|
|
528
|
+
all_registry=(_terminology_rule(),),
|
|
529
|
+
)
|
|
530
|
+
assert base_report.failed_rules == ()
|
|
531
|
+
assert staged_report.failed_rules == ()
|
|
532
|
+
assert base_report.diagnostics == ()
|
|
533
|
+
assert len(staged_report.diagnostics) == 1
|
|
534
|
+
assert staged_report.diagnostics[0].location is not None
|
|
535
|
+
assert staged_report.diagnostics[0].location.path == PurePosixPath("docs/README.md")
|
|
340
536
|
|
|
537
|
+
|
|
538
|
+
def test_terminology_base_suppresses_identifier_present_in_comparison_base(
|
|
539
|
+
tmp_path: Path,
|
|
540
|
+
) -> None:
|
|
541
|
+
comparison_revision = _existing_identifier_from_base(tmp_path)
|
|
542
|
+
lint_report = _lint_base(tmp_path, comparison_revision)
|
|
543
|
+
assert lint_report.failed_rules == ()
|
|
341
544
|
assert lint_report.diagnostics == ()
|
|
342
545
|
|
|
343
546
|
|
|
547
|
+
def test_terminology_base_handles_renames_and_deletions(
|
|
548
|
+
tmp_path: Path,
|
|
549
|
+
) -> None:
|
|
550
|
+
comparison_revision = _renamed_and_deleted_near_miss_from_base(tmp_path)
|
|
551
|
+
lint_report = _lint_base(tmp_path, comparison_revision)
|
|
552
|
+
assert lint_report.failed_rules == ()
|
|
553
|
+
assert len(lint_report.diagnostics) == 1
|
|
554
|
+
assert lint_report.diagnostics[0].location is not None
|
|
555
|
+
assert lint_report.diagnostics[0].location.path == PurePosixPath("docs/README.md")
|
|
556
|
+
|
|
557
|
+
|
|
344
558
|
def test_terminology_rule_skips_file_selection(tmp_path: Path) -> None:
|
|
345
559
|
document_set = DocumentSet(
|
|
346
560
|
(Document.from_text("docs/README.md", "text\n"),),
|
|
@@ -28,7 +28,7 @@ def _git_environment() -> dict[str, str]:
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
|
|
31
|
-
def
|
|
31
|
+
def _git_stdout(repository_root: Path, *all_arguments: str) -> str:
|
|
32
32
|
completed = subprocess.run(
|
|
33
33
|
[constants.GIT_EXECUTABLE, *all_arguments],
|
|
34
34
|
cwd=repository_root,
|
|
@@ -38,6 +38,11 @@ def _run_git(repository_root: Path, *all_arguments: str) -> None:
|
|
|
38
38
|
env=_git_environment(),
|
|
39
39
|
)
|
|
40
40
|
assert completed.returncode == 0, completed.stderr
|
|
41
|
+
return completed.stdout.strip()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _run_git(repository_root: Path, *all_arguments: str) -> None:
|
|
45
|
+
_git_stdout(repository_root, *all_arguments)
|
|
41
46
|
|
|
42
47
|
|
|
43
48
|
def _initialize_repository(repository_root: Path) -> Path:
|
|
@@ -200,7 +205,9 @@ def test_staged_missing_non_delete_blob_should_raise(
|
|
|
200
205
|
def test_git_blob_unexpected_failure_should_raise(
|
|
201
206
|
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
202
207
|
) -> None:
|
|
203
|
-
def failed_git(
|
|
208
|
+
def failed_git(
|
|
209
|
+
*_all_arguments: object, **_all_keywords: object
|
|
210
|
+
) -> subprocess.CompletedProcess[bytes]:
|
|
204
211
|
return subprocess.CompletedProcess(
|
|
205
212
|
args=[constants.GIT_EXECUTABLE],
|
|
206
213
|
returncode=1,
|
|
@@ -254,6 +261,22 @@ def test_base_source_should_compare_merge_base_and_read_worktree_bytes(
|
|
|
254
261
|
assert selected_document.origin == ContentOrigin.REVISION_DIFF
|
|
255
262
|
|
|
256
263
|
|
|
264
|
+
def test_base_selection_records_merge_base_revision_and_staged_does_not(
|
|
265
|
+
tmp_path: Path,
|
|
266
|
+
) -> None:
|
|
267
|
+
repository_root = _initialize_repository(tmp_path)
|
|
268
|
+
_commit_text(repository_root, "file.py", "base-text\n")
|
|
269
|
+
_run_git(repository_root, "checkout", "--quiet", "-b", "feature")
|
|
270
|
+
_commit_text(repository_root, "file.py", "feature-committed\n")
|
|
271
|
+
|
|
272
|
+
merge_base = _git_stdout(repository_root, "merge-base", "main", "HEAD")
|
|
273
|
+
base_document_set = select_documents(LintRequest.base(repository_root, "main"))
|
|
274
|
+
staged_document_set = select_documents(LintRequest.staged(repository_root))
|
|
275
|
+
|
|
276
|
+
assert base_document_set.base_revision == merge_base
|
|
277
|
+
assert staged_document_set.base_revision is None
|
|
278
|
+
|
|
279
|
+
|
|
257
280
|
def test_repository_source_should_read_tracked_worktree_files_only(
|
|
258
281
|
tmp_path: Path,
|
|
259
282
|
) -> None:
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Preserve Git's active index without accepting repository-directory overrides."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
from policy_lint import selection_git
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_active_git_index_survives_environment_sanitization(
|
|
12
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch,
|
|
13
|
+
) -> None:
|
|
14
|
+
index_path = tmp_path / "index"
|
|
15
|
+
monkeypatch.setenv("GIT_INDEX_FILE", str(index_path))
|
|
16
|
+
monkeypatch.setenv("GIT_DIR", "foreign-repository")
|
|
17
|
+
monkeypatch.setenv("GIT_WORK_TREE", "foreign-worktree")
|
|
18
|
+
monkeypatch.setenv("PRESERVED_SETTING", "fixture")
|
|
19
|
+
active_environment = selection_git._git_subprocess_environment()
|
|
20
|
+
assert active_environment["GIT_INDEX_FILE"] == str(index_path)
|
|
21
|
+
assert "GIT_DIR" not in active_environment
|
|
22
|
+
assert "GIT_WORK_TREE" not in active_environment
|
|
23
|
+
assert active_environment["PRESERVED_SETTING"] == "fixture"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_default_index_is_not_replaced_by_an_invented_path(
|
|
27
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
28
|
+
) -> None:
|
|
29
|
+
monkeypatch.delenv("GIT_INDEX_FILE", raising=False)
|
|
30
|
+
assert "GIT_INDEX_FILE" not in selection_git._git_subprocess_environment()
|