gentle-pi 4.0.0 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (425) hide show
  1. package/README.md +157 -18
  2. package/assets/agents/gentle-ai-explore.md +11 -4
  3. package/assets/agents/gentle-ai-verify.md +23 -4
  4. package/assets/agents/gentle-ai-worker.md +6 -6
  5. package/assets/install-wizard/index.html +47 -0
  6. package/assets/install-wizard/wizard.css +1279 -0
  7. package/assets/install-wizard/wizard.js +951 -0
  8. package/assets/orchestrator-delegation.md +70 -163
  9. package/assets/orchestrator-memory.md +11 -4
  10. package/assets/orchestrator-prompts.md +48 -0
  11. package/assets/orchestrator-tracking.md +53 -0
  12. package/assets/orchestrator-verification.md +28 -0
  13. package/assets/orchestrator-writer.md +38 -0
  14. package/assets/orchestrator.md +29 -25
  15. package/assets/sounds/LICENSE.md +52 -0
  16. package/assets/sounds/attention.wav +0 -0
  17. package/assets/sounds/error.wav +0 -0
  18. package/assets/sounds/success.wav +0 -0
  19. package/bin/engram-guard +9 -0
  20. package/bin/gentle-shell-install.mjs +302 -0
  21. package/bin/gentle-shell.mjs +708 -22
  22. package/contracts/review-provider-contract-mirror/provider-contract.lock.json +7 -7
  23. package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/manifest.json +2 -2
  24. package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/orchestration/pi.md +16 -4
  25. package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/generated/provider-capabilities.baseline.json +2 -2
  26. package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/generated/provider-roles.baseline.json +1 -1
  27. package/docs/auto-permission-answers.md +25 -0
  28. package/docs/auto-permission-evidence.md +39 -0
  29. package/docs/bundled-install.md +399 -0
  30. package/docs/delegated-verification.md +3 -5
  31. package/docs/gentle-agents-activity.md +513 -0
  32. package/docs/gentle-shell-uninstall.md +136 -0
  33. package/docs/gentle-shell.md +90 -33
  34. package/docs/install-wizard.md +2040 -0
  35. package/docs/native-authority-architecture.md +1 -1
  36. package/docs/permission-modes.md +127 -0
  37. package/docs/prompt-history.md +1 -1
  38. package/docs/readme-reference.md +310 -47
  39. package/docs/render-cache-performance.md +28 -0
  40. package/docs/session-profile-format.md +39 -0
  41. package/docs/sound-notifications-proposal.md +191 -0
  42. package/docs/sound-notifications.md +114 -0
  43. package/docs/yolo-mode.md +11 -10
  44. package/extensions/ask-user-choice.ts +4 -0
  45. package/extensions/ask-user-question.ts +6 -1
  46. package/extensions/child-context.ts +11 -2
  47. package/extensions/child-safety.ts +4 -2
  48. package/extensions/codegraph-tools.ts +18 -5
  49. package/extensions/gentle-agents.ts +718 -87
  50. package/extensions/gentle-ai.ts +991 -263
  51. package/extensions/gentle-herdr-activity.ts +81 -0
  52. package/extensions/gentle-notifications.ts +104 -0
  53. package/extensions/gentle-shell.ts +271 -80
  54. package/extensions/gentle-todo.ts +123 -18
  55. package/extensions/history/index.ts +11 -6
  56. package/extensions/pi-pretty.ts +39 -3
  57. package/extensions/quiet-tools.ts +91 -21
  58. package/extensions/skill-registry.ts +175 -46
  59. package/extensions/startup-banner.ts +11 -2
  60. package/lib/agent-home.ts +10 -1
  61. package/lib/agent-model-resolution.ts +80 -0
  62. package/lib/agent-profiles.ts +13 -3
  63. package/lib/agents-completion-delivery.ts +11 -3
  64. package/lib/agents-messaging.ts +1 -1
  65. package/lib/agents-protocol.ts +40 -1
  66. package/lib/agents-runner.ts +90 -15
  67. package/lib/auto-permission-answers.ts +133 -0
  68. package/lib/auto-permission-classifier.ts +459 -0
  69. package/lib/auto-permission-evidence.ts +88 -0
  70. package/lib/auto-permission-memory.ts +69 -0
  71. package/lib/auto-permission-runtime.ts +484 -0
  72. package/lib/auto-permission-tool-context.ts +61 -0
  73. package/lib/background-jobs-tools.ts +202 -0
  74. package/lib/background-jobs.ts +253 -0
  75. package/lib/background-monitor.ts +154 -0
  76. package/lib/bounded-writer-admission.ts +83 -10
  77. package/lib/builtin-codemode-optout.ts +166 -0
  78. package/lib/card-content-policy.ts +85 -0
  79. package/lib/child-package-injection.ts +62 -0
  80. package/lib/codemode-renderer.ts +14 -4
  81. package/lib/command-palette-catalog.ts +3 -2
  82. package/lib/context-threshold.ts +26 -0
  83. package/lib/destructive-command-guard.ts +104 -28
  84. package/lib/gentle-ai-binary.ts +109 -4
  85. package/lib/gentle-shell-bundled.ts +535 -0
  86. package/lib/gentle-shell-launcher.ts +75 -24
  87. package/lib/gentle-shell-startup-flow.ts +54 -0
  88. package/lib/gentle-shell-startup-update.ts +92 -0
  89. package/lib/gentle-shell-uninstall.ts +840 -0
  90. package/lib/gentle-shell-update-panel.ts +231 -0
  91. package/lib/herdr-activity.ts +183 -0
  92. package/lib/inprocess-reviewer.ts +3 -0
  93. package/lib/jobs-sidebar-state.ts +20 -0
  94. package/lib/jobs-view.ts +161 -0
  95. package/lib/model-price-ratio.ts +83 -0
  96. package/lib/model-routing-authority.ts +6 -1
  97. package/lib/nan-provider.ts +33 -11
  98. package/lib/native-review-cli.ts +194 -73
  99. package/lib/notification-audio-native.ts +162 -0
  100. package/lib/notification-audio-windows.ts +399 -0
  101. package/lib/notification-audio.ts +332 -0
  102. package/lib/notification-customize.ts +241 -0
  103. package/lib/notification-events.ts +113 -0
  104. package/lib/notification-policy.ts +148 -0
  105. package/lib/notification-pulse-client.ts +367 -0
  106. package/lib/notification-pulse-protocol.ts +305 -0
  107. package/lib/notification-pulse-stream.ts +213 -0
  108. package/lib/notification-pulse-worker.ts +66 -0
  109. package/lib/notification-scheduler.ts +173 -0
  110. package/lib/notification-service.ts +202 -0
  111. package/lib/notification-ui.ts +87 -0
  112. package/lib/odd-phase.ts +1 -1
  113. package/lib/orchestrator-alias.ts +20 -0
  114. package/lib/orchestrator-catalog.ts +79 -0
  115. package/lib/orchestrator-consultation.ts +61 -0
  116. package/lib/orchestrator-discovery.ts +49 -0
  117. package/lib/orchestrator-helper-consent.ts +122 -0
  118. package/lib/orchestrator-helper.ts +167 -0
  119. package/lib/orchestrator-message-card.ts +105 -0
  120. package/lib/orchestrator-presence.ts +115 -14
  121. package/lib/orchestrator-scope.ts +80 -0
  122. package/lib/orchestrator-state.ts +107 -0
  123. package/lib/orchestrator-tool-card.ts +280 -0
  124. package/lib/orchestrator-work-search.ts +218 -0
  125. package/lib/orchestrator-work.ts +79 -0
  126. package/lib/permission-mode-policy.ts +59 -0
  127. package/lib/review-candidate-view-owner.ts +8 -6
  128. package/lib/review-candidate-view.ts +3 -2
  129. package/lib/review-compact-contract.ts +2 -2
  130. package/lib/review-host-relay.ts +7 -2
  131. package/lib/review-integration-v2.ts +100 -22
  132. package/lib/review-publication-gate.ts +1 -1
  133. package/lib/review-risk-assessment.ts +77 -51
  134. package/lib/review-sidebar-state.ts +2 -1
  135. package/lib/review-transaction.ts +72 -3
  136. package/lib/runtime-metrics-children.ts +1 -1
  137. package/lib/runtime-metrics-native.ts +3 -2
  138. package/lib/selection-engine.ts +22 -7
  139. package/lib/session-change-capture.ts +5 -4
  140. package/lib/session-changes.ts +27 -22
  141. package/lib/session-profile-binding.ts +83 -0
  142. package/lib/session-profile-freeze.ts +261 -0
  143. package/lib/session-profile-persistence.ts +187 -0
  144. package/lib/session-worktree-registry.ts +74 -8
  145. package/lib/shell-bar.ts +41 -11
  146. package/lib/shell-changes-view.ts +40 -13
  147. package/lib/shell-changes.ts +88 -71
  148. package/lib/shell-prompt.ts +6 -4
  149. package/lib/shell-sidebar-layout.ts +5 -1
  150. package/lib/shell-todo.ts +81 -26
  151. package/lib/shell-usage.ts +4 -3
  152. package/lib/status-timing-diagnostics.ts +201 -0
  153. package/lib/telemetry-trigger.ts +3 -1
  154. package/lib/vim-editor-adapter.ts +27 -5
  155. package/lib/visual-customization-policy.ts +20 -9
  156. package/lib/visual-customize-view.ts +171 -20
  157. package/lib/writer-surfaces.ts +162 -0
  158. package/lib/yolo-session-policy.ts +198 -121
  159. package/package.json +5 -5
  160. package/runtime/child-package-injection.mjs +63 -0
  161. package/runtime/gentle-ai-binary.mjs +109 -4
  162. package/runtime/gentle-shell-bundled.mjs +536 -0
  163. package/runtime/gentle-shell-launcher.mjs +74 -23
  164. package/runtime/gentle-shell-startup-flow.mjs +55 -0
  165. package/runtime/gentle-shell-startup-update.mjs +93 -0
  166. package/runtime/gentle-shell-uninstall.mjs +841 -0
  167. package/runtime/gentle-shell-update-panel.mjs +232 -0
  168. package/runtime/native-review-cli.mjs +191 -70
  169. package/runtime/review-integration-v2.mjs +100 -22
  170. package/runtime/review-risk-assessment.mjs +77 -51
  171. package/runtime/status-timing-diagnostics.mjs +202 -0
  172. package/runtime/telemetry-trigger.mjs +3 -1
  173. package/scripts/bootstrap.cmd +255 -0
  174. package/scripts/bootstrap.sh +168 -0
  175. package/scripts/build-distribution.mjs +157 -0
  176. package/scripts/build-installer-bundles.mjs +136 -0
  177. package/scripts/build-runtime-modules.mjs +7 -0
  178. package/scripts/bundled-install.mjs +1308 -0
  179. package/scripts/bundled-wizard.mjs +399 -0
  180. package/scripts/check-types.mjs +4 -6
  181. package/scripts/gentle-ai-installer.mjs +79 -32
  182. package/scripts/install-gentle-ai.mjs +48 -1
  183. package/scripts/install-tui-mode-setting.mjs +10 -1
  184. package/scripts/install-wizard-preview.mjs +154 -0
  185. package/scripts/installer-downloads.mjs +648 -0
  186. package/scripts/installer-preflight.mjs +387 -0
  187. package/scripts/installer-probes.mjs +564 -0
  188. package/scripts/installer-runner.mjs +1102 -0
  189. package/scripts/installer-server.mjs +881 -0
  190. package/scripts/installer-windows-artifacts.json +15 -0
  191. package/scripts/installer-windows.mjs +752 -0
  192. package/scripts/main-channel.mjs +452 -0
  193. package/scripts/npm-pack-result.mjs +31 -0
  194. package/scripts/pi-update.mjs +223 -0
  195. package/scripts/render-cache-performance-worker.mjs +205 -0
  196. package/scripts/render-cache-performance.mjs +115 -0
  197. package/scripts/run-test-suite.mjs +22 -8
  198. package/scripts/test-installer-acceptance.sh +161 -0
  199. package/scripts/test-packed-runner.mjs +23 -5
  200. package/scripts/types-baseline.json +2 -92
  201. package/scripts/verify-package-files.mjs +82 -18
  202. package/skills/gentle-ai/SKILL.md +12 -11
  203. package/tests/agent-home.test.ts +56 -3
  204. package/tests/agent-model-resolution.test.ts +113 -0
  205. package/tests/agent-profiles.test.ts +12 -0
  206. package/tests/agent-risk-escalation.test.ts +123 -0
  207. package/tests/agents-completion-delivery.test.ts +17 -0
  208. package/tests/agents-protocol.test.ts +57 -0
  209. package/tests/agents-queries.test.ts +9 -9
  210. package/tests/agents-runner-process.test.ts +31 -0
  211. package/tests/agents-runner.test.ts +89 -4
  212. package/tests/append-system-prompt-route.test.ts +18 -1
  213. package/tests/auto-mode-human-answers.test.ts +68 -0
  214. package/tests/auto-mode-invariants.test.ts +276 -0
  215. package/tests/auto-mode-sdk.test.ts +84 -0
  216. package/tests/auto-mode.test.ts +49 -0
  217. package/tests/auto-permission-answers.test.ts +170 -0
  218. package/tests/auto-permission-classifier.test.ts +634 -0
  219. package/tests/auto-permission-compound.test.ts +24 -0
  220. package/tests/auto-permission-evidence.test.ts +88 -0
  221. package/tests/auto-permission-memory.test.ts +46 -0
  222. package/tests/auto-permission-public-regressions.test.ts +111 -0
  223. package/tests/auto-permission-runtime.test.ts +454 -0
  224. package/tests/autonomous-guard.test.ts +86 -16
  225. package/tests/background-cache-warming.test.ts +2 -1
  226. package/tests/background-jobs.test.ts +227 -0
  227. package/tests/background-monitor.test.ts +195 -0
  228. package/tests/background-subagents-default-mode.test.ts +28 -5
  229. package/tests/bounded-stop-contract.test.ts +85 -0
  230. package/tests/bounded-writer-admission.test.ts +52 -1
  231. package/tests/bridge-wake-lifecycle.test.ts +129 -0
  232. package/tests/bridge-wake-rendering.test.ts +56 -0
  233. package/tests/build-distribution.test.ts +269 -0
  234. package/tests/builtin-codemode-optout.test.ts +242 -0
  235. package/tests/bundled-install.test.ts +1319 -0
  236. package/tests/bundled-wizard.test.ts +505 -0
  237. package/tests/card-content-policy.test.ts +68 -0
  238. package/tests/card-style-policy.test.ts +34 -2
  239. package/tests/child-codegraph-bootstrap.test.ts +74 -0
  240. package/tests/child-package-entrypoints.test.ts +65 -0
  241. package/tests/child-package-injection.test.ts +74 -0
  242. package/tests/child-safety.test.ts +12 -0
  243. package/tests/child-web-package.test.ts +224 -0
  244. package/tests/codegraph-tools.test.ts +35 -0
  245. package/tests/codemode-rendering.test.ts +107 -2
  246. package/tests/command-palette.test.ts +5 -5
  247. package/tests/context-threshold.test.ts +31 -0
  248. package/tests/delegate-for-reason-contract.test.ts +327 -0
  249. package/tests/delegated-key-learnings-contract.test.ts +3 -2
  250. package/tests/devbinary/native-review-parity.devtest.ts +1 -1
  251. package/tests/devbinary/pi-host-relay.devtest.ts +173 -3
  252. package/tests/engram-safety.test.ts +316 -0
  253. package/tests/generic-agent-tools.test.ts +16 -1
  254. package/tests/gentle-agents.test.ts +1895 -76
  255. package/tests/gentle-ai-binary-isolation.test.ts +135 -0
  256. package/tests/gentle-ai-binary.test.ts +19 -2
  257. package/tests/gentle-ai-child-guards.test.ts +202 -0
  258. package/tests/gentle-ai-dev-binary-surfacing.test.ts +125 -15
  259. package/tests/gentle-ai-installer.test.ts +548 -58
  260. package/tests/gentle-ai-prompt-model-routing.test.ts +123 -0
  261. package/tests/gentle-ai-renderer.test.ts +6 -6
  262. package/tests/gentle-ai.test.ts +999 -31
  263. package/tests/gentle-herdr-activity.test.ts +100 -0
  264. package/tests/gentle-notifications.test.ts +427 -0
  265. package/tests/gentle-shell-bin.test.ts +263 -19
  266. package/tests/gentle-shell-bundled.test.ts +1036 -0
  267. package/tests/gentle-shell-launcher.test.ts +251 -25
  268. package/tests/gentle-shell-startup-flow.test.ts +75 -0
  269. package/tests/gentle-shell-startup-update.test.ts +85 -0
  270. package/tests/gentle-shell-uninstall.test.ts +978 -0
  271. package/tests/gentle-shell-update-panel.test.ts +158 -0
  272. package/tests/gentle-shell.test.ts +611 -100
  273. package/tests/gentle-todo.test.ts +317 -14
  274. package/tests/herdr-activity.test.ts +243 -0
  275. package/tests/history-agent-home.test.ts +81 -0
  276. package/tests/history-child-guard.test.ts +70 -0
  277. package/tests/inprocess-reviewer.test.ts +20 -0
  278. package/tests/install-gentle-ai-failure.test.ts +69 -0
  279. package/tests/install-tui-mode-setting.test.ts +15 -0
  280. package/tests/install-wizard.test.ts +1223 -0
  281. package/tests/installer-bundles.test.ts +122 -0
  282. package/tests/installer-posix-bootstrap.test.ts +1201 -0
  283. package/tests/installer-preflight.test.ts +750 -0
  284. package/tests/installer-probes.test.ts +1106 -0
  285. package/tests/installer-runner.test.ts +2159 -0
  286. package/tests/installer-server.test.ts +1567 -0
  287. package/tests/installer-windows-bootstrap.test.ts +2410 -0
  288. package/tests/jobs-view.test.ts +160 -0
  289. package/tests/main-channel-upgrade.test.ts +395 -0
  290. package/tests/main-channel.test.ts +344 -0
  291. package/tests/maintainer/provider-relay.maintest.ts +13 -12
  292. package/tests/model-price-ratio.test.ts +116 -0
  293. package/tests/model-profiles-preservation.test.ts +153 -0
  294. package/tests/model-routing-package.test.ts +10 -0
  295. package/tests/nan-provider-runtime.test.ts +156 -0
  296. package/tests/nan-provider.test.ts +28 -18
  297. package/tests/native-binary-gate.test.ts +2 -0
  298. package/tests/native-review-capability-contract.test.ts +7 -1
  299. package/tests/native-review-cli.test.ts +236 -1
  300. package/tests/native-review-consent.test.ts +2 -2
  301. package/tests/native-review-parity-runtime.test.ts +1 -1
  302. package/tests/native-review-parity.test.ts +12 -48
  303. package/tests/native-risk-evidence-parity.test.ts +21 -0
  304. package/tests/no-self-pause-contract.test.ts +87 -0
  305. package/tests/notification-audio-native.test.ts +281 -0
  306. package/tests/notification-audio-windows.test.ts +581 -0
  307. package/tests/notification-audio.test.ts +548 -0
  308. package/tests/notification-customize.test.ts +655 -0
  309. package/tests/notification-events.test.ts +115 -0
  310. package/tests/notification-policy.test.ts +201 -0
  311. package/tests/notification-pulse-client.test.ts +356 -0
  312. package/tests/notification-pulse-protocol.test.ts +213 -0
  313. package/tests/notification-pulse-stream.test.ts +320 -0
  314. package/tests/notification-scheduler.test.ts +258 -0
  315. package/tests/notification-ui.test.ts +157 -0
  316. package/tests/npm-pack-result.test.ts +41 -0
  317. package/tests/odd-integration.test.ts +14 -5
  318. package/tests/odd-routing-canonical-ratchet.test.ts +53 -12
  319. package/tests/odd-routing-contract.test.ts +80 -23
  320. package/tests/odd-runtime-delegation-gate.test.ts +4 -1
  321. package/tests/orchestrator-budget.test.ts +29 -6
  322. package/tests/orchestrator-catalog.test.ts +146 -0
  323. package/tests/orchestrator-consultation-sdk.test.ts +614 -0
  324. package/tests/orchestrator-consultation.test.ts +111 -0
  325. package/tests/orchestrator-discovery.test.ts +241 -0
  326. package/tests/orchestrator-helper-consent.test.ts +171 -0
  327. package/tests/orchestrator-helper.test.ts +297 -0
  328. package/tests/orchestrator-message-card.test.ts +190 -0
  329. package/tests/orchestrator-presence.test.ts +23 -0
  330. package/tests/orchestrator-rdd-ownership.test.ts +6 -5
  331. package/tests/orchestrator-scope.test.ts +107 -0
  332. package/tests/orchestrator-state.test.ts +114 -0
  333. package/tests/orchestrator-status-publication-contract.test.ts +34 -0
  334. package/tests/orchestrator-tool-card.test.ts +398 -0
  335. package/tests/orchestrator-work-search.test.ts +318 -0
  336. package/tests/orchestrator-work.test.ts +147 -0
  337. package/tests/package-manifest.test.ts +105 -21
  338. package/tests/packed-runner-owned-path.test.ts +35 -1
  339. package/tests/parallel-reason-contract.test.ts +21 -0
  340. package/tests/parallel-writer-admission.test.ts +210 -0
  341. package/tests/permission-mode-policy.test.ts +194 -0
  342. package/tests/persona-single-channel.test.ts +4 -12
  343. package/tests/pi-pretty.test.ts +56 -0
  344. package/tests/pi-update-launcher.test.ts +87 -0
  345. package/tests/pi-update.test.ts +186 -0
  346. package/tests/provider-defect-handoff.test.ts +4 -3
  347. package/tests/quiet-tool-rendering.test.ts +160 -3
  348. package/tests/rdd-aware-verification-contract.test.ts +29 -41
  349. package/tests/rdd-status-line.test.ts +52 -0
  350. package/tests/render-cache-performance.test.ts +48 -0
  351. package/tests/review-agent-end-preflight.test.ts +62 -10
  352. package/tests/review-candidate-view.test.ts +101 -12
  353. package/tests/review-contract-prompt.test.ts +33 -4
  354. package/tests/review-controller-native-recovery.test.ts +327 -17
  355. package/tests/review-controller-native-routing.test.ts +255 -1
  356. package/tests/review-controller-workspace-root.test.ts +43 -2
  357. package/tests/review-controller.test.ts +2 -2
  358. package/tests/review-correction-lifecycle.test.ts +2 -1
  359. package/tests/review-gate.test.ts +7 -4
  360. package/tests/review-graph-schema.test.ts +2 -2
  361. package/tests/review-host-relay-routing.test.ts +5 -3
  362. package/tests/review-host-relay.test.ts +49 -2
  363. package/tests/review-integration-v2-forward.test.ts +21 -1
  364. package/tests/review-json-arguments.test.ts +167 -0
  365. package/tests/review-last-event-closure.test.ts +157 -8
  366. package/tests/review-ledger-contract.test.ts +5 -4
  367. package/tests/review-policy-judgment-day.test.ts +1 -1
  368. package/tests/review-policy-ordinary.test.ts +2 -2
  369. package/tests/review-relay-transport-agent.test.ts +66 -1
  370. package/tests/review-risk-assessment.test.ts +109 -33
  371. package/tests/review-session-standing-permission-controller.test.ts +16 -17
  372. package/tests/review-session-standing-permission-ipc.test.ts +6 -5
  373. package/tests/review-test-fixtures.ts +7 -1
  374. package/tests/review-transaction.test.ts +18 -1
  375. package/tests/run-test-suite.test.ts +83 -5
  376. package/tests/runtime-harness.mjs +42 -15
  377. package/tests/runtime-metrics-extension.test.ts +1 -1
  378. package/tests/runtime-metrics-native.test.ts +31 -1
  379. package/tests/selection-engine.test.ts +65 -5
  380. package/tests/session-change-capture.test.ts +10 -10
  381. package/tests/session-changes-shell.test.ts +242 -48
  382. package/tests/session-changes.test.ts +17 -24
  383. package/tests/session-profile-binding.test.ts +163 -0
  384. package/tests/session-profile-freeze.test.ts +394 -0
  385. package/tests/session-profile-persistence.test.ts +334 -0
  386. package/tests/session-subject-routing-contract.test.ts +34 -0
  387. package/tests/session-worktree-registry.test.ts +57 -2
  388. package/tests/shell-bar.test.ts +40 -0
  389. package/tests/shell-changes-view.test.ts +85 -34
  390. package/tests/shell-changes.test.ts +79 -62
  391. package/tests/shell-render-cache-runtime.test.ts +69 -0
  392. package/tests/shell-render-cache.test.ts +129 -0
  393. package/tests/shell-sidebar-layout.test.ts +27 -2
  394. package/tests/shell-todo.test.ts +132 -11
  395. package/tests/shell-usage-view.test.ts +1 -1
  396. package/tests/shell-usage.test.ts +35 -0
  397. package/tests/short-check-routing-contract.test.ts +49 -0
  398. package/tests/skill-registry-watcher.test.ts +119 -0
  399. package/tests/skill-registry.test.ts +311 -5
  400. package/tests/startup-banner.test.ts +117 -3
  401. package/tests/startup-home-repo.test.ts +170 -0
  402. package/tests/status-timing-diagnostics.test.ts +283 -0
  403. package/tests/support/orchestrator-modules.ts +19 -0
  404. package/tests/task-size-routing-contract.test.ts +241 -0
  405. package/tests/telemetry-trigger.test.ts +27 -1
  406. package/tests/test-strength-contract.test.ts +124 -0
  407. package/tests/verify-agent-contract.test.ts +90 -0
  408. package/tests/verify-first-launch-contract.test.ts +139 -0
  409. package/tests/verify-package-files.test.ts +56 -0
  410. package/tests/vim-editor-adapter.test.ts +47 -21
  411. package/tests/visual-customization-policy.test.ts +32 -3
  412. package/tests/visual-customize-view.test.ts +288 -10
  413. package/tests/writer-edit-surface-scope.test.ts +59 -2
  414. package/tests/writer-surfaces.test.ts +199 -0
  415. package/tests/yolo-customize.test.ts +18 -3
  416. package/tests/yolo-mode-runtime.test.ts +37 -10
  417. package/tests/yolo-mode.test.ts +38 -5
  418. package/tests/yolo-session-policy.test.ts +1 -1
  419. /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/README.md +0 -0
  420. /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/schemas/lens.schema.json +0 -0
  421. /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/schemas/refuter.schema.json +0 -0
  422. /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/schemas/targeted-validator.schema.json +0 -0
  423. /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/vectors/lens.json +0 -0
  424. /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/vectors/refuter.json +0 -0
  425. /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/vectors/targeted-validator.json +0 -0
