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,26 @@
1
+ import assert from 'node:assert/strict';
2
+ import { spawnSync } from 'node:child_process';
3
+ import { readFileSync } from 'node:fs';
4
+ import { dirname, join } from 'node:path';
5
+ import test from 'node:test';
6
+ import { fileURLToPath } from 'node:url';
7
+
8
+ const CI_DIRECTORY = dirname(fileURLToPath(import.meta.url));
9
+ const DRIVER_PATH = join(CI_DIRECTORY, 'installer-lifecycle.mjs');
10
+ const DRIVER_TIMEOUT_MILLISECONDS = 60_000;
11
+
12
+ test('the installer lifecycle driver completes in an isolated home', () => {
13
+ const lifecycleProcess = spawnSync(process.execPath, [DRIVER_PATH], {
14
+ encoding: 'utf8',
15
+ timeout: DRIVER_TIMEOUT_MILLISECONDS,
16
+ });
17
+ assert.equal(lifecycleProcess.status, 0, `${lifecycleProcess.stdout}\n${lifecycleProcess.stderr}`);
18
+ assert.match(lifecycleProcess.stdout, /ALL CHECKS PASSED/);
19
+ assert.match(lifecycleProcess.stdout, /Sandbox removed:/);
20
+ });
21
+
22
+ test('the Windows adapter invokes the CI-owned lifecycle driver', () => {
23
+ const adapterSource = readFileSync(join(CI_DIRECTORY, 'windows-installer-lifecycle.ps1'), 'utf8');
24
+ assert.match(adapterSource, /\$driverPath = Join-Path \$PSScriptRoot 'installer-lifecycle\.mjs'/);
25
+ assert.doesNotMatch(adapterSource, /skills\\run-claude-dev-env/);
26
+ });
@@ -1,13 +1,3 @@
1
- # Windows CI adapter for the Node installer lifecycle driver.
2
- #
3
- # The driver already redirects HOME, USERPROFILE, and GIT_CONFIG_GLOBAL into a
4
- # throwaway sandbox. This adapter records that isolation contract, runs the
5
- # 16-check driver, and writes bounded evidence under -EvidenceRoot so a failed
6
- # Windows runner leaves an actionable artifact.
7
- #
8
- # Usage (from packages/claude-dev-env):
9
- # pwsh -NoProfile -File scripts/ci/windows-installer-lifecycle.ps1 -EvidenceRoot <dir>
10
-
11
1
  [CmdletBinding()]
