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
@@ -0,0 +1,130 @@
1
+ import { test } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { readFileSync } from 'node:fs';
4
+ import {
5
+ FOLDED_HOOK_RELATIVE_PATHS,
6
+ RETIRED_HOOK_REGISTRATION_RELATIVE_PATHS,
7
+ mergeHooksIntoSettings,
8
+ } from './install.mjs';
9
+
10
+ const allRetiredWriteHooks = [
11
+ 'blocking/code_rules_enforcer.py',
12
+ 'blocking/tdd_enforcer.py',
13
+ 'blocking/windows_rmtree_blocker.py',
14
+ 'blocking/state_description_blocker.py',
15
+ 'blocking/subprocess_budget_completeness.py',
16
+ 'blocking/hook_prose_detector_consistency.py',
17
+ 'blocking/workflow_substitution_slot_blocker.py',
18
+ ];
19
+ const allRetiredPolicyRegistrations = [
20
+ 'blocking/plain_language_blocker.py',
21
+ 'lifecycle/config_change_guard.py',
22
+ 'blocking/docstring_rule_gate_count_blocker.py',
23
+ 'blocking/write_existing_file_blocker.py',
24
+ 'blocking/sensitive_file_protector.py',
25
+ 'blocking/pii_prevention_blocker.py',
26
+ 'blocking/duplicate_rmtree_helper_blocker.py',
27
+ 'blocking/claude_md_orphan_file_blocker.py',
28
+ 'blocking/package_inventory_stale_blocker.py',
29
+ 'blocking/env_var_table_code_drift_blocker.py',
30
+ 'blocking/pytest_testpaths_orphan_blocker.py',
31
+ 'blocking/destructive_command_blocker.py',
32
+ 'blocking/shell_substitution_blocker.py',
33
+ 'blocking/piped_pytest_blocker.py',
34
+ 'blocking/cursor_cli_python_misfire_blocker.py',
35
+ 'blocking/unscoped_search_blocker.py',
36
+ 'blocking/nas_ssh_binary_enforcer.py',
37
+ 'blocking/block_main_commit.py',
38
+ 'blocking/session_edit_stage_gate.py',
39
+ 'blocking/bash_pre_tool_use_dispatcher.py',
40
+ 'blocking/stop_dispatcher.py',
41
+ 'blocking/bot_mention_comment_blocker.py',
42
+ 'blocking/fable_spawn_gate.py',
43
+ 'blocking/luna_fast_mode_gate.py',
44
+ 'blocking/orchestrator_refresh_reschedule_gate.py',
45
+ 'blocking/ask_user_question_shape_blocker.py',
46
+ 'blocking/send_user_file_open_locally_blocker.py',
47
+ 'blocking/question_to_user_enforcer.py',
48
+ 'blocking/session_handoff_blocker.py',
49
+ ];
50
+ const sourceHooks = JSON.parse(readFileSync(new URL('../hooks/hooks.json', import.meta.url), 'utf8'));
51
+ const installedRoot = '/fixture-home/.claude';
52
+ const foreignCommand = 'python /unmanaged/keep_this_hook.py';
53
+ const foreignSessionCommand = 'node /unmanaged/session.js';
54
+
55
+ function legacySettings() {
56
+ return {
57
+ permissions: {
58
+ allow: ['Read'],
59
+ deny: ['Bash(git push --force)'],
60
+ ask: ['Bash(git push)'],
61
+ },
62
+ hooks: {
63
+ PreToolUse: [{
64
+ matcher: 'Write|Edit|MultiEdit|apply_patch',
65
+ hooks: [
66
+ { type: 'command', command: foreignCommand, timeout: 17 },
67
+ ...allRetiredWriteHooks.map(relativePath => ({
68
+ type: 'command',
69
+ command: `python ${installedRoot}/hooks/${relativePath}`,
70
+ timeout: 10,
71
+ })),
72
+ ...allRetiredPolicyRegistrations.map(relativePath => ({
73
+ type: 'command',
74
+ command: `python ${installedRoot}/hooks/${relativePath}`,
75
+ timeout: 10,
76
+ })),
77
+ ],
78
+ }],
79
+ SessionStart: [{
80
+ matcher: '',
81
+ hooks: [{ type: 'command', command: foreignSessionCommand, timeout: 9 }],
82
+ }],
83
+ },
84
+ };
85
+ }
86
+
87
+ function allCommands(settings) {
88
+ return Object.values(settings.hooks).flatMap(groups =>
89
+ groups.flatMap(group => group.hooks.map(hook => hook.command)));
90
+ }
91
+
92
+ test('every retired write registration already has an installer-owned prune path', () => {
93
+ for (const relativePath of allRetiredWriteHooks) {
94
+ assert.ok(FOLDED_HOOK_RELATIVE_PATHS.has(relativePath), relativePath);
95
+ }
96
+ });
97
+
98
+ test('every retired policy registration has an installer-owned prune path', () => {
99
+ for (const relativePath of allRetiredPolicyRegistrations) {
100
+ assert.ok(RETIRED_HOOK_REGISTRATION_RELATIVE_PATHS.has(relativePath), relativePath);
101
+ }
102
+ });
103
+
104
+ test('upgrade prunes legacy direct file checks while retaining foreign hooks', () => {
105
+ const settings = legacySettings();
106
+ mergeHooksIntoSettings(settings, sourceHooks, installedRoot, 'python3');
107
+ const commands = allCommands(settings);
108
+ for (const relativePath of [...allRetiredWriteHooks, ...allRetiredPolicyRegistrations]) {
109
+ assert.ok(!commands.some(command => command.includes(relativePath)), relativePath);
110
+ }
111
+ assert.ok(commands.includes(foreignCommand));
112
+ assert.ok(commands.includes(foreignSessionCommand));
113
+ assert.deepEqual(settings.permissions, {
114
+ allow: ['Read'],
115
+ deny: ['Bash(git push --force)'],
116
+ ask: ['Bash(git push)'],
117
+ });
118
+ assert.ok(commands.every(command => !command.includes('/blocking/stop_dispatcher.py')));
119
+ assert.ok(commands.some(command => command.includes('/blocking/pre_tool_use_dispatcher.py')));
120
+ assert.ok(commands.every(command => !command.includes('/blocking/bash_pre_tool_use_dispatcher.py')));
121
+ });
122
+
123
+ test('reinstall does not restore retired direct checks or duplicate foreign hooks', () => {
124
+ const settings = legacySettings();
125
+ mergeHooksIntoSettings(settings, sourceHooks, installedRoot, 'python3');
126
+ const firstInstall = structuredClone(settings);
127
+ mergeHooksIntoSettings(settings, sourceHooks, installedRoot, 'python3');
128
+ assert.deepEqual(settings, firstInstall);
129
+ assert.equal(allCommands(settings).filter(command => command === foreignCommand).length, 1);
130
+ });
@@ -244,6 +244,16 @@ test('install.mjs --help documents profile target selection', () => {
244
244
  assert.match(result.stdout, /targetIdentity|target identity|per target/i);
245
245
  });
