gentle-pi 3.7.0 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (590) hide show
  1. package/README.md +155 -10
  2. package/assets/agents/gentle-ai-explore.md +13 -6
  3. package/assets/agents/gentle-ai-verify.md +27 -6
  4. package/assets/agents/gentle-ai-worker.md +13 -15
  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 +75 -169
  9. package/assets/orchestrator-memory.md +12 -26
  10. package/assets/orchestrator-prompts.md +48 -0
  11. package/assets/orchestrator-skills.md +1 -1
  12. package/assets/orchestrator-tracking.md +53 -0
  13. package/assets/orchestrator-verification.md +28 -0
  14. package/assets/orchestrator-writer.md +38 -0
  15. package/assets/orchestrator.md +30 -38
  16. package/assets/sounds/LICENSE.md +52 -0
  17. package/assets/sounds/attention.wav +0 -0
  18. package/assets/sounds/error.wav +0 -0
  19. package/assets/sounds/success.wav +0 -0
  20. package/assets/support/strict-tdd-verify.md +4 -266
  21. package/assets/support/strict-tdd.md +8 -360
  22. package/bin/engram-guard +9 -0
  23. package/bin/gentle-shell-install.mjs +302 -0
  24. package/bin/gentle-shell.mjs +956 -50
  25. package/contracts/review-provider-contract-mirror/provider-contract.lock.json +7 -7
  26. package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/manifest.json +2 -2
  27. package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/orchestration/pi.md +16 -4
  28. package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/generated/provider-capabilities.baseline.json +2 -2
  29. package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/generated/provider-roles.baseline.json +1 -1
  30. package/docs/bundled-install.md +397 -0
  31. package/docs/delegated-verification.md +27 -4
  32. package/docs/gentle-agents-activity.md +537 -0
  33. package/docs/gentle-shell-uninstall.md +136 -0
  34. package/docs/gentle-shell.md +165 -51
  35. package/docs/install-wizard.md +2040 -0
  36. package/docs/native-authority-architecture.md +3 -3
  37. package/docs/permission-modes.md +82 -0
  38. package/docs/prompt-history.md +280 -0
  39. package/docs/readme-reference.md +364 -231
  40. package/docs/render-cache-performance.md +28 -0
  41. package/docs/sound-notifications-proposal.md +191 -0
  42. package/docs/sound-notifications.md +114 -0
  43. package/docs/telemetry.md +1 -1
  44. package/docs/yolo-mode.md +86 -0
  45. package/extensions/child-context.ts +35 -0
  46. package/extensions/child-safety.ts +25 -0
  47. package/extensions/codegraph-tools.ts +18 -5
  48. package/extensions/gentle-agents.ts +1093 -326
  49. package/extensions/gentle-ai.ts +1636 -890
  50. package/extensions/gentle-herdr-activity.ts +81 -0
  51. package/extensions/gentle-notifications.ts +104 -0
  52. package/extensions/gentle-shell.ts +1619 -136
  53. package/extensions/gentle-stats.ts +101 -0
  54. package/extensions/gentle-todo.ts +126 -20
  55. package/extensions/history/atomic-write.ts +38 -0
  56. package/extensions/history/hide-prompts.ts +183 -0
  57. package/extensions/history/index.ts +1424 -0
  58. package/extensions/history/load-shared-history.ts +39 -0
  59. package/extensions/history/selector-helpers.ts +538 -0
  60. package/extensions/history/session-scan.ts +233 -0
  61. package/extensions/history/store.ts +1119 -0
  62. package/extensions/nan-provider.ts +6 -0
  63. package/extensions/pi-pretty.ts +39 -3
  64. package/extensions/quiet-tools.ts +254 -92
  65. package/extensions/resume-hint.ts +60 -0
  66. package/extensions/skill-registry.ts +191 -58
  67. package/extensions/startup-banner.ts +71 -33
  68. package/lib/agent-assets.ts +604 -0
  69. package/lib/agent-home.ts +10 -1
  70. package/lib/agent-model-resolution.ts +80 -0
  71. package/lib/agent-profile-pin.ts +12 -0
  72. package/lib/agent-profiles.ts +13 -3
  73. package/lib/agents-completion-delivery.ts +11 -3
  74. package/lib/agents-message-delivery.ts +181 -0
  75. package/lib/agents-messaging.ts +1 -1
  76. package/lib/agents-protocol.ts +99 -0
  77. package/lib/agents-runner.ts +180 -107
  78. package/lib/agents-view.ts +26 -3
  79. package/lib/agents-widget.ts +16 -9
  80. package/lib/append-system-prompt.ts +21 -0
  81. package/lib/auto-permission-classifier.ts +280 -0
  82. package/lib/auto-permission-runtime.ts +268 -0
  83. package/lib/background-jobs-tools.ts +202 -0
  84. package/lib/background-jobs.ts +253 -0
  85. package/lib/background-monitor.ts +154 -0
  86. package/lib/bounded-writer-admission.ts +220 -0
  87. package/lib/builtin-codemode-optout.ts +166 -0
  88. package/lib/card-content-policy.ts +85 -0
  89. package/lib/card-style-policy.ts +60 -0
  90. package/lib/child-context-files.ts +166 -0
  91. package/lib/child-package-injection.ts +62 -0
  92. package/lib/codemode-renderer.ts +195 -0
  93. package/lib/command-palette-catalog.ts +5 -10
  94. package/lib/command-palette.ts +25 -14
  95. package/lib/context-threshold.ts +26 -0
  96. package/lib/destructive-command-guard.ts +144 -0
  97. package/lib/gentle-ai-binary.ts +109 -4
  98. package/lib/gentle-ai-elapsed-store.ts +87 -0
  99. package/lib/gentle-ai-renderer.ts +196 -39
  100. package/lib/gentle-shell-bundled.ts +517 -0
  101. package/lib/gentle-shell-launcher.ts +89 -27
  102. package/lib/gentle-shell-resume-hint.ts +176 -0
  103. package/lib/gentle-shell-startup-flow.ts +54 -0
  104. package/lib/gentle-shell-startup-update.ts +92 -0
  105. package/lib/gentle-shell-uninstall.ts +840 -0
  106. package/lib/gentle-shell-update-panel.ts +231 -0
  107. package/lib/herdr-activity.ts +183 -0
  108. package/lib/history-capture-policy.ts +95 -0
  109. package/lib/inprocess-reviewer.ts +3 -0
  110. package/lib/jobs-sidebar-state.ts +20 -0
  111. package/lib/jobs-view.ts +161 -0
  112. package/lib/model-price-ratio.ts +83 -0
  113. package/lib/model-routing-authority.ts +5 -1
  114. package/lib/nan-provider.ts +249 -0
  115. package/lib/native-review-cli.ts +242 -180
  116. package/lib/notification-audio-native.ts +162 -0
  117. package/lib/notification-audio-windows.ts +399 -0
  118. package/lib/notification-audio.ts +332 -0
  119. package/lib/notification-customize.ts +241 -0
  120. package/lib/notification-events.ts +113 -0
  121. package/lib/notification-policy.ts +148 -0
  122. package/lib/notification-pulse-client.ts +367 -0
  123. package/lib/notification-pulse-protocol.ts +305 -0
  124. package/lib/notification-pulse-stream.ts +213 -0
  125. package/lib/notification-pulse-worker.ts +66 -0
  126. package/lib/notification-scheduler.ts +173 -0
  127. package/lib/notification-service.ts +202 -0
  128. package/lib/notification-ui.ts +87 -0
  129. package/lib/odd-phase-inference.ts +231 -0
  130. package/lib/odd-phase.ts +141 -0
  131. package/lib/orchestrator-alias.ts +20 -0
  132. package/lib/orchestrator-catalog.ts +79 -0
  133. package/lib/orchestrator-consultation.ts +61 -0
  134. package/lib/orchestrator-discovery.ts +49 -0
  135. package/lib/orchestrator-helper-consent.ts +122 -0
  136. package/lib/orchestrator-helper.ts +167 -0
  137. package/lib/orchestrator-message-card.ts +105 -0
  138. package/lib/orchestrator-presence.ts +115 -14
  139. package/lib/orchestrator-scope.ts +80 -0
  140. package/lib/orchestrator-state.ts +107 -0
  141. package/lib/orchestrator-tool-card.ts +280 -0
  142. package/lib/orchestrator-work-search.ts +218 -0
  143. package/lib/orchestrator-work.ts +79 -0
  144. package/lib/overlay-repaint.ts +26 -0
  145. package/lib/permission-mode-policy.ts +59 -0
  146. package/lib/pi-tui-keys.ts +53 -0
  147. package/lib/review-candidate-view-owner.ts +73 -21
  148. package/lib/review-candidate-view.ts +115 -27
  149. package/lib/review-compact-contract.ts +2 -2
  150. package/lib/review-host-relay.ts +7 -2
  151. package/lib/review-integration-v2.ts +100 -22
  152. package/lib/review-publication-gate.ts +1 -1
  153. package/lib/review-reminder-receipt.ts +48 -8
  154. package/lib/review-risk-assessment.ts +227 -56
  155. package/lib/review-sidebar-state.ts +224 -0
  156. package/lib/review-transaction.ts +72 -3
  157. package/lib/runtime-metrics-children.ts +1 -1
  158. package/lib/runtime-metrics-native.ts +3 -2
  159. package/lib/selection-engine.ts +527 -0
  160. package/lib/session-change-capture.ts +5 -4
  161. package/lib/session-changes.ts +27 -22
  162. package/lib/session-messaging-grants.ts +135 -0
  163. package/lib/session-profile-binding.ts +83 -0
  164. package/lib/session-profile-freeze.ts +266 -0
  165. package/lib/session-worktree-registry.ts +87 -9
  166. package/lib/shell-bar.ts +215 -30
  167. package/lib/shell-card.ts +287 -18
  168. package/lib/shell-changes-view.ts +42 -14
  169. package/lib/shell-changes.ts +86 -71
  170. package/lib/shell-prompt.ts +102 -6
  171. package/lib/shell-sidebar-layout.ts +83 -15
  172. package/lib/shell-sidebar.ts +15 -1
  173. package/lib/shell-todo.ts +84 -31
  174. package/lib/shell-usage-view.ts +9 -4
  175. package/lib/shell-usage.ts +70 -13
  176. package/lib/stats-collector.ts +381 -0
  177. package/lib/stats-view.ts +431 -0
  178. package/lib/status-timing-diagnostics.ts +201 -0
  179. package/lib/telemetry-trigger.ts +3 -1
  180. package/lib/theme-customization.ts +52 -0
  181. package/lib/vim-editor-adapter.ts +383 -0
  182. package/lib/vim-normal-engine.ts +154 -0
  183. package/lib/vim-operator-engine.ts +416 -0
  184. package/lib/vim-policy.ts +49 -0
  185. package/lib/vim-visual-engine.ts +107 -0
  186. package/lib/visual-customization-policy.ts +119 -0
  187. package/lib/visual-customize-view.ts +481 -0
  188. package/lib/visual-profiles.ts +228 -0
  189. package/lib/writer-surfaces.ts +162 -0
  190. package/lib/yolo-session-policy.ts +317 -0
  191. package/package.json +20 -8
  192. package/runtime/child-package-injection.mjs +63 -0
  193. package/runtime/gentle-ai-binary.mjs +109 -4
  194. package/runtime/gentle-shell-bundled.mjs +518 -0
  195. package/runtime/gentle-shell-launcher.mjs +87 -25
  196. package/runtime/gentle-shell-resume-hint.mjs +177 -0
  197. package/runtime/gentle-shell-startup-flow.mjs +55 -0
  198. package/runtime/gentle-shell-startup-update.mjs +93 -0
  199. package/runtime/gentle-shell-uninstall.mjs +841 -0
  200. package/runtime/gentle-shell-update-panel.mjs +232 -0
  201. package/runtime/native-review-cli.mjs +239 -177
  202. package/runtime/review-integration-v2.mjs +100 -22
  203. package/runtime/review-risk-assessment.mjs +225 -54
  204. package/runtime/status-timing-diagnostics.mjs +202 -0
  205. package/runtime/telemetry-trigger.mjs +3 -1
  206. package/scripts/bootstrap.cmd +255 -0
  207. package/scripts/bootstrap.sh +168 -0
  208. package/scripts/build-distribution.mjs +157 -0
  209. package/scripts/build-installer-bundles.mjs +136 -0
  210. package/scripts/build-runtime-modules.mjs +8 -0
  211. package/scripts/bundled-install.mjs +1308 -0
  212. package/scripts/bundled-wizard.mjs +399 -0
  213. package/scripts/check-types.mjs +4 -6
  214. package/scripts/gentle-ai-installer.mjs +83 -35
  215. package/scripts/install-gentle-ai.mjs +48 -1
  216. package/scripts/install-tui-mode-setting.mjs +10 -1
  217. package/scripts/install-wizard-preview.mjs +154 -0
  218. package/scripts/installer-downloads.mjs +648 -0
  219. package/scripts/installer-preflight.mjs +387 -0
  220. package/scripts/installer-probes.mjs +564 -0
  221. package/scripts/installer-runner.mjs +1102 -0
  222. package/scripts/installer-server.mjs +881 -0
  223. package/scripts/installer-windows-artifacts.json +15 -0
  224. package/scripts/installer-windows.mjs +752 -0
  225. package/scripts/main-channel.mjs +452 -0
  226. package/scripts/mirror-odd-routing.mjs +2 -2
  227. package/scripts/npm-pack-result.mjs +31 -0
  228. package/scripts/render-cache-performance-worker.mjs +205 -0
  229. package/scripts/render-cache-performance.mjs +115 -0
  230. package/scripts/run-test-suite.mjs +90 -0
  231. package/scripts/test-installer-acceptance.sh +161 -0
  232. package/scripts/test-packed-runner.mjs +54 -19
  233. package/scripts/types-baseline.json +2 -92
  234. package/scripts/verify-package-files.mjs +91 -38
  235. package/skills/branch-pr/SKILL.md +24 -52
  236. package/skills/chained-pr/SKILL.md +31 -15
  237. package/skills/chained-pr/references/chaining-details.md +31 -20
  238. package/skills/gentle-ai/SKILL.md +16 -21
  239. package/skills/issue-creation/SKILL.md +8 -2
  240. package/skills/issue-creation/references/delegated-workflow-actions.md +19 -0
  241. package/skills/work-unit-commits/SKILL.md +4 -3
  242. package/tests/agent-home.test.ts +56 -3
  243. package/tests/agent-model-resolution.test.ts +113 -0
  244. package/tests/agent-profiles.test.ts +30 -0
  245. package/tests/agent-risk-escalation.test.ts +123 -0
  246. package/tests/agents-completion-delivery.test.ts +17 -0
  247. package/tests/agents-fake-child.ts +2 -2
  248. package/tests/agents-message-delivery.test.ts +106 -0
  249. package/tests/agents-protocol.test.ts +97 -0
  250. package/tests/agents-queries.test.ts +9 -9
  251. package/tests/agents-runner-process.test.ts +31 -0
  252. package/tests/agents-runner.test.ts +467 -93
  253. package/tests/agents-view-thread-identity.test.ts +169 -0
  254. package/tests/agents-view.test.ts +8 -2
  255. package/tests/agents-widget.test.ts +154 -15
  256. package/tests/append-system-prompt-route.test.ts +160 -0
  257. package/tests/append-system-prompt.test.ts +46 -0
  258. package/tests/artifact-language.test.ts +19 -213
  259. package/tests/ask-user-question.test.ts +44 -1
  260. package/tests/asset-installation-runtime.test.ts +5 -16
  261. package/tests/auto-mode-invariants.test.ts +258 -0
  262. package/tests/auto-mode-sdk.test.ts +57 -0
  263. package/tests/auto-mode.test.ts +49 -0
  264. package/tests/auto-permission-classifier.test.ts +283 -0
  265. package/tests/auto-permission-runtime.test.ts +196 -0
  266. package/tests/autonomous-guard.test.ts +69 -1
  267. package/tests/background-cache-warming.test.ts +2 -1
  268. package/tests/background-jobs.test.ts +227 -0
  269. package/tests/background-monitor.test.ts +195 -0
  270. package/tests/background-subagents-default-mode.test.ts +28 -5
  271. package/tests/bounded-stop-contract.test.ts +85 -0
  272. package/tests/bounded-writer-admission.test.ts +146 -0
  273. package/tests/branch-pr-skill.test.ts +43 -0
  274. package/tests/bridge-wake-lifecycle.test.ts +129 -0
  275. package/tests/bridge-wake-rendering.test.ts +56 -0
  276. package/tests/build-distribution.test.ts +269 -0
  277. package/tests/builtin-codemode-optout.test.ts +242 -0
  278. package/tests/bundled-install.test.ts +1319 -0
  279. package/tests/bundled-wizard.test.ts +505 -0
  280. package/tests/card-content-policy.test.ts +68 -0
  281. package/tests/card-style-policy.test.ts +87 -0
  282. package/tests/chained-pr-skill.test.ts +124 -0
  283. package/tests/child-codegraph-bootstrap.test.ts +74 -0
  284. package/tests/child-context-files.test.ts +255 -0
  285. package/tests/child-package-entrypoints.test.ts +65 -0
  286. package/tests/child-package-injection.test.ts +74 -0
  287. package/tests/child-safety.test.ts +94 -0
  288. package/tests/child-web-package.test.ts +224 -0
  289. package/tests/codegraph-tools.test.ts +35 -0
  290. package/tests/codemode-rendering.test.ts +596 -0
  291. package/tests/command-palette.test.ts +43 -7
  292. package/tests/context-threshold.test.ts +31 -0
  293. package/tests/delegate-for-reason-contract.test.ts +327 -0
  294. package/tests/delegated-key-learnings-contract.test.ts +3 -78
  295. package/tests/destructive-command-guard.test.ts +84 -0
  296. package/tests/devbinary/native-review-parity.devtest.ts +171 -3
  297. package/tests/devbinary/non-git-subagent-bootstrap.devtest.ts +193 -0
  298. package/tests/devbinary/pi-host-relay.devtest.ts +173 -3
  299. package/tests/engram-safety.test.ts +288 -0
  300. package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-28T10-00-00-000Z_aaa.jsonl +7 -0
  301. package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-29T23-00-00-000Z_bbb.jsonl +3 -0
  302. package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-30T08-00-00-000Z_ddd.jsonl +2 -0
  303. package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-30T09-00-00-000Z_eee.jsonl +3 -0
  304. package/tests/fixtures/stats/sessions/--work-alpha--/run-1/session.jsonl +2 -0
  305. package/tests/fixtures/stats/sessions/--work-beta--/2026-09-01T12-00-00-000Z_ccc.jsonl +2 -0
  306. package/tests/fixtures/stats/user-pi/sessions/--work-alpha--/2026-09-28T10-00-00-000Z_aaa.jsonl +2 -0
  307. package/tests/fixtures/stats/user-pi/sessions/--work-alpha--/2026-09-29T23-00-00-000Z_bbb.jsonl +4 -0
  308. package/tests/fixtures/stats/user-pi/sessions/--work-gamma--/2026-09-20T09-00-00-000Z_fff.jsonl +3 -0
  309. package/tests/generic-agent-tools.test.ts +69 -0
  310. package/tests/gentle-agents.test.ts +2804 -346
  311. package/tests/gentle-ai-binary-isolation.test.ts +135 -0
  312. package/tests/gentle-ai-binary.test.ts +21 -4
  313. package/tests/gentle-ai-child-guards.test.ts +202 -0
  314. package/tests/gentle-ai-dev-binary-surfacing.test.ts +125 -15
  315. package/tests/gentle-ai-elapsed-store.test.ts +68 -0
  316. package/tests/gentle-ai-installer.test.ts +566 -62
  317. package/tests/gentle-ai-prompt-model-routing.test.ts +123 -0
  318. package/tests/gentle-ai-renderer.test.ts +493 -14
  319. package/tests/gentle-ai.test.ts +1427 -236
  320. package/tests/gentle-card-text.ts +2 -1
  321. package/tests/gentle-herdr-activity.test.ts +100 -0
  322. package/tests/gentle-notifications.test.ts +427 -0
  323. package/tests/gentle-shell-bin.test.ts +896 -115
  324. package/tests/gentle-shell-bundled.test.ts +1002 -0
  325. package/tests/gentle-shell-launcher.test.ts +333 -65
  326. package/tests/gentle-shell-resume-hint.test.ts +270 -0
  327. package/tests/gentle-shell-startup-flow.test.ts +75 -0
  328. package/tests/gentle-shell-startup-update.test.ts +85 -0
  329. package/tests/gentle-shell-uninstall.test.ts +978 -0
  330. package/tests/gentle-shell-update-panel.test.ts +158 -0
  331. package/tests/gentle-shell.test.ts +4216 -119
  332. package/tests/gentle-stats.test.ts +152 -0
  333. package/tests/gentle-theme.test.ts +4 -1
  334. package/tests/gentle-todo.test.ts +333 -10
  335. package/tests/herdr-activity.test.ts +243 -0
  336. package/tests/history-agent-home.test.ts +81 -0
  337. package/tests/history-atomic-write.test.ts +57 -0
  338. package/tests/history-capture-policy.test.ts +102 -0
  339. package/tests/history-child-guard.test.ts +70 -0
  340. package/tests/history-command-registration.test.ts +164 -0
  341. package/tests/history-dedupe-entries.test.ts +123 -0
  342. package/tests/history-delete-backfill.test.ts +190 -0
  343. package/tests/history-delete-confirm.test.ts +460 -0
  344. package/tests/history-dispatch.test.ts +180 -0
  345. package/tests/history-drain-hidden.test.ts +110 -0
  346. package/tests/history-drain-order.test.ts +98 -0
  347. package/tests/history-expanded-globals.test.ts +62 -0
  348. package/tests/history-gc.test.ts +832 -0
  349. package/tests/history-header-layout.test.ts +265 -0
  350. package/tests/history-hide-prompts.test.ts +275 -0
  351. package/tests/history-lazy-windowing.test.ts +508 -0
  352. package/tests/history-legacy-migrate-v2.test.ts +297 -0
  353. package/tests/history-load-shared-history.test.ts +53 -0
  354. package/tests/history-max-results-cap.test.ts +76 -0
  355. package/tests/history-multi-reader.test.ts +203 -0
  356. package/tests/history-off-path.test.ts +170 -0
  357. package/tests/history-openflow-integration.test.ts +173 -0
  358. package/tests/history-overlay-margin.test.ts +326 -0
  359. package/tests/history-preview-layout.test.ts +93 -0
  360. package/tests/history-registry.test.ts +143 -0
  361. package/tests/history-scope-delete.test.ts +411 -0
  362. package/tests/history-search-caret-keys.test.ts +142 -0
  363. package/tests/history-seed-bootstrap.test.ts +170 -0
  364. package/tests/history-seed-regen.test.ts +129 -0
  365. package/tests/history-selector-windowing.test.ts +94 -0
  366. package/tests/history-session-scan-directory.test.ts +87 -0
  367. package/tests/history-session-scan-extract.test.ts +583 -0
  368. package/tests/history-session-writer.test.ts +351 -0
  369. package/tests/history-store-paths.test.ts +79 -0
  370. package/tests/history-tombstone-exact.test.ts +139 -0
  371. package/tests/history-wheel-mouse.test.ts +242 -0
  372. package/tests/inprocess-reviewer.test.ts +49 -19
  373. package/tests/install-gentle-ai-failure.test.ts +69 -0
  374. package/tests/install-tui-mode-setting.test.ts +15 -0
  375. package/tests/install-wizard.test.ts +1223 -0
  376. package/tests/installer-bundles.test.ts +122 -0
  377. package/tests/installer-posix-bootstrap.test.ts +1201 -0
  378. package/tests/installer-preflight.test.ts +750 -0
  379. package/tests/installer-probes.test.ts +1106 -0
  380. package/tests/installer-runner.test.ts +2158 -0
  381. package/tests/installer-server.test.ts +1567 -0
  382. package/tests/installer-windows-bootstrap.test.ts +2410 -0
  383. package/tests/issue-creation-skill.test.ts +61 -0
  384. package/tests/jobs-view.test.ts +160 -0
  385. package/tests/main-channel-upgrade.test.ts +389 -0
  386. package/tests/main-channel.test.ts +344 -0
  387. package/tests/maintainer/provider-relay.maintest.ts +13 -12
  388. package/tests/model-price-ratio.test.ts +116 -0
  389. package/tests/model-profiles-preservation.test.ts +153 -0
  390. package/tests/model-routing-authority.test.ts +16 -0
  391. package/tests/model-routing-package.test.ts +10 -0
  392. package/tests/nan-provider-runtime.test.ts +156 -0
  393. package/tests/nan-provider.test.ts +481 -0
  394. package/tests/native-binary-gate.test.ts +2 -0
  395. package/tests/native-review-capability-contract.test.ts +13 -1
  396. package/tests/native-review-cli.test.ts +239 -118
  397. package/tests/native-review-consent.test.ts +2 -2
  398. package/tests/native-review-parity-runtime.test.ts +101 -4
  399. package/tests/native-review-parity.test.ts +12 -48
  400. package/tests/native-risk-evidence-parity.test.ts +21 -0
  401. package/tests/no-self-pause-contract.test.ts +87 -0
  402. package/tests/notification-audio-native.test.ts +281 -0
  403. package/tests/notification-audio-windows.test.ts +581 -0
  404. package/tests/notification-audio.test.ts +548 -0
  405. package/tests/notification-customize.test.ts +655 -0
  406. package/tests/notification-events.test.ts +115 -0
  407. package/tests/notification-policy.test.ts +201 -0
  408. package/tests/notification-pulse-client.test.ts +356 -0
  409. package/tests/notification-pulse-protocol.test.ts +213 -0
  410. package/tests/notification-pulse-stream.test.ts +320 -0
  411. package/tests/notification-scheduler.test.ts +258 -0
  412. package/tests/notification-ui.test.ts +157 -0
  413. package/tests/npm-pack-result.test.ts +41 -0
  414. package/tests/odd-integration.test.ts +76 -0
  415. package/tests/odd-phase-inference.test.ts +213 -0
  416. package/tests/odd-phase-loader.test.ts +253 -0
  417. package/tests/odd-phase.test.ts +307 -0
  418. package/tests/odd-routing-canonical-ratchet.test.ts +60 -14
  419. package/tests/odd-routing-contract.test.ts +158 -50
  420. package/tests/orchestrator-budget.test.ts +43 -45
  421. package/tests/orchestrator-catalog.test.ts +146 -0
  422. package/tests/orchestrator-consultation-sdk.test.ts +614 -0
  423. package/tests/orchestrator-consultation.test.ts +111 -0
  424. package/tests/orchestrator-discovery.test.ts +241 -0
  425. package/tests/orchestrator-helper-consent.test.ts +171 -0
  426. package/tests/orchestrator-helper.test.ts +297 -0
  427. package/tests/orchestrator-message-card.test.ts +190 -0
  428. package/tests/orchestrator-presence.test.ts +23 -0
  429. package/tests/orchestrator-rdd-ownership.test.ts +8 -7
  430. package/tests/orchestrator-scope.test.ts +107 -0
  431. package/tests/orchestrator-state.test.ts +114 -0
  432. package/tests/orchestrator-status-publication-contract.test.ts +34 -0
  433. package/tests/orchestrator-tool-card.test.ts +398 -0
  434. package/tests/orchestrator-work-search.test.ts +318 -0
  435. package/tests/orchestrator-work.test.ts +147 -0
  436. package/tests/overlay-repaint.test.ts +74 -0
  437. package/tests/package-manifest.test.ts +344 -123
  438. package/tests/packed-runner-owned-path.test.ts +80 -0
  439. package/tests/parallel-reason-contract.test.ts +21 -0
  440. package/tests/parallel-writer-admission.test.ts +210 -0
  441. package/tests/permission-mode-policy.test.ts +194 -0
  442. package/tests/persona-single-channel.test.ts +10 -18
  443. package/tests/pi-pretty.test.ts +56 -0
  444. package/tests/provider-defect-handoff.test.ts +7 -14
  445. package/tests/quiet-bash-runtime.test.ts +76 -0
  446. package/tests/quiet-tool-rendering.test.ts +569 -187
  447. package/tests/rdd-aware-verification-contract.test.ts +103 -40
  448. package/tests/rdd-status-line.test.ts +9 -4
  449. package/tests/render-cache-performance.test.ts +48 -0
  450. package/tests/resume-hint-extension.test.ts +122 -0
  451. package/tests/review-agent-end-preflight.test.ts +237 -21
  452. package/tests/review-candidate-owner-retry.test.ts +22 -1
  453. package/tests/review-candidate-view.test.ts +399 -12
  454. package/tests/review-contract-prompt.test.ts +138 -44
  455. package/tests/review-controller-lock-status.test.ts +0 -1
  456. package/tests/review-controller-native-recovery.test.ts +327 -17
  457. package/tests/review-controller-native-routing.test.ts +866 -6
  458. package/tests/review-controller-workspace-root.test.ts +206 -6
  459. package/tests/review-controller.test.ts +2 -2
  460. package/tests/review-correction-lifecycle.test.ts +2 -1
  461. package/tests/review-gate.test.ts +7 -4
  462. package/tests/review-graph-schema.test.ts +2 -2
  463. package/tests/review-host-relay-routing.test.ts +342 -4
  464. package/tests/review-host-relay.test.ts +49 -2
  465. package/tests/review-integration-v2-forward.test.ts +221 -1
  466. package/tests/review-json-arguments.test.ts +167 -0
  467. package/tests/review-last-event-closure.test.ts +157 -8
  468. package/tests/review-ledger-contract.test.ts +14 -37
  469. package/tests/review-policy-judgment-day.test.ts +1 -1
  470. package/tests/review-policy-ordinary.test.ts +2 -2
  471. package/tests/review-relay-transport-agent.test.ts +66 -1
  472. package/tests/review-reminder-receipt.test.ts +47 -1
  473. package/tests/review-risk-assessment.test.ts +594 -26
  474. package/tests/review-session-standing-permission-controller.test.ts +16 -17
  475. package/tests/review-session-standing-permission-ipc.test.ts +6 -5
  476. package/tests/review-sidebar-state.test.ts +402 -0
  477. package/tests/review-test-fixtures.ts +7 -1
  478. package/tests/review-transaction.test.ts +18 -1
  479. package/tests/run-test-suite.test.ts +202 -0
  480. package/tests/runtime-harness.mjs +186 -800
  481. package/tests/runtime-metrics-children.test.ts +16 -23
  482. package/tests/runtime-metrics-extension.test.ts +1 -1
  483. package/tests/runtime-metrics-native.test.ts +31 -1
  484. package/tests/selection-engine.test.ts +451 -0
  485. package/tests/session-change-capture.test.ts +10 -10
  486. package/tests/session-changes-shell.test.ts +242 -48
  487. package/tests/session-changes.test.ts +17 -24
  488. package/tests/session-messaging-grants.test.ts +255 -0
  489. package/tests/session-profile-binding.test.ts +163 -0
  490. package/tests/session-profile-freeze.test.ts +381 -0
  491. package/tests/session-subject-routing-contract.test.ts +34 -0
  492. package/tests/session-worktree-registry.test.ts +134 -2
  493. package/tests/shell-bar.test.ts +422 -1
  494. package/tests/shell-card.test.ts +353 -1
  495. package/tests/shell-changes-view.test.ts +135 -32
  496. package/tests/shell-changes.test.ts +61 -62
  497. package/tests/shell-prompt.test.ts +94 -2
  498. package/tests/shell-render-cache-runtime.test.ts +69 -0
  499. package/tests/shell-render-cache.test.ts +129 -0
  500. package/tests/shell-sidebar-layout.test.ts +351 -22
  501. package/tests/shell-sidebar-scroll-benchmark.test.ts +255 -0
  502. package/tests/shell-todo.test.ts +207 -3
  503. package/tests/shell-usage-view.test.ts +28 -1
  504. package/tests/shell-usage.test.ts +108 -0
  505. package/tests/short-check-routing-contract.test.ts +49 -0
  506. package/tests/skill-registry-watcher.test.ts +119 -0
  507. package/tests/skill-registry.test.ts +357 -2
  508. package/tests/startup-banner.test.ts +246 -4
  509. package/tests/startup-home-repo.test.ts +170 -0
  510. package/tests/stats-collector.test.ts +195 -0
  511. package/tests/stats-view.test.ts +202 -0
  512. package/tests/status-timing-diagnostics.test.ts +283 -0
  513. package/tests/support/orchestrator-modules.ts +19 -0
  514. package/tests/task-size-routing-contract.test.ts +241 -0
  515. package/tests/telemetry-trigger.test.ts +107 -20
  516. package/tests/test-strength-contract.test.ts +124 -0
  517. package/tests/theme-customization.test.ts +72 -0
  518. package/tests/verify-agent-contract.test.ts +90 -0
  519. package/tests/verify-first-launch-contract.test.ts +139 -0
  520. package/tests/verify-package-files.test.ts +56 -0
  521. package/tests/vim-editor-adapter-host-resolution.test.ts +37 -0
  522. package/tests/vim-editor-adapter.test.ts +804 -0
  523. package/tests/vim-normal-engine.test.ts +101 -0
  524. package/tests/vim-operator-engine.test.ts +215 -0
  525. package/tests/vim-policy.test.ts +19 -0
  526. package/tests/vim-visual-engine.test.ts +52 -0
  527. package/tests/visual-customization-policy.test.ts +139 -0
  528. package/tests/visual-customize-view.test.ts +696 -0
  529. package/tests/visual-profiles.test.ts +87 -0
  530. package/tests/writer-edit-surface-scope.test.ts +59 -2
  531. package/tests/writer-surfaces.test.ts +199 -0
  532. package/tests/yolo-customize.test.ts +271 -0
  533. package/tests/yolo-mode-runtime.test.ts +188 -0
  534. package/tests/yolo-mode.test.ts +289 -0
  535. package/tests/yolo-session-policy.test.ts +59 -0
  536. package/themes/Gentle.json +2 -1
  537. package/themes/Gentleman-Cute.json +2 -1
  538. package/themes/Gentleman-Sexy.json +2 -1
  539. package/assets/agents/sdd-apply.md +0 -159
  540. package/assets/agents/sdd-archive.md +0 -228
  541. package/assets/agents/sdd-design.md +0 -49
  542. package/assets/agents/sdd-explore.md +0 -48
  543. package/assets/agents/sdd-init.md +0 -56
  544. package/assets/agents/sdd-onboard.md +0 -52
  545. package/assets/agents/sdd-proposal.md +0 -64
  546. package/assets/agents/sdd-remediate.md +0 -37
  547. package/assets/agents/sdd-research.md +0 -49
  548. package/assets/agents/sdd-spec.md +0 -192
  549. package/assets/agents/sdd-status.md +0 -54
  550. package/assets/agents/sdd-tasks.md +0 -108
  551. package/assets/agents/sdd-verify.md +0 -124
  552. package/assets/chains/sdd-full.chain.md +0 -83
  553. package/assets/chains/sdd-plan.chain.md +0 -56
  554. package/assets/chains/sdd-verify.chain.md +0 -43
  555. package/assets/sdd-orchestrator-workflow.md +0 -319
  556. package/assets/support/sdd-status-contract.md +0 -77
  557. package/docs/assets/diagrams/sdd-cycle.svg +0 -14
  558. package/extensions/sdd-init.ts +0 -816
  559. package/lib/openspec-deltas.ts +0 -156
  560. package/lib/sdd-preflight.ts +0 -1066
  561. package/lib/sdd-research-capabilities.ts +0 -94
  562. package/lib/sdd-status.ts +0 -26
  563. package/tests/fixtures/legacy/sdd-research-v2.5.0.md +0 -54
  564. package/tests/fixtures/native-review-cli/v2.1.3/bind-sdd.json +0 -25
  565. package/tests/fixtures/v0.10.7/assets/agents/sdd-apply.md +0 -132
  566. package/tests/openspec-deltas.test.ts +0 -209
  567. package/tests/sdd-agent-tools.test.ts +0 -156
  568. package/tests/sdd-archive-replay.test.ts +0 -82
  569. package/tests/sdd-classical-continuation.test.ts +0 -74
  570. package/tests/sdd-execution-routing-contract.test.ts +0 -44
  571. package/tests/sdd-managed-runtime-settlement.test.ts +0 -155
  572. package/tests/sdd-native-managed-uptake.test.ts +0 -243
  573. package/tests/sdd-no-attempts-contract.test.ts +0 -15
  574. package/tests/sdd-odd-integration.test.ts +0 -33
  575. package/tests/sdd-optional-research.test.ts +0 -124
  576. package/tests/sdd-planning-routing-contract.test.ts +0 -45
  577. package/tests/sdd-preflight-rpc-input.test.ts +0 -125
  578. package/tests/sdd-preflight.test.ts +0 -541
  579. package/tests/sdd-research-capabilities.test.ts +0 -114
  580. package/tests/sdd-research-live.test.ts +0 -241
  581. package/tests/sdd-selection-transport.test.ts +0 -653
  582. package/tests/sdd-status.test.ts +0 -9
  583. package/tests/sdd-task-truth.test.ts +0 -43
  584. /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/README.md +0 -0
  585. /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/schemas/lens.schema.json +0 -0
  586. /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/schemas/refuter.schema.json +0 -0
  587. /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/schemas/targeted-validator.schema.json +0 -0
  588. /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/vectors/lens.json +0 -0
  589. /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/vectors/refuter.json +0 -0
  590. /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/vectors/targeted-validator.json +0 -0
