peaks-loop 4.1.2 → 4.1.3

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 (323) hide show
  1. package/.claude-plugin/marketplace.json +136 -47
  2. package/CHANGELOG.md +56 -0
  3. package/README-en.md +1 -1
  4. package/README.md +1 -1
  5. package/contracts/readonly-argv-whitelist.json +211 -0
  6. package/dist/cli/commands/audit-artifact-runners.d.ts +16 -0
  7. package/dist/cli/commands/audit-artifact-runners.js +170 -0
  8. package/dist/cli/commands/audit-command-shared.d.ts +97 -0
  9. package/dist/cli/commands/audit-command-shared.js +60 -0
  10. package/dist/cli/commands/audit-commands.d.ts +7 -31
  11. package/dist/cli/commands/audit-commands.js +64 -473
  12. package/dist/cli/commands/audit-goal-runners.d.ts +17 -0
  13. package/dist/cli/commands/audit-goal-runners.js +114 -0
  14. package/dist/cli/commands/audit-red-lines-runners.d.ts +11 -0
  15. package/dist/cli/commands/audit-red-lines-runners.js +43 -0
  16. package/dist/cli/commands/audit-refusal-data.d.ts +23 -0
  17. package/dist/cli/commands/audit-refusal-data.js +69 -0
  18. package/dist/cli/commands/audit-static-runners.d.ts +13 -0
  19. package/dist/cli/commands/audit-static-runners.js +139 -0
  20. package/dist/cli/commands/await-runners.d.ts +11 -0
  21. package/dist/cli/commands/await-runners.js +148 -0
  22. package/dist/cli/commands/code-auto-compact-command.d.ts +3 -0
  23. package/dist/cli/commands/code-auto-compact-command.js +124 -0
  24. package/dist/cli/commands/code-auto-compact-envelope.d.ts +22 -0
  25. package/dist/cli/commands/code-auto-compact-envelope.js +39 -0
  26. package/dist/cli/commands/code-context-audit-command.d.ts +3 -0
  27. package/dist/cli/commands/code-context-audit-command.js +68 -0
  28. package/dist/cli/commands/code-context-now-command.d.ts +3 -0
  29. package/dist/cli/commands/code-context-now-command.js +124 -0
  30. package/dist/cli/commands/code-context-now-envelope.d.ts +66 -0
  31. package/dist/cli/commands/code-context-now-envelope.js +136 -0
  32. package/dist/cli/commands/code-context-now-mapping.d.ts +27 -0
  33. package/dist/cli/commands/code-context-now-mapping.js +43 -0
  34. package/dist/cli/commands/code-emit-handoff-command.d.ts +3 -0
  35. package/dist/cli/commands/code-emit-handoff-command.js +99 -0
  36. package/dist/cli/commands/code-gate-step-08-command.d.ts +3 -0
  37. package/dist/cli/commands/code-gate-step-08-command.js +139 -0
  38. package/dist/cli/commands/code-post-compact-detect-command.d.ts +3 -0
  39. package/dist/cli/commands/code-post-compact-detect-command.js +50 -0
  40. package/dist/cli/commands/code-runtime-commands.d.ts +9 -43
  41. package/dist/cli/commands/code-runtime-commands.js +20 -672
  42. package/dist/cli/commands/code-runtime-session.d.ts +1 -0
  43. package/dist/cli/commands/code-runtime-session.js +15 -0
  44. package/dist/cli/commands/compact-command.d.ts +6 -2
  45. package/dist/cli/commands/compact-command.js +12 -559
  46. package/dist/cli/commands/compact-dry-run-command.d.ts +3 -0
  47. package/dist/cli/commands/compact-dry-run-command.js +87 -0
  48. package/dist/cli/commands/compact-force-command.d.ts +3 -0
  49. package/dist/cli/commands/compact-force-command.js +98 -0
  50. package/dist/cli/commands/compact-harness-window-command.d.ts +3 -0
  51. package/dist/cli/commands/compact-harness-window-command.js +174 -0
  52. package/dist/cli/commands/compact-history-command.d.ts +3 -0
  53. package/dist/cli/commands/compact-history-command.js +89 -0
  54. package/dist/cli/commands/compact-read-commands.d.ts +3 -0
  55. package/dist/cli/commands/compact-read-commands.js +123 -0
  56. package/dist/cli/commands/compact-settle-command.d.ts +3 -0
  57. package/dist/cli/commands/compact-settle-command.js +130 -0
  58. package/dist/cli/commands/compact-shared.d.ts +9 -0
  59. package/dist/cli/commands/compact-shared.js +26 -0
  60. package/dist/cli/commands/core/skill-command.d.ts +1 -44
  61. package/dist/cli/commands/core/skill-command.js +13 -541
  62. package/dist/cli/commands/core/skill-context-verdict.d.ts +38 -0
  63. package/dist/cli/commands/core/skill-context-verdict.js +63 -0
  64. package/dist/cli/commands/core/skill-generated-config-notice.d.ts +41 -0
  65. package/dist/cli/commands/core/skill-generated-config-notice.js +75 -0
  66. package/dist/cli/commands/core/skill-heartbeat-commands.d.ts +3 -0
  67. package/dist/cli/commands/core/skill-heartbeat-commands.js +50 -0
  68. package/dist/cli/commands/core/skill-presence-commands.d.ts +3 -0
  69. package/dist/cli/commands/core/skill-presence-commands.js +123 -0
  70. package/dist/cli/commands/core/skill-presence-housekeeping-commands.d.ts +3 -0
  71. package/dist/cli/commands/core/skill-presence-housekeeping-commands.js +165 -0
  72. package/dist/cli/commands/core/skill-registry-commands.d.ts +3 -0
  73. package/dist/cli/commands/core/skill-registry-commands.js +117 -0
  74. package/dist/cli/commands/core/skill-runbook-command.d.ts +3 -0
  75. package/dist/cli/commands/core/skill-runbook-command.js +29 -0
  76. package/dist/cli/commands/dispatch-command-spec.d.ts +2 -0
  77. package/dist/cli/commands/dispatch-command-spec.js +62 -0
  78. package/dist/cli/commands/dispatch-commands.js +159 -775
  79. package/dist/cli/commands/dispatch-detached-branch.d.ts +3 -0
  80. package/dist/cli/commands/dispatch-detached-branch.js +58 -0
  81. package/dist/cli/commands/dispatch-envelope.d.ts +36 -0
  82. package/dist/cli/commands/dispatch-envelope.js +104 -0
  83. package/dist/cli/commands/dispatch-isolation-lease.d.ts +17 -0
  84. package/dist/cli/commands/dispatch-isolation-lease.js +149 -0
  85. package/dist/cli/commands/dispatch-preflight.d.ts +7 -0
  86. package/dist/cli/commands/dispatch-preflight.js +105 -0
  87. package/dist/cli/commands/dispatch-prompt-blocks.d.ts +10 -0
  88. package/dist/cli/commands/dispatch-prompt-blocks.js +56 -0
  89. package/dist/cli/commands/dispatch-prompt-composition.d.ts +30 -0
  90. package/dist/cli/commands/dispatch-prompt-composition.js +171 -0
  91. package/dist/cli/commands/dispatch-record-write.d.ts +24 -0
  92. package/dist/cli/commands/dispatch-record-write.js +96 -0
  93. package/dist/cli/commands/dispatch-tool-call.d.ts +15 -0
  94. package/dist/cli/commands/dispatch-tool-call.js +128 -0
  95. package/dist/cli/commands/evolution-command-shared.d.ts +78 -0
  96. package/dist/cli/commands/evolution-command-shared.js +107 -0
  97. package/dist/cli/commands/evolution-commands.d.ts +11 -1
  98. package/dist/cli/commands/evolution-commands.js +19 -350
  99. package/dist/cli/commands/evolution-evaluate-command.d.ts +24 -0
  100. package/dist/cli/commands/evolution-evaluate-command.js +138 -0
  101. package/dist/cli/commands/evolution-propose-command.d.ts +23 -0
  102. package/dist/cli/commands/evolution-propose-command.js +145 -0
  103. package/dist/cli/commands/evolution-status-command.d.ts +17 -0
  104. package/dist/cli/commands/evolution-status-command.js +41 -0
  105. package/dist/cli/commands/evolution-verdict-commands.d.ts +30 -0
  106. package/dist/cli/commands/evolution-verdict-commands.js +100 -0
  107. package/dist/cli/commands/finalize-accumulator.d.ts +44 -0
  108. package/dist/cli/commands/finalize-accumulator.js +55 -0
  109. package/dist/cli/commands/finalize-branches.d.ts +34 -0
  110. package/dist/cli/commands/finalize-branches.js +119 -0
  111. package/dist/cli/commands/finalize-runners.d.ts +3 -0
  112. package/dist/cli/commands/finalize-runners.js +92 -0
  113. package/dist/cli/commands/finalize-selection.d.ts +65 -0
  114. package/dist/cli/commands/finalize-selection.js +35 -0
  115. package/dist/cli/commands/gate-commands.js +26 -8
  116. package/dist/cli/commands/job-checkpoint-commands.d.ts +3 -0
  117. package/dist/cli/commands/job-checkpoint-commands.js +157 -0
  118. package/dist/cli/commands/job-command-shared.d.ts +109 -0
  119. package/dist/cli/commands/job-command-shared.js +72 -0
  120. package/dist/cli/commands/job-commands.d.ts +4 -64
  121. package/dist/cli/commands/job-commands.js +42 -552
  122. package/dist/cli/commands/job-cost-check-command.d.ts +3 -0
  123. package/dist/cli/commands/job-cost-check-command.js +41 -0
  124. package/dist/cli/commands/job-init-status-commands.d.ts +3 -0
  125. package/dist/cli/commands/job-init-status-commands.js +149 -0
  126. package/dist/cli/commands/job-run-commands.d.ts +3 -0
  127. package/dist/cli/commands/job-run-commands.js +97 -0
  128. package/dist/cli/commands/job-state-root.d.ts +35 -0
  129. package/dist/cli/commands/job-state-root.js +76 -0
  130. package/dist/cli/commands/loop-eval-capture-index.d.ts +4 -0
  131. package/dist/cli/commands/loop-eval-capture-index.js +44 -0
  132. package/dist/cli/commands/loop-eval-commands.d.ts +8 -5
  133. package/dist/cli/commands/loop-eval-commands.js +12 -626
  134. package/dist/cli/commands/loop-eval-eval-command.d.ts +3 -0
  135. package/dist/cli/commands/loop-eval-eval-command.js +168 -0
  136. package/dist/cli/commands/loop-eval-monotonic-command.d.ts +3 -0
  137. package/dist/cli/commands/loop-eval-monotonic-command.js +90 -0
  138. package/dist/cli/commands/loop-eval-parents.d.ts +7 -0
  139. package/dist/cli/commands/loop-eval-parents.js +20 -0
  140. package/dist/cli/commands/loop-eval-run-code.d.ts +7 -0
  141. package/dist/cli/commands/loop-eval-run-code.js +40 -0
  142. package/dist/cli/commands/loop-eval-run-command.d.ts +3 -0
  143. package/dist/cli/commands/loop-eval-run-command.js +113 -0
  144. package/dist/cli/commands/loop-eval-spec-commands.d.ts +3 -0
  145. package/dist/cli/commands/loop-eval-spec-commands.js +165 -0
  146. package/dist/cli/commands/loop-eval-spec-lint-command.d.ts +3 -0
  147. package/dist/cli/commands/loop-eval-spec-lint-command.js +42 -0
  148. package/dist/cli/commands/loop-eval-workflow-commands.d.ts +3 -0
  149. package/dist/cli/commands/loop-eval-workflow-commands.js +141 -0
  150. package/dist/cli/commands/mcp-commands.d.ts +10 -0
  151. package/dist/cli/commands/mcp-commands.js +157 -0
  152. package/dist/cli/commands/request-command-options.d.ts +49 -0
  153. package/dist/cli/commands/request-command-options.js +1 -0
  154. package/dist/cli/commands/request-commands.d.ts +3 -9
  155. package/dist/cli/commands/request-commands.js +17 -522
  156. package/dist/cli/commands/request-init-command.d.ts +3 -0
  157. package/dist/cli/commands/request-init-command.js +100 -0
  158. package/dist/cli/commands/request-lint-command.d.ts +3 -0
  159. package/dist/cli/commands/request-lint-command.js +50 -0
  160. package/dist/cli/commands/request-list-command.d.ts +10 -0
  161. package/dist/cli/commands/request-list-command.js +53 -0
  162. package/dist/cli/commands/request-repair-status-command.d.ts +3 -0
  163. package/dist/cli/commands/request-repair-status-command.js +61 -0
  164. package/dist/cli/commands/request-show-command.d.ts +3 -0
  165. package/dist/cli/commands/request-show-command.js +56 -0
  166. package/dist/cli/commands/request-transition-command.d.ts +3 -0
  167. package/dist/cli/commands/request-transition-command.js +122 -0
  168. package/dist/cli/commands/request-transition-failures.d.ts +8 -0
  169. package/dist/cli/commands/request-transition-failures.js +90 -0
  170. package/dist/cli/commands/request-transition-hooks.d.ts +50 -0
  171. package/dist/cli/commands/request-transition-hooks.js +115 -0
  172. package/dist/cli/commands/request-transition-preflight.d.ts +10 -0
  173. package/dist/cli/commands/request-transition-preflight.js +107 -0
  174. package/dist/cli/commands/scan-command-shared.d.ts +96 -0
  175. package/dist/cli/commands/scan-command-shared.js +83 -0
  176. package/dist/cli/commands/scan-commands.d.ts +3 -2
  177. package/dist/cli/commands/scan-commands.js +23 -463
  178. package/dist/cli/commands/scan-inventory-commands.d.ts +3 -0
  179. package/dist/cli/commands/scan-inventory-commands.js +96 -0
  180. package/dist/cli/commands/scan-project-commands.d.ts +3 -0
  181. package/dist/cli/commands/scan-project-commands.js +65 -0
  182. package/dist/cli/commands/scan-scope-commands.d.ts +3 -0
  183. package/dist/cli/commands/scan-scope-commands.js +149 -0
  184. package/dist/cli/commands/scan-scope-helpers.d.ts +8 -0
  185. package/dist/cli/commands/scan-scope-helpers.js +54 -0
  186. package/dist/cli/commands/scan-structural-commands.d.ts +3 -0
  187. package/dist/cli/commands/scan-structural-commands.js +113 -0
  188. package/dist/cli/commands/share-commands.d.ts +12 -68
  189. package/dist/cli/commands/share-commands.js +47 -556
  190. package/dist/cli/commands/share-error-hints.d.ts +14 -0
  191. package/dist/cli/commands/share-error-hints.js +38 -0
  192. package/dist/cli/commands/share-identity.d.ts +13 -0
  193. package/dist/cli/commands/share-identity.js +22 -0
  194. package/dist/cli/commands/share-record-paths.d.ts +37 -0
  195. package/dist/cli/commands/share-record-paths.js +68 -0
  196. package/dist/cli/commands/share-runners.d.ts +3 -0
  197. package/dist/cli/commands/share-runners.js +158 -0
  198. package/dist/cli/commands/shared-read-runners.d.ts +3 -0
  199. package/dist/cli/commands/shared-read-runners.js +51 -0
  200. package/dist/cli/commands/slice-check-command.d.ts +3 -0
  201. package/dist/cli/commands/slice-check-command.js +54 -0
  202. package/dist/cli/commands/slice-commands.d.ts +2 -22
  203. package/dist/cli/commands/slice-commands.js +18 -517
  204. package/dist/cli/commands/slice-decompose-artifacts.d.ts +18 -0
  205. package/dist/cli/commands/slice-decompose-artifacts.js +79 -0
  206. package/dist/cli/commands/slice-decompose-command.d.ts +3 -0
  207. package/dist/cli/commands/slice-decompose-command.js +123 -0
  208. package/dist/cli/commands/slice-list-commands.d.ts +3 -0
  209. package/dist/cli/commands/slice-list-commands.js +182 -0
  210. package/dist/cli/commands/slice-pick-command.d.ts +3 -0
  211. package/dist/cli/commands/slice-pick-command.js +60 -0
  212. package/dist/cli/commands/slice-plan-command.d.ts +24 -0
  213. package/dist/cli/commands/slice-plan-command.js +111 -0
  214. package/dist/cli/commands/workflow-commands.d.ts +10 -1
  215. package/dist/cli/commands/workflow-commands.js +6 -594
  216. package/dist/cli/commands/workflow-gate-commands.d.ts +3 -0
  217. package/dist/cli/commands/workflow-gate-commands.js +116 -0
  218. package/dist/cli/commands/workflow-plan-helpers.d.ts +48 -0
  219. package/dist/cli/commands/workflow-plan-helpers.js +103 -0
  220. package/dist/cli/commands/workflow-planning-commands.d.ts +3 -0
  221. package/dist/cli/commands/workflow-planning-commands.js +56 -0
  222. package/dist/cli/commands/workflow-swarm-actions.d.ts +7 -0
  223. package/dist/cli/commands/workflow-swarm-actions.js +109 -0
  224. package/dist/cli/commands/workflow-swarm-commands.d.ts +3 -0
  225. package/dist/cli/commands/workflow-swarm-commands.js +138 -0
  226. package/dist/cli/commands/workflow-tech-actions.d.ts +10 -0
  227. package/dist/cli/commands/workflow-tech-actions.js +150 -0
  228. package/dist/cli/commands/workspace/init-actions.d.ts +10 -0
  229. package/dist/cli/commands/workspace/init-actions.js +199 -0
  230. package/dist/cli/commands/workspace/init-codegraph-stake.d.ts +10 -0
  231. package/dist/cli/commands/workspace/init-codegraph-stake.js +54 -0
  232. package/dist/cli/commands/workspace/init-command.d.ts +8 -124
  233. package/dist/cli/commands/workspace/init-command.js +59 -623
  234. package/dist/cli/commands/workspace/init-context.d.ts +58 -0
  235. package/dist/cli/commands/workspace/init-context.js +121 -0
  236. package/dist/cli/commands/workspace/init-envelope.d.ts +3 -0
  237. package/dist/cli/commands/workspace/init-envelope.js +34 -0
  238. package/dist/cli/commands/workspace/init-failures.d.ts +3 -0
  239. package/dist/cli/commands/workspace/init-failures.js +84 -0
  240. package/dist/cli/commands/workspace/init-hooks-decision.d.ts +24 -0
  241. package/dist/cli/commands/workspace/init-hooks-decision.js +189 -0
  242. package/dist/cli/commands/workspace/init-options.d.ts +102 -0
  243. package/dist/cli/commands/workspace/init-options.js +1 -0
  244. package/dist/cli/commands/worktree-lease-commands.d.ts +10 -6
  245. package/dist/cli/commands/worktree-lease-commands.js +42 -512
  246. package/dist/cli/commands/worktree-lease-gc-command.d.ts +12 -0
  247. package/dist/cli/commands/worktree-lease-gc-command.js +117 -0
  248. package/dist/cli/commands/worktree-lease-git.d.ts +22 -0
  249. package/dist/cli/commands/worktree-lease-git.js +48 -0
  250. package/dist/cli/commands/worktree-lease-list-command.d.ts +12 -0
  251. package/dist/cli/commands/worktree-lease-list-command.js +100 -0
  252. package/dist/cli/commands/worktree-lease-load.d.ts +37 -0
  253. package/dist/cli/commands/worktree-lease-load.js +28 -0
  254. package/dist/cli/commands/worktree-lease-release-command.d.ts +13 -0
  255. package/dist/cli/commands/worktree-lease-release-command.js +105 -0
  256. package/dist/cli/commands/worktree-lease-renew-command.d.ts +11 -0
  257. package/dist/cli/commands/worktree-lease-renew-command.js +99 -0
  258. package/dist/cli/commands/worktree-lease-session.d.ts +22 -0
  259. package/dist/cli/commands/worktree-lease-session.js +29 -0
  260. package/dist/cli/commands/worktree-lease-spawn-command.d.ts +11 -0
  261. package/dist/cli/commands/worktree-lease-spawn-command.js +119 -0
  262. package/dist/cli/commands/worktree-lease-status-command.d.ts +11 -0
  263. package/dist/cli/commands/worktree-lease-status-command.js +86 -0
  264. package/dist/cli/commands/worktree-lease-store.d.ts +19 -0
  265. package/dist/cli/commands/worktree-lease-store.js +24 -0
  266. package/dist/cli/program.js +5 -0
  267. package/dist/services/distribution/mcp-install-runner.d.ts +21 -0
  268. package/dist/services/distribution/mcp-install-runner.js +59 -0
  269. package/dist/services/distribution/mcp-install.d.ts +95 -0
  270. package/dist/services/distribution/mcp-install.js +179 -0
  271. package/dist/services/distribution/plugin-manifest.d.ts +97 -0
  272. package/dist/services/distribution/plugin-manifest.js +126 -0
  273. package/dist/services/hooks/mcp-surface-gate.d.ts +19 -0
  274. package/dist/services/hooks/mcp-surface-gate.js +87 -0
  275. package/dist/services/ide/adapters/claude-code-adapter.d.ts +4 -35
  276. package/dist/services/ide/adapters/claude-code-adapter.js +48 -171
  277. package/dist/services/ide/adapters/claude-code-local-state.d.ts +28 -0
  278. package/dist/services/ide/adapters/claude-code-local-state.js +112 -0
  279. package/dist/services/ide/adapters/claude-code-model-window.d.ts +37 -0
  280. package/dist/services/ide/adapters/claude-code-model-window.js +113 -0
  281. package/dist/services/ide/ide-mcp-install-types.d.ts +78 -0
  282. package/dist/services/ide/ide-mcp-install-types.js +104 -0
  283. package/dist/services/ide/ide-types.d.ts +45 -1
  284. package/dist/services/ide/mcp-tool-matcher.d.ts +34 -0
  285. package/dist/services/ide/mcp-tool-matcher.js +84 -0
  286. package/dist/services/lint/npx-resolver.js +7 -0
  287. package/dist/services/mcp/cli-executor.d.ts +36 -0
  288. package/dist/services/mcp/cli-executor.js +97 -0
  289. package/dist/services/mcp/json-rpc.d.ts +81 -0
  290. package/dist/services/mcp/json-rpc.js +116 -0
  291. package/dist/services/mcp/memory-tool.d.ts +3 -0
  292. package/dist/services/mcp/memory-tool.js +62 -0
  293. package/dist/services/mcp/server-main.d.ts +1 -0
  294. package/dist/services/mcp/server-main.js +66 -0
  295. package/dist/services/mcp/server.d.ts +22 -0
  296. package/dist/services/mcp/server.js +152 -0
  297. package/dist/services/mcp/surface.d.ts +62 -0
  298. package/dist/services/mcp/surface.js +198 -0
  299. package/dist/services/mcp/tool-core.d.ts +42 -0
  300. package/dist/services/mcp/tool-core.js +137 -0
  301. package/dist/services/mcp/tools.d.ts +13 -0
  302. package/dist/services/mcp/tools.js +58 -0
  303. package/dist/services/readonly-surface/argv-guard.d.ts +32 -0
  304. package/dist/services/readonly-surface/argv-guard.js +164 -0
  305. package/dist/services/readonly-surface/readonly-whitelist.d.ts +147 -0
  306. package/dist/services/readonly-surface/readonly-whitelist.js +107 -0
  307. package/dist/services/readonly-surface/whitelist-generator.d.ts +64 -0
  308. package/dist/services/readonly-surface/whitelist-generator.js +212 -0
  309. package/dist/services/skills/hooks-codegate-superpowers.d.ts +1 -0
  310. package/dist/services/skills/hooks-codegate-superpowers.js +26 -0
  311. package/dist/services/skills/hooks-settings-service.js +10 -2
  312. package/package.json +17 -6
  313. package/scripts/install-skills-bundles.mjs +233 -0
  314. package/scripts/install-skills-fs.mjs +148 -0
  315. package/scripts/install-skills-ide.mjs +279 -0
  316. package/scripts/install-skills-output-style-default.mjs +196 -0
  317. package/scripts/install-skills-output-styles.mjs +121 -0
  318. package/scripts/install-skills-report.mjs +42 -0
  319. package/scripts/install-skills-user-config.mjs +198 -0
  320. package/scripts/install-skills.mjs +36 -1066
  321. package/scripts/watch-tree.mjs +140 -0
  322. package/scripts/watch.mjs +4 -126
  323. package/skills/peaks-status/SKILL.md +31 -13
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Canonicalize a user-supplied `--project <path>` value.
3
+ *
4
+ * Git Bash on Windows hands us forward-slash paths
5
+ * (`C:/Users/.../peaks-loop`) while `peaks workspace init` writes the
6
+ * backslash form, and either side may carry a trailing separator or
7
+ * differing case. Resolving to the real path here means every
8
+ * downstream consumer (`getSessionId`, `setSessionMeta`,
9
+ * `setSkillPresence`) sees one stable form.
10
+ *
11
+ * Returns the input unchanged when it cannot be resolved (path does
12
+ * not exist yet, or is not readable) so a bad `--project` still
13
+ * reaches the existing error handling rather than throwing here.
14
+ */
15
+ export declare function canonicalizeProjectOption(project: string | undefined): string | undefined;
16
+ /**
17
+ * D1 (2026-09-15) — generated-config staleness, carried on `skill presence`.
18
+ *
19
+ * WHY THIS CALL. `npm i -g peaks-loop@<newer>` upgrades the CLI and leaves the
20
+ * project's generated config exactly as the OLD release wrote it:
21
+ * `initWorkspace` is the only writer and `ensureSession` early-returns once a
22
+ * session is bound, so nothing re-runs the generator. `.claude/settings.local.json`
23
+ * is drift-checked — but only on an init that never comes. The user found the
24
+ * previous instance of this by deleting their `.claude/*.json` and restarting;
25
+ * nothing in the product told them to.
26
+ *
27
+ * `peaks skill presence` is the ONE peaks call every skill makes in every turn
28
+ * (CLAUDE.md mandates it at the start of every response), so it is the only
29
+ * channel guaranteed to carry a drift notice to the LLM that can act on it —
30
+ * the same reasoning as the loop-hygiene verdict attached one screen down. A
31
+ * rule that lives only in a SKILL.md body is compacted away; a warning that
32
+ * rides the per-turn tool output is not.
33
+ *
34
+ * The field is additive and present only when stale, so no existing consumer
35
+ * of the envelope changes shape. `null` projectRoot means the caller had no
36
+ * project to inspect — skipped, not assumed stale.
37
+ */
38
+ export declare function generatedConfigNotice(projectRoot: string | undefined): {
39
+ field: Record<string, unknown>;
40
+ warnings: string[];
41
+ };
@@ -0,0 +1,75 @@
1
+ // Split out of `core/skill-command.ts`:
2
+ // `--project` canonicalization plus the D1 generated-config staleness notice
3
+ // that rides the per-turn `peaks skill presence` read.
4
+ import { stableRealPath } from '../../../shared/path-utils.js';
5
+ import { detectStaleGeneratedArtifacts } from '../../../services/workspace/generated-artifacts-stamp.js';
6
+ /**
7
+ * Canonicalize a user-supplied `--project <path>` value.
8
+ *
9
+ * Git Bash on Windows hands us forward-slash paths
10
+ * (`C:/Users/.../peaks-loop`) while `peaks workspace init` writes the
11
+ * backslash form, and either side may carry a trailing separator or
12
+ * differing case. Resolving to the real path here means every
13
+ * downstream consumer (`getSessionId`, `setSessionMeta`,
14
+ * `setSkillPresence`) sees one stable form.
15
+ *
16
+ * Returns the input unchanged when it cannot be resolved (path does
17
+ * not exist yet, or is not readable) so a bad `--project` still
18
+ * reaches the existing error handling rather than throwing here.
19
+ */
20
+ export function canonicalizeProjectOption(project) {
21
+ if (project === undefined)
22
+ return undefined;
23
+ try {
24
+ return stableRealPath(project);
25
+ }
26
+ catch {
27
+ return project;
28
+ }
29
+ }
30
+ /**
31
+ * D1 (2026-09-15) — generated-config staleness, carried on `skill presence`.
32
+ *
33
+ * WHY THIS CALL. `npm i -g peaks-loop@<newer>` upgrades the CLI and leaves the
34
+ * project's generated config exactly as the OLD release wrote it:
35
+ * `initWorkspace` is the only writer and `ensureSession` early-returns once a
36
+ * session is bound, so nothing re-runs the generator. `.claude/settings.local.json`
37
+ * is drift-checked — but only on an init that never comes. The user found the
38
+ * previous instance of this by deleting their `.claude/*.json` and restarting;
39
+ * nothing in the product told them to.
40
+ *
41
+ * `peaks skill presence` is the ONE peaks call every skill makes in every turn
42
+ * (CLAUDE.md mandates it at the start of every response), so it is the only
43
+ * channel guaranteed to carry a drift notice to the LLM that can act on it —
44
+ * the same reasoning as the loop-hygiene verdict attached one screen down. A
45
+ * rule that lives only in a SKILL.md body is compacted away; a warning that
46
+ * rides the per-turn tool output is not.
47
+ *
48
+ * The field is additive and present only when stale, so no existing consumer
49
+ * of the envelope changes shape. `null` projectRoot means the caller had no
50
+ * project to inspect — skipped, not assumed stale.
51
+ */
52
+ export function generatedConfigNotice(projectRoot) {
53
+ if (projectRoot === undefined)
54
+ return { field: {}, warnings: [] };
55
+ const staleness = detectStaleGeneratedArtifacts(projectRoot);
56
+ if (!staleness.stale)
57
+ return { field: {}, warnings: [] };
58
+ const onDiskVersion = staleness.onDisk?.packageVersion ?? '(unstamped)';
59
+ return {
60
+ field: {
61
+ generatedArtifacts: {
62
+ stale: true,
63
+ reasons: staleness.reasons,
64
+ onDiskPackageVersion: staleness.onDisk?.packageVersion ?? null,
65
+ installedPackageVersion: staleness.expected.packageVersion
66
+ }
67
+ },
68
+ warnings: [
69
+ `Generated config at '${projectRoot}' was produced by peaks-loop ${onDiskVersion} ` +
70
+ `but ${staleness.expected.packageVersion} is installed (${staleness.reasons.join(', ')}). ` +
71
+ `Re-run \`peaks workspace init\` ` +
72
+ `to regenerate .claude/settings.local.json and the offline template copy.`
73
+ ]
74
+ };
75
+ }
@@ -0,0 +1,3 @@
1
+ import type { Command } from 'commander';
2
+ import { type ProgramIO } from '../../cli-helpers.js';
3
+ export declare function registerSkillHeartbeatCommands(skill: Command, io: ProgramIO): void;
@@ -0,0 +1,50 @@
1
+ import { getSkillPresence, touchSkillHeartbeat } from '../../../services/skills/skill-presence-service.js';
2
+ import { detectPresenceMarker } from '../../../services/hooks/presence-marker-detector.js';
3
+ import { findProjectRoot } from '../../../services/config/config-safety.js';
4
+ import { ok } from 'peaks-loop-shared/result';
5
+ import { addJsonOption, printResult } from '../../cli-helpers.js';
6
+ const HEARTBEAT_DESCRIPTION = 'Show the heartbeat status of the active Peaks skill';
7
+ const HEARTBEAT_TOUCH_DESCRIPTION = 'Update the heartbeat timestamp (called by the LLM each turn to confirm peaks skill context is alive)';
8
+ const DETECT_MARKER_LOSS_DESCRIPTION = 'Detect whether the latest assistant message lost the Peaks-Loop status header while a peaks skill is still active (slice 028 detection primitive).';
9
+ const DETECT_MARKER_LOSS_MESSAGE_HELP = 'latest assistant message text to scan (defaults to reading the most recent LLM response from the stdin pipe, or empty string when no pipe is attached)';
10
+ function runSkillHeartbeat(options, io) {
11
+ const presence = getSkillPresence();
12
+ if (presence === null) {
13
+ printResult(io, ok('skill.heartbeat', { active: false, heartbeat: 'none' }), options.json);
14
+ return;
15
+ }
16
+ printResult(io, ok('skill.heartbeat', {
17
+ active: true,
18
+ skill: presence.skill,
19
+ gate: presence.gate ?? null,
20
+ lastHeartbeat: presence.lastHeartbeat ?? presence.setAt,
21
+ setAt: presence.setAt
22
+ }), options.json);
23
+ }
24
+ function runSkillHeartbeatTouch(options, io) {
25
+ const updated = touchSkillHeartbeat();
26
+ if (updated === null) {
27
+ printResult(io, ok('skill.heartbeat:touch', { active: false, heartbeat: 'none' }), options.json);
28
+ return;
29
+ }
30
+ printResult(io, ok('skill.heartbeat:touch', {
31
+ active: true,
32
+ skill: updated.skill,
33
+ lastHeartbeat: updated.lastHeartbeat
34
+ }), options.json);
35
+ }
36
+ function runSkillDetectMarkerLoss(options, io) {
37
+ const projectRoot = options.project ?? findProjectRoot(process.cwd()) ?? process.cwd();
38
+ const message = options.message ?? '';
39
+ const result = detectPresenceMarker({ project: projectRoot, latestAssistantMessage: message });
40
+ printResult(io, ok('skill.detect-marker-loss', result), options.json);
41
+ }
42
+ export function registerSkillHeartbeatCommands(skill, io) {
43
+ addJsonOption(skill.command('heartbeat').description(HEARTBEAT_DESCRIPTION)).action((options) => runSkillHeartbeat(options, io));
44
+ addJsonOption(skill.command('heartbeat:touch').description(HEARTBEAT_TOUCH_DESCRIPTION)).action((options) => runSkillHeartbeatTouch(options, io));
45
+ addJsonOption(skill
46
+ .command('detect-marker-loss')
47
+ .description(DETECT_MARKER_LOSS_DESCRIPTION)
48
+ .option('--project <path>', 'project root path (auto-detected from cwd when omitted)')
49
+ .option('--message <text>', DETECT_MARKER_LOSS_MESSAGE_HELP)).action((options) => runSkillDetectMarkerLoss(options, io));
50
+ }
@@ -0,0 +1,3 @@
1
+ import type { Command } from 'commander';
2
+ import { type ProgramIO } from '../../cli-helpers.js';
3
+ export declare function registerSkillPresenceCommands(skill: Command, io: ProgramIO): void;
@@ -0,0 +1,123 @@
1
+ import { setSkillPresence, getSkillPresence, isSkillPresenceMode, checkStalePresence } from '../../../services/skills/skill-presence-service.js';
2
+ import { findProjectRoot } from '../../../services/config/config-safety.js';
3
+ import { getSessionId, setSessionMeta } from '../../../services/session/session-manager.js';
4
+ import { resolveCallerProjection } from '../../../services/session/resolve-caller-id.js';
5
+ import { fail, ok } from 'peaks-loop-shared/result';
6
+ import { addJsonOption, printResult } from '../../cli-helpers.js';
7
+ import { canonicalizeProjectOption, generatedConfigNotice } from './skill-generated-config-notice.js';
8
+ import { contextVerdict } from './skill-context-verdict.js';
9
+ const PRESENCE_DESCRIPTION = 'Show the currently active Peaks skill (alias: presence:get)';
10
+ const PRESENCE_CHECK_STALE_HELP = 'slice 002 (v2.15.0): also report whether the recorded outer session id still matches the current one. Default false (back-compat).';
11
+ const PRESENCE_PROJECT_HELP = 'project root (default: cwd)';
12
+ const PRESENCE_SET_DESCRIPTION = 'Set the currently active Peaks skill for session-wide visibility. Slice 4.0.8: requires a bound session and an adapter-resolved caller id (fail-closed); raw unlink is rejected.';
13
+ const PRESENCE_SET_MODE_HELP = 'execution mode';
14
+ const PRESENCE_SET_GATE_HELP = 'current gate';
15
+ const PRESENCE_SET_PROJECT_HELP = 'project root path (auto-detected from cwd when omitted)';
16
+ function presenceActiveData(presence, parts) {
17
+ return {
18
+ active: true,
19
+ ...presence,
20
+ ...(parts.verdict.context !== null ? { context: parts.verdict.context } : {}),
21
+ ...parts.generatedConfig.field
22
+ };
23
+ }
24
+ function presenceStaleData(presence, staleness, parts) {
25
+ return {
26
+ active: true,
27
+ ...presence,
28
+ stale: staleness.stale,
29
+ staleReason: staleness.reason,
30
+ currentOuterSessionId: staleness.currentOuterSessionId,
31
+ recordedOuterSessionId: staleness.recordedOuterSessionId,
32
+ ...(parts.verdict.context !== null ? { context: parts.verdict.context } : {}),
33
+ ...parts.generatedConfig.field
34
+ };
35
+ }
36
+ function runSkillPresence(options, io) {
37
+ const projectOption = canonicalizeProjectOption(options.project);
38
+ const generatedConfig = generatedConfigNotice(projectOption ?? findProjectRoot(process.cwd()) ?? process.cwd());
39
+ const presence = getSkillPresence(projectOption);
40
+ if (presence === null) {
41
+ printResult(io, ok('skill.presence', { active: false, ...generatedConfig.field }, generatedConfig.warnings), options.json);
42
+ return;
43
+ }
44
+ // Loop-hygiene verdict: attached to every ACTIVE read, so the
45
+ // zero-pause contract travels with the one call every skill already
46
+ // makes — in every mode and every consumer project.
47
+ const verdict = contextVerdict(projectOption ?? process.cwd());
48
+ const parts = { verdict, generatedConfig };
49
+ // `--check-stale` lets callers (peaks-code Step 1, statusline) get both
50
+ // pieces of info from a single CLI invocation. The presence is returned
51
+ // UNCHANGED — it is a read-only flag, not a clear.
52
+ const data = options.checkStale === true
53
+ ? presenceStaleData(presence, checkStalePresence({ projectRootOverride: projectOption }), parts)
54
+ : presenceActiveData(presence, parts);
55
+ printResult(io, ok('skill.presence', data, generatedConfig.warnings, verdict.nextActions), options.json);
56
+ }
57
+ function printInvalidMode(io, options, name) {
58
+ printResult(io, fail('skill.presence:set', 'INVALID_MODE', `Invalid mode: ${options.mode} (expected one of: full-auto, assisted, strict, 24h)`, { name, mode: options.mode }, ['Use a valid mode: full-auto, assisted, strict, or 24h']), options.json);
59
+ process.exitCode = 1;
60
+ }
61
+ function printSessionNotBound(io, options, name, projectRoot) {
62
+ printResult(io, fail('skill.presence:set', 'PEAKS_SESSION_NOT_BOUND', 'No canonical peaks session is bound for this project (RD §3 D1).', { projectRoot, name }, ['Run `peaks workspace init --project <p>` first, then re-run `peaks skill presence:set`.']), options.json);
63
+ process.exitCode = 1;
64
+ }
65
+ function printCallerNotResolved(io, options, target, err) {
66
+ const message = err instanceof Error ? err.message : String(err);
67
+ printResult(io, fail('skill.presence:set', 'PEAKS_CALLER_NOT_RESOLVED', `Active IDE adapter could not resolve a callerId (RD §3 D1): ${message}`, target, [
68
+ 'Ensure the active IDE is detected by `peaks` and the IDE session variable is set.',
69
+ 'Or set PEAKS_CALLER_ID=<id> in the environment for scripted usage.'
70
+ ]), options.json);
71
+ process.exitCode = 1;
72
+ }
73
+ function runSkillPresenceSet(name, options, io) {
74
+ const projectOption = canonicalizeProjectOption(options.project);
75
+ const projectRoot = projectOption ?? findProjectRoot(process.cwd()) ?? process.cwd();
76
+ if (options.mode !== undefined && !isSkillPresenceMode(options.mode)) {
77
+ printInvalidMode(io, options, name);
78
+ return;
79
+ }
80
+ // Slice 4.0.8 (D1 + D2): `presence:set` is fail-closed. We refuse
81
+ // any write when (a) no peaks session is bound, or (b) the active
82
+ // IDE adapter cannot resolve a valid callerId. Both failures
83
+ // surface BEFORE any filesystem write. The legacy
84
+ // `setSkillPresence` wrapper is kept as a compat shim for tests
85
+ // and CLI flows that intentionally do not need a session — but
86
+ // production CLI traffic must go through this gate.
87
+ const boundSessionId = getSessionId(projectRoot);
88
+ if (boundSessionId === null) {
89
+ printSessionNotBound(io, options, name, projectRoot);
90
+ return;
91
+ }
92
+ try {
93
+ resolveCallerProjection({ projectRoot });
94
+ }
95
+ catch (err) {
96
+ printCallerNotResolved(io, options, { name, projectRoot }, err);
97
+ return;
98
+ }
99
+ const presence = setSkillPresence(name, options.mode, options.gate, projectOption);
100
+ // Session metadata is updated when a session is bound (read-only
101
+ // path: `getSessionId`). We do not auto-spawn a session.
102
+ if (boundSessionId !== null) {
103
+ setSessionMeta(projectRoot, boundSessionId, {
104
+ skill: name,
105
+ ...(options.mode ? { mode: options.mode } : {}),
106
+ ...(options.gate ? { gate: options.gate } : {})
107
+ });
108
+ }
109
+ printResult(io, ok('skill.presence:set', { active: true, ...presence }), options.json);
110
+ }
111
+ export function registerSkillPresenceCommands(skill, io) {
112
+ addJsonOption(skill
113
+ .command('presence')
114
+ .description(PRESENCE_DESCRIPTION)
115
+ .option('--check-stale', PRESENCE_CHECK_STALE_HELP)
116
+ .option('--project <path>', PRESENCE_PROJECT_HELP)).action((options) => runSkillPresence(options, io));
117
+ addJsonOption(skill
118
+ .command('presence:set <name>')
119
+ .description(PRESENCE_SET_DESCRIPTION)
120
+ .option('--mode <mode>', PRESENCE_SET_MODE_HELP)
121
+ .option('--gate <gate>', PRESENCE_SET_GATE_HELP)
122
+ .option('--project <path>', PRESENCE_SET_PROJECT_HELP)).action((name, options) => runSkillPresenceSet(name, options, io));
123
+ }
@@ -0,0 +1,3 @@
1
+ import type { Command } from 'commander';
2
+ import { type ProgramIO } from '../../cli-helpers.js';
3
+ export declare function registerSkillPresenceHousekeepingCommands(skill: Command, io: ProgramIO): void;
@@ -0,0 +1,165 @@
1
+ import { clearSkillPresence, getSkillPresence, checkStalePresence } from '../../../services/skills/skill-presence-service.js';
2
+ import { findProjectRoot } from '../../../services/config/config-safety.js';
3
+ import { generateProjectContext } from '../../../services/memory/project-context-service.js';
4
+ import { gcStalePresenceLeases } from '../../../services/skills/presence-lease-service.js';
5
+ import { fail, ok } from 'peaks-loop-shared/result';
6
+ import { addJsonOption, getErrorMessage, printResult } from '../../cli-helpers.js';
7
+ const PRESENCE_CLEAR_DESCRIPTION = 'Unlink the DEPRECATED pre-4.0.11 single-slot presence marker files (`.peaks/_runtime/active-skill.json`, `.peaks/.active-skill.json`). It does NOT terminalize a live presence lease — a workflow-bound lease terminalizes through `peaks workflow terminalize --workflow <id> --reason <reason>`, an ad-hoc lease only at session exit, and raw unlink is FORBIDDEN for both. The envelope reports what is still active.';
8
+ const PRESENCE_CLEAR_PROJECT_HELP = 'project root path (auto-detected from cwd when omitted)';
9
+ const LEASE_GC_DESCRIPTION = 'Manually sweep stale presence leases for the canonical project. Both predicates required: now - lastHeartbeat > 1h AND now - startedAt > 24h. Drained leases are classified; corrupt graphs surface as PEAKS_GRAPH_REF_BROKEN warnings and are excluded.';
10
+ const LEASE_GC_PROJECT_HELP = 'project root (default: cwd)';
11
+ const LEASE_GC_NOW_HELP = 'override the current time (test seam)';
12
+ const PRESENCE_CHECK_STALE_DESCRIPTION = 'Slice 002 (v2.15.0) AC-1: report whether the recorded presence outer session id still matches the current outer session id. ' +
13
+ 'Returns { stale: boolean, reason: "outer-session-mismatch" | "no-presence" | null }. ' +
14
+ 'Pure read-only — does NOT clear the presence (use `peaks skill presence:clear` for that).';
15
+ const PRESENCE_CHECK_STALE_PROJECT_HELP = 'project root (default: cwd)';
16
+ const PRESENCE_CHECK_STALE_CURRENT_OUTER_HELP = 'override the current outer session id (test seam; default: read from PEAKS_OUTER_SESSION_ID / CLAUDE_CODE_SESSION_ID)';
17
+ const PRESENCE_CLEAR_SURVIVOR_ACTIONS = [
18
+ 'A workflow-bound lease terminalizes through `peaks workflow terminalize --workflow <id> --reason <reason>`.',
19
+ 'An ad-hoc lease (`peaks skill presence:set`) is terminalizable only at session exit; `presence:clear` will not remove it, and raw unlink is FORBIDDEN.',
20
+ 'Run `peaks skill presence --json` to read the lease that survived.'
21
+ ];
22
+ const LEASE_GC_NEXT_ACTIONS = [
23
+ 'GC predicate: now - lastHeartbeat > 1h AND now - startedAt > 24h (RD §3 D2 + D3).',
24
+ 'Re-run `peaks workspace init` or `peaks skill presence:set` to sweep the same project on the bound trigger.'
25
+ ];
26
+ function presenceClearData(active, removed, cleared) {
27
+ return {
28
+ active,
29
+ removed,
30
+ cleared,
31
+ ...(cleared ? {} : { reason: 'live-lease-survives-presence-clear' }),
32
+ projectContextUpdated: true
33
+ };
34
+ }
35
+ async function runPresenceClear(options, io) {
36
+ const projectRoot = options.project ?? findProjectRoot(process.cwd()) ?? process.cwd();
37
+ // What this command actually is: a stale-marker cleanup for projects
38
+ // carrying a pre-4.0.11 single-slot file, and nothing more. The previous
39
+ // description claimed it "routes workflow leases through `workflow
40
+ // terminalize`" and that a "compat wrapper re-routes [it] to
41
+ // `terminalizePresenceLease` when a workflow binding is present" — there is
42
+ // no such wrapper, and no such routing happens here or in
43
+ // `clearSkillPresence`. A caller who read that line and ran this command
44
+ // expecting a live workflow lease to terminalize got a successful exit code
45
+ // and a still-running lease.
46
+ const removed = clearSkillPresence(options.project);
47
+ // Auto-update project context so future sessions have up-to-date history.
48
+ // bootstraps `.peaks/project-scan/` (idempotent). Await the async
49
+ // signature; failure is still non-fatal so we don't block the clear.
50
+ try {
51
+ await generateProjectContext(projectRoot);
52
+ }
53
+ catch {
54
+ // TODO(g2): legacy silent catch — grace: 1 minor release (v2.14.0)
55
+ // non-fatal: context update failure should not block presence clear
56
+ }
57
+ // Report the state the project is actually in, not the state this command
58
+ // intended to reach. `clearSkillPresence` unlinks only the DEPRECATED
59
+ // single-slot marker files; the canonical sid-scoped lease is deliberately
60
+ // left alive for an AD-HOC lease (raw unlink is FORBIDDEN — only session
61
+ // exit may terminalize one; workflow leases route through `workflow
62
+ // terminalize`). So after a `presence:set` the very next `peaks skill
63
+ // presence` call still reads `active: true`. A hardcoded `active: false`
64
+ // therefore reported a state the command never reached, and the caller had
65
+ // no way to tell that from a real clear.
66
+ //
67
+ // Re-reading through `getSkillPresence` — the same projection the
68
+ // `skill presence` command serves — is the only derivation that cannot
69
+ // drift from what the next command prints: it is the same read path, on
70
+ // the same project root, evaluated after every write this command makes.
71
+ const active = getSkillPresence(projectRoot) !== null;
72
+ // `cleared` is the live question the caller is actually asking ("is it gone
73
+ // now?"), which `removed` could never answer: a legacy marker being unlinked
74
+ // says nothing about the lease the next command will read. When it is false
75
+ // the envelope says WHY, in the two routes that can terminalize a lease —
76
+ // without this the caller saw `active: true, removed: false` and had no way
77
+ // to tell "you ran the wrong command" from "this command has no opinion".
78
+ const cleared = !active;
79
+ printResult(io, ok('skill.presence:clear', presenceClearData(active, removed, cleared), [], cleared ? [] : PRESENCE_CLEAR_SURVIVOR_ACTIONS), options.json);
80
+ }
81
+ function leaseGcData(result) {
82
+ return {
83
+ envelopeVersion: '4.0.8',
84
+ removed: result.removed,
85
+ retained: result.retained,
86
+ trigger: result.trigger,
87
+ inFlightBatch: result.inFlightBatch,
88
+ warnings: result.warnings,
89
+ errors: result.errors
90
+ };
91
+ }
92
+ // Slice 4.0.8 (RD §4): manual lease GC primitive. LLM-coordinated;
93
+ // never a user-typed requirement. The user / LLM runner invokes
94
+ // `peaks skill lease gc --project <p>` to drain leases that meet
95
+ // both stale predicates (24h start AND 1h heartbeat) for the
96
+ // canonical project. Returns a typed envelope so the runner can
97
+ // branch on the aggregate counters.
98
+ // Note: `.command('lease gc')` implicitly creates the `lease`
99
+ // parent under `skill`; we MUST NOT also call `.command('lease')`
100
+ // separately, or Commander.js throws "cannot add command 'lease' as
101
+ // already have command 'lease'" at startup.
102
+ async function runLeaseGc(options, io) {
103
+ const projectRoot = options.project ?? findProjectRoot(process.cwd()) ?? process.cwd();
104
+ try {
105
+ const result = await gcStalePresenceLeases({
106
+ projectRoot,
107
+ ...(options.now !== undefined ? { now: options.now } : {}),
108
+ trigger: 'manual'
109
+ });
110
+ printResult(io, ok('skill.lease.gc', leaseGcData(result), result.warnings.map((w) => `${w.code}: ${w.message}`), LEASE_GC_NEXT_ACTIONS), options.json);
111
+ }
112
+ catch (err) {
113
+ printResult(io, fail('skill.lease.gc', 'PEAKS_LEASE_GC_FAILED', getErrorMessage(err), { projectRoot }, [
114
+ 'Re-run with a valid --project and ensure the session is bound.'
115
+ ]), options.json);
116
+ process.exitCode = 1;
117
+ }
118
+ }
119
+ // peaks-code Step 1 (and `peaks code should-pause --step
120
+ // step-1-mode-select`) calls this to decide whether the recorded
121
+ // `mode` field can be trusted or whether the LLM must AskUserQuestion.
122
+ function runPresenceCheckStale(options, io) {
123
+ // when the user omits the flag. The service-layer branch
124
+ // `'currentOuter' in opts` returns true for an explicit
125
+ // `undefined` (the key exists on the spread object literal),
126
+ // which would skip the env-var fallback and pin `current =
127
+ // undefined` — always reading the presence as stale. Build a
128
+ // sparse opts object so the service can fall back to
129
+ // `getCurrentOuterSessionId()` (reads PEAKS_OUTER_SESSION_ID /
130
+ // CLAUDE_CODE_SESSION_ID).
131
+ const checkOpts = options.project !== undefined ? { projectRootOverride: options.project } : {};
132
+ if (options.currentOuter !== undefined) {
133
+ checkOpts.currentOuter = options.currentOuter;
134
+ }
135
+ const result = checkStalePresence(checkOpts);
136
+ // Always emit `currentOuterSessionId` in the JSON envelope (even
137
+ // tooling (statusline, sub-agent dispatch) reads the field by
138
+ // name, never by `data.currentOuterSessionId ?? ''`. JSON.stringify
139
+ // drops `undefined` properties, so we coerce to '' before
140
+ // wrapping in the envelope.
141
+ const data = {
142
+ stale: result.stale,
143
+ reason: result.reason,
144
+ presence: result.presence,
145
+ currentOuterSessionId: result.currentOuterSessionId ?? '',
146
+ recordedOuterSessionId: result.recordedOuterSessionId ?? ''
147
+ };
148
+ printResult(io, ok('skill.presence:check-stale', data), options.json);
149
+ }
150
+ export function registerSkillPresenceHousekeepingCommands(skill, io) {
151
+ addJsonOption(skill
152
+ .command('presence:clear')
153
+ .description(PRESENCE_CLEAR_DESCRIPTION)
154
+ .option('--project <path>', PRESENCE_CLEAR_PROJECT_HELP)).action((options) => runPresenceClear(options, io));
155
+ addJsonOption(skill
156
+ .command('lease gc')
157
+ .description(LEASE_GC_DESCRIPTION)
158
+ .option('--project <path>', LEASE_GC_PROJECT_HELP)
159
+ .option('--now <iso>', LEASE_GC_NOW_HELP)).action((options) => runLeaseGc(options, io));
160
+ addJsonOption(skill
161
+ .command('presence:check-stale')
162
+ .description(PRESENCE_CHECK_STALE_DESCRIPTION)
163
+ .option('--project <path>', PRESENCE_CHECK_STALE_PROJECT_HELP)
164
+ .option('--current-outer <id>', PRESENCE_CHECK_STALE_CURRENT_OUTER_HELP)).action((options) => runPresenceCheckStale(options, io));
165
+ }
@@ -0,0 +1,3 @@
1
+ import type { Command } from 'commander';
2
+ import { type ProgramIO } from '../../cli-helpers.js';
3
+ export declare function registerSkillRegistryCommands(skill: Command, io: ProgramIO): void;
@@ -0,0 +1,117 @@
1
+ import { runDoctor } from '../../../services/doctor/index.js';
2
+ import { listSkills } from '../../../services/skills/skill-registry.js';
3
+ import { runSkillSync, SYNC_PLATFORMS } from '../../../services/skills/sync-service.js';
4
+ import { ok, fail } from 'peaks-loop-shared/result';
5
+ import { addJsonOption, getErrorMessage, printResult } from '../../cli-helpers.js';
6
+ const PLATFORM_IDS = SYNC_PLATFORMS.join(', ');
7
+ const LIST_DESCRIPTION = 'List skills derived from skills/*/SKILL.md';
8
+ const LIST_INCLUDE_INTERNAL_HELP = 'include skills with visibility: internal (default: hide them)';
9
+ const DOCTOR_DESCRIPTION = 'Run skill-related doctor checks';
10
+ const SYNC_DESCRIPTION = `Sync the peaks-* skill family to one or all of the 8 supported LLM-CLI platforms (${PLATFORM_IDS}). Idempotent.`;
11
+ const SYNC_PLATFORM_HELP = `sync only one platform (default: --all). Valid: ${PLATFORM_IDS}`;
12
+ const SYNC_ALL_HELP = 'sync all 8 platforms (default if --platform is omitted)';
13
+ const SYNC_DRY_RUN_HELP = 'do not write; emit the same shape with applied=false';
14
+ const SYNC_RECONCILE_HELP = 'repair Peaks-managed skill Junctions whose targets were deleted with a host worktree';
15
+ const SYNC_PROJECT_HELP = 'project root (default: cwd)';
16
+ function sortSkillsForDisplay(skills) {
17
+ return [...skills].sort((a, b) => {
18
+ // door) — list it FIRST so users discover the dispatcher before
19
+ // any specific leaf. Followed by peaks-sop (current default
20
+ // runbook showcase) and peaks-code (canonical code-domain
21
+ // orchestrator); everything else falls back to alphabetical.
22
+ if (a.name === 'peaks-solo')
23
+ return -1;
24
+ if (b.name === 'peaks-solo')
25
+ return 1;
26
+ if (a.name === 'peaks-sop')
27
+ return -1;
28
+ if (b.name === 'peaks-sop')
29
+ return 1;
30
+ if (a.name === 'peaks-code')
31
+ return -1;
32
+ if (b.name === 'peaks-code')
33
+ return 1;
34
+ return a.name.localeCompare(b.name);
35
+ });
36
+ }
37
+ function runSkillList(options, io) {
38
+ return listSkills().then((all) => {
39
+ const skills = options.includeInternal === true ? all : all.filter((s) => s.visibility !== 'internal');
40
+ if (options.json === true) {
41
+ printResult(io, ok('skill.list', { skills }), true);
42
+ return;
43
+ }
44
+ for (const skill of sortSkillsForDisplay(skills)) {
45
+ io.stdout(` ${skill.name.padEnd(14)}${skill.description}`);
46
+ }
47
+ io.stdout(`\n Invoke any skill by typing its name in conversation (e.g. \`peaks-sop\`).`);
48
+ });
49
+ }
50
+ function runSkillDoctor(options, io) {
51
+ return runDoctor().then((report) => {
52
+ const skillChecks = report.checks.filter((check) => check.id.startsWith('skill'));
53
+ const failed = skillChecks.filter((check) => !check.ok).length;
54
+ if (options.json === true) {
55
+ printResult(io, ok('skill.doctor', { checks: skillChecks, ok: failed === 0 }), true);
56
+ }
57
+ else {
58
+ for (const check of skillChecks) {
59
+ const icon = check.ok ? '+' : '×';
60
+ io.stdout(` ${icon} ${check.message}`);
61
+ }
62
+ io.stdout(`\n ${skillChecks.length - failed} passed, ${failed} failed`);
63
+ if (failed > 0) {
64
+ io.stderr('\nOne or more skill checks failed.');
65
+ }
66
+ }
67
+ if (failed > 0) {
68
+ process.exitCode = 1;
69
+ }
70
+ });
71
+ }
72
+ async function runSkillSyncCommand(options, io) {
73
+ try {
74
+ const projectRoot = options.project ?? process.cwd();
75
+ const platforms = options.platform !== undefined ? [options.platform] : undefined;
76
+ const result = await runSkillSync({
77
+ projectRoot,
78
+ ...(platforms !== undefined ? { platforms } : {}),
79
+ ...(options.dryRun === true ? { dryRun: true } : {}),
80
+ ...(options.reconcileJunctions === true ? { reconcileJunctions: true } : {})
81
+ });
82
+ const envelope = ok('skill.sync', result, [], [
83
+ `syncedCount: ${result.syncedCount}/${result.perPlatform.length} platforms`,
84
+ `totalInstalled: ${result.totalInstalled} skill symlinks`,
85
+ result.failedCount > 0
86
+ ? `failedCount: ${result.failedCount} (run \`peaks skill status\` for details)`
87
+ : 'no failures'
88
+ ]);
89
+ printResult(io, envelope, options.json);
90
+ if (result.failedCount > 0) {
91
+ process.exitCode = 1;
92
+ }
93
+ }
94
+ catch (error) {
95
+ const message = getErrorMessage(error);
96
+ printResult(io, fail('skill.sync', 'SKILL_SYNC_FAILED', message, { applied: false }, [message]), options.json);
97
+ process.exitCode = 1;
98
+ }
99
+ }
100
+ export function registerSkillRegistryCommands(skill, io) {
101
+ addJsonOption(skill
102
+ .command('list')
103
+ .description(LIST_DESCRIPTION)
104
+ .option('--include-internal', LIST_INCLUDE_INTERNAL_HELP)).action((options) => runSkillList(options, io));
105
+ addJsonOption(skill.command('doctor').description(DOCTOR_DESCRIPTION)).action((options) => runSkillDoctor(options, io));
106
+ // Slice #12 final piece (per spec §9 line 1105):
107
+ // `peaks skills sync 8 平台分发`. Idempotent: re-running is a
108
+ // no-op when the symlinks are already correct.
109
+ addJsonOption(skill
110
+ .command('sync')
111
+ .description(SYNC_DESCRIPTION)
112
+ .option('--platform <id>', SYNC_PLATFORM_HELP)
113
+ .option('--all', SYNC_ALL_HELP)
114
+ .option('--dry-run', SYNC_DRY_RUN_HELP)
115
+ .option('--reconcile-junctions', SYNC_RECONCILE_HELP)
116
+ .option('--project <path>', SYNC_PROJECT_HELP)).action((options) => runSkillSyncCommand(options, io));
117
+ }
@@ -0,0 +1,3 @@
1
+ import type { Command } from 'commander';
2
+ import { type ProgramIO } from '../../cli-helpers.js';
3
+ export declare function registerSkillRunbookCommand(skill: Command, io: ProgramIO): void;
@@ -0,0 +1,29 @@
1
+ import { inspectSkillRunbook } from '../../../services/skills/skill-runbook-service.js';
2
+ import { fail, ok } from 'peaks-loop-shared/result';
3
+ import { addJsonOption, getErrorMessage, printResult } from '../../cli-helpers.js';
4
+ export function registerSkillRunbookCommand(skill, io) {
5
+ addJsonOption(skill
6
+ .command('runbook <name>')
7
+ .description('Inspect a skill Default runbook section and its --apply authorization-note status')).action(async (name, options) => {
8
+ try {
9
+ const inspection = await inspectSkillRunbook(name);
10
+ const result = inspection.ok
11
+ ? ok('skill.runbook', inspection)
12
+ : fail('skill.runbook', inspection.hasRunbook ? 'SKILL_RUNBOOK_APPLY_UNGATED' : 'SKILL_RUNBOOK_MISSING', inspection.hasRunbook
13
+ ? `Skill ${inspection.name} has ${inspection.destructiveApplyLines.length} destructive --apply command(s) without an authorization/dry-run note`
14
+ : `Skill ${inspection.name} is missing a ## Default runbook section`, inspection, inspection.hasRunbook
15
+ ? [
16
+ 'Add an authorization or --dry-run note next to destructive --apply lines in the runbook section'
17
+ ]
18
+ : ['Add a `## Default runbook` section to the skill SKILL.md']);
19
+ printResult(io, result, options.json);
20
+ if (!inspection.ok) {
21
+ process.exitCode = 1;
22
+ }
23
+ }
24
+ catch (error) {
25
+ printResult(io, fail('skill.runbook', 'SKILL_NOT_FOUND', getErrorMessage(error), { name }), options.json);
26
+ process.exitCode = 1;
27
+ }
28
+ });
29
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from 'commander';
2
+ export declare function buildDispatchCommandSpec(parent: Command): Command;