12
2
  param(
13
3
  [Parameter(Mandatory = $true)]
@@ -17,7 +7,7 @@ param(
17
7
  $ErrorActionPreference = 'Stop'
18
8
 
19
9
  $packageRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path
20
- $driverPath = Join-Path $packageRoot '.claude\skills\run-claude-dev-env\driver.mjs'
10
+ $driverPath = Join-Path $PSScriptRoot 'installer-lifecycle.mjs'
21
11
  $allIsolationEnvNames = @(
22
12
  'HOME',
23
13
  'USERPROFILE',
@@ -1,8 +1,4 @@
1
- """Constants for resolving directories inside the installed shared tree.
2
-
3
- These name the shape of the installed tree itself, not any one worker or
4
- skill that reads it, so every caller of shared_tree_paths shares them.
5
- """
1
+ """Directory names for the installed shared tree."""
6
2
 
7
3
  from __future__ import annotations
8
4
 
@@ -15,3 +11,6 @@ SCRIPTS_DIRECTORY_NAME: str = "scripts"
15
11
  PROCESS_TREE_DIRECTORY_NAME: str = "process-tree"
16
12
 
17
13
  PROCESS_TREE_KILL_MODULE_FILENAME: str = "process_tree_kill.py"
14
+
15
+ AGENTS_DIRECTORY_SUFFIX: str = ".agents"
16
+ DEFAULT_MANAGED_ROOT_NAME: str = ".claude"
@@ -0,0 +1,206 @@
1
+ """Validate durable GitHub post inputs before publication."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import sys
7
+ from collections.abc import Sequence
8
+ from dataclasses import dataclass
9
+ from pathlib import Path
10
+
11
+ from durable_post_lint_config.config.constants import (
12
+ ACTION_PR_CREATE,
13
+ ACTION_PR_EDIT,
14
+ ALL_BARE_VOLATILE_PATH_MARKERS,
15
+ ALL_BODY_REQUIRED_ACTIONS,
16
+ ALL_PATH_ANCHORED_VOLATILE_PATH_MARKERS,
17
+ ALL_POST_ACTIONS,
18
+ ALL_PR_DESCRIPTION_ACTIONS,
19
+ ALL_REQUIRED_PR_DESCRIPTION_HEADINGS,
20
+ ALL_TITLE_ACTIONS,
21
+ BODY_FILE_ENCODING,
22
+ BODY_FILE_UNREADABLE_MESSAGE,
23
+ BODY_MUST_NOT_BE_EMPTY_MESSAGE,
24
+ BODY_REQUIRED_MESSAGE,
25
+ CLEAN_EXIT_CODE,
26
+ CONTENT_FINDING_EXIT_CODE,
27
+ CONVENTIONAL_TITLE_PATTERN,
28
+ EDIT_INPUT_REQUIRED_MESSAGE,
29
+ EMPTY_BODY_CODE,
30
+ INPUT_ERROR_EXIT_CODE,
31
+ INVALID_ACTION_MESSAGE,
32
+ INVALID_TITLE_CODE,
33
+ INVALID_TITLE_MESSAGE,
34
+ MISSING_HEADING_CODE,
35
+ MISSING_HEADING_MESSAGE_TEMPLATE,
36
+ PATH_ANCHOR_CHARACTER,
37
+ PATH_SEGMENT_START_CHARACTERS,
38
+ TITLE_NOT_ALLOWED_MESSAGE,
39
+ TITLE_REQUIRED_MESSAGE,
40
+ VOLATILE_PATH_CODE,
41
+ VOLATILE_PATH_MESSAGE,
42
+ )
43
+
44
+
45
+ class DurablePostUsageError(ValueError):
46
+ """Report an invalid GitHub post request shape."""
47
+
48
+
49
+ class DurablePostInputError(OSError):
50
+ """Report an unreadable GitHub post input file."""
51
+
52
+
53
+ @dataclass(frozen=True)
54
+ class DurablePostFinding:
55
+ """One GitHub post content finding."""
56
+
57
+ code: str
58
+ message: str
59
+
60
+
61
+ def _character_starts_path_segment(character: str) -> bool:
62
+ return bool(character) and (
63
+ character.isalnum() or character in PATH_SEGMENT_START_CHARACTERS
64
+ )
65
+
66
+
67
+ def _text_has_anchored_marker(normalized_text: str, marker: str) -> bool:
68
+ search_start = 0
69
+ while True:
70
+ found_index = normalized_text.find(marker, search_start)
71
+ if found_index < 0:
72
+ return False
73
+ preceding_character = normalized_text[found_index - 1 : found_index]
74
+ following_index = found_index + len(marker)
75
+ following_character = normalized_text[following_index : following_index + 1]
76
+ if (
77
+ preceding_character == PATH_ANCHOR_CHARACTER
78
+ or _character_starts_path_segment(following_character)
79
+ ):
80
+ return True
81
+ search_start = found_index + 1
82
+
83
+
84
+ def find_volatile_path_marker(body_text: str) -> str | None:
85
+ """Return the first volatile path marker in a GitHub post body."""
86
+ normalized_text = body_text.replace("\\", PATH_ANCHOR_CHARACTER).lower()
87
+ for each_marker in ALL_PATH_ANCHORED_VOLATILE_PATH_MARKERS:
88
+ if _text_has_anchored_marker(normalized_text, each_marker):
89
+ return each_marker
90
+ for each_marker in ALL_BARE_VOLATILE_PATH_MARKERS:
91
+ if each_marker in normalized_text:
92
+ return each_marker
93
+ return None
94
+
95
+
96
+ def read_body_file(body_file: Path) -> str:
97
+ """Read one UTF-8 body file without exposing its contents on failure."""
98
+ try:
99
+ return body_file.read_text(encoding=BODY_FILE_ENCODING)
100
+ except (OSError, UnicodeError) as error:
101
+ raise DurablePostInputError(BODY_FILE_UNREADABLE_MESSAGE) from error
102
+
103
+
104
+ def _validate_request_shape(
105
+ action: str,
106
+ title: str | None,
107
+ body_text: str | None,
108
+ ) -> None:
109
+ if action not in ALL_POST_ACTIONS:
110
+ raise DurablePostUsageError(INVALID_ACTION_MESSAGE)
111
+ if action in ALL_BODY_REQUIRED_ACTIONS and body_text is None:
112
+ raise DurablePostUsageError(BODY_REQUIRED_MESSAGE)
113
+ if action == ACTION_PR_CREATE and not title:
114
+ raise DurablePostUsageError(TITLE_REQUIRED_MESSAGE)
115
+ if action == ACTION_PR_EDIT and title is None and body_text is None:
116
+ raise DurablePostUsageError(EDIT_INPUT_REQUIRED_MESSAGE)
117
+ if title is not None and action not in ALL_TITLE_ACTIONS:
118
+ raise DurablePostUsageError(TITLE_NOT_ALLOWED_MESSAGE)
119
+
120
+
121
+ def _description_heading_findings(body_text: str) -> list[DurablePostFinding]:
122
+ all_body_lines = {each_line.strip() for each_line in body_text.splitlines()}
123
+ return [
124
+ DurablePostFinding(
125
+ code=MISSING_HEADING_CODE,
126
+ message=MISSING_HEADING_MESSAGE_TEMPLATE.format(heading=each_heading),
127
+ )
128
+ for each_heading in ALL_REQUIRED_PR_DESCRIPTION_HEADINGS
129
+ if f"## {each_heading}" not in all_body_lines
130
+ ]
131
+
132
+
133
+ def _title_findings(title: str | None) -> list[DurablePostFinding]:
134
+ if title is None or CONVENTIONAL_TITLE_PATTERN.fullmatch(title) is not None:
135
+ return []
136
+ return [DurablePostFinding(code=INVALID_TITLE_CODE, message=INVALID_TITLE_MESSAGE)]
137
+
138
+
139
+ def _body_findings(
140
+ action: str,
141
+ body_text: str | None,
142
+ ) -> list[DurablePostFinding]:
143
+ if body_text is None:
144
+ return []
145
+ all_findings: list[DurablePostFinding] = []
146
+ if not body_text.strip():
147
+ all_findings.append(
148
+ DurablePostFinding(
149
+ code=EMPTY_BODY_CODE,
150
+ message=BODY_MUST_NOT_BE_EMPTY_MESSAGE,
151
+ )
152
+ )
153
+ if action in ALL_PR_DESCRIPTION_ACTIONS:
154
+ all_findings.extend(_description_heading_findings(body_text))
155
+ if find_volatile_path_marker(body_text) is not None:
156
+ all_findings.append(
157
+ DurablePostFinding(
158
+ code=VOLATILE_PATH_CODE,
159
+ message=VOLATILE_PATH_MESSAGE,
160
+ )
161
+ )
162
+ return all_findings
163
+
164
+
165
+ def lint_durable_post(
166
+ action: str,
167
+ title: str | None,
168
+ body_text: str | None,
169
+ ) -> tuple[DurablePostFinding, ...]:
170
+ """Return content findings for one locally valid GitHub post request."""
171
+ _validate_request_shape(action, title, body_text)
172
+ return (*_title_findings(title), *_body_findings(action, body_text))
173
+
174
+
175
+ def _parse_arguments(all_arguments: Sequence[str]) -> argparse.Namespace:
176
+ parser = argparse.ArgumentParser(description=__doc__, allow_abbrev=False)
177
+ parser.add_argument("--action", required=True)
178
+ parser.add_argument("--title")
179
+ parser.add_argument("--body-file", type=Path)
180
+ return parser.parse_args(list(all_arguments))
181
+
182
+
183
+ def main(all_arguments: Sequence[str]) -> int:
184
+ """Validate one GitHub post and return its result status."""
185
+ arguments = _parse_arguments(all_arguments)
186
+ try:
187
+ body_text = (
188
+ read_body_file(arguments.body_file)
189
+ if arguments.body_file is not None
190
+ else None
191
+ )
192
+ all_findings = lint_durable_post(
193
+ action=arguments.action,
194
+ title=arguments.title,
195
+ body_text=body_text,
196
+ )
197
+ except (DurablePostInputError, DurablePostUsageError) as error:
198
+ sys.stderr.write(f"{error}\n")
199
+ return INPUT_ERROR_EXIT_CODE
200
+ for each_finding in all_findings:
201
+ sys.stderr.write(f"{each_finding.message}\n")
202
+ return CONTENT_FINDING_EXIT_CODE if all_findings else CLEAN_EXIT_CODE
203
+
204
+
205
+ if __name__ == "__main__":
206
+ raise SystemExit(main(sys.argv[1:]))
@@ -0,0 +1,96 @@
1
+ """Configuration constants for durable GitHub post validation."""
2
+
3
+ import re
4
+
5
+ ACTION_GITHUB_MCP_POST = "github-mcp-post"
6
+ ACTION_ISSUE_COMMENT = "issue-comment"
7
+ ACTION_ISSUE_CREATE = "issue-create"
8
+ ACTION_ISSUE_EDIT = "issue-edit"
9
+ ACTION_PR_COMMENT = "pr-comment"
10
+ ACTION_PR_CREATE = "pr-create"
11
+ ACTION_PR_EDIT = "pr-edit"
12
+ ACTION_PR_REVIEW = "pr-review"
13
+
14
+ ALL_POST_ACTIONS: frozenset[str] = frozenset(
15
+ {
16
+ ACTION_GITHUB_MCP_POST,
17
+ ACTION_ISSUE_COMMENT,
18
+ ACTION_ISSUE_CREATE,
19
+ ACTION_ISSUE_EDIT,
20
+ ACTION_PR_COMMENT,
21
+ ACTION_PR_CREATE,
22
+ ACTION_PR_EDIT,
23
+ ACTION_PR_REVIEW,
24
+ }
25
+ )
26
+ ALL_BODY_REQUIRED_ACTIONS: frozenset[str] = frozenset(
27
+ {
28
+ ACTION_GITHUB_MCP_POST,
29
+ ACTION_ISSUE_COMMENT,
30
+ ACTION_ISSUE_CREATE,
31
+ ACTION_ISSUE_EDIT,
32
+ ACTION_PR_COMMENT,
33
+ ACTION_PR_CREATE,
34
+ ACTION_PR_REVIEW,
35
+ }
36
+ )
37
+ ALL_PR_DESCRIPTION_ACTIONS: frozenset[str] = frozenset(
38
+ {ACTION_PR_CREATE, ACTION_PR_EDIT}
39
+ )
40
+ ALL_TITLE_ACTIONS: frozenset[str] = frozenset({ACTION_PR_CREATE, ACTION_PR_EDIT})
41
+ ALL_CONVENTIONAL_TITLE_TYPES: tuple[str, ...] = (
42
+ "build",
43
+ "chore",
44
+ "ci",
45
+ "docs",
46
+ "feat",
47
+ "fix",
48
+ "perf",
49
+ "refactor",
50
+ "revert",
51
+ "style",
52
+ "test",
53
+ )
54
+ CONVENTIONAL_TITLE_PATTERN: re.Pattern[str] = re.compile(
55
+ rf"^(?:{'|'.join(ALL_CONVENTIONAL_TITLE_TYPES)})(?:\([^)\r\n]+\))?!?: [^\s].*$"
56
+ )
57
+ ALL_REQUIRED_PR_DESCRIPTION_HEADINGS: tuple[str, ...] = (
58
+ "Summary",
59
+ "Description",
60
+ "Why",
61
+ "How",
62
+ "Verification",
63
+ )
64
+ ALL_PATH_ANCHORED_VOLATILE_PATH_MARKERS: tuple[str, ...] = (
65
+ ".claude-profile-a/jobs/",
66
+ ".claude/worktrees/",
67
+ )
68
+ ALL_BARE_VOLATILE_PATH_MARKERS: tuple[str, ...] = (
69
+ "appdata/local/temp",
70
+ "/tmp/",
71
+ "%temp%",
72
+ "$env:temp",
73
+ "$claude_job_dir",
74
+ )
75
+ BODY_FILE_ENCODING = "utf-8"
76
+ CLEAN_EXIT_CODE = 0
77
+ CONTENT_FINDING_EXIT_CODE = 1
78
+ EMPTY_BODY_CODE = "empty-body"
79
+ INPUT_ERROR_EXIT_CODE = 2
80
+ PATH_ANCHOR_CHARACTER = "/"
81
+ PATH_SEGMENT_START_CHARACTERS = "_-"
82
+ BODY_FILE_UNREADABLE_MESSAGE = "body file is not readable UTF-8 text"
83
+ BODY_REQUIRED_MESSAGE = "this action requires a body file"
84
+ BODY_MUST_NOT_BE_EMPTY_MESSAGE = "body file must contain text"
85
+ EDIT_INPUT_REQUIRED_MESSAGE = "pr-edit requires a title or body file"
86
+ INVALID_ACTION_MESSAGE = "unsupported durable post action"
87
+ INVALID_TITLE_CODE = "invalid-pr-title"
88
+ INVALID_TITLE_MESSAGE = (
89
+ "pull request title does not use the repository Conventional Commit form"
90
+ )
91
+ MISSING_HEADING_CODE = "missing-pr-description-heading"
92
+ MISSING_HEADING_MESSAGE_TEMPLATE = "body is missing required heading: {heading}"
93
+ TITLE_NOT_ALLOWED_MESSAGE = "this action does not accept a title"
94
+ TITLE_REQUIRED_MESSAGE = "pr-create requires a title"
95
+ VOLATILE_PATH_CODE = "volatile-local-path"
96
+ VOLATILE_PATH_MESSAGE = "body contains a volatile local artifact path"
@@ -0,0 +1,25 @@
1
+ """Tests for durable GitHub post linter constants."""
2
+
3
+ from durable_post_lint_config.config.constants import (
4
+ ALL_CONVENTIONAL_TITLE_TYPES,
5
+ ALL_POST_ACTIONS,
6
+ CONVENTIONAL_TITLE_PATTERN,
7
+ )
8
+
9
+
10
+ def test_action_inventory_covers_pull_request_issue_and_tool_posts() -> None:
11
+ assert ALL_POST_ACTIONS == {
12
+ "github-mcp-post",
13
+ "issue-comment",
14
+ "issue-create",
15
+ "issue-edit",
16
+ "pr-comment",
17
+ "pr-create",
18
+ "pr-edit",
19
+ "pr-review",
20
+ }
21
+
22
+
23
+ def test_title_pattern_uses_every_configured_type() -> None:
24
+ for each_title_type in ALL_CONVENTIONAL_TITLE_TYPES:
25
+ assert CONVENTIONAL_TITLE_PATTERN.fullmatch(f"{each_title_type}: one change")
@@ -23,30 +23,69 @@ def hook_configuration_diagnostics(
23
23
  adapter_support._document_path(repository_root, document)
24
24
  if document.path.name != "hooks.json":
25
25
  return ()
26
- all_messages = _hook_configuration_messages(document.text)
26
+ all_messages = _hook_configuration_messages(document.text, document.prior_text)
27
27
  return adapter_support._diagnostics_for_messages(
28
28
  document, "hook-configuration", all_messages
29
29
  )
30
30
 
31
31
 
32
- def _hook_configuration_messages(configuration_text: str) -> tuple[str, ...]:
32
+ def _parsed_configuration(
33
+ configuration_text: str,
34
+ invalid_json_message: str,
35
+ invalid_object_message: str,
36
+ ) -> dict[object, object]:
33
37
  try:
34
38
  parsed_configuration = json.loads(configuration_text)
35
- except json.JSONDecodeError:
36
- return ("Hook configuration is not valid JSON",)
39
+ except json.JSONDecodeError as error:
40
+ raise ValueError(invalid_json_message) from error
37
41
  if not isinstance(parsed_configuration, dict):
38
- return ("Hook configuration must be an object",)
39
- all_registered_strings = _registered_hook_strings(parsed_configuration)
40
- if any(
41
- _contains_action_boundary_marker(each_string)
42
- for each_string in all_registered_strings
43
- ):
42
+ raise TypeError(invalid_object_message)
43
+ return parsed_configuration
44
+
45
+
46
+ def _action_boundary_strings(
47
+ all_configuration_entries: dict[object, object],
48
+ ) -> frozenset[str]:
49
+ return frozenset(
50
+ each_string
51
+ for each_string in _registered_hook_strings(all_configuration_entries)
52
+ if _contains_action_boundary_marker(each_string)
53
+ )
54
+
55
+
56
+ def _hook_configuration_messages(
57
+ configuration_text: str,
58
+ prior_configuration_text: str | None,
59
+ ) -> tuple[str, ...]:
60
+ try:
61
+ current_configuration = _parsed_configuration(
62
+ configuration_text,
63
+ "Hook configuration is not valid JSON",
64
+ "Hook configuration must be an object",
65
+ )
66
+ all_current_action_boundary_strings = _action_boundary_strings(
67
+ current_configuration
68
+ )
69
+ if prior_configuration_text is None:
70
+ all_prior_action_boundary_strings: frozenset[str] = frozenset()
71
+ else:
72
+ prior_configuration = _parsed_configuration(
73
+ prior_configuration_text,
74
+ "Prior hook configuration is not valid JSON",
75
+ "Prior hook configuration must be an object",
76
+ )
77
+ all_prior_action_boundary_strings = _action_boundary_strings(
78
+ prior_configuration
79
+ )
80
+ except (TypeError, ValueError) as error:
81
+ return (str(error),)
82
+ if all_current_action_boundary_strings - all_prior_action_boundary_strings:
44
83
  return ("Hook configuration registers a deny, block, or ask policy path",)
45
84
  return ()
46
85
 
47
86
 
48
87
  def _registered_hook_strings(
49
- all_configuration_entries: dict[object, object]
88
+ all_configuration_entries: dict[object, object],
50
89
  ) -> tuple[str, ...]:
51
90
  all_registered_strings: list[str] = []
52
91
  for each_key, each_entry in all_configuration_entries.items():
@@ -62,7 +101,9 @@ def _registered_strings_for_entry(
62
101
  all_registered_strings: list[str] = []
63
102
  if each_key in constants.ALL_HOOK_REGISTRATION_KEYS and isinstance(each_entry, str):
64
103
  all_registered_strings.append(each_entry)
65
- if each_key in constants.ALL_DECISION_REGISTRATION_KEYS and isinstance(each_entry, str):
104
+ if each_key in constants.ALL_DECISION_REGISTRATION_KEYS and isinstance(
105
+ each_entry, str
106
+ ):
66
107
  all_registered_strings.append(each_entry)
67
108
  all_registered_strings.extend(_registered_nested_strings(each_entry))
68
109
  return tuple(all_registered_strings)
@@ -29,24 +29,31 @@ def terminology_diagnostics(
29
29
  document_set: DocumentSet,
30
30
  load_module: adapter_support.HookModuleLoader,
31
31
  ) -> tuple[Diagnostic, ...]:
32
- """Report staged prose that near-misses a newly introduced identifier.
32
+ """Report prose that near-misses a newly introduced identifier.
33
33
 
34
34
  Args:
35
- document_set: Candidate staged documents and repository root.
35
+ document_set: Staged or base change selection and repository root.
36
36
  load_module: Shared script module loader.
37
37
 
38
38
  Returns:
39
39
  Diagnostics that name the prose file and line.
40
40
  """
41
- if document_set.selection is not SelectionKind.STAGED:
41
+ all_arguments: tuple[Path | str, ...]
42
+ if document_set.selection is SelectionKind.BASE:
43
+ if document_set.base_revision is None:
44
+ raise ValueError("Base selection is missing its comparison revision")
45
+ checker_name = "strict_base_terminology_findings"
46
+ all_arguments = (document_set.repository_root, document_set.base_revision)
47
+ elif document_set.selection is SelectionKind.STAGED:
48
+ checker_name = "strict_staged_terminology_findings"
49
+ all_arguments = (document_set.repository_root,)
50
+ else:
42
51
  return ()
43
52
  terminology_module = load_module(constants.TERMINOLOGY_SWEEP_MODULE_NAME)
44
53
  helper_stdout = StringIO()
45
54
  helper_stderr = StringIO()
46
55
  with redirect_stdout(helper_stdout), redirect_stderr(helper_stderr):
47
- all_findings = terminology_module.strict_staged_terminology_findings(
48
- document_set.repository_root
49
- )
56
+ all_findings = getattr(terminology_module, checker_name)(*all_arguments)
50
57
  return tuple(_terminology_diagnostic(each_finding) for each_finding in all_findings)
51
58
 
52
59
 
@@ -1,10 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import importlib
4
+ import os
4
5
  import sys
5
6
  from pathlib import Path
6
7
  from types import ModuleType
7
8
 
9
+ from shared_tree_paths import resolve_shared_scripts_directory
10
+
8
11
  from . import adapter_configuration, adapter_detectors, adapter_pairing, adapter_support
9
12
  from .config import constants
10
13
  from .model import Diagnostic, Document, DocumentSet
@@ -24,10 +27,10 @@ def _hooks_module(module_name: str) -> ModuleType:
24
27
 
25
28
 
26
29
  def _pr_loop_script_module(module_name: str) -> ModuleType:
27
- package_directory = Path(__file__).resolve().parent.parent.parent
28
- scripts_directory = str(
29
- package_directory.joinpath(*constants.ALL_PR_LOOP_SCRIPTS_PATH_SEGMENTS)
30
- )
30
+ scripts_directory = str(resolve_shared_scripts_directory(
31
+ __file__, os.environ, constants.PR_LOOP_DIRECTORY_NAME,
32
+ f"{module_name}{constants.PYTHON_SUFFIX}", constants.SHARED_ROOT_PARENT_INDEX
33
+ ))
31
34
  if scripts_directory not in sys.path:
32
35
  sys.path.insert(0, scripts_directory)
33
36
  return importlib.import_module(module_name)
@@ -5,6 +5,7 @@ INVALID_INPUT_EXIT_CODE = 2
5
5
  GIT_EXECUTABLE = "git"
6
6
  UTF8_ENCODING = "utf-8"
7
7
  PATH_SEPARATOR = "/"
8
+ ARCHIVED_SKILLS_DIRECTORY_NAME = "skill-archive"
8
9
  NUL_BYTE = b"\0"
9
10
  ALL_GIT_ROOT_ARGUMENTS = ("rev-parse", "--show-toplevel")
10
11
  ALL_GIT_HEAD_ARGUMENTS = ("rev-parse", "--verify", "HEAD")
@@ -13,6 +14,7 @@ GIT_MERGE_BASE_ARGUMENT = "merge-base"
13
14
  GIT_HEAD_REFERENCE = "HEAD"
14
15
  GIT_INDEX_REFERENCE_PREFIX = ""
15
16
  GIT_ENVIRONMENT_VARIABLE_PREFIX = "GIT_"
17
+ GIT_INDEX_ENVIRONMENT_VARIABLE = "GIT_INDEX_FILE"
16
18
  ALL_GIT_BLOB_ARGUMENTS = ("cat-file", "blob")
17
19
  ALL_MISSING_BLOB_ERROR_FRAGMENTS = (
18
20
  "does not exist in",
@@ -75,3 +77,6 @@ TERMINOLOGY_FINDING_PATTERN = re.compile(
75
77
  TERMINOLOGY_PATH_GROUP = "path"
76
78
  TERMINOLOGY_LINE_NUMBER_GROUP = "line_number"
77
79
  TERMINOLOGY_MESSAGE_GROUP = "message"
80
+
81
+ PR_LOOP_DIRECTORY_NAME = "pr-loop"
82
+ SHARED_ROOT_PARENT_INDEX = 2
@@ -1,8 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from collections.abc import Iterable, Sequence
4
- from pathlib import Path
4
+ from pathlib import Path, PurePosixPath
5
5
 
6
+ from .config.constants import ARCHIVED_SKILLS_DIRECTORY_NAME
6
7
  from .model import (
7
8
  ChangeSetRule,
8
9
  Diagnostic,
@@ -25,6 +26,9 @@ def lint(
25
26
  ) -> LintReport:
26
27
  """Run the selected rules on one source selection.
27
28
 
29
+ Repository and diff selections check active code. File and editor
30
+ selections include archived skills.
31
+
28
32
  Args:
29
33
  request: Repository root, source, and rule sets.
30
34
  all_registry: Optional registry replacement.
@@ -32,10 +36,14 @@ def lint(
32
36
  Returns:
33
37
  A report of diagnostics and rule execution state.
34
38
  """
35
- document_set = select_documents(request)
36
- candidate_rules = tuple(all_registry) if all_registry is not None else default_registry()
39
+ document_set = _runtime_document_set(select_documents(request))
40
+ candidate_rules = (
41
+ tuple(all_registry) if all_registry is not None else default_registry()
42
+ )
37
43
  selected = selected_rules(candidate_rules, request.rule_sets)
38
- all_diagnostics, executed, failed, skipped = _run_selected_rules(selected, document_set)
44
+ all_diagnostics, executed, failed, skipped = _run_selected_rules(
45
+ selected, document_set
46
+ )
39
47
  return LintReport(
40
48
  1,
41
49
  tuple(sorted(all_diagnostics, key=_diagnostic_sort_key)),
@@ -46,6 +54,42 @@ def lint(
46
54
  )
47
55
 
48
56
 
57
+ def _is_runtime_path(path: PurePosixPath) -> bool:
58
+ return path.parts[0] != ARCHIVED_SKILLS_DIRECTORY_NAME
59
+
60
+
61
+ def _runtime_document_set(document_set: DocumentSet) -> DocumentSet:
62
+ if document_set.selection in {SelectionKind.FILES, SelectionKind.TEXT}:
63
+ return document_set
64
+ all_documents = tuple(
65
+ each_document
66
+ for each_document in document_set.documents
67
+ if _is_runtime_path(each_document.path)
68
+ )
69
+ all_deleted_paths = tuple(
70
+ each_path
71
+ for each_path in document_set.deleted_paths
72
+ if _is_runtime_path(each_path)
73
+ ) + tuple(
74
+ old_path
75
+ for old_path, new_path in document_set.renamed_paths
76
+ if _is_runtime_path(old_path) and not _is_runtime_path(new_path)
77
+ )
78
+ all_renamed_paths = tuple(
79
+ each_pair
80
+ for each_pair in document_set.renamed_paths
81
+ if _is_runtime_path(each_pair[1])
82
+ )
83
+ return DocumentSet(
84
+ all_documents,
85
+ document_set.selection,
86
+ document_set.repository_root,
87
+ all_deleted_paths,
88
+ all_renamed_paths,
89
+ document_set.base_revision,
90
+ )
91
+
92
+
49
93
  def _run_selected_rules(
50
94
  all_rules: tuple[Rule, ...], document_set: DocumentSet
51
95
  ) -> tuple[list[Diagnostic], tuple[str, ...], tuple[str, ...], tuple[str, ...]]:
@@ -106,6 +106,7 @@ class DocumentSet:
106
106
  repository_root: Path
107
107
  deleted_paths: tuple[PurePosixPath, ...] = ()
108
108
  renamed_paths: tuple[tuple[PurePosixPath, PurePosixPath], ...] = ()
109
+ base_revision: str | None = None
109
110
 
110
111
 
111
112
  @dataclass(frozen=True)
@@ -141,7 +141,7 @@ def default_registry() -> tuple[model.Rule, ...]:
141
141
  model.ChangeSetRule(
142
142
  "terminology-sweep",
143
143
  frozenset({"changed"}),
144
- frozenset({model.SelectionKind.STAGED}),
144
+ frozenset({model.SelectionKind.STAGED, model.SelectionKind.BASE}),
145
145
  adapters.terminology_diagnostics,
146
146
  ),
147
147
  model.RepositoryRule(