@@ -0,0 +1,452 @@
1
+ // Main channel: Gentle Shell and Gentle AI built from the latest `main` commits.
2
+ //
3
+ // Neither repository publishes main builds, so both are produced locally from
4
+ // an exact commit SHA resolved at install or upgrade time:
5
+ // - Gentle AI: `go install <module>@<sha>` in a sealed Go environment, so the
6
+ // module source is verified by Go's checksum database (sum.golang.org), as
7
+ // the Windows release build already is. The binary must report a version
8
+ // naming that commit, then it is activated through the existing dev-binary
9
+ // override, which never falls back to the pinned binary silently.
10
+ // - Gentle Shell: the exact commit's source tarball, versioned
11
+ // `<version>-main.<sha12>` and packed without `prepack` (which runs the full
12
+ // test suite), then installed globally like any local tarball.
13
+ // The recorded channel lets `gentle-shell upgrade` follow release or main.
14
+ import { dirname, isAbsolute, join, posix, relative, win32 } from "node:path";
15
+ import { gentleAiDevBinaryRegistrationPath, registerGentleAiDevBinary, unregisterGentleAiDevBinary } from "../runtime/gentle-ai-binary.mjs";
16
+ import { acquireGo as acquirePinnedGo, installedGo } from "./installer-downloads.mjs";
17
+ import { goAcquisition, pnpmGlobalBin, requirements } from "./installer-preflight.mjs";
18
+
19
+ export const SHELL_REPOSITORY = "Gentleman-Programming/gentle-shell";
20
+ export const GENTLE_AI_REPOSITORY = "Gentleman-Programming/gentle-ai";
21
+ export const GENTLE_AI_MAIN_PACKAGE = "github.com/gentleman-programming/gentle-ai/v4/cmd/gentle-ai";
22
+ export const CHANNEL_SCHEMA = "gentle-shell.channel/v1";
23
+ export const CHANNELS = Object.freeze(["release", "main"]);
24
+
25
+ const SHA = /^[0-9a-f]{40}$/;
26
+ const MINUTE = 60_000;
27
+ const deadlines = Object.freeze({ build: 15 * MINUTE, version: 30_000, extract: 2 * MINUTE, pack: 5 * MINUTE });
28
+ const MAX_SOURCE_BYTES = 100 * 1024 * 1024;
29
+
30
+ export class MainChannelError extends Error {
31
+ constructor(code, message, cause) {
32
+ super(`${code}: ${message}`, cause === undefined ? undefined : { cause });
33
+ this.name = "MainChannelError";
34
+ this.code = code;
35
+ }
36
+ }
37
+
38
+ /** Same config home as the dev-binary override: GENTLE_PI_CONFIG_HOME or ~/.pi/gentle-ai. */
39
+ export function configHome({ env, home }) {
40
+ return env.GENTLE_PI_CONFIG_HOME ?? join(home, ".pi", "gentle-ai");
41
+ }
42
+ export function channelStatePath(ctx) {
43
+ return join(configHome(ctx), "channel.json");
44
+ }
45
+
46
+ function validState(value) {
47
+ if (typeof value !== "object" || value === null || Array.isArray(value) || value.schema !== CHANNEL_SCHEMA) return null;
48
+ if (value.channel === "release" && Object.keys(value).length === 2) return { channel: "release" };
49
+ if (value.channel === "main" && Object.keys(value).length === 4 && SHA.test(value.shellCommit) && SHA.test(value.gentleAiCommit)) {
50
+ return { channel: "main", shellCommit: value.shellCommit, gentleAiCommit: value.gentleAiCommit };
51
+ }
52
+ return null;
53
+ }
54
+
55
+ /** The recorded channel; no record means release. A malformed record fails closed. */
56
+ export async function readChannel(ctx, fs) {
57
+ let text;
58
+ try {
59
+ text = await fs.readFile(channelStatePath(ctx), "utf8");
60
+ } catch (error) {
61
+ if (error?.code === "ENOENT") return { channel: "release" };
62
+ throw new MainChannelError("channel-state-invalid", `${channelStatePath(ctx)} could not be read`);
63
+ }
64
+ let parsed = null;
65
+ try { parsed = validState(JSON.parse(text)); } catch { parsed = null; }
66
+ if (!parsed) throw new MainChannelError("channel-state-invalid", `${channelStatePath(ctx)} is not a valid ${CHANNEL_SCHEMA} record; fix or remove it`);
67
+ return parsed;
68
+ }
69
+
70
+ export async function writeChannel(ctx, state, fs) {
71
+ const record = { schema: CHANNEL_SCHEMA, ...state };
72
+ if (!validState(record)) throw new MainChannelError("channel-state-invalid", "refusing to record an invalid channel state");
73
+ const path = channelStatePath(ctx);
74
+ await fs.mkdir(dirname(path), { recursive: true });
75
+ await fs.writeFile(`${path}.tmp`, `${JSON.stringify(record)}\n`);
76
+ await fs.rename(`${path}.tmp`, path);
77
+ }
78
+
79
+ /** Latest commit of `main` as an exact SHA (GitHub's raw-SHA media type). */
80
+ export async function resolveMainCommit(repository, { fetch }) {
81
+ let response;
82
+ try {
83
+ response = await fetch(`https://api.github.com/repos/${repository}/commits/main`, {
84
+ headers: { Accept: "application/vnd.github.sha", "User-Agent": "gentle-shell" },
85
+ });
86
+ } catch {
87
+ throw new MainChannelError("main-commit-unavailable", `the latest main commit of ${repository} could not be fetched`);
88
+ }
89
+ const sha = response?.ok ? String(await response.text()).trim() : "";
90
+ if (!SHA.test(sha)) {
91
+ throw new MainChannelError("main-commit-unavailable", `GitHub did not return the latest main commit of ${repository} (HTTP ${response?.status ?? "error"})`);
92
+ }
93
+ return sha;
94
+ }
95
+
96
+ export function mainVersion(baseVersion, commit) {
97
+ return `${baseVersion}-main.${commit.slice(0, 12)}`;
98
+ }
99
+
100
+ function succeeded(result) {
101
+ return result?.code === 0 && result.timedOut !== true && result.signal == null;
102
+ }
103
+
104
+ /** The failed command's bounded stderr tail as an error cause, or undefined when it printed nothing. */
105
+ function stderrCause(result) {
106
+ const tail = typeof result?.stderrTail === "string" ? result.stderrTail.trim() : "";
107
+ return tail.length > 0 ? new Error(tail) : undefined;
108
+ }
109
+
110
+ /** On Windows, System32's bsdtar by absolute path: an MSYS tar earlier on PATH (Git for
111
+ * Windows' usr\bin) reads a drive-letter archive path such as `D:\...` as a remote host. */
112
+ function tarCommand(platform) {
113
+ return platform === "win32" ? win32.join(process.env.SystemRoot ?? "C:\\Windows", "System32", "tar.exe") : "tar";
114
+ }
115
+
116
+ function sealedGoEnvironment(goPath, buildDirectory, platform) {
117
+ const temporary = join(buildDirectory, "tmp");
118
+ const base = {
119
+ // -modcacherw: Go makes its module cache read-only, which would stop its removal.
120
+ GOENV: "off", GOFLAGS: "-modcacherw", GOWORK: "off", GOTOOLCHAIN: "local", GOSUMDB: "sum.golang.org",
121
+ GONOSUMDB: "", GOPRIVATE: "", GONOPROXY: "", GOINSECURE: "", GOPROXY: "https://proxy.golang.org", CGO_ENABLED: "0",
122
+ GOBIN: join(buildDirectory, "gobin"), GOPATH: join(buildDirectory, "gopath"),
123
+ GOMODCACHE: join(buildDirectory, "gomodcache"), GOCACHE: join(buildDirectory, "gocache"),
124
+ };
125
+ if (platform === "win32") {
126
+ const root = process.env.SystemRoot ?? "C:\\Windows";
127
+ return { ...base, SystemRoot: root, TEMP: temporary, TMP: temporary, PATH: [dirname(goPath), join(root, "System32"), root].join(";") };
128
+ }
129
+ return { ...base, HOME: buildDirectory, TMPDIR: temporary, PATH: [dirname(goPath), "/usr/bin", "/bin"].join(":") };
130
+ }
131
+
132
+ /** Builds Gentle AI from `commit`, verifies the binary names it and registers it as the override. */
133
+ export async function buildMainGentleAi({ commit, ctx, platform, goPath, run, fs }) {
134
+ if (!SHA.test(commit)) throw new MainChannelError("main-gentle-ai-unverified", "the Gentle AI commit is not an exact SHA");
135
+ const executable = platform === "win32" ? "gentle-ai.exe" : "gentle-ai";
136
+ const directory = join(configHome(ctx), "main", "gentle-ai", commit);
137
+ const buildDirectory = join(configHome(ctx), "main", ".build");
138
+ await fs.rm(buildDirectory, { recursive: true, force: true });
139
+ const env = sealedGoEnvironment(goPath, buildDirectory, platform);
140
+ for (const path of [env.GOBIN, env.GOPATH, env.GOMODCACHE, env.GOCACHE, join(buildDirectory, "tmp")]) await fs.mkdir(path, { recursive: true });
141
+ try {
142
+ const install = await run(goPath, ["install", `${GENTLE_AI_MAIN_PACKAGE}@${commit}`], { env, cwd: buildDirectory, deadlineMs: deadlines.build, stderrTail: 4096 });
143
+ if (!succeeded(install)) throw new MainChannelError("main-gentle-ai-build-failed", `go install ${GENTLE_AI_MAIN_PACKAGE}@${commit} failed`, stderrCause(install));
144
+ await fs.mkdir(directory, { recursive: true });
145
+ const binaryPath = join(directory, executable);
146
+ await fs.copyFile(join(env.GOBIN, executable), binaryPath);
147
+ if (platform !== "win32") await fs.chmod(binaryPath, 0o755);
148
+ // Go stamps the module pseudo-version, whose suffix is the commit's first 12 characters.
149
+ const probe = await run(binaryPath, ["version"], { env: { PATH: env.PATH }, deadlineMs: deadlines.version });
150
+ const match = succeeded(probe) && /^gentle-ai (\S+)$/m.exec(String(probe.stdout ?? ""));
151
+ if (!match || !match[1].endsWith(`-${commit.slice(0, 12)}`)) {
152
+ await fs.rm(directory, { recursive: true, force: true });
153
+ throw new MainChannelError("main-gentle-ai-unverified", `the built Gentle AI does not report commit ${commit.slice(0, 12)}`);
154
+ }
155
+ registerGentleAiDevBinary(binaryPath, ctx, platform);
156
+ return { binaryPath, version: match[1] };
157
+ } finally {
158
+ await fs.rm(buildDirectory, { recursive: true, force: true });
159
+ }
160
+ }
161
+
162
+ /** Packs Gentle Shell from `commit` as `<version>-main.<sha12>` without running `prepack`. */
163
+ export async function packMainShell({ commit, ctx, fetch, run, pnpm, fs, platform = process.platform }) {
164
+ if (!SHA.test(commit)) throw new MainChannelError("main-shell-pack-failed", "the Gentle Shell commit is not an exact SHA");
165
+ const packages = join(configHome(ctx), "main", "packages");
166
+ const work = join(configHome(ctx), "main", ".source");
167
+ await fs.rm(work, { recursive: true, force: true });
168
+ await fs.mkdir(join(work, "src"), { recursive: true });
169
+ await fs.mkdir(packages, { recursive: true });
170
+ try {
171
+ let response;
172
+ try {
173
+ response = await fetch(`https://codeload.github.com/${SHELL_REPOSITORY}/tar.gz/${commit}`, { headers: { "User-Agent": "gentle-shell" } });
174
+ } catch {
175
+ response = null;
176
+ }
177
+ const bytes = response?.ok ? Buffer.from(await response.arrayBuffer()) : null;
178
+ if (!bytes || bytes.length === 0 || bytes.length > MAX_SOURCE_BYTES) {
179
+ throw new MainChannelError("main-shell-download-failed", `the Gentle Shell source for ${commit.slice(0, 12)} could not be downloaded`);
180
+ }
181
+ const archive = join(work, "source.tgz");
182
+ await fs.writeFile(archive, bytes);
183
+ const source = join(work, "src");
184
+ const extract = await run(tarCommand(platform), ["-xzf", archive, "-C", source, "--strip-components=1"], { deadlineMs: deadlines.extract, stderrTail: 4096 });
185
+ if (!succeeded(extract)) {
186
+ throw new MainChannelError("main-shell-pack-failed", "the Gentle Shell source archive could not be extracted", stderrCause(extract));
187
+ }
188
+ const manifestPath = join(source, "package.json");
189
+ const manifest = JSON.parse(await fs.readFile(manifestPath, "utf8"));
190
+ if (manifest?.name !== "gentle-pi" || typeof manifest.version !== "string") {
191
+ throw new MainChannelError("main-shell-pack-failed", "the downloaded source is not the gentle-pi package");
192
+ }
193
+ manifest.version = mainVersion(manifest.version, commit);
194
+ // prepack runs the full test suite; postinstall (the native binary) stays.
195
+ if (manifest.scripts) {
196
+ delete manifest.scripts.prepack;
197
+ delete manifest.scripts.prepare;
198
+ }
199
+ await fs.writeFile(manifestPath, `${JSON.stringify(manifest, null, "\t")}\n`);
200
+ const pack = await run(pnpm.command, [...pnpm.prefix, "pack", "--pack-destination", packages], { cwd: source, deadlineMs: deadlines.pack });
201
+ const tgz = join(packages, `gentle-pi-${manifest.version}.tgz`);
202
+ if (!succeeded(pack) || !(await fs.stat(tgz).then((info) => info.isFile(), () => false))) {
203
+ throw new MainChannelError("main-shell-pack-failed", "the Gentle Shell main package could not be packed");
204
+ }
205
+ return tgz;
206
+ } finally {
207
+ await fs.rm(work, { recursive: true, force: true });
208
+ }
209
+ }
210
+
211
+ /** The installer runner's main-channel adapter over real network and files. */
212
+ export function mainChannelAdapter({ fetch = globalThis.fetch, fs }) {
213
+ return {
214
+ resolveCommit: (repository) => resolveMainCommit(repository, { fetch }),
215
+ buildGentleAi: ({ commit, goPath, platform, ctx, run }) => buildMainGentleAi({ commit, ctx, platform, goPath, run, fs }),
216
+ packShell: ({ commit, ctx, run, pnpm, platform }) => packMainShell({ commit, ctx, fetch, run, pnpm, fs, platform }),
217
+ writeChannel: (ctx, state) => writeChannel(ctx, state, fs),
218
+ };
219
+ }
220
+
221
+ // ---------------------------------------------------------------------------
222
+ // `gentle-shell upgrade [--channel release|main]`
223
+ // ---------------------------------------------------------------------------
224
+
225
+ const STABLE = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
226
+ const UPGRADE_USAGE = "usage: gentle-shell upgrade [--channel release|main]";
227
+ const INSTALL_DEADLINE = 20 * MINUTE;
228
+
229
+ function parseUpgradeArgs(args) {
230
+ if (args.length === 0) return null;
231
+ let value;
232
+ if (args.length === 1 && args[0].startsWith("--channel=")) value = args[0].slice("--channel=".length);
233
+ else if (args.length === 2 && args[0] === "--channel") value = args[1];
234
+ if (!CHANNELS.includes(value)) throw new MainChannelError("upgrade-usage", UPGRADE_USAGE);
235
+ return value;
236
+ }
237
+
238
+ function older(left, right) {
239
+ const [a, b] = [STABLE.exec(left), STABLE.exec(right)];
240
+ if (!a || !b) return true;
241
+ for (let index = 1; index <= 3; index += 1) {
242
+ if (Number(a[index]) !== Number(b[index])) return Number(a[index]) < Number(b[index]);
243
+ }
244
+ return false;
245
+ }
246
+
247
+ function inside(parent, child) {
248
+ const path = relative(parent, child);
249
+ return path !== "" && !path.startsWith("..") && !isAbsolute(path);
250
+ }
251
+
252
+ /** The latest stable gentle-pi release on the npm registry. */
253
+ export async function latestRelease(fetch) {
254
+ let version = null;
255
+ try {
256
+ const response = await fetch("https://registry.npmjs.org/gentle-pi/latest", { headers: { Accept: "application/json" } });
257
+ if (response?.ok) version = (await response.json())?.version;
258
+ } catch {
259
+ version = null;
260
+ }
261
+ if (typeof version !== "string" || !STABLE.test(version)) {
262
+ throw new MainChannelError("latest-release-unavailable", "the latest gentle-pi release could not be read from the npm registry");
263
+ }
264
+ return version;
265
+ }
266
+
267
+ /**
268
+ * Which package manager owns an installed package (gentle-pi unless `name` says
269
+ * otherwise), from real (symlink-free) paths: pnpm when it lives under PNPM_HOME,
270
+ * npm only when it is `<npm root -g>/<name>` itself, otherwise null — for example
271
+ * an `npm link` of a source checkout, which must never be reinstalled over.
272
+ * Paths follow `platform` (this host by default): case-insensitive on Windows.
273
+ */
274
+ export function installOwner({ packageRoot, pnpmHome, npmRoot, name = "gentle-pi", platform = process.platform }) {
275
+ const path = platform === "win32" ? win32 : posix;
276
+ const comparable = (value) => (platform === "win32" ? value.toLowerCase() : value);
277
+ if (typeof packageRoot !== "string") return null;
278
+ if (typeof pnpmHome === "string") {
279
+ const rest = path.relative(pnpmHome, packageRoot);
280
+ if (rest !== "" && !rest.startsWith("..") && !path.isAbsolute(rest)) return "pnpm";
281
+ }
282
+ if (typeof npmRoot === "string" && comparable(path.relative(npmRoot, packageRoot)) === comparable(path.normalize(name))) return "npm";
283
+ return null;
284
+ }
285
+
286
+ /** How a package manager runs, as { command, prefix }: what the host's
287
+ * `invocation(name)` adapter returns when it has one (on Windows, node.exe and
288
+ * the manager's JS entry, or a native .exe), otherwise the command `which` finds,
289
+ * with no prefix. A Windows .cmd or .bat cannot run with shell:false, so it
290
+ * counts as missing. Null when it is missing.
291
+ */
292
+ async function invocationOf(name, { which, invocation }, platform) {
293
+ const found = invocation ? await invocation(name) : await which(name).then((command) => (command ? { command, prefix: [] } : null));
294
+ if (!found || (platform === "win32" && /\.(?:cmd|bat)$/i.test(found.command))) return null;
295
+ return found;
296
+ }
297
+
298
+ async function npmGlobalRoot(npm, run, fs) {
299
+ if (!npm) return null;
300
+ const result = await run(npm.command, [...npm.prefix, "root", "-g"], { deadlineMs: deadlines.version });
301
+ const reported = succeeded(result) ? String(result.stdout ?? "").trim().split(/\r?\n/).at(-1) : "";
302
+ return isAbsolute(reported) ? fs.realpath(reported).catch(() => null) : null;
303
+ }
304
+
305
+ const OWNER_ADVICE = Object.freeze({ upgrade: "update it the way you installed it", uninstall: "remove it the way you installed it" });
306
+
307
+ /**
308
+ * The package manager that owns this installation, as { name, command, prefix },
309
+ * or a typed refusal whose code names the `command` that asked (`upgrade` or
310
+ * `uninstall`). adapters: { fs, run, which, invocation? }, as runUpgrade takes them.
311
+ */
312
+ export async function ownerManager({ ctx, platform, packageRoot, adapters, command: purpose = "upgrade" }) {
313
+ const { fs, run } = adapters;
314
+ const bin = pnpmGlobalBin({ platform, env: { HOME: ctx.home, ...ctx.env } });
315
+ const npm = await invocationOf("npm", adapters, platform);
316
+ const [pnpmHome, root, npmRoot] = await Promise.all([
317
+ bin ? fs.realpath(bin.pnpmHome).catch(() => null) : null,
318
+ fs.realpath(packageRoot).catch(() => null),
319
+ npmGlobalRoot(npm, run, fs),
320
+ ]);
321
+ const name = installOwner({ packageRoot: root, pnpmHome, npmRoot, platform });
322
+ if (!name) {
323
+ throw new MainChannelError(`${purpose}-owner-unknown`, `neither pnpm nor npm owns ${root ?? packageRoot} (a linked source checkout, for example); ${OWNER_ADVICE[purpose]}`);
324
+ }
325
+ const manager = name === "npm" ? npm : await invocationOf("pnpm", adapters, platform);
326
+ if (!manager) {
327
+ throw new MainChannelError(`${purpose}-manager-missing`, `${name}, which owns this Gentle Shell installation, is not on PATH as a command this ${purpose} can run`);
328
+ }
329
+ return { name, ...manager };
330
+ }
331
+
332
+ async function installGlobal(manager, spec, run) {
333
+ const argv = manager.name === "pnpm" ? ["add", "-g", spec, "--allow-build=gentle-pi"] : ["install", "-g", spec];
334
+ if (!succeeded(await run(manager.command, [...manager.prefix, ...argv], { deadlineMs: INSTALL_DEADLINE }))) {
335
+ throw new MainChannelError("upgrade-install-failed", `${manager.name} could not install ${spec}`);
336
+ }
337
+ }
338
+
339
+ const REMOVE_GLOBAL = Object.freeze({ pnpm: Object.freeze(["remove", "-g", "gentle-pi"]), npm: Object.freeze(["uninstall", "-g", "gentle-pi"]) });
340
+
341
+ /** The command that removes the global gentle-pi with `name` (pnpm or npm), as a user types it. */
342
+ export function removeGlobalCommand(name) {
343
+ return `${name} ${REMOVE_GLOBAL[name].join(" ")}`;
344
+ }
345
+
346
+ /** Removes the global gentle-pi with the manager ownerManager resolved (its
347
+ * verified invocation); true when it succeeded. Nothing else is removed.
348
+ */
349
+ export async function removeGlobalShell(manager, run) {
350
+ return succeeded(await run(manager.command, [...manager.prefix, ...REMOVE_GLOBAL[manager.name]], { deadlineMs: INSTALL_DEADLINE }));
351
+ }
352
+
353
+ /** Removes the dev-binary override only when it points at a main build this module made. */
354
+ async function removeMainOverride(ctx, fs) {
355
+ try {
356
+ const registration = JSON.parse(await fs.readFile(gentleAiDevBinaryRegistrationPath(ctx), "utf8"));
357
+ if (typeof registration?.path === "string" && inside(join(configHome(ctx), "main", "gentle-ai"), registration.path)) {
358
+ unregisterGentleAiDevBinary(ctx);
359
+ }
360
+ } catch {
361
+ // No registration, or one that is not ours: leave it alone.
362
+ }
363
+ }
364
+
365
+ /** The Go a main build runs: the user's, only when it reports a stable version
366
+ * that meets requirements.go; otherwise the pinned Go the installer published
367
+ * under the config home (`tools/go`), downloaded and verified there by
368
+ * `acquire` when it is not published yet. A missing, older or unchecked Go is
369
+ * never run for the build, and the user's Go is never changed.
370
+ */
371
+ async function mainGo({ userGo, pinnedGo, acquire, run }) {
372
+ if (userGo) {
373
+ const result = await run(userGo, ["version"], { deadlineMs: deadlines.version });
374
+ const match = succeeded(result) && /^go version go(0|[1-9]\d*)\.(0|[1-9]\d*)(?:\.(0|[1-9]\d*))? \S+$/.exec(String(result.stdout ?? "").trim());
375
+ if (match && !older(`${match[1]}.${match[2]}.${match[3] ?? "0"}`, requirements.go)) return userGo;
376
+ }
377
+ if (pinnedGo) return pinnedGo;
378
+ try {
379
+ const { goPath } = await acquire();
380
+ return goPath;
381
+ } catch (error) {
382
+ // acquireGo wraps every failure; its cause names what went wrong (download, checksum, destination).
383
+ const reason = [error, error?.cause].filter((item) => item instanceof Error).map((item) => item.message).join(": ") || String(error);
384
+ throw new MainChannelError("main-requires-tools", `the main channel builds Gentle AI with Go ${requirements.go} or newer, and getting the pinned ` +
385
+ `Go ${goAcquisition.version} failed (${reason}); nothing was built, fix the cause, then run gentle-shell upgrade again`);
386
+ }
387
+ }
388
+
389
+ /**
390
+ * Updates Gentle Shell along its recorded channel (or the one `--channel` selects):
391
+ * release goes to the latest npm release, main to the latest `main` commits of
392
+ * Gentle Shell and Gentle AI, rebuilding only what moved. Returns the exit code;
393
+ * failures throw MainChannelError (`upgrade-usage` is a usage error).
394
+ * adapters: { fetch, run, fs, which, invocation?, migrate? }; npm and pnpm run as
395
+ * invocationOf resolves them (Windows hosts pass `invocation`). On release,
396
+ * `migrate({ latest, owner })` (S4) may move this installation to the bundled
397
+ * one first: null (no migration, or one that failed and left this package
398
+ * untouched) keeps the path below for this run, otherwise { code, installed }.
399
+ * `owner` is called only when a migration can happen.
400
+ */
401
+ export async function runUpgrade({ args, ctx, platform, arch = process.arch, packageRoot, currentVersion, adapters, out }) {
402
+ const { fetch, run, fs, which } = adapters;
403
+ const requested = parseUpgradeArgs(args);
404
+ const state = await readChannel(ctx, fs);
405
+ const channel = requested ?? state.channel;
406
+ if (channel === "release") {
407
+ const latest = await latestRelease(fetch);
408
+ const migrated = adapters.migrate ? await adapters.migrate({ latest, owner: () => ownerManager({ ctx, platform, packageRoot, adapters }) }) : null;
409
+ if (migrated) {
410
+ // The old package may stay: it follows the release channel, without a main override.
411
+ if (migrated.installed) {
412
+ await removeMainOverride(ctx, fs);
413
+ await writeChannel(ctx, { channel: "release" }, fs);
414
+ }
415
+ return migrated.code;
416
+ }
417
+ if (state.channel === "release" && !older(currentVersion, latest)) {
418
+ out(`gentle-shell ${currentVersion} is already the latest release.`);
419
+ return 0;
420
+ }
421
+ await installGlobal(await ownerManager({ ctx, platform, packageRoot, adapters }), `gentle-pi@${latest}`, run);
422
+ await removeMainOverride(ctx, fs);
423
+ await writeChannel(ctx, { channel: "release" }, fs);
424
+ out(`Updated gentle-shell ${currentVersion} to ${latest} (release).`);
425
+ return 0;
426
+ }
427
+ const [userGo, pnpm] = [await which("go"), await invocationOf("pnpm", adapters, platform)];
428
+ const pinnedGo = installedGo(join(configHome(ctx), "tools", "go"), platform, arch);
429
+ if (!pnpm) {
430
+ throw new MainChannelError("main-requires-tools", "the main channel packs Gentle Shell with pnpm; put it on PATH (Gentle AI is built with your Go, or with a pinned Go downloaded for the build)");
431
+ }
432
+ const acquire = adapters.acquireGo ?? (() => acquirePinnedGo({ root: join(configHome(ctx), "tools", "go"), platform, arch }));
433
+ const gentleAiCommit = await resolveMainCommit(GENTLE_AI_REPOSITORY, { fetch });
434
+ const shellCommit = await resolveMainCommit(SHELL_REPOSITORY, { fetch });
435
+ const onMain = state.channel === "main";
436
+ const aiCurrent = onMain && state.gentleAiCommit === gentleAiCommit;
437
+ const shellCurrent = onMain && state.shellCommit === shellCommit && currentVersion.endsWith(`-main.${shellCommit.slice(0, 12)}`);
438
+ const summary = `Gentle Shell ${shellCommit.slice(0, 12)}, Gentle AI ${gentleAiCommit.slice(0, 12)}`;
439
+ if (aiCurrent && shellCurrent) {
440
+ out(`gentle-shell is already at the latest main: ${summary}.`);
441
+ return 0;
442
+ }
443
+ if (!aiCurrent) await buildMainGentleAi({ commit: gentleAiCommit, ctx, platform, goPath: await mainGo({ userGo, pinnedGo, acquire, run }), run, fs });
444
+ if (!shellCurrent) {
445
+ const manager = await ownerManager({ ctx, platform, packageRoot, adapters });
446
+ const tgz = await packMainShell({ commit: shellCommit, ctx, fetch, run, pnpm, fs, platform });
447
+ await installGlobal(manager, tgz, run);
448
+ }
449
+ await writeChannel(ctx, { channel: "main", shellCommit, gentleAiCommit }, fs);
450
+ out(`Updated to the latest main: ${summary}.`);
451
+ return 0;
452
+ }
@@ -38,8 +38,8 @@ import (
38
38
  "fmt"
39
39
  "os"
40
40
 
41
- "github.com/gentleman-programming/gentle-ai/v3/internal/components/agentguidance"
42
- "github.com/gentleman-programming/gentle-ai/v3/internal/model"
41
+ "github.com/gentleman-programming/gentle-ai/v4/internal/components/agentguidance"
42
+ "github.com/gentleman-programming/gentle-ai/v4/internal/model"
43
43
  )
