peaks-loop 4.1.3 → 4.1.4

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 (402) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/CHANGELOG.md +56 -0
  3. package/README-en.md +1 -1
  4. package/README.md +1 -1
  5. package/dist/cli/commands/asset-command-shared.d.ts +12 -0
  6. package/dist/cli/commands/asset-command-shared.js +45 -0
  7. package/dist/cli/commands/asset-commands.d.ts +6 -1
  8. package/dist/cli/commands/asset-commands.js +11 -372
  9. package/dist/cli/commands/asset-crystallize-command.d.ts +3 -0
  10. package/dist/cli/commands/asset-crystallize-command.js +60 -0
  11. package/dist/cli/commands/asset-crystallize-payload.d.ts +47 -0
  12. package/dist/cli/commands/asset-crystallize-payload.js +85 -0
  13. package/dist/cli/commands/asset-crystallize-runner.d.ts +3 -0
  14. package/dist/cli/commands/asset-crystallize-runner.js +133 -0
  15. package/dist/cli/commands/asset-dispose-command.d.ts +3 -0
  16. package/dist/cli/commands/asset-dispose-command.js +99 -0
  17. package/dist/cli/commands/asset-status-command.d.ts +3 -0
  18. package/dist/cli/commands/asset-status-command.js +100 -0
  19. package/dist/cli/commands/baseline-audit-command.d.ts +3 -0
  20. package/dist/cli/commands/baseline-audit-command.js +64 -0
  21. package/dist/cli/commands/baseline-command-shared.d.ts +23 -0
  22. package/dist/cli/commands/baseline-command-shared.js +39 -0
  23. package/dist/cli/commands/baseline-commands.js +33 -313
  24. package/dist/cli/commands/baseline-freeze-commands.d.ts +4 -0
  25. package/dist/cli/commands/baseline-freeze-commands.js +81 -0
  26. package/dist/cli/commands/baseline-guard-command.d.ts +3 -0
  27. package/dist/cli/commands/baseline-guard-command.js +52 -0
  28. package/dist/cli/commands/baseline-read-commands.d.ts +5 -0
  29. package/dist/cli/commands/baseline-read-commands.js +72 -0
  30. package/dist/cli/commands/baseline-rollback-commands.d.ts +4 -0
  31. package/dist/cli/commands/baseline-rollback-commands.js +88 -0
  32. package/dist/cli/commands/classify-downgrade-command.d.ts +9 -0
  33. package/dist/cli/commands/classify-downgrade-command.js +21 -0
  34. package/dist/cli/commands/classify-run-command.d.ts +9 -0
  35. package/dist/cli/commands/classify-run-command.js +73 -0
  36. package/dist/cli/commands/classify-signals.d.ts +5 -0
  37. package/dist/cli/commands/classify-signals.js +104 -0
  38. package/dist/cli/commands/code-emit-handoff-command.js +40 -3
  39. package/dist/cli/commands/code-gate-step-08-command.js +25 -9
  40. package/dist/cli/commands/codegraph-command-options.d.ts +22 -0
  41. package/dist/cli/commands/codegraph-command-options.js +22 -0
  42. package/dist/cli/commands/codegraph-commands.d.ts +2 -2
  43. package/dist/cli/commands/codegraph-commands.js +14 -397
  44. package/dist/cli/commands/codegraph-config-restore-command.d.ts +3 -0
  45. package/dist/cli/commands/codegraph-config-restore-command.js +200 -0
  46. package/dist/cli/commands/codegraph-proxy-commands.d.ts +10 -0
  47. package/dist/cli/commands/codegraph-proxy-commands.js +159 -0
  48. package/dist/cli/commands/codegraph-repair-commands.d.ts +3 -0
  49. package/dist/cli/commands/codegraph-repair-commands.js +94 -0
  50. package/dist/cli/commands/codegraph-status-command.d.ts +1 -1
  51. package/dist/cli/commands/codegraph-status-command.js +27 -214
  52. package/dist/cli/commands/codegraph-status-integrity.d.ts +44 -0
  53. package/dist/cli/commands/codegraph-status-integrity.js +121 -0
  54. package/dist/cli/commands/codegraph-status-json.d.ts +5 -0
  55. package/dist/cli/commands/codegraph-status-json.js +127 -0
  56. package/dist/cli/commands/container-command-shared.d.ts +60 -0
  57. package/dist/cli/commands/container-command-shared.js +52 -0
  58. package/dist/cli/commands/container-commands.d.ts +1 -1
  59. package/dist/cli/commands/container-commands.js +4 -216
  60. package/dist/cli/commands/container-release-command.d.ts +3 -0
  61. package/dist/cli/commands/container-release-command.js +105 -0
  62. package/dist/cli/commands/container-spawn-command.d.ts +3 -0
  63. package/dist/cli/commands/container-spawn-command.js +148 -0
  64. package/dist/cli/commands/context-check-command.d.ts +3 -0
  65. package/dist/cli/commands/context-check-command.js +81 -0
  66. package/dist/cli/commands/context-commands.d.ts +2 -7
  67. package/dist/cli/commands/context-commands.js +6 -276
  68. package/dist/cli/commands/context-layer-command.d.ts +3 -0
  69. package/dist/cli/commands/context-layer-command.js +34 -0
  70. package/dist/cli/commands/context-layer-fetchers.d.ts +15 -0
  71. package/dist/cli/commands/context-layer-fetchers.js +162 -0
  72. package/dist/cli/commands/context-status-command.d.ts +17 -0
  73. package/dist/cli/commands/context-status-command.js +41 -0
  74. package/dist/cli/commands/contract-write-command.d.ts +3 -0
  75. package/dist/cli/commands/contract-write-command.js +144 -0
  76. package/dist/cli/commands/core/doctor-bindings.d.ts +17 -0
  77. package/dist/cli/commands/core/doctor-bindings.js +30 -0
  78. package/dist/cli/commands/core/doctor-command.d.ts +4 -18
  79. package/dist/cli/commands/core/doctor-command.js +2 -323
  80. package/dist/cli/commands/core/doctor-logs.d.ts +8 -0
  81. package/dist/cli/commands/core/doctor-logs.js +32 -0
  82. package/dist/cli/commands/core/doctor-probes.d.ts +3 -0
  83. package/dist/cli/commands/core/doctor-probes.js +55 -0
  84. package/dist/cli/commands/core/doctor-run-command.d.ts +12 -0
  85. package/dist/cli/commands/core/doctor-run-command.js +172 -0
  86. package/dist/cli/commands/core/doctor-summary.d.ts +7 -0
  87. package/dist/cli/commands/core/doctor-summary.js +39 -0
  88. package/dist/cli/commands/core/memory-command-shared.d.ts +25 -0
  89. package/dist/cli/commands/core/memory-command-shared.js +20 -0
  90. package/dist/cli/commands/core/memory-command.d.ts +1 -1
  91. package/dist/cli/commands/core/memory-command.js +6 -227
  92. package/dist/cli/commands/core/memory-delegated-verbs.d.ts +4 -0
  93. package/dist/cli/commands/core/memory-delegated-verbs.js +178 -0
  94. package/dist/cli/commands/core/memory-extract-command.d.ts +3 -0
  95. package/dist/cli/commands/core/memory-extract-command.js +90 -0
  96. package/dist/cli/commands/core/memory-sync-command.d.ts +3 -0
  97. package/dist/cli/commands/core/memory-sync-command.js +38 -0
  98. package/dist/cli/commands/core/session-command.d.ts +1 -1
  99. package/dist/cli/commands/core/session-command.js +12 -206
  100. package/dist/cli/commands/core/session-info-command.d.ts +3 -0
  101. package/dist/cli/commands/core/session-info-command.js +142 -0
  102. package/dist/cli/commands/core/session-verb-commands.d.ts +5 -0
  103. package/dist/cli/commands/core/session-verb-commands.js +58 -0
  104. package/dist/cli/commands/core/standards-command.d.ts +1 -24
  105. package/dist/cli/commands/core/standards-command.js +33 -201
  106. package/dist/cli/commands/core/standards-init-command.d.ts +3 -0
  107. package/dist/cli/commands/core/standards-init-command.js +87 -0
  108. package/dist/cli/commands/core/standards-lint.d.ts +24 -0
  109. package/dist/cli/commands/core/standards-lint.js +57 -0
  110. package/dist/cli/commands/core/standards-migrate-command.d.ts +3 -0
  111. package/dist/cli/commands/core/standards-migrate-command.js +57 -0
  112. package/dist/cli/commands/cron-scheduler-commands.d.ts +5 -1
  113. package/dist/cli/commands/cron-scheduler-commands.js +36 -206
  114. package/dist/cli/commands/cron-scheduler-run-once-command.d.ts +7 -0
  115. package/dist/cli/commands/cron-scheduler-run-once-command.js +35 -0
  116. package/dist/cli/commands/cron-scheduler-shared.d.ts +6 -0
  117. package/dist/cli/commands/cron-scheduler-shared.js +48 -0
  118. package/dist/cli/commands/cron-scheduler-start-command.d.ts +7 -0
  119. package/dist/cli/commands/cron-scheduler-start-command.js +65 -0
  120. package/dist/cli/commands/cron-scheduler-status-command.d.ts +7 -0
  121. package/dist/cli/commands/cron-scheduler-status-command.js +46 -0
  122. package/dist/cli/commands/cron-scheduler-stop-command.d.ts +7 -0
  123. package/dist/cli/commands/cron-scheduler-stop-command.js +61 -0
  124. package/dist/cli/commands/dispatch-dag-envelope.d.ts +41 -0
  125. package/dist/cli/commands/dispatch-dag-envelope.js +49 -0
  126. package/dist/cli/commands/dispatch-dag-failure.d.ts +18 -0
  127. package/dist/cli/commands/dispatch-dag-failure.js +16 -0
  128. package/dist/cli/commands/dispatch-dag-preflight.d.ts +51 -0
  129. package/dist/cli/commands/dispatch-dag-preflight.js +105 -0
  130. package/dist/cli/commands/dispatch-dag-runners.d.ts +48 -0
  131. package/dist/cli/commands/dispatch-dag-runners.js +73 -0
  132. package/dist/cli/commands/dispatch-from-dag.d.ts +27 -0
  133. package/dist/cli/commands/dispatch-from-dag.js +87 -257
  134. package/dist/cli/commands/final-review-command-envelopes.d.ts +44 -0
  135. package/dist/cli/commands/final-review-command-envelopes.js +100 -0
  136. package/dist/cli/commands/final-review-command-shared.d.ts +52 -0
  137. package/dist/cli/commands/final-review-command-shared.js +35 -0
  138. package/dist/cli/commands/final-review-commands.d.ts +7 -27
  139. package/dist/cli/commands/final-review-commands.js +13 -240
  140. package/dist/cli/commands/final-review-prepare-command.d.ts +3 -0
  141. package/dist/cli/commands/final-review-prepare-command.js +200 -0
  142. package/dist/cli/commands/governance-classify-contract-commands.d.ts +23 -9
  143. package/dist/cli/commands/governance-classify-contract-commands.js +26 -269
  144. package/dist/cli/commands/hooks-command-shared.d.ts +42 -0
  145. package/dist/cli/commands/hooks-command-shared.js +32 -0
  146. package/dist/cli/commands/hooks-commands.d.ts +20 -0
  147. package/dist/cli/commands/hooks-commands.js +75 -309
  148. package/dist/cli/commands/hooks-install-envelopes.d.ts +6 -0
  149. package/dist/cli/commands/hooks-install-envelopes.js +144 -0
  150. package/dist/cli/commands/hooks-status-command.d.ts +3 -0
  151. package/dist/cli/commands/hooks-status-command.js +93 -0
  152. package/dist/cli/commands/hooks-uninstall-command.d.ts +3 -0
  153. package/dist/cli/commands/hooks-uninstall-command.js +43 -0
  154. package/dist/cli/commands/loop-command-shared.d.ts +31 -0
  155. package/dist/cli/commands/loop-command-shared.js +15 -0
  156. package/dist/cli/commands/loop-commands.d.ts +12 -17
  157. package/dist/cli/commands/loop-commands.js +56 -238
  158. package/dist/cli/commands/loop-export-command.d.ts +3 -0
  159. package/dist/cli/commands/loop-export-command.js +73 -0
  160. package/dist/cli/commands/loop-goal-compose-command.d.ts +3 -0
  161. package/dist/cli/commands/loop-goal-compose-command.js +36 -0
  162. package/dist/cli/commands/loop-import-command.d.ts +3 -0
  163. package/dist/cli/commands/loop-import-command.js +86 -0
  164. package/dist/cli/commands/loop-placeholder-commands.d.ts +3 -0
  165. package/dist/cli/commands/loop-placeholder-commands.js +52 -0
  166. package/dist/cli/commands/memory-command-shared.d.ts +59 -0
  167. package/dist/cli/commands/memory-command-shared.js +39 -0
  168. package/dist/cli/commands/memory-commands.d.ts +6 -79
  169. package/dist/cli/commands/memory-commands.js +19 -309
  170. package/dist/cli/commands/memory-ingest-command.d.ts +3 -0
  171. package/dist/cli/commands/memory-ingest-command.js +50 -0
  172. package/dist/cli/commands/memory-list-command.d.ts +16 -0
  173. package/dist/cli/commands/memory-list-command.js +129 -0
  174. package/dist/cli/commands/memory-reindex-command.d.ts +9 -0
  175. package/dist/cli/commands/memory-reindex-command.js +78 -0
  176. package/dist/cli/commands/memory-rotate-command.d.ts +3 -0
  177. package/dist/cli/commands/memory-rotate-command.js +55 -0
  178. package/dist/cli/commands/memory-search-command.d.ts +7 -0
  179. package/dist/cli/commands/memory-search-command.js +46 -0
  180. package/dist/cli/commands/openspec-archive-command.d.ts +3 -0
  181. package/dist/cli/commands/openspec-archive-command.js +55 -0
  182. package/dist/cli/commands/openspec-command-shared.d.ts +62 -0
  183. package/dist/cli/commands/openspec-command-shared.js +135 -0
  184. package/dist/cli/commands/openspec-commands.d.ts +1 -1
  185. package/dist/cli/commands/openspec-commands.js +33 -306
  186. package/dist/cli/commands/openspec-from-doctor-command.d.ts +3 -0
  187. package/dist/cli/commands/openspec-from-doctor-command.js +66 -0
  188. package/dist/cli/commands/openspec-init-command.d.ts +3 -0
  189. package/dist/cli/commands/openspec-init-command.js +49 -0
  190. package/dist/cli/commands/openspec-list-command.d.ts +3 -0
  191. package/dist/cli/commands/openspec-list-command.js +27 -0
  192. package/dist/cli/commands/openspec-render-command.d.ts +3 -0
  193. package/dist/cli/commands/openspec-render-command.js +33 -0
  194. package/dist/cli/commands/openspec-show-command.d.ts +3 -0
  195. package/dist/cli/commands/openspec-show-command.js +33 -0
  196. package/dist/cli/commands/openspec-to-rd-command.d.ts +3 -0
  197. package/dist/cli/commands/openspec-to-rd-command.js +33 -0
  198. package/dist/cli/commands/openspec-validate-command.d.ts +3 -0
  199. package/dist/cli/commands/openspec-validate-command.js +45 -0
  200. package/dist/cli/commands/playwright-browser-command.d.ts +8 -0
  201. package/dist/cli/commands/playwright-browser-command.js +61 -0
  202. package/dist/cli/commands/playwright-commands.d.ts +10 -62
  203. package/dist/cli/commands/playwright-commands.js +77 -400
  204. package/dist/cli/commands/playwright-envelope.d.ts +8 -0
  205. package/dist/cli/commands/playwright-envelope.js +29 -0
  206. package/dist/cli/commands/playwright-ls-stop-commands.d.ts +4 -0
  207. package/dist/cli/commands/playwright-ls-stop-commands.js +74 -0
  208. package/dist/cli/commands/playwright-session-store.d.ts +65 -0
  209. package/dist/cli/commands/playwright-session-store.js +189 -0
  210. package/dist/cli/commands/project-commands.d.ts +19 -0
  211. package/dist/cli/commands/project-commands.js +98 -259
  212. package/dist/cli/commands/project-context-command.d.ts +3 -0
  213. package/dist/cli/commands/project-context-command.js +44 -0
  214. package/dist/cli/commands/project-dashboard-command.d.ts +3 -0
  215. package/dist/cli/commands/project-dashboard-command.js +74 -0
  216. package/dist/cli/commands/project-knowledge-command.d.ts +3 -0
  217. package/dist/cli/commands/project-knowledge-command.js +48 -0
  218. package/dist/cli/commands/project-memories-show-command.d.ts +3 -0
  219. package/dist/cli/commands/project-memories-show-command.js +87 -0
  220. package/dist/cli/commands/project-memory-index-command.d.ts +3 -0
  221. package/dist/cli/commands/project-memory-index-command.js +31 -0
  222. package/dist/cli/commands/qa-archive-screenshots-command.d.ts +3 -0
  223. package/dist/cli/commands/qa-archive-screenshots-command.js +53 -0
  224. package/dist/cli/commands/qa-commands.d.ts +14 -87
  225. package/dist/cli/commands/qa-commands.js +17 -315
  226. package/dist/cli/commands/qa-context-prestep.d.ts +1 -0
  227. package/dist/cli/commands/qa-context-prestep.js +34 -0
  228. package/dist/cli/commands/qa-run-command.d.ts +3 -0
  229. package/dist/cli/commands/qa-run-command.js +105 -0
  230. package/dist/cli/commands/qa-run-options.d.ts +23 -0
  231. package/dist/cli/commands/qa-run-options.js +31 -0
  232. package/dist/cli/commands/qa-run-slice.d.ts +41 -0
  233. package/dist/cli/commands/qa-run-slice.js +145 -0
  234. package/dist/cli/commands/release-canary-command.d.ts +28 -0
  235. package/dist/cli/commands/release-canary-command.js +129 -0
  236. package/dist/cli/commands/release-command-shared.d.ts +13 -0
  237. package/dist/cli/commands/release-command-shared.js +18 -0
  238. package/dist/cli/commands/release-commands.d.ts +10 -18
  239. package/dist/cli/commands/release-commands.js +25 -298
  240. package/dist/cli/commands/release-done-command.d.ts +3 -0
  241. package/dist/cli/commands/release-done-command.js +64 -0
  242. package/dist/cli/commands/release-hotfix-command.d.ts +3 -0
  243. package/dist/cli/commands/release-hotfix-command.js +50 -0
  244. package/dist/cli/commands/release-plan-command.d.ts +3 -0
  245. package/dist/cli/commands/release-plan-command.js +33 -0
  246. package/dist/cli/commands/release-precheck-command.d.ts +3 -0
  247. package/dist/cli/commands/release-precheck-command.js +28 -0
  248. package/dist/cli/commands/release-promote-command.d.ts +3 -0
  249. package/dist/cli/commands/release-promote-command.js +34 -0
  250. package/dist/cli/commands/release-rollback-command.d.ts +3 -0
  251. package/dist/cli/commands/release-rollback-command.js +33 -0
  252. package/dist/cli/commands/release-watch-command.d.ts +3 -0
  253. package/dist/cli/commands/release-watch-command.js +44 -0
  254. package/dist/cli/commands/sediment-argv-flags.d.ts +54 -0
  255. package/dist/cli/commands/sediment-argv-flags.js +116 -0
  256. package/dist/cli/commands/sediment-command-shared.d.ts +15 -0
  257. package/dist/cli/commands/sediment-command-shared.js +5 -0
  258. package/dist/cli/commands/sediment-commands.d.ts +35 -59
  259. package/dist/cli/commands/sediment-commands.js +36 -590
  260. package/dist/cli/commands/sediment-pool-commands.d.ts +9 -0
  261. package/dist/cli/commands/sediment-pool-commands.js +185 -0
  262. package/dist/cli/commands/sediment-query-commands.d.ts +4 -0
  263. package/dist/cli/commands/sediment-query-commands.js +94 -0
  264. package/dist/cli/commands/sediment-release-commands.d.ts +8 -0
  265. package/dist/cli/commands/sediment-release-commands.js +232 -0
  266. package/dist/cli/commands/sop-advance-command.d.ts +3 -0
  267. package/dist/cli/commands/sop-advance-command.js +160 -0
  268. package/dist/cli/commands/sop-check-command.d.ts +3 -0
  269. package/dist/cli/commands/sop-check-command.js +44 -0
  270. package/dist/cli/commands/sop-command-shared.d.ts +23 -0
  271. package/dist/cli/commands/sop-command-shared.js +18 -0
  272. package/dist/cli/commands/sop-commands.d.ts +15 -1
  273. package/dist/cli/commands/sop-commands.js +26 -240
  274. package/dist/cli/commands/sop-init-command.d.ts +3 -0
  275. package/dist/cli/commands/sop-init-command.js +51 -0
  276. package/dist/cli/commands/sop-lint-command.d.ts +3 -0
  277. package/dist/cli/commands/sop-lint-command.js +64 -0
  278. package/dist/cli/commands/sop-register-command.d.ts +3 -0
  279. package/dist/cli/commands/sop-register-command.js +48 -0
  280. package/dist/cli/commands/sop-registry-command.d.ts +3 -0
  281. package/dist/cli/commands/sop-registry-command.js +30 -0
  282. package/dist/cli/commands/statusline-command-shared.d.ts +73 -0
  283. package/dist/cli/commands/statusline-command-shared.js +90 -0
  284. package/dist/cli/commands/statusline-commands.d.ts +1 -19
  285. package/dist/cli/commands/statusline-commands.js +48 -287
  286. package/dist/cli/commands/statusline-compact-command.d.ts +4 -0
  287. package/dist/cli/commands/statusline-compact-command.js +54 -0
  288. package/dist/cli/commands/statusline-install-command.d.ts +3 -0
  289. package/dist/cli/commands/statusline-install-command.js +48 -0
  290. package/dist/cli/commands/statusline-render-command.d.ts +9 -0
  291. package/dist/cli/commands/statusline-render-command.js +55 -0
  292. package/dist/cli/commands/statusline-status-command.d.ts +3 -0
  293. package/dist/cli/commands/statusline-status-command.js +23 -0
  294. package/dist/cli/commands/statusline-uninstall-command.d.ts +3 -0
  295. package/dist/cli/commands/statusline-uninstall-command.js +23 -0
  296. package/dist/cli/commands/test-command-run-helpers.d.ts +27 -0
  297. package/dist/cli/commands/test-command-run-helpers.js +73 -0
  298. package/dist/cli/commands/test-command-runner-resolution.d.ts +57 -0
  299. package/dist/cli/commands/test-command-runner-resolution.js +183 -0
  300. package/dist/cli/commands/test-commands.d.ts +8 -56
  301. package/dist/cli/commands/test-commands.js +26 -249
  302. package/dist/cli/commands/vm-command-shared.d.ts +23 -0
  303. package/dist/cli/commands/vm-command-shared.js +23 -0
  304. package/dist/cli/commands/vm-commands.d.ts +1 -28
  305. package/dist/cli/commands/vm-commands.js +36 -270
  306. package/dist/cli/commands/vm-hypervisor-runtime.d.ts +15 -0
  307. package/dist/cli/commands/vm-hypervisor-runtime.js +64 -0
  308. package/dist/cli/commands/vm-release-command.d.ts +5 -0
  309. package/dist/cli/commands/vm-release-command.js +101 -0
  310. package/dist/cli/commands/vm-spawn-command.d.ts +5 -0
  311. package/dist/cli/commands/vm-spawn-command.js +177 -0
  312. package/dist/cli/commands/web-command-shared.d.ts +12 -0
  313. package/dist/cli/commands/web-command-shared.js +30 -0
  314. package/dist/cli/commands/web-commands.d.ts +7 -14
  315. package/dist/cli/commands/web-commands.js +6 -314
  316. package/dist/cli/commands/web-install-command.d.ts +10 -0
  317. package/dist/cli/commands/web-install-command.js +116 -0
  318. package/dist/cli/commands/web-lifecycle-commands.d.ts +9 -30
  319. package/dist/cli/commands/web-lifecycle-commands.js +14 -322
  320. package/dist/cli/commands/web-login-command.d.ts +14 -0
  321. package/dist/cli/commands/web-login-command.js +189 -0
  322. package/dist/cli/commands/web-op-payload.d.ts +22 -0
  323. package/dist/cli/commands/web-op-payload.js +89 -0
  324. package/dist/cli/commands/web-op-refusals.d.ts +37 -0
  325. package/dist/cli/commands/web-op-refusals.js +73 -0
  326. package/dist/cli/commands/web-op-runner.d.ts +14 -0
  327. package/dist/cli/commands/web-op-runner.js +166 -0
  328. package/dist/cli/commands/web-status-command.d.ts +4 -0
  329. package/dist/cli/commands/web-status-command.js +32 -0
  330. package/dist/cli/commands/web-stop-command.d.ts +10 -0
  331. package/dist/cli/commands/web-stop-command.js +59 -0
  332. package/dist/cli/commands/web-verb-specs.d.ts +18 -0
  333. package/dist/cli/commands/web-verb-specs.js +66 -0
  334. package/dist/cli/commands/workflow-command-shared.d.ts +102 -0
  335. package/dist/cli/commands/workflow-command-shared.js +74 -0
  336. package/dist/cli/commands/workflow-graph-commands.d.ts +3 -0
  337. package/dist/cli/commands/workflow-graph-commands.js +73 -0
  338. package/dist/cli/commands/workflow-init-command.d.ts +3 -0
  339. package/dist/cli/commands/workflow-init-command.js +68 -0
  340. package/dist/cli/commands/workflow-lifecycle-commands.d.ts +9 -59
  341. package/dist/cli/commands/workflow-lifecycle-commands.js +14 -348
  342. package/dist/cli/commands/workflow-node-commands.d.ts +3 -0
  343. package/dist/cli/commands/workflow-node-commands.js +154 -0
  344. package/dist/cli/commands/workflow-terminalize-command.d.ts +3 -0
  345. package/dist/cli/commands/workflow-terminalize-command.js +56 -0
  346. package/dist/services/code/auto-compact-artifacts.d.ts +54 -0
  347. package/dist/services/code/auto-compact-artifacts.js +135 -0
  348. package/dist/services/code/auto-compact-attempt.d.ts +46 -0
  349. package/dist/services/code/auto-compact-attempt.js +88 -0
  350. package/dist/services/code/auto-compact-decision.d.ts +74 -0
  351. package/dist/services/code/auto-compact-decision.js +149 -0
  352. package/dist/services/code/auto-compact-dispatch-gate.d.ts +36 -0
  353. package/dist/services/code/auto-compact-dispatch-gate.js +86 -0
  354. package/dist/services/code/auto-compact-envelopes.d.ts +33 -0
  355. package/dist/services/code/auto-compact-envelopes.js +134 -0
  356. package/dist/services/code/auto-compact-history.d.ts +46 -0
  357. package/dist/services/code/auto-compact-history.js +17 -0
  358. package/dist/services/code/auto-compact-orchestrator.d.ts +11 -240
  359. package/dist/services/code/auto-compact-orchestrator.js +165 -686
  360. package/dist/services/code/auto-compact-run-types.d.ts +123 -0
  361. package/dist/services/code/auto-compact-run-types.js +1 -0
  362. package/dist/services/code/emit-handoff.d.ts +35 -1
  363. package/dist/services/code/emit-handoff.js +128 -7
  364. package/dist/services/code/step-08-gate.d.ts +28 -19
  365. package/dist/services/code/step-08-gate.js +92 -35
  366. package/dist/services/config/config-coercion.d.ts +7 -0
  367. package/dist/services/config/config-coercion.js +156 -0
  368. package/dist/services/config/config-file-io.d.ts +11 -0
  369. package/dist/services/config/config-file-io.js +61 -0
  370. package/dist/services/config/config-get.d.ts +2 -0
  371. package/dist/services/config/config-get.js +36 -0
  372. package/dist/services/config/config-global-loader.d.ts +15 -0
  373. package/dist/services/config/config-global-loader.js +111 -0
  374. package/dist/services/config/config-nested-path.d.ts +3 -0
  375. package/dist/services/config/config-nested-path.js +53 -0
  376. package/dist/services/config/config-primitives.d.ts +16 -0
  377. package/dist/services/config/config-primitives.js +24 -0
  378. package/dist/services/config/config-service.d.ts +9 -33
  379. package/dist/services/config/config-service.js +16 -665
  380. package/dist/services/config/config-validation.d.ts +19 -0
  381. package/dist/services/config/config-validation.js +152 -0
  382. package/dist/services/config/config-workspace.d.ts +10 -0
  383. package/dist/services/config/config-workspace.js +158 -0
  384. package/dist/services/skills/hooks-settings-deny-list.d.ts +61 -0
  385. package/dist/services/skills/hooks-settings-deny-list.js +173 -0
  386. package/dist/services/skills/hooks-settings-envelopes.d.ts +20 -0
  387. package/dist/services/skills/hooks-settings-envelopes.js +79 -0
  388. package/dist/services/skills/hooks-settings-file-shape.d.ts +43 -0
  389. package/dist/services/skills/hooks-settings-file-shape.js +207 -0
  390. package/dist/services/skills/hooks-settings-paths.d.ts +34 -0
  391. package/dist/services/skills/hooks-settings-paths.js +75 -0
  392. package/dist/services/skills/hooks-settings-read-installed.d.ts +39 -0
  393. package/dist/services/skills/hooks-settings-read-installed.js +60 -0
  394. package/dist/services/skills/hooks-settings-service.d.ts +12 -212
  395. package/dist/services/skills/hooks-settings-service.js +71 -657
  396. package/dist/services/skills/hooks-settings-types.d.ts +139 -0
  397. package/dist/services/skills/hooks-settings-types.js +1 -0
  398. package/dist/services/workspace/claude-settings-template-merge.d.ts +89 -0
  399. package/dist/services/workspace/claude-settings-template-merge.js +243 -0
  400. package/dist/services/workspace/claude-settings-template.d.ts +29 -87
  401. package/dist/services/workspace/claude-settings-template.js +81 -236
  402. package/package.json +5 -5