@@ -0,0 +1,387 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { posix, win32 } from "node:path";
3
+ import {
4
+ INSTALLER_VERSION,
5
+ GENTLE_AI_WINDOWS_MINIMUM_GO_VERSION,
6
+ } from "./gentle-ai-installer.mjs";
7
+ import { artifactFor, goPinVersion } from "./installer-downloads.mjs";
8
+
9
+ // Read package metadata only: never import the launcher or execute postinstall.
10
+ const metadata = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
11
+ function minimum(range) {
12
+ if (!/^>=\d+\.\d+\.\d+$/.test(range)) throw new Error("Unsupported preflight requirement range");
13
+ return range.slice(2);
14
+ }
15
+ export const requirements = Object.freeze({
16
+ node: minimum(metadata.engines.node),
17
+ pi: minimum(metadata.peerDependencies["@earendil-works/pi-coding-agent"]),
18
+ // pnpm pack can omit packageManager; acquisition still has a verified pin.
19
+ pnpm: metadata.packageManager?.replace(/^pnpm@/, "") ?? artifactFor("pnpm").version,
20
+ shell: metadata.version,
21
+ gentleAi: INSTALLER_VERSION,
22
+ go: GENTLE_AI_WINDOWS_MINIMUM_GO_VERSION,
23
+ });
24
+
25
+ /** Pi version the installer installs next to gentle-pi, and the version an older
26
+ * Pi is updated to (never below requirements.pi; the runner asserts it).
27
+ */
28
+ export const PI_INSTALL_VERSION = "1.2.0";
29
+
30
+ /** Runtime persisted under PNPM_HOME. Bootstrap-only Node: `pnpm runtime set
31
+ * node <node> -g`, then `pnpm add -g npm@<npm> pnpm@<pnpm>`, or only npm when
32
+ * pnpm is already persistent (never downgraded). Persistent Node: one
33
+ * `pnpm add -g` of only the missing npm and/or bootstrap-only pnpm.
34
+ * npm 11.19.0 is the npm bundled with Node 24.21.0.
35
+ */
36
+ export const persistencePins = Object.freeze({ node: "24.21.0", npm: "11.19.0", pnpm: requirements.pnpm });
37
+
38
+ /** Go the installer downloads only to build Gentle AI, when a build needs Go and
39
+ * the user's Go is missing, older than requirements.go or unchecked (installer-downloads.mjs).
40
+ */
41
+ export const goAcquisition = Object.freeze({ version: goPinVersion });
42
+
43
+ /** pnpm 11 global bin directory: `$PNPM_HOME/bin`, not `$PNPM_HOME` itself.
44
+ * PNPM_HOME is the user's existing absolute value, otherwise pnpm's documented
45
+ * platform default. onPath compares PATH entries with that bin directory, so a
46
+ * globalBin probe must report this path. Returns null when it is unknowable.
47
+ */
48
+ export function pnpmGlobalBin({ platform, env }) {
49
+ const path = platform === "win32" ? win32 : posix;
50
+ const absolute = (value) => typeof value === "string" && value.length > 0 && path.isAbsolute(value);
51
+ let pnpmHome = env.PNPM_HOME;
52
+ if (pnpmHome !== undefined) {
53
+ if (!absolute(pnpmHome)) return null;
54
+ } else if (platform === "win32") {
55
+ if (!absolute(env.LOCALAPPDATA)) return null;
56
+ pnpmHome = path.join(env.LOCALAPPDATA, "pnpm");
57
+ } else {
58
+ if (!absolute(env.HOME)) return null;
59
+ if (platform === "darwin") pnpmHome = path.join(env.HOME, "Library", "pnpm");
60
+ else pnpmHome = path.join(absolute(env.XDG_DATA_HOME) ? env.XDG_DATA_HOME : path.join(env.HOME, ".local", "share"), "pnpm");
61
+ }
62
+ pnpmHome = path.normalize(pnpmHome).replace(/(.)[\\/]+$/, "$1");
63
+ const bin = path.join(pnpmHome, "bin");
64
+ const comparable = (value) => {
65
+ const normal = path.normalize(value).replace(/(.)[\\/]+$/, "$1");
66
+ return platform === "win32" ? normal.toLowerCase() : normal;
67
+ };
68
+ const pathKey = platform === "win32" ? Object.keys(env).find((key) => key.toUpperCase() === "PATH") : "PATH";
69
+ const entries = String((pathKey && env[pathKey]) ?? "").split(path.delimiter).filter(absolute);
70
+ return { pnpmHome, path: bin, onPath: entries.some((entry) => comparable(entry) === comparable(bin)) };
71
+ }
72
+
73
+ const probeNames = ["node", "pnpm", "pi", "shell", "gentleAi", "go", "globalBin", "setup"];
74
+
75
+ /** Collect with caller-owned read-only probes; no default process/home adapters.
76
+ * A tool probe returns { available, version, usable }; missing is available:false.
77
+ * pnpm also needs compatible:true (runtime/capability evidence), shell global:true,
78
+ * and Gentle AI compatible:true (normal binary resolver/integrity evidence).
79
+ * Node may add persistent (resolvable from the user's PATH without bootstrap
80
+ * tool directories) and npm (a usable npm resolves there) booleans; pnpm may
81
+ * add persistent (resolvable from the user's PATH). A bootstrap-only Node or
82
+ * pnpm may add found: the stable version of the older or incompatible one on the
83
+ * user's PATH that the bootstrap left in place; a pnpm probe for the user's own
84
+ * pnpm in `$PNPM_HOME/bin` (where persisting pnpm writes) adds inGlobalBin:true
85
+ * and reports that pnpm itself, never as found, or, when that pnpm is never run
86
+ * (untrusted:true) or reports no stable version (unchecked:true), the bootstrap's
87
+ * pnpm in its place with persistent:true. globalBin returns { available, path, writable, onPath } for
88
+ * pnpmGlobalBin's `$PNPM_HOME/bin` directory; setup returns boolean, or
89
+ * { available: true, recoverable: true } for the pinned stack this pnpm installed
90
+ * whose setup did not finish (only its setup is then planned).
91
+ * Version values are exact stable versions, not raw arbitrary command output.
92
+ * On Windows the wizard passes pnpmHome, its PNPM_HOME decision (windowsPnpmHome),
93
+ * which the inventory keeps. A blocked decision runs no probe at all: the user's
94
+ * tools would otherwise run with that PNPM_HOME's bin first on PATH.
95
+ * On Windows a `folders` probe runs last: { node?, npm?, go?, pi?, shell? }, what
96
+ * the walk found on the folders those tools run from (S6 notice). It is kept as
97
+ * `folders` only when it names something; a failed walk is simply no record.
98
+ */
99
+ export async function collectInventory({ platform, arch, probes = {}, pnpmHome }) {
100
+ const inventory = { platform, arch, ...(pnpmHome === undefined || pnpmHome === null ? {} : { pnpmHome }) };
101
+ if (pnpmHomeBlocker(inventory)) return inventory;
102
+ for (const name of probeNames) {
103
+ try {
104
+ inventory[name] = probes[name] ? await probes[name]() : { available: null };
105
+ } catch {
106
+ // Do not retain probe errors: they can contain private paths or credentials.
107
+ inventory[name] = { available: null };
108
+ }
109
+ }
110
+ // Optional, advisory only (the plan notes a missing Git): kept only from a probe that answered.
111
+ if (probes.git) {
112
+ const git = await Promise.resolve().then(() => probes.git()).catch(() => null);
113
+ if (plainRecord(git) && typeof git.available === "boolean") inventory.git = { available: git.available };
114
+ }
115
+ if (platform === "win32" && probes.folders) {
116
+ const folders = await Promise.resolve().then(() => probes.folders()).catch(() => null);
117
+ if (plainRecord(folders) && !("available" in folders) && Object.keys(folders).length > 0) inventory.folders = folders;
118
+ }
119
+ return inventory;
120
+ }
121
+
122
+ const MAIN_BUILD = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)-main\.[0-9a-f]{12}$/;
123
+ function plainRecord(value) {
124
+ return typeof value === "object" && value !== null && !Array.isArray(value);
125
+ }
126
+ /** Windows only: the blocker of a PNPM_HOME decision that is untrusted, or whose
127
+ * walk could not finish (S6), with the plan's record of it; null otherwise.
128
+ */
129
+ function pnpmHomeBlocker({ platform, pnpmHome: home }) {
130
+ if (platform !== "win32" || !plainRecord(home)) return null;
131
+ if (home.failed === true) return { blocker: { code: "unknown-tool", tool: "pnpmHome" } };
132
+ if (home.available !== true || !plainRecord(home.untrusted) || typeof home.path !== "string") return null;
133
+ return { blocker: { code: "untrusted-pnpm-home", tool: "pnpmHome" },
134
+ record: { status: "untrusted", path: home.path, source: home.source === "user" ? "user" : "default" } };
135
+ }
136
+ function versionParts(version) {
137
+ if (typeof version !== "string" || !/^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.test(version)) return null;
138
+ const parts = version.replace(/^v/, "").split(".").map(Number);
139
+ return parts.every(Number.isSafeInteger) ? parts : null;
140
+ }
141
+ function compareVersions(left, right) {
142
+ for (let i = 0; i < 3; i += 1) {
143
+ if (left[i] !== right[i]) return left[i] > right[i] ? 1 : -1;
144
+ }
145
+ return 0;
146
+ }
147
+ if (compareVersions(versionParts(persistencePins.node), versionParts(requirements.node)) < 0) {
148
+ throw new Error("Persistent Node pin is below the repository minimum");
149
+ }
150
+ if (compareVersions(versionParts(goAcquisition.version), versionParts(requirements.go)) < 0) {
151
+ throw new Error("Go acquisition pin is below the Go requirement");
152
+ }
153
+ function classify(observation, required, extraCheck = () => true, exact = false) {
154
+ if (observation?.available === false) return "unavailable";
155
+ if (observation?.available !== true) return "unknown";
156
+ const version = versionParts(observation.version);
157
+ if (!version) return "unknown";
158
+ const order = compareVersions(version, versionParts(required));
159
+ // A known version below the minimum is incompatible whether or not it runs.
160
+ if (order < 0) return "incompatible";
161
+ if (observation.usable !== true) return "unknown";
162
+ if (exact && order !== 0) return "incompatible";
163
+ const extra = extraCheck(observation);
164
+ if (extra === false) return "incompatible";
165
+ return extra === true ? "reusable" : "unknown";
166
+ }
167
+
168
+ /** Pure ordered intent plan, not executable commands or a readiness certificate.
169
+ * Blocked plans contain no actions: never overwrite an incompatible/unknown tool.
170
+ * Compatible newer Node/Pi/Shell versions remain owned by their existing install.
171
+ */
172
+ export function planPreflight(inventory, { channel = "release" } = {}) {
173
+ if (channel !== "release" && channel !== "main") throw new TypeError(`Unsupported installation channel: ${channel}`);
174
+ const main = channel === "main";
175
+ const tools = {};
176
+ const blockers = [];
177
+ const actions = [];
178
+ const { platform, arch } = inventory;
179
+ const supportedTarget = ["linux", "darwin", "win32"].includes(platform) && ["x64", "arm64"].includes(arch);
180
+ if (!supportedTarget) {
181
+ return { tools, blockers: [{ code: "unsupported-target", tool: "target" }], actions, ready: false };
182
+ }
183
+ // A blocked Windows PNPM_HOME decision: no probe ran, so it is the only blocker.
184
+ const homeBlocked = pnpmHomeBlocker(inventory);
185
+ if (homeBlocked) return { tools: homeBlocked.record ? { pnpmHome: homeBlocked.record } : {}, blockers: [homeBlocked.blocker], actions, ready: false };
186
+ // An installed Gentle Shell whose owner (pnpm or npm) is known is updated with
187
+ // that package manager instead of blocking: older, unusable or main-channel
188
+ // versions on release, and any version on main. A current pnpm-owned Shell
189
+ // keeps the pinned-stack checks below; a current npm-owned one needs nothing.
190
+ const shellSeen = inventory.shell;
191
+ const ownedShell = shellSeen?.available === true && (shellSeen.owner === "pnpm" || shellSeen.owner === "npm") &&
192
+ (versionParts(shellSeen.version) !== null || MAIN_BUILD.test(String(shellSeen.version)));
193
+ const current = ownedShell && shellSeen.usable === true && versionParts(shellSeen.version) !== null &&
194
+ compareVersions(versionParts(shellSeen.version), versionParts(requirements.shell)) >= 0;
195
+ const update = ownedShell && (main || !current) ? channel : null;
196
+ const npmCurrent = ownedShell && !update && shellSeen.owner === "npm";
197
+ function record(name, status, required) {
198
+ tools[name] = { status, ...(required ? { required } : {}) };
199
+ // Gentle AI and setup are only checked for the pinned pnpm Shell, so their
200
+ // unknown status adds nothing once that Shell blocks or is not the pinned one.
201
+ const shellBlocks = tools.shell?.status === "incompatible" || tools.shell?.status === "unknown" || update !== null || npmCurrent;
202
+ const derived = status === "unknown" && (name === "gentleAi" || name === "setup") && shellBlocks;
203
+ if (!derived && (status === "unknown" || status === "incompatible")) {
204
+ blockers.push({ code: `${status}-tool`, tool: name });
205
+ }
206
+ }
207
+ record("node", classify(inventory.node, requirements.node), requirements.node);
208
+ // An older Pi whose owner (pnpm or npm) is known is updated with that package
209
+ // manager to PI_INSTALL_VERSION instead of blocking; the plan records the
210
+ // found version and owner the update must still match. An older Pi neither
211
+ // owns (mise, Homebrew, a binary) is left as it is: the installer's Pi is
212
+ // installed alongside, exactly as when Pi is absent.
213
+ const piSeen = inventory.pi;
214
+ const piStatus = classify(piSeen, requirements.pi);
215
+ if (piStatus === "incompatible" && piSeen.usable === true && (piSeen.owner === "pnpm" || piSeen.owner === "npm")) {
216
+ tools.pi = { status: "needs-update", required: requirements.pi, version: piSeen.version, owner: piSeen.owner };
217
+ } else if (piStatus === "incompatible" && piSeen.usable === true) {
218
+ tools.pi = { status: "needs-install", required: requirements.pi, version: piSeen.version };
219
+ } else {
220
+ record("pi", piStatus, requirements.pi);
221
+ }
222
+ const updatePi = tools.pi.status === "needs-update";
223
+ const installPi = tools.pi.status === "unavailable" || tools.pi.status === "needs-install";
224
+ if (update) record("shell", "needs-update", requirements.shell);
225
+ else if (npmCurrent) record("shell", "reusable", requirements.shell);
226
+ else record("shell", classify(inventory.shell, requirements.shell, (o) => o.global), requirements.shell);
227
+ // packageManager pins the acquisition choice, not a minimum supported pnpm.
228
+ // Existing versions require explicit engine/capability evidence from the probe.
229
+ record("pnpm", classify(inventory.pnpm, "0.0.0", (o) => o.compatible), requirements.pnpm);
230
+ // A user's pnpm in $PNPM_HOME/bin that could not be checked is left as it is while
231
+ // the bootstrap's pnpm runs the installer; the plan copy says so before consent.
232
+ if (tools.pnpm.status === "reusable" && inventory.pnpm.inGlobalBin === true && inventory.pnpm.unchecked === true) {
233
+ tools.pnpm = { ...tools.pnpm, unchecked: true };
234
+ }
235
+ // An older Node or an incompatible pnpm on the user's PATH is left as it is: the
236
+ // bootstrap acquired its pinned copy, which the probe reports (bootstrap-only)
237
+ // with the user's stable version as `found`. That copy runs the installer and
238
+ // is persisted below; the plan records both versions.
239
+ const replacedRuntimes = [];
240
+ for (const name of ["node", "pnpm"]) {
241
+ const seen = inventory[name];
242
+ const found = versionParts(seen?.found);
243
+ const replaced = name === "node" ? found !== null && compareVersions(found, versionParts(requirements.node)) < 0 : found !== null;
244
+ if (tools[name].status !== "reusable" || seen.persistent !== false || !replaced) continue;
245
+ tools[name] = { ...tools[name], found: found.join("."), version: persistencePins[name] };
246
+ replacedRuntimes.push(name);
247
+ }
248
+ record("gentleAi", classify(inventory.gentleAi, requirements.gentleAi, (o) => o.compatible, true), requirements.gentleAi);
249
+ const needsNative = tools.gentleAi.status === "unavailable";
250
+ // The main channel builds Gentle AI from source on every platform.
251
+ // gentle-pi's postinstall may build Gentle AI from source on Windows, so a
252
+ // Windows release update needs Go like an installation; main always does.
253
+ const windowsBuild = platform === "win32" && (update !== null || (needsNative && !npmCurrent));
254
+ const goStatus = windowsBuild || main ? classify(inventory.go, requirements.go) : "not-required";
255
+ if (goStatus === "unavailable" || goStatus === "incompatible" || goStatus === "unknown") {
256
+ // A missing, older or unreadable Go is left as it is: the build gets the
257
+ // installer's pinned Go, first on PATH, downloaded after consent only when
258
+ // a build actually runs.
259
+ const found = goStatus === "incompatible" ? versionParts(inventory.go.version).join(".") : null;
260
+ tools.go = { status: "needs-acquire", required: requirements.go, version: goAcquisition.version, ...(found ? { found } : {}),
261
+ ...(goStatus === "unknown" ? { unchecked: true } : {}) };
262
+ } else {
263
+ record("go", goStatus, requirements.go);
264
+ }
265
+ const bin = inventory.globalBin;
266
+ const binKnown = bin?.available === true && typeof bin.path === "string" && bin.path.trim().length > 0 &&
267
+ bin.writable === true && typeof bin.onPath === "boolean";
268
+ record("globalBin", bin?.available === false ? "unavailable" : binKnown ? (bin.onPath ? "reusable" : "needs-setup") : "unknown");
269
+ // Windows only: the PNPM_HOME decision the wizard made before probing (S6,
270
+ // windowsPnpmHome). A passing user or default folder changes nothing here; a
271
+ // private one is recorded for the runner and the plan copy (a blocked one
272
+ // returned above).
273
+ const home = platform === "win32" && plainRecord(inventory.pnpmHome) ? inventory.pnpmHome : null;
274
+ if (home?.available === true && home.source === "private" && typeof home.path === "string" && typeof home.rejected?.path === "string") {
275
+ const finding = Object.fromEntries(["check", "at", "sid", "account", "rights"]
276
+ .filter((key) => typeof home.rejected[key] === "string").map((key) => [key, home.rejected[key]]));
277
+ tools.pnpmHome = { status: "private", path: home.path, default: home.rejected.path, finding };
278
+ }
279
+ // Windows only (S6 notice, never a blocker): reused tools whose folders another
280
+ // account can change, from the inventory's one `folders` walk. Only tools this
281
+ // plan uses as they are: the user's Node and its npm, a Go a build reuses, a Pi
282
+ // kept as it is, and an npm-owned Gentle Shell that is kept or updated by npm.
283
+ const folders = platform === "win32" && plainRecord(inventory.folders) ? inventory.folders : null;
284
+ if (folders) {
285
+ const userNode = tools.node.status === "reusable" && tools.node.found === undefined && inventory.node?.persistent === true;
286
+ const used = { node: userNode, npm: userNode && inventory.node?.npm === true, go: tools.go.status === "reusable", pi: tools.pi.status === "reusable",
287
+ shell: inventory.shell?.owner === "npm" && ["reusable", "needs-update"].includes(tools.shell.status) };
288
+ // A walk rejection, or a path the walk could not check; nothing else.
289
+ const known = /^(?:(?:target|parent|ancestor)-(?:reparse|owner|acl-mask)|unchecked)$/;
290
+ const reused = Object.keys(used).filter((tool) => used[tool] && plainRecord(folders[tool]) && known.test(String(folders[tool].check)))
291
+ .map((tool) => ({ tool, ...Object.fromEntries(["check", "at", "sid", "account", "rights"]
292
+ .filter((key) => typeof folders[tool][key] === "string").map((key) => [key, folders[tool][key]])) }));
293
+ if (reused.length > 0) tools.folders = { status: "notice", reused };
294
+ }
295
+ const missingShell = tools.shell.status === "unavailable";
296
+ // Setup recovery: the setup probe proved the pinned stack this pnpm installed
297
+ // (an earlier run stopped in setup), so only the public setup is rerun.
298
+ const recovering = inventory.setup?.available === true && inventory.setup.recoverable === true &&
299
+ ["pi", "shell", "gentleAi"].every((name) => tools[name].status === "reusable");
300
+ let setupStatus = "unknown";
301
+ if (missingShell || needsNative || inventory.setup === false || recovering) setupStatus = "needs-setup";
302
+ else if (inventory.setup === true) setupStatus = "reusable";
303
+ record("setup", setupStatus);
304
+ // Runtimes are persisted only while installing Gentle Shell (the runner's
305
+ // persistence variants): with an existing Shell, updated or current, a replaced
306
+ // Node or pnpm keeps blocking as before. A setup recovery persists nothing and
307
+ // runs with the bootstrap's copy.
308
+ if (["needs-update", "reusable"].includes(tools.shell.status) && !recovering) {
309
+ for (const name of replacedRuntimes) blockers.push({ code: "incompatible-tool", tool: name });
310
+ }
311
+ if (blockers.length) return { tools, blockers, actions, ready: false };
312
+
313
+ function action(id, kind, target, version) {
314
+ actions.push({ id, kind, target, ...(version ? { version } : {}) });
315
+ }
316
+ // The pinned Go pair, at `at`, before the first step that builds with it.
317
+ function acquireGo(at = actions.length) {
318
+ if (tools.go.status !== "needs-acquire") return;
319
+ actions.splice(at, 0, { id: "acquire-go", kind: "acquire", target: "go", version: goAcquisition.version },
320
+ { id: "verify-go", kind: "verify", target: "go" });
321
+ }
322
+ // Existing Gentle Shell: update it (installing a missing Pi or updating an older
323
+ // one first), then set it up. A current npm Shell only gets its Pi updated or
324
+ // the installer's Pi added.
325
+ if (update || npmCurrent) {
326
+ // Only an update builds: main, or a Windows release postinstall.
327
+ if (update) acquireGo();
328
+ if (updatePi) action("update-pi", "upgrade", "pi", PI_INSTALL_VERSION);
329
+ // A current npm Shell next to an older Pi neither manager owns gets the installer's Pi.
330
+ if (!update && tools.pi.status === "needs-install") action("install-pi", "install-global", "pi", requirements.pi);
331
+ if (update) {
332
+ if (installPi) action("install-pi", "install-global", "pi", requirements.pi);
333
+ action(`update-shell-${update}`, "upgrade", "shell");
334
+ action("setup-shell", "normal-setup", "shell");
335
+ }
336
+ action("verify-readiness", "verify", "stack");
337
+ return { tools, blockers, actions, ready: actions.length === 1 };
338
+ }
339
+ function acquire(name) {
340
+ if (tools[name].status !== "unavailable") return;
341
+ action(`acquire-${name}`, "acquire", name, requirements[name]);
342
+ action(`verify-${name}`, "verify", name);
343
+ }
344
+ acquire("node");
345
+ acquire("pnpm");
346
+ if (tools.globalBin.status !== "reusable") action("setup-global-bin", "setup", "globalBin");
347
+ // The pinned Go comes before anything is persisted or installed, so a failed
348
+ // download leaves this computer as it was.
349
+ const goAt = actions.length;
350
+ // A bootstrap Node lives in a temporary tools directory, and Gentle AI's Engram
351
+ // step needs a working npm: persist what is missing under PNPM_HOME through
352
+ // pnpm itself. A persistent Node is never replaced or shadowed. A recovery
353
+ // never persists: the earlier run did that before installing the stack.
354
+ const node = inventory.node;
355
+ const persistable = tools.node.status === "reusable" && !recovering;
356
+ // A persistent pnpm, such as a newer pnpm 11 in $PNPM_HOME/bin, is never replaced.
357
+ const pnpm = inventory.pnpm?.persistent === false;
358
+ if (persistable && node.persistent === false) {
359
+ action("persist-node", "persist-runtime", "node", persistencePins.node);
360
+ if (pnpm) action("persist-package-managers", "install-global", "package-managers");
361
+ else action("persist-npm", "install-global", "npm", persistencePins.npm);
362
+ action("configure-npm-prefix", "configure", "npm-prefix");
363
+ } else if (persistable) {
364
+ const npm = node.npm === false;
365
+ if (npm && pnpm) action("persist-package-managers", "install-global", "package-managers");
366
+ else if (npm) action("persist-npm", "install-global", "npm", persistencePins.npm);
367
+ else if (pnpm) action("persist-pnpm", "install-global", "pnpm", persistencePins.pnpm);
368
+ }
369
+ if (installPi) action("install-pi", "install-global", "pi", requirements.pi);
370
+ if (updatePi) action("update-pi", "upgrade", "pi", PI_INSTALL_VERSION);
371
+ if (missingShell) action("install-shell", "install-global", "shell", requirements.shell);
372
+ // Global gentle-pi postinstall owns native provisioning when Shell is missing.
373
+ // Otherwise the later runner must reuse that existing installer, not duplicate it.
374
+ if (needsNative && !missingShell) action("provision-native", "existing-installer", "gentleAi", requirements.gentleAi);
375
+ if (tools.setup.status !== "reusable") action("setup-shell", "normal-setup", "shell");
376
+ action("verify-readiness", "verify", "stack");
377
+ // Main overlays the verified release installation with both latest main commits;
378
+ // a stack that is already set up installs nothing (`gentle-shell upgrade --channel main` switches it).
379
+ const mainBuild = main && actions.length > 1;
380
+ if (mainBuild) {
381
+ action("build-gentle-ai-main", "build-native-main", "gentleAi");
382
+ action("install-shell-main", "install-global", "shell");
383
+ action("record-channel", "configure", "channel");
384
+ }
385
+ if (mainBuild || windowsBuild) acquireGo(goAt);
386
+ return { tools, blockers, actions, ready: actions.length === 1 };
387
+ }