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.
Files changed (350) hide show
  1. package/.agents/agents/pr-description-writer.md +2 -2
  2. package/.agents/agents/test_agent_frontmatter.py +2 -2
  3. package/.agents/agents/test_pr_description_writer_style.py +1 -3
  4. package/.agents/skills/e-code-review/SKILL.md +12 -12
  5. package/.agents/skills/e-code-review/preflight-proposal.contract.test.mjs +20 -0
  6. package/.agents/skills/e-code-review/reference/fix.md +15 -17
  7. package/.agents/skills/e-code-review/reference/loop.md +35 -39
  8. package/.agents/skills/e-code-review/reference/mode-contract.test.mjs +2 -1
  9. package/.agents/skills/e-code-review/reference/preflight-proposal.md +44 -9
  10. package/.agents/skills/e-code-review/reference/runner-selection.md +13 -14
  11. package/.agents/skills/issue-tracker/SKILL.md +7 -0
  12. package/.agents/skills/pull-request/SKILL.md +216 -0
  13. package/.agents/skills/pull-request/reference/publication-tasks.md +18 -0
  14. package/.agents/skills/pull-request/scripts/github_pr_command_constants/__init__.py +1 -0
  15. package/.agents/skills/pull-request/scripts/github_pr_command_constants/config/__init__.py +1 -0
  16. package/.agents/skills/pull-request/scripts/github_pr_command_constants/config/constants.py +69 -0
  17. package/.agents/skills/pull-request/scripts/pull_request.py +265 -0
  18. package/.agents/skills/pull-request/scripts/recover_legacy_author.py +251 -0
  19. package/.agents/skills/pull-request/scripts/test_github_pr_command_constants.py +58 -0
  20. package/.agents/skills/pull-request/scripts/test_pull_request.py +396 -0
  21. package/.agents/skills/pull-request/scripts/test_recover_legacy_author.py +221 -0
  22. package/.agents/skills/skill-builder/SKILL.md +3 -196
  23. package/.agents/skills/test-runner/SKILL.md +69 -0
  24. package/.agents/skills/test-runner/scripts/preflight_checks.py +365 -0
  25. package/.agents/skills/test-runner/scripts/run_tests.py +316 -0
  26. package/.agents/skills/test-runner/scripts/test_preflight_checks.py +185 -0
  27. package/.agents/skills/test-runner/scripts/test_run_tests.py +281 -0
  28. package/.agents/skills/test-runner/scripts/test_runner_constants/__init__.py +1 -0
  29. package/.agents/skills/test-runner/scripts/test_runner_constants/config/__init__.py +1 -0
  30. package/.agents/skills/test-runner/scripts/test_runner_constants/config/constants.py +96 -0
  31. package/.agents/skills-archived/pr-loop-cloud-transport/reference/identity-and-hooks.md +1 -1
  32. package/_shared/pr-loop/scripts/pr_loop_shared_constants/terminology_sweep_constants.py +6 -0
  33. package/_shared/pr-loop/scripts/terminology_sweep.py +64 -19
  34. package/_shared/pr-loop/scripts/tests/test_terminology_sweep.py +59 -0
  35. package/bin/ever-shipped-skills.mjs +2 -0
  36. package/bin/ever-shipped-skills.test.mjs +8 -0
  37. package/bin/install.agents-home.test.mjs +15 -13
  38. package/bin/install.mjs +37 -1
  39. package/bin/install.policy-lint.test.mjs +78 -0
  40. package/bin/install.prune.test.mjs +149 -20
  41. package/bin/install.pstack-plugin.test.mjs +114 -0
  42. package/bin/install.test.mjs +61 -7
  43. package/bin/refresh-pstack-plugin-skills.test.mjs +211 -0
  44. package/hooks/advisory/pr_done_reminder.py +342 -0
  45. package/hooks/advisory/test_pr_done_reminder.py +249 -0
  46. package/hooks/blocking/bash_post_call_dispatcher.py +53 -6
  47. package/hooks/blocking/code_rules_paired_test.py +22 -15
  48. package/hooks/blocking/pii_prevention_blocker.py +32 -51
  49. package/hooks/blocking/test_bash_post_call_dispatcher.py +55 -17
  50. package/hooks/blocking/test_bash_pre_tool_use_dispatcher.py +1 -4
  51. package/hooks/blocking/test_code_rules_command_dispatch.py +2 -2
  52. package/hooks/blocking/test_code_rules_enforcer_paired_test.py +36 -29
  53. package/hooks/blocking/test_code_rules_js_resume_task_enumeration.py +4 -4
  54. package/hooks/blocking/test_code_rules_js_returns_object_schemaless.py +5 -5
  55. package/hooks/blocking/test_pii_prevention_blocker.py +37 -89
  56. package/hooks/blocking/test_pre_tool_use_dispatcher.py +68 -133
  57. package/hooks/blocking/test_pre_tool_use_dispatcher_native.py +20 -51
  58. package/hooks/blocking/test_precommit_code_rules_gate_native_owner.py +18 -18
  59. package/hooks/gh_post_body_texts.py +195 -0
  60. package/hooks/git-hooks/git_hooks_constants/staged_policy_lint.py +12 -0
  61. package/hooks/git-hooks/pre_commit.py +52 -11
  62. package/hooks/git-hooks/test_installed_policy_lint.py +159 -0
  63. package/hooks/git-hooks/test_native_policy_commit.py +87 -0
  64. package/hooks/git-hooks/test_pre_commit.py +246 -0
  65. package/hooks/hooks.json +1 -16
  66. package/hooks/hooks_constants/bash_post_call_dispatcher_constants.py +14 -2
  67. package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +0 -8
  68. package/hooks/hooks_constants/gh_post_body_texts_constants.py +12 -0
  69. package/hooks/hooks_constants/pr_done_reminder_constants.py +98 -0
  70. package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +10 -43
  71. package/hooks/hooks_constants/test_bash_post_call_dispatcher_constants.py +17 -7
  72. package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +0 -7
  73. package/hooks/hooks_constants/test_failure_recorder_constants.py +3 -3
  74. package/hooks/hooks_constants/test_gh_post_body_texts_constants.py +19 -0
  75. package/hooks/hooks_constants/test_pr_done_reminder_constants.py +40 -0
  76. package/hooks/hooks_constants/test_pre_tool_use_dispatcher_constants.py +20 -32
  77. package/hooks/observability/test_failure_recorder.py +1 -1
  78. package/hooks/pyproject.toml +1 -1
  79. package/hooks/test_gh_post_body_texts.py +79 -0
  80. package/package.json +2 -2
  81. package/rules/durable-post-artifacts.md +32 -20
  82. package/rules/gh-cli-conventions.md +23 -13
  83. package/rules/git-workflow.md +21 -8
  84. package/scripts/check.ps1 +0 -1
  85. package/{.agents/skills/run-claude-dev-env/driver.mjs → scripts/ci/installer-lifecycle.mjs} +3 -28
  86. package/scripts/ci/installer-lifecycle.test.mjs +26 -0
  87. package/scripts/ci/windows-installer-lifecycle.ps1 +1 -11
  88. package/scripts/dev_env_scripts_constants/shared_tree_constants.py +4 -5
  89. package/scripts/durable_post_lint.py +206 -0
  90. package/scripts/durable_post_lint_config/config/constants.py +96 -0
  91. package/scripts/durable_post_lint_config/config/test_constants.py +25 -0
  92. package/scripts/policy_lint/adapter_configuration.py +53 -12
  93. package/scripts/policy_lint/adapter_detectors.py +13 -6
  94. package/scripts/policy_lint/adapters.py +7 -4
  95. package/scripts/policy_lint/config/constants.py +5 -0
  96. package/scripts/policy_lint/engine.py +48 -4
  97. package/scripts/policy_lint/model.py +1 -0
  98. package/scripts/policy_lint/registry.py +1 -1
  99. package/scripts/policy_lint/selection.py +2 -1
  100. package/scripts/policy_lint/selection_git.py +2 -0
  101. package/scripts/refresh_pstack_plugin_skills.mjs +200 -0
  102. package/scripts/repository_checks/config/constants.py +0 -5
  103. package/scripts/shared_tree_paths.py +32 -3
  104. package/scripts/test_durable_post_lint.py +332 -0
  105. package/scripts/test_shared_tree_paths.py +33 -0
  106. package/scripts/tests/test_adapter_configuration.py +54 -0
  107. package/scripts/tests/test_policy_lint_rules_archive.py +120 -0
  108. package/scripts/tests/test_policy_lint_rules_engine.py +25 -4
  109. package/scripts/tests/test_policy_lint_rules_installed_loader.py +29 -0
  110. package/scripts/tests/test_policy_lint_rules_registry.py +227 -13
  111. package/scripts/tests/test_policy_lint_selection.py +25 -2
  112. package/scripts/tests/test_policy_lint_selection_native_index.py +30 -0
  113. package/scripts/tests/test_rules.py +3 -7
  114. package/.agents/skills/_shared/pr-loop/.claude/CLAUDE.md +0 -1
  115. package/.agents/skills/_shared/pr-loop/AGENTS.md +0 -1
  116. package/.agents/skills/_shared/pr-loop/audit-contract.md +0 -5
  117. package/.agents/skills/_shared/pr-loop/audit-reply-template.md +0 -5
  118. package/.agents/skills/_shared/pr-loop/code-rules-gate.md +0 -5
  119. package/.agents/skills/_shared/pr-loop/fix-protocol.md +0 -5
  120. package/.agents/skills/_shared/pr-loop/gh-payloads.md +0 -5
  121. package/.agents/skills/_shared/pr-loop/portable-driver.md +0 -149
  122. package/.agents/skills/_shared/pr-loop/post-audit-thread-contract.md +0 -5
  123. package/.agents/skills/_shared/pr-loop/precatch-rubric.md +0 -5
  124. package/.agents/skills/_shared/pr-loop/preflight-proposal.contract.test.mjs +0 -119
  125. package/.agents/skills/_shared/pr-loop/preflight-proposal.md +0 -5
  126. package/.agents/skills/_shared/pr-loop/prompts/.claude/CLAUDE.md +0 -1
  127. package/.agents/skills/_shared/pr-loop/prompts/AGENTS.md +0 -1
  128. package/.agents/skills/_shared/pr-loop/prompts/pr-consistency-audit.xml +0 -324
  129. package/.agents/skills/_shared/pr-loop/scripts/.claude/CLAUDE.md +0 -1
  130. package/.agents/skills/_shared/pr-loop/scripts/AGENTS.md +0 -1
  131. package/.agents/skills/_shared/pr-loop/scripts/RUNTIME_SCRIPTS.md +0 -29
  132. package/.agents/skills/_shared/pr-loop/scripts/_cli_utils.py +0 -22
  133. package/.agents/skills/_shared/pr-loop/scripts/_path_resolver.py +0 -186
  134. package/.agents/skills/_shared/pr-loop/scripts/_xml_utils.py +0 -20
  135. package/.agents/skills/_shared/pr-loop/scripts/audit_category_schema.py +0 -355
  136. package/.agents/skills/_shared/pr-loop/scripts/build_audit_prompt.py +0 -289
  137. package/.agents/skills/_shared/pr-loop/scripts/build_converge_task_list.py +0 -310
  138. package/.agents/skills/_shared/pr-loop/scripts/build_fix_prompt.py +0 -292
  139. package/.agents/skills/_shared/pr-loop/scripts/init_loop_state.py +0 -134
  140. package/.agents/skills/_shared/pr-loop/scripts/portable_converge_driver.py +0 -1646
  141. package/.agents/skills/_shared/pr-loop/scripts/preflight_worktree.py +0 -392
  142. package/.agents/skills/_shared/pr-loop/scripts/select_converge_pacer.py +0 -215
  143. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/.claude/CLAUDE.md +0 -1
  144. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/AGENTS.md +0 -1
  145. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/__init__.py +0 -0
  146. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/audit_category_schema_constants.py +0 -37
  147. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/converge_task_list_constants.py +0 -56
  148. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/handoff_constants.py +0 -45
  149. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/pacer_constants.py +0 -47
  150. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/path_resolver_constants.py +0 -157
  151. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/portable_driver_constants.py +0 -177
  152. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/preflight_constants.py +0 -70
  153. package/.agents/skills/_shared/pr-loop/scripts/teardown_worktrees.py +0 -172
  154. package/.agents/skills/_shared/pr-loop/scripts/test__path_resolver.py +0 -106
  155. package/.agents/skills/_shared/pr-loop/scripts/test_audit_category_schema.py +0 -94
  156. package/.agents/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +0 -499
  157. package/.agents/skills/_shared/pr-loop/scripts/test_build_converge_task_list.py +0 -140
  158. package/.agents/skills/_shared/pr-loop/scripts/test_build_fix_prompt.py +0 -262
  159. package/.agents/skills/_shared/pr-loop/scripts/test_init_loop_state.py +0 -71
  160. package/.agents/skills/_shared/pr-loop/scripts/test_portable_converge_driver.py +0 -1119
  161. package/.agents/skills/_shared/pr-loop/scripts/test_preflight_worktree.py +0 -287
  162. package/.agents/skills/_shared/pr-loop/scripts/test_select_converge_pacer.py +0 -207
  163. package/.agents/skills/_shared/pr-loop/scripts/test_teardown_worktrees.py +0 -59
  164. package/.agents/skills/_shared/pr-loop/scripts/test_write_handoff.py +0 -201
  165. package/.agents/skills/_shared/pr-loop/scripts/write_audit_outcomes.py +0 -182
  166. package/.agents/skills/_shared/pr-loop/scripts/write_fix_outcomes.py +0 -206
  167. package/.agents/skills/_shared/pr-loop/scripts/write_handoff.py +0 -309
  168. package/.agents/skills/_shared/pr-loop/state-schema.md +0 -5
  169. package/.agents/skills/_shared/pr-loop/worker-spawn.md +0 -5
  170. package/.agents/skills/autoconverge/.claude/CLAUDE.md +0 -1
  171. package/.agents/skills/autoconverge/AGENTS.md +0 -1
  172. package/.agents/skills/autoconverge/SKILL.md +0 -431
  173. package/.agents/skills/autoconverge/reference/.claude/CLAUDE.md +0 -1
  174. package/.agents/skills/autoconverge/reference/AGENTS.md +0 -1
  175. package/.agents/skills/autoconverge/reference/closing-report.md +0 -93
  176. package/.agents/skills/autoconverge/reference/convergence-gates.md +0 -31
  177. package/.agents/skills/autoconverge/reference/convergence.md +0 -219
  178. package/.agents/skills/autoconverge/reference/copilot-findings.md +0 -51
  179. package/.agents/skills/autoconverge/reference/gotchas.md +0 -66
  180. package/.agents/skills/autoconverge/reference/headless-safety.md +0 -44
  181. package/.agents/skills/autoconverge/reference/multi-pr.md +0 -128
  182. package/.agents/skills/autoconverge/reference/self-closing-loop.md +0 -84
  183. package/.agents/skills/autoconverge/reference/stop-conditions.md +0 -127
  184. package/.agents/skills/autoconverge/test_portable_pacer_gate.py +0 -54
  185. package/.agents/skills/autoconverge/workflow/.claude/CLAUDE.md +0 -1
  186. package/.agents/skills/autoconverge/workflow/AGENTS.md +0 -1
  187. package/.agents/skills/autoconverge/workflow/aggregate_runs.py +0 -371
  188. package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/.claude/CLAUDE.md +0 -1
  189. package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/AGENTS.md +0 -1
  190. package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/__init__.py +0 -0
  191. package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/render_report_constants.py +0 -250
  192. package/.agents/skills/autoconverge/workflow/converge.clean-audit.test.mjs +0 -665
  193. package/.agents/skills/autoconverge/workflow/converge.codex-gate.test.mjs +0 -472
  194. package/.agents/skills/autoconverge/workflow/converge.contract.test.mjs +0 -64
  195. package/.agents/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +0 -1009
  196. package/.agents/skills/autoconverge/workflow/converge.fix-progress.test.mjs +0 -107
  197. package/.agents/skills/autoconverge/workflow/converge.fix-recovery.test.mjs +0 -33
  198. package/.agents/skills/autoconverge/workflow/converge.merge-conflict.test.mjs +0 -15
  199. package/.agents/skills/autoconverge/workflow/converge.mjs +0 -3132
  200. package/.agents/skills/autoconverge/workflow/converge.p2-advance.test.mjs +0 -202
  201. package/.agents/skills/autoconverge/workflow/converge.path-aware.test.mjs +0 -47
  202. package/.agents/skills/autoconverge/workflow/converge.precatch.test.mjs +0 -152
  203. package/.agents/skills/autoconverge/workflow/converge.run-input.test.mjs +0 -116
  204. package/.agents/skills/autoconverge/workflow/converge_multi.mjs +0 -169
  205. package/.agents/skills/autoconverge/workflow/converge_multi.run-input.test.mjs +0 -173
  206. package/.agents/skills/autoconverge/workflow/convergence_summary.py +0 -110
  207. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a11d903476b803493.jsonl +0 -2
  208. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a26213978adeef6fb.jsonl +0 -2
  209. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a3def0d15ed9d9110.jsonl +0 -2
  210. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a41f41b1b708ee3b7.jsonl +0 -2
  211. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a758b880abecc3ff7.jsonl +0 -2
  212. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a8897b89656b1bd16.jsonl +0 -2
  213. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-ab1c2d3e4f5a6b7c8.jsonl +0 -2
  214. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-abd463d744a1437bc.jsonl +0 -2
  215. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-ad19d027ae8ee1816.jsonl +0 -2
  216. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/workflows/wf_881252e6-700.json +0 -266
  217. package/.agents/skills/autoconverge/workflow/render_report.py +0 -1024
  218. package/.agents/skills/autoconverge/workflow/test_aggregate_runs.py +0 -134
  219. package/.agents/skills/autoconverge/workflow/test_convergence_summary.py +0 -149
  220. package/.agents/skills/autoconverge/workflow/test_render_report.py +0 -812
  221. package/.agents/skills/hitl/SKILL.md +0 -45
  222. package/.agents/skills/pr-cleanup/SKILL.md +0 -140
  223. package/.agents/skills/pr-cleanup/reference/process-inventory.md +0 -17
  224. package/.agents/skills/pr-cleanup/reference/task-seeds.md +0 -22
  225. package/.agents/skills/pr-name-by-capability/SKILL.md +0 -73
  226. package/.agents/skills/pr-name-by-capability/reference/fetch-commands.md +0 -29
  227. package/.agents/skills/pr-name-by-capability/reference/offense-examples.md +0 -32
  228. package/.agents/skills/pr-name-by-capability/reference/report-template.md +0 -29
  229. package/.agents/skills/pr-name-by-capability/reference/rule-checklist.md +0 -38
  230. package/.agents/skills/pr-name-by-capability/reference/task-seeds.md +0 -10
  231. package/.agents/skills/pr-plain-language-cleanup/SKILL.md +0 -93
  232. package/.agents/skills/pr-refinement/SKILL.md +0 -31
  233. package/.agents/skills/pr-shared-extraction/SKILL.md +0 -177
  234. package/.agents/skills/pr-shared-extraction/reference/examples.md +0 -46
  235. package/.agents/skills/pr-shared-extraction/reference/offense-taxonomy.md +0 -100
  236. package/.agents/skills/pr-small-cl/SKILL.md +0 -52
  237. package/.agents/skills/pr-title-description/SKILL.md +0 -96
  238. package/.agents/skills/prototype/SKILL.md +0 -85
  239. package/.agents/skills/prototype/reference/honest-limitations.md +0 -23
  240. package/.agents/skills/prototype/reference/promotion-tasks.md +0 -23
  241. package/.agents/skills/prototype/scripts/build_sandbox_settings.py +0 -249
  242. package/.agents/skills/prototype/scripts/conftest.py +0 -15
  243. package/.agents/skills/prototype/scripts/launch_sandbox.py +0 -205
  244. package/.agents/skills/prototype/scripts/probe_sandbox_safety.py +0 -311
  245. package/.agents/skills/prototype/scripts/prototype_scripts_constants/__init__.py +0 -1
  246. package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/__init__.py +0 -0
  247. package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/build_sandbox_settings_constants.py +0 -41
  248. package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/launch_sandbox_constants.py +0 -23
  249. package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/probe_sandbox_safety_constants.py +0 -45
  250. package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/prototype_common_constants.py +0 -10
  251. package/.agents/skills/prototype/scripts/test_build_sandbox_settings.py +0 -275
  252. package/.agents/skills/prototype/scripts/test_launch_sandbox.py +0 -303
  253. package/.agents/skills/prototype/scripts/test_probe_sandbox_safety.py +0 -312
  254. package/.agents/skills/prototype/workflows/promotion.md +0 -25
  255. package/.agents/skills/prototype/workflows/sandbox.md +0 -35
  256. package/.agents/skills/rebase/.claude/CLAUDE.md +0 -1
  257. package/.agents/skills/rebase/AGENTS.md +0 -1
  258. package/.agents/skills/rebase/SKILL.md +0 -178
  259. package/.agents/skills/review-router/SKILL.md +0 -51
  260. package/.agents/skills/review-router/reference/route-policy.json +0 -7
  261. package/.agents/skills/review-router/reference/task-seeds.md +0 -1
  262. package/.agents/skills/review-router/scripts/conftest.py +0 -4
  263. package/.agents/skills/review-router/scripts/review_router.py +0 -50
  264. package/.agents/skills/review-router/scripts/review_router_cli.py +0 -211
  265. package/.agents/skills/review-router/scripts/review_router_constants/__init__.py +0 -1
  266. package/.agents/skills/review-router/scripts/review_router_constants/config/__init__.py +0 -0
  267. package/.agents/skills/review-router/scripts/review_router_constants/config/constants.py +0 -13
  268. package/.agents/skills/review-router/scripts/test_review_router.py +0 -23
  269. package/.agents/skills/review-router/scripts/test_review_router_cli.py +0 -47
  270. package/.agents/skills/review-tier/SKILL.md +0 -56
  271. package/.agents/skills/review-tier/reference/task-seeds.md +0 -1
  272. package/.agents/skills/review-tier/reference/tier-policy.json +0 -9
  273. package/.agents/skills/review-tier/scripts/conftest.py +0 -4
  274. package/.agents/skills/review-tier/scripts/review_tier.py +0 -359
  275. package/.agents/skills/review-tier/scripts/review_tier_constants/__init__.py +0 -1
  276. package/.agents/skills/review-tier/scripts/review_tier_constants/config/__init__.py +0 -0
  277. package/.agents/skills/review-tier/scripts/review_tier_constants/config/constants.py +0 -29
  278. package/.agents/skills/review-tier/scripts/test_generation.py +0 -159
  279. package/.agents/skills/review-tier/scripts/test_review_tier.py +0 -33
  280. package/.agents/skills/run-claude-dev-env/SKILL.md +0 -84
  281. package/.agents/skills/session-log/.claude/CLAUDE.md +0 -1
  282. package/.agents/skills/session-log/AGENTS.md +0 -1
  283. package/.agents/skills/session-log/SKILL.md +0 -170
  284. package/.agents/skills/session-log/templates/frontmatter.md +0 -40
  285. package/.agents/skills/session-tidy/.claude/CLAUDE.md +0 -1
  286. package/.agents/skills/session-tidy/AGENTS.md +0 -1
  287. package/.agents/skills/session-tidy/SKILL.md +0 -191
  288. package/.agents/skills/skill-builder/references/.claude/CLAUDE.md +0 -1
  289. package/.agents/skills/skill-builder/references/AGENTS.md +0 -1
  290. package/.agents/skills/skill-builder/references/delegation-map.md +0 -130
  291. package/.agents/skills/skill-builder/references/description-field.md +0 -111
  292. package/.agents/skills/skill-builder/references/deterministic-elements.md +0 -218
  293. package/.agents/skills/skill-builder/references/progressive-disclosure.md +0 -122
  294. package/.agents/skills/skill-builder/references/self-audit-checklist.md +0 -126
  295. package/.agents/skills/skill-builder/references/skill-modularity.md +0 -111
  296. package/.agents/skills/skill-builder/references/skill-types.md +0 -232
  297. package/.agents/skills/skill-builder/references/thariq-x-post-skills.json +0 -33
  298. package/.agents/skills/skill-builder/templates/.claude/CLAUDE.md +0 -1
  299. package/.agents/skills/skill-builder/templates/AGENTS.md +0 -1
  300. package/.agents/skills/skill-builder/templates/gap-analysis.md +0 -87
  301. package/.agents/skills/skill-builder/workflows/.claude/CLAUDE.md +0 -1
  302. package/.agents/skills/skill-builder/workflows/AGENTS.md +0 -1
  303. package/.agents/skills/skill-builder/workflows/improve-skill.md +0 -136
  304. package/.agents/skills/skill-builder/workflows/new-skill.md +0 -179
  305. package/.agents/skills/skill-builder/workflows/polish-skill.md +0 -139
  306. package/.agents/skills/source-command-sr-loop/SKILL.md +0 -82
  307. package/.agents/skills/update/.claude/CLAUDE.md +0 -1
  308. package/.agents/skills/update/AGENTS.md +0 -1
  309. package/.agents/skills/update/SKILL.md +0 -190
  310. package/_shared/pr-loop/AGENTS.md +0 -1
  311. package/_shared/pr-loop/CLAUDE.md +0 -1
  312. package/_shared/pr-loop/audit-contract.md +0 -183
  313. package/_shared/pr-loop/audit-reply-template.md +0 -151
  314. package/_shared/pr-loop/code-rules-gate.md +0 -65
  315. package/_shared/pr-loop/fix-protocol.md +0 -60
  316. package/_shared/pr-loop/gh-payloads.md +0 -75
  317. package/_shared/pr-loop/post-audit-thread-contract.md +0 -51
  318. package/_shared/pr-loop/precatch-rubric.md +0 -66
  319. package/_shared/pr-loop/preflight-proposal.md +0 -56
  320. package/_shared/pr-loop/scripts/post_audit_thread.py +0 -1242
  321. package/_shared/pr-loop/scripts/pr_loop_shared_constants/post_audit_thread_constants.py +0 -199
  322. package/_shared/pr-loop/scripts/tests/test_post_audit_thread.py +0 -1115
  323. package/_shared/pr-loop/scripts/tests/test_post_audit_thread_constants.py +0 -129
  324. package/_shared/pr-loop/state-schema.md +0 -45
  325. package/_shared/pr-loop/worker-spawn.md +0 -189
  326. package/hooks/_gh_pr_author_swap_utils.py +0 -1225
  327. package/hooks/blocking/conventional_pr_title_gate.py +0 -443
  328. package/hooks/blocking/gh_body_arg_blocker.py +0 -176
  329. package/hooks/blocking/gh_pr_author_enforcer.py +0 -487
  330. package/hooks/blocking/gh_pr_author_restore.py +0 -105
  331. package/hooks/blocking/pr_description_writer_gate.py +0 -256
  332. package/hooks/blocking/test_conventional_pr_title_gate.py +0 -640
  333. package/hooks/blocking/test_gh_body_arg_blocker.py +0 -441
  334. package/hooks/blocking/test_gh_pr_author_enforcer.py +0 -1166
  335. package/hooks/blocking/test_gh_pr_author_restore.py +0 -513
  336. package/hooks/blocking/test_gh_pr_author_swap_utils.py +0 -910
  337. package/hooks/blocking/test_pr_description_writer_gate.py +0 -234
  338. package/hooks/blocking/test_preflight_check.py +0 -343
  339. package/hooks/blocking/test_test_preflight_check.py +0 -80
  340. package/hooks/blocking/test_volatile_path_in_post_blocker.py +0 -322
  341. package/hooks/blocking/volatile_path_in_post_blocker.py +0 -461
  342. package/hooks/hooks_constants/conventional_pr_title_gate_constants.py +0 -58
  343. package/hooks/hooks_constants/gh_pr_author_swap_constants.py +0 -76
  344. package/hooks/hooks_constants/pr_description_writer_gate_constants.py +0 -73
  345. package/hooks/hooks_constants/volatile_path_in_post_blocker_constants.py +0 -55
  346. package/hooks/observability/pr_description_writer_spawn_tracker.py +0 -113
  347. package/hooks/observability/test_pr_description_writer_spawn_tracker.py +0 -150
  348. package/hooks/session/gh_pr_author_session_cleanup.py +0 -175
  349. package/hooks/session/test_gh_pr_author_session_cleanup.py +0 -576
  350. 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
- (_changed_document_rule("state-description", adapters.state_description_diagnostics),),
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("flaky", lambda document, _root: _flaky_diagnostics(document)),
355
- _changed_document_rule("stable", lambda document, _root: _stable_diagnostics(document)),
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
- "TOOL_NAME: str = \"tool\"\n",
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 adapters.hook_configuration_diagnostics(configuration_document, tmp_path) == ()
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 adapters.hook_configuration_diagnostics(configuration_document, tmp_path) == ()
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({SelectionKind.STAGED})
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
- "The premium-request-budget field gates the run.\n",
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(LintRequest.staged(tmp_path), all_registry=(_terminology_rule(),))
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 _run_git(repository_root: Path, *all_arguments: str) -> None:
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(*_all_arguments: object, **_all_keywords: object) -> subprocess.CompletedProcess[bytes]:
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()