@@ -1,100 +1,39 @@
1
1
  import { existsSync } from 'node:fs';
2
2
  import { homedir } from 'node:os';
3
- import { join, resolve } from 'node:path';
4
- import { assertSafeSettingsFile } from '../ide/shared/safe-path.js';
5
- import { atomicWriteJson, readJsonObjectFile } from '../ide/shared/atomic-json.js';
3
+ import { atomicWriteJson } from '../ide/shared/atomic-json.js';
6
4
  import { getAdapter } from '../ide/ide-registry.js';
7
- import { resolveHookSpec, resolveHookEntries, hasHookSpec, resolveLegacySentinels, SUPERPOWERS_DENIED_SKILLS, formatSuperpowersDenyEntry, SUPERPOWERS_DENY_SENTINELS, hasExternalGateExemptions, withExternalGateExemptions, withoutExternalGateExemptions } from './hooks-codegate-superpowers.js';
5
+ import { resolveHookSpec, resolveHookEntries, hasHookSpec, resolveLegacySentinels, hasExternalGateExemptions, withExternalGateExemptions } from './hooks-codegate-superpowers.js';
8
6
  export { HOOK_ENFORCE_SENTINEL, HOOK_CODE_GATE_SENTINEL, HOOK_CODE_GATE_MATCHER, HOOK_CODE_GATE_EVENT, HOOK_CODE_GATE_COMMAND, SUPERPOWERS_DENIED_SKILLS } from './hooks-codegate-superpowers.js';
