claude-dev-env 4.3.0 → 7.1.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 (480) hide show
  1. package/.agents/agents/pr-description-writer.md +2 -2
  2. package/.agents/agents/test_agent_frontmatter.py +2 -2
  3. package/.agents/agents/test_pr_description_writer_style.py +1 -3
  4. package/.agents/skills/e-code-review/SKILL.md +12 -12
  5. package/.agents/skills/e-code-review/preflight-proposal.contract.test.mjs +20 -0
  6. package/.agents/skills/e-code-review/reference/fix.md +15 -17
  7. package/.agents/skills/e-code-review/reference/loop.md +35 -39
  8. package/.agents/skills/e-code-review/reference/mode-contract.test.mjs +2 -1
  9. package/.agents/skills/e-code-review/reference/preflight-proposal.md +44 -9
  10. package/.agents/skills/e-code-review/reference/runner-selection.md +13 -14
  11. package/.agents/skills/issue-tracker/SKILL.md +5 -4
  12. package/.agents/skills/pull-request/SKILL.md +11 -13
  13. package/.agents/skills/pull-request/reference/publication-tasks.md +3 -3
  14. package/.agents/skills/pull-request/scripts/github_pr_command_constants/config/constants.py +9 -1
  15. package/.agents/skills/pull-request/scripts/pull_request.py +35 -44
  16. package/.agents/skills/pull-request/scripts/test_github_pr_command_constants.py +9 -0
  17. package/.agents/skills/pull-request/scripts/test_pull_request.py +35 -3
  18. package/.agents/skills/session-continuity/README.md +189 -0
  19. package/.agents/skills/session-continuity/SKILL.md +114 -0
  20. package/.agents/skills/session-continuity/continuity.mjs +375 -0
  21. package/.agents/skills/session-continuity/continuity.test.mjs +303 -0
  22. package/.agents/skills/skill-builder/SKILL.md +3 -196
  23. package/.agents/skills/syncing-submodules/scripts/test_sync_parent_pointer.py +0 -7
  24. package/.agents/skills/windows-scheduled-task/SKILL.md +153 -0
  25. package/_shared/pr-loop/scripts/pr_loop_shared_constants/terminology_sweep_constants.py +6 -0
  26. package/_shared/pr-loop/scripts/terminology_sweep.py +64 -19
  27. package/_shared/pr-loop/scripts/tests/test_terminology_sweep.py +59 -0
  28. package/bin/cde.mjs +36 -8
  29. package/bin/cde.test.mjs +48 -0
  30. package/bin/ever-shipped-skills.mjs +2 -20
  31. package/bin/ever-shipped-skills.test.mjs +8 -0
  32. package/bin/expand_home_directory_tokens.mjs +4 -5
  33. package/bin/expand_home_directory_tokens.test.mjs +128 -0
  34. package/bin/git_hooks_installer.mjs +22 -3
  35. package/bin/git_hooks_installer.test.mjs +159 -5
  36. package/bin/install-session-continuity.mjs +78 -0
  37. package/bin/install-session-continuity.test.mjs +42 -0
  38. package/bin/install.agents-home.test.mjs +15 -13
  39. package/bin/install.codex-rules.test.mjs +5 -2
  40. package/bin/install.cursor-rules.test.mjs +5 -2
  41. package/bin/install.mjs +506 -63
  42. package/bin/install.policy-lint.test.mjs +130 -0
  43. package/bin/install.profiles.test.mjs +10 -0
  44. package/bin/install.prune.test.mjs +286 -37
  45. package/bin/install.pstack-plugin.test.mjs +114 -0
  46. package/bin/install.test.mjs +537 -120
  47. package/bin/install.transaction.test.mjs +3 -3
  48. package/bin/refresh-pstack-plugin-skills.test.mjs +211 -0
  49. package/docs/CODE_RULES.md +1 -1
  50. package/hooks/advisory/pr_done_reminder.py +342 -0
  51. package/hooks/advisory/test_pr_done_reminder.py +249 -0
  52. package/hooks/blocking/bash_post_call_dispatcher.py +53 -6
  53. package/hooks/blocking/code_rules_banned_identifiers.py +3 -0
  54. package/hooks/blocking/code_rules_paired_test.py +22 -15
  55. package/hooks/blocking/precommit_code_rules_gate.py +5 -8
  56. package/hooks/blocking/test_bash_dispatcher_interpreter_starts.py +7 -52
  57. package/hooks/blocking/test_bash_post_call_dispatcher.py +65 -4
  58. package/hooks/blocking/test_bash_pre_tool_use_dispatcher.py +0 -69
  59. package/hooks/blocking/test_code_rules_banned_identifiers.py +64 -0
  60. package/hooks/blocking/test_code_rules_enforcer_paired_test.py +36 -29
  61. package/hooks/blocking/test_code_rules_js_resume_task_enumeration.py +4 -4
  62. package/hooks/blocking/test_code_rules_js_returns_object_schemaless.py +5 -5
  63. package/hooks/blocking/test_pre_tool_use_dispatcher.py +43 -342
  64. package/hooks/blocking/test_pre_tool_use_dispatcher_native.py +20 -51
  65. package/hooks/blocking/test_shared_stdin_adoption.py +1 -47
  66. package/hooks/git-hooks/git_hooks_constants/staged_policy_lint.py +12 -0
  67. package/hooks/git-hooks/git_hooks_constants/verification_notice_constants.py +116 -0
  68. package/hooks/git-hooks/post_commit.py +33 -0
  69. package/hooks/git-hooks/pre_commit.py +18 -49
  70. package/hooks/git-hooks/pre_push.py +45 -541
  71. package/hooks/git-hooks/test_installed_policy_lint.py +208 -0
  72. package/hooks/git-hooks/test_native_hook_support.py +129 -0
  73. package/hooks/git-hooks/test_native_policy_commit.py +120 -0
  74. package/hooks/git-hooks/test_post_commit.py +43 -0
  75. package/hooks/git-hooks/test_pre_commit.py +38 -148
  76. package/hooks/git-hooks/test_pre_push.py +48 -1631
  77. package/hooks/git-hooks/test_verification_notice.py +60 -0
  78. package/hooks/git-hooks/test_verification_start.py +173 -0
  79. package/hooks/git-hooks/verification_notice.py +192 -0
  80. package/hooks/git-hooks/verification_notice_context.py +253 -0
  81. package/hooks/git-hooks/verification_notice_state.py +237 -0
  82. package/hooks/git-hooks/verification_start.py +100 -0
  83. package/hooks/hooks.json +1 -120
  84. package/hooks/hooks_constants/banned_identifiers_constants.py +4 -0
  85. package/hooks/hooks_constants/bash_post_call_dispatcher_constants.py +14 -1
  86. package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +1 -15
  87. package/hooks/hooks_constants/post_tool_use_dispatcher_constants.py +0 -4
  88. package/hooks/hooks_constants/pr_done_reminder_constants.py +98 -0
  89. package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +10 -93
  90. package/hooks/hooks_constants/stop_dispatcher_constants.py +1 -4
  91. package/hooks/hooks_constants/test_bash_post_call_dispatcher_constants.py +19 -2
  92. package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +3 -66
  93. package/hooks/hooks_constants/test_pr_done_reminder_constants.py +40 -0
  94. package/hooks/hooks_constants/test_pre_tool_use_dispatcher_constants.py +46 -201
  95. package/hooks/hooks_constants/test_stop_dispatcher_constants.py +3 -25
  96. package/hooks/validation/test_post_tool_use_dispatcher.py +35 -298
  97. package/hooks/validators/run_all_validators.py +5 -1
  98. package/hooks/validators/security_checks.py +57 -2
  99. package/hooks/validators/test_hook_timing_harness.py +2 -5
  100. package/hooks/validators/test_run_all_validators_mutation_tool_parity.py +3 -11
  101. package/hooks/validators/test_run_all_validators_policy_lint.py +36 -0
  102. package/hooks/validators/test_security_checks.py +57 -0
  103. package/package.json +2 -2
  104. package/rules/confirm-implementation-forks.md +2 -0
  105. package/rules/durable-post-artifacts.md +2 -1
  106. package/rules/git-workflow.md +18 -6
  107. package/rules/pstack-host-mapping.md +48 -0
  108. package/rules/vault-context.md +3 -5
  109. package/rules/verify-runtime-state.md +7 -0
  110. package/scripts/audit-hooks-cli.mjs +5 -0
  111. package/scripts/audit-hooks.mjs +72 -16
  112. package/scripts/automatic_advisory/__init__.py +1 -0
  113. package/scripts/automatic_advisory/checkout.py +51 -0
  114. package/scripts/automatic_advisory/cli.py +334 -0
  115. package/scripts/automatic_advisory/config/__init__.py +1 -0
  116. package/scripts/automatic_advisory/config/constants.py +93 -0
  117. package/scripts/automatic_advisory/config/timing.py +4 -0
  118. package/scripts/automatic_advisory/configuration.py +197 -0
  119. package/scripts/automatic_advisory/execution.py +350 -0
  120. package/scripts/automatic_advisory/git.py +69 -0
  121. package/scripts/automatic_advisory/model.py +107 -0
  122. package/scripts/automatic_advisory/process_host.py +32 -0
  123. package/scripts/automatic_advisory/publisher.py +84 -0
  124. package/scripts/automatic_advisory/runner.py +399 -0
  125. package/scripts/automatic_advisory/state.py +116 -0
  126. package/scripts/automatic_advisory/window_flags.py +41 -0
  127. package/scripts/automatic_advisory/windows_job.py +122 -0
  128. package/scripts/check.ps1 +0 -1
  129. package/{.agents/skills/run-claude-dev-env/driver.mjs → scripts/ci/installer-lifecycle.mjs} +3 -28
  130. package/scripts/ci/installer-lifecycle.test.mjs +26 -0
  131. package/scripts/ci/windows-installer-lifecycle.ps1 +1 -11
  132. package/scripts/codex_compat_materializer.py +99 -13
  133. package/scripts/dev_env_scripts_constants/shared_tree_constants.py +4 -5
  134. package/{.agents/skills/_shared/pr-loop/scripts → scripts}/durable_post_lint.py +35 -43
  135. package/{.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/durable_post_lint_constants.py → scripts/durable_post_lint_config/config/constants.py} +10 -10
  136. package/{.agents/skills/_shared/pr-loop/scripts/test_durable_post_lint_constants.py → scripts/durable_post_lint_config/config/test_constants.py} +14 -1
  137. package/scripts/local_report_cli.py +184 -0
  138. package/scripts/local_report_core.py +247 -0
  139. package/scripts/local_report_publisher.py +12 -0
  140. package/scripts/local_report_validation.py +357 -0
  141. package/scripts/local_verification/__init__.py +23 -0
  142. package/scripts/local_verification/check_runner.py +364 -0
  143. package/scripts/local_verification/check_support.py +99 -0
  144. package/scripts/local_verification/cli.py +158 -0
  145. package/scripts/local_verification/command_runner.py +84 -0
  146. package/scripts/local_verification/config/__init__.py +1 -0
  147. package/scripts/local_verification/config/constants.py +89 -0
  148. package/scripts/local_verification/config/timing.py +1 -0
  149. package/scripts/local_verification/git_state.py +146 -0
  150. package/scripts/local_verification/manifest.py +224 -0
  151. package/scripts/local_verification/model.py +174 -0
  152. package/scripts/local_verification/runner.py +234 -0
  153. package/scripts/policy_lint/adapter_detectors.py +13 -6
  154. package/scripts/policy_lint/adapter_pairing.py +58 -3
  155. package/scripts/policy_lint/adapters.py +7 -4
  156. package/scripts/policy_lint/config/approved_test_pairs.py +323 -0
  157. package/scripts/policy_lint/config/constants.py +5 -0
  158. package/scripts/policy_lint/engine.py +48 -4
  159. package/scripts/policy_lint/model.py +1 -0
  160. package/scripts/policy_lint/registry.py +1 -1
  161. package/scripts/policy_lint/selection.py +2 -1
  162. package/scripts/policy_lint/selection_git.py +2 -0
  163. package/scripts/pr_verification/__init__.py +1 -0
  164. package/scripts/pr_verification/branch_refs.py +114 -0
  165. package/scripts/pr_verification/config/__init__.py +1 -0
  166. package/scripts/pr_verification/config/constants.py +84 -0
  167. package/scripts/pr_verification/config/timing.py +5 -0
  168. package/scripts/pr_verification/github.py +137 -0
  169. package/scripts/pr_verification/github_api.py +376 -0
  170. package/scripts/pr_verification/github_labels.py +88 -0
  171. package/scripts/pr_verification/github_parsing.py +182 -0
  172. package/scripts/pr_verification/github_transport.py +91 -0
  173. package/scripts/pr_verification/lock.py +122 -0
  174. package/scripts/pr_verification/model.py +43 -0
  175. package/scripts/pr_verification/requirements.txt +1 -0
  176. package/scripts/refresh_pstack_plugin_skills.mjs +200 -0
  177. package/scripts/shared_tree_paths.py +32 -3
  178. package/{.agents/skills/_shared/pr-loop/scripts → scripts}/test_durable_post_lint.py +97 -0
  179. package/scripts/test_shared_tree_paths.py +33 -0
  180. package/scripts/tests/test-audit-hooks-cli.mjs +150 -0
  181. package/scripts/tests/test-audit-hooks.mjs +94 -2
  182. package/scripts/tests/test_automatic_advisory.py +376 -0
  183. package/scripts/tests/test_automatic_advisory_job_cleanup.py +71 -0
  184. package/scripts/tests/test_automatic_advisory_polling.py +216 -0
  185. package/scripts/tests/test_automatic_advisory_recovery.py +320 -0
  186. package/scripts/tests/test_automatic_advisory_repairs.py +400 -0
  187. package/scripts/tests/test_automatic_advisory_settings.py +48 -0
  188. package/scripts/tests/test_automatic_advisory_window_flags.py +163 -0
  189. package/scripts/tests/test_closed_pr_label.py +157 -0
  190. package/scripts/tests/test_codex_compat_materializer.py +189 -6
  191. package/scripts/tests/test_github_labels.py +101 -0
  192. package/scripts/tests/test_github_transport.py +125 -0
  193. package/scripts/tests/test_local_report_publisher.py +396 -0
  194. package/scripts/tests/test_local_report_publisher_cli.py +85 -0
  195. package/scripts/tests/test_local_verification.py +348 -0
  196. package/scripts/tests/test_local_verification_cli.py +98 -0
  197. package/scripts/tests/test_policy_lint_rules_archive.py +120 -0
  198. package/scripts/tests/test_policy_lint_rules_engine.py +25 -4
  199. package/scripts/tests/test_policy_lint_rules_installed_loader.py +29 -0
  200. package/scripts/tests/test_policy_lint_rules_pairing.py +163 -0
  201. package/scripts/tests/test_policy_lint_rules_registry.py +227 -13
  202. package/scripts/tests/test_policy_lint_selection.py +25 -2
  203. package/scripts/tests/test_policy_lint_selection_native_index.py +30 -0
  204. package/scripts/tests/test_pr_verification_github.py +392 -0
  205. package/scripts/tests/test_pr_verification_lock.py +168 -0
  206. package/scripts/tests/test_rules.py +3 -7
  207. package/scripts/tests/test_verification_notice.py +347 -0
  208. package/.agents/skills/_shared/pr-loop/.claude/CLAUDE.md +0 -1
  209. package/.agents/skills/_shared/pr-loop/audit-contract.md +0 -5
  210. package/.agents/skills/_shared/pr-loop/audit-reply-template.md +0 -5
  211. package/.agents/skills/_shared/pr-loop/code-rules-gate.md +0 -5
  212. package/.agents/skills/_shared/pr-loop/fix-protocol.md +0 -5
  213. package/.agents/skills/_shared/pr-loop/gh-payloads.md +0 -5
  214. package/.agents/skills/_shared/pr-loop/portable-driver.md +0 -149
  215. package/.agents/skills/_shared/pr-loop/post-audit-thread-contract.md +0 -5
  216. package/.agents/skills/_shared/pr-loop/precatch-rubric.md +0 -5
  217. package/.agents/skills/_shared/pr-loop/preflight-proposal.contract.test.mjs +0 -119
  218. package/.agents/skills/_shared/pr-loop/preflight-proposal.md +0 -5
  219. package/.agents/skills/_shared/pr-loop/prompts/.claude/CLAUDE.md +0 -1
  220. package/.agents/skills/_shared/pr-loop/prompts/AGENTS.md +0 -1
  221. package/.agents/skills/_shared/pr-loop/prompts/pr-consistency-audit.xml +0 -324
  222. package/.agents/skills/_shared/pr-loop/scripts/.claude/CLAUDE.md +0 -1
  223. package/.agents/skills/_shared/pr-loop/scripts/AGENTS.md +0 -1
  224. package/.agents/skills/_shared/pr-loop/scripts/RUNTIME_SCRIPTS.md +0 -29
  225. package/.agents/skills/_shared/pr-loop/scripts/_cli_utils.py +0 -22
  226. package/.agents/skills/_shared/pr-loop/scripts/_path_resolver.py +0 -186
  227. package/.agents/skills/_shared/pr-loop/scripts/_xml_utils.py +0 -20
  228. package/.agents/skills/_shared/pr-loop/scripts/audit_category_schema.py +0 -355
  229. package/.agents/skills/_shared/pr-loop/scripts/build_audit_prompt.py +0 -289
  230. package/.agents/skills/_shared/pr-loop/scripts/build_converge_task_list.py +0 -310
  231. package/.agents/skills/_shared/pr-loop/scripts/build_fix_prompt.py +0 -292
  232. package/.agents/skills/_shared/pr-loop/scripts/init_loop_state.py +0 -134
  233. package/.agents/skills/_shared/pr-loop/scripts/portable_converge_driver.py +0 -1646
  234. package/.agents/skills/_shared/pr-loop/scripts/preflight_worktree.py +0 -392
  235. package/.agents/skills/_shared/pr-loop/scripts/select_converge_pacer.py +0 -215
  236. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/.claude/CLAUDE.md +0 -1
  237. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/AGENTS.md +0 -1
  238. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/__init__.py +0 -0
  239. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/audit_category_schema_constants.py +0 -37
  240. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/converge_task_list_constants.py +0 -56
  241. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/handoff_constants.py +0 -45
  242. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/pacer_constants.py +0 -47
  243. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/path_resolver_constants.py +0 -157
  244. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/portable_driver_constants.py +0 -177
  245. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/preflight_constants.py +0 -70
  246. package/.agents/skills/_shared/pr-loop/scripts/teardown_worktrees.py +0 -172
  247. package/.agents/skills/_shared/pr-loop/scripts/test__path_resolver.py +0 -106
  248. package/.agents/skills/_shared/pr-loop/scripts/test_audit_category_schema.py +0 -94
  249. package/.agents/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +0 -499
  250. package/.agents/skills/_shared/pr-loop/scripts/test_build_converge_task_list.py +0 -140
  251. package/.agents/skills/_shared/pr-loop/scripts/test_build_fix_prompt.py +0 -262
  252. package/.agents/skills/_shared/pr-loop/scripts/test_init_loop_state.py +0 -71
  253. package/.agents/skills/_shared/pr-loop/scripts/test_portable_converge_driver.py +0 -1119
  254. package/.agents/skills/_shared/pr-loop/scripts/test_preflight_worktree.py +0 -287
  255. package/.agents/skills/_shared/pr-loop/scripts/test_select_converge_pacer.py +0 -207
  256. package/.agents/skills/_shared/pr-loop/scripts/test_teardown_worktrees.py +0 -59
  257. package/.agents/skills/_shared/pr-loop/scripts/test_write_handoff.py +0 -201
  258. package/.agents/skills/_shared/pr-loop/scripts/write_audit_outcomes.py +0 -182
  259. package/.agents/skills/_shared/pr-loop/scripts/write_fix_outcomes.py +0 -206
  260. package/.agents/skills/_shared/pr-loop/scripts/write_handoff.py +0 -309
  261. package/.agents/skills/_shared/pr-loop/state-schema.md +0 -5
  262. package/.agents/skills/_shared/pr-loop/worker-spawn.md +0 -5
  263. package/.agents/skills/autoconverge/.claude/CLAUDE.md +0 -1
  264. package/.agents/skills/autoconverge/AGENTS.md +0 -1
  265. package/.agents/skills/autoconverge/SKILL.md +0 -431
  266. package/.agents/skills/autoconverge/reference/.claude/CLAUDE.md +0 -1
  267. package/.agents/skills/autoconverge/reference/AGENTS.md +0 -1
  268. package/.agents/skills/autoconverge/reference/closing-report.md +0 -93
  269. package/.agents/skills/autoconverge/reference/convergence-gates.md +0 -31
  270. package/.agents/skills/autoconverge/reference/convergence.md +0 -219
  271. package/.agents/skills/autoconverge/reference/copilot-findings.md +0 -51
  272. package/.agents/skills/autoconverge/reference/gotchas.md +0 -66
  273. package/.agents/skills/autoconverge/reference/headless-safety.md +0 -44
  274. package/.agents/skills/autoconverge/reference/multi-pr.md +0 -128
  275. package/.agents/skills/autoconverge/reference/self-closing-loop.md +0 -84
  276. package/.agents/skills/autoconverge/reference/stop-conditions.md +0 -127
  277. package/.agents/skills/autoconverge/test_portable_pacer_gate.py +0 -54
  278. package/.agents/skills/autoconverge/workflow/.claude/CLAUDE.md +0 -1
  279. package/.agents/skills/autoconverge/workflow/AGENTS.md +0 -1
  280. package/.agents/skills/autoconverge/workflow/aggregate_runs.py +0 -371
  281. package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/.claude/CLAUDE.md +0 -1
  282. package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/AGENTS.md +0 -1
  283. package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/__init__.py +0 -0
  284. package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/render_report_constants.py +0 -250
  285. package/.agents/skills/autoconverge/workflow/converge.clean-audit.test.mjs +0 -665
  286. package/.agents/skills/autoconverge/workflow/converge.codex-gate.test.mjs +0 -472
  287. package/.agents/skills/autoconverge/workflow/converge.contract.test.mjs +0 -64
  288. package/.agents/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +0 -1009
  289. package/.agents/skills/autoconverge/workflow/converge.fix-progress.test.mjs +0 -107
  290. package/.agents/skills/autoconverge/workflow/converge.fix-recovery.test.mjs +0 -33
  291. package/.agents/skills/autoconverge/workflow/converge.merge-conflict.test.mjs +0 -15
  292. package/.agents/skills/autoconverge/workflow/converge.mjs +0 -3132
  293. package/.agents/skills/autoconverge/workflow/converge.p2-advance.test.mjs +0 -202
  294. package/.agents/skills/autoconverge/workflow/converge.path-aware.test.mjs +0 -47
  295. package/.agents/skills/autoconverge/workflow/converge.precatch.test.mjs +0 -152
  296. package/.agents/skills/autoconverge/workflow/converge.run-input.test.mjs +0 -116
  297. package/.agents/skills/autoconverge/workflow/converge_multi.mjs +0 -169
  298. package/.agents/skills/autoconverge/workflow/converge_multi.run-input.test.mjs +0 -173
  299. package/.agents/skills/autoconverge/workflow/convergence_summary.py +0 -110
  300. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a11d903476b803493.jsonl +0 -2
  301. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a26213978adeef6fb.jsonl +0 -2
  302. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a3def0d15ed9d9110.jsonl +0 -2
  303. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a41f41b1b708ee3b7.jsonl +0 -2
  304. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a758b880abecc3ff7.jsonl +0 -2
  305. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a8897b89656b1bd16.jsonl +0 -2
  306. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-ab1c2d3e4f5a6b7c8.jsonl +0 -2
  307. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-abd463d744a1437bc.jsonl +0 -2
  308. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-ad19d027ae8ee1816.jsonl +0 -2
  309. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/workflows/wf_881252e6-700.json +0 -266
  310. package/.agents/skills/autoconverge/workflow/render_report.py +0 -1024
  311. package/.agents/skills/autoconverge/workflow/test_aggregate_runs.py +0 -134
  312. package/.agents/skills/autoconverge/workflow/test_convergence_summary.py +0 -149
  313. package/.agents/skills/autoconverge/workflow/test_render_report.py +0 -812
  314. package/.agents/skills/hitl/SKILL.md +0 -45
  315. package/.agents/skills/pr-cleanup/SKILL.md +0 -140
  316. package/.agents/skills/pr-cleanup/reference/process-inventory.md +0 -17
  317. package/.agents/skills/pr-cleanup/reference/task-seeds.md +0 -22
  318. package/.agents/skills/pr-name-by-capability/SKILL.md +0 -73
  319. package/.agents/skills/pr-name-by-capability/reference/fetch-commands.md +0 -29
  320. package/.agents/skills/pr-name-by-capability/reference/offense-examples.md +0 -32
  321. package/.agents/skills/pr-name-by-capability/reference/report-template.md +0 -29
  322. package/.agents/skills/pr-name-by-capability/reference/rule-checklist.md +0 -38
  323. package/.agents/skills/pr-name-by-capability/reference/task-seeds.md +0 -10
  324. package/.agents/skills/pr-plain-language-cleanup/SKILL.md +0 -93
  325. package/.agents/skills/pr-refinement/SKILL.md +0 -31
  326. package/.agents/skills/pr-shared-extraction/SKILL.md +0 -177
  327. package/.agents/skills/pr-shared-extraction/reference/examples.md +0 -46
  328. package/.agents/skills/pr-shared-extraction/reference/offense-taxonomy.md +0 -100
  329. package/.agents/skills/pr-small-cl/SKILL.md +0 -52
  330. package/.agents/skills/pr-title-description/SKILL.md +0 -96
  331. package/.agents/skills/prototype/SKILL.md +0 -85
  332. package/.agents/skills/prototype/reference/honest-limitations.md +0 -23
  333. package/.agents/skills/prototype/reference/promotion-tasks.md +0 -23
  334. package/.agents/skills/prototype/scripts/build_sandbox_settings.py +0 -249
  335. package/.agents/skills/prototype/scripts/conftest.py +0 -15
  336. package/.agents/skills/prototype/scripts/launch_sandbox.py +0 -205
  337. package/.agents/skills/prototype/scripts/probe_sandbox_safety.py +0 -311
  338. package/.agents/skills/prototype/scripts/prototype_scripts_constants/__init__.py +0 -1
  339. package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/__init__.py +0 -0
  340. package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/build_sandbox_settings_constants.py +0 -41
  341. package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/launch_sandbox_constants.py +0 -23
  342. package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/probe_sandbox_safety_constants.py +0 -45
  343. package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/prototype_common_constants.py +0 -10
  344. package/.agents/skills/prototype/scripts/test_build_sandbox_settings.py +0 -275
  345. package/.agents/skills/prototype/scripts/test_launch_sandbox.py +0 -303
  346. package/.agents/skills/prototype/scripts/test_probe_sandbox_safety.py +0 -312
  347. package/.agents/skills/prototype/workflows/promotion.md +0 -25
  348. package/.agents/skills/prototype/workflows/sandbox.md +0 -35
  349. package/.agents/skills/rebase/.claude/CLAUDE.md +0 -1
  350. package/.agents/skills/rebase/AGENTS.md +0 -1
  351. package/.agents/skills/rebase/SKILL.md +0 -178
  352. package/.agents/skills/review-router/SKILL.md +0 -51
  353. package/.agents/skills/review-router/reference/route-policy.json +0 -7
  354. package/.agents/skills/review-router/reference/task-seeds.md +0 -1
  355. package/.agents/skills/review-router/scripts/conftest.py +0 -4
  356. package/.agents/skills/review-router/scripts/review_router.py +0 -50
  357. package/.agents/skills/review-router/scripts/review_router_cli.py +0 -211
  358. package/.agents/skills/review-router/scripts/review_router_constants/__init__.py +0 -1
  359. package/.agents/skills/review-router/scripts/review_router_constants/config/__init__.py +0 -0
  360. package/.agents/skills/review-router/scripts/review_router_constants/config/constants.py +0 -13
  361. package/.agents/skills/review-router/scripts/test_review_router.py +0 -23
  362. package/.agents/skills/review-router/scripts/test_review_router_cli.py +0 -47
  363. package/.agents/skills/review-tier/SKILL.md +0 -56
  364. package/.agents/skills/review-tier/reference/task-seeds.md +0 -1
  365. package/.agents/skills/review-tier/reference/tier-policy.json +0 -9
  366. package/.agents/skills/review-tier/scripts/conftest.py +0 -4
  367. package/.agents/skills/review-tier/scripts/review_tier.py +0 -359
  368. package/.agents/skills/review-tier/scripts/review_tier_constants/__init__.py +0 -1
  369. package/.agents/skills/review-tier/scripts/review_tier_constants/config/__init__.py +0 -0
  370. package/.agents/skills/review-tier/scripts/review_tier_constants/config/constants.py +0 -29
  371. package/.agents/skills/review-tier/scripts/test_generation.py +0 -159
  372. package/.agents/skills/review-tier/scripts/test_review_tier.py +0 -33
  373. package/.agents/skills/run-claude-dev-env/SKILL.md +0 -84
  374. package/.agents/skills/session-log/.claude/CLAUDE.md +0 -1
  375. package/.agents/skills/session-log/AGENTS.md +0 -1
  376. package/.agents/skills/session-log/SKILL.md +0 -170
  377. package/.agents/skills/session-log/templates/frontmatter.md +0 -40
  378. package/.agents/skills/session-tidy/.claude/CLAUDE.md +0 -1
  379. package/.agents/skills/session-tidy/AGENTS.md +0 -1
  380. package/.agents/skills/session-tidy/SKILL.md +0 -191
  381. package/.agents/skills/skill-builder/references/.claude/CLAUDE.md +0 -1
  382. package/.agents/skills/skill-builder/references/AGENTS.md +0 -1
  383. package/.agents/skills/skill-builder/references/delegation-map.md +0 -130
  384. package/.agents/skills/skill-builder/references/description-field.md +0 -111
  385. package/.agents/skills/skill-builder/references/deterministic-elements.md +0 -218
  386. package/.agents/skills/skill-builder/references/progressive-disclosure.md +0 -122
  387. package/.agents/skills/skill-builder/references/self-audit-checklist.md +0 -126
  388. package/.agents/skills/skill-builder/references/skill-modularity.md +0 -111
  389. package/.agents/skills/skill-builder/references/skill-types.md +0 -232
  390. package/.agents/skills/skill-builder/references/thariq-x-post-skills.json +0 -33
  391. package/.agents/skills/skill-builder/templates/.claude/CLAUDE.md +0 -1
  392. package/.agents/skills/skill-builder/templates/AGENTS.md +0 -1
  393. package/.agents/skills/skill-builder/templates/gap-analysis.md +0 -87
  394. package/.agents/skills/skill-builder/workflows/.claude/CLAUDE.md +0 -1
  395. package/.agents/skills/skill-builder/workflows/AGENTS.md +0 -1
  396. package/.agents/skills/skill-builder/workflows/improve-skill.md +0 -136
  397. package/.agents/skills/skill-builder/workflows/new-skill.md +0 -179
  398. package/.agents/skills/skill-builder/workflows/polish-skill.md +0 -139
  399. package/.agents/skills/source-command-sr-loop/SKILL.md +0 -82
  400. package/.agents/skills/update/.claude/CLAUDE.md +0 -1
  401. package/.agents/skills/update/AGENTS.md +0 -1
  402. package/.agents/skills/update/SKILL.md +0 -190
  403. package/_shared/pr-loop/AGENTS.md +0 -1
  404. package/_shared/pr-loop/audit-contract.md +0 -183
  405. package/_shared/pr-loop/audit-reply-template.md +0 -151
  406. package/_shared/pr-loop/code-rules-gate.md +0 -65
  407. package/_shared/pr-loop/fix-protocol.md +0 -60
  408. package/_shared/pr-loop/gh-payloads.md +0 -75
  409. package/_shared/pr-loop/post-audit-thread-contract.md +0 -51
  410. package/_shared/pr-loop/precatch-rubric.md +0 -66
  411. package/_shared/pr-loop/preflight-proposal.md +0 -56
  412. package/_shared/pr-loop/scripts/post_audit_thread.py +0 -1242
  413. package/_shared/pr-loop/scripts/pr_loop_shared_constants/post_audit_thread_constants.py +0 -199
  414. package/_shared/pr-loop/scripts/tests/test_post_audit_thread.py +0 -1115
  415. package/_shared/pr-loop/scripts/tests/test_post_audit_thread_constants.py +0 -129
  416. package/_shared/pr-loop/state-schema.md +0 -45
  417. package/_shared/pr-loop/worker-spawn.md +0 -189
  418. package/hooks/blocking/ask_user_question_shape_blocker.py +0 -83
  419. package/hooks/blocking/bot_mention_comment_blocker.py +0 -76
  420. package/hooks/blocking/claude_md_orphan_file_blocker.py +0 -129
  421. package/hooks/blocking/cursor_cli_python_misfire_blocker.py +0 -108
  422. package/hooks/blocking/destructive_command_blocker.py +0 -2073
  423. package/hooks/blocking/fable_spawn_gate.py +0 -187
  424. package/hooks/blocking/luna_fast_mode_gate.py +0 -160
  425. package/hooks/blocking/nas_ssh_binary_enforcer.py +0 -163
  426. package/hooks/blocking/orchestrator_refresh_reschedule_gate.py +0 -260
  427. package/hooks/blocking/package_inventory_stale_blocker.py +0 -107
  428. package/hooks/blocking/pii_prevention_blocker.py +0 -365
  429. package/hooks/blocking/piped_pytest_blocker.py +0 -1223
  430. package/hooks/blocking/question_to_user_enforcer.py +0 -130
  431. package/hooks/blocking/send_user_file_open_locally_blocker.py +0 -71
  432. package/hooks/blocking/sensitive_file_protector.py +0 -167
  433. package/hooks/blocking/session_edit_stage_gate.py +0 -684
  434. package/hooks/blocking/session_handoff_blocker.py +0 -182
  435. package/hooks/blocking/shell_substitution_blocker.py +0 -350
  436. package/hooks/blocking/test_ask_user_question_shape.py +0 -69
  437. package/hooks/blocking/test_bot_mention_comment_blocker.py +0 -131
  438. package/hooks/blocking/test_claude_md_orphan_file_blocker.py +0 -1277
  439. package/hooks/blocking/test_cursor_cli_python_misfire_blocker.py +0 -208
  440. package/hooks/blocking/test_destructive_command_blocker.py +0 -2024
  441. package/hooks/blocking/test_destructive_command_blocker_deny_mode.py +0 -105
  442. package/hooks/blocking/test_destructive_command_blocker_no_verify.py +0 -122
  443. package/hooks/blocking/test_destructive_command_blocker_patterns.py +0 -133
  444. package/hooks/blocking/test_fable_spawn_gate.py +0 -381
  445. package/hooks/blocking/test_inventory_deadlock_resolution.py +0 -154
  446. package/hooks/blocking/test_luna_fast_mode_gate.py +0 -211
  447. package/hooks/blocking/test_nas_ssh_binary_enforcer.py +0 -225
  448. package/hooks/blocking/test_orchestrator_refresh_reschedule_gate.py +0 -231
  449. package/hooks/blocking/test_package_inventory_stale_blocker.py +0 -382
  450. package/hooks/blocking/test_pii_prevention_blocker.py +0 -1222
  451. package/hooks/blocking/test_pii_prevention_windows_git_path.py +0 -41
  452. package/hooks/blocking/test_pii_write_surface_ephemeral.py +0 -221
  453. package/hooks/blocking/test_piped_pytest_blocker.py +0 -587
  454. package/hooks/blocking/test_precommit_code_rules_gate_native_owner.py +0 -233
  455. package/hooks/blocking/test_question_to_user_enforcer.py +0 -169
  456. package/hooks/blocking/test_send_user_file_open_locally_blocker.py +0 -118
  457. package/hooks/blocking/test_sensitive_file_protector.py +0 -365
  458. package/hooks/blocking/test_session_edit_stage_gate.py +0 -537
  459. package/hooks/blocking/test_session_edit_stage_gate_staging.py +0 -64
  460. package/hooks/blocking/test_session_edit_stage_gate_support.py +0 -204
  461. package/hooks/blocking/test_session_edit_stage_gate_tracker.py +0 -116
  462. package/hooks/blocking/test_session_handoff_blocker.py +0 -318
  463. package/hooks/blocking/test_shell_substitution_blocker.py +0 -298
  464. package/hooks/blocking/test_unscoped_search_blocker.py +0 -102
  465. package/hooks/blocking/test_write_existing_file_blocker.py +0 -122
  466. package/hooks/blocking/tests/test_pii_prevention_blocker.py +0 -259
  467. package/hooks/blocking/unscoped_search_blocker.py +0 -391
  468. package/hooks/blocking/write_existing_file_blocker.py +0 -124
  469. package/hooks/hooks_constants/bot_mention_comment_blocker_constants.py +0 -19
  470. package/hooks/hooks_constants/cursor_cli_python_misfire_blocker_constants.py +0 -79
  471. package/hooks/hooks_constants/fable_spawn_gate_constants.py +0 -61
  472. package/hooks/hooks_constants/luna_fast_mode_gate_constants.py +0 -45
  473. package/hooks/hooks_constants/nas_ssh_binary_enforcer_constants.py +0 -20
  474. package/hooks/hooks_constants/orchestrator_refresh_reschedule_gate_constants.py +0 -57
  475. package/hooks/hooks_constants/send_user_file_open_locally_blocker_constants.py +0 -17
  476. package/hooks/hooks_constants/sensitive_file_protector_constants.py +0 -40
  477. package/hooks/hooks_constants/session_handoff_blocker_constants.py +0 -10
  478. package/hooks/hooks_constants/shell_substitution_blocker_constants.py +0 -80
  479. /package/.agents/skills/{_shared/pr-loop → session-continuity}/AGENTS.md +0 -0
  480. /package/{_shared/pr-loop → .agents/skills/session-continuity}/CLAUDE.md +0 -0
