claude-dev-env 4.3.0 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (293) 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 +5 -4
  12. package/.agents/skills/pull-request/SKILL.md +11 -13
  13. package/.agents/skills/pull-request/reference/publication-tasks.md +3 -3
  14. package/.agents/skills/pull-request/scripts/github_pr_command_constants/config/constants.py +9 -1
  15. package/.agents/skills/pull-request/scripts/pull_request.py +33 -44
  16. package/.agents/skills/pull-request/scripts/test_github_pr_command_constants.py +9 -0
  17. package/.agents/skills/pull-request/scripts/test_pull_request.py +1 -3
  18. package/.agents/skills/skill-builder/SKILL.md +3 -196
  19. package/_shared/pr-loop/scripts/pr_loop_shared_constants/terminology_sweep_constants.py +6 -0
  20. package/_shared/pr-loop/scripts/terminology_sweep.py +64 -19
  21. package/_shared/pr-loop/scripts/tests/test_terminology_sweep.py +59 -0
  22. package/bin/install.agents-home.test.mjs +15 -13
  23. package/bin/install.mjs +28 -0
  24. package/bin/install.policy-lint.test.mjs +78 -0
  25. package/bin/install.prune.test.mjs +59 -20
  26. package/bin/install.pstack-plugin.test.mjs +114 -0
  27. package/bin/install.test.mjs +37 -0
  28. package/bin/refresh-pstack-plugin-skills.test.mjs +211 -0
  29. package/hooks/advisory/pr_done_reminder.py +342 -0
  30. package/hooks/advisory/test_pr_done_reminder.py +249 -0
  31. package/hooks/blocking/bash_post_call_dispatcher.py +53 -6
  32. package/hooks/blocking/code_rules_paired_test.py +22 -15
  33. package/hooks/blocking/test_bash_post_call_dispatcher.py +65 -4
  34. package/hooks/blocking/test_code_rules_enforcer_paired_test.py +36 -29
  35. package/hooks/blocking/test_code_rules_js_resume_task_enumeration.py +4 -4
  36. package/hooks/blocking/test_code_rules_js_returns_object_schemaless.py +5 -5
  37. package/hooks/blocking/test_pre_tool_use_dispatcher.py +68 -133
  38. package/hooks/blocking/test_pre_tool_use_dispatcher_native.py +20 -51
  39. package/hooks/blocking/test_precommit_code_rules_gate_native_owner.py +18 -18
  40. package/hooks/git-hooks/git_hooks_constants/staged_policy_lint.py +12 -0
  41. package/hooks/git-hooks/pre_commit.py +52 -11
  42. package/hooks/git-hooks/test_installed_policy_lint.py +159 -0
  43. package/hooks/git-hooks/test_native_policy_commit.py +87 -0
  44. package/hooks/git-hooks/test_pre_commit.py +246 -0
  45. package/hooks/hooks.json +1 -1
  46. package/hooks/hooks_constants/bash_post_call_dispatcher_constants.py +14 -1
  47. package/hooks/hooks_constants/pr_done_reminder_constants.py +98 -0
  48. package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +10 -43
  49. package/hooks/hooks_constants/test_bash_post_call_dispatcher_constants.py +19 -2
  50. package/hooks/hooks_constants/test_pr_done_reminder_constants.py +40 -0
  51. package/hooks/hooks_constants/test_pre_tool_use_dispatcher_constants.py +20 -32
  52. package/package.json +2 -2
  53. package/rules/durable-post-artifacts.md +2 -1
  54. package/rules/git-workflow.md +6 -6
  55. package/scripts/check.ps1 +0 -1
  56. package/{.agents/skills/run-claude-dev-env/driver.mjs → scripts/ci/installer-lifecycle.mjs} +3 -28
  57. package/scripts/ci/installer-lifecycle.test.mjs +26 -0
  58. package/scripts/ci/windows-installer-lifecycle.ps1 +1 -11
  59. package/scripts/dev_env_scripts_constants/shared_tree_constants.py +4 -5
  60. package/{.agents/skills/_shared/pr-loop/scripts → scripts}/durable_post_lint.py +5 -41
  61. package/{.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/durable_post_lint_constants.py → scripts/durable_post_lint_config/config/constants.py} +0 -10
  62. package/{.agents/skills/_shared/pr-loop/scripts/test_durable_post_lint_constants.py → scripts/durable_post_lint_config/config/test_constants.py} +1 -1
  63. package/scripts/policy_lint/adapter_detectors.py +13 -6
  64. package/scripts/policy_lint/adapters.py +7 -4
  65. package/scripts/policy_lint/config/constants.py +5 -0
  66. package/scripts/policy_lint/engine.py +48 -4
  67. package/scripts/policy_lint/model.py +1 -0
  68. package/scripts/policy_lint/registry.py +1 -1
  69. package/scripts/policy_lint/selection.py +2 -1
  70. package/scripts/policy_lint/selection_git.py +2 -0
  71. package/scripts/refresh_pstack_plugin_skills.mjs +200 -0
  72. package/scripts/shared_tree_paths.py +32 -3
  73. package/scripts/test_shared_tree_paths.py +33 -0
  74. package/scripts/tests/test_policy_lint_rules_archive.py +120 -0
  75. package/scripts/tests/test_policy_lint_rules_engine.py +25 -4
  76. package/scripts/tests/test_policy_lint_rules_installed_loader.py +29 -0
  77. package/scripts/tests/test_policy_lint_rules_registry.py +227 -13
  78. package/scripts/tests/test_policy_lint_selection.py +25 -2
  79. package/scripts/tests/test_policy_lint_selection_native_index.py +30 -0
  80. package/scripts/tests/test_rules.py +3 -7
  81. package/.agents/skills/_shared/pr-loop/.claude/CLAUDE.md +0 -1
  82. package/.agents/skills/_shared/pr-loop/AGENTS.md +0 -1
  83. package/.agents/skills/_shared/pr-loop/audit-contract.md +0 -5
  84. package/.agents/skills/_shared/pr-loop/audit-reply-template.md +0 -5
  85. package/.agents/skills/_shared/pr-loop/code-rules-gate.md +0 -5
  86. package/.agents/skills/_shared/pr-loop/fix-protocol.md +0 -5
  87. package/.agents/skills/_shared/pr-loop/gh-payloads.md +0 -5
  88. package/.agents/skills/_shared/pr-loop/portable-driver.md +0 -149
  89. package/.agents/skills/_shared/pr-loop/post-audit-thread-contract.md +0 -5
  90. package/.agents/skills/_shared/pr-loop/precatch-rubric.md +0 -5
  91. package/.agents/skills/_shared/pr-loop/preflight-proposal.contract.test.mjs +0 -119
  92. package/.agents/skills/_shared/pr-loop/preflight-proposal.md +0 -5
  93. package/.agents/skills/_shared/pr-loop/prompts/.claude/CLAUDE.md +0 -1
  94. package/.agents/skills/_shared/pr-loop/prompts/AGENTS.md +0 -1
  95. package/.agents/skills/_shared/pr-loop/prompts/pr-consistency-audit.xml +0 -324
  96. package/.agents/skills/_shared/pr-loop/scripts/.claude/CLAUDE.md +0 -1
  97. package/.agents/skills/_shared/pr-loop/scripts/AGENTS.md +0 -1
  98. package/.agents/skills/_shared/pr-loop/scripts/RUNTIME_SCRIPTS.md +0 -29
  99. package/.agents/skills/_shared/pr-loop/scripts/_cli_utils.py +0 -22
  100. package/.agents/skills/_shared/pr-loop/scripts/_path_resolver.py +0 -186
  101. package/.agents/skills/_shared/pr-loop/scripts/_xml_utils.py +0 -20
  102. package/.agents/skills/_shared/pr-loop/scripts/audit_category_schema.py +0 -355
  103. package/.agents/skills/_shared/pr-loop/scripts/build_audit_prompt.py +0 -289
  104. package/.agents/skills/_shared/pr-loop/scripts/build_converge_task_list.py +0 -310
  105. package/.agents/skills/_shared/pr-loop/scripts/build_fix_prompt.py +0 -292
  106. package/.agents/skills/_shared/pr-loop/scripts/init_loop_state.py +0 -134
  107. package/.agents/skills/_shared/pr-loop/scripts/portable_converge_driver.py +0 -1646
  108. package/.agents/skills/_shared/pr-loop/scripts/preflight_worktree.py +0 -392
  109. package/.agents/skills/_shared/pr-loop/scripts/select_converge_pacer.py +0 -215
  110. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/.claude/CLAUDE.md +0 -1
  111. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/AGENTS.md +0 -1
  112. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/__init__.py +0 -0
  113. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/audit_category_schema_constants.py +0 -37
  114. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/converge_task_list_constants.py +0 -56
  115. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/handoff_constants.py +0 -45
  116. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/pacer_constants.py +0 -47
  117. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/path_resolver_constants.py +0 -157
  118. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/portable_driver_constants.py +0 -177
  119. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/preflight_constants.py +0 -70
  120. package/.agents/skills/_shared/pr-loop/scripts/teardown_worktrees.py +0 -172
  121. package/.agents/skills/_shared/pr-loop/scripts/test__path_resolver.py +0 -106
  122. package/.agents/skills/_shared/pr-loop/scripts/test_audit_category_schema.py +0 -94
  123. package/.agents/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +0 -499
  124. package/.agents/skills/_shared/pr-loop/scripts/test_build_converge_task_list.py +0 -140
  125. package/.agents/skills/_shared/pr-loop/scripts/test_build_fix_prompt.py +0 -262
  126. package/.agents/skills/_shared/pr-loop/scripts/test_init_loop_state.py +0 -71
  127. package/.agents/skills/_shared/pr-loop/scripts/test_portable_converge_driver.py +0 -1119
  128. package/.agents/skills/_shared/pr-loop/scripts/test_preflight_worktree.py +0 -287
  129. package/.agents/skills/_shared/pr-loop/scripts/test_select_converge_pacer.py +0 -207
  130. package/.agents/skills/_shared/pr-loop/scripts/test_teardown_worktrees.py +0 -59
  131. package/.agents/skills/_shared/pr-loop/scripts/test_write_handoff.py +0 -201
  132. package/.agents/skills/_shared/pr-loop/scripts/write_audit_outcomes.py +0 -182
  133. package/.agents/skills/_shared/pr-loop/scripts/write_fix_outcomes.py +0 -206
  134. package/.agents/skills/_shared/pr-loop/scripts/write_handoff.py +0 -309
  135. package/.agents/skills/_shared/pr-loop/state-schema.md +0 -5
  136. package/.agents/skills/_shared/pr-loop/worker-spawn.md +0 -5
  137. package/.agents/skills/autoconverge/.claude/CLAUDE.md +0 -1
  138. package/.agents/skills/autoconverge/AGENTS.md +0 -1
  139. package/.agents/skills/autoconverge/SKILL.md +0 -431
  140. package/.agents/skills/autoconverge/reference/.claude/CLAUDE.md +0 -1
  141. package/.agents/skills/autoconverge/reference/AGENTS.md +0 -1
  142. package/.agents/skills/autoconverge/reference/closing-report.md +0 -93
  143. package/.agents/skills/autoconverge/reference/convergence-gates.md +0 -31
  144. package/.agents/skills/autoconverge/reference/convergence.md +0 -219
  145. package/.agents/skills/autoconverge/reference/copilot-findings.md +0 -51
  146. package/.agents/skills/autoconverge/reference/gotchas.md +0 -66
  147. package/.agents/skills/autoconverge/reference/headless-safety.md +0 -44
  148. package/.agents/skills/autoconverge/reference/multi-pr.md +0 -128
  149. package/.agents/skills/autoconverge/reference/self-closing-loop.md +0 -84
  150. package/.agents/skills/autoconverge/reference/stop-conditions.md +0 -127
  151. package/.agents/skills/autoconverge/test_portable_pacer_gate.py +0 -54
  152. package/.agents/skills/autoconverge/workflow/.claude/CLAUDE.md +0 -1
  153. package/.agents/skills/autoconverge/workflow/AGENTS.md +0 -1
  154. package/.agents/skills/autoconverge/workflow/aggregate_runs.py +0 -371
  155. package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/.claude/CLAUDE.md +0 -1
  156. package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/AGENTS.md +0 -1
  157. package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/__init__.py +0 -0
  158. package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/render_report_constants.py +0 -250
  159. package/.agents/skills/autoconverge/workflow/converge.clean-audit.test.mjs +0 -665
  160. package/.agents/skills/autoconverge/workflow/converge.codex-gate.test.mjs +0 -472
  161. package/.agents/skills/autoconverge/workflow/converge.contract.test.mjs +0 -64
  162. package/.agents/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +0 -1009
  163. package/.agents/skills/autoconverge/workflow/converge.fix-progress.test.mjs +0 -107
  164. package/.agents/skills/autoconverge/workflow/converge.fix-recovery.test.mjs +0 -33
  165. package/.agents/skills/autoconverge/workflow/converge.merge-conflict.test.mjs +0 -15
  166. package/.agents/skills/autoconverge/workflow/converge.mjs +0 -3132
  167. package/.agents/skills/autoconverge/workflow/converge.p2-advance.test.mjs +0 -202
  168. package/.agents/skills/autoconverge/workflow/converge.path-aware.test.mjs +0 -47
  169. package/.agents/skills/autoconverge/workflow/converge.precatch.test.mjs +0 -152
  170. package/.agents/skills/autoconverge/workflow/converge.run-input.test.mjs +0 -116
  171. package/.agents/skills/autoconverge/workflow/converge_multi.mjs +0 -169
  172. package/.agents/skills/autoconverge/workflow/converge_multi.run-input.test.mjs +0 -173
  173. package/.agents/skills/autoconverge/workflow/convergence_summary.py +0 -110
  174. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a11d903476b803493.jsonl +0 -2
  175. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a26213978adeef6fb.jsonl +0 -2
  176. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a3def0d15ed9d9110.jsonl +0 -2
  177. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a41f41b1b708ee3b7.jsonl +0 -2
  178. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a758b880abecc3ff7.jsonl +0 -2
  179. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a8897b89656b1bd16.jsonl +0 -2
  180. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-ab1c2d3e4f5a6b7c8.jsonl +0 -2
  181. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-abd463d744a1437bc.jsonl +0 -2
  182. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-ad19d027ae8ee1816.jsonl +0 -2
  183. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/workflows/wf_881252e6-700.json +0 -266
  184. package/.agents/skills/autoconverge/workflow/render_report.py +0 -1024
  185. package/.agents/skills/autoconverge/workflow/test_aggregate_runs.py +0 -134
  186. package/.agents/skills/autoconverge/workflow/test_convergence_summary.py +0 -149
  187. package/.agents/skills/autoconverge/workflow/test_render_report.py +0 -812
  188. package/.agents/skills/hitl/SKILL.md +0 -45
  189. package/.agents/skills/pr-cleanup/SKILL.md +0 -140
  190. package/.agents/skills/pr-cleanup/reference/process-inventory.md +0 -17
  191. package/.agents/skills/pr-cleanup/reference/task-seeds.md +0 -22
  192. package/.agents/skills/pr-name-by-capability/SKILL.md +0 -73
  193. package/.agents/skills/pr-name-by-capability/reference/fetch-commands.md +0 -29
  194. package/.agents/skills/pr-name-by-capability/reference/offense-examples.md +0 -32
  195. package/.agents/skills/pr-name-by-capability/reference/report-template.md +0 -29
  196. package/.agents/skills/pr-name-by-capability/reference/rule-checklist.md +0 -38
  197. package/.agents/skills/pr-name-by-capability/reference/task-seeds.md +0 -10
  198. package/.agents/skills/pr-plain-language-cleanup/SKILL.md +0 -93
  199. package/.agents/skills/pr-refinement/SKILL.md +0 -31
  200. package/.agents/skills/pr-shared-extraction/SKILL.md +0 -177
  201. package/.agents/skills/pr-shared-extraction/reference/examples.md +0 -46
  202. package/.agents/skills/pr-shared-extraction/reference/offense-taxonomy.md +0 -100
  203. package/.agents/skills/pr-small-cl/SKILL.md +0 -52
  204. package/.agents/skills/pr-title-description/SKILL.md +0 -96
  205. package/.agents/skills/prototype/SKILL.md +0 -85
  206. package/.agents/skills/prototype/reference/honest-limitations.md +0 -23
  207. package/.agents/skills/prototype/reference/promotion-tasks.md +0 -23
  208. package/.agents/skills/prototype/scripts/build_sandbox_settings.py +0 -249
  209. package/.agents/skills/prototype/scripts/conftest.py +0 -15
  210. package/.agents/skills/prototype/scripts/launch_sandbox.py +0 -205
  211. package/.agents/skills/prototype/scripts/probe_sandbox_safety.py +0 -311
  212. package/.agents/skills/prototype/scripts/prototype_scripts_constants/__init__.py +0 -1
  213. package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/__init__.py +0 -0
  214. package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/build_sandbox_settings_constants.py +0 -41
  215. package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/launch_sandbox_constants.py +0 -23
  216. package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/probe_sandbox_safety_constants.py +0 -45
  217. package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/prototype_common_constants.py +0 -10
  218. package/.agents/skills/prototype/scripts/test_build_sandbox_settings.py +0 -275
  219. package/.agents/skills/prototype/scripts/test_launch_sandbox.py +0 -303
  220. package/.agents/skills/prototype/scripts/test_probe_sandbox_safety.py +0 -312
  221. package/.agents/skills/prototype/workflows/promotion.md +0 -25
  222. package/.agents/skills/prototype/workflows/sandbox.md +0 -35
  223. package/.agents/skills/rebase/.claude/CLAUDE.md +0 -1
  224. package/.agents/skills/rebase/AGENTS.md +0 -1
  225. package/.agents/skills/rebase/SKILL.md +0 -178
  226. package/.agents/skills/review-router/SKILL.md +0 -51
  227. package/.agents/skills/review-router/reference/route-policy.json +0 -7
  228. package/.agents/skills/review-router/reference/task-seeds.md +0 -1
  229. package/.agents/skills/review-router/scripts/conftest.py +0 -4
  230. package/.agents/skills/review-router/scripts/review_router.py +0 -50
  231. package/.agents/skills/review-router/scripts/review_router_cli.py +0 -211
  232. package/.agents/skills/review-router/scripts/review_router_constants/__init__.py +0 -1
  233. package/.agents/skills/review-router/scripts/review_router_constants/config/__init__.py +0 -0
  234. package/.agents/skills/review-router/scripts/review_router_constants/config/constants.py +0 -13
  235. package/.agents/skills/review-router/scripts/test_review_router.py +0 -23
  236. package/.agents/skills/review-router/scripts/test_review_router_cli.py +0 -47
  237. package/.agents/skills/review-tier/SKILL.md +0 -56
  238. package/.agents/skills/review-tier/reference/task-seeds.md +0 -1
  239. package/.agents/skills/review-tier/reference/tier-policy.json +0 -9
  240. package/.agents/skills/review-tier/scripts/conftest.py +0 -4
  241. package/.agents/skills/review-tier/scripts/review_tier.py +0 -359
  242. package/.agents/skills/review-tier/scripts/review_tier_constants/__init__.py +0 -1
  243. package/.agents/skills/review-tier/scripts/review_tier_constants/config/__init__.py +0 -0
  244. package/.agents/skills/review-tier/scripts/review_tier_constants/config/constants.py +0 -29
  245. package/.agents/skills/review-tier/scripts/test_generation.py +0 -159
  246. package/.agents/skills/review-tier/scripts/test_review_tier.py +0 -33
  247. package/.agents/skills/run-claude-dev-env/SKILL.md +0 -84
  248. package/.agents/skills/session-log/.claude/CLAUDE.md +0 -1
  249. package/.agents/skills/session-log/AGENTS.md +0 -1
  250. package/.agents/skills/session-log/SKILL.md +0 -170
  251. package/.agents/skills/session-log/templates/frontmatter.md +0 -40
  252. package/.agents/skills/session-tidy/.claude/CLAUDE.md +0 -1
  253. package/.agents/skills/session-tidy/AGENTS.md +0 -1
  254. package/.agents/skills/session-tidy/SKILL.md +0 -191
  255. package/.agents/skills/skill-builder/references/.claude/CLAUDE.md +0 -1
  256. package/.agents/skills/skill-builder/references/AGENTS.md +0 -1
  257. package/.agents/skills/skill-builder/references/delegation-map.md +0 -130
  258. package/.agents/skills/skill-builder/references/description-field.md +0 -111
  259. package/.agents/skills/skill-builder/references/deterministic-elements.md +0 -218
  260. package/.agents/skills/skill-builder/references/progressive-disclosure.md +0 -122
  261. package/.agents/skills/skill-builder/references/self-audit-checklist.md +0 -126
  262. package/.agents/skills/skill-builder/references/skill-modularity.md +0 -111
  263. package/.agents/skills/skill-builder/references/skill-types.md +0 -232
  264. package/.agents/skills/skill-builder/references/thariq-x-post-skills.json +0 -33
  265. package/.agents/skills/skill-builder/templates/.claude/CLAUDE.md +0 -1
  266. package/.agents/skills/skill-builder/templates/AGENTS.md +0 -1
  267. package/.agents/skills/skill-builder/templates/gap-analysis.md +0 -87
  268. package/.agents/skills/skill-builder/workflows/.claude/CLAUDE.md +0 -1
  269. package/.agents/skills/skill-builder/workflows/AGENTS.md +0 -1
  270. package/.agents/skills/skill-builder/workflows/improve-skill.md +0 -136
  271. package/.agents/skills/skill-builder/workflows/new-skill.md +0 -179
  272. package/.agents/skills/skill-builder/workflows/polish-skill.md +0 -139
  273. package/.agents/skills/source-command-sr-loop/SKILL.md +0 -82
  274. package/.agents/skills/update/.claude/CLAUDE.md +0 -1
  275. package/.agents/skills/update/AGENTS.md +0 -1
  276. package/.agents/skills/update/SKILL.md +0 -190
  277. package/_shared/pr-loop/AGENTS.md +0 -1
  278. package/_shared/pr-loop/CLAUDE.md +0 -1
  279. package/_shared/pr-loop/audit-contract.md +0 -183
  280. package/_shared/pr-loop/audit-reply-template.md +0 -151
  281. package/_shared/pr-loop/code-rules-gate.md +0 -65
  282. package/_shared/pr-loop/fix-protocol.md +0 -60
  283. package/_shared/pr-loop/gh-payloads.md +0 -75
  284. package/_shared/pr-loop/post-audit-thread-contract.md +0 -51
  285. package/_shared/pr-loop/precatch-rubric.md +0 -66
  286. package/_shared/pr-loop/preflight-proposal.md +0 -56
  287. package/_shared/pr-loop/scripts/post_audit_thread.py +0 -1242
  288. package/_shared/pr-loop/scripts/pr_loop_shared_constants/post_audit_thread_constants.py +0 -199
  289. package/_shared/pr-loop/scripts/tests/test_post_audit_thread.py +0 -1115
  290. package/_shared/pr-loop/scripts/tests/test_post_audit_thread_constants.py +0 -129
  291. package/_shared/pr-loop/state-schema.md +0 -45
  292. package/_shared/pr-loop/worker-spawn.md +0 -189
  293. /package/{.agents/skills/_shared/pr-loop/scripts → scripts}/test_durable_post_lint.py +0 -0
