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
@@ -67,21 +67,38 @@ test("native reclaim and recover preserve their exact authority bindings", async
67
67
  ]);
68
68
  });
69
69
 
70
- test("native abandon carries the exact discarded-work authorization and audit record", async () => {
70
+ test("native ABANDON rejects free-text reasons before launching native", async () => {
71
+ const queue = queuedAdapter([]);
71
72
  const request = {
72
- cwd: "/repo",
73
- lineage: "abandoned",
74
- expectedRevision: "revision-1",
75
- snapshotIdentity: SHA,
76
- capturedLensResults: ["00-risk.json"],
77
- findingsPresent: true,
78
- actor: "maintainer",
79
- reason: "discard candidate",
73
+ cwd: "/repo", lineage: "abandoned", expectedRevision: "revision-1", snapshotIdentity: SHA,
74
+ capturedLensResults: ["00-risk.json"], findingsPresent: true, actor: "maintainer",
75
+ reason: "discard candidate" as unknown as import("../lib/native-review-cli.ts").NativeReviewAbandonRequest["reason"],
80
76
  };
81
- const queue = queuedAdapter([{ stdout: JSON.stringify({ operation: "review/abandon", record: { schema: "gentle-ai.review-reclaim-audit/v1", lineage_id: "abandoned", status: "committed" } }) }]);
82
- const result = await client(queue.adapter).abandon({ ...request, maintainerAuthorization: nativeReviewAbandonAuthorization(request) });
83
- assert.equal(result.record.status, "committed");
84
- assert.deepEqual(queue.calls[0]?.arguments.slice(0, 10), ["review", "abandon", "--cwd", "/repo", "--lineage", "abandoned", "--expected-revision", "revision-1", "--actor", "maintainer"]);
77
+ await assert.rejects(
78
+ () => client(queue.adapter).abandon({ ...request, maintainerAuthorization: nativeReviewAbandonAuthorization(request) }),
79
+ /Native ABANDON reason must be "operator_disposition" or "retired_schema"/,
80
+ );
81
+ assert.equal(queue.calls.length, 0);
82
+ });
83
+
84
+ test("native abandon carries the exact discarded-work authorization and audit record", async () => {
85
+ for (const reason of ["operator_disposition", "retired_schema"] as const) {
86
+ const request = {
87
+ cwd: "/repo",
88
+ lineage: "abandoned",
89
+ expectedRevision: "revision-1",
90
+ snapshotIdentity: SHA,
91
+ capturedLensResults: ["00-risk.json"],
92
+ findingsPresent: true,
93
+ actor: "maintainer",
94
+ reason,
95
+ };
96
+ const queue = queuedAdapter([{ stdout: JSON.stringify({ operation: "review/abandon", record: { schema: "gentle-ai.review-reclaim-audit/v1", lineage_id: "abandoned", status: "committed" } }) }]);
97
+ const result = await client(queue.adapter).abandon({ ...request, maintainerAuthorization: nativeReviewAbandonAuthorization(request) });
98
+ assert.equal(result.record.status, "committed");
99
+ assert.deepEqual(queue.calls[0]?.arguments.slice(0, 10), ["review", "abandon", "--cwd", "/repo", "--lineage", "abandoned", "--expected-revision", "revision-1", "--actor", "maintainer"]);
100
+ assert.deepEqual(queue.calls[0]?.arguments.slice(10, 12), ["--reason", reason]);
101
+ }
85
102
  });
86
103
 
87
104
  // gentle-ai 0ed9225f ("close on the last causal event") removed evidence records
@@ -238,10 +255,26 @@ test("native recovery operations reject malformed canonical inputs before a proc
238
255
  assert.equal(queue.calls.length, 0);
239
256
  });
240
257
 
258
+ test("native RECOVER rejects malformed or unacknowledged committed selectors before launch", async () => {
259
+ const queue = queuedAdapter([]);
260
+ const request = { cwd: "/repo", predecessorLineage: "predecessor", expectedPredecessorRevision: "revision-1", successorLineage: "successor", disposition: "invalidated" as const, actor: "maintainer", reason: "recover authority" };
261
+ for (const selectors of [
262
+ { baseRef: " base", committedOnly: true },
263
+ { baseRef: "base\u0000", committedOnly: true },
264
+ { baseRef: "base" },
265
+ { baseRef: "base", committedOnly: false },
266
+ { committedOnly: true },
267
+ { committedOnly: false },
268
+ ]) {
269
+ await assert.rejects(() => client(queue.adapter).recover({ ...request, ...selectors }), /Native RECOVER (baseRef|committedOnly)/);
270
+ }
271
+ assert.equal(queue.calls.length, 0);
272
+ });
273
+
241
274
  test("partial maintenance failures preserve the provider audit record and unknown mutation outcome", async () => {
242
275
  const request = {
243
276
  cwd: "/repo", lineage: "abandoned", expectedRevision: "revision-1", snapshotIdentity: SHA,
244
- capturedLensResults: ["00-risk.json"], findingsPresent: true, actor: "maintainer", reason: "discard candidate",
277
+ capturedLensResults: ["00-risk.json"], findingsPresent: true, actor: "maintainer", reason: "operator_disposition" as const,
245
278
  };
246
279
  const queue = queuedAdapter([{ exitCode: 1, stdout: JSON.stringify({ operation: "review/abandon", record: { schema: "gentle-ai.review-reclaim-audit/v1", lineage_id: request.lineage, status: "partial" } }) }]);
247
280
  await assert.rejects(
@@ -290,7 +323,7 @@ test("native abandon rejects a legacy authorization before process launch", asyn
290
323
  capturedLensResults: ["00-risk.json"],
291
324
  findingsPresent: true,
292
325
  actor: "maintainer",
293
- reason: "discard candidate",
326
+ reason: "operator_disposition" as const,
294
327
  };
295
328
  const queue = queuedAdapter([]);
296
329
  await assert.rejects(
@@ -332,12 +365,145 @@ function interactiveContext(confirm: boolean): ExtensionContext {
332
365
  return { cwd: process.cwd(), hasUI: true, ui: { confirm: async () => confirm } } as unknown as ExtensionContext;
333
366
  }
334
367
 
368
+ const SHA2 = `sha256:${"b".repeat(64)}`;
369
+
370
+ function abandonedInventoryNative(calls: Array<Record<string, unknown>>): import("../lib/native-review-cli.ts").NativeReviewCli {
371
+ return {
372
+ reviewStatus: async () => ({
373
+ repository: "/canonical/repository",
374
+ complete: true,
375
+ authoritative: true,
376
+ entries: [
377
+ { version: "legacy-v1", status: "invalid", path: "/authority/legacy", problems: [] },
378
+ { version: "compact-v2", status: "active", path: "/authority/compact", lineageId: "stranded", revision: SHA, snapshotIdentity: SHA2, state: "correction_required", discardedWork: { capturedLensResults: ["00-review-risk", "01-review-resilience"], findingsPresent: true } },
379
+ ],
380
+ }),
381
+ abandon: async (request: Record<string, unknown>) => { calls.push(request); return { record: { schema: "gentle-ai.review-reclaim-audit/v1", lineage_id: "stranded", status: "committed" } }; },
382
+ } as unknown as import("../lib/native-review-cli.ts").NativeReviewCli;
383
+ }
384
+
385
+ test("ABANDON rejects free-text reasons before inventory, confirmation, or mutation", async () => {
386
+ let statusCalls = 0;
387
+ let confirmCalls = 0;
388
+ let abandonCalls = 0;
389
+ const native = {
390
+ reviewStatus: async () => { statusCalls += 1; throw new Error("must not read inventory"); },
391
+ abandon: async () => { abandonCalls += 1; throw new Error("must not abandon"); },
392
+ } as unknown as import("../lib/native-review-cli.ts").NativeReviewCli;
393
+ const context = { hasUI: true, ui: { confirm: async () => { confirmCalls += 1; return true; } } } as unknown as ExtensionContext;
394
+ const result = await __testing.executeReviewControllerOperation({ operation: "abandon", input: JSON.stringify({ lineage: "stranded", actor: "maintainer", reason: "discard candidate" }) }, process.cwd(), native, undefined, undefined, context);
395
+ assert.equal(result.outcome, "native-input-invalid");
396
+ assert.deepEqual(result.allowed_reasons, ["operator_disposition", "retired_schema"]);
397
+ assert.equal(result.mutation_performed, false);
398
+ assert.equal(result.mutation_outcome, "none");
399
+ assert.equal(statusCalls, 0);
400
+ assert.equal(confirmCalls, 0);
401
+ assert.equal(abandonCalls, 0);
402
+ });
403
+
404
+ test("ABANDON derives its discarded-work inputs from fresh native inventory", async () => {
405
+ const calls: Array<Record<string, unknown>> = [];
406
+ const native = abandonedInventoryNative(calls);
407
+ const result = await __testing.executeReviewControllerOperation({ operation: "abandon", input: JSON.stringify({ lineage: "stranded", actor: "maintainer", reason: "operator_disposition" }) }, process.cwd(), native, undefined, undefined, interactiveContext(true));
408
+ assert.equal(result.mutation_outcome, "committed");
409
+ assert.equal(calls.length, 1);
410
+ assert.equal(calls[0]?.lineage, "stranded");
411
+ assert.equal(calls[0]?.expectedRevision, SHA);
412
+ assert.equal(calls[0]?.snapshotIdentity, SHA2);
413
+ assert.deepEqual(calls[0]?.capturedLensResults, ["00-review-risk", "01-review-resilience"]);
414
+ assert.equal(calls[0]?.findingsPresent, true);
415
+ assert.equal(calls[0]?.maintainerAuthorization, `gentle-ai.review-abandon-authorization/v2\nlineage=stranded\nrevision=${SHA}\nsnapshot_identity=${SHA2}\nreason=operator_disposition\ncaptured_lens_results=00-review-risk,01-review-resilience\nfindings_present=true\nactor=maintainer`);
416
+ });
417
+
418
+ test("ABANDON accepts the top-level lineageId and requests only actor and reason", async () => {
419
+ const calls: Array<Record<string, unknown>> = [];
420
+ const native = abandonedInventoryNative(calls);
421
+ const missing = await __testing.executeReviewControllerOperation({ operation: "abandon", lineageId: "stranded", input: JSON.stringify({}) }, process.cwd(), native);
422
+ assert.equal(missing.outcome, "native-input-required");
423
+ assert.deepEqual(missing.missing_input, ["actor", "reason"]);
424
+ const committed = await __testing.executeReviewControllerOperation({ operation: "abandon", lineageId: "stranded", input: JSON.stringify({ actor: "maintainer", reason: "retired_schema" }) }, process.cwd(), native, undefined, undefined, interactiveContext(true));
425
+ assert.equal(committed.mutation_outcome, "committed");
426
+ assert.equal(calls[0]?.lineage, "stranded");
427
+ });
428
+
429
+ test("ABANDON rejects caller-supplied inventory-derived fields without mutating", async () => {
430
+ const calls: Array<Record<string, unknown>> = [];
431
+ const native = abandonedInventoryNative(calls);
432
+ const injected = await __testing.executeReviewControllerOperation({ operation: "abandon", input: JSON.stringify({ lineage: "stranded", actor: "maintainer", reason: "operator_disposition", expectedRevision: SHA, snapshotIdentity: SHA2, capturedLensResults: ["00-review-risk"], findingsPresent: true }) }, process.cwd(), native, undefined, undefined, interactiveContext(true));
433
+ assert.equal(injected.outcome, "native-input-invalid");
434
+ const authorization = await __testing.executeReviewControllerOperation({ operation: "abandon", input: JSON.stringify({ lineage: "stranded", actor: "maintainer", reason: "operator_disposition", maintainerAuthorization: "forged" }) }, process.cwd(), native, undefined, undefined, interactiveContext(true));
435
+ assert.equal(authorization.outcome, "native-input-invalid");
436
+ assert.equal(calls.length, 0);
437
+ });
438
+
439
+ test("ABANDON blocks when no eligible compact-v2 entry carries discarded work", async () => {
440
+ const native = {
441
+ reviewStatus: async () => ({
442
+ repository: "/canonical/repository",
443
+ complete: true,
444
+ authoritative: true,
445
+ entries: [{ version: "compact-v2", status: "active", path: "/authority/compact", lineageId: "stranded", revision: SHA, snapshotIdentity: SHA2, state: "terminal", problems: [] }],
446
+ }),
447
+ abandon: async () => { throw new Error("abandon must not run for an ineligible lineage"); },
448
+ } as unknown as import("../lib/native-review-cli.ts").NativeReviewCli;
449
+ const ineligible = await __testing.executeReviewControllerOperation({ operation: "abandon", input: JSON.stringify({ lineage: "stranded", actor: "maintainer", reason: "operator_disposition" }) }, process.cwd(), native, undefined, undefined, interactiveContext(true));
450
+ assert.equal(ineligible.outcome, "native-abandon-ineligible");
451
+ assert.equal(ineligible.mutation_performed, false);
452
+ const incomplete = {
453
+ reviewStatus: async () => ({ repository: "/canonical/repository", complete: false, authoritative: false, entries: [] }),
454
+ abandon: async () => { throw new Error("abandon must not run for an incomplete inventory"); },
455
+ } as unknown as import("../lib/native-review-cli.ts").NativeReviewCli;
456
+ const blocked = await __testing.executeReviewControllerOperation({ operation: "abandon", input: JSON.stringify({ lineage: "stranded", actor: "maintainer", reason: "operator_disposition" }) }, process.cwd(), incomplete, undefined, undefined, interactiveContext(true));
457
+ assert.equal(blocked.outcome, "native-abandon-ineligible");
458
+ });
459
+
460
+ test("ABANDON rechecks the derived authority after approval and fails closed on drift", async () => {
461
+ let reads = 0;
462
+ const native = {
463
+ reviewStatus: async () => {
464
+ reads += 1;
465
+ return reads === 1
466
+ ? {
467
+ repository: "/canonical/repository",
468
+ complete: true,
469
+ authoritative: true,
470
+ entries: [{ version: "compact-v2", status: "active", path: "/authority/compact", lineageId: "stranded", revision: SHA, snapshotIdentity: SHA2, state: "correction_required", discardedWork: { capturedLensResults: ["00-review-risk", "01-review-resilience"], findingsPresent: true } }],
471
+ }
472
+ : {
473
+ repository: "/canonical/repository",
474
+ complete: true,
475
+ authoritative: true,
476
+ entries: [{ version: "compact-v2", status: "active", path: "/authority/compact", lineageId: "stranded", revision: `sha256:${"c".repeat(64)}`, snapshotIdentity: SHA2, state: "correction_required", discardedWork: { capturedLensResults: ["00-review-risk", "01-review-resilience"], findingsPresent: true } }],
477
+ };
478
+ },
479
+ abandon: async () => { throw new Error("must not abandon a drifted authority"); },
480
+ } as unknown as import("../lib/native-review-cli.ts").NativeReviewCli;
481
+ const drifted = await __testing.executeReviewControllerOperation({ operation: "abandon", input: JSON.stringify({ lineage: "stranded", actor: "maintainer", reason: "operator_disposition" }) }, process.cwd(), native, undefined, undefined, interactiveContext(true));
482
+ assert.equal(drifted.outcome, "native-abandon-authority-changed");
483
+ assert.equal(drifted.mutation_performed, false);
484
+ assert.equal(reads, 2);
485
+ });
486
+
487
+ test("ABANDON fails closed headlessly and on declined approval", async () => {
488
+ const calls: Array<Record<string, unknown>> = [];
489
+ const native = abandonedInventoryNative(calls);
490
+ await assert.rejects(
491
+ __testing.executeReviewControllerOperation({ operation: "abandon", input: JSON.stringify({ lineage: "stranded", actor: "maintainer", reason: "operator_disposition" }) }, process.cwd(), native),
492
+ /interactive Pi UI.*fails closed/i,
493
+ );
494
+ await assert.rejects(
495
+ __testing.executeReviewControllerOperation({ operation: "abandon", input: JSON.stringify({ lineage: "stranded", actor: "maintainer", reason: "operator_disposition" }) }, process.cwd(), native, undefined, undefined, interactiveContext(false)),
496
+ /not explicitly authorized/,
497
+ );
498
+ assert.equal(calls.length, 0);
499
+ });
500
+
335
501
  test("maintenance cancellation preserves the exact signal and unknown mutation outcome", async () => {
336
502
  const controller = new AbortController();
337
503
  const request = {
338
504
  cwd: "/repo", lineage: "abandoned", expectedRevision: "revision-1", snapshotIdentity: SHA,
339
505
  capturedLensResults: ["00-risk.json"], findingsPresent: true,
340
- actor: "maintainer", reason: "discard candidate",
506
+ actor: "maintainer", reason: "operator_disposition" as const,
341
507
  };
342
508
  const adapter: ExecFileAdapter = async (call) => {
343
509
  assert.equal(call.signal, controller.signal);
@@ -397,6 +563,35 @@ test("RECOVER derives a provider-bound authorization, rechecks it, and fails clo
397
563
  assert.equal(changed.mutation_outcome, "none");
398
564
  });
399
565
 
566
+ test("approved RECOVER preserves the frozen committed-range selector from both STATUS reads", async () => {
567
+ const baseCommit = "1".repeat(40);
568
+ const providerBaseTree = "2".repeat(40);
569
+ const statuses: Array<Record<string, unknown>> = [];
570
+ const queue = queuedAdapter([{ stdout: JSON.stringify({ schema: "gentle-ai.review-recovery/v1" }) }]);
571
+ const review = client(queue.adapter);
572
+ const native = {
573
+ targetStatus: async (request: Record<string, unknown>) => { statuses.push(request); return recoveryStatus(); },
574
+ recover: review.recover.bind(review),
575
+ } as unknown as import("../lib/native-review-cli.ts").NativeReviewCli;
576
+ const views = {
577
+ hasProjection: (lineage: string, cwd: string) => lineage === "predecessor" && cwd === process.cwd(),
578
+ resolveProjection: () => ({ committedOnly: true, baseCommit, providerBaseTree }),
579
+ } as unknown as NonNullable<Parameters<typeof __testing.executeReviewControllerOperation>[4]>;
580
+ const input = { predecessorLineage: "predecessor", expectedPredecessorRevision: "revision-1", successorLineage: "successor", disposition: "invalidated", actor: "maintainer", reason: "recover authority" };
581
+ const result = await __testing.executeReviewControllerOperation({ operation: "recover", input: JSON.stringify(input) }, process.cwd(), native, undefined, views, interactiveContext(true));
582
+ assert.equal(result.mutation_outcome, "committed");
583
+ assert.equal(statuses.length, 2);
584
+ for (const status of statuses) {
585
+ assert.equal(status.baseRef, providerBaseTree);
586
+ assert.equal(status.committedOnly, true);
587
+ }
588
+ const args = queue.calls[0]!.arguments;
589
+ assert.ok(args.includes("--base-ref"), "RECOVER must carry the STATUS base selector");
590
+ assert.equal(args[args.indexOf("--base-ref") + 1], providerBaseTree);
591
+ assert.ok(args.includes("--committed-only"));
592
+ assert.ok(args.includes("--maintainer-authorization"));
593
+ });
594
+
400
595
  test("RECOVER, RESET, and RECONCILE keep provider inputs and failures authority-scoped", async () => {
401
596
  const recoverMissing = await __testing.executeReviewControllerOperation({ operation: "recover", input: JSON.stringify({ predecessorLineage: "predecessor", disposition: "not-a-disposition" }) }, process.cwd(), {
402
597
  recover: async () => { throw new Error("must not recover"); },
@@ -415,7 +610,7 @@ test("RECOVER, RESET, and RECONCILE keep provider inputs and failures authority-
415
610
 
416
611
  for (const error of [
417
612
  new NativeReviewCliError("cancelled", "review/reconcile-authority", true, true, "cancelled"),
418
- new NativeReviewCliError("non_zero", "review/reconcile-authority", true, true, "partial", undefined, { schema: "gentle-ai.review-reconcile-audit/v1", status: "partial" }),
613
+ new NativeReviewCliError("non-zero", "review/reconcile-authority", true, true, "partial", undefined, { schema: "gentle-ai.review-reconcile-audit/v1", status: "partial" }),
419
614
  ]) {
420
615
  const failed = await __testing.executeReviewControllerOperation({ operation: "reconcile-authority", input: JSON.stringify(reconciliation) }, process.cwd(), {
421
616
  reconcileAuthority: async () => { throw error; },
@@ -493,3 +688,118 @@ test("REPAIR_LEGACY_ALIAS derives its immutable target from fresh native invento
493
688
  const injected = await __testing.executeReviewControllerOperation({ operation: "repair-legacy-alias", input: JSON.stringify({ lineage: "legacy-alias", actor: "maintainer", reason: "repair alias", repository: "/injected" }) }, process.cwd(), native, undefined, undefined, interactiveContext(true));
494
689
  assert.equal(injected.outcome, "native-input-invalid");
495
690
  });
691
+
692
+ // Adapter-level ABANDON battery (reviewer input on #1668): the registered
693
+ // gentle_review tool driven through the real NativeReviewCliV216 adapter, so
694
+ // the native status decoder and the exact abandon argv are exercised, not a
695
+ // faked NativeReviewCli surface.
696
+ const ELIGIBLE_ENTRY = { version: "compact-v2", path: ".git/gentle-ai/reviews/current.json", status: "active", lineage_id: "stranded", state: "correction_required", revision: SHA, snapshot_identity: SHA2, problems: [], discarded_work: { captured_lens_results: ["00-review-risk", "01-review-resilience"], findings_present: true } };
697
+ const INCOMPLETE_DUPLICATE = { version: "compact-v2", path: ".git/gentle-ai/reviews/duplicate.json", status: "incomplete-store-entry", lineage_id: "stranded", problems: [] };
698
+ const ABANDON_RECORD = { operation: "review/abandon", record: { schema: "gentle-ai.review-reclaim-audit/v1", lineage_id: "stranded", status: "committed" } };
699
+
700
+ function statusStdout(entries: object[], repository: string = process.cwd(), authoritative: boolean = true): string {
701
+ return JSON.stringify({ schema: "gentle-ai.review-authority-status/v1", operation: "review/status", repository, complete: authoritative, authoritative, status: "active", entries, locks: [], diagnostics: [] });
702
+ }
703
+
704
+ const ABANDON_INPUT = { operation: "abandon", input: JSON.stringify({ lineage: "stranded", actor: "maintainer", reason: "operator_disposition" }) } as const;
705
+
706
+ test("registered ABANDON decodes authoritative inventory and dispatches the exact binding once", async () => {
707
+ const queue = queuedAdapter([{ stdout: statusStdout([ELIGIBLE_ENTRY]) }, { stdout: statusStdout([ELIGIBLE_ENTRY]) }, { stdout: JSON.stringify(ABANDON_RECORD) }]);
708
+ const controller = registeredController(client(queue.adapter) as unknown as import("../lib/native-review-cli.ts").NativeReviewCli);
709
+ const result = await controller.execute("t", ABANDON_INPUT, undefined, undefined, interactiveContext(true));
710
+ assert.equal((result.details as { mutation_outcome?: string }).mutation_outcome, "committed");
711
+ assert.equal(queue.calls.length, 3);
712
+ assert.deepEqual(queue.calls[0]?.arguments, ["review", "status", "--cwd", process.cwd()]);
713
+ assert.deepEqual(queue.calls[2]?.arguments, ["review", "abandon", "--cwd", process.cwd(), "--lineage", "stranded", "--expected-revision", SHA, "--actor", "maintainer", "--reason", "operator_disposition", "--maintainer-authorization", `gentle-ai.review-abandon-authorization/v2\nlineage=stranded\nrevision=${SHA}\nsnapshot_identity=${SHA2}\nreason=operator_disposition\ncaptured_lens_results=00-review-risk,01-review-resilience\nfindings_present=true\nactor=maintainer`]);
714
+ });
715
+
716
+ test("registered ABANDON rejects duplicate and non-authoritative inventories before approval", async () => {
717
+ const duplicateQueue = queuedAdapter([{ stdout: statusStdout([ELIGIBLE_ENTRY, INCOMPLETE_DUPLICATE]) }]);
718
+ const duplicate = await (registeredController(client(duplicateQueue.adapter) as unknown as import("../lib/native-review-cli.ts").NativeReviewCli)).execute("t", ABANDON_INPUT, undefined, undefined, interactiveContext(true));
719
+ assert.equal((duplicate.details as { outcome?: string }).outcome, "native-abandon-ineligible");
720
+ assert.equal(duplicateQueue.calls.length, 1);
721
+ const unauthoritativeQueue = queuedAdapter([{ stdout: statusStdout([ELIGIBLE_ENTRY], process.cwd(), false) }]);
722
+ const unauthoritative = await (registeredController(client(unauthoritativeQueue.adapter) as unknown as import("../lib/native-review-cli.ts").NativeReviewCli)).execute("t", ABANDON_INPUT, undefined, undefined, interactiveContext(true));
723
+ assert.equal((unauthoritative.details as { outcome?: string }).outcome, "native-abandon-ineligible");
724
+ assert.equal(unauthoritativeQueue.calls.length, 1);
725
+ });
726
+
727
+ test("registered ABANDON blocks foreign repositories and inventory failures without dispatch", async () => {
728
+ const foreignQueue = queuedAdapter([{ stdout: statusStdout([ELIGIBLE_ENTRY], "/foreign/repository") }]);
729
+ const foreign = await (registeredController(client(foreignQueue.adapter) as unknown as import("../lib/native-review-cli.ts").NativeReviewCli)).execute("t", ABANDON_INPUT, undefined, undefined, interactiveContext(true));
730
+ assert.equal((foreign.details as { status?: string }).status, "blocked");
731
+ assert.equal(foreignQueue.calls.length, 1);
732
+ const brokenQueue = queuedAdapter([{ stdout: "{not json" }]);
733
+ const broken = await (registeredController(client(brokenQueue.adapter) as unknown as import("../lib/native-review-cli.ts").NativeReviewCli)).execute("t", ABANDON_INPUT, undefined, undefined, interactiveContext(true));
734
+ assert.equal((broken.details as { status?: string }).status, "blocked");
735
+ assert.equal(brokenQueue.calls.length, 1);
736
+ });
737
+
738
+ test("native status repository identity tolerates Windows casing and rejects foreign roots", async () => {
739
+ const cased = queuedAdapter([{ stdout: statusStdout([ELIGIBLE_ENTRY], "c:\\REPO") }]);
740
+ const inventory = await client(cased.adapter).reviewStatus({ cwd: "C:\\Repo" });
741
+ assert.equal(inventory.repository, "c:\\REPO");
742
+ const foreign = queuedAdapter([{ stdout: statusStdout([ELIGIBLE_ENTRY], "D:\\Other") }]);
743
+ await assert.rejects(() => client(foreign.adapter).reviewStatus({ cwd: "C:\\Repo" }), /repository mismatch/);
744
+ });
745
+
746
+ test("registered ABANDON fails closed on cancellation at the inventory and approval boundaries", async () => {
747
+ const cancelling = new AbortController();
748
+ const cancellingAdapter: ExecFileAdapter = async (request) => {
749
+ cancelling.abort();
750
+ return { stdout: statusStdout([ELIGIBLE_ENTRY]), stderr: "", exitCode: 0, signal: null, timedOut: false, outputLimitExceeded: false };
751
+ };
752
+ await assert.rejects(
753
+ (registeredController(client(cancellingAdapter) as unknown as import("../lib/native-review-cli.ts").NativeReviewCli)).execute("t", ABANDON_INPUT, cancelling.signal, undefined, interactiveContext(true)),
754
+ /cancelled/,
755
+ );
756
+ const afterApproval = new AbortController();
757
+ const queue = queuedAdapter([{ stdout: statusStdout([ELIGIBLE_ENTRY]) }]);
758
+ const context = { cwd: process.cwd(), hasUI: true, ui: { confirm: async () => { afterApproval.abort(); return true; } } } as unknown as ExtensionContext;
759
+ await assert.rejects(
760
+ (registeredController(client(queue.adapter) as unknown as import("../lib/native-review-cli.ts").NativeReviewCli)).execute("t", ABANDON_INPUT, afterApproval.signal, undefined, context),
761
+ /cancelled/,
762
+ );
763
+ assert.equal(queue.calls.length, 1);
764
+ });
765
+
766
+ test("registered ABANDON cancels during the second inventory without dispatching abandon argv", async () => {
767
+ const cancelling = new AbortController();
768
+ const calls: Array<Parameters<ExecFileAdapter>[0]> = [];
769
+ const adapter: ExecFileAdapter = async (request) => {
770
+ calls.push(request);
771
+ assert.equal(request.signal, cancelling.signal);
772
+ if (calls.length === 2) cancelling.abort();
773
+ return { stdout: request.arguments[1] === "abandon" ? JSON.stringify(ABANDON_RECORD) : statusStdout([ELIGIBLE_ENTRY]), stderr: "", exitCode: 0, signal: null, timedOut: false, outputLimitExceeded: false };
774
+ };
775
+ let approvals = 0;
776
+ const context = { cwd: process.cwd(), hasUI: true, ui: { confirm: async (_title: string, message: string) => {
777
+ approvals++;
778
+ assert.equal(cancelling.signal.aborted, false);
779
+ assert.ok(message.includes(`gentle-ai.review-abandon-authorization/v2\nlineage=stranded\nrevision=${SHA}\nsnapshot_identity=${SHA2}\nreason=operator_disposition\ncaptured_lens_results=00-review-risk,01-review-resilience\nfindings_present=true\nactor=maintainer`));
780
+ return true;
781
+ } } } as unknown as ExtensionContext;
782
+ const controller = registeredController(client(adapter) as unknown as import("../lib/native-review-cli.ts").NativeReviewCli);
783
+ let cancellation: unknown;
784
+ try {
785
+ await controller.execute("second-inventory-cancel", ABANDON_INPUT, cancelling.signal, undefined, context);
786
+ } catch (error) {
787
+ cancellation = error;
788
+ }
789
+ assert.equal(approvals, 1);
790
+ assert.ok(calls.every((request) => request.arguments[1] === "status"), "cancellation during the second inventory must not dispatch abandon argv");
791
+ assert.equal(calls.length, 2);
792
+ assert.match(String(cancellation), /cancelled/);
793
+ });
794
+
795
+ test("registered ABANDON rejects terminal status and terminal state entries before approval", async () => {
796
+ const terminalStatusEntry = { ...ELIGIBLE_ENTRY, status: "approved" };
797
+ const terminalStatusQueue = queuedAdapter([{ stdout: statusStdout([terminalStatusEntry]) }]);
798
+ const terminalStatus = await (registeredController(client(terminalStatusQueue.adapter) as unknown as import("../lib/native-review-cli.ts").NativeReviewCli)).execute("t", ABANDON_INPUT, undefined, undefined, interactiveContext(true));
799
+ assert.equal((terminalStatus.details as { outcome?: string }).outcome, "native-abandon-ineligible");
800
+ assert.equal(terminalStatusQueue.calls.length, 1);
801
+ const terminalStateQueue = queuedAdapter([{ stdout: statusStdout([{ ...ELIGIBLE_ENTRY, state: "closed" }]) }]);
802
+ const terminalState = await (registeredController(client(terminalStateQueue.adapter) as unknown as import("../lib/native-review-cli.ts").NativeReviewCli)).execute("t", ABANDON_INPUT, undefined, undefined, interactiveContext(true));
803
+ assert.equal((terminalState.details as { outcome?: string }).outcome, "native-abandon-ineligible");
804
+ assert.equal(terminalStateQueue.calls.length, 1);
805
+ });