9
- // --- Module-level defaults (claude-code) -----------------------------------
10
- // These exports remain for backward compat — tests and downstream callers
11
- // that only care about Claude Code can keep importing them. The per-IDE
12
- // values are computed lazily inside each public function call.
13
- /** Default (claude-code) hook command — kept as a stable export for tests. */
7
+ export { readInstalledEntriesFromSettings } from './hooks-settings-read-installed.js';
8
+ export { TRIGGER_PHRASES, listSuperpowersDenyEntries, withSuperpowersSkillDenylist, withTriggeredDenyList, withoutSuperpowersSkillDenylist, withoutTriggeredDenyList } from './hooks-settings-deny-list.js';
9
+ import { assertSafeSettingsPathCompat, resolveLocalSettingsPath, resolveSettingsRoot } from './hooks-settings-paths.js';
10
+ import { describeEntryTargets, isInstalledForEntries, readSettingsFile, targetIsSatisfied, withHooksInstalled } from './hooks-settings-file-shape.js';
11
+ import { applyRemovalToTarget, hookStatusFromTargets, removedResult, unsupportedHookStatus } from './hooks-settings-envelopes.js';
12
+ import { withSuperpowersSkillDenylist, withTriggeredDenyList } from './hooks-settings-deny-list.js';
13
+ /** Default (claude-code) hook command. */
14
14
  export const HOOK_ENFORCE_COMMAND = `peaks gate enforce --project "\${CLAUDE_PROJECT_DIR}" --json`;