@@ -34,8 +34,8 @@ check_js_returns_object_schemaless_branch = (
34
34
 
35
35
  _MJS_PATH = "skills/autoconverge/workflow/converge.mjs"
36
36
 
37
- _SHIPPED_CONVERGE_MJS = (
38
- _HOOK_DIRECTORY.parents[1] / ".agents" / "skills" / "autoconverge" / "workflow" / "converge.mjs"
37
+ _ARCHIVED_CONVERGE_MJS = (
38
+ _HOOK_DIRECTORY.parents[3] / "skill-archive" / "autoconverge" / "workflow" / "converge.mjs"
39
39
  )
40
40
 
41
41
 
@@ -161,7 +161,7 @@ def test_skips_python_files() -> None:
161
161
  assert issues == []
162
162
 
163
163
 
164
- def test_shipped_converge_mjs_passes_its_own_check() -> None:
165
- shipped_source = _SHIPPED_CONVERGE_MJS.read_text(encoding="utf-8")
166
- issues = check_js_returns_object_schemaless_branch(shipped_source, _MJS_PATH)
164
+ def test_archived_converge_regression_fixture_passes_the_check() -> None:
165
+ archived_source = _ARCHIVED_CONVERGE_MJS.read_text(encoding="utf-8")
166
+ issues = check_js_returns_object_schemaless_branch(archived_source, _MJS_PATH)
167
167
  assert issues == []
@@ -428,44 +428,30 @@ def test_context_survives_alongside_deny_reason(
428
428
  assert dispatcher_result.stdout.strip(), "Dispatcher must emit output when denying"
429
429
 
430
430
 
431
- def test_all_deny_reasons_present_when_multiple_hooks_deny() -> None:
432
- """When two or more hooks deny, all their reasons appear in the dispatcher output.
433
-
434
- Uses a Write to an existing markdown path with a historical phrase
435
- ("previously") so write_existing_file_blocker and state_description_blocker
436
- both deny.
437
- """
438
- existing_markdown_path = str(Path(__file__).resolve().parent / "CLAUDE.md")
439
- multi_deny_content = (
440
- "# Guide\n\n"
441
- "Previously the system used a different mechanism.\n"
442
- )
443
- payload_text = _write_payload(existing_markdown_path, multi_deny_content)
431
+ def test_all_deny_reasons_present_when_multiple_hooks_deny(
432
+ monkeypatch: pytest.MonkeyPatch, tmp_path: Path,
433
+ ) -> None:
434
+ """Preserve every deny reason from the retained file-boundary checks."""
435
+ monkeypatch.setenv("HOME", str(tmp_path))
436
+ monkeypatch.setenv("USERPROFILE", str(tmp_path))
437
+ existing_sensitive_path = tmp_path / ".env"
438
+ existing_sensitive_path.write_text("VALUE=fixture\n", encoding="utf-8")
439
+ payload_text = _write_payload(str(existing_sensitive_path), "VALUE=changed\n")
444
440
  _assert_dispatcher_matches_individual_hooks(payload_text, WRITE_TOOL_NAME)
445
-
446
441
  dispatcher_result = _run_dispatcher(payload_text)
447
442
  dispatcher_is_deny, dispatcher_reason = _parse_hook_decision(dispatcher_result)
448
- assert dispatcher_is_deny, "Dispatcher must deny when any hook denies"
449
-
450
- applicable_entries = _applicable_entries_for_tool(WRITE_TOOL_NAME)
443
+ assert dispatcher_is_deny
451
444
  all_expected_deny_reasons: list[str] = []
452
- for each_entry in applicable_entries:
445
+ for each_entry in _applicable_entries_for_tool(WRITE_TOOL_NAME):
453
446
  completed_process = _run_hook_subprocess(each_entry.script_relative_path, payload_text)
454
447
  is_deny, reason_text = _parse_hook_decision(completed_process)
455
448
  if is_deny and reason_text:
456
449
  all_expected_deny_reasons.append(reason_text)
457
-
458
450
  assert len(all_expected_deny_reasons) >= 2, (
459
- f"Test payload must trip at least two hooks — got {len(all_expected_deny_reasons)}. "
460
- "Check that an existing path triggers write_existing_file_blocker and "
461
- "'previously' triggers state_description_blocker on a Write."
451
+ "Existing-file and sensitive-file checks must both deny the fixture"
462
452
  )
463
453
  for each_reason in all_expected_deny_reasons:
464
- assert each_reason in dispatcher_reason, (
465
- f"Missing deny reason in dispatcher output.\n"
466
- f"Expected reason: {each_reason!r}\n"
467
- f"Dispatcher reason: {dispatcher_reason!r}"
468
- )
454
+ assert each_reason in dispatcher_reason
469
455
 
470
456
 
471
457
  def test_aggregate_exit_code_two_signals_deny() -> None:
@@ -686,18 +672,12 @@ def test_emit_allow_preserves_system_message_and_additional_context(
686
672
  assert parsed["systemMessage"] == "system-a\nsystem-b"
687
673
 
688
674
 
689
- def test_later_hook_deny_survives_early_hook_exit() -> None:
690
- """Dispatcher denies even when an earlier hook exits cleanly before a later hook denies.
691
-
692
- state_description_blocker denies a markdown write with a historical phrase.
693
- Earlier hooks exit 0 (allow). The dispatcher must catch each hook's
694
- SystemExit and continue, so the later denial reaches the aggregator.
695
- """
696
- payload_text = _write_payload(
697
- _MARKDOWN_FILE_PATH,
698
- "# Doc\n\nPreviously this section used a different mechanism.\n",
699
- )
675
+ def test_later_hook_deny_survives_early_hook_exit(tmp_path: Path) -> None:
676
+ """A retained document-reference denial survives earlier successful checks."""
677
+ payload_text = _orphan_claude_md_payload(tmp_path)
700
678
  _assert_dispatcher_matches_individual_hooks(payload_text, WRITE_TOOL_NAME)
679
+ is_deny, reason = _parse_hook_decision(_run_dispatcher(payload_text))
680
+ assert is_deny, reason
701
681
 
702
682
 
703
683
  def test_dispatcher_write_applies_both_groups() -> None:
@@ -712,8 +692,8 @@ def test_dispatcher_write_applies_both_groups() -> None:
712
692
  assert "blocking/write_existing_file_blocker.py" in all_write_script_paths, (
713
693
  "write_existing_file_blocker (Group A) must be in Write applicable set"
714
694
  )
715
- assert len(all_write_entries) == 18, (
716
- f"Write tool must apply to all 18 hosted hooks, got {len(all_write_entries)}"
695
+ assert len(all_write_entries) == 11, (
696
+ f"Write tool must apply to the 11 retained write hooks, got {len(all_write_entries)}"
717
697
  )
718
698
 
719
699
 
@@ -727,8 +707,8 @@ def test_dispatcher_edit_applies_both_groups() -> None:
727
707
  assert "advisory/refactor_guard.py" in all_edit_script_paths, (
728
708
  "refactor_guard is Edit-scoped and hosted, so it belongs in the Edit applicable set"
729
709
  )
730
- assert len(all_edit_entries) == 20, (
731
- f"expected 20 Edit entries, got {len(all_edit_entries)}"
710
+ assert len(all_edit_entries) == 13, (
711
+ f"expected 13 retained Edit entries, got {len(all_edit_entries)}"
732
712
  )
733
713
 
734
714
 
@@ -751,7 +731,7 @@ def test_dispatcher_multi_edit_reaches_the_sensitive_protector() -> None:
751
731
  def test_dispatcher_apply_patch_applies_immediate_harm_hooks() -> None:
752
732
  """apply_patch applies to exactly the immediate-harm roster.
753
733
 
754
- apply_patch reaches only the immediate-harm-plus-TDD roster the constants
734
+ apply_patch reaches only the retained immediate-harm roster the constants
755
735
  module names, not the full Write/Edit/MultiEdit lint surface.
756
736
  """
757
737
  all_apply_patch_entries = _applicable_entries_for_tool(APPLY_PATCH_TOOL_NAME)
@@ -885,45 +865,19 @@ def _parse_hook_allow(completed_process: subprocess.CompletedProcess[str]) -> bo
885
865
  return hook_specific.get("permissionDecision") == "allow"
886
866
 
887
867
 
888
- def test_dispatcher_reemits_explicit_allow_from_tdd_enforcer(tmp_path: Path) -> None:
889
- """The dispatcher re-emits an explicit allow when tdd_enforcer's allow branch fires.
890
-
891
- tdd_enforcer writes an explicit allow payload for a constants-only Python
892
- Write, which auto-approves the write standalone. Run against the dispatcher,
893
- the same payload must produce an explicit allow decision identical to the
894
- standalone tdd_enforcer output, rather than a silent fall-back to the default
895
- permission flow.
896
-
897
- Args:
898
- tmp_path: Pytest temp directory hosting the fresh config target path.
899
- """
868
+ def test_retired_tdd_hook_cannot_auto_approve_the_write(tmp_path: Path) -> None:
869
+ """Pairing belongs to staged lint; its edit-time auto-approval is absent."""
900
870
  config_target_path = str(tmp_path / "config" / "timing.py")
901
871
  constants_only_content = (
902
872
  '"""Timing constants."""\n\nMAXIMUM_RETRIES = 3\nRETRY_DELAY_SECONDS = 5\n'
903
873
  )
904
874
  payload_text = _write_payload(config_target_path, constants_only_content)
905
-
906
875
  standalone_result = _run_hook_subprocess("blocking/tdd_enforcer.py", payload_text)
907
- assert _parse_hook_allow(standalone_result), (
908
- "tdd_enforcer must emit an explicit allow for a constants-only Python Write — "
909
- "if it does not, this fixture no longer exercises the allow branch"
910
- )
911
-
876
+ assert _parse_hook_allow(standalone_result)
912
877
  dispatcher_result = _run_dispatcher(payload_text)
913
- dispatcher_is_deny, _reason = _parse_hook_decision(dispatcher_result)
914
- assert not dispatcher_is_deny, "dispatcher must not deny a payload tdd_enforcer allows"
915
- assert _parse_hook_allow(dispatcher_result), (
916
- "dispatcher must re-emit an explicit allow when a hosted hook allows explicitly "
917
- "and no hook denies, matching the standalone tdd_enforcer behavior — "
918
- f"got stdout {dispatcher_result.stdout.strip()!r}"
919
- )
920
- dispatcher_payload = json.loads(dispatcher_result.stdout.strip())
921
- standalone_payload = json.loads(standalone_result.stdout.strip())
922
- assert dispatcher_payload == standalone_payload, (
923
- "dispatcher allow payload must match the standalone tdd_enforcer allow payload.\n"
924
- f"Standalone: {standalone_payload!r}\n"
925
- f"Dispatcher: {dispatcher_payload!r}"
926
- )
878
+ dispatcher_is_deny, reason = _parse_hook_decision(dispatcher_result)
879
+ assert not dispatcher_is_deny, reason
880
+ assert not _parse_hook_allow(dispatcher_result)
927
881
 
928
882
 
929
883
  def test_runpy_hosted_hook_sees_its_own_argv_not_the_dispatchers(tmp_path: Path) -> None:
@@ -1007,13 +961,7 @@ def _dispatcher_matchers(all_pre_tool_use_groups: list[dict]) -> list[str]:
1007
961
 
1008
962
 
1009
963
  def test_apply_patch_has_no_standalone_code_rules_enforcer_hooks_json_entry() -> None:
1010
- """apply_patch's code_rules_enforcer coverage comes only from the dispatcher.
1011
-
1012
- hooks.json once registered a standalone apply_patch to code_rules_enforcer.py
1013
- PreToolUse command. That entry is now folded into the dispatcher's own
1014
- Write|Edit|MultiEdit|apply_patch matcher, so no separate command may still
1015
- spawn code_rules_enforcer.py for apply_patch on its own.
1016
- """
964
+ """The retired code-rules hook has no direct apply_patch registration."""
1017
965
  hooks_json_text = (_HOOKS_ROOT / "hooks.json").read_text(encoding="utf-8")
1018
966
  all_pre_tool_use_groups = json.loads(hooks_json_text)["hooks"]["PreToolUse"]
1019
967
 
@@ -1115,60 +1063,47 @@ def test_dispatcher_denies_apply_patch_add_onto_an_existing_path(tmp_path: Path)
1115
1063
 
1116
1064
 
1117
1065
  def test_dispatcher_allows_clean_apply_patch_add(tmp_path: Path) -> None:
1118
- """The dispatcher allows an apply_patch "add" whose paired test already exists.
1119
-
1120
- tdd_enforcer now reaches apply_patch the same way it reaches Write: an
1121
- untested production file is exactly the immediate-harm case the apply_patch
1122
- boundary rule names, so "clean" here means test-first was honored before
1123
- the tool call, not that TDD is skipped for this tool. The paired test file
1124
- is written to disk first, matching how test_dispatcher_surfaces_migration_
1125
- warning_for_edit proves a fresh Edit passes tdd_enforcer.
1126
- """
1127
- (tmp_path / "test_services.py").write_text(
1128
- "def test_add_one():\n assert True\n", encoding="utf-8"
1129
- )
1066
+ """A clean patch can proceed before the staged linter evaluates test pairing."""
1130
1067
  patch_command = _codex_add_patch(
1131
- "services.py",
1132
- "def add_one(value: int) -> int:\n return value + 1\n",
1068
+ "services.py", "def add_one(value: int) -> int:\n return value + 1\n",
1133
1069
  )
1134
1070
  payload_text = _apply_patch_payload(str(tmp_path), patch_command)
1135
-
1136
1071
  dispatcher_result = _run_dispatcher(payload_text)
1137
1072
  is_deny, reason_text = _parse_hook_decision(dispatcher_result)
1138
- assert not is_deny, (
1139
- f"dispatcher must allow an apply_patch add with a fresh paired test, "
1140
- f"got deny reason {reason_text!r}"
1141
- )
1142
-
1143
-
1144
- def test_hosted_hook_set_covers_all_write_edit_blocking_hooks() -> None:
1145
- """The hosted hook set covers all previously-registered Write/Edit blocking hooks.
1146
-
1147
- Verifies that removing the standalone gate entries from hooks.json did not
1148
- silently drop coverage: every script path that was registered as a blocking
1149
- PreToolUse hook for Write/Edit is present in the dispatcher's hosted set.
1150
- """
1151
- all_hosted_script_paths = frozenset(
1073
+ assert not is_deny, reason_text
1074
+
1075
+
1076
+ ALL_RETAINED_PATHS = {
1077
+ "blocking/write_existing_file_blocker.py",
1078
+ "blocking/sensitive_file_protector.py",
1079
+ "blocking/pii_prevention_blocker.py",
1080
+ "validation/hook_format_validator.py",
1081
+ "blocking/duplicate_rmtree_helper_blocker.py",
1082
+ "blocking/claude_md_orphan_file_blocker.py",
1083
+ "blocking/package_inventory_stale_blocker.py",
1084
+ "blocking/env_var_table_code_drift_blocker.py",
1085
+ "blocking/pytest_testpaths_orphan_blocker.py",
1086
+ "blocking/open_questions_in_plans_blocker.py",
1087
+ "blocking/docstring_rule_gate_count_blocker.py",
1088
+ "advisory/refactor_guard.py",
1089
+ "advisory/migration_safety_advisor.py",
1090
+ }
1091
+ ALL_RETIRED_PATHS = {
1092
+ "blocking/code_rules_enforcer.py",
1093
+ "blocking/tdd_enforcer.py",
1094
+ "blocking/windows_rmtree_blocker.py",
1095
+ "blocking/state_description_blocker.py",
1096
+ "blocking/subprocess_budget_completeness.py",
1097
+ "blocking/hook_prose_detector_consistency.py",
1098
+ "blocking/workflow_substitution_slot_blocker.py",
1099
+ }
1100
+
1101
+ def test_hosted_hook_set_retains_boundaries_and_excludes_replaced_file_checks() -> None:
1102
+ """Keep unresolved controls and exclude the seven staged-linter replacements."""
1103
+ all_hosted_script_paths = {
1152
1104
  each_entry.script_relative_path for each_entry in ALL_HOSTED_HOOK_ENTRIES
1153
- )
1154
- previously_registered_blocking_hooks: frozenset[str] = frozenset({
1155
- "blocking/write_existing_file_blocker.py",
1156
- "blocking/sensitive_file_protector.py",
1157
- "validation/hook_format_validator.py",
1158
- "blocking/code_rules_enforcer.py",
1159
- "blocking/tdd_enforcer.py",
1160
- "blocking/windows_rmtree_blocker.py",
1161
- "blocking/state_description_blocker.py",
1162
- "blocking/subprocess_budget_completeness.py",
1163
- "blocking/hook_prose_detector_consistency.py",
1164
- "blocking/workflow_substitution_slot_blocker.py",
1165
- "blocking/claude_md_orphan_file_blocker.py",
1166
- "blocking/pytest_testpaths_orphan_blocker.py",
1167
- "blocking/open_questions_in_plans_blocker.py",
1168
- })
1169
- for each_script_path in previously_registered_blocking_hooks:
1170
- assert each_script_path in all_hosted_script_paths, (
1171
- f"Previously-registered blocking hook {each_script_path!r} is missing "
1172
- "from the dispatcher's hosted hook set — coverage was lost when the "
1173
- "standalone entry was removed from hooks.json"
1174
- )
1105
+ }
1106
+ assert all_hosted_script_paths == ALL_RETAINED_PATHS
1107
+ assert not all_hosted_script_paths.intersection(ALL_RETIRED_PATHS)
1108
+ for each_relative_path in ALL_RETIRED_PATHS:
1109
+ assert (_HOOKS_ROOT / each_relative_path).is_file()
@@ -1,16 +1,8 @@
1
- """Native-equivalence tests for the nativized PreToolUse hosted hooks.
2
-
3
- For the state_description_blocker hook the dispatcher runs natively. This suite
4
- asserts the native evaluate() call and the hook's standalone __main__ subprocess
5
- path decide identically on the same payload: same allow-or-deny, same deny-reason
6
- text. It also asserts the dispatcher reaches the same decision through its native
7
- path.
8
-
9
- The corpus pairs allowing payloads with denying payloads for each hook so the
10
- equivalence holds across both outcomes. Each payload names an absolute file
11
- under this repository and carries the repository root as its ``cwd``, so the
12
- decision belongs to this checkout rather than to the directory pytest runs
13
- from.
1
+ """Preserve detector equivalence while retiring its dispatcher registration.
2
+
3
+ The standalone state-description detector remains callable by the policy linter.
4
+ Its function and script agree on valid and invalid inputs; the write dispatcher
5
+ allows these policy findings to reach the staged linter.
14
6
  """
15
7
 
16
8
  from __future__ import annotations
@@ -195,28 +187,6 @@ def _deny_reason_from_dispatcher(payload_dictionary: dict[str, object]) -> str |
195
187
  )
196
188
  return _deny_reason_from_script_stdout(completed_process.stdout.strip())
197
189
 
198
- def _deny_payload_from_dispatcher(payload_dictionary: dict[str, object]) -> dict[str, object]:
199
- """Run the dispatcher as a subprocess and return its parsed deny payload.
200
-
201
- Args:
202
- payload_dictionary: The payload dict to send as JSON on stdin.
203
-
204
- Returns:
205
- The dispatcher's emitted deny JSON parsed into a dict.
206
- """
207
- completed_process = subprocess.run(
208
- [sys.executable, _DISPATCHER_SCRIPT],
209
- check=False,
210
- input=json.dumps(payload_dictionary),
211
- capture_output=True,
212
- text=True,
213
- encoding="utf-8",
214
- env=_subprocess_environment(),
215
- )
216
- parsed_payload = json.loads(completed_process.stdout.strip())
217
- assert isinstance(parsed_payload, dict)
218
- return parsed_payload
219
-
220
190
  def test_state_description_native_allows_match_script() -> None:
221
191
  """state_description_blocker native allow matches the script's allow."""
222
192
  payload_dictionary = _write_payload_dictionary(_MARKDOWN_PATH, _STATE_DESCRIPTION_ALLOW_CONTENT)
@@ -262,18 +232,17 @@ def test_state_description_native_multi_edit_deny_matches_script() -> None:
262
232
  assert native_reason == script_reason
263
233
 
264
234
 
265
- def test_dispatcher_native_path_denies_state_description() -> None:
266
- """The dispatcher's native path denies a state_description_blocker violation."""
235
+ def test_dispatcher_allows_the_retired_state_description_rule() -> None:
236
+ """The detector still finds the violation, but the write dispatcher allows it."""
267
237
  payload_dictionary = _write_payload_dictionary(_MARKDOWN_PATH, _STATE_DESCRIPTION_DENY_CONTENT)
268
238
  native_reason = _evaluate_with_prose_style_opt_in(state_description_blocker.evaluate, payload_dictionary)
269
239
  dispatcher_reason = _deny_reason_from_dispatcher(payload_dictionary)
270
240
  assert native_reason is not None
271
- assert dispatcher_reason is not None
272
- assert native_reason in dispatcher_reason
241
+ assert dispatcher_reason is None
273
242
 
274
243
 
275
- def test_dispatcher_native_state_description_carries_additional_context() -> None:
276
- """The dispatcher's state-description deny carries the standalone additionalContext."""
244
+ def test_standalone_state_description_retains_additional_context() -> None:
245
+ """The retained standalone interface still supplies its corrective context."""
277
246
  payload_dictionary = _write_payload_dictionary(_MARKDOWN_PATH, _STATE_DESCRIPTION_DENY_CONTENT)
278
247
  deny_reason = _evaluate_with_prose_style_opt_in(state_description_blocker.evaluate, payload_dictionary)
279
248
  assert deny_reason is not None
@@ -281,20 +250,20 @@ def test_dispatcher_native_state_description_carries_additional_context() -> Non
281
250
  standalone_hook_specific = standalone_payload["hookSpecificOutput"]
282
251
  assert isinstance(standalone_hook_specific, dict)
283
252
  expected_additional_context = standalone_hook_specific["additionalContext"]
284
- dispatcher_payload = _deny_payload_from_dispatcher(payload_dictionary)
285
- dispatcher_hook_specific = dispatcher_payload.get("hookSpecificOutput", {})
286
- assert isinstance(dispatcher_hook_specific, dict)
287
- assert dispatcher_hook_specific.get("additionalContext") == expected_additional_context
253
+ script_payload = json.loads(_run_script_subprocess(_STATE_DESCRIPTION_SCRIPT, payload_dictionary))
254
+ script_hook_specific = script_payload.get("hookSpecificOutput", {})
255
+ assert isinstance(script_hook_specific, dict)
256
+ assert script_hook_specific.get("additionalContext") == expected_additional_context
288
257
 
289
- def test_dispatcher_native_state_description_carries_system_message() -> None:
290
- """The dispatcher's state-description deny carries the standalone systemMessage."""
258
+ def test_standalone_state_description_retains_system_message() -> None:
259
+ """The retained standalone interface still supplies its system message."""
291
260
  payload_dictionary = _write_payload_dictionary(_MARKDOWN_PATH, _STATE_DESCRIPTION_DENY_CONTENT)
292
261
  deny_reason = _evaluate_with_prose_style_opt_in(state_description_blocker.evaluate, payload_dictionary)
293
262
  assert deny_reason is not None
294
263
  standalone_payload = state_description_blocker.build_deny_payload(deny_reason)
295
264
  expected_system_message = standalone_payload["systemMessage"]
296
265
  assert isinstance(expected_system_message, str)
297
- dispatcher_payload = _deny_payload_from_dispatcher(payload_dictionary)
298
- dispatcher_system_message = dispatcher_payload.get("systemMessage")
299
- assert isinstance(dispatcher_system_message, str)
300
- assert expected_system_message in dispatcher_system_message
266
+ script_payload = json.loads(_run_script_subprocess(_STATE_DESCRIPTION_SCRIPT, payload_dictionary))
267
+ script_system_message = script_payload.get("systemMessage")
268
+ assert isinstance(script_system_message, str)
269
+ assert expected_system_message in script_system_message
@@ -10,7 +10,6 @@ from __future__ import annotations
10
10
  from collections.abc import Iterator
11
11
  from contextlib import contextmanager
12
12
  import json
13
- import shutil
14
13
  import stat
15
14
  import subprocess
16
15
  import sys
@@ -21,12 +20,20 @@ import pytest
21
20
  HOOKS_ROOT = Path(__file__).resolve().parent.parent
22
21
  BLOCKING_ROOT = HOOKS_ROOT / "blocking"
23
22
  GIT_HOOKS_ROOT = HOOKS_ROOT / "git-hooks"
24
- PRE_COMMIT_SOURCE_PATH = GIT_HOOKS_ROOT / "pre_commit.py"
25
- GATE_UTILS_SOURCE_PATH = GIT_HOOKS_ROOT / "gate_utils.py"
26
- GIT_HOOKS_CONSTANTS_SOURCE_PATH = GIT_HOOKS_ROOT / "git_hooks_constants"
27
23
  DISPATCHER_SOURCE_PATH = BLOCKING_ROOT / "bash_pre_tool_use_dispatcher.py"
28
24
  GIT_COMMAND_TIMEOUT_SECONDS = 60
29
- CLEAN_MODULE_SOURCE = "def add_one(number: int) -> int:\n return number + 1\n"
25
+ CLEAN_MODULE_SOURCE = (
26
+ "def add_one(number: int) -> int:\n"
27
+ ' """Increase the number.\n\n'
28
+ " Args:\n number: The input number.\n\n"
29
+ " Returns:\n The next number.\n"
30
+ ' """\n'
31
+ " return number + 1\n"
32
+ )
33
+ PAIRED_TEST_SOURCE = (
34
+ "from widget import add_one\n\n"
35
+ "def test_add_one() -> None:\n assert add_one(1) == 2\n"
36
+ )
30
37
 
31
38
 
32
39
  def run_git(
@@ -69,21 +76,13 @@ def write_gate_script(gate_path: Path, marker_path: Path, exit_code: int) -> Non
69
76
 
70
77
 
71
78
  def install_native_pre_commit(hooks_path: Path) -> None:
72
- """Install the native pre-commit module and its self-contained imports."""
79
+ """Register the actual package owner with its complete linter dependency tree."""
73
80
  hooks_path.mkdir()
74
- shutil.copyfile(PRE_COMMIT_SOURCE_PATH, hooks_path / "pre_commit.py")
75
- shutil.copyfile(GATE_UTILS_SOURCE_PATH, hooks_path / "gate_utils.py")
76
- shutil.copytree(
77
- GIT_HOOKS_CONSTANTS_SOURCE_PATH,
78
- hooks_path / "git_hooks_constants",
79
- )
80
81
  pre_commit_path = hooks_path / "pre-commit"
81
82
  pre_commit_path.write_text(
82
83
  "#!/usr/bin/env python3\n"
83
84
  "import sys\n"
84
- "from pathlib import Path\n"
85
- "shim_directory = Path(__file__).resolve().parent\n"
86
- "sys.path.insert(0, str(shim_directory))\n"
85
+ f"sys.path.insert(0, {str(GIT_HOOKS_ROOT)!r})\n"
87
86
  "import pre_commit\n"
88
87
  "sys.exit(pre_commit.main())\n",
89
88
  encoding="utf-8",
@@ -133,9 +132,10 @@ def temporary_hooks_path(repository_root: Path, benchmark_hooks_path: Path) -> I
133
132
 
134
133
 
135
134
  def stage_module(repository_root: Path) -> None:
136
- """Write and stage one Python module for the commit fixture."""
135
+ """Stage a valid module and matching test before exercising the legacy gate."""
137
136
  (repository_root / "widget.py").write_text(CLEAN_MODULE_SOURCE, encoding="utf-8")
138
- run_git(repository_root, "add", "widget.py")
137
+ (repository_root / "test_widget.py").write_text(PAIRED_TEST_SOURCE, encoding="utf-8")
138
+ run_git(repository_root, "add", "widget.py", "test_widget.py")
139
139
 
140
140
 
141
141
  def build_bash_payload(repository_root: Path) -> str:
@@ -216,7 +216,7 @@ def test_temporary_hooks_path_restores_shared_worktree_config_on_timeout(
216
216
  initialize_repository(tmp_path)
217
217
  supported_hooks_path = tmp_path / "supported_hooks"
218
218
  supported_hooks_path.mkdir()
219
- run_git(tmp_path, "config", "--local", "core.hooksPath", str(supported_hooks_path))
219
+ run_git(tmp_path, "config", "core.hooksPath", str(supported_hooks_path))
220
220
  unrelated_worktree = tmp_path.parent / "unrelated_worktree"
221
221
  run_git(tmp_path, "worktree", "add", "--detach", str(unrelated_worktree))
222
222
  benchmark_hooks_path = tmp_path / "benchmark_hooks"
@@ -0,0 +1,12 @@
1
+ """Constants for the native pre-commit policy-lint step."""
2
+
3
+ POLICY_LINT_PACKAGE_PARENT_INDEX = 2
4
+ POLICY_LINT_SCRIPT_RELATIVE_PATH = "scripts/cde_lint.py"
5
+ POLICY_LINT_STAGED_ARGUMENT = "--staged"
6
+ POLICY_LINT_TIMEOUT_SECONDS = 240
7
+ POLICY_LINT_INFRASTRUCTURE_EXIT_CODE = 2
8
+ POLICY_LINT_UNAVAILABLE_MESSAGE = (
9
+ "cde lint is missing from this hook installation; reinstall claude-dev-env "
10
+ "before committing"
11
+ )
12
+ POLICY_LINT_FAILED_MESSAGE = "cde lint could not complete: {error}"
@@ -1,17 +1,13 @@
1
1
  #!/usr/bin/env python3
2
- """Native Git pre-commit owner for staged CODE_RULES validation.
2
+ """Run staged policy lint, then the existing staged CODE_RULES gate.
3
3
 
4
- Installed to the user's shared git-hooks directory via the claude-dev-env
5
- installer; git invokes this file as `pre-commit` (the installer strips the
6
- `_` and `.py` suffix when copying into the live hooks path).
4
+ Git invokes this module through the installed native pre-commit shim. Policy
5
+ lint reads the Git index, including GIT_INDEX_FILE when Git supplies an
6
+ alternate index. The existing gate still covers checks whose replacement
7
+ coverage is unproven.
7
8
 
8
- The Agent Bash commit surface records passive staged-surface evidence. This
9
- native hook invokes the shared gate and owns the staged commit decision.
10
-
11
- Exit codes:
12
- 0 - staged changes pass the gate (or the gate is not installed locally).
13
- 1 - staged changes introduce one or more blocking violations.
14
- 2 - unexpected invocation failure (e.g., subprocess could not launch).
9
+ A missing linter, launch failure, or timeout is an infrastructure failure.
10
+ Linter diagnostics and failed-rule statuses reach Git.
15
11
  """
16
12
 
17
13
  from __future__ import annotations
@@ -27,6 +23,43 @@ from git_hooks_constants import (
27
23
  IMMEDIATE_SCOPE_ARGUMENT,
28
24
  INVOKE_GATE_FAILURE_MESSAGE,
29
25
  )
26
+ from git_hooks_constants.staged_policy_lint import (
27
+ POLICY_LINT_FAILED_MESSAGE,
28
+ POLICY_LINT_INFRASTRUCTURE_EXIT_CODE,
29
+ POLICY_LINT_PACKAGE_PARENT_INDEX,
30
+ POLICY_LINT_SCRIPT_RELATIVE_PATH,
31
+ POLICY_LINT_STAGED_ARGUMENT,
32
+ POLICY_LINT_TIMEOUT_SECONDS,
33
+ POLICY_LINT_UNAVAILABLE_MESSAGE,
34
+ )
35
+
36
+
37
+ def resolve_policy_lint_script_path() -> Path:
38
+ """Return the policy-linter entry point in this package or managed home."""
39
+ package_root = Path(__file__).resolve().parents[POLICY_LINT_PACKAGE_PARENT_INDEX]
40
+ return package_root / POLICY_LINT_SCRIPT_RELATIVE_PATH
41
+
42
+
43
+ def run_staged_policy_lint() -> int:
44
+ """Run the staged linter with Git's environment and working directory.
45
+
46
+ Returns:
47
+ The linter status, or the infrastructure status when it cannot run.
48
+ """
49
+ try:
50
+ script_path = resolve_policy_lint_script_path()
51
+ if not script_path.is_file():
52
+ sys.stderr.write(POLICY_LINT_UNAVAILABLE_MESSAGE + "\n")
53
+ return POLICY_LINT_INFRASTRUCTURE_EXIT_CODE
54
+ completion = subprocess.run(
55
+ [sys.executable, str(script_path.resolve(strict=True)), POLICY_LINT_STAGED_ARGUMENT],
56
+ check=False,
57
+ timeout=POLICY_LINT_TIMEOUT_SECONDS,
58
+ )
59
+ except (OSError, subprocess.TimeoutExpired) as launch_error:
60
+ sys.stderr.write(POLICY_LINT_FAILED_MESSAGE.format(error=launch_error) + "\n")
61
+ return POLICY_LINT_INFRASTRUCTURE_EXIT_CODE
62
+ return completion.returncode
30
63
 
31
64
 
32
65
  def invoke_gate(gate_script_path: Path) -> int:
@@ -50,6 +83,14 @@ def invoke_gate(gate_script_path: Path) -> int:
50
83
 
51
84
 
52
85
  def main() -> int:
86
+ """Run local staged policy lint before the existing native commit checks.
87
+
88
+ Returns:
89
+ The first failing check's status, or zero when the checks pass.
90
+ """
91
+ lint_exit_code = run_staged_policy_lint()
92
+ if lint_exit_code != 0:
93
+ return lint_exit_code
53
94
  gate_script_not_found_message = GATE_SCRIPT_NOT_FOUND_MESSAGE
54
95
  gate_script_path, exact_allowed_path = resolve_gate_script_path()
55
96
  if not is_safe_regular_file(gate_script_path, exact_allowed_path):