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
@@ -11,7 +11,7 @@ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-a
11
11
  import { __testing, createGentleAiExtension, PendingReviewConsentRegistry } from "../extensions/gentle-ai.ts";
12
12
  import { CandidateViewError, CandidateViewRegistry } from "../lib/review-candidate-view.ts";
13
13
  import { NATIVE_REVIEW_ERROR_CODE, NativeReviewCliError, NativeReviewConsentRequiredError, type NativeReviewCli } from "../lib/native-review-cli.ts";
14
- import { decodeReviewConsentV3, decodeReviewStatusV3, type ReviewCollectInputV3, type ReviewStatusV3 } from "../lib/review-integration-v2.ts";
14
+ import { decodeReviewLastEventClosureV1, decodeReviewConsentV3, decodeReviewStatusV3, type ReviewCollectInputV3, type ReviewStatusV3 } from "../lib/review-integration-v2.ts";
15
15
 
16
16
  const SHA = `sha256:${"a".repeat(64)}`;
17
17
  const TREE = "b".repeat(40);
@@ -129,6 +129,149 @@ test("STATUS renders the managed_assets_outdated continuation command as the act
129
129
  assert.equal(degraded.hint, undefined);
130
130
  });
131
131
 
132
+ test("caller-relative STATUS approved guidance preserves selected root against a different session", async () => {
133
+ const lineageId = "caller-relative-status";
134
+ const root = realpathSync(process.cwd());
135
+ const native = { targetStatus: async () => approvedAcknowledgementStatus(lineageId, root) } as unknown as NativeReviewCli;
136
+ const result = await __testing.executeReviewControllerOperation({ operation: "status", lineageId, workspaceRoot: root }, dirname(root), native);
137
+ assert.equal(result.next_action, `gentle_review {"operation":"acknowledge-approved","lineageId":"${lineageId}","workspaceRoot":${JSON.stringify(root)}}`);
138
+ });
139
+
140
+ test("caller-relative approved closure guidance preserves selected root and canonical omission", async () => {
141
+ const lineageId = "caller-relative-closure";
142
+ const root = realpathSync(process.cwd());
143
+ const { submission: _submission, artifactSubject: _artifactSubject, ...base } = collectInput(lineageId);
144
+ const input = { ...base, name: "provider_refuter", schema: "https://gentle-ai.dev/schema/review/refuter/v1", captureOperation: "review.capture-refuter", arguments: [
145
+ { name: "lineage", value: lineageId, token: `--lineage=${lineageId}` },
146
+ { name: "target", value: SHA, token: `--target=${SHA}` },
147
+ { name: "agent", value: "pi", token: "--agent=pi" },
148
+ { name: "execute", value: "true", token: "--execute=true" },
149
+ ] } as unknown as ReviewCollectInputV3;
150
+ const transition = approvedAcknowledgementStatus(lineageId, root).nextTransition;
151
+ assert.equal(transition?.kind, "execute");
152
+ if (transition?.kind !== "execute") throw new Error("expected acknowledgement transition");
153
+ const acknowledgement = transition.execute;
154
+ const native = {
155
+ targetStatus: async () => ({ ...status(lineageId), nextTransition: { kind: "collect", reasonCode: "provider_role_required", collect: { inputs: [input] } } }),
156
+ captureProviderRole: async () => ({ schema: "gentle-ai.review-last-event-closure/v1", operation: "review.capture-refuter", lineageId, state: "approved", storeRevision: SHA, acknowledgement }),
157
+ } as unknown as NativeReviewCli;
158
+ const result = await __testing.executeReviewCaptureOperation({ lineageId, workspaceRoot: root, collectBinding: JSON.stringify(input) }, dirname(root), native);
159
+ assert.equal(result.next_action, `gentle_review {"operation":"acknowledge-approved","lineageId":"${lineageId}","workspaceRoot":${JSON.stringify(root)}}`);
160
+ const canonical = await __testing.executeReviewCaptureOperation({ lineageId, collectBinding: JSON.stringify(input) }, root, native);
161
+ assert.equal(canonical.next_action, `gentle_review {"operation":"acknowledge-approved","lineageId":"${lineageId}"}`);
162
+ assert.deepEqual((result.closure as Record<string, unknown>).acknowledgement, (canonical.closure as Record<string, unknown>).acknowledgement);
163
+ // This root-only registry double models a remembered lineage without a
164
+ // frozen projection, and enforces the same root identity at admission.
165
+ const boundViews = {
166
+ resolveWorkspaceRoot: (requestedLineage: string) => {
167
+ assert.equal(requestedLineage, lineageId);
168
+ return root;
169
+ },
170
+ assertWorkspaceRoot: (requestedLineage: string, requestedRoot: string) => {
171
+ assert.equal(requestedLineage, lineageId);
172
+ assert.equal(realpathSync(requestedRoot), root);
173
+ },
174
+ hasProjection: () => false,
175
+ } as unknown as CandidateViewRegistry;
176
+ const bound = await __testing.executeReviewCaptureOperation({ lineageId, collectBinding: JSON.stringify(input) }, dirname(root), native, undefined, boundViews);
177
+ assert.equal(bound.next_action, canonical.next_action);
178
+ });
179
+
180
+ test("caller-relative grouped capture closure preserves selected workspace root", async (t) => {
181
+ t.after(() => __testing.setReviewHostRelayGroupRunnersForTesting());
182
+ const lineageId = "caller-relative-grouped";
183
+ const root = realpathSync(process.cwd());
184
+ const inputs = (["review-risk", "review-resilience", "review-readability", "review-reliability"] as const).map((lens, order) => {
185
+ const input = collectInput(lineageId);
186
+ input.arguments = input.arguments.map((argument) => {
187
+ const value = argument.name === "lens" ? lens : argument.name === "order" ? String(order) : argument.name === "subject-hash" ? `sha256:${String(order).repeat(64)}` : argument.value;
188
+ return { ...argument, value, token: `--${argument.name}=${value}` };
189
+ });
190
+ input.submission!.argumentTokens = [...input.arguments.map((argument) => argument.token!), "--input={{value}}"];
191
+ input.arguments = [...input.arguments, { name: "agent", value: "pi", token: "--agent=pi" }, { name: "materialize", value: "true", token: "--materialize=true" }];
192
+ input.artifactSubject = { ...input.artifactSubject!, lens, selectedOrder: order, subjectHash: `sha256:${String(order).repeat(64)}` };
193
+ return input;
194
+ });
195
+ const transition = approvedAcknowledgementStatus(lineageId, root).nextTransition;
196
+ if (transition?.kind !== "execute") throw new Error("expected acknowledgement transition");
197
+ const acknowledgement = transition.execute;
198
+ const native = { targetStatus: async (request: { cwd: string }) => {
199
+ assert.equal(request.cwd, root);
200
+ return { ...status(lineageId, inputs), repositoryContext: { capability: "review.opaque_repository_context", handle: `rctx1_${"c".repeat(64)}`, revision: SHA, targetIdentity: SHA } };
201
+ } } as unknown as NativeReviewCli;
202
+ const wireAcknowledgement = { ...acknowledgement, binding: { lineage_id: lineageId, revision: SHA, target_identity: SHA } };
203
+ const wireClosure = { schema: "gentle-ai.review-last-event-closure/v1", operation: "review/capture-result", lineage_id: lineageId, state: "approved", store_revision: SHA, action: "approved by reviewers", acknowledgement: wireAcknowledgement };
204
+ const decodedClosure = decodeReviewLastEventClosureV1(wireClosure);
205
+ assert.equal(decodedClosure.lineageId, lineageId);
206
+ assert.deepEqual(decodedClosure.acknowledgement?.raw, wireAcknowledgement);
207
+ let submissions = 0;
208
+ __testing.setReviewHostRelayGroupRunnersForTesting(async (requests) => requests.map((request) => ({ request, promptByteLength: 64, resultByteLength: 32 })), async () => {
209
+ submissions += 1;
210
+ return { promptByteLength: 64, resultByteLength: 32, submission: JSON.stringify(wireClosure) };
211
+ });
212
+ const result = await __testing.executeReviewCaptureGroupOperation({ lineageId, workspaceRoot: root, collectBindings: inputs.map((input) => JSON.stringify(input)), reviewerRunAcknowledged: true }, dirname(root), native);
213
+ assert.equal(submissions, 1, JSON.stringify(result));
214
+ assert.equal(result.outcome, "native-last-event-closure", JSON.stringify(result));
215
+ assert.equal(result.next_action, `gentle_review {"operation":"acknowledge-approved","lineageId":"${lineageId}","workspaceRoot":${JSON.stringify(root)}}`);
216
+ });
217
+
218
+ // Issue #1371: approved status mappings and closure envelopes provide additive next_action
219
+ test("STATUS on approved target renders additive next_action naming exact gentle_review acknowledge-approved invocation", async () => {
220
+ const lineageId = "status-approved-ack";
221
+ const native = { targetStatus: async () => approvedAcknowledgementStatus(lineageId) } as unknown as NativeReviewCli;
222
+ const result = await __testing.executeReviewControllerOperation({ operation: "status", lineageId }, process.cwd(), native);
223
+ assert.equal(result.status, "blocked");
224
+ assert.equal(result.next_action, `gentle_review {"operation":"acknowledge-approved","lineageId":"${lineageId}"}`);
225
+ });
226
+
227
+ test("STATUS on approved target preserves workspaceRoot in next_action when distinct from process cwd", async (t) => {
228
+ const lineageId = "status-approved-ack-worktree";
229
+ const worktreeRoot = repository(t);
230
+ const native = { targetStatus: async () => approvedAcknowledgementStatus(lineageId, worktreeRoot) } as unknown as NativeReviewCli;
231
+ const result = await __testing.executeReviewControllerOperation({ operation: "status", lineageId, workspaceRoot: worktreeRoot }, worktreeRoot, native);
232
+ assert.equal(result.status, "blocked");
233
+ assert.equal(result.next_action, `gentle_review {"operation":"acknowledge-approved","lineageId":"${lineageId}","workspaceRoot":${JSON.stringify(worktreeRoot)}}`);
234
+ });
235
+
236
+ test("invalid acknowledgement input returns self-healing next_action for canonical lineage and fallback slug otherwise", async (t) => {
237
+ const lineageId = "canonical-lineage-ack";
238
+ const native = { targetStatus: async () => approvedAcknowledgementStatus(lineageId) } as unknown as NativeReviewCli;
239
+
240
+ // 1. Controller-only input with canonical lineage (no workspaceRoot)
241
+ const rejectedWithInput = await __testing.executeReviewControllerOperation(
242
+ { operation: "acknowledge-approved", lineageId, input: "{}" },
243
+ process.cwd(),
244
+ native,
245
+ );
246
+ assert.equal(rejectedWithInput.outcome, "native-approved-acknowledgement-input-invalid");
247
+ assert.equal(rejectedWithInput.reason, "controller-only-input");
248
+ assert.equal(rejectedWithInput.field, "input");
249
+ assert.equal(rejectedWithInput.next_action, `gentle_review {"operation":"acknowledge-approved","lineageId":"${lineageId}"}`);
250
+
251
+ // 2. Controller-only input with canonical lineage and distinct workspaceRoot
252
+ const worktreeRoot = repository(t);
253
+ const rejectedWithWorktree = await __testing.executeReviewControllerOperation(
254
+ { operation: "acknowledge-approved", lineageId, input: "{}", workspaceRoot: worktreeRoot },
255
+ process.cwd(),
256
+ native,
257
+ );
258
+ assert.equal(rejectedWithWorktree.outcome, "native-approved-acknowledgement-input-invalid");
259
+ assert.equal(
260
+ rejectedWithWorktree.next_action,
261
+ `gentle_review {"operation":"acknowledge-approved","lineageId":"${lineageId}","workspaceRoot":${JSON.stringify(worktreeRoot)}}`,
262
+ );
263
+
264
+ // 3. Non-canonical / invalid lineage returns fallback slug
265
+ const rejectedNonCanonical = await __testing.executeReviewControllerOperation(
266
+ { operation: "acknowledge-approved", lineageId: "invalid lineage with newline\n", input: "{}" },
267
+ process.cwd(),
268
+ native,
269
+ );
270
+ assert.equal(rejectedNonCanonical.outcome, "native-approved-acknowledgement-input-invalid");
271
+ assert.equal(rejectedNonCanonical.reason, "controller-only-input");
272
+ assert.equal(rejectedNonCanonical.next_action, "resubmit-the-exact-lineage-without-controller-only-input");
273
+ });
274
+
132
275
  test("public acknowledgement relays one current provider vector and never replays after authority burn", async () => {
133
276
  const lineageId = "acknowledge-approved";
134
277
  const requests: Array<Record<string, unknown>> = [];
@@ -1779,6 +1922,7 @@ test("plain START adopts the pre-lineage selection retained by inspect and delet
1779
1922
  retained,
1780
1923
  );
1781
1924
  assert.equal(started.operation, "start");
1925
+ assert.ok(typeof started.result === "object" && started.result !== null && "lineage_id" in started.result);
1782
1926
  assert.equal(started.result.lineage_id, "review-started");
1783
1927
  assert.equal(starts.length, 1);
1784
1928
  assert.equal(requests.length, 3);
@@ -2234,6 +2378,7 @@ test("a failed START retains a pre-lineage selection for a same-candidate retry"
2234
2378
  { operation: "start", input: JSON.stringify({ mode: "ordinary" }) },
2235
2379
  cwd, native, undefined, null, undefined, retained,
2236
2380
  );
2381
+ assert.ok(typeof retried.result === "object" && retried.result !== null && "lineage_id" in retried.result);
2237
2382
  assert.equal(retried.result.lineage_id, "retried");
2238
2383
  assert.equal(starts, 2);
2239
2384
  assert.equal(retained.has(`${cwd}\u0000`), false);
@@ -2439,6 +2584,115 @@ test("targeted-validator provider vectors preserve their nonuniform native closu
2439
2584
  assert.equal("correction_target_identity" in result, false);
2440
2585
  });
2441
2586
 
2587
+ test("approved capture closure envelope carries additive next_action alongside untouched raw acknowledgement", async () => {
2588
+ const closureLineage = "review-approved-closure-next-action";
2589
+ const input = {
2590
+ name: "provider_targeted_validator",
2591
+ schema: "https://gentle-ai.dev/schema/review/targeted-validator/v1",
2592
+ captureOperation: "review.capture-validation",
2593
+ arguments: [
2594
+ { name: "lineage", value: closureLineage, token: `--lineage=${closureLineage}` },
2595
+ { name: "target", value: SHA, token: `--target=${SHA}` },
2596
+ { name: "agent", value: "pi", token: "--agent=pi" },
2597
+ { name: "execute", value: "true", token: "--execute=true" },
2598
+ ],
2599
+ } as unknown as ReviewCollectInputV3;
2600
+ const roleStatus = { ...status(closureLineage), nextTransition: { kind: "collect", reasonCode: "provider_role_required", collect: { inputs: [input] } } } as ReviewStatusV3;
2601
+ const ackContinuation = {
2602
+ operation: "review.acknowledge-approved",
2603
+ command: "gentle-ai review acknowledge-approved --provider-vector",
2604
+ arguments: [
2605
+ { name: "cwd", value: process.cwd(), token: `--cwd=${process.cwd()}` },
2606
+ { name: "lineage", value: closureLineage, token: `--lineage=${closureLineage}` },
2607
+ { name: "target", value: SHA, token: `--target=${SHA}` },
2608
+ { name: "expected-revision", value: SHA, token: `--expected-revision=${SHA}` },
2609
+ { name: "token", value: "provider-tok", token: "--token=provider-tok" },
2610
+ ],
2611
+ preconditions: [{ name: "state", value: "approved" }],
2612
+ binding: { lineageId: closureLineage, revision: SHA, targetIdentity: SHA },
2613
+ };
2614
+ const native = {
2615
+ targetStatus: async () => roleStatus,
2616
+ captureProviderRole: async () => ({
2617
+ schema: "gentle-ai.review-last-event-closure/v1",
2618
+ operation: "review/capture-validation",
2619
+ lineageId: closureLineage,
2620
+ state: "approved",
2621
+ action: "approved by validator",
2622
+ storeRevision: SHA,
2623
+ acknowledgement: {
2624
+ operation: "review.acknowledge-approved",
2625
+ command: "gentle-ai review acknowledge-approved --provider-vector",
2626
+ arguments: ackContinuation.arguments,
2627
+ preconditions: ackContinuation.preconditions,
2628
+ binding: ackContinuation.binding,
2629
+ raw: ackContinuation,
2630
+ },
2631
+ }),
2632
+ } as unknown as NativeReviewCli;
2633
+ const result = await __testing.executeReviewCaptureOperation({ lineageId: closureLineage, collectBinding: JSON.stringify(input) }, process.cwd(), native);
2634
+ assert.equal(result.status, "closed");
2635
+ assert.equal(result.outcome, "native-last-event-closure");
2636
+ assert.equal(result.next_action, `gentle_review {"operation":"acknowledge-approved","lineageId":"${closureLineage}"}`);
2637
+ assert.equal((result.closure as { next_action?: string }).next_action, `gentle_review {"operation":"acknowledge-approved","lineageId":"${closureLineage}"}`);
2638
+ assert.deepEqual((result.closure as { acknowledgement?: unknown }).acknowledgement, ackContinuation);
2639
+ });
2640
+
2641
+ test("approved capture closure envelope preserves workspaceRoot in next_action when distinct from process cwd", async (t) => {
2642
+ const closureLineage = "review-approved-closure-next-action-worktree";
2643
+ const worktreeRoot = repository(t);
2644
+ const input = {
2645
+ name: "provider_targeted_validator",
2646
+ schema: "https://gentle-ai.dev/schema/review/targeted-validator/v1",
2647
+ captureOperation: "review.capture-validation",
2648
+ arguments: [
2649
+ { name: "lineage", value: closureLineage, token: `--lineage=${closureLineage}` },
2650
+ { name: "target", value: SHA, token: `--target=${SHA}` },
2651
+ { name: "agent", value: "pi", token: "--agent=pi" },
2652
+ { name: "execute", value: "true", token: "--execute=true" },
2653
+ ],
2654
+ } as unknown as ReviewCollectInputV3;
2655
+ const roleStatus = { ...status(closureLineage), nextTransition: { kind: "collect", reasonCode: "provider_role_required", collect: { inputs: [input] } } } as ReviewStatusV3;
2656
+ const ackContinuation = {
2657
+ operation: "review.acknowledge-approved",
2658
+ command: "gentle-ai review acknowledge-approved --provider-vector",
2659
+ arguments: [
2660
+ { name: "cwd", value: worktreeRoot, token: `--cwd=${worktreeRoot}` },
2661
+ { name: "lineage", value: closureLineage, token: `--lineage=${closureLineage}` },
2662
+ { name: "target", value: SHA, token: `--target=${SHA}` },
2663
+ { name: "expected-revision", value: SHA, token: `--expected-revision=${SHA}` },
2664
+ { name: "token", value: "provider-tok", token: "--token=provider-tok" },
2665
+ ],
2666
+ preconditions: [{ name: "state", value: "approved" }],
2667
+ binding: { lineageId: closureLineage, revision: SHA, targetIdentity: SHA },
2668
+ };
2669
+ const native = {
2670
+ targetStatus: async () => roleStatus,
2671
+ captureProviderRole: async () => ({
2672
+ schema: "gentle-ai.review-last-event-closure/v1",
2673
+ operation: "review/capture-validation",
2674
+ lineageId: closureLineage,
2675
+ state: "approved",
2676
+ action: "approved by validator",
2677
+ storeRevision: SHA,
2678
+ acknowledgement: {
2679
+ operation: "review.acknowledge-approved",
2680
+ command: "gentle-ai review acknowledge-approved --provider-vector",
2681
+ arguments: ackContinuation.arguments,
2682
+ preconditions: ackContinuation.preconditions,
2683
+ binding: ackContinuation.binding,
2684
+ raw: ackContinuation,
2685
+ },
2686
+ }),
2687
+ } as unknown as NativeReviewCli;
2688
+ const result = await __testing.executeReviewCaptureOperation({ lineageId: closureLineage, collectBinding: JSON.stringify(input), workspaceRoot: worktreeRoot }, worktreeRoot, native);
2689
+ assert.equal(result.status, "closed");
2690
+ assert.equal(result.outcome, "native-last-event-closure");
2691
+ assert.equal(result.next_action, `gentle_review {"operation":"acknowledge-approved","lineageId":"${closureLineage}","workspaceRoot":${JSON.stringify(worktreeRoot)}}`);
2692
+ assert.equal((result.closure as { next_action?: string }).next_action, `gentle_review {"operation":"acknowledge-approved","lineageId":"${closureLineage}","workspaceRoot":${JSON.stringify(worktreeRoot)}}`);
2693
+ assert.deepEqual((result.closure as { acknowledgement?: unknown }).acknowledgement, ackContinuation);
2694
+ });
2695
+
2442
2696
  test("targeted-validator captures echo the distinct provider correction target identity", async () => {
2443
2697
  const closureLineage = "review-validator-correction-target";
2444
2698
  const correctionTarget = `sha256:${"d".repeat(64)}`;
@@ -38,7 +38,7 @@ function runtime(
38
38
  const tools = new Map<string, RegisteredTool>();
39
39
  let toolCall: ToolCallHandler | undefined;
40
40
  const handlers = new Map<string, (event: unknown, ctx: ExtensionContext) => unknown>();
41
- const dependencies = { nativeReviewCli, candidateViews } as unknown as Parameters<typeof createGentleAiExtension>[0];
41
+ const dependencies = { nativeReviewCli, candidateViews, processEnv: { GENTLE_PI_AGENTS_CHILD: "0" } } as unknown as Parameters<typeof createGentleAiExtension>[0];
42
42
  createGentleAiExtension(dependencies)({
43
43
  on(name: string, handler: ToolCallHandler) {
44
44
  if (name === "tool_call") toolCall = handler;
@@ -192,6 +192,47 @@ test("invalid bootstrap target selectors reject before native STATUS", async ()
192
192
  assert.equal(statusCalls, 0);
193
193
  });
194
194
 
195
+ test("START forwards the agent lens selection in canonical order and rejects malformed selections before native STATUS", async (t) => {
196
+ const cwd = repository(t);
197
+ writeFileSync(join(cwd, "app.ts"), "export const value = 2;\n");
198
+ const startRequests: Parameters<NativeReviewCli["start"]>[0][] = [];
199
+ const { controller } = runtime(fakeNative({
200
+ start: async (request) => {
201
+ startRequests.push(request);
202
+ return { lineageId: "lens-selection-lineage", state: "reviewing", riskLevel: "medium", selectedLenses: ["review-risk", "review-reliability"], changedFiles: 1, changedLines: 1, correctionBudget: 1, action: "created", lensesRequired: true };
203
+ },
204
+ }));
205
+ const started = await controller.execute("start-lenses", { operation: "start", input: JSON.stringify({ mode: "ordinary", lenses: ["reliability", "review-risk"], lensesReason: "changes token parsing" }) }, undefined, undefined, context(cwd));
206
+ assert.notEqual((started.details as { status?: string }).status, "blocked");
207
+ assert.equal(startRequests.length, 1);
208
+ assert.deepEqual(startRequests[0]?.lenses, ["review-risk", "review-reliability"]);
209
+ assert.equal(startRequests[0]?.lensesReason, "changes token parsing");
210
+
211
+ let statusCalls = 0;
212
+ const native = fakeNative({
213
+ reviewMode: async () => ({ operation: "status", scope: "clone", status: { global: "on", cloneLocal: "", effective: "on", source: "global" } }),
214
+ targetStatus: async () => { statusCalls += 1; throw new Error("must not reach STATUS"); },
215
+ start: async () => { throw new Error("must not reach START"); },
216
+ });
217
+ for (const input of [
218
+ { mode: "ordinary", lenses: ["risk"] },
219
+ { mode: "ordinary", lensesReason: "why" },
220
+ { mode: "ordinary", lenses: [], lensesReason: "why" },
221
+ { mode: "ordinary", lenses: "risk", lensesReason: "why" },
222
+ { mode: "ordinary", lenses: ["security"], lensesReason: "why" },
223
+ { mode: "ordinary", lenses: ["risk", "review-risk"], lensesReason: "why" },
224
+ { mode: "ordinary", lenses: ["risk"], lensesReason: " " },
225
+ { mode: "ordinary", lenses: ["risk"], lensesReason: "x".repeat(501) },
226
+ { mode: "ordinary", lenses: ["risk"], lensesReason: "why", focus: "risk" },
227
+ ]) {
228
+ const result = await __testing.executeReviewControllerOperation({ operation: "start", input: JSON.stringify(input) }, cwd, native);
229
+ assert.equal(result.status, "blocked", JSON.stringify(input));
230
+ assert.equal(result.reason, "lenses-invalid", JSON.stringify(input));
231
+ assert.equal(result.outcome, "native-start-input-invalid", JSON.stringify(input));
232
+ }
233
+ assert.equal(statusCalls, 0);
234
+ });
235
+
195
236
  test("repository discovery ignores ambient Git root overrides", async (t) => {
196
237
  const sessionRoot = repository(t);
197
238
  const targetRoot = repository(t, "gentle-pi-isolated-target-");
@@ -698,7 +739,7 @@ test("same-session START binding migrates to one validation capture without a FI
698
739
  captureProviderRole: async (request) => {
699
740
  captureCalls += 1;
700
741
  assert.equal(request.captureOperation, "review.capture-validation");
701
- return { schema: "gentle-ai.review-last-event-closure/v1", operation: "review.capture-validation", lineageId, state: "approved", storeRevision: `sha256:${"a".repeat(64)}` };
742
+ return { schema: "gentle-ai.review-last-event-closure/v1", operation: "review/capture-validation", lineageId, state: "approved", storeRevision: `sha256:${"a".repeat(64)}` };
702
743
  },
703
744
  });
704
745
  const { controller } = runtime(native, candidateViews);
@@ -371,9 +371,9 @@ test("failed START gives exact mode and serialization guidance and creates no li
371
371
  operation: "start",
372
372
  lineageId: "nested-start-input",
373
373
  idempotencyKey: "nested-start-input-key",
374
- input: { mode: "ordinary" },
374
+ input: { mode: "standard" },
375
375
  }, undefined, undefined, ctx),
376
- /START input must be a JSON string.*no lineage was created.*do not call STATUS or ADVANCE/is,
376
+ /only "ordinary" or "judgment-day".*no lineage was created.*do not call STATUS or ADVANCE/is,
377
377
  );
378
378
  await assert.rejects(
379
379
  controller.execute("invalid-json-start-input", {
@@ -61,7 +61,7 @@ test("passed is the only outcome that unlocks targeted validation", () => {
61
61
  assert.equal(step.kind, "run-targeted-validation");
62
62
  assert.equal(step.unlocksTargetedValidation, true);
63
63
  assert.equal(step.transactionOpen, false);
64
- assert.equal(step.escalation, undefined);
64
+ assert.equal(Object.hasOwn(step, "escalation"), false);
65
65
  // The provider issues the request; Pi must re-query STATUS for it rather
66
66
  // than inventing one, which is why the step names the operation instead of
67
67
  // carrying a fabricated payload.
@@ -95,6 +95,7 @@ test("verification_failed carries the prior identity as supersedes and demands a
95
95
  test("verification_failed never consumes budget even when lines were already charged", () => {
96
96
  const charged = { ...STATUS, changedLinesCharged: 40 };
97
97
  const step = resolveCorrectionStep(charged, evidence("verification_failed"));
98
+ assert.equal(step.kind, "recapture-required");
98
99
 
99
100
  // The invariant is about what THIS outcome adds, not about resetting prior
100
101
  // accounting: a failed verification must not move the needle at all.
@@ -14,6 +14,7 @@ import {
14
14
  recheckReleaseFastPathRemoteHeadV1,
15
15
  resolveConfiguredPushDestinationV1,
16
16
  type GateTargetV1,
17
+ type ReleaseGateTargetV1,
17
18
  type GhCommandRunnerV1,
18
19
  type ReleaseFastPathEvidenceV1,
19
20
  } from "../lib/review-publication-gate.ts";
@@ -177,14 +178,14 @@ function temporaryAuthority(t: test.TestContext): GateRepository & {
177
178
  test("unbranded receipts are rejected before lifecycle gate evaluation", (t) => {
178
179
  const { repository, finalTree, store, receipt } = temporaryAuthority(t);
179
180
  execFileSync("git", ["read-tree", finalTree], { cwd: repository });
180
- assert.throws(() => validateReviewGate({
181
+ assert.throws(() => Reflect.apply(validateReviewGate, undefined, [{
181
182
  store,
182
183
  receipt,
183
184
  target: { kind: GATE_TARGET_KIND.INTENDED_COMMIT, intended_commit_tree: finalTree },
184
185
  repositoryCwd: repository,
185
186
  idempotencyKey: "unbranded-gate",
186
187
  scopeBudget: budget(),
187
- }), /branded authoritative receipt/i);
188
+ }]), /branded authoritative receipt/i);
188
189
  });
189
190
 
190
191
  test("authoritative receipts cannot be validated through another repository store", (t) => {
@@ -333,6 +334,7 @@ test("push gate allows normal same-name updates while preserving exact-old and c
333
334
  const driftedUpdate = {
334
335
  kind: GATE_TARGET_KIND.PUSH,
335
336
  remote,
337
+ destination_id: target.destination_id,
336
338
  updates: [{
337
339
  ...target.updates[1],
338
340
  old_object: finalCommit,
@@ -347,6 +349,7 @@ test("push gate allows normal same-name updates while preserving exact-old and c
347
349
  const createOverExisting = {
348
350
  kind: GATE_TARGET_KIND.PUSH,
349
351
  remote,
352
+ destination_id: target.destination_id,
350
353
  updates: [{ ...target.updates[0], destination_ref: "refs/heads/final" }],
351
354
  } as const;
352
355
  assert.equal(
@@ -580,7 +583,7 @@ test("scope child claim and parent gate journal publish atomically across faults
580
583
  assert.equal(store.read(receipt.body.lineage_id).child_claims?.length, 1);
581
584
  });
582
585
 
583
- function releaseTarget(repository: GateRepository): GateTargetV1 {
586
+ function releaseTarget(repository: GateRepository): ReleaseGateTargetV1 {
584
587
  return {
585
588
  kind: GATE_TARGET_KIND.RELEASE,
586
589
  tag_ref: "refs/tags/v1.2.3",
@@ -628,7 +631,7 @@ test("release fast path independently accepts complete successful Check Runs des
628
631
  const repository = createGateRepository(t);
629
632
  setRemoteMain(repository, repository.finalCommit);
630
633
  const calls: string[][] = [];
631
- const checkRuns = (checks: unknown[], totalCount = checks.length, legacyStatus = "pending"): GhCommandRunnerV1 => (args) => {
634
+ const checkRuns = (checks: readonly unknown[], totalCount = checks.length, legacyStatus = "pending"): GhCommandRunnerV1 => (args) => {
632
635
  calls.push([...args]);
633
636
  if (args[1] === `repos/{owner}/{repo}/commits/${repository.finalCommit}/check-runs?per_page=100`) {
634
637
  return { status: 0, stdout: JSON.stringify({ total_count: totalCount, returned: checks.length, checks }) };
@@ -63,8 +63,8 @@ test("event schema commits a canonical reducer transition and input", () => {
63
63
  payload: { initial: true },
64
64
  reduced_state_hash: stateHash,
65
65
  } as never);
66
- assert.equal((event.body as Record<string, unknown>).reducer_transition, "start");
67
- assert.deepEqual((event.body as Record<string, unknown>).reducer_input, { source: "controller" });
66
+ assert.equal(event.body.reducer_transition, "start");
67
+ assert.deepEqual(event.body.reducer_input, { source: "controller" });
68
68
  assert.throws(() => createReviewEventV1({
69
69
  lineage_id: "lineage-a",
70
70
  sequence: 0,
@@ -211,6 +211,8 @@ function nativeHarness(statuses: readonly ReviewStatusV3[], unachievableResponde
211
211
  native: undefined as unknown as NativeReviewCli,
212
212
  };
213
213
  harness.native = {
214
+ start: async () => { throw new Error("unexpected START"); },
215
+ reviewStatus: async () => { throw new Error("unexpected compact STATUS"); },
214
216
  targetStatus: async (request) => {
215
217
  harness.statusCalls.push({ cwd: request.cwd, ...(request.lineageId === undefined ? {} : { lineageId: request.lineageId }), ...(request.agent === undefined ? {} : { agent: request.agent }) });
216
218
  const next = harness.statusQueue.shift();
@@ -226,7 +228,7 @@ async function runCapture(cwd: string, harness: RoutingHarness, lineageId: strin
226
228
  const selected = harness.statusQueue[0]?.nextTransition?.collect?.inputs[0];
227
229
  if (selected === undefined) throw new Error("capture test requires one current collect input");
228
230
  return await __testing.executeReviewCaptureOperation(
229
- { lineageId, collectBinding: JSON.stringify(selected), ...input },
231
+ { lineageId, collectBinding: selected, ...input },
230
232
  cwd,
231
233
  harness.native,
232
234
  undefined, undefined, undefined, false, modelRegistry,
@@ -436,9 +438,9 @@ test("Pi-authored review documents are rejected at the capture input boundary",
436
438
  assert.equal(relayCalls, 0);
437
439
  });
438
440
 
439
- function groupInputs(lineageId: string, revision = SHA): ReviewCollectInputV3[] { return ["review-risk", "review-resilience", "review-readability", "review-reliability"].map((lens, order) => relayCollectInput(lineageId, lens, order, true, "provider", revision)); }
441
+ function groupInputs(lineageId: string, revision = SHA): ReviewCollectInputV3[] { return (["review-risk", "review-resilience", "review-readability", "review-reliability"] as const).map((lens, order) => relayCollectInput(lineageId, lens, order, true, "provider", revision)); }
440
442
 
441
- async function runCaptureGroup(cwd: string, harness: RoutingHarness, lineageId: string, inputs: readonly ReviewCollectInputV3[], reviewerRunAcknowledged = true, modelRegistry?: InProcessReviewerRegistry): Promise<Record<string, unknown>> { return await __testing.executeReviewCaptureGroupOperation({ lineageId, collectBindings: inputs.map((input) => JSON.stringify(input)), reviewerRunAcknowledged }, cwd, harness.native, undefined, undefined, undefined, false, modelRegistry) as Record<string, unknown>; }
443
+ async function runCaptureGroup(cwd: string, harness: RoutingHarness, lineageId: string, inputs: readonly ReviewCollectInputV3[], reviewerRunAcknowledged = true, modelRegistry?: InProcessReviewerRegistry): Promise<Record<string, unknown>> { return await __testing.executeReviewCaptureGroupOperation({ lineageId, collectBindings: inputs.map((input, index) => index % 2 === 0 ? input : JSON.stringify(input)), reviewerRunAcknowledged }, cwd, harness.native, undefined, undefined, undefined, false, modelRegistry) as Record<string, unknown>; }
442
444
 
443
445
  function prepared(request: ReviewHostRelayRequest) { return { request, promptByteLength: 64, resultByteLength: 32 }; }
444
446
 
@@ -4,6 +4,7 @@ import { tmpdir } from "node:os";
4
4
  import { join } from "node:path";
5
5
  import test from "node:test";
6
6
  import { createNodeExecFileAdapter } from "../lib/native-review-cli.ts";
7
+ import { setBundledInstallForTesting } from "../lib/gentle-shell-bundled.ts";
7
8
  import { INPROCESS_REVIEWER_FAILURE, type InProcessReviewerOutcome, type InProcessReviewerRequest, type InProcessReviewerDeps, runInProcessReviewer } from "../lib/inprocess-reviewer.ts";
8
9
  import {
9
10
  REVIEW_HOST_RELAY_FAILURE,
@@ -53,7 +54,7 @@ const FAKE_GENTLE_AI = `#!/usr/bin/env node
53
54
  const fs = require("node:fs");
54
55
  const path = require("node:path");
55
56
  const argv = process.argv.slice(2);
56
- if (process.env.RELAY_FAKE_LOG) fs.appendFileSync(process.env.RELAY_FAKE_LOG, JSON.stringify({ argv, cwd: process.cwd(), contract: process.env.${GENTLE_PI_REVIEW_RELAY_CONTRACT_ENV} ?? null }) + "\\n");
57
+ if (process.env.RELAY_FAKE_LOG) fs.appendFileSync(process.env.RELAY_FAKE_LOG, JSON.stringify({ argv, cwd: process.cwd(), contract: process.env.${GENTLE_PI_REVIEW_RELAY_CONTRACT_ENV} ?? null, home: process.env.GENTLE_AI_HOME ?? null }) + "\\n");
57
58
  if (argv.some((token) => token === "--materialize" || token.startsWith("--materialize="))) {
58
59
  const mode = process.env.RELAY_FAKE_MATERIALIZE_MODE || "ok";
59
60
  if (mode === "ok") { process.stdout.write(Buffer.from(process.env.RELAY_FAKE_PROMPT_B64 || "", "base64")); process.exit(0); }
@@ -137,7 +138,7 @@ function harness(t: test.TestContext, overrides: Record<string, string> = {}): R
137
138
  return { directory, gentleAi, logPath, submitCapturePath, targetCwd, environment };
138
139
  }
139
140
 
140
- function readLog(path: string): Array<{ argv: string[]; contract: string | null; cwd?: string }> {
141
+ function readLog(path: string): Array<{ argv: string[]; contract: string | null; cwd?: string; home?: string | null }> {
141
142
  if (!existsSync(path)) return [];
142
143
  return readFileSync(path, "utf8").split("\n").filter((line) => line.length > 0).map((line) => JSON.parse(line));
143
144
  }
@@ -273,6 +274,24 @@ test("the central native CLI runner declares the relay contract on every gentle-
273
274
  }
274
275
  });
275
276
 
277
+ // S9: in a bundled install (the testing overlay stands in for this package
278
+ // running from it) both relay invocations get Gentle AI's isolation
279
+ // variables; otherwise neither does.
280
+ test("both relay gentle-ai invocations carry Gentle AI's isolation variables only in a bundled install", async (t) => {
281
+ const ordinary = harness(t);
282
+ await runRelay(ordinary);
283
+ assert.deepEqual(readLog(ordinary.logPath).map((call) => call.home), [null, null]);
284
+ const prefix = realpathSync(mkdtempSync(join(tmpdir(), "gentle-ai-isolation-relay-")));
285
+ setBundledInstallForTesting({ root: prefix });
286
+ t.after(() => {
287
+ setBundledInstallForTesting(undefined);
288
+ rmSync(prefix, { recursive: true, force: true });
289
+ });
290
+ const bundled = harness(t, { GENTLE_SHELL_PREFIX: prefix });
291
+ await runRelay(bundled);
292
+ assert.deepEqual(readLog(bundled.logPath).map((call) => call.home), [join(prefix, "gentle-ai"), join(prefix, "gentle-ai")]);
293
+ });
294
+
276
295
  test("relay contract constants are the compiled gentle-ai handshake values", () => {
277
296
  assert.equal(GENTLE_PI_REVIEW_RELAY_CONTRACT_ENV, "GENTLE_PI_REVIEW_RELAY_CONTRACT");
278
297
  assert.equal(GENTLE_PI_REVIEW_RELAY_CONTRACT, "gentle-pi.review-relay/v1");
@@ -1036,3 +1055,31 @@ test("the negotiated decoder carries the provider submission through the capture
1036
1055
  submission: { ...rawSubmission, values: [{ slot: "reviewer_result", domain: "artifact_path_or_stdin", substitution_location: rawSubmission.argument_tokens.length }] },
1037
1056
  }] } }), /substitution_location/);
1038
1057
  });
1058
+
1059
+ for (const transport of ["sse", "websocket", "websocket-cached", "auto", undefined] as const) {
1060
+ test(`relay forwards reviewer transport ${transport ?? "absent"} unchanged`, async (t) => {
1061
+ const fixture = harness(t);
1062
+ const reviewer = textReviewer(REVIEWER_TEXT);
1063
+ const result = await runRelay(fixture, { reviewerTransport: transport }, reviewer.runReviewer);
1064
+ assert.equal(reviewer.calls.length, 1);
1065
+ assert.equal(reviewer.calls[0]!.transport, transport);
1066
+ assert.equal(Object.hasOwn(reviewer.calls[0]!, "transport"), transport !== undefined);
1067
+ assert.equal(reviewer.calls[0]!.selection, "openai/gpt-5");
1068
+ assert.equal(result.resultByteLength, Buffer.byteLength(REVIEWER_TEXT));
1069
+ assert.equal(readFileSync(fixture.submitCapturePath, "utf8"), REVIEWER_TEXT);
1070
+ });
1071
+
1072
+ test(`group relay forwards reviewer transport ${transport ?? "absent"} to every completion`, async (t) => {
1073
+ const fixture = harness(t);
1074
+ const reviewer = textReviewer(REVIEWER_TEXT);
1075
+ const requests = reviewerGroupRequests(fixture).map((request) => ({ ...request, reviewerTransport: transport }));
1076
+ const results = await runReviewHostRelayReviewerGroup(requests, (request) => prepareReviewHostRelaySlot(request, reviewer.runReviewer));
1077
+ assert.equal(results.length, requests.length);
1078
+ assert.equal(reviewer.calls.length, requests.length);
1079
+ for (const request of reviewer.calls) {
1080
+ assert.equal(request.transport, transport);
1081
+ assert.equal(Object.hasOwn(request, "transport"), transport !== undefined);
1082
+ }
1083
+ assert.equal(readLog(fixture.logPath).length, requests.length, "group preparation only materializes, never submits");
1084
+ });
1085
+ }
@@ -947,7 +947,7 @@ test("review-acknowledged/v1 is disjoint from every prior captured identity in b
947
947
  ["status/v3", decodeReviewStatusV3],
948
948
  ["start/v3", decodeReviewStartV3],
949
949
  ["start/v4", decodeReviewStartV4],
950
- ["capabilities/v2", decodeReviewCapabilitiesV2],
950
+ ["capabilities/v2", (value) => decodeReviewCapabilitiesV2(value, CAPTURED_DIGEST)],
951
951
  ["consent/v2", decodeReviewConsentV2],
952
952
  ["consent/v3", decodeReviewConsentV3],
953
953
  ["last-event-closure/v1", decodeReviewLastEventClosureV1],
@@ -1004,3 +1004,23 @@ test("v7 escalation strictly decodes all causes and optional refuter evidence",
1004
1004
  assert.throws(() => decodeReviewStatusV3({ ...escalatedStatusV7(), schema: `gentle-ai.review-integration.status/v${version}` }), /not allowed/);
1005
1005
  }
1006
1006
  });
1007
+
1008
+ test("START/v4 accepts every risk reason code and signal the published start schema allows", () => {
1009
+ // gentle-ai publishes dangerous_sink (code and signal) and agent_escalation
1010
+ // (code and signal) in start-v4.schema.json; a high-risk START that names
1011
+ // them must decode instead of failing as schema-incompatible (#5374).
1012
+ const start = reviewingStartV4();
1013
+ start.risk_reasons = [
1014
+ { code: "dangerous_sink", signal: "dangerous_sink", path: "auth/session.py" },
1015
+ { code: "hot_path", signal: "auth", path: "auth/session.py" },
1016
+ { code: "process_boundary", signal: "shell_process", path: "auth/session.py" },
1017
+ { code: "agent_escalation", signal: "agent_escalation" },
1018
+ ];
1019
+ const decoded = decodeReviewStartV4(start);
1020
+ assert.deepEqual(decoded.riskReasons.map((reason) => reason.code), ["dangerous_sink", "hot_path", "process_boundary", "agent_escalation"]);
1021
+ assert.deepEqual(decoded.riskReasons.map((reason) => reason.signal), ["dangerous_sink", "auth", "shell_process", "agent_escalation"]);
1022
+
1023
+ const unknown = reviewingStartV4();
1024
+ unknown.risk_reasons = [{ code: "code_from_a_newer_cli" }];
1025
+ assert.throws(() => decodeReviewStartV4(unknown), /risk_reasons\[0\]\.code is unsupported/);
1026
+ });