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
@@ -25,16 +25,43 @@ const RETIRED_SKILL_DIRECTORIES = [
25
25
  'post-audit-findings',
26
26
  'pr-consistency-audit',
27
27
  'bdd-protocol',
28
+ 'hitl',
29
+ 'autoconverge',
30
+ 'pr-cleanup',
31
+ 'pr-name-by-capability',
32
+ 'pr-plain-language-cleanup',
33
+ 'pr-refinement',
34
+ 'pr-shared-extraction',
35
+ 'pr-small-cl',
36
+ 'pr-title-description',
37
+ 'prototype',
38
+ 'rebase',
39
+ 'review-router',
40
+ 'review-tier',
41
+ 'run-claude-dev-env',
42
+ 'session-log',
43
+ 'session-tidy',
44
+ 'source-command-sr-loop',
45
+ 'update',
28
46
  ];
29
47
  const PERSONAL_SKILL_DIRECTORIES = ['credit-card-picker', 'midjourney-sref'];
30
- const SHIPPED_SKILL_DIRECTORY = 'autoconverge';
48
+ const SHIPPED_SKILL_DIRECTORY = 'e-code-review';
31
49
  const PRUNED_BACKUP_DIRECTORY_NAME = '.claude-dev-env-pruned';
32
50
  const SKIP_PRUNE_NOTICE_MARKER = 'Skipping retired-skill and stale-file prune';
33
51
  const STALE_SKILL_FILE_RELATIVE_SEGMENTS = ['scripts', 'retired_module.py'];
34
52
  const RUNTIME_ARTIFACT_RELATIVE_SEGMENTS = ['scripts', '__pycache__', 'helper.cpython-312.pyc'];
35
53
  const SCOPED_GROUP_SKILL_DIRECTORY = 'orchestrator';