246
246
 
247
+ test('install.mjs --help states that a positional path argument carries no meaning', () => {
248
+ const result = spawnSync(process.execPath, [INSTALL_MODULE_PATH, '--help'], {
249
+ encoding: 'utf8',
250
+ env: process.env,
251
+ });
252
+ assert.equal(result.status, 0, result.stderr);
253
+ assert.match(result.stdout, /reads only flags/i);
254
+ assert.match(result.stdout, /path argument/i);
255
+ });
256
+
247
257
  test('install.mjs wires select-install-targets for multi-profile entry', () => {
248
258
  const source = readFileSync(INSTALL_MODULE_PATH, 'utf8');
249
259
  assert.match(source, /select-install-targets\.mjs/);
@@ -13,6 +13,7 @@ import {
13
13
  import { tmpdir } from 'node:os';
14
14
  import { join, dirname, basename } from 'node:path';
15
15
  import { fileURLToPath } from 'node:url';
16
+ import { detectPython } from './install.mjs';
16
17
  const THIS_DIRECTORY = dirname(fileURLToPath(import.meta.url));
17
18
  const INSTALLER_PATH = join(THIS_DIRECTORY, 'install.mjs');
18
19
  const INSTALLER_PROCESS_TIMEOUT_MS = 60_000;
@@ -25,16 +26,43 @@ const RETIRED_SKILL_DIRECTORIES = [
25
26
  'post-audit-findings',
26
27
  'pr-consistency-audit',
27
28
  'bdd-protocol',
29
+ 'hitl',
30
+ 'autoconverge',
31
+ 'pr-cleanup',
32
+ 'pr-name-by-capability',
33
+ 'pr-plain-language-cleanup',
34
+ 'pr-refinement',
35
+ 'pr-shared-extraction',
36
+ 'pr-small-cl',
37
+ 'pr-title-description',
38
+ 'prototype',
39
+ 'rebase',
40
+ 'review-router',
41
+ 'review-tier',
42
+ 'run-claude-dev-env',
43
+ 'session-log',
44
+ 'session-tidy',
45
+ 'source-command-sr-loop',
46
+ 'update',
28
47
  ];
29
48
  const PERSONAL_SKILL_DIRECTORIES = ['credit-card-picker', 'midjourney-sref'];
30
- const SHIPPED_SKILL_DIRECTORY = 'autoconverge';
49
+ const SHIPPED_SKILL_DIRECTORY = 'e-code-review';
31
50
  const PRUNED_BACKUP_DIRECTORY_NAME = '.claude-dev-env-pruned';
32
51
  const SKIP_PRUNE_NOTICE_MARKER = 'Skipping retired-skill and stale-file prune';
33
52
  const STALE_SKILL_FILE_RELATIVE_SEGMENTS = ['scripts', 'retired_module.py'];
34
53
  const RUNTIME_ARTIFACT_RELATIVE_SEGMENTS = ['scripts', '__pycache__', 'helper.cpython-312.pyc'];
35
54
  const SCOPED_GROUP_SKILL_DIRECTORY = 'orchestrator';
36
- const CORE_REVIEW_GUIDE_SKILL_DIRECTORIES = [
37
- 'pr-small-cl',
55
+ const CORE_SKILL_DIRECTORIES = [
56
+ 'orchestrator',
57
+ 'orchestrator-refresh',
58
+ 'team-advisor',
59
+ 'grok-spawn',
60
+ 'everything-search',
61
+ 'test-runner',
62
+ 'privacy-hygiene',
63
+ 'issue-tracker',
64
+ 'task-build',
65
+ 'eli5',
38
66
  ];
39
67
  const PRIOR_RUN_BACKUP_DIRECTORY_NAMES = [
40
68
  '2020-01-01T00-00-00-000Z',
@@ -57,6 +85,8 @@ const STALE_RULE_FILE_NAME = 'retired-rule.md';
57
85
  const STALE_SHARED_FILE_SEGMENTS = ['pr-loop', 'retired_shared.md'];
58
86
  const STALE_SHARED_SKILL_FILE_SEGMENTS = [SHARED_DIRECTORY_NAME, 'retired_skill_shared.md'];
59
87
  const RETIRED_HOOK_RELATIVE_SEGMENTS = ['blocking', 'retired_gate.py'];
88
+ const RETIRED_HOOK_SEEDED_CONTENTS = 'a gate an earlier revision shipped\n';
89
+ const FOLDED_RETIRED_HOOK_RELATIVE_SEGMENTS = ['blocking', 'ask_user_question_shape_blocker.py'];
60
90
  const USER_HOOK_COMMAND = 'python3 my_own_gate.py --user-authored';
61
91
  const RETIRED_HOOK_EVENT_TYPE = 'PreToolUse';
62
92
  const DROPPED_HOOK_EVENT_TYPE = 'PreCompact';
@@ -150,7 +180,7 @@ function createSandbox(homeDirectoryPrefix = 'cdev-prune-home-') {
150
180
  return { homeDirectory, claudeDirectory, skillsDirectory, manifestPath };
151
181
  }
152
182
 
153
- test('a core install runs the spaced-path Write dispatcher and captures its red denial', () => {
183
+ test('a core install runs the spaced-path Write dispatcher and allows the write', () => {
154
184
  const sandbox = createSandbox('cdev prune & (home)-');
155
185
  try {
156
186
  runInstaller(sandbox.homeDirectory, ['--only', 'core']);
@@ -177,12 +207,8 @@ test('a core install runs the spaced-path Write dispatcher and captures its red
177
207
  });
178
208
 
179
209
  assert.equal(dispatcherRun.status, 0, dispatcherRun.stderr);
180
- const shownRedDecision = JSON.parse(dispatcherRun.stdout.trim());
181
- assert.equal(shownRedDecision.hookSpecificOutput.permissionDecision, 'deny');
182
- assert.match(
183
- shownRedDecision.hookSpecificOutput.permissionDecisionReason,
184
- /BLOCKED: Write on existing file/,
185
- );
210
+ assert.equal(dispatcherRun.stdout.trim(), '', 'no hosted hook denies a Write');
211
+ assert.equal(readFileSync(protectedFilePath, 'utf8'), 'existing content\n');
186
212
  } finally {
187
213
  rmSync(sandbox.homeDirectory, { recursive: true, force: true });
188
214
  }
@@ -693,25 +719,33 @@ test('a scoped --only install leaves retired skills in place because prune runs
693
719
  }
694
720
  });
695
721
 
696
- test('a full reinstall keeps autoconverge because the package ships it again', () => {
722
+ test('a full reinstall keeps the skill-builder stub and archives its obsolete support files', () => {
697
723
  const sandbox = createSandbox();
698
724
  try {
699
- plantSkillDirectory(sandbox.skillsDirectory, 'autoconverge', true);
700
- writeFileSync(join(sandbox.skillsDirectory, 'autoconverge', 'SKILL.md'), 'stale seeded copy\n');
725
+ const skillName = 'skill-builder';
726
+ plantSkillDirectory(sandbox.skillsDirectory, skillName, true);
727
+ const stubPath = join(sandbox.skillsDirectory, skillName, 'SKILL.md');
728
+ const obsoleteReferencePath = join(sandbox.skillsDirectory, skillName, 'references', 'retired.md');
729
+ writeFileWithParents(obsoleteReferencePath, 'obsolete skill-builder guidance\n');
730
+ writeFileSync(sandbox.manifestPath, JSON.stringify({
731
+ package: 'claude-dev-env',
732
+ version: '0.0.0',
733
+ installedAt: new Date().toISOString(),
734
+ files: [stubPath, obsoleteReferencePath],
735
+ skills: [skillName],
736
+ }, null, 2) + '\n');
701
737
 
702
738
  const installerOutput = runInstaller(sandbox.homeDirectory, []);
703
739
 
740
+ assert.equal(installerOutput.includes(SKIP_PRUNE_NOTICE_MARKER), false);
741
+ assert.equal(existsSync(stubPath), true, 'the requested stub stays installed');
742
+ assert.match(readFileSync(stubPath, 'utf8'), /TODO: Rework to follow pstack philosophy\./);
743
+ assert.ok(readManifest(sandbox.manifestPath).skills.includes(skillName));
744
+ assert.equal(existsSync(obsoleteReferencePath), false);
704
745
  assert.equal(
705
- installerOutput.includes(SKIP_PRUNE_NOTICE_MARKER),
706
- false,
707
- 'the full install runs the prune rather than skipping it',
708
- );
709
- const restoredSkillPath = join(sandbox.skillsDirectory, 'autoconverge', 'SKILL.md');
710
- assert.equal(existsSync(restoredSkillPath), true, 'autoconverge survives and is reinstalled');
711
- assert.notEqual(
712
- readFileSync(restoredSkillPath, 'utf8'),
713
- 'stale seeded copy\n',
714
- 'the shipped autoconverge overwrites the stale seeded copy',
746
+ prunedSkillBackupContains(sandbox.claudeDirectory, join(skillName, 'references', 'retired.md')),
747
+ true,
748
+ 'old implementation files move to the recovery backup while the stub stays',
715
749
  );
716
750
  } finally {
717
751
  rmSync(sandbox.homeDirectory, { recursive: true, force: true });
@@ -739,20 +773,24 @@ test('a scoped --only install records the skill names it installed', () => {
739
773
  }
740
774
  });
741
775
 
742
- test('a scoped core install ships the canonical review guide skills', () => {
776
+ test('a fresh scoped core install ships active skills and omits retired skills', () => {
743
777
  const sandbox = createSandbox();
744
778
  try {
745
779
  runInstaller(sandbox.homeDirectory, ['--only', 'core']);
746
780
 
747
- const missingGuideSkillNames = CORE_REVIEW_GUIDE_SKILL_DIRECTORIES.filter(
781
+ const missingSkillNames = CORE_SKILL_DIRECTORIES.filter(
748
782
  eachSkillName => !existsSync(join(sandbox.skillsDirectory, eachSkillName, 'SKILL.md')),
749
783
  );
750
- assert.deepEqual(missingGuideSkillNames, []);
784
+ assert.deepEqual(missingSkillNames, []);
751
785
 
752
786
  const recordedSkillNames = new Set(readManifest(sandbox.manifestPath).skills);
753
- for (const eachSkillName of CORE_REVIEW_GUIDE_SKILL_DIRECTORIES) {
787
+ for (const eachSkillName of CORE_SKILL_DIRECTORIES) {
754
788
  assert.equal(recordedSkillNames.has(eachSkillName), true);
755
789
  }
790
+ for (const eachSkillName of RETIRED_SKILL_DIRECTORIES) {
791
+ assert.equal(existsSync(join(sandbox.skillsDirectory, eachSkillName)), false);
792
+ assert.equal(recordedSkillNames.has(eachSkillName), false);
793
+ }
756
794
  } finally {
757
795
  rmSync(sandbox.homeDirectory, { recursive: true, force: true });
758
796
  }
@@ -919,7 +957,7 @@ function seedRetiredManagedHook(sandbox) {
919
957
  const retiredHookPath = join(
920
958
  sandbox.claudeDirectory, HOOKS_DIRECTORY_NAME, ...RETIRED_HOOK_RELATIVE_SEGMENTS,
921
959
  );
922
- writeFileWithParents(retiredHookPath, 'a gate an earlier revision shipped\n');
960
+ writeFileWithParents(retiredHookPath, RETIRED_HOOK_SEEDED_CONTENTS);
923
961
  appendManifestFiles(sandbox.manifestPath, [retiredHookPath]);
924
962
  const retiredHookCommand = `python3 "${retiredHookPath.replace(/\\/g, '/')}"`;
925
963
  const lookalikeHookCommand = `python3 "${retiredHookPath.replace(/\\/g, '/')}.bak"`;
@@ -1019,7 +1057,7 @@ test('a full reinstall leaves a manifest-recorded path under no managed root in
1019
1057
  }
1020
1058
  });
1021
1059
 
1022
- test('a reinstall removes a retired post-commit shim and preserves a foreign git hook', () => {
1060
+ test('a reinstall regenerates a stale post-commit shim and preserves a foreign git hook', () => {
1023
1061
  const sandbox = createSandbox();
1024
1062
  try {
1025
1063
  runInstaller(sandbox.homeDirectory, []);
@@ -1029,18 +1067,18 @@ test('a reinstall removes a retired post-commit shim and preserves a foreign git
1029
1067
  const postCommitShimPath = join(gitHooksDirectory, POST_COMMIT_HOOK_NAME);
1030
1068
  const foreignHookPath = join(gitHooksDirectory, FOREIGN_HOOK_NAME);
1031
1069
  const foreignHookContentBytes = Buffer.from('foreign hook content\n', 'utf8');
1032
- writeFileWithParents(postCommitShimPath, 'generated by an earlier install\n');
1070
+ const staleShimContent = 'generated by an earlier install\n';
1071
+ writeFileWithParents(postCommitShimPath, staleShimContent);
1033
1072
  writeFileWithParents(foreignHookPath, foreignHookContentBytes);
1034
1073
  appendManifestFiles(sandbox.manifestPath, [postCommitShimPath]);
1035
1074
 
1036
1075
  runInstaller(sandbox.homeDirectory, []);
1037
1076
 
1038
1077
  const freshManifest = readManifest(sandbox.manifestPath);
1039
- assert.equal(existsSync(postCommitShimPath), false);
1078
+ assert.notEqual(readFileSync(postCommitShimPath, 'utf8'), staleShimContent);
1040
1079
  assert.deepEqual(readFileSync(foreignHookPath), foreignHookContentBytes);
1041
- assert.equal(freshManifest.files.includes(postCommitShimPath), false);
1042
1080
  assert.equal(freshManifest.files.includes(foreignHookPath), false);
1043
- for (const gitHookName of [PRE_COMMIT_HOOK_NAME, PRE_PUSH_HOOK_NAME]) {
1081
+ for (const gitHookName of [PRE_COMMIT_HOOK_NAME, PRE_PUSH_HOOK_NAME, POST_COMMIT_HOOK_NAME]) {
1044
1082
  const generatedShimPath = join(gitHooksDirectory, gitHookName);
1045
1083
  assert.equal(existsSync(generatedShimPath), true);
1046
1084
  assert.equal(freshManifest.files.includes(generatedShimPath), true);
@@ -1050,7 +1088,7 @@ test('a reinstall removes a retired post-commit shim and preserves a foreign git
1050
1088
  }
1051
1089
  });
1052
1090
 
1053
- test('a full reinstall drops a retired hook script and its settings entry in one run', () => {
1091
+ test('a full reinstall retires a hook script and leaves an inert stand-in the open session can still run', () => {
1054
1092
  const sandbox = createSandbox();
1055
1093
  try {
1056
1094
  runInstaller(sandbox.homeDirectory, []);
@@ -1058,9 +1096,26 @@ test('a full reinstall drops a retired hook script and its settings entry in one
1058
1096
  retiredHookPath, retiredHookCommand, lookalikeHookCommand,
1059
1097
  } = seedRetiredManagedHook(sandbox);
1060
1098
 
1061
- runInstaller(sandbox.homeDirectory, []);
1099
+ const installOutput = runInstallerReadingBothStreams(sandbox.homeDirectory, []);
1062
1100
 
1063
- assert.equal(existsSync(retiredHookPath), false, 'the retired hook script leaves ~/.claude/hooks');
1101
+ assert.notEqual(
1102
+ readFileSync(retiredHookPath, 'utf8'),
1103
+ RETIRED_HOOK_SEEDED_CONTENTS,
1104
+ 'the retired gate itself no longer runs at that path',
1105
+ );
1106
+ const standInRun = spawnSync(detectPython(), [retiredHookPath], { encoding: 'utf8' });
1107
+ assert.equal(
1108
+ standInRun.status,
1109
+ 0,
1110
+ 'a session holding the old registration runs the stand-in and gets an allow',
1111
+ );
1112
+ assert.equal(standInRun.stdout, '', 'the stand-in writes nothing to stdout');
1113
+ assert.equal(standInRun.stderr, '', 'the stand-in writes nothing to stderr');
1114
+ assert.match(
1115
+ installOutput,
1116
+ /Restart open sessions: this install retired blocking\/retired_gate\.py/,
1117
+ 'the run names the retired registration and asks for a restart',
1118
+ );
1064
1119
  assert.equal(
1065
1120
  prunedBackupContains(
1066
1121
  sandbox.claudeDirectory, join(HOOKS_DIRECTORY_NAME, ...RETIRED_HOOK_RELATIVE_SEGMENTS),
@@ -1084,6 +1139,59 @@ test('a full reinstall drops a retired hook script and its settings entry in one
1084
1139
  true,
1085
1140
  'a user command whose path is the retired tail plus a suffix names another file and stays',
1086
1141
  );
1142
+
1143
+ runInstaller(sandbox.homeDirectory, []);
1144
+
1145
+ assert.equal(
1146
+ existsSync(retiredHookPath),
1147
+ false,
1148
+ 'the next run finds no registration naming the stand-in, so the stand-in leaves too',
1149
+ );
1150
+ } finally {
1151
+ rmSync(sandbox.homeDirectory, { recursive: true, force: true });
1152
+ }
1153
+ });
1154
+
1155
+ test('a full reinstall leaves a stand-in for a registration the package itself stopped shipping', () => {
1156
+ const sandbox = createSandbox();
1157
+ try {
1158
+ runInstaller(sandbox.homeDirectory, []);
1159
+ const retiredHookPath = join(
1160
+ sandbox.claudeDirectory,
1161
+ HOOKS_DIRECTORY_NAME,
1162
+ ...FOLDED_RETIRED_HOOK_RELATIVE_SEGMENTS,
1163
+ );
1164
+ writeFileWithParents(retiredHookPath, RETIRED_HOOK_SEEDED_CONTENTS);
1165
+ appendManifestFiles(sandbox.manifestPath, [retiredHookPath]);
1166
+ const retiredHookCommand = `python3 "${retiredHookPath.replace(/\\/g, '/')}"`;
1167
+ const settings = readSettings(sandbox.claudeDirectory);
1168
+ settings.hooks[RETIRED_HOOK_EVENT_TYPE].push({
1169
+ matcher: 'AskUserQuestion',
1170
+ hooks: [{ type: 'command', command: retiredHookCommand }],
1171
+ });
1172
+ writeFileSync(
1173
+ join(sandbox.claudeDirectory, SETTINGS_FILE_NAME),
1174
+ JSON.stringify(settings, null, 4) + '\n',
1175
+ );
1176
+
1177
+ const installOutput = runInstallerReadingBothStreams(sandbox.homeDirectory, []);
1178
+
1179
+ assert.equal(
1180
+ allHookCommands(readSettings(sandbox.claudeDirectory)).includes(retiredHookCommand),
1181
+ false,
1182
+ 'the registration the package stopped shipping leaves settings.json',
1183
+ );
1184
+ const standInRun = spawnSync(detectPython(), [retiredHookPath], { encoding: 'utf8' });
1185
+ assert.equal(
1186
+ standInRun.status,
1187
+ 0,
1188
+ 'a session still holding that registration runs a stand-in and gets an allow',
1189
+ );
1190
+ assert.match(
1191
+ installOutput,
1192
+ /Restart open sessions: this install retired blocking\/ask_user_question_shape_blocker\.py/,
1193
+ 'the run names the registration the merge would otherwise drop in silence',
1194
+ );
1087
1195
  } finally {
1088
1196
  rmSync(sandbox.homeDirectory, { recursive: true, force: true });
1089
1197
  }
@@ -1189,7 +1297,23 @@ test('a full reinstall removes retired pull request hooks and preserves foreign
1189
1297
 
1190
1298
  const freshManifest = readManifest(sandbox.manifestPath);
1191
1299
  const allCommands = allHookCommands(readSettings(sandbox.claudeDirectory));
1192
- for (const retiredPath of retiredPaths) {
1300
+ const registeredRetiredPaths = new Set(RETIRED_PULL_REQUEST_REGISTRATION_PATHS);
1301
+ for (const relativePath of RETIRED_PULL_REQUEST_HOOK_RELATIVE_PATHS) {
1302
+ const retiredPath = join(
1303
+ sandbox.claudeDirectory, HOOKS_DIRECTORY_NAME, ...relativePath.split('/'),
1304
+ );
1305
+ if (registeredRetiredPaths.has(relativePath)) {
1306
+ assert.equal(
1307
+ spawnSync(detectPython(), [retiredPath], { encoding: 'utf8' }).status,
1308
+ 0,
1309
+ `${relativePath} keeps an inert stand-in while a session still registers it`,
1310
+ );
1311
+ assert.ok(
1312
+ freshManifest.files.includes(retiredPath),
1313
+ `${relativePath} is recorded, so a later run retires the stand-in`,
1314
+ );
1315
+ continue;
1316
+ }
1193
1317
  assert.equal(existsSync(retiredPath), false, `${retiredPath} leaves installed hooks`);
1194
1318
  assert.equal(freshManifest.files.includes(retiredPath), false);
1195
1319
  }
@@ -1203,6 +1327,131 @@ test('a full reinstall removes retired pull request hooks and preserves foreign
1203
1327
  }
1204
1328
  });
1205
1329
 
1330
+
1331
+ const CODEX_DIRECTORY_NAME = '.codex';
1332
+ const CODEX_HOOKS_CONFIGURATION_FILE_NAME = 'hooks.json';
1333
+ const FOREIGN_TOOL_DIRECTORY_NAME = 'other-tool';
1334
+ const FOREIGN_CODEX_HOOK_COMMAND = 'python3 my_own_codex_gate.py --user-authored';
1335
+
1336
+
1337
+ /**
1338
+ * Build the command one hook registration runs for a script under a hooks root.
1339
+ *
1340
+ * @param {string} hooksRoot The hooks directory the registration names.
1341
+ * @param {string[]} relativeSegments The script path segments under that directory.
1342
+ * @returns {string} The registration command.
1343
+ */
1344
+ function hookCommandUnder(hooksRoot, relativeSegments) {
1345
+ return `python3 "${join(hooksRoot, ...relativeSegments).replace(/\\/g, '/')}"`;
1346
+ }
1347
+
1348
+
1349
+ /**
1350
+ * Write a Codex hooks file registering one retired script beside a foreign entry.
1351
+ *
1352
+ * @param {{homeDirectory: string}} sandbox The sandbox paths.
1353
+ * @returns {{codexHooksPath: string, codexRetiredCommand: string}} The file and its retired entry.
1354
+ */
1355
+ function seedCodexHookRegistrations(sandbox) {
1356
+ const codexDirectory = join(sandbox.homeDirectory, CODEX_DIRECTORY_NAME);
1357
+ const codexHooksPath = join(codexDirectory, CODEX_HOOKS_CONFIGURATION_FILE_NAME);
1358
+ const codexRetiredCommand = hookCommandUnder(
1359
+ join(codexDirectory, HOOKS_DIRECTORY_NAME), RETIRED_HOOK_RELATIVE_SEGMENTS,
1360
+ );
1361
+ writeFileWithParents(codexHooksPath, JSON.stringify({
1362
+ hooks: {
1363
+ [RETIRED_HOOK_EVENT_TYPE]: [{
1364
+ matcher: 'Bash',
1365
+ hooks: [
1366
+ { type: 'command', command: codexRetiredCommand },
1367
+ { type: 'command', command: FOREIGN_CODEX_HOOK_COMMAND },
1368
+ ],
1369
+ }],
1370
+ },
1371
+ }, null, 4) + '\n');
1372
+ return { codexHooksPath, codexRetiredCommand };
1373
+ }
1374
+
1375
+
1376
+ test('a full reinstall prunes a retired registration the Codex hooks file holds and leaves a stand-in', () => {
1377
+ const sandbox = createSandbox();
1378
+ try {
1379
+ runInstaller(sandbox.homeDirectory, []);
1380
+ const retiredHookPath = join(
1381
+ sandbox.claudeDirectory, HOOKS_DIRECTORY_NAME, ...RETIRED_HOOK_RELATIVE_SEGMENTS,
1382
+ );
1383
+ writeFileWithParents(retiredHookPath, RETIRED_HOOK_SEEDED_CONTENTS);
1384
+ appendManifestFiles(sandbox.manifestPath, [retiredHookPath]);
1385
+ const { codexHooksPath, codexRetiredCommand } = seedCodexHookRegistrations(sandbox);
1386
+
1387
+ const installOutput = runInstallerReadingBothStreams(sandbox.homeDirectory, []);
1388
+
1389
+ const codexCommands = allHookCommands(JSON.parse(readFileSync(codexHooksPath, 'utf8')));
1390
+ assert.equal(
1391
+ codexCommands.includes(codexRetiredCommand),
1392
+ false,
1393
+ 'the Codex registration naming the retired script is pruned like a settings.json one',
1394
+ );
1395
+ assert.equal(
1396
+ codexCommands.includes(FOREIGN_CODEX_HOOK_COMMAND),
1397
+ true,
1398
+ 'the user-authored Codex entry sharing the matcher group survives',
1399
+ );
1400
+ assert.equal(
1401
+ spawnSync(detectPython(), [retiredHookPath], { encoding: 'utf8' }).status,
1402
+ 0,
1403
+ 'the Codex registration alone earns the retired path an inert stand-in',
1404
+ );
1405
+ assert.match(
1406
+ installOutput,
1407
+ /Restart open sessions: this install retired blocking\/retired_gate\.py/,
1408
+ 'the run names the retired registration and asks for a restart',
1409
+ );
1410
+ } finally {
1411
+ rmSync(sandbox.homeDirectory, { recursive: true, force: true });
1412
+ }
1413
+ });
1414
+
1415
+
1416
+ test('a full reinstall leaves a registration under a hooks root the installer does not own', () => {
1417
+ const sandbox = createSandbox();
1418
+ try {
1419
+ runInstaller(sandbox.homeDirectory, []);
1420
+ seedRetiredManagedHook(sandbox);
1421
+ const foreignHooksRoot = join(
1422
+ sandbox.homeDirectory, FOREIGN_TOOL_DIRECTORY_NAME, '.claude', HOOKS_DIRECTORY_NAME,
1423
+ );
1424
+ const foreignHookPath = join(foreignHooksRoot, ...RETIRED_HOOK_RELATIVE_SEGMENTS);
1425
+ const foreignHookBytes = Buffer.from('another tool owns this gate\n', 'utf8');
1426
+ writeFileWithParents(foreignHookPath, foreignHookBytes);
1427
+ const foreignCommand = hookCommandUnder(foreignHooksRoot, RETIRED_HOOK_RELATIVE_SEGMENTS);
1428
+ const settings = readSettings(sandbox.claudeDirectory);
1429
+ settings.hooks[RETIRED_HOOK_EVENT_TYPE].push({
1430
+ matcher: 'Bash',
1431
+ hooks: [{ type: 'command', command: foreignCommand }],
1432
+ });
1433
+ writeFileSync(
1434
+ join(sandbox.claudeDirectory, SETTINGS_FILE_NAME),
1435
+ JSON.stringify(settings, null, 4) + '\n',
1436
+ );
1437
+
1438
+ runInstaller(sandbox.homeDirectory, []);
1439
+
1440
+ assert.equal(
1441
+ allHookCommands(readSettings(sandbox.claudeDirectory)).includes(foreignCommand),
1442
+ true,
1443
+ 'a registration whose hooks root the installer does not own keeps its entry',
1444
+ );
1445
+ assert.deepEqual(
1446
+ readFileSync(foreignHookPath),
1447
+ foreignHookBytes,
1448
+ 'the foreign script keeps its own bytes, so no stand-in replaced it',
1449
+ );
1450
+ } finally {
1451
+ rmSync(sandbox.homeDirectory, { recursive: true, force: true });
1452
+ }
1453
+ });
1454
+
1206
1455
  test('a full reinstall removes a retired hook entry under an event type the current config leaves out', () => {
1207
1456
  const sandbox = createSandbox();
1208
1457
  try {