44
44
 
45
45
  func main() {
@@ -0,0 +1,31 @@
1
+ // npm <=11 emits an array; npm 12 emits a map keyed by package name.
2
+ // Both consumers pack exactly one package. Never reinterpret npm error objects.
3
+ const isRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
4
+ const isNonemptyString = (value) => typeof value === "string" && value.trim().length > 0;
5
+
6
+ export function normalizeNpmPackResult(value) {
7
+ let entries;
8
+ if (Array.isArray(value)) {
9
+ entries = value;
10
+ } else if (isRecord(value)) {
11
+ const pairs = Object.entries(value);
12
+ if (!pairs.every(([name, entry]) => isRecord(entry) && entry.name === name)) {
13
+ throw new Error("npm pack returned a malformed named package map");
14
+ }
15
+ entries = pairs.map(([, entry]) => entry);
16
+ } else {
17
+ throw new Error("npm pack returned neither a package array nor a named package map");
18
+ }
19
+ if (entries.length !== 1) throw new Error("npm pack did not return exactly one package");
20
+ const entry = entries[0];
21
+ if (!isRecord(entry) || !isNonemptyString(entry.name) || !isNonemptyString(entry.filename)
22
+ || !Array.isArray(entry.files) || entry.files.length === 0
23
+ || !entry.files.every((file) => isRecord(file) && isNonemptyString(file.path))) {
24
+ throw new Error("npm pack returned malformed package metadata");
25
+ }
26
+ return [entry];
27
+ }
28
+
29
+ export function parseNpmPackResult(output) {
30
+ return normalizeNpmPackResult(JSON.parse(output));
31
+ }