36
- const CORE_REVIEW_GUIDE_SKILL_DIRECTORIES = [
37
- 'pr-small-cl',
54
+ const CORE_SKILL_DIRECTORIES = [
55
+ 'orchestrator',
56
+ 'orchestrator-refresh',
57
+ 'team-advisor',
58
+ 'grok-spawn',
59
+ 'everything-search',
60
+ 'test-runner',
61
+ 'privacy-hygiene',
62
+ 'issue-tracker',
63
+ 'task-build',
64
+ 'eli5',
38
65
  ];
39
66
  const PRIOR_RUN_BACKUP_DIRECTORY_NAMES = [
40
67
  '2020-01-01T00-00-00-000Z',
@@ -66,6 +93,41 @@ const NESTED_SKILL_DIRECTORY = 'foo';
66
93
  const NESTED_SKILL_FILE_SEGMENTS = [NESTED_SKILL_DIRECTORY, 'scripts', 'a.py'];
67
94
  const MYPY_INI_FILE_NAME = '.mypy.ini';
68
95
  const SKIPPED_RECORD_SUMMARY_MARKER = 'manifest record(s) skipped';
96
+ const RETIRED_PULL_REQUEST_HOOK_RELATIVE_PATHS = [
97
+ '_gh_pr_author_swap_utils.py',
98
+ 'test__gh_pr_author_swap_utils.py',
99
+ 'blocking/conventional_pr_title_gate.py',
100
+ 'blocking/gh_body_arg_blocker.py',
101
+ 'blocking/gh_pr_author_enforcer.py',
102
+ 'blocking/gh_pr_author_restore.py',
103
+ 'blocking/pr_description_writer_gate.py',
104
+ 'blocking/test_conventional_pr_title_gate.py',
105
+ 'blocking/test_gh_body_arg_blocker.py',
106
+ 'blocking/test_gh_pr_author_enforcer.py',
107
+ 'blocking/test_gh_pr_author_restore.py',
108
+ 'blocking/test_gh_pr_author_swap_utils.py',
109
+ 'blocking/test_pr_description_writer_gate.py',
110
+ 'blocking/test_volatile_path_in_post_blocker.py',
111
+ 'blocking/volatile_path_in_post_blocker.py',
112
+ 'hooks_constants/conventional_pr_title_gate_constants.py',
113
+ 'hooks_constants/gh_pr_author_swap_constants.py',
114
+ 'hooks_constants/pr_description_writer_gate_constants.py',
115
+ 'hooks_constants/volatile_path_in_post_blocker_constants.py',
116
+ 'observability/pr_description_writer_spawn_tracker.py',
117
+ 'observability/test_pr_description_writer_spawn_tracker.py',
118
+ 'session/gh_pr_author_session_cleanup.py',
119
+ 'session/test_gh_pr_author_session_cleanup.py',
120
+ ];
121
+ const RETIRED_PULL_REQUEST_REGISTRATION_PATHS = [
122
+ 'blocking/conventional_pr_title_gate.py',
123
+ 'blocking/gh_body_arg_blocker.py',
124
+ 'blocking/gh_pr_author_enforcer.py',
125
+ 'blocking/gh_pr_author_restore.py',
126
+ 'blocking/pr_description_writer_gate.py',
127
+ 'blocking/volatile_path_in_post_blocker.py',
128
+ 'observability/pr_description_writer_spawn_tracker.py',
129
+ 'session/gh_pr_author_session_cleanup.py',
130
+ ];
69
131
 
70
132
  /**
71
133
  * Report whether a path under one managed root landed in a run backup.
@@ -658,25 +720,33 @@ test('a scoped --only install leaves retired skills in place because prune runs
658
720
  }
659
721
  });
660
722
 
661
- test('a full reinstall keeps autoconverge because the package ships it again', () => {
723
+ test('a full reinstall keeps the skill-builder stub and archives its obsolete support files', () => {
662
724
  const sandbox = createSandbox();
663
725
  try {
664
- plantSkillDirectory(sandbox.skillsDirectory, 'autoconverge', true);
665
- writeFileSync(join(sandbox.skillsDirectory, 'autoconverge', 'SKILL.md'), 'stale seeded copy\n');
726
+ const skillName = 'skill-builder';
727
+ plantSkillDirectory(sandbox.skillsDirectory, skillName, true);
728
+ const stubPath = join(sandbox.skillsDirectory, skillName, 'SKILL.md');
729
+ const obsoleteReferencePath = join(sandbox.skillsDirectory, skillName, 'references', 'retired.md');
730
+ writeFileWithParents(obsoleteReferencePath, 'obsolete skill-builder guidance\n');
731
+ writeFileSync(sandbox.manifestPath, JSON.stringify({
732
+ package: 'claude-dev-env',
733
+ version: '0.0.0',
734
+ installedAt: new Date().toISOString(),
735
+ files: [stubPath, obsoleteReferencePath],
736
+ skills: [skillName],
737
+ }, null, 2) + '\n');
666
738
 
667
739
  const installerOutput = runInstaller(sandbox.homeDirectory, []);
668
740
 
741
+ assert.equal(installerOutput.includes(SKIP_PRUNE_NOTICE_MARKER), false);
742
+ assert.equal(existsSync(stubPath), true, 'the requested stub stays installed');
743
+ assert.match(readFileSync(stubPath, 'utf8'), /TODO: Rework to follow pstack philosophy\./);
744
+ assert.ok(readManifest(sandbox.manifestPath).skills.includes(skillName));
745
+ assert.equal(existsSync(obsoleteReferencePath), false);
669
746
  assert.equal(
670
- installerOutput.includes(SKIP_PRUNE_NOTICE_MARKER),
671
- false,
672
- 'the full install runs the prune rather than skipping it',
673
- );
674
- const restoredSkillPath = join(sandbox.skillsDirectory, 'autoconverge', 'SKILL.md');
675
- assert.equal(existsSync(restoredSkillPath), true, 'autoconverge survives and is reinstalled');
676
- assert.notEqual(
677
- readFileSync(restoredSkillPath, 'utf8'),
678
- 'stale seeded copy\n',
679
- 'the shipped autoconverge overwrites the stale seeded copy',
747
+ prunedSkillBackupContains(sandbox.claudeDirectory, join(skillName, 'references', 'retired.md')),
748
+ true,
749
+ 'old implementation files move to the recovery backup while the stub stays',
680
750
  );
681
751
  } finally {
682
752
  rmSync(sandbox.homeDirectory, { recursive: true, force: true });
@@ -704,20 +774,24 @@ test('a scoped --only install records the skill names it installed', () => {
704
774
  }
705
775
  });
706
776
 
707
- test('a scoped core install ships the canonical review guide skills', () => {
777
+ test('a fresh scoped core install ships active skills and omits retired skills', () => {
708
778
  const sandbox = createSandbox();
709
779
  try {
710
780
  runInstaller(sandbox.homeDirectory, ['--only', 'core']);
711
781
 
712
- const missingGuideSkillNames = CORE_REVIEW_GUIDE_SKILL_DIRECTORIES.filter(
782
+ const missingSkillNames = CORE_SKILL_DIRECTORIES.filter(
713
783
  eachSkillName => !existsSync(join(sandbox.skillsDirectory, eachSkillName, 'SKILL.md')),
714
784
  );
715
- assert.deepEqual(missingGuideSkillNames, []);
785
+ assert.deepEqual(missingSkillNames, []);
716
786
 
717
787
  const recordedSkillNames = new Set(readManifest(sandbox.manifestPath).skills);
718
- for (const eachSkillName of CORE_REVIEW_GUIDE_SKILL_DIRECTORIES) {
788
+ for (const eachSkillName of CORE_SKILL_DIRECTORIES) {
719
789
  assert.equal(recordedSkillNames.has(eachSkillName), true);
720
790
  }
791
+ for (const eachSkillName of RETIRED_SKILL_DIRECTORIES) {
792
+ assert.equal(existsSync(join(sandbox.skillsDirectory, eachSkillName)), false);
793
+ assert.equal(recordedSkillNames.has(eachSkillName), false);
794
+ }
721
795
  } finally {
722
796
  rmSync(sandbox.homeDirectory, { recursive: true, force: true });
723
797
  }
@@ -1113,6 +1187,61 @@ test('a full reinstall removes the retired Everything path rewriter and keeps a
1113
1187
  }
1114
1188
  });
1115
1189
 
1190
+ test('a full reinstall removes retired pull request hooks and preserves foreign and recovery state', () => {
1191
+ const sandbox = createSandbox();
1192
+ try {
1193
+ runInstaller(sandbox.homeDirectory, []);
1194
+ const retiredPaths = RETIRED_PULL_REQUEST_HOOK_RELATIVE_PATHS.map(relativePath => (
1195
+ join(sandbox.claudeDirectory, HOOKS_DIRECTORY_NAME, ...relativePath.split('/'))
1196
+ ));
1197
+ for (const retiredPath of retiredPaths) {
1198
+ writeFileWithParents(retiredPath, 'an earlier install managed this file\n');
1199
+ }
1200
+ appendManifestFiles(sandbox.manifestPath, retiredPaths);
1201
+ const foreignHookPath = join(
1202
+ sandbox.claudeDirectory, HOOKS_DIRECTORY_NAME, 'blocking', 'foreign_pr_hook.py',
1203
+ );
1204
+ const foreignHookBytes = Buffer.from('a user hook the installer never wrote\n', 'utf8');
1205
+ writeFileWithParents(foreignHookPath, foreignHookBytes);
1206
+ const recoveryStatePath = join(
1207
+ sandbox.homeDirectory, 'pending', 'gh_pr_author_swap_session-a.json',
1208
+ );
1209
+ const recoveryStateBytes = Buffer.from('{"original_account":"prior"}\n', 'utf8');
1210
+ writeFileWithParents(recoveryStatePath, recoveryStateBytes);
1211
+ const settings = readSettings(sandbox.claudeDirectory);
1212
+ settings.hooks.PreToolUse.push({
1213
+ matcher: 'Bash',
1214
+ hooks: [
1215
+ ...RETIRED_PULL_REQUEST_REGISTRATION_PATHS.map(relativePath => ({
1216
+ type: 'command',
1217
+ command: `python3 "${join(sandbox.claudeDirectory, HOOKS_DIRECTORY_NAME, ...relativePath.split('/')).replace(/\\/g, '/')}"`,
1218
+ })),
1219
+ { type: 'command', command: `python3 "${foreignHookPath.replace(/\\/g, '/')}"` },
1220
+ ],
1221
+ });
1222
+ writeFileSync(
1223
+ join(sandbox.claudeDirectory, SETTINGS_FILE_NAME),
1224
+ JSON.stringify(settings, null, 4) + '\n',
1225
+ );
1226
+
1227
+ runInstaller(sandbox.homeDirectory, []);
1228
+
1229
+ const freshManifest = readManifest(sandbox.manifestPath);
1230
+ const allCommands = allHookCommands(readSettings(sandbox.claudeDirectory));
1231
+ for (const retiredPath of retiredPaths) {
1232
+ assert.equal(existsSync(retiredPath), false, `${retiredPath} leaves installed hooks`);
1233
+ assert.equal(freshManifest.files.includes(retiredPath), false);
1234
+ }
1235
+ for (const relativePath of RETIRED_PULL_REQUEST_REGISTRATION_PATHS) {
1236
+ assert.ok(allCommands.every(command => !command.includes(relativePath)));
1237
+ }
1238
+ assert.deepEqual(readFileSync(foreignHookPath), foreignHookBytes);
1239
+ assert.deepEqual(readFileSync(recoveryStatePath), recoveryStateBytes);
1240
+ } finally {
1241
+ rmSync(sandbox.homeDirectory, { recursive: true, force: true });
1242
+ }
1243
+ });
1244
+
1116
1245
  test('a full reinstall removes a retired hook entry under an event type the current config leaves out', () => {
1117
1246
  const sandbox = createSandbox();
1118
1247
  try {
@@ -0,0 +1,114 @@
1
+ import { test } from 'node:test';
2
+ import { strict as assert } from 'node:assert';
3
+ import {
4
+ mkdtempSync,
5
+ mkdirSync,
6
+ writeFileSync,
7
+ readFileSync,
8
+ existsSync,
9
+ rmSync,
10
+ } from 'node:fs';
11
+ import { tmpdir } from 'node:os';
12
+ import { dirname, join } from 'node:path';
13
+ import { fileURLToPath } from 'node:url';
14
+ import { execFileSync } from 'node:child_process';
15
+ import { resolveInstallRoot } from './resolve-install-root.mjs';
16
+ import { PSTACK_PLUGIN_MANIFEST_RELATIVE_PATH } from '../scripts/refresh_pstack_plugin_skills.mjs';
17
+
18
+ const THIS_DIRECTORY = dirname(fileURLToPath(import.meta.url));
19
+ const INSTALLER_PATH = join(THIS_DIRECTORY, 'install.mjs');
20
+ const PACKAGE_DIRECTORY = dirname(THIS_DIRECTORY);
21
+
22
+ function runInstaller(homeDirectory, extraArguments) {
23
+ return execFileSync('node', [INSTALLER_PATH, ...extraArguments], {
24
+ cwd: PACKAGE_DIRECTORY,
25
+ encoding: 'utf8',
26
+ env: {
27
+ ...process.env,
28
+ HOME: homeDirectory,
29
+ USERPROFILE: homeDirectory,
30
+ CODEX_HOME: join(homeDirectory, '.codex'),
31
+ CLAUDE_CONFIG_DIR: join(homeDirectory, '.claude'),
32
+ GIT_CONFIG_GLOBAL: join(homeDirectory, '.gitconfig'),
33
+ },
34
+ });
35
+ }
36
+
37
+ function seedPstackSkills(agentsHome, skillNames) {
38
+ const pluginRoot = join(agentsHome, 'skills', 'pstack');
39
+ for (const eachSkillName of skillNames) {
40
+ const skillDirectory = join(pluginRoot, eachSkillName);
41
+ mkdirSync(skillDirectory, { recursive: true });
42
+ writeFileSync(join(skillDirectory, 'SKILL.md'), '---\nname: ' + eachSkillName + '\n---\n');
43
+ }
44
+ return pluginRoot;
45
+ }
46
+
47
+ function withTemporaryHome(runAssertions) {
48
+ const homeDirectory = mkdtempSync(join(tmpdir(), 'cdev-pstack-plugin-'));
49
+ try {
50
+ const resolution = resolveInstallRoot({
51
+ homeDirectory,
52
+ environment: {},
53
+ explicitTarget: null,
54
+ });
55
+ runAssertions(homeDirectory, resolution);
56
+ } finally {
57
+ rmSync(homeDirectory, { recursive: true, force: true });
58
+ }
59
+ }
60
+
61
+ test('a pstack folder the user installed gains a plugin manifest', () => {
62
+ withTemporaryHome((homeDirectory, resolution) => {
63
+ const pluginRoot = seedPstackSkills(resolution.agentsHome, ['how', 'why']);
64
+
65
+ runInstaller(homeDirectory, ['--only', 'core']);
66
+
67
+ const manifestPath = join(pluginRoot, PSTACK_PLUGIN_MANIFEST_RELATIVE_PATH);
68
+ assert.equal(existsSync(manifestPath), true);
69
+ const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
70
+ assert.equal(manifest.name, 'pstack');
71
+ assert.deepEqual(manifest.skills, ['./how', './why']);
72
+ });
73
+ });
74
+
75
+ test('a second install leaves an unchanged manifest byte for byte', () => {
76
+ withTemporaryHome((homeDirectory, resolution) => {
77
+ const pluginRoot = seedPstackSkills(resolution.agentsHome, ['how']);
78
+
79
+ runInstaller(homeDirectory, ['--only', 'core']);
80
+ const manifestPath = join(pluginRoot, PSTACK_PLUGIN_MANIFEST_RELATIVE_PATH);
81
+ const afterFirstInstall = readFileSync(manifestPath, 'utf8');
82
+ runInstaller(homeDirectory, ['--only', 'core']);
83
+
84
+ assert.equal(readFileSync(manifestPath, 'utf8'), afterFirstInstall);
85
+ });
86
+ });
87
+
88
+ test('a later install lists a skill a pstack update added', () => {
89
+ withTemporaryHome((homeDirectory, resolution) => {
90
+ const pluginRoot = seedPstackSkills(resolution.agentsHome, ['how']);
91
+ runInstaller(homeDirectory, ['--only', 'core']);
92
+
93
+ seedPstackSkills(resolution.agentsHome, ['why']);
94
+ runInstaller(homeDirectory, ['--only', 'core']);
95
+
96
+ const manifestPath = join(pluginRoot, PSTACK_PLUGIN_MANIFEST_RELATIVE_PATH);
97
+ const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
98
+ assert.deepEqual(manifest.skills, ['./how', './why']);
99
+ });
100
+ });
101
+
102
+ test('an install with no pstack folder writes no manifest and does not fail', () => {
103
+ withTemporaryHome((homeDirectory, resolution) => {
104
+ runInstaller(homeDirectory, ['--only', 'core']);
105
+
106
+ const manifestPath = join(
107
+ resolution.agentsHome,
108
+ 'skills',
109
+ 'pstack',
110
+ PSTACK_PLUGIN_MANIFEST_RELATIVE_PATH,
111
+ );
112
+ assert.equal(existsSync(manifestPath), false);
113
+ });
114
+ });
@@ -43,6 +43,7 @@ import {
43
43
  retiredManagedHookRelativePaths,
44
44
  pruneRetiredHookEntriesFromSettings,
45
45
  retainNewestRunBackupOnly,
46
+ refreshInstalledPstackPluginManifest,
46
47
  } from './install.mjs';
47
48
  import { EVER_SHIPPED_SKILL_NAMES } from './ever-shipped-skills.mjs';
48
49
  import {
@@ -228,6 +229,11 @@ test('CORE_SKILLS ships ELI5 as the leaf presentation skill', () => {
228
229
  assert.equal(INSTALL_GROUPS.core.skills.includes('eli5'), true);
229
230
  });
230
231
 
232
+ test('CORE_SKILLS ships test-runner for explicit Django and Playwright readiness checks', () => {
233
+ assert.ok(CORE_SKILLS.includes('test-runner'));
234
+ assert.equal(INSTALL_GROUPS.core.skills.includes('test-runner'), true);
235
+ });
236
+
231
237
 
232
238
  test('EVER_SHIPPED_SKILL_NAMES retains imagegen so reinstall prunes the retired skill', () => {
233
239
  assert.ok(
@@ -1246,7 +1252,7 @@ const OLD_FOLDED_HOOKS_SETTINGS = {
1246
1252
 
1247
1253
 
1248
1254
  test('FOLDED_HOOK_RELATIVE_PATHS contains folded hooks plus retired entries', () => {
1249
- assert.equal(FOLDED_HOOK_RELATIVE_PATHS.size, 16);
1255
+ assert.equal(FOLDED_HOOK_RELATIVE_PATHS.size, 21);
1250
1256
  assert.ok(FOLDED_HOOK_RELATIVE_PATHS.has('blocking/write_existing_file_blocker.py'));
1251
1257
  assert.ok(FOLDED_HOOK_RELATIVE_PATHS.has('blocking/code_rules_enforcer.py'));
1252
1258
  assert.ok(FOLDED_HOOK_RELATIVE_PATHS.has('blocking/pytest_testpaths_orphan_blocker.py'));
@@ -1256,16 +1262,22 @@ test('FOLDED_HOOK_RELATIVE_PATHS contains folded hooks plus retired entries', ()
1256
1262
 
1257
1263
 
1258
1264
  test('retired hook registrations stay managed so reinstall removes them', () => {
1259
- const retiredPath = 'session/untracked_repo_detector.py';
1265
+ const retiredPaths = new Set([
1266
+ 'session/untracked_repo_detector.py',
1267
+ 'session/gh_pr_author_session_cleanup.py',
1268
+ 'observability/pr_description_writer_spawn_tracker.py',
1269
+ ]);
1260
1270
  const shippedHooks = JSON.parse(
1261
1271
  readFileSync(new URL('../hooks/hooks.json', import.meta.url), 'utf8')
1262
1272
  );
1263
1273
  const shippedCommands = Object.values(shippedHooks.hooks).flatMap(matcherGroups => (
1264
1274
  matcherGroups.flatMap(matcherGroup => matcherGroup.hooks.map(hook => hook.command))
1265
1275
  ));
1266
- assert.deepEqual(RETIRED_HOOK_REGISTRATION_RELATIVE_PATHS, new Set([retiredPath]));
1267
- assert.ok(managedHookScriptRelativePaths({ hooks: {} }).has(retiredPath));
1268
- assert.ok(shippedCommands.every(command => !command.includes(retiredPath)));
1276
+ assert.deepEqual(RETIRED_HOOK_REGISTRATION_RELATIVE_PATHS, retiredPaths);
1277
+ for (const retiredPath of retiredPaths) {
1278
+ assert.ok(managedHookScriptRelativePaths({ hooks: {} }).has(retiredPath));
1279
+ assert.ok(shippedCommands.every(command => !command.includes(retiredPath)));
1280
+ }
1269
1281
  });
1270
1282
 
1271
1283
 
@@ -1288,6 +1300,11 @@ test('FOLDED_HOOK_RELATIVE_PATHS lists every hook the PreToolUse dispatcher host
1288
1300
  ];
1289
1301
  const retiredHooks = [
1290
1302
  'blocking/md_to_html_blocker.py',
1303
+ 'blocking/gh_body_arg_blocker.py',
1304
+ 'blocking/volatile_path_in_post_blocker.py',
1305
+ 'blocking/conventional_pr_title_gate.py',
1306
+ 'blocking/gh_pr_author_enforcer.py',
1307
+ 'blocking/pr_description_writer_gate.py',
1291
1308
  'session/untracked_repo_detector.py',
1292
1309
  ];
1293
1310
  for (const hostedPath of dispatcherHostedHooks) {
@@ -1480,12 +1497,13 @@ const OLD_POST_FOLDED_HOOKS_SETTINGS = {
1480
1497
  };
1481
1498
 
1482
1499
 
1483
- test('POST_FOLDED_HOOK_RELATIVE_PATHS contains the after-write hooks and the retired md_to_html_companion', () => {
1484
- assert.equal(POST_FOLDED_HOOK_RELATIVE_PATHS.size, 4);
1500
+ test('POST_FOLDED_HOOK_RELATIVE_PATHS contains hosted and retired post-tool hooks', () => {
1501
+ assert.equal(POST_FOLDED_HOOK_RELATIVE_PATHS.size, 5);
1485
1502
  assert.ok(POST_FOLDED_HOOK_RELATIVE_PATHS.has('validation/mypy_validator.py'));
1486
1503
  assert.ok(POST_FOLDED_HOOK_RELATIVE_PATHS.has('workflow/auto_formatter.py'));
1487
1504
  assert.ok(POST_FOLDED_HOOK_RELATIVE_PATHS.has('workflow/doc_gist_auto_publish.py'));
1488
1505
  assert.ok(POST_FOLDED_HOOK_RELATIVE_PATHS.has('workflow/md_to_html_companion.py'));
1506
+ assert.ok(POST_FOLDED_HOOK_RELATIVE_PATHS.has('blocking/gh_pr_author_restore.py'));
1489
1507
  });
1490
1508
 
1491
1509
 
@@ -2524,3 +2542,39 @@ test('pruneStaleInstalledFiles leaves the run backup root standing when the rena
2524
2542
  rmSync(sandbox.root, { recursive: true, force: true });
2525
2543
  }
2526
2544
  });
2545
+
2546
+ test('the pstack step writes a manifest for a skills root that carries pstack', () => {
2547
+ const skillsRoot = mkdtempSync(join(tmpdir(), 'cdev-pstack-step-'));
2548
+ try {
2549
+ const skillDirectory = join(skillsRoot, 'pstack', 'how');
2550
+ mkdirSync(skillDirectory, { recursive: true });
2551
+ writeFileSync(join(skillDirectory, 'SKILL.md'), '---\nname: how\n---\n');
2552
+
2553
+ const manifestPath = refreshInstalledPstackPluginManifest(skillsRoot);
2554
+
2555
+ assert.equal(
2556
+ manifestPath,
2557
+ join(skillsRoot, 'pstack', '.claude-plugin', 'plugin.json'),
2558
+ 'the step reports the manifest it wrote so the run records the path',
2559
+ );
2560
+ const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
2561
+ assert.deepEqual(manifest.skills, ['./how']);
2562
+ assert.equal(
2563
+ refreshInstalledPstackPluginManifest(skillsRoot),
2564
+ null,
2565
+ 'a second run writes nothing and reports no path',
2566
+ );
2567
+ } finally {
2568
+ rmSync(skillsRoot, { recursive: true, force: true });
2569
+ }
2570
+ });
2571
+
2572
+ test('the pstack step reports no path for a skills root without pstack', () => {
2573
+ const skillsRoot = mkdtempSync(join(tmpdir(), 'cdev-pstack-absent-'));
2574
+ try {
2575
+ assert.equal(refreshInstalledPstackPluginManifest(skillsRoot), null);
2576
+ assert.equal(existsSync(join(skillsRoot, 'pstack')), false);
2577
+ } finally {
2578
+ rmSync(skillsRoot, { recursive: true, force: true });
2579
+ }
2580
+ });
@@ -0,0 +1,211 @@
1
+ import { test } from 'node:test';
2
+ import { strict as assert } from 'node:assert';
3
+ import {
4
+ mkdtempSync,
5
+ mkdirSync,
6
+ readFileSync,
7
+ rmSync,
8
+ symlinkSync,
9
+ writeFileSync,
10
+ } from 'node:fs';
11
+ import { tmpdir } from 'node:os';
12
+ import { dirname, join } from 'node:path';
13
+ import { fileURLToPath } from 'node:url';
14
+ import { execFileSync } from 'node:child_process';
15
+ import {
16
+ PSTACK_PLUGIN_MANIFEST_RELATIVE_PATH,
17
+ findPstackSkillDirectoryNames,
18
+ refreshPstackPluginManifest,
19
+ } from '../scripts/refresh_pstack_plugin_skills.mjs';
20
+
21
+ function buildPluginRoot(homeDirectory, skillNames, manifest) {
22
+ const pluginRoot = join(homeDirectory, 'pstack');
23
+ for (const eachSkillName of skillNames) {
24
+ const skillDirectory = join(pluginRoot, eachSkillName);
25
+ mkdirSync(skillDirectory, { recursive: true });
26
+ writeFileSync(join(skillDirectory, 'SKILL.md'), '---\n');
27
+ }
28
+ const manifestPath = join(pluginRoot, PSTACK_PLUGIN_MANIFEST_RELATIVE_PATH);
29
+ mkdirSync(join(pluginRoot, '.claude-plugin'), { recursive: true });
30
+ if (manifest !== null) {
31
+ writeFileSync(manifestPath, JSON.stringify(manifest));
32
+ }
33
+ return pluginRoot;
34
+ }
35
+
36
+ function withTemporaryHome(runAssertions) {
37
+ const homeDirectory = mkdtempSync(join(tmpdir(), 'cdev-pstack-manifest-'));
38
+ try {
39
+ runAssertions(homeDirectory);
40
+ } finally {
41
+ rmSync(homeDirectory, { recursive: true, force: true });
42
+ }
43
+ }
44
+
45
+ test('finds only sub-folders that hold a skill entry file', () => {
46
+ withTemporaryHome((homeDirectory) => {
47
+ const pluginRoot = buildPluginRoot(homeDirectory, ['how', 'why'], { skills: [] });
48
+ mkdirSync(join(pluginRoot, 'docs'), { recursive: true });
49
+ writeFileSync(join(pluginRoot, '.claude-plugin', 'SKILL.md'), '---\n');
50
+
51
+ assert.deepEqual(findPstackSkillDirectoryNames(pluginRoot), ['how', 'why']);
52
+ });
53
+ });
54
+
55
+ test('writes a manifest when the plugin folder has none', () => {
56
+ withTemporaryHome((homeDirectory) => {
57
+ const pluginRoot = buildPluginRoot(homeDirectory, ['how'], null);
58
+
59
+ const outcome = refreshPstackPluginManifest(pluginRoot);
60
+
61
+ assert.equal(outcome.didWrite, true);
62
+ const manifestPath = join(pluginRoot, PSTACK_PLUGIN_MANIFEST_RELATIVE_PATH);
63
+ const written = JSON.parse(readFileSync(manifestPath, 'utf8'));
64
+ assert.equal(written.name, 'pstack');
65
+ assert.deepEqual(written.skills, ['./how']);
66
+ });
67
+ });
68
+
69
+ test('adds a skill folder the manifest does not list', () => {
70
+ withTemporaryHome((homeDirectory) => {
71
+ const pluginRoot = buildPluginRoot(homeDirectory, ['how', 'why'], {
72
+ name: 'pstack',
73
+ skills: ['./how'],
74
+ });
75
+
76
+ const outcome = refreshPstackPluginManifest(pluginRoot);
77
+
78
+ assert.equal(outcome.didWrite, true);
79
+ assert.deepEqual(outcome.previousSkillPaths, ['./how']);
80
+ assert.deepEqual(outcome.currentSkillPaths, ['./how', './why']);
81
+ });
82
+ });
83
+
84
+ test('leaves the manifest byte for byte when nothing changed', () => {
85
+ withTemporaryHome((homeDirectory) => {
86
+ const pluginRoot = buildPluginRoot(homeDirectory, ['how'], {
87
+ name: 'pstack',
88
+ skills: ['./how'],
89
+ });
90
+ const manifestPath = join(pluginRoot, PSTACK_PLUGIN_MANIFEST_RELATIVE_PATH);
91
+ writeFileSync(manifestPath, JSON.stringify({ name: 'pstack', skills: ['./how'] }));
92
+ const before = readFileSync(manifestPath, 'utf8');
93
+
94
+ const outcome = refreshPstackPluginManifest(pluginRoot);
95
+
96
+ assert.equal(outcome.didWrite, false);
97
+ assert.equal(readFileSync(manifestPath, 'utf8'), before);
98
+ });
99
+ });
100
+
101
+ test('reads a string skills value as a one-entry list', () => {
102
+ withTemporaryHome((homeDirectory) => {
103
+ const pluginRoot = buildPluginRoot(homeDirectory, ['how'], {
104
+ name: 'pstack',
105
+ skills: './',
106
+ });
107
+
108
+ const outcome = refreshPstackPluginManifest(pluginRoot);
109
+
110
+ assert.deepEqual(outcome.previousSkillPaths, ['./']);
111
+ assert.deepEqual(outcome.currentSkillPaths, ['./how']);
112
+ });
113
+ });
114
+
115
+ test('keeps manifest fields the refresher does not own', () => {
116
+ withTemporaryHome((homeDirectory) => {
117
+ const pluginRoot = buildPluginRoot(homeDirectory, ['how'], {
118
+ name: 'pstack',
119
+ version: '9.9.9',
120
+ description: 'a description the user edited',
121
+ skills: [],
122
+ });
123
+
124
+ refreshPstackPluginManifest(pluginRoot);
125
+
126
+ const manifestPath = join(pluginRoot, PSTACK_PLUGIN_MANIFEST_RELATIVE_PATH);
127
+ const written = JSON.parse(readFileSync(manifestPath, 'utf8'));
128
+ assert.equal(written.version, '9.9.9');
129
+ assert.equal(written.description, 'a description the user edited');
130
+ });
131
+ });
132
+
133
+ test('refuses a folder that is itself one skill', () => {
134
+ withTemporaryHome((homeDirectory) => {
135
+ const pluginRoot = buildPluginRoot(homeDirectory, ['how'], null);
136
+ writeFileSync(join(pluginRoot, 'SKILL.md'), '---\n');
137
+
138
+ const outcome = refreshPstackPluginManifest(pluginRoot);
139
+
140
+ assert.equal(outcome.didWrite, false);
141
+ assert.equal(outcome.reason, 'plugin root is itself a skill');
142
+ });
143
+ });
144
+
145
+ test('running the file directly reports what it did', () => {
146
+ withTemporaryHome((homeDirectory) => {
147
+ const pluginRoot = buildPluginRoot(homeDirectory, ['how'], null);
148
+ const modulePath = join(
149
+ dirname(dirname(fileURLToPath(import.meta.url))),
150
+ 'scripts',
151
+ 'refresh_pstack_plugin_skills.mjs',
152
+ );
153
+
154
+ const firstRun = execFileSync('node', [modulePath, pluginRoot], { encoding: 'utf8' });
155
+ const secondRun = execFileSync('node', [modulePath, pluginRoot], { encoding: 'utf8' });
156
+
157
+ assert.match(firstRun, /Updated\. 1 skills listed\./);
158
+ assert.match(secondRun, /No change\. 1 skills listed\./);
159
+ });
160
+ });
161
+
162
+ test('running the file through a directory link still reports what it did', () => {
163
+ withTemporaryHome((homeDirectory) => {
164
+ const pluginRoot = buildPluginRoot(homeDirectory, ['how'], null);
165
+ const scriptsDirectory = join(
166
+ dirname(dirname(fileURLToPath(import.meta.url))),
167
+ 'scripts',
168
+ );
169
+ const linkedScriptsDirectory = join(homeDirectory, 'linked-scripts');
170
+ symlinkSync(scriptsDirectory, linkedScriptsDirectory, 'junction');
171
+ const linkedModulePath = join(linkedScriptsDirectory, 'refresh_pstack_plugin_skills.mjs');
172
+
173
+ const output = execFileSync('node', [linkedModulePath, pluginRoot], { encoding: 'utf8' });
174
+
175
+ assert.match(output, /Updated\. 1 skills listed\./);
176
+ });
177
+ });
178
+
179
+ test('with no folder argument it follows CLAUDE_CONFIG_DIR', () => {
180
+ withTemporaryHome((homeDirectory) => {
181
+ const claudeConfigDirectory = join(homeDirectory, 'profile', '.claude');
182
+ const pluginRoot = join(claudeConfigDirectory, 'skills', 'pstack');
183
+ mkdirSync(join(pluginRoot, 'how'), { recursive: true });
184
+ writeFileSync(join(pluginRoot, 'how', 'SKILL.md'), '---\n');
185
+ const modulePath = join(
186
+ dirname(dirname(fileURLToPath(import.meta.url))),
187
+ 'scripts',
188
+ 'refresh_pstack_plugin_skills.mjs',
189
+ );
190
+
191
+ const output = execFileSync('node', [modulePath], {
192
+ encoding: 'utf8',
193
+ env: { ...process.env, CLAUDE_CONFIG_DIR: claudeConfigDirectory },
194
+ });
195
+
196
+ assert.match(output, /Updated\. 1 skills listed\./);
197
+ const manifestPath = join(pluginRoot, PSTACK_PLUGIN_MANIFEST_RELATIVE_PATH);
198
+ assert.deepEqual(JSON.parse(readFileSync(manifestPath, 'utf8')).skills, ['./how']);
199
+ });
200
+ });
201
+
202
+ test('refuses a folder that holds no skills', () => {
203
+ withTemporaryHome((homeDirectory) => {
204
+ const pluginRoot = buildPluginRoot(homeDirectory, [], null);
205
+
206
+ const outcome = refreshPstackPluginManifest(pluginRoot);
207
+
208
+ assert.equal(outcome.didWrite, false);
209
+ assert.equal(outcome.reason, 'plugin root holds no skills');
210
+ });
211
+ });