15
- function resolveIde(options) {
16
- return options?.ide ?? 'claude-code';
17
- }
18
- /** Slice #013: read the skipProgress opt-in flag. Slice #014: the underlying install no longer emits the progress-start entry, so the flag is effectively a no-op (kept for API stability). */
19
- function resolveSkipProgress(options) {
20
- return options?.skipProgress === true;
21
- }
22
- /** Resolve settings root dir for a scope. */
23
- function resolveSettingsRoot(scope, projectRoot) {
24
- if (scope === 'global')
25
- return resolve(homedir());
26
- if (!projectRoot) {
27
- throw new Error('Project scope requires a project root');
28
- }
29
- return resolve(projectRoot);
30
- }
31
15
  function resolveSettingsPath(scope, ide, projectRoot) {
32
- const root = resolveSettingsRoot(scope, projectRoot);
33
16
  const adapter = getAdapter(ide);
34
17
  return adapter.settings.resolveSettingsFile(scope, scope === 'global' ? homedir() : projectRoot);
35
18
  }
36
- function assertSafeSettingsPathCompat(scope, ide, root, settingsPath) {
37
- const adapter = getAdapter(ide);
38
- assertSafeSettingsFile(scope, root, adapter.settings.dirName, adapter.settings.settingsFileName);
39
- // The compat path receives the already-computed settingsPath; double-check
40
- // that the computed path matches what assertSafeSettingsFile would have
41
- // produced. This guards against drift between the two resolvers.
42
- const expected = adapter.settings.resolveSettingsFile(scope, scope === 'global' ? homedir() : root);
43
- if (expected !== settingsPath) {
44
- throw new Error(`settings path drift: ${expected} vs ${settingsPath}`);
45
- }
19
+ function resolveIde(options) {
20
+ return options?.ide ?? 'claude-code';
46
21
  }
47
22
  /**
48
- * Resolve the machine-local settings file an IDE may carry (e.g. Claude
49
- * Code's `.claude/settings.local.json`), or `undefined` when there is none.
50
- *
51
- * B3(b) 2026-09-13: the machine-local layer is an adapter-DECLARED capability,
52
- * not a name list. It used to be two hardcoded facts here — a `Set(['claude-code'])`
53
- * membership test and the filename literal `'settings.local.json'` — i.e. a
54
- * module that routes per-IDE was itself naming one IDE and one IDE's file.
55
- * Both now read off the adapter that owns them
56
- * (`IdeSettingsLocation.localSettingsFileName`), exactly as
57
- * `auto-compact-dispatcher.ts` already did for the same field.
58
- *
59
- * Behaviour is UNCHANGED, and that was verified rather than assumed: over all
60
- * nine registered `IdeId`s, `IDES_WITH_LOCAL_SETTINGS.has(id)` was `true` for
61
- * exactly the ids whose adapter declares `localSettingsFileName` (claude-code
62
- * alone, `'settings.local.json'`). So the set was an exact duplicate of a
63
- * declaration the adapters already carried, not an independent policy — and
64
- * a new adapter that declares the field now gets this path for free instead
65
- * of being silently denied it.
23
+ * The peaks-managed entries grouped by the settings file that carries each one.
66
24
  *
67
- * This ALSO removes a live instance of the vendor guard's limit 1 (indirect
68
- * identity: `SET.has(ide)`), which no AST check over comparisons or literals
69
- * can see. The guard's header named this exact line as the example.
70
- *
71
- * Global scope resolves to `undefined`: the global settings file
72
- * (`~/.claude/settings.json`) is already machine-local, so nothing needs
73
- * relocating there.
25
+ * Peaks hooks normally land in the adapter's settings file. The gate-enforce Bash
26
+ * entry is the exception: on Windows its `shell` is pinned to `powershell`, because
27
+ * the default Git-Bash shell force-allocates a console window on every Bash tool call.
28
+ * A machine-specific value must never be committed into the SHARED file a macOS/Linux
29
+ * teammate reads, so that entry lands in the machine-local file instead; routing keys
30
+ * off its own `machineLocal` flag, never off `process.platform`.
74
31
  */
75
- function resolveLocalSettingsPath(scope, ide, projectRoot) {
76
- if (scope === 'global')
77
- return undefined;
78
- const adapter = getAdapter(ide);
79
- const localFileName = adapter.settings.localSettingsFileName;
80
- // `undefined` = this IDE has no machine-local layer. Note the order: a
81
- // caller reaching here with an id the registry does not know still throws
82
- // from `getAdapter` above, exactly as it did before — every path into this
83
- // function already called `getAdapter(ide)` via `resolveSettingsPath`, so
84
- // no new throw path is introduced.
85
- if (localFileName === undefined)
86
- return undefined;
87
- const root = resolveSettingsRoot(scope, projectRoot);
88
- assertSafeSettingsFile(scope, root, adapter.settings.dirName, localFileName);
89
- return join(root, adapter.settings.dirName, localFileName);
90
- }
91
32
  function resolveHookTargets(scope, ide, projectRoot) {
92
33
  const sharedPath = resolveSettingsPath(scope, ide, projectRoot);
93
34
  const localPath = resolveLocalSettingsPath(scope, ide, projectRoot);
94
35
  const wantsEnvExemptions = ide === 'claude-code';
95
36
  if (localPath === undefined || localPath === sharedPath) {
96
- // Global scope lands here: the user-level file is already machine-local,
97
- // so it is the safe target (there is no sibling to prefer).
98
37
  return [
99
38
  {
100
39
  settingsPath: sharedPath,
@@ -114,187 +53,32 @@ function resolveHookTargets(scope, ide, projectRoot) {
114
53
  }
115
54
  return [shared, local];
116
55
  }
117
- /** True when `target` already holds everything the install would write to it. */
118
- function targetIsSatisfied(target, allSentinels) {
119
- const settings = readSettingsFile(target.settingsPath);
120
- if (!shapeMatchesDesired(settings, target.entries, allSentinels))
121
- return false;
122
- return target.envExemptions !== true || hasExternalGateExemptions(settings);
123
- }
124
- /** Flatten the resolved targets into one `{ matcher, sentinel, settingsPath }` row per entry. */
125
- function describeEntryTargets(targets) {
126
- return targets.flatMap((target) => target.entries.map((entry) => ({
127
- matcher: entry.matcher,
128
- sentinel: entry.sentinel,
129
- settingsPath: target.settingsPath
130
- })));
131
- }
132
- /** Read a settings file as an object, or `{}` when it does not exist yet. */
133
- function readSettingsFile(settingsPath) {
134
- return existsSync(settingsPath) ? readJsonObjectFile(settingsPath) : {};
135
- }
136
- /** Read the existing hook array entries for the adapter's hookEvent (tolerant of any prior shape). */
137
- function readHookEventEntries(settings, eventKey) {
138
- const hooks = settings.hooks;
139
- if (!hooks || typeof hooks !== 'object' || Array.isArray(hooks))
140
- return [];
141
- const arr = hooks[eventKey];
142
- return Array.isArray(arr) ? arr : [];
143
- }
144
- /** Read the existing hook array entries from a `settings.hooks` object (already extracted). */
145
- function readHookEntriesFromHooks(hooks, eventKey) {
146
- const arr = hooks[eventKey];
147
- return Array.isArray(arr) ? arr : [];
148
- }
149
- /** True when every command handler in the entry matches a known peaks sentinel for the given IDE. */
150
- function entryIsPeaksManaged(entry, sentinels) {
151
- const handlers = Array.isArray(entry?.hooks) ? entry.hooks : [];
152
- if (handlers.length === 0)
153
- return false;
154
- return handlers.every((h) => {
155
- if (typeof h?.command !== 'string')
156
- return false;
157
- const cmd = h.command;
158
- return sentinels.some((sentinel) => cmd.includes(sentinel));
159
- });
160
- }
161
- /**
162
- * Slice #014: read the *actually-installed* peaks-managed hook entries
163
- * from a settings object. Replaces the pre-#014 `listInstalledEntriesForIde`
164
- * helper in `hooks-commands.ts`, which returned the IDE-EXPECTED list
165
- * (a hardcoded 2-entry array per adapter) rather than what was on disk.
166
- * That bug surfaced when slice #013's local cleanup installed
167
- * `peaks hooks install --no-progress` (gate-enforce only), but the
168
- * status command still reported `entries: [Bash, Task]` because the
169
- * helper didn't read the file.
170
- *
171
- * The new helper:
172
- * 1. reads each `hooks.<event>` array,
173
- * 2. filters to entries that are peaks-managed for the given IDE
174
- * (matches the legacy sentinel set: gate-enforce + the no-longer-
175
- * installed progress-start),
176
- * 3. returns one `{ matcher, sentinel }` row per entry, taking the
177
- * FIRST matching sentinel per entry (entries have a single command
178
- * handler in practice, but the loop tolerates multi-handler
179
- * entries by taking the first match).
180
- *
181
- * Pre-#014 settings.json files that have a stale progress-start entry
182
- * will see it surface in the result. This is intentional: the status
183
- * command is the user's tool for "what is on disk right now", and
184
- * surfacing a stale entry is the only way the user can know to run
185
- * `peaks hooks install` (which now strips it) or `peaks hooks
186
- * uninstall` (which removes it).
187
- */
188
- export function readInstalledEntriesFromSettings(settings, ide) {
189
- const sentinels = resolveLegacySentinels(ide);
190
- // Walk every event key the settings file has, not just the
191
- // adapter-declared one. A pre-#014 install could have left a
192
- // progress-start entry on a different event than the gate-enforce
193
- // entry (Trae: both on beforeToolCall, but a stale install on a
194
- // future IDE could split them).
195
- const hooksRoot = settings.hooks;
196
- if (!hooksRoot || typeof hooksRoot !== 'object' || Array.isArray(hooksRoot))
197
- return [];
198
- const result = [];
199
- for (const eventKey of Object.keys(hooksRoot)) {
200
- const entries = readHookEntriesFromHooks(hooksRoot, eventKey);
201
- for (const entry of entries) {
202
- if (!entryIsPeaksManaged(entry, sentinels))
203
- continue;
204
- const matcher = typeof entry.matcher === 'string' ? entry.matcher : '';
205
- // Find the first matching sentinel inside the entry's command
206
- // handlers. For each handler, find the first sentinel substring
207
- // it contains. We pick the first handler's first matching
208
- // sentinel (entries have a single command in practice).
209
- const firstHandler = Array.isArray(entry.hooks) ? entry.hooks[0] : undefined;
210
- const cmd = typeof firstHandler?.command === 'string' ? firstHandler.command : '';
211
- const sentinel = sentinels.find((s) => cmd.includes(s));
212
- if (matcher === '' || sentinel === undefined)
213
- continue;
214
- result.push({ matcher, sentinel });
215
- }
216
- }
217
- return result;
218
- }
219
- /**
220
- * Merge the peaks-managed Skill denylist into the existing settings object.
221
- * Preserves every other `permissions.deny` entry the user (or another tool)
222
- * has written so the install is additive, not destructive. The output is
223
- * always deterministic: the same input always yields the same output,
224
- * regardless of insertion order in the source array.
225
- *
226
- * Defensive behavior:
227
- * - `settings.permissions` is missing or non-object → install creates a
228
- * fresh `permissions: { deny: [...our entries] }` object.
229
- * - `settings.permissions.deny` is missing or non-array → install replaces
230
- * the field with a fresh array containing only our entries.
231
- * - Duplicate entries (any source) are collapsed to one via `new Set`.
232
- *
233
- * The function is pure: it does not mutate the input. Atomic write happens
234
- * at the call site (`applyHookInstall`).
235
- */
236
- export function withSuperpowersSkillDenylist(settings) {
237
- const ourEntries = SUPERPOWERS_DENIED_SKILLS.map(formatSuperpowersDenyEntry);
238
- const permissions = settings.permissions &&
239
- typeof settings.permissions === 'object' &&
240
- !Array.isArray(settings.permissions)
241
- ? settings.permissions
242
- : {};
243
- const existingDeny = Array.isArray(permissions.deny)
244
- ? permissions.deny.filter((d) => typeof d === 'string')
245
- : [];
246
- const otherDeny = existingDeny.filter((d) => !SUPERPOWERS_DENY_SENTINELS.has(d));
247
- const finalDeny = [...new Set([...otherDeny, ...ourEntries])];
56
+ function readInstallEnvelope(scope, projectRoot, options, alreadyInstalled) {
57
+ const ide = resolveIde(options);
58
+ const root = resolveSettingsRoot(scope, projectRoot);
59
+ const settingsPath = resolveSettingsPath(scope, ide, projectRoot);
60
+ assertSafeSettingsPathCompat(scope, ide, root, settingsPath);
61
+ const targets = resolveHookTargets(scope, ide, projectRoot);
62
+ const spec = resolveHookSpec(ide);
63
+ const localTarget = targets.find((t) => t.settingsPath !== settingsPath);
248
64
  return {
249
- ...settings,
250
- permissions: { ...permissions, deny: finalDeny }
65
+ ide,
66
+ targets,
67
+ settingsPath,
68
+ plan: {
69
+ scope,
70
+ settingsPath,
71
+ exists: existsSync(settingsPath),
72
+ // Written here, in the published field order a `--json` caller reads.
73
+ alreadyInstalled: alreadyInstalled(targets, ide),
74
+ desiredCommand: spec.hookEnforceCommand,
75
+ sentinel: spec.hookEnforceSentinel,
76
+ matcher: spec.hookEnforceMatcher,
77
+ ...(localTarget !== undefined ? { localSettingsPath: localTarget.settingsPath } : {}),
78
+ entryTargets: describeEntryTargets(targets)
79
+ }
251
80
  };
252
81
  }
253
- /**
254
- * Inverse of `withSuperpowersSkillDenylist`. Removes every peaks-managed
255
- * `UseSkill(...)` entry from the deny list. If the resulting deny list is
256
- * empty, the `deny` field itself is deleted; if the `permissions` object
257
- * becomes empty, the `permissions` field itself is deleted. This is the
258
- * uninstall path: every field we wrote must be removed exactly, and no
259
- * other field may be touched.
260
- *
261
- * Pure (no mutation).
262
- */
263
- export function withoutSuperpowersSkillDenylist(settings) {
264
- const permissions = settings.permissions &&
265
- typeof settings.permissions === 'object' &&
266
- !Array.isArray(settings.permissions)
267
- ? settings.permissions
268
- : {};
269
- if (!Array.isArray(permissions.deny)) {
270
- return settings;
271
- }
272
- const remainingDeny = permissions.deny
273
- .filter((d) => typeof d === 'string')
274
- .filter((d) => !SUPERPOWERS_DENY_SENTINELS.has(d));
275
- const nextPermissions = { ...permissions };
276
- if (remainingDeny.length > 0) {
277
- nextPermissions.deny = remainingDeny;
278
- }
279
- else {
280
- delete nextPermissions.deny;
281
- }
282
- if (Object.keys(nextPermissions).length === 0) {
283
- // The whole `permissions` object is empty (no deny, no allow, no other
284
- // sub-fields). Drop it to avoid leaving an empty object on disk.
285
- const { permissions: _omit, ...rest } = settings;
286
- return rest;
287
- }
288
- return { ...settings, permissions: nextPermissions };
289
- }
290
- /**
291
- * Read-only introspection. Returns the deny entries that `peaks hooks
292
- * install` would write today. Useful for `peaks hooks status` and the
293
- * dry-run envelope.
294
- */
295
- export function listSuperpowersDenyEntries() {
296
- return SUPERPOWERS_DENIED_SKILLS.map(formatSuperpowersDenyEntry);
297
- }
298
82
  /** Default (claude-code) peaks-managed hook entries — kept as a stable export for tests. Slice #014: only the gate-enforce entry. */
299
83
  export const PEAKS_HOOK_ENTRIES = (() => {
300
84
  const spec = resolveHookSpec('claude-code');
@@ -307,211 +91,30 @@ export const PEAKS_HOOK_ENTRIES = (() => {
307
91
  }
308
92
  ];
309
93
  })();
310
- function isInstalledForEntries(settings, entries) {
311
- const sentinels = entries.map((e) => e.sentinel);
312
- // Check every distinct event key our entries could be on.
313
- const eventKeys = new Set(entries.map((e) => e.event));
314
- for (const eventKey of eventKeys) {
315
- if (readHookEventEntries(settings, eventKey).some((e) => entryIsPeaksManaged(e, sentinels))) {
316
- return true;
317
- }
318
- }
319
- return false;
320
- }
321
- /**
322
- * Slice #014: detect the "stale progress entry after pre-#014 install"
323
- * case. The desired shape is gate-enforce-only. If the file currently
324
- * has a peaks-managed progress-start entry (left behind by a pre-#014
325
- * install), the install is NOT a no-op — it must strip the stale
326
- * entry. This helper returns true exactly when the desired shape is
327
- * fully reflected on disk: gate-enforce present AND no legacy
328
- * progress-start present.
329
- */
330
- function shapeMatchesDesired(settings, entries, allPeaksSentinels) {
331
- const eventKeys = new Set(entries.map((e) => e.event));
332
- for (const eventKey of eventKeys) {
333
- // Only the entries that belong to THIS event key can be expected on it —
334
- // the desired set must be per-event, or a target whose entries span two
335
- // event keys (claude-code: SessionStart + PreToolUse) would never look
336
- // installed and every `peaks hooks install` would rewrite the files.
337
- const desiredSentinels = new Set(entries.filter((e) => e.event === eventKey).map((e) => e.sentinel));
338
- const present = readHookEventEntries(settings, eventKey);
339
- const peaksPresent = present.filter((e) => entryIsPeaksManaged(e, allPeaksSentinels));
340
- // (a) every peaks-managed entry currently on disk must match the
341
- // desired sentinel set (no stale entries the caller wants removed).
342
- for (const entry of peaksPresent) {
343
- const entrySentinels = (entry.hooks ?? [])
344
- .map((h) => allPeaksSentinels.find((s) => String(h.command ?? '').includes(s)))
345
- .filter((s) => Boolean(s));
346
- if (entrySentinels.some((s) => !desiredSentinels.has(s))) {
347
- return false;
348
- }
349
- }
350
- // (b) every desired entry must be on disk AND carry the matcher it declares.
351
- // Presence alone cannot see a WRONG matcher, and a wrong matcher is not
352
- // cosmetic: `''` and `Bash|Task` route the same command to different
353
- // tool sets, so the entry is present while the hook never fires on the
354
- // residual R2): a `PostCompact` entry hand-corrupted to matcher
355
- // `auto|manual` survived `peaks hooks install` unchanged, and the
356
- // installer was structurally unable to repair it.
357
- // An absent matcher reads as `''` — the form Claude Code takes as "every
358
- // source" — so a file written before matchers were explicit converges
359
- // once instead of churning on every install.
360
- for (const desired of entries.filter((e) => e.event === eventKey)) {
361
- // The identity of a desired entry is the PAIR (sentinel, matcher), not the
362
- // sentinel alone: one command may legitimately be installed under several
363
- // matchers (the read-only MCP surface rides `peaks gate enforce` under its
364
- // own matcher set). Matching on the sentinel first found that first entry
365
- // every time, so the second one never looked installed and every install
366
- // rewrote the file. A wrong matcher is still un-repaired — it fails to
367
- // match and the caller rewrites.
368
- const onDisk = peaksPresent.find((entry) => (entry.matcher ?? '') === desired.matcher &&
369
- (entry.hooks ?? []).some((h) => String(h.command ?? '').includes(desired.sentinel)));
370
- if (onDisk === undefined)
371
- return false;
372
- }
373
- }
374
- return true;
375
- }
376
94
  export function planHookInstall(scope, projectRoot, options) {
377
- const ide = resolveIde(options);
378
- const _skipProgress = resolveSkipProgress(options);
379
- const root = resolveSettingsRoot(scope, projectRoot);
380
- const settingsPath = resolveSettingsPath(scope, ide, projectRoot);
381
- assertSafeSettingsPathCompat(scope, ide, root, settingsPath);
382
- const targets = resolveHookTargets(scope, ide, projectRoot);
383
- const exists = existsSync(settingsPath);
384
- const spec = resolveHookSpec(ide);
385
- const localTarget = targets.find((t) => t.settingsPath !== settingsPath);
386
- return {
387
- scope,
388
- settingsPath,
389
- exists,
390
- // The env clause mirrors `applyHookInstall`'s: without it the dry-run would
391
- // claim "nothing to do" about a file the install is going to write.
392
- alreadyInstalled: targets.every((t) => {
393
- const settings = readSettingsFile(t.settingsPath);
394
- return (isInstalledForEntries(settings, t.entries) &&
395
- (t.envExemptions !== true || hasExternalGateExemptions(settings)));
396
- }),
397
- desiredCommand: spec.hookEnforceCommand,
398
- sentinel: spec.hookEnforceSentinel,
399
- matcher: spec.hookEnforceMatcher,
400
- ...(localTarget !== undefined ? { localSettingsPath: localTarget.settingsPath } : {}),
401
- entryTargets: describeEntryTargets(targets)
402
- };
403
- }
404
- /**
405
- * Merge a target's peaks-managed hook entries into `settings`, preserving
406
- * all other keys and hooks. Entries routed to a DIFFERENT settings file are
407
- * not part of `entries`, so the legacy-sentinel filter below strips them
408
- * from this file (that is what migrates a gate-enforce entry that an older
409
- * peaks release wrote into the shared file).
410
- */
411
- function withHooksInstalled(settings, entries, allSentinels) {
412
- const existingHooks = settings.hooks && typeof settings.hooks === 'object' && !Array.isArray(settings.hooks)
413
- ? settings.hooks
414
- : {};
415
- // Our entries may sit on more than one event key. Group by event so each
416
- // event array is independently merged.
417
- const ourByEvent = new Map();
418
- for (const spec of entries) {
419
- const list = ourByEvent.get(spec.event) ?? [];
420
- list.push(spec);
421
- ourByEvent.set(spec.event, list);
422
- }
423
- // Slice #014: the legacy sentinel set includes the progress-start
424
- // sentinel so a pre-#014 install's stale progress-start entry is
425
- // stripped by the filter (the file converges on the new
426
- // gate-enforce-only shape, idempotently). The desired set (passed
427
- // in below) only contains the gate-enforce sentinel.
428
- const nextHooks = { ...existingHooks };
429
- // An event key this file used to carry but whose entries all moved to the
430
- // other file still needs its stale peaks entries stripped.
431
- const eventKeys = new Set([...ourByEvent.keys(), ...Object.keys(existingHooks)]);
432
- for (const eventKey of eventKeys) {
433
- const existing = readHookEntriesFromHooks(nextHooks, eventKey);
434
- const nonPeaks = existing.filter((entry) => !entryIsPeaksManaged(entry, allSentinels));
435
- const ourFormatted = (ourByEvent.get(eventKey) ?? []).map((spec) => ({
436
- matcher: spec.matcher,
437
- hooks: [
438
- {
439
- type: 'command',
440
- command: spec.command,
441
- ...(spec.shell !== undefined ? { shell: spec.shell } : {})
442
- }
443
- ]
444
- }));
445
- const merged = [...nonPeaks, ...ourFormatted];
446
- if (merged.length > 0) {
447
- nextHooks[eventKey] = merged;
448
- }
449
- else {
450
- delete nextHooks[eventKey];
451
- }
452
- }
453
- return {
454
- ...settings,
455
- hooks: nextHooks
456
- };
95
+ // The env clause mirrors `applyHookInstall`'s: without it the dry run claims "nothing
96
+ // to do" about a file the install is about to write.
97
+ return readInstallEnvelope(scope, projectRoot, options, (targets) => targets.every((t) => {
98
+ const settings = readSettingsFile(t.settingsPath);
99
+ return (isInstalledForEntries(settings, t.entries) &&
100
+ (t.envExemptions !== true || hasExternalGateExemptions(settings)));
101
+ })).plan;
457
102
  }
458
103
  export function applyHookInstall(scope, projectRoot, options) {
459
- const ide = resolveIde(options);
460
- const _skipProgress = resolveSkipProgress(options);
461
- const root = resolveSettingsRoot(scope, projectRoot);
462
- const settingsPath = resolveSettingsPath(scope, ide, projectRoot);
463
- assertSafeSettingsPathCompat(scope, ide, root, settingsPath);
464
- const exists = existsSync(settingsPath);
465
- const spec = resolveHookSpec(ide);
466
- const targets = resolveHookTargets(scope, ide, projectRoot);
104
+ // `alreadyInstalled` reflects the FULL desired shape — gate-enforce-only, no stale
105
+ // progress-start entry, the external-gate exemption present. Presence alone is not
106
+ // enough: a file left by an older release would look installed forever.
107
+ const { ide, targets, settingsPath, plan: baseResult } = readInstallEnvelope(scope, projectRoot, options, (t, i) => t.every((target) => targetIsSatisfied(target, resolveLegacySentinels(i))));
467
108
  const allSentinels = resolveLegacySentinels(ide);
468
- const localTarget = targets.find((t) => t.settingsPath !== settingsPath);
469
- // Slice #014: `alreadyInstalled` reflects the FULL desired shape
470
- // (gate-enforce-only + no stale progress-start entry). Pre-#014
471
- // installs that left a progress-start entry behind will be treated
472
- // as not-yet-installed, so the merge strips the stale entry on the
473
- // next install call. This is the only path that converges the file
474
- // on the new shape; pure presence-checks are insufficient.
475
- //
476
- // The external-gate exemption is part of the desired shape too, so a project
477
- // installed by a release that predates it still converges on upgrade.
478
- const alreadyInstalled = targets.every((t) => targetIsSatisfied(t, allSentinels));
479
- const baseResult = {
480
- scope,
481
- settingsPath,
482
- exists,
483
- alreadyInstalled,
484
- desiredCommand: spec.hookEnforceCommand,
485
- sentinel: spec.hookEnforceSentinel,
486
- matcher: spec.hookEnforceMatcher,
487
- ...(localTarget !== undefined ? { localSettingsPath: localTarget.settingsPath } : {}),
488
- entryTargets: describeEntryTargets(targets)
489
- };
490
- if (baseResult.alreadyInstalled) {
109
+ if (baseResult.alreadyInstalled)
491
110
  return { ...baseResult, applied: false };
492
- }
493
- // `permissions.deny` block alongside the gate hook in a single atomic
494
- // write so the two halves of the install are always co-located. The
495
- // helper is idempotent and additive: any user-written deny entries
496
- // are preserved.
497
- //
498
- // withTriggeredDenyList. If the existing settings file has a
499
- // superpowers / git-worktree / podman-run entry (any source),
500
- // the install appends a defensive `Edit(deny-trigger:<phrase>)`
501
- // entry. The chain cannot run raw `git worktree add` (or
502
- // `podman run`) without an explicit `peaks hooks uninstall`
503
- // first. Both helpers are pure + idempotent.
504
- //
505
- // The `permissions.deny` block lives in the adapter's settings file
506
- // only (as it always has) — it is committed, so the deny list is
507
- // shared; the machine-local file carries hook entries plus, for Claude
508
- // Code, the third-party gate exemptions (machine-local by the same
509
- // argument as the hook `shell` pin).
111
+ // The `permissions.deny` block rides the same atomic write as the hook entries, so
112
+ // the two halves of the install are always co-located. It lives in the adapter's
113
+ // settings file only — it is committed, so the deny list is shared.
510
114
  for (const target of targets) {
511
115
  let next = withHooksInstalled(readSettingsFile(target.settingsPath), target.entries, allSentinels);
512
- if (target.envExemptions === true) {
116
+ if (target.envExemptions === true)
513
117
  next = withExternalGateExemptions(next);
514
- }
515
118
  const merged = target.settingsPath === settingsPath
516
119
  ? withTriggeredDenyList(withSuperpowersSkillDenylist(next))
517
120
  : next;
@@ -525,227 +128,38 @@ export function removeHookInstall(scope, projectRoot, options) {
525
128
  const settingsPath = resolveSettingsPath(scope, ide, projectRoot);
526
129
  assertSafeSettingsPathCompat(scope, ide, root, settingsPath);
527
130
  // An IDE with no HOOK_COMMAND_BY_IDE entry can never carry a peaks hook, so
528
- // "remove it" is already true. That is a no-op success, not a failure: the
529
- // post-condition the caller asked for holds. This stays asymmetric with
530
- // `applyHookInstall`, which still fails closed for the same IDEs — reporting
531
- // a successful install there would claim an enforcement that cannot exist.
532
- //
533
- // `resolveHookTargets` below is what throws for these IDEs, so the check has
534
- // to come first. Note `resolveSettingsPath` above still runs: an IDE the
535
- // adapter registry does not know at all remains a hard error.
131
+ // "remove it" is already true: a no-op success, not a failure. This stays
132
+ // asymmetric with `applyHookInstall`, which fails closed for the same IDEs;
133
+ // `resolveHookTargets` below is what throws for them, so the check comes first.
536
134
  if (!hasHookSpec(ide)) {
537
- const localSettingsPath = resolveLocalSettingsPath(scope, ide, projectRoot);
538
- return {
539
- scope,
540
- settingsPath,
541
- removed: false,
542
- ...(localSettingsPath !== undefined && localSettingsPath !== settingsPath
543
- ? { localSettingsPath }
544
- : {})
545
- };
135
+ return removedResult(scope, settingsPath, resolveLocalSettingsPath(scope, ide, projectRoot), false);
546
136
  }
547
137
  const targets = resolveHookTargets(scope, ide, projectRoot);
548
138
  const localTarget = targets.find((t) => t.settingsPath !== settingsPath);
549
139
  const present = targets.filter((t) => existsSync(t.settingsPath));
550
140
  if (present.length === 0) {
551
- return {
552
- scope,
553
- settingsPath,
554
- removed: false,
555
- ...(localTarget !== undefined ? { localSettingsPath: localTarget.settingsPath } : {})
556
- };
141
+ return removedResult(scope, settingsPath, localTarget?.settingsPath, false);
557
142
  }
558
- // Slice #014: uninstall must remove the gate-enforce entry AND any
559
- // legacy progress-start entry that a pre-#014 install left behind.
560
- // The legacy sentinel set covers both shapes so the uninstall
561
- // converges the file on "no peaks-managed entries", regardless of
562
- // what shape the file was in when the user ran uninstall.
143
+ // The legacy sentinel set covers BOTH the gate-enforce and the pre-#014 progress-start
144
+ // entries, so uninstall converges the file however it was shaped.
563
145
  const sentinels = resolveLegacySentinels(ide);
564
146
  let removedAny = false;
565
147
  for (const target of present) {
566
- const settings = readJsonObjectFile(target.settingsPath);
567
- const existingHooks = settings.hooks ?? {};
568
- // Scan every event key on disk as well as the ones we install on:
569
- // entries may have been routed to the other settings file by a
570
- // previous install.
571
- const eventKeys = new Set([
572
- ...target.entries.map((e) => e.event),
573
- ...Object.keys(existingHooks)
574
- ]);
575
- const nextHooks = { ...existingHooks };
576
- for (const eventKey of eventKeys) {
577
- const entries = readHookEntriesFromHooks(nextHooks, eventKey);
578
- const kept = entries.filter((entry) => !entryIsPeaksManaged(entry, sentinels));
579
- if (kept.length !== entries.length)
580
- removedAny = true;
581
- if (kept.length > 0) {
582
- nextHooks[eventKey] = kept;
583
- }
584
- else {
585
- delete nextHooks[eventKey];
586
- }
587
- }
588
- const nextSettings = { ...settings };
589
- if (Object.keys(nextHooks).length > 0) {
590
- nextSettings.hooks = nextHooks;
148
+ if (applyRemovalToTarget(target, sentinels, target.settingsPath === settingsPath)) {
149
+ removedAny = true;
591
150
  }
592
- else {
593
- delete nextSettings.hooks;
594
- }
595
- // Strips the peaks-managed `UseSkill(...)` deny entries alongside the
596
- // hook entries; user-written entries are untouched. The helper self-
597
- // decomposes an empty `permissions` object.
598
- //
599
- // trigger-style deny entries via withoutTriggeredDenyList. The
600
- // chain of helpers is order-independent (each is idempotent and
601
- // additive over the same set of peaks-managed entries).
602
- let finalSettings = nextSettings;
603
- if (target.envExemptions === true) {
604
- finalSettings = withoutExternalGateExemptions(finalSettings);
605
- }
606
- if (target.settingsPath === settingsPath) {
607
- finalSettings = withoutTriggeredDenyList(withoutSuperpowersSkillDenylist(finalSettings));
608
- }
609
- atomicWriteJson(target.settingsPath, finalSettings);
610
151
  }
611
- return {
612
- scope,
613
- settingsPath,
614
- removed: removedAny,
615
- ...(localTarget !== undefined ? { localSettingsPath: localTarget.settingsPath } : {})
616
- };
152
+ return removedResult(scope, settingsPath, localTarget?.settingsPath, removedAny);
617
153
  }
618
154
  export function readHookStatus(scope, projectRoot, options) {
619
155
  const ide = resolveIde(options);
620
156
  const root = resolveSettingsRoot(scope, projectRoot);
621
157
  const settingsPath = resolveSettingsPath(scope, ide, projectRoot);
622
158
  assertSafeSettingsPathCompat(scope, ide, root, settingsPath);
623
- // An IDE with no HOOK_COMMAND_BY_IDE entry has no hook on disk and can never
624
- // have one, so "what is installed?" has a definite answer: nothing. That is a
625
- // no-op success, exactly like `removeHookInstall` — the query ran, the disk
626
- // was read, and the honest reply is `supportsHooks: false, installed: false`.
627
- // This stays asymmetric with `applyHookInstall`, which still fails closed for
628
- // the same IDEs: reporting a successful INSTALL would claim an enforcement
629
- // that cannot exist, whereas reporting a successful STATUS claims nothing.
630
- //
631
- // `resolveHookTargets` below is what throws for these IDEs, so the check has
632
- // to come first. `resolveSettingsPath` above still runs: an IDE the adapter
633
- // registry does not know at all remains a hard error.
159
+ // As in `removeHookInstall`: an IDE with no HOOK_COMMAND_BY_IDE entry has no hook on
160
+ // disk and can never have one, so "what is installed?" has a definite answer — nothing.
634
161
  if (!hasHookSpec(ide)) {
635
- const localSettingsPath = resolveLocalSettingsPath(scope, ide, projectRoot);
636
- return {
637
- scope,
638
- settingsPath,
639
- exists: existsSync(settingsPath),
640
- ...(localSettingsPath !== undefined && localSettingsPath !== settingsPath
641
- ? { localSettingsPath, localExists: existsSync(localSettingsPath) }
642
- : {}),
643
- installed: false,
644
- supportsHooks: false
645
- };
646
- }
647
- const targets = resolveHookTargets(scope, ide, projectRoot);
648
- const exists = existsSync(settingsPath);
649
- const localTarget = targets.find((t) => t.settingsPath !== settingsPath);
650
- return {
651
- scope,
652
- settingsPath,
653
- exists,
654
- ...(localTarget !== undefined
655
- ? {
656
- localSettingsPath: localTarget.settingsPath,
657
- localExists: existsSync(localTarget.settingsPath)
658
- }
659
- : {}),
660
- installed: targets.some((t) => {
661
- const settings = readSettingsFile(t.settingsPath);
662
- return Object.keys(settings).length > 0 && isInstalledForEntries(settings, t.entries);
663
- }),
664
- supportsHooks: true
665
- };
666
- }
667
- /**
668
- *
669
- * In addition to the static SUPERPOWERS_DENIED_SKILLS list, peaks
670
- * scans the existing settings file for "trigger phrases" that
671
- * indicate the user has configured the superpowers chain (or
672
- * any other auto-run worktree-creating tool) and appends a
673
- * defensive deny to prevent the chain from running raw
674
- * `git worktree add` (or `podman run`) directly.
675
- *
676
- * Triggers detected (substring match on the existing
677
- * permissions.deny / permissions.allow list):
678
- *
679
- * - `superpowers:using-git-worktrees` — the chain's ground zero
680
- * - `superpowers:brainstorming` etc. — the upstream skill
681
- * - `Bash(git worktree add` — the user has explicitly
682
- * allowed raw git worktree, which our L2 lease-aware gate
683
- * would also intercept (defense in depth)
684
- *
685
- * Append-only — never replaces, never re-orders, never removes
686
- * existing entries. Idempotent: re-running the install with
687
- * the same input yields the same output.
688
- */
689
- export const TRIGGER_PHRASES = Object.freeze([
690
- 'superpowers:using-git-worktrees',
691
- 'superpowers:brainstorming',
692
- 'superpowers:writing-plans',
693
- 'superpowers:subagent-driven-development',
694
- 'Bash(git worktree add',
695
- 'Bash(podman run'
696
- ]);
697
- /**
698
- * Format a trigger-phrase into a stable `Edit` deny entry. We deny
699
- * `Edit` (not `Bash`) on the *settings file* itself, so the
700
- * user cannot edit the trigger away without an explicit
701
- * `peaks hooks uninstall` first. The skill name (when
702
- * applicable) is the value after the colon.
703
- */
704
- function formatTriggerDenyEntry(phrase) {
705
- return `Edit(deny-trigger:${phrase})`;
706
- }
707
- export function withTriggeredDenyList(settings) {
708
- const existingDeny = Array.isArray(settings.permissions?.deny)
709
- ? settings.permissions.deny.filter((d) => typeof d === 'string')
710
- : [];
711
- // Detect triggers in the existing allow + deny lists.
712
- const existingAllow = Array.isArray(settings.permissions?.allow)
713
- ? settings.permissions.allow.filter((d) => typeof d === 'string')
714
- : [];
715
- const haystack = [...existingDeny, ...existingAllow].join('|');
716
- const triggered = TRIGGER_PHRASES.filter((p) => haystack.includes(p));
717
- if (triggered.length === 0)
718
- return settings;
719
- const triggeredEntries = triggered.map(formatTriggerDenyEntry);
720
- const permissions = settings.permissions &&
721
- typeof settings.permissions === 'object' &&
722
- !Array.isArray(settings.permissions)
723
- ? settings.permissions
724
- : {};
725
- const otherDeny = existingDeny.filter((d) => !triggeredEntries.includes(d));
726
- return {
727
- ...settings,
728
- permissions: { ...permissions, deny: [...new Set([...otherDeny, ...triggeredEntries])] }
729
- };
730
- }
731
- /** Inverse of withTriggeredDenyList: strip every deny-trigger entry. */
732
- export function withoutTriggeredDenyList(settings) {
733
- const permissions = settings.permissions &&
734
- typeof settings.permissions === 'object' &&
735
- !Array.isArray(settings.permissions)
736
- ? settings.permissions
737
- : {};
738
- const existingDeny = Array.isArray(permissions.deny)
739
- ? permissions.deny.filter((d) => typeof d === 'string')
740
- : [];
741
- const kept = existingDeny.filter((d) => !d.startsWith('Edit(deny-trigger:'));
742
- const next = { ...permissions, deny: kept };
743
- if (kept.length === 0)
744
- delete next.deny;
745
- if (Object.keys(next).length === 0) {
746
- const out = { ...settings };
747
- delete out.permissions;
748
- return out;
162
+ return unsupportedHookStatus(scope, settingsPath, resolveLocalSettingsPath(scope, ide, projectRoot));
749
163
  }
750
- return { ...settings, permissions: next };
164
+ return hookStatusFromTargets(scope, settingsPath, resolveHookTargets(scope, ide, projectRoot));
751
165
  }