@@ -14,7 +14,7 @@ import {
14
14
  } from 'node:fs';
15
15
  import { tmpdir } from 'node:os';
16
16
  import { join, dirname } from 'node:path';
17
- import { pathToFileURL } from 'node:url';
17
+ import { fileURLToPath, pathToFileURL } from 'node:url';
18
18
 
19
19
  import {
20
20
  collectPackageSourceConflicts,
@@ -42,13 +42,17 @@ import {
42
42
  caseOnlyRenameSourceName,
43
43
  retiredManagedHookRelativePaths,
44
44
  pruneRetiredHookEntriesFromSettings,
45
+ settingsHookCommandsAtPath,
46
+ retiredHookPathsNamedByCommands,
47
+ writeRetiredHookStandIns,
48
+ ownedHookRootComparisonKeys,
49
+ commandNamesOwnedHookScript,
50
+ hookCommandComparisonSpelling,
51
+ detectPython,
45
52
  retainNewestRunBackupOnly,
53
+ refreshInstalledPstackPluginManifest,
46
54
  } from './install.mjs';
47
55
  import { EVER_SHIPPED_SKILL_NAMES } from './ever-shipped-skills.mjs';
48
- import {
49
- expandHomeDirectoryTokens,
50
- expandHomeDirectoryTokensInSettings,
51
- } from './expand_home_directory_tokens.mjs';
52
56
 
53
57
 
54
58
  test('installer reports the authoritative Git hook names', () => {
@@ -347,115 +351,6 @@ test('interpreterCommandFromPath quotes an interpreter path that contains a spac
347
351
  });
348
352
 
349
353
 
350
- test('expandHomeDirectoryTokens expands $HOME, ${HOME}, and ~/', () => {
351
- assert.equal(
352
- expandHomeDirectoryTokens(
353
- 'python $HOME/.claude/hooks/session/fix_worktree_hookspath.py',
354
- 'C:\\Users\\x',
355
- ),
356
- 'python C:/Users/x/.claude/hooks/session/fix_worktree_hookspath.py',
357
- );
358
- assert.equal(
359
- expandHomeDirectoryTokens('python ${HOME}/.claude/hooks/a.py', '/home/x'),
360
- 'python /home/x/.claude/hooks/a.py',
361
- );
362
- assert.equal(
363
- expandHomeDirectoryTokens('python ~/.claude/hooks/a.py', '/home/x'),
364
- 'python /home/x/.claude/hooks/a.py',
365
- );
366
- assert.equal(
367
- expandHomeDirectoryTokens('echo $HOMEPATH', 'C:/Users/x'),
368
- 'echo $HOMEPATH',
369
- );
370
- });
371
-
372
-
373
- test('expandHomeDirectoryTokens inserts dollar characters in home paths literally', () => {
374
- const homeWithReplaceMetacharacters = 'C:/Users/$&evil$1';
375
- assert.equal(
376
- expandHomeDirectoryTokens('python $HOME/.claude/a.py', homeWithReplaceMetacharacters),
377
- 'python C:/Users/$&evil$1/.claude/a.py',
378
- );
379
- assert.equal(
380
- expandHomeDirectoryTokens('python ${HOME}/.claude/a.py', homeWithReplaceMetacharacters),
381
- 'python C:/Users/$&evil$1/.claude/a.py',
382
- );
383
- assert.equal(
384
- expandHomeDirectoryTokens('python ~/.claude/a.py', homeWithReplaceMetacharacters),
385
- 'python C:/Users/$&evil$1/.claude/a.py',
386
- );
387
- });
388
-
389
-
390
- test('expandHomeDirectoryTokens expands ${HOME} before $HOME so braces stay intact', () => {
391
- assert.equal(
392
- expandHomeDirectoryTokens('python ${HOME}/.claude/a.py', '/home/x'),
393
- 'python /home/x/.claude/a.py',
394
- );
395
- });
396
-
397
-
398
- test('expandHomeDirectoryTokens strips trailing slashes from the home directory', () => {
399
- assert.equal(
400
- expandHomeDirectoryTokens('python $HOME/.claude/a.py', 'C:/Users/x/'),
401
- 'python C:/Users/x/.claude/a.py',
402
- );
403
- });
404
-
405
-
406
- test('expandHomeDirectoryTokensInSettings skips non-array hook event values', () => {
407
- const settings = {
408
- hooks: {
409
- SessionStart: 'not-an-array',
410
- PreToolUse: [
411
- {
412
- matcher: 'Write',
413
- hooks: [{ type: 'command', command: 'python $HOME/.claude/hooks/a.py' }],
414
- },
415
- ],
416
- },
417
- };
418
- expandHomeDirectoryTokensInSettings(settings, 'C:/Users/x');
419
- assert.equal(settings.hooks.SessionStart, 'not-an-array');
420
- assert.equal(
421
- settings.hooks.PreToolUse[0].hooks[0].command,
422
- 'python C:/Users/x/.claude/hooks/a.py',
423
- );
424
- });
425
-
426
-
427
- test('expandHomeDirectoryTokensInSettings rewrites hooks and statusLine', () => {
428
- const settings = {
429
- hooks: {
430
- SessionStart: [
431
- {
432
- matcher: '',
433
- hooks: [
434
- {
435
- type: 'command',
436
- command: 'python $HOME/.claude/hooks/session/fix_worktree_hookspath.py',
437
- },
438
- ],
439
- },
440
- ],
441
- },
442
- statusLine: {
443
- type: 'command',
444
- command: 'python "$HOME/.claude/statusline-command.py"',
445
- },
446
- };
447
- expandHomeDirectoryTokensInSettings(settings, 'C:/Users/x');
448
- assert.equal(
449
- settings.hooks.SessionStart[0].hooks[0].command,
450
- 'python C:/Users/x/.claude/hooks/session/fix_worktree_hookspath.py',
451
- );
452
- assert.equal(
453
- settings.statusLine.command,
454
- 'python "C:/Users/x/.claude/statusline-command.py"',
455
- );
456
- });
457
-
458
-
459
354
  test('mergeHooksIntoSettings expands residual $HOME in preserved user hooks', () => {
460
355
  const hooksConfig = {
461
356
  hooks: {
@@ -919,6 +814,19 @@ test('commandReferencesManagedHook matches a managed script followed by a whites
919
814
  });
920
815
 
921
816
 
817
+ test('commandReferencesManagedHook matches a shipped script name under a hooks root this installer never writes', () => {
818
+ const managedPaths = new Set(['blocking/code_rules_enforcer.py']);
819
+
820
+ assert.ok(
821
+ commandReferencesManagedHook(
822
+ 'python3 "/other-tool/.claude/hooks/blocking/code_rules_enforcer.py"',
823
+ managedPaths,
824
+ ),
825
+ 'the merge matcher reads the /.claude/hooks/ tail alone, so another tool\'s copy of a shipped script name is inside the managed set',
826
+ );
827
+ });
828
+
829
+
922
830
  test('commandReferencesManagedHook matches the rewritten inline validators-runner hook that carries no script tail', () => {
923
831
  const managedPaths = new Set(['blocking/code_rules_enforcer.py']);
924
832
  const rewrittenInlineCommand =
@@ -1265,6 +1173,35 @@ test('retired hook registrations stay managed so reinstall removes them', () =>
1265
1173
  'session/untracked_repo_detector.py',
1266
1174
  'session/gh_pr_author_session_cleanup.py',
1267
1175
  'observability/pr_description_writer_spawn_tracker.py',
1176
+ 'blocking/plain_language_blocker.py',
1177
+ 'lifecycle/config_change_guard.py',
1178
+ 'blocking/docstring_rule_gate_count_blocker.py',
1179
+ 'blocking/write_existing_file_blocker.py',
1180
+ 'blocking/sensitive_file_protector.py',
1181
+ 'blocking/pii_prevention_blocker.py',
1182
+ 'blocking/duplicate_rmtree_helper_blocker.py',
1183
+ 'blocking/claude_md_orphan_file_blocker.py',
1184
+ 'blocking/package_inventory_stale_blocker.py',
1185
+ 'blocking/env_var_table_code_drift_blocker.py',
1186
+ 'blocking/pytest_testpaths_orphan_blocker.py',
1187
+ 'blocking/destructive_command_blocker.py',
1188
+ 'blocking/shell_substitution_blocker.py',
1189
+ 'blocking/piped_pytest_blocker.py',
1190
+ 'blocking/cursor_cli_python_misfire_blocker.py',
1191
+ 'blocking/unscoped_search_blocker.py',
1192
+ 'blocking/nas_ssh_binary_enforcer.py',
1193
+ 'blocking/block_main_commit.py',
1194
+ 'blocking/session_edit_stage_gate.py',
1195
+ 'blocking/bash_pre_tool_use_dispatcher.py',
1196
+ 'blocking/stop_dispatcher.py',
1197
+ 'blocking/bot_mention_comment_blocker.py',
1198
+ 'blocking/fable_spawn_gate.py',
1199
+ 'blocking/luna_fast_mode_gate.py',
1200
+ 'blocking/orchestrator_refresh_reschedule_gate.py',
1201
+ 'blocking/ask_user_question_shape_blocker.py',
1202
+ 'blocking/send_user_file_open_locally_blocker.py',
1203
+ 'blocking/question_to_user_enforcer.py',
1204
+ 'blocking/session_handoff_blocker.py',
1268
1205
  ]);
1269
1206
  const shippedHooks = JSON.parse(
1270
1207
  readFileSync(new URL('../hooks/hooks.json', import.meta.url), 'utf8')
@@ -1416,7 +1353,7 @@ test('mergeHooksIntoSettings is idempotent when run twice against an already-upd
1416
1353
  });
1417
1354
 
1418
1355
 
1419
- test('shipped hooks.json matches the dispatcher design: dispatchers registered, run_all_validators retained, no folded hook standalone', () => {
1356
+ test('shipped hooks.json keeps only nonblocking policy handlers and lifecycle hooks', () => {
1420
1357
  const shippedHooksConfig = JSON.parse(
1421
1358
  readFileSync(new URL('../hooks/hooks.json', import.meta.url), 'utf8')
1422
1359
  );
@@ -1428,8 +1365,8 @@ test('shipped hooks.json matches the dispatcher design: dispatchers registered,
1428
1365
 
1429
1366
  assert.equal(
1430
1367
  countManagedRunAllValidatorsHooks(shippedHooksConfig),
1431
- 1,
1432
- 'shipped hooks.json must retain the inline run_all_validators runner on the write path',
1368
+ 0,
1369
+ 'shipped hooks.json must retire the inline run_all_validators runner from the write path',
1433
1370
  );
1434
1371
 
1435
1372
  const allPostToolUseGroups = shippedHooksConfig.hooks.PostToolUse || [];
@@ -1437,6 +1374,12 @@ test('shipped hooks.json matches the dispatcher design: dispatchers registered,
1437
1374
  .flatMap(group => group.hooks.map(hook => hook.command))
1438
1375
  .filter(cmd => cmd.includes('post_tool_use_dispatcher.py'));
1439
1376
  assert.equal(postDispatcherCommands.length, 1, 'shipped hooks.json must register the PostToolUse dispatcher exactly once');
1377
+ const allCommands = Object.values(shippedHooksConfig.hooks).flatMap(matcherGroups => (
1378
+ matcherGroups.flatMap(matcherGroup => matcherGroup.hooks.map(hook => hook.command))
1379
+ ));
1380
+ for (const retiredPath of RETIRED_HOOK_REGISTRATION_RELATIVE_PATHS) {
1381
+ assert.ok(allCommands.every(command => !command.includes(retiredPath)), retiredPath);
1382
+ }
1440
1383
 
1441
1384
  const allSessionStartCommands = (shippedHooksConfig.hooks.SessionStart || [])
1442
1385
  .flatMap(group => group.hooks.map(hook => hook.command));
@@ -2183,6 +2126,22 @@ function createSettingsFixture(settings, indentWidth) {
2183
2126
  }
2184
2127
 
2185
2128
 
2129
+ /**
2130
+ * Build the ownership one sandbox's own ~/.claude/hooks root supplies.
2131
+ *
2132
+ * A retirement walk that falls back to its own defaults reads the installed hook
2133
+ * roots and the home directory of the machine running the test, so the answer
2134
+ * changes with the host. Handing every walk a sandbox ownership keeps the test
2135
+ * reading the fixture it wrote and nothing else.
2136
+ *
2137
+ * @param {string} sandboxRoot The sandbox home the `$HOME` and `~` spellings expand to.
2138
+ * @returns {{allOwnedHookRootKeys: Set<string>, homeDirectory: string}} The ownership.
2139
+ */
2140
+ function sandboxClaudeHooksOwnership(sandboxRoot) {
2141
+ return sandboxHookOwnership(sandboxRoot, [join(sandboxRoot, '.claude', 'hooks')]);
2142
+ }
2143
+
2144
+
2186
2145
  test('pruneRetiredHookEntriesFromSettings removes the retired entry and keeps the user-authored one', () => {
2187
2146
  const retiredCommand = `python3 "$HOME/.claude/hooks/${RETIRED_HOOK_RELATIVE_PATH}"`;
2188
2147
  const userCommand = 'python3 my_own_gate.py --user-authored';
@@ -2205,7 +2164,9 @@ test('pruneRetiredHookEntriesFromSettings removes the retired entry and keeps th
2205
2164
  }, 4);
2206
2165
  try {
2207
2166
  const removedCount = pruneRetiredHookEntriesFromSettings(
2208
- settingsPath, new Set([RETIRED_HOOK_RELATIVE_PATH]),
2167
+ settingsPath,
2168
+ new Set([RETIRED_HOOK_RELATIVE_PATH]),
2169
+ sandboxClaudeHooksOwnership(sandboxRoot),
2209
2170
  );
2210
2171
 
2211
2172
  assert.equal(removedCount, 2, 'both entries running the retired script are removed');
@@ -2250,7 +2211,9 @@ test('pruneRetiredHookEntriesFromSettings keeps every settings shape the install
2250
2211
  }, 4);
2251
2212
  try {
2252
2213
  const removedCount = pruneRetiredHookEntriesFromSettings(
2253
- settingsPath, new Set([RETIRED_HOOK_RELATIVE_PATH]),
2214
+ settingsPath,
2215
+ new Set([RETIRED_HOOK_RELATIVE_PATH]),
2216
+ sandboxClaudeHooksOwnership(sandboxRoot),
2254
2217
  );
2255
2218
 
2256
2219
  assert.equal(removedCount, 1, 'the one entry running the retired script is the only removal');
@@ -2391,7 +2354,9 @@ test('pruneRetiredHookEntriesFromSettings leaves settings.json byte-identical wh
2391
2354
  const bytesBefore = readFileSync(settingsPath, 'utf8');
2392
2355
 
2393
2356
  const removedCount = pruneRetiredHookEntriesFromSettings(
2394
- settingsPath, new Set([RETIRED_HOOK_RELATIVE_PATH]),
2357
+ settingsPath,
2358
+ new Set([RETIRED_HOOK_RELATIVE_PATH]),
2359
+ sandboxClaudeHooksOwnership(sandboxRoot),
2395
2360
  );
2396
2361
 
2397
2362
  assert.equal(removedCount, 0, 'no entry runs a retired script');
@@ -2406,6 +2371,361 @@ test('pruneRetiredHookEntriesFromSettings leaves settings.json byte-identical wh
2406
2371
  });
2407
2372
 
2408
2373
 
2374
+ const ALL_HOST_CONFIGURATION_INDENT_WIDTHS = [2, 4];
2375
+
2376
+
2377
+ /**
2378
+ * Build a settings fixture whose one matcher group registers a retired hook
2379
+ * beside a user-authored entry, so a prune removes one entry and rewrites the file.
2380
+ *
2381
+ * @param {string} relativePath The retired hook path the registration runs.
2382
+ * @returns {object} A settings object holding both entries.
2383
+ */
2384
+ function settingsRegisteringRetiredAndUserHooks(relativePath) {
2385
+ return {
2386
+ hooks: {
2387
+ PreToolUse: [{
2388
+ matcher: 'Write|Edit',
2389
+ hooks: [
2390
+ { type: 'command', command: `python3 "$HOME/.claude/hooks/${relativePath}"` },
2391
+ { type: 'command', command: 'python3 my_own_gate.py --user-authored' },
2392
+ ],
2393
+ }],
2394
+ },
2395
+ };
2396
+ }
2397
+
2398
+
2399
+ test('pruneRetiredHookEntriesFromSettings writes back the indentation the host configuration file already used', () => {
2400
+ for (const indentWidth of ALL_HOST_CONFIGURATION_INDENT_WIDTHS) {
2401
+ const { settingsPath, sandboxRoot } = createSettingsFixture(
2402
+ settingsRegisteringRetiredAndUserHooks(RETIRED_HOOK_RELATIVE_PATH), indentWidth,
2403
+ );
2404
+ try {
2405
+ const removedCount = pruneRetiredHookEntriesFromSettings(
2406
+ settingsPath,
2407
+ new Set([RETIRED_HOOK_RELATIVE_PATH]),
2408
+ sandboxClaudeHooksOwnership(sandboxRoot),
2409
+ );
2410
+
2411
+ assert.equal(removedCount, 1, `the retired entry leaves the ${indentWidth}-space file`);
2412
+ const settingsText = readFileSync(settingsPath, 'utf8');
2413
+ assert.equal(
2414
+ settingsText,
2415
+ JSON.stringify(JSON.parse(settingsText), null, indentWidth) + '\n',
2416
+ `a file written with ${indentWidth} spaces is handed back with ${indentWidth} spaces`,
2417
+ );
2418
+ } finally {
2419
+ rmSync(sandboxRoot, { recursive: true, force: true });
2420
+ }
2421
+ }
2422
+ });
2423
+
2424
+
2425
+ const SECOND_RETIRED_HOOK_RELATIVE_PATH = 'blocking/retired_second_gate.py';
2426
+
2427
+
2428
+ /**
2429
+ * Build a sandbox holding a ~/.claude/hooks root and a settings.json beside it.
2430
+ *
2431
+ * @param {object} settings The settings object to serialize.
2432
+ * @returns {{sandboxRoot: string, hooksRoot: string, settingsPath: string}} The sandbox paths.
2433
+ */
2434
+ function createRetiredHookSandbox(settings) {
2435
+ const sandboxRoot = mkdtempSync(join(tmpdir(), 'cdev-retired-hook-stand-in-'));
2436
+ const hooksRoot = join(sandboxRoot, '.claude', 'hooks');
2437
+ mkdirSync(hooksRoot, { recursive: true });
2438
+ const settingsPath = join(sandboxRoot, 'settings.json');
2439
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 4) + '\n');
2440
+ return { sandboxRoot, hooksRoot, settingsPath };
2441
+ }
2442
+
2443
+
2444
+ /**
2445
+ * Build a settings fixture registering one retired hook command.
2446
+ *
2447
+ * @param {string} relativePath The retired hook path the registration runs.
2448
+ * @returns {object} A settings object holding that registration and a user entry.
2449
+ */
2450
+ function settingsRegistering(relativePath) {
2451
+ return {
2452
+ hooks: {
2453
+ PreToolUse: [{
2454
+ matcher: 'AskUserQuestion',
2455
+ hooks: [
2456
+ { type: 'command', command: `python3 "$HOME/.claude/hooks/${relativePath}"` },
2457
+ { type: 'command', command: 'python3 my_own_gate.py --user-authored' },
2458
+ ],
2459
+ }],
2460
+ },
2461
+ };
2462
+ }
2463
+
2464
+
2465
+ test('retiredHookPathsNamedByCommands names only the retired scripts a live registration runs', () => {
2466
+ const { sandboxRoot, settingsPath } = createRetiredHookSandbox(
2467
+ settingsRegistering(RETIRED_HOOK_RELATIVE_PATH),
2468
+ );
2469
+ try {
2470
+ const namedPaths = retiredHookPathsNamedByCommands(
2471
+ settingsHookCommandsAtPath(settingsPath),
2472
+ new Set([RETIRED_HOOK_RELATIVE_PATH, SECOND_RETIRED_HOOK_RELATIVE_PATH]),
2473
+ sandboxClaudeHooksOwnership(sandboxRoot),
2474
+ );
2475
+
2476
+ assert.deepEqual(
2477
+ [...namedPaths],
2478
+ [RETIRED_HOOK_RELATIVE_PATH],
2479
+ 'the registered retired script is named and the unregistered one is left out',
2480
+ );
2481
+ } finally {
2482
+ rmSync(sandboxRoot, { recursive: true, force: true });
2483
+ }
2484
+ });
2485
+
2486
+
2487
+ test('settingsHookCommandsAtPath reads no command from a settings file it cannot use', () => {
2488
+ const { sandboxRoot, settingsPath } = createRetiredHookSandbox({ hooks: {} });
2489
+ try {
2490
+ writeFileSync(settingsPath, '{ this is not JSON');
2491
+
2492
+ assert.deepEqual(
2493
+ settingsHookCommandsAtPath(settingsPath),
2494
+ [],
2495
+ 'settings the installer cannot parse hold no command',
2496
+ );
2497
+ assert.deepEqual(
2498
+ settingsHookCommandsAtPath(join(sandboxRoot, 'absent.json')),
2499
+ [],
2500
+ 'a settings file that is not there holds no command',
2501
+ );
2502
+ assert.equal(
2503
+ retiredHookPathsNamedByCommands(
2504
+ [],
2505
+ new Set([RETIRED_HOOK_RELATIVE_PATH]),
2506
+ sandboxClaudeHooksOwnership(sandboxRoot),
2507
+ ).size,
2508
+ 0,
2509
+ 'with no command to read, no retired script is named',
2510
+ );
2511
+ } finally {
2512
+ rmSync(sandboxRoot, { recursive: true, force: true });
2513
+ }
2514
+ });
2515
+
2516
+
2517
+ test('a stale registration keeps working because the retired hook leaves an inert stand-in behind', () => {
2518
+ const { sandboxRoot, hooksRoot, settingsPath } = createRetiredHookSandbox(
2519
+ settingsRegistering(RETIRED_HOOK_RELATIVE_PATH),
2520
+ );
2521
+ try {
2522
+ const namedPaths = retiredHookPathsNamedByCommands(
2523
+ settingsHookCommandsAtPath(settingsPath),
2524
+ new Set([RETIRED_HOOK_RELATIVE_PATH, SECOND_RETIRED_HOOK_RELATIVE_PATH]),
2525
+ sandboxClaudeHooksOwnership(sandboxRoot),
2526
+ );
2527
+
2528
+ const standInPaths = writeRetiredHookStandIns(hooksRoot, namedPaths);
2529
+
2530
+ const standInPath = join(hooksRoot, ...RETIRED_HOOK_RELATIVE_PATH.split('/'));
2531
+ assert.deepEqual(standInPaths, [standInPath], 'one stand-in is written, for the registered path');
2532
+ assert.equal(
2533
+ existsSync(join(hooksRoot, ...SECOND_RETIRED_HOOK_RELATIVE_PATH.split('/'))),
2534
+ false,
2535
+ 'a retired path no registration names gets no stand-in',
2536
+ );
2537
+
2538
+ const standInRun = spawnSync(detectPython(), [standInPath], { encoding: 'utf8' });
2539
+
2540
+ assert.equal(standInRun.status, 0, 'the stand-in exits 0, so a stale registration allows the tool');
2541
+ assert.equal(standInRun.stdout, '', 'the stand-in writes nothing to stdout');
2542
+ assert.equal(standInRun.stderr, '', 'the stand-in writes nothing to stderr');
2543
+ } finally {
2544
+ rmSync(sandboxRoot, { recursive: true, force: true });
2545
+ }
2546
+ });
2547
+
2548
+
2549
+ test('writeRetiredHookStandIns leaves a hook file the prune could not move exactly as it stands', () => {
2550
+ const { sandboxRoot, hooksRoot } = createRetiredHookSandbox({ hooks: {} });
2551
+ try {
2552
+ const survivingPath = join(hooksRoot, ...RETIRED_HOOK_RELATIVE_PATH.split('/'));
2553
+ mkdirSync(dirname(survivingPath), { recursive: true });
2554
+ const survivingContents = 'raise SystemExit(2)\n';
2555
+ writeFileSync(survivingPath, survivingContents);
2556
+
2557
+ const standInPaths = writeRetiredHookStandIns(
2558
+ hooksRoot, new Set([RETIRED_HOOK_RELATIVE_PATH]),
2559
+ );
2560
+
2561
+ assert.deepEqual(standInPaths, [], 'a path still holding a file gets no stand-in');
2562
+ assert.equal(
2563
+ readFileSync(survivingPath, 'utf8'),
2564
+ survivingContents,
2565
+ 'the file a failed prune left behind keeps its own bytes',
2566
+ );
2567
+ } finally {
2568
+ rmSync(sandboxRoot, { recursive: true, force: true });
2569
+ }
2570
+ });
2571
+
2572
+
2573
+ const AGENTS_HOOKS_ROOT_SEGMENTS = ['.agents', 'hooks'];
2574
+ const CODEX_HOOKS_ROOT_SEGMENTS = ['.codex', 'hooks'];
2575
+ const FOREIGN_HOOKS_ROOT_SEGMENTS = ['other-tool', '.claude', 'hooks'];
2576
+
2577
+
2578
+ /**
2579
+ * Build the ownership one retirement walk reads for one sandbox.
2580
+ *
2581
+ * @param {string} sandboxRoot The sandbox home the `$HOME` spelling expands to.
2582
+ * @param {string[]} allHookRootPaths The hooks directories the installer owns there.
2583
+ * @returns {{allOwnedHookRootKeys: Set<string>, homeDirectory: string}} The ownership.
2584
+ */
2585
+ function sandboxHookOwnership(sandboxRoot, allHookRootPaths) {
2586
+ return {
2587
+ allOwnedHookRootKeys: ownedHookRootComparisonKeys(allHookRootPaths),
2588
+ homeDirectory: sandboxRoot,
2589
+ };
2590
+ }
2591
+
2592
+
2593
+ /**
2594
+ * Build the command one hook registration runs for a script under a hooks root.
2595
+ *
2596
+ * @param {string} hooksRoot The hooks directory the registration names.
2597
+ * @param {string} relativePath The script path under that directory.
2598
+ * @returns {string} The registration command.
2599
+ */
2600
+ function hookCommandUnder(hooksRoot, relativePath) {
2601
+ const scriptPath = join(hooksRoot, ...relativePath.split('/')).replace(/\\/g, '/');
2602
+ return `python3 "${scriptPath}"`;
2603
+ }
2604
+
2605
+
2606
+ test('a retired registration spelled under the agents hooks root is named, because both roots are owned', () => {
2607
+ const sandboxRoot = mkdtempSync(join(tmpdir(), 'cdev-owned-hook-roots-'));
2608
+ try {
2609
+ const claudeHooksRoot = join(sandboxRoot, '.claude', 'hooks');
2610
+ const agentsHooksRoot = join(sandboxRoot, ...AGENTS_HOOKS_ROOT_SEGMENTS);
2611
+ const codexHooksRoot = join(sandboxRoot, ...CODEX_HOOKS_ROOT_SEGMENTS);
2612
+
2613
+ const namedPaths = retiredHookPathsNamedByCommands(
2614
+ [
2615
+ hookCommandUnder(agentsHooksRoot, RETIRED_HOOK_RELATIVE_PATH),
2616
+ hookCommandUnder(codexHooksRoot, SECOND_RETIRED_HOOK_RELATIVE_PATH),
2617
+ ],
2618
+ new Set([RETIRED_HOOK_RELATIVE_PATH, SECOND_RETIRED_HOOK_RELATIVE_PATH]),
2619
+ sandboxHookOwnership(
2620
+ sandboxRoot, [claudeHooksRoot, agentsHooksRoot, codexHooksRoot],
2621
+ ),
2622
+ );
2623
+
2624
+ assert.deepEqual(
2625
+ [...namedPaths].sort(),
2626
+ [RETIRED_HOOK_RELATIVE_PATH, SECOND_RETIRED_HOOK_RELATIVE_PATH].sort(),
2627
+ 'a registration under any owned hooks root names its retired script',
2628
+ );
2629
+ } finally {
2630
+ rmSync(sandboxRoot, { recursive: true, force: true });
2631
+ }
2632
+ });
2633
+
2634
+
2635
+ test('a retired registration reaching an owned root through a junction is named', () => {
2636
+ const sandboxRoot = mkdtempSync(join(tmpdir(), 'cdev-owned-hook-junction-'));
2637
+ try {
2638
+ const agentsHooksRoot = join(sandboxRoot, ...AGENTS_HOOKS_ROOT_SEGMENTS);
2639
+ const claudeHooksRoot = join(sandboxRoot, '.claude', 'hooks');
2640
+ mkdirSync(agentsHooksRoot, { recursive: true });
2641
+ mkdirSync(dirname(claudeHooksRoot), { recursive: true });
2642
+ symlinkSync(agentsHooksRoot, claudeHooksRoot, 'junction');
2643
+
2644
+ const namedPaths = retiredHookPathsNamedByCommands(
2645
+ [hookCommandUnder(agentsHooksRoot, RETIRED_HOOK_RELATIVE_PATH)],
2646
+ new Set([RETIRED_HOOK_RELATIVE_PATH]),
2647
+ sandboxHookOwnership(sandboxRoot, [claudeHooksRoot]),
2648
+ );
2649
+
2650
+ assert.deepEqual(
2651
+ [...namedPaths],
2652
+ [RETIRED_HOOK_RELATIVE_PATH],
2653
+ 'the junction and the directory it reaches are one owned hooks root',
2654
+ );
2655
+ } finally {
2656
+ rmSync(sandboxRoot, { recursive: true, force: true });
2657
+ }
2658
+ });
2659
+
2660
+
2661
+ test('a registration under a hooks root the installer does not own is never named or pruned', () => {
2662
+ const sandboxRoot = mkdtempSync(join(tmpdir(), 'cdev-foreign-hook-root-'));
2663
+ try {
2664
+ const claudeHooksRoot = join(sandboxRoot, '.claude', 'hooks');
2665
+ const foreignHooksRoot = join(sandboxRoot, ...FOREIGN_HOOKS_ROOT_SEGMENTS);
2666
+ const foreignCommand = hookCommandUnder(foreignHooksRoot, RETIRED_HOOK_RELATIVE_PATH);
2667
+ const settingsPath = join(sandboxRoot, 'settings.json');
2668
+ writeFileSync(settingsPath, JSON.stringify({
2669
+ hooks: {
2670
+ PreToolUse: [{
2671
+ matcher: '*',
2672
+ hooks: [{ type: 'command', command: foreignCommand }],
2673
+ }],
2674
+ },
2675
+ }, null, 4) + '\n');
2676
+ const ownership = sandboxHookOwnership(sandboxRoot, [claudeHooksRoot]);
2677
+
2678
+ const namedPaths = retiredHookPathsNamedByCommands(
2679
+ [foreignCommand], new Set([RETIRED_HOOK_RELATIVE_PATH]), ownership,
2680
+ );
2681
+ const removedCount = pruneRetiredHookEntriesFromSettings(
2682
+ settingsPath, new Set([RETIRED_HOOK_RELATIVE_PATH]), ownership,
2683
+ );
2684
+
2685
+ assert.equal(namedPaths.size, 0, 'a foreign registration earns no stand-in');
2686
+ assert.equal(removedCount, 0, 'a foreign registration is never pruned');
2687
+ assert.equal(
2688
+ JSON.parse(readFileSync(settingsPath, 'utf8')).hooks.PreToolUse[0].hooks[0].command,
2689
+ foreignCommand,
2690
+ 'the foreign entry stays exactly as the other tool wrote it',
2691
+ );
2692
+ } finally {
2693
+ rmSync(sandboxRoot, { recursive: true, force: true });
2694
+ }
2695
+ });
2696
+
2697
+
2698
+ const SANDBOX_HOME_DIRECTORY = process.platform === 'win32'
2699
+ ? 'c:/cdev-sandbox-home'
2700
+ : '/cdev-sandbox-home';
2701
+
2702
+
2703
+ test('a hook command spelled with a leading tilde expands to the home directory the tilde stands for', () => {
2704
+ const tildeCommand = `python3 ~/.claude/hooks/${RETIRED_HOOK_RELATIVE_PATH}`;
2705
+
2706
+ const comparisonSpelling = hookCommandComparisonSpelling(
2707
+ tildeCommand, SANDBOX_HOME_DIRECTORY,
2708
+ );
2709
+
2710
+ assert.equal(
2711
+ comparisonSpelling,
2712
+ `python3 ${SANDBOX_HOME_DIRECTORY}/.claude/hooks/${RETIRED_HOOK_RELATIVE_PATH}`,
2713
+ 'the home directory replaces the tilde rather than following it',
2714
+ );
2715
+ assert.ok(
2716
+ commandNamesOwnedHookScript(
2717
+ tildeCommand,
2718
+ RETIRED_HOOK_RELATIVE_PATH,
2719
+ sandboxHookOwnership(
2720
+ SANDBOX_HOME_DIRECTORY,
2721
+ [join(SANDBOX_HOME_DIRECTORY, '.claude', 'hooks')],
2722
+ ),
2723
+ ),
2724
+ 'the expanded command reaches the owned hooks root',
2725
+ );
2726
+ });
2727
+
2728
+
2409
2729
  const PRIOR_RUN_BACKUP_NAMES = ['2020-01-01T00-00-00-000Z', '2021-06-15T12-30-45-123Z'];
2410
2730
  const THIS_RUN_BACKUP_NAME = '2026-07-25T18-04-11-923Z';
2411
2731
  const MOVED_SKILL_BACKUP_SEGMENTS = ['skills', 'demo'];
@@ -2541,3 +2861,100 @@ test('pruneStaleInstalledFiles leaves the run backup root standing when the rena
2541
2861
  rmSync(sandbox.root, { recursive: true, force: true });
2542
2862
  }
2543
2863
  });
2864
+
2865
+ test('the pstack step writes a manifest for a skills root that carries pstack', () => {
2866
+ const skillsRoot = mkdtempSync(join(tmpdir(), 'cdev-pstack-step-'));
2867
+ try {
2868
+ const skillDirectory = join(skillsRoot, 'pstack', 'how');
2869
+ mkdirSync(skillDirectory, { recursive: true });
2870
+ writeFileSync(join(skillDirectory, 'SKILL.md'), '---\nname: how\n---\n');
2871
+
2872
+ const manifestPath = refreshInstalledPstackPluginManifest(skillsRoot);
2873
+
2874
+ assert.equal(
2875
+ manifestPath,
2876
+ join(skillsRoot, 'pstack', '.claude-plugin', 'plugin.json'),
2877
+ 'the step reports the manifest it wrote so the run records the path',
2878
+ );
2879
+ const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
2880
+ assert.deepEqual(manifest.skills, ['./how']);
2881
+ assert.equal(
2882
+ refreshInstalledPstackPluginManifest(skillsRoot),
2883
+ null,
2884
+ 'a second run writes nothing and reports no path',
2885
+ );
2886
+ } finally {
2887
+ rmSync(skillsRoot, { recursive: true, force: true });
2888
+ }
2889
+ });
2890
+
2891
+ test('the pstack step reports no path for a skills root without pstack', () => {
2892
+ const skillsRoot = mkdtempSync(join(tmpdir(), 'cdev-pstack-absent-'));
2893
+ try {
2894
+ assert.equal(refreshInstalledPstackPluginManifest(skillsRoot), null);
2895
+ assert.equal(existsSync(join(skillsRoot, 'pstack')), false);
2896
+ } finally {
2897
+ rmSync(skillsRoot, { recursive: true, force: true });
2898
+ }
2899
+ });
2900
+
2901
+ const RETIRED_SKILLS = ['pr-small-cl', 'session-log', 'session-tidy'];
2902
+
2903
+ test('each core skill has a shipped entry point', () => {
2904
+ for (const skillName of CORE_SKILLS) {
2905
+ assert.ok(existsSync(new URL(`../.agents/skills/${skillName}/SKILL.md`, import.meta.url)), skillName);
2906
+ }
2907
+ });
2908
+
2909
+ test('retired skills leave install groups and stay in the cleanup registry', () => {
2910
+ const selectedSkills = Object.values(INSTALL_GROUPS).flatMap(group => group.skills ?? []);
2911
+ assert.equal(Object.hasOwn(INSTALL_GROUPS, 'journal'), false);
2912
+ for (const skillName of RETIRED_SKILLS) {
2913
+ assert.equal(selectedSkills.includes(skillName), false, skillName);
2914
+ assert.ok(EVER_SHIPPED_SKILL_NAMES.has(skillName), skillName);
2915
+ }
2916
+ });
2917
+
2918
+ test('README and vault guidance use current session tools', () => {
2919
+ const readme = readFileSync(new URL('../../../README.md', import.meta.url), 'utf8');
2920
+ const vaultRule = readFileSync(new URL('../rules/vault-context.md', import.meta.url), 'utf8');
2921
+ assert.doesNotMatch(readme, /--only journal|session-log|session-tidy/);
2922
+ assert.doesNotMatch(vaultRule, /`session-log`|`recall`/);
2923
+ assert.match(vaultRule, /obsidian MCP tools/);
2924
+ assert.match(vaultRule, /project.*frontmatter/);
2925
+ });
2926
+
2927
+ test('the help output states that the installer reads only flags', () => {
2928
+ const helpRun = spawnSync(
2929
+ process.execPath,
2930
+ [fileURLToPath(new URL('./install.mjs', import.meta.url)), '--help'],
2931
+ { encoding: 'utf8', env: process.env },
2932
+ );
2933
+
2934
+ assert.equal(helpRun.status, 0, helpRun.stderr);
2935
+ assert.match(helpRun.stdout, /reads only flags/i);
2936
+ assert.match(helpRun.stdout, /bare path argument carries no meaning/i);
2937
+ });
2938
+
2939
+
2940
+
2941
+ test('pruneRetiredHookEntriesFromSettings warns and returns 0 when the host configuration file cannot be read', () => {
2942
+ const sandboxRoot = mkdtempSync(join(tmpdir(), 'cdev-unreadable-host-configuration-'));
2943
+ const unreadableConfigurationPath = join(sandboxRoot, 'hooks.json');
2944
+ mkdirSync(unreadableConfigurationPath);
2945
+ try {
2946
+ const { returnedValue, allWarnings } = captureWarnings(() => (
2947
+ pruneRetiredHookEntriesFromSettings(
2948
+ unreadableConfigurationPath,
2949
+ new Set([RETIRED_HOOK_RELATIVE_PATH]),
2950
+ sandboxClaudeHooksOwnership(sandboxRoot),
2951
+ )
2952
+ ));
2953
+
2954
+ assert.equal(returnedValue, 0, 'an unreadable file removes no entry and ends the walk of that file');
2955
+ assert.equal(allWarnings.length, 1, 'the unreadable file is reported once');
2956
+ assert.match(allWarnings[0], /hooks\.json/, 'the warning names the file it left alone');
2957
+ } finally {
2958
+ rmSync(sandboxRoot, { recursive: true, force: true });
2959
+ }
2960
+ });