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
@@ -136,7 +136,7 @@ const GROUPS: CommandPaletteGroup[] = [
136
136
  },
137
137
  {
138
138
  title: "Session",
139
- items: [{ command: "gentle:changes", label: "Browse captured changes", shortcut: "alt+g" }],
139
+ items: [{ command: "gentle:changes", label: "Browse changes", shortcut: "alt+g" }],
140
140
  },
141
141
  ];
142
142
 
@@ -245,9 +245,9 @@ test("render shows group titles and one row per item, with the shortcut right-al
245
245
  assert.match(lines, /Session/);
246
246
  assert.match(lines, /Assign models and effort/);
247
247
  assert.match(lines, /Agent-model profiles/);
248
- const changesLine = render(GROUPS, "").find((line) => line.includes("Browse captured changes"));
249
- assert.ok(changesLine, "expected a row for Browse captured changes");
250
- assert.match(changesLine!, /Browse captured changes[\s\S]*alt\+g/);
248
+ const changesLine = render(GROUPS, "").find((line) => line.includes("Browse changes"));
249
+ assert.ok(changesLine, "expected a row for Browse changes");
250
+ assert.match(changesLine!, /Browse changes[\s\S]*alt\+g/);
251
251
  });
252
252
 
253
253
  test("render omits the shortcut column for an item without one", () => {
@@ -376,7 +376,7 @@ test("COMMAND_PALETTE_CATALOG matches the curated command set, in order", () =>
376
376
  "gentle:toggle-text-logo",
377
377
  "gentle:dev-binary",
378
378
  ]);
379
- assert.deepEqual(byTitle("Session"), ["gentle:yolo", "gentle:changes", "gentle:agents", "gentle:usage", "gentle:review-session-permission"]);
379
+ assert.deepEqual(byTitle("Session"), ["gentle:yolo", "gentle:changes", "gentle:agents", "gentle:jobs", "gentle:usage", "gentle:review-session-permission"]);
380
380
  assert.deepEqual(byTitle("Diagnostics"), ["gentle:status", "gentle:doctor"]);
381
381
  assert.equal(byTitle("SDD"), undefined);
382
382
  assert.deepEqual(byTitle("Skills"), ["skill-registry:refresh"]);
@@ -0,0 +1,31 @@
1
+ import assert from "node:assert/strict";
2
+ import { test } from "node:test";
3
+ import { CONTEXT_HIGH_PERCENT, ContextHighLatch } from "../lib/context-threshold.ts";
4
+
5
+ test("warns exactly once when a reading reaches the threshold, then stays silent", () => {
6
+ assert.equal(CONTEXT_HIGH_PERCENT, 90);
7
+ const latch = new ContextHighLatch();
8
+ assert.equal(latch.observe(0), false);
9
+ assert.equal(latch.observe(89.9), false);
10
+ assert.equal(latch.observe(90), true, "the threshold is inclusive");
11
+ for (const percent of [90, 91, 99, 140]) assert.equal(latch.observe(percent), false, `same episode at ${percent}% must not warn again`);
12
+ });
13
+
14
+ test("unknown readings preserve the latch; a below-threshold reading rearms it", () => {
15
+ const latch = new ContextHighLatch();
16
+ assert.equal(latch.observe(95), true);
17
+ assert.equal(latch.observe(null), false, "a null reading after compaction is not a rearm");
18
+ assert.equal(latch.observe(undefined), false);
19
+ assert.equal(latch.observe(96), false, "the same episode cannot warn twice across an unknown reading");
20
+ assert.equal(latch.observe(80), false);
21
+ assert.equal(latch.observe(91), true, "dropping below the threshold starts a new episode");
22
+ });
23
+
24
+ test("only an explicit rearm starts a new episode and non-finite readings never warn", () => {
25
+ const latch = new ContextHighLatch();
26
+ assert.equal(latch.observe(94), true);
27
+ latch.rearm();
28
+ assert.equal(latch.observe(94), true, "a successful compaction or new session rearms");
29
+ assert.equal(latch.observe(94), false);
30
+ for (const value of [Number.NaN, Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY]) assert.equal(latch.observe(value), false);
31
+ });
@@ -0,0 +1,327 @@
1
+ import assert from "node:assert/strict";
2
+ import { createHash } from "node:crypto";
3
+ import { readFileSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import test from "node:test";
6
+ import { __testing } from "../extensions/gentle-ai.ts";
7
+ import { HIGH_RISK_ITEMS } from "../lib/review-risk-assessment.ts";
8
+ import { readDelegationDetail } from "./support/orchestrator-modules.ts";
9
+
10
+ // gentle-shell#1731: the delegated writer fires on named reasons (parallelism,
11
+ // context), never on task size. A large task keeps the ODD logbook (#1494
12
+ // resume test) and works inline when no reason fires. T24 (L49) turned the
13
+ // cost reason off: a price ratio never fires the Writer rule, while configured
14
+ // per-agent model routing still applies to writers launched for other reasons.
15
+ // These are instruction-delivery contracts, not proof of model adherence.
16
+
17
+ const read = (relative: string): string => readFileSync(join(import.meta.dirname, "..", relative), "utf8");
18
+ const core = read("assets/orchestrator.md");
19
+ const delegation = readDelegationDetail();
20
+ const writer = read("assets/orchestrator-writer.md");
21
+ const verification = read("assets/orchestrator-verification.md");
22
+ const skill = read("skills/gentle-ai/SKILL.md");
23
+
24
+ function lineStarting(text: string, prefix: string): string {
25
+ const line = text.split("\n").find((entry) => entry.startsWith(prefix));
26
+ assert.ok(line, `missing line starting with: ${prefix}`);
27
+ return line;
28
+ }
29
+
30
+ const coreWriter = lineStarting(core, "5. **Writer rule**");
31
+ const delegationWriter = lineStarting(delegation, "5. **Writer trigger (Writer rule):**");
32
+
33
+ test("AC1: the core writer rule fires on named reasons, never on size or file count", () => {
34
+ for (const clause of [
35
+ "never by file count or a large task alone",
36
+ "`orchestrator-writer.md`",
37
+ "2+ independent units, disjoint files, each heavier than a subagent start",
38
+ "Context backstop",
39
+ ]) {
40
+ assert.ok(coreWriter.includes(clause), `core Writer rule is missing: ${clause}`);
41
+ }
42
+ assert.ok(!core.includes("large task → one bounded `gentle-ai-worker` per task"), "core keeps the size-based writer rule");
43
+ assert.ok(
44
+ core.includes("large tasks get ODD tracking and workers only by the Writer rule, else inline"),
45
+ "core Task Size must keep tracking for large tasks and run them inline without a writer reason",
46
+ );
47
+ });
48
+
49
+ test("AC1: the lazy writer trigger names its reasons and keeps the logbook on the inline path", () => {
50
+ for (const clause of [
51
+ "a large task alone never delegates, and file count never fires this trigger",
52
+ "Delegate one bounded writer per unit only for a named reason",
53
+ "(a) parallelism",
54
+ "(b) context",
55
+ "Without a reason, the parent works inline, following the logbook (feature document, mirror, `todo`, work-unit commits)",
56
+ ]) {
57
+ assert.ok(delegationWriter.includes(clause), `lazy Writer trigger is missing: ${clause}`);
58
+ }
59
+ for (const [path, text] of Object.entries({ delegation, writer, skill })) {
60
+ assert.ok(!text.includes("a large task delegates one bounded writer per task"), `${path} keeps the size-based writer trigger`);
61
+ assert.ok(!text.includes("one writer per task"), `${path} keeps one writer per task`);
62
+ assert.ok(!text.includes("a large task (track and writer)"), `${path} still fires the writer on size`);
63
+ }
64
+ assert.ok(writer.includes("never on size alone"), "the writer module must say the Writer rule never fires on size alone");
65
+ assert.ok(skill.includes("a writer reason (parallel units or context)"), "the skill must name the writer reasons");
66
+ });
67
+
68
+ test("AC2: parallelism needs 2+ independent units, disjoint files, each heavier than a subagent start", () => {
69
+ assert.ok(
70
+ delegationWriter.includes(
71
+ "(a) parallelism — 2+ independent units with disjoint files, each clearly heavier than starting a subagent; medium tasks included",
72
+ ),
73
+ "lazy Writer trigger must state the parallelism threshold",
74
+ );
75
+ assert.ok(writer.includes("parallel units"), "the writer module must name the parallelism reason");
76
+ });
77
+
78
+ test("T24/L49: a price ratio no longer fires the Writer rule", () => {
79
+ assert.doesNotMatch(coreWriter, /ratio|price|Model routing|small path/i, "core Writer rule still fires on cost");
80
+ assert.ok(!core.includes("Model routing:"), "the orchestrator core never carries the fact line");
81
+ const intro = lineStarting(writer, "Parent Pi session only.");
82
+ assert.ok(intro.includes("The Writer rule fires on a named reason (parallel units, the context backstop), never on size alone or a price ratio"), "the writer module must name only the remaining reasons");
83
+ assert.doesNotMatch(writer, /about 3x|price ratio of|- \*\*Model routing\*\*/, "the writer module keeps the cost reason");
84
+ // T24 follow-up: no surface names the price ratio as a writer reason.
85
+ for (const [path, text] of Object.entries({ delegationWriter, skill, readme: read("docs/readme-reference.md") })) {
86
+ assert.doesNotMatch(text, /model routing —|price ratio of about 3x/, `${path} still names the cost reason`);
87
+ }
88
+ });
89
+
90
+ test("T24/L49: configured per-agent model routing still applies to writers launched for other reasons", () => {
91
+ const intro = lineStarting(writer, "Parent Pi session only.");
92
+ assert.ok(intro.includes("Configured per-agent models (`subagents.json` `model_profiles`) still apply to every writer it launches."), "the writer module must keep configured routing");
93
+ assert.ok(
94
+ delegation.includes("Let `pi-subagents` resolve model and thinking from `.pi/settings.json`, `.pi/subagents.json`, global subagent config, and runtime defaults."),
95
+ "delegated launches must keep resolving configured models",
96
+ );
97
+ });
98
+
99
+ test("AC4: verification stays risk-gated and the writer reasons never include risk", () => {
100
+ assert.ok(
101
+ core.includes(
102
+ "3. **Verification rule** — high risk → independent `gentle-ai-verify` after the change's own checks (`orchestrator-verification.md`); otherwise checks run inline.",
103
+ ),
104
+ "core Verification rule changed",
105
+ );
106
+ assert.ok(
107
+ delegation.includes(
108
+ "3. **Verification rule** (gentle-pi#661/#662, RDD-aware): a high-risk change (Task Size) gets an independent `gentle-ai-verify` run after the change's own checks; otherwise whoever made the change runs its focused test and suite inline, small tasks included.",
109
+ ),
110
+ "lazy Verification rule changed",
111
+ );
112
+ for (const [label, text] of Object.entries({ coreWriter, delegationWriter })) {
113
+ assert.ok(!/high risk|high-risk|verify/i.test(text), `${label} mixes risk or verification into the writer reasons`);
114
+ }
115
+ assert.ok(!writer.includes("(1) data or irreversible effects"), "the writer module restates the high-risk list");
116
+ });
117
+
118
+ // T3 (S5-S7, AC5): the parallel review protocol lives in the lazy verification
119
+ // module, so it loads only when delegation happens.
120
+ function sectionFrom(text: string, heading: string): string {
121
+ const start = text.indexOf(heading);
122
+ assert.ok(start >= 0, `missing section: ${heading}`);
123
+ const next = text.indexOf("\n## ", start + heading.length);
124
+ return text.slice(start, next < 0 ? undefined : next + 1);
125
+ }
126
+
127
+ const reviewSection = (): string => sectionFrom(verification, "## Parallel review protocol (gentle-shell#1731)");
128
+
129
+ function reviewItem(prefix: string): string {
130
+ return lineStarting(reviewSection(), prefix);
131
+ }
132
+
133
+ test("AC5/S5: every worker self-reviews against the spec by reference before returning", () => {
134
+ const item = reviewItem("1. **Self-review**");
135
+ for (const clause of [
136
+ "in its own session before returning",
137
+ "spec sections by reference (#1713)",
138
+ "the request's authorized examples, tests, and typecheck",
139
+ "fixes and continues",
140
+ "requirement by requirement",
141
+ "Low and medium risk need nothing else.",
142
+ ]) {
143
+ assert.ok(item.includes(clause), `self-review item is missing: ${clause}`);
144
+ }
145
+ assert.ok(writer.includes("Parallel review protocol") && writer.includes("`orchestrator-verification.md`"), "the writer module must point at the review protocol");
146
+ });
147
+
148
+ test("AC5/S6/L4: per-worker independent verify fires only on assess or escalate, never on the summary alone", () => {
149
+ const item = reviewItem("2. **Independent verify per unit**");
150
+ for (const clause of [
151
+ "in parallel when several finish together",
152
+ "only when that unit is high risk",
153
+ "`assess` over its actual diff",
154
+ "the worker's own `escalate`",
155
+ "never inferred from the worker's summary alone",
156
+ 'its work-unit commit (`{"baseRef":"<previous>","committedOnly":true}`)',
157
+ "its own isolated worktree",
158
+ ]) {
159
+ assert.ok(item.includes(clause), `independent verify item is missing: ${clause}`);
160
+ }
161
+ assert.ok(!reviewSection().includes("(1) data or irreversible effects"), "the protocol restates the high-risk list instead of referencing it");
162
+ assert.ok(item.includes("high-risk list in Task Size"), "the protocol must reference the core high-risk list");
163
+ });
164
+
165
+ test("AC5/S7: one inline full-suite seam check after parallel units", () => {
166
+ const item = reviewItem("3. **Seam check**");
167
+ for (const clause of ["after parallel units finish", "one inline full-suite command", "parent spot check", "seams between units"]) {
168
+ assert.ok(item.includes(clause), `seam check item is missing: ${clause}`);
169
+ }
170
+ });
171
+
172
+ // T12 (S4, S6; L24): in the bench verify ran once per worker cycle instead of
173
+ // once per unit, and a parent scoped it to "T1 only".
174
+ test("T12/S6: one independent verify per delegated unit after its self-review, never per worker cycle", () => {
175
+ const item = reviewItem("2. **Independent verify per unit**");
176
+ for (const clause of [
177
+ "once, after its final self-review",
178
+ "never per worker cycle or retry",
179
+ "its Risk line",
180
+ "small-model bias per the tier table",
181
+ ]) {
182
+ assert.ok(item.includes(clause), `independent verify timing is missing: ${clause}`);
183
+ }
184
+ assert.ok(!item.includes("(1) "), "the timing rule restates the high-risk list");
185
+ });
186
+
187
+ // T15 (S6, L38): after3 B x4 paid two same-model verifies with 0 defects,
188
+ // while verify caught real defects in smaller-model (Luna) writer code.
189
+ test("T15/S6: same-model inline code over several deliveries gets one verify at the feature's end; smaller-model writer code stays per unit", () => {
190
+ const item = reviewItem("2. **Independent verify per unit**");
191
+ for (const clause of [
192
+ "Same-model inline code over several deliveries of one feature",
193
+ "one verify at the feature's end",
194
+ "smaller-model writer code stays per unit",
195
+ "only when that unit is high risk",
196
+ "small-model bias per the tier table",
197
+ ]) {
198
+ assert.ok(item.includes(clause), `independent verify exception is missing: ${clause}`);
199
+ }
200
+ });
201
+
202
+ test("T11/S4: the parent hands verify the whole spec, authorizes its probes and resolves every unmet item", () => {
203
+ const item = reviewItem("4. **Verify handoff**");
204
+ for (const clause of [
205
+ "the whole feature document",
206
+ "every `S#`, never one task",
207
+ "the baseline commit",
208
+ "probes on isolated state",
209
+ "typecheck",
210
+ "`gentle-ai-verify` holds the checklist",
211
+ "every unmet item before closing",
212
+ "the writer commits its probes as regression tests",
213
+ ]) {
214
+ assert.ok(item.includes(clause), `verify handoff item is missing: ${clause}`);
215
+ }
216
+ });
217
+
218
+ // verify-always-rdd-high S1 deliberately rewrote this section: verification follows the
219
+ // risk tier in every RDD state. The pin still guards against accidental drift.
220
+ test("AC4/S4: the normative verification rule text is unchanged by the review protocol", () => {
221
+ const normative = sectionFrom(verification, "## Verification rule (normative)");
222
+ assert.equal(
223
+ createHash("sha256").update(normative).digest("hex"),
224
+ "1f66f6998c4659fb08d81b83d350dcf5e8279b3f04a9e1a3a2951ed2abe7e54b",
225
+ "the normative Verification rule section changed",
226
+ );
227
+ assert.ok(verification.includes("or a delegated writer returns"), "the module must still load when a delegated writer returns");
228
+ });
229
+
230
+ test("S1: the small path stays inline and no lazy surface keeps the size-based writer route", () => {
231
+ assert.ok(core.includes("No explore, worker, or verifier"), "small path must stay inline");
232
+ assert.ok(!delegation.includes("| Write a large (tracked) task | — | ✅ one writer per task |"), "delegation table keeps the size-based writer row");
233
+ assert.ok(delegation.includes("| Write a large task with no Writer rule reason | ✅ following the logbook | — |"), "delegation table must route a reasonless large task inline");
234
+ assert.ok(!delegation.includes("implementing a large tracked task (writer)"), "Simple Delegation keeps the size-based writer route");
235
+ });
236
+
237
+ // T7-T9 (L14): the bench showed the rules decide WHETHER to delegate but not
238
+ // HOW. T7 forces a risk declaration on every code change, small path included,
239
+ // so it lives in the always-on core. T9 binds how the parallelism reason
240
+ // launches writers, so it lives in the lazy writer module (T24 retired T8's
241
+ // cost-reason launch rule with the cost reason itself).
242
+ const taskSize = sectionFrom(core, "## Task Size");
243
+
244
+ test("T7/AC4: every code change closes with a forced risk line that routes any listed item to independent verify", () => {
245
+ const rule = lineStarting(taskSize, "**Risk line**");
246
+ for (const clause of [
247
+ "close every code change, small path or delegated",
248
+ "`Risk: item N (reason)`",
249
+ "`Risk: none`",
250
+ "per the list",
251
+ "any item → Verification rule",
252
+ ]) {
253
+ assert.ok(rule.includes(clause), `risk line rule is missing: ${clause}`);
254
+ }
255
+ const highRisk = taskSize.indexOf("**High risk**");
256
+ assert.ok(highRisk >= 0 && taskSize.indexOf("**Risk line**") > highRisk, "the risk line must follow the high-risk list it is checked against");
257
+ // The small path loads no lazy module, so the rule must render in the always-on prompt.
258
+ assert.ok(__testing.getOrchestratorPrompt().includes(rule), "the risk line must reach the always-on prompt");
259
+ for (const [path, text] of Object.entries({ delegation, writer, verification })) {
260
+ assert.ok(!text.includes("`Risk: item N (reason)`"), `${path} restates the core risk line`);
261
+ }
262
+ });
263
+
264
+ // T10 (S4, L19-L21): the risk line tagged items 1 and 3 for additive features,
265
+ // so verify ran on most tasks. High risk is changing or breaking existing
266
+ // things, not adding.
267
+ function highRiskItem(n: number): string {
268
+ const list = lineStarting(taskSize, "**High risk**");
269
+ const start = list.indexOf(`(${n}) `);
270
+ const next = list.indexOf(`; (${n + 1}) `, start);
271
+ const end = next >= 0 ? next : list.indexOf(". ", start);
272
+ assert.ok(start >= 0 && end > start, `high-risk item ${n} is missing`);
273
+ return list.slice(start, end);
274
+ }
275
+
276
+ test("T10/AC4: item 1 covers changing stored data or unvalidated writes, not ordinary new records", () => {
277
+ const item = highRiskItem(1);
278
+ for (const clause of ["migrations", "rewriting or deleting stored data", "format changes", "writing data without validation", "not saving new records"]) {
279
+ assert.ok(item.includes(clause), `item 1 is missing: ${clause}`);
280
+ }
281
+ assert.ok(!item.includes("persisted data"), "item 1 keeps the broad persisted-data form");
282
+ });
283
+
284
+ test("T10/AC4: item 3 covers changing or removing consumed contracts, not adding a flag, command or optional field", () => {
285
+ const item = highRiskItem(3);
286
+ assert.ok(item.startsWith("(3) changing or removing contracts others already consume ("), `item 3 is not limited to existing contracts: ${item}`);
287
+ for (const clause of ["public API", "CLI flags", "config formats", "exports", "mirrored prompts", "not adding a flag, command or optional field"]) {
288
+ assert.ok(item.includes(clause), `item 3 is missing: ${clause}`);
289
+ }
290
+ });
291
+
292
+ test("T10: escalate labels name the core high-risk items", () => {
293
+ for (const [n, label] of Object.entries(HIGH_RISK_ITEMS)) {
294
+ assert.ok(highRiskItem(Number(n)).startsWith(`(${n}) ${label}`), `escalate label ${n} drifts from the core list: ${label}`);
295
+ }
296
+ });
297
+
298
+ test("T9/AC2: the parallelism reason launches every unit together in background and waits for all", () => {
299
+ const parallel = lineStarting(writer, "- **Parallelism**");
300
+ for (const clause of [
301
+ "every disjoint unit in the same turn",
302
+ '`subagent_run` with `mode: "background"`',
303
+ "wait for all completions",
304
+ "serial launches void the reason",
305
+ "work inline",
306
+ "Seam check",
307
+ ]) {
308
+ assert.ok(parallel.includes(clause), `writer parallelism launch rule is missing: ${clause}`);
309
+ }
310
+ assert.ok(!/high risk|high-risk|verify/i.test(parallel), "parallel mixes risk or verification into the writer reasons");
311
+ });
312
+
313
+ // T4 (S2, AC6): the runtime rejects an overlapping live writer at admission,
314
+ // so the single-writer wording relaxes to disjoint surfaces or isolated worktrees.
315
+ test("AC6: parallel writers need disjoint Allowed edit surfaces (runtime-enforced) or isolated worktrees", () => {
316
+ const rule = "arallel writers only with disjoint Allowed edit surfaces (runtime-enforced) or isolated worktrees";
317
+ const harness = read("extensions/gentle-ai.ts");
318
+ const docs = read("docs/readme-reference.md");
319
+ assert.ok(core.includes(`- P${rule}.`), "core Safety must carry the relaxed writer rule");
320
+ assert.ok(harness.includes(`- P${rule}.`), "harness principles must carry the relaxed writer rule");
321
+ for (const [path, text] of Object.entries({ core, delegation, harness, skill, docs })) {
322
+ assert.ok(text.includes(rule), `${path} is missing the relaxed writer rule`);
323
+ for (const retired of ["single-threaded", "Never run parallel writers in one worktree", "do not run parallel writers unless isolated worktrees", "preserves a single writer thread", "one writer per task"]) {
324
+ assert.ok(!text.includes(retired), `${path} keeps the single-writer wording: ${retired}`);
325
+ }
326
+ }
327
+ });
@@ -4,6 +4,7 @@ import { dirname, join, relative, sep } from "node:path";
4
4
  import { tmpdir } from "node:os";
5
5
  import test from "node:test";
6
6
  import { fileURLToPath } from "node:url";
7
+ import { readDelegationDetail } from "./support/orchestrator-modules.ts";
7
8
 
8
9
  const ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
9
10
  const AGENTS = join(ROOT, "assets", "agents");
@@ -53,7 +54,7 @@ function strictJsonAgents(): string[] {
53
54
  }
54
55
 
55
56
  test("generic delegation contract instructs the same `## Key Learnings` closing block", () => {
56
- const delegation = readFileSync(join(ASSETS, "orchestrator-delegation.md"), "utf8");
57
+ const delegation = readDelegationDetail();
57
58
  const section = readSection(delegation, "Key Learnings closing block");
58
59
  assert.ok(section, "orchestrator-delegation.md must have a Key Learnings closing block section");
59
60
  for (const [label, regex] of KL_SEMANTICS) {
@@ -159,6 +160,6 @@ test("strict review and Judgment Day agents do not gain Key Learnings or trailin
159
160
  });
160
161
 
161
162
  test("the canonical Key Learnings heading has no trailing colon in any asset", () => {
162
- const delegation = readFileSync(join(ASSETS, "orchestrator-delegation.md"), "utf8");
163
+ const delegation = readDelegationDetail();
163
164
  assert.doesNotMatch(delegation, /`## Key Learnings:`/);
164
165
  });
@@ -32,7 +32,7 @@ const devBinaryGate = requireDevBinary({
32
32
  exists: typeof DEV_BINARY === "string" && DEV_BINARY.length > 0 && DEV_BINARY.startsWith("/") && existsSync(DEV_BINARY),
33
33
  env: process.env,
34
34
  });
35
- if (!devBinaryGate.run) console.log(`tests/devbinary/native-review-parity.devtest.ts: ${devBinaryGate.reason}`);
35
+ if (devBinaryGate.run === false) console.log(`tests/devbinary/native-review-parity.devtest.ts: ${devBinaryGate.reason}`);
36
36
  const RUNNABLE = devBinaryGate.run;
37
37
  const DEV_HOME = mkdtempSync(join(tmpdir(), "gentle-pi-dev-binary-home-"));
38
38
  const ORIGINAL_HOME = process.env.HOME;
@@ -85,9 +85,9 @@ const relayDevBinaryGate = POSIX
85
85
  })
86
86
  : { run: false as const, reason: "Windows is explicitly skipped until a native fake-pi.exe exists; this test never enables a shell fallback." };
87
87
  const RUNNABLE = POSIX && primaryDevBinaryGate.run && relayDevBinaryGate.run;
88
- if (!POSIX) console.log(`tests/devbinary/pi-host-relay.devtest.ts: ${relayDevBinaryGate.reason}`);
89
- if (!primaryDevBinaryGate.run) console.log(`tests/devbinary/pi-host-relay.devtest.ts: ${primaryDevBinaryGate.reason}`);
90
- if (!relayDevBinaryGate.run && POSIX) console.log(`tests/devbinary/pi-host-relay.devtest.ts: ${relayDevBinaryGate.reason}`);
88
+ if (relayDevBinaryGate.run === false && !POSIX) console.log(`tests/devbinary/pi-host-relay.devtest.ts: ${relayDevBinaryGate.reason}`);
89
+ if (primaryDevBinaryGate.run === false) console.log(`tests/devbinary/pi-host-relay.devtest.ts: ${primaryDevBinaryGate.reason}`);
90
+ if (relayDevBinaryGate.run === false && POSIX) console.log(`tests/devbinary/pi-host-relay.devtest.ts: ${relayDevBinaryGate.reason}`);
91
91
 
92
92
  const ZERO_FINDING_PATHS = Object.freeze([".github/workflows/relay.yml"]);
93
93
 
@@ -530,6 +530,176 @@ test("dev-binary: a garbage reviewer result is refused at admission as a proven
530
530
  assert.equal(reoffered, true, "the unconsumed slot must be reoffered by fresh STATUS");
531
531
  });
532
532
 
533
+ test("dev-binary: Pi returns a terminal single-lens escalation from the real committed candidate capture", { skip: !RUNNABLE }, async (t) => {
534
+ const cwd = repository(t, "gentle-pi-escalation-");
535
+ const home = mkdtempSync(join(tmpdir(), "gentle-pi-escalation-home-"));
536
+ t.after(() => { rmSync(home, { recursive: true, force: true }); __testing.setReviewHostRelayRunnerForTesting(); });
537
+ const environment = { ...reviewEnvironment(home), GENTLE_PI_CONFIG_HOME: join(home, "config"), GENTLE_PI_AGENT_HOME: join(home, "agent") }, binary = RELAY_DEV_BINARY!;
538
+ const baseRef = git(cwd, "rev-parse", "HEAD");
539
+ writeFileSync(join(cwd, "app.ts"), "export const value = 2;\n");
540
+ git(cwd, "add", "app.ts");
541
+ git(cwd, "commit", "-qm", "candidate");
542
+ enableGlobalReview(binary, cwd, cwd, environment);
543
+ const calls: NativeProcessCall[] = [];
544
+ const { controller, capture } = reviewToolsForNative(devNativeCli(binary, environment, calls));
545
+ const context = sessionContext(cwd), selector = { baseRef, committedOnly: true };
546
+ await controller.execute("escalation-inspect", { operation: "inspect", workspaceRoot: cwd }, undefined, undefined, context);
547
+ const prompted = record((await controller.execute("escalation-start", { operation: "start", workspaceRoot: cwd, input: JSON.stringify({ mode: "ordinary", ...selector }) }, undefined, undefined, context)).details, "escalation start");
548
+ assert.equal(prompted.outcome, "native-review-consent-required");
549
+ const started = record((await controller.execute("escalation-consent", { operation: "answer-consent", input: JSON.stringify({ consentBinding: prompted.consent_binding, answer: "granted" }) }, undefined, undefined, context)).details, "escalation consent");
550
+ const lineage = stringValue(record(started.result, "escalation start result").lineage_id, "escalation lineage");
551
+ const statusParameters = { operation: "status", lineageId: lineage, workspaceRoot: cwd, input: JSON.stringify(selector) };
552
+ const before = record((await controller.execute("escalation-status", statusParameters, undefined, undefined, context)).details, "escalation status");
553
+ assert.equal(record(record(before.result, "initial status").projection, "committed projection").kind, "base-diff");
554
+ const binding = collectBindingFor(before, "review.capture-result");
555
+ const reviewer = fauxReviewerFor((subjectHash) => JSON.stringify({
556
+ subject_hash: subjectHash,
557
+ inspection: { status: "completed", paths: ["app.ts"] },
558
+ findings: [{ id: "R3-001", location: "app.ts:1", severity: "CRITICAL", claim: "synthetic severe finding with unknown causal origin", proof_refs: ["app.ts:1"], evidence_class: "deterministic", causal_disposition: "unknown" }],
559
+ evidence: ["inspected the frozen committed candidate"],
560
+ }));
561
+ __testing.setReviewHostRelayRunnerForTesting(async (request) => {
562
+ reviewer.enqueue();
563
+ return await runReviewHostRelaySlot({ ...request, gentleAiExecutable: binary, environment, reviewerRegistry: reviewer.registry, selection: reviewer.selection, routingKey: reviewer.routingKey });
564
+ });
565
+ const parameters = { lineageId: lineage, workspaceRoot: cwd, collectBinding: binding };
566
+ const forecast = record((await capture.execute("escalation-forecast", parameters, undefined, undefined, context)).details, "escalation forecast");
567
+ assert.equal(forecast.outcome, "reviewer-model-run-forecast");
568
+ assert.equal(record(forecast.cost_forecast, "escalation forecast cost").model_runs, 1);
569
+ assert.equal(reviewer.calls.length, 0);
570
+ const offset = calls.length;
571
+ const result = record((await capture.execute("escalation-capture", { ...parameters, reviewerRunAcknowledged: true }, undefined, undefined, context)).details, "escalation capture");
572
+ assert.equal(result.status, "closed", JSON.stringify(result));
573
+ assert.equal(result.state, "escalated");
574
+ const closure = record(result.closure, "escalation closure");
575
+ assert.deepEqual(closure.escalation, { cause: "unknown_causality", finding_ids: ["R3-001"] });
576
+ assert.equal(closure.acknowledgement, undefined);
577
+ assert.equal(result.next_action, undefined);
578
+ assert.equal(reviewer.calls.length, 1);
579
+ assert.equal(calls.slice(offset).filter((call) => call.arguments[1] === "status").length, 1, "capture closes without a post-success reconciliation");
580
+ const after = record((await controller.execute("escalation-terminal-status", statusParameters, undefined, undefined, context)).details, "terminal status");
581
+ const nativeStatus = record(after.result, "terminal native status");
582
+ assert.equal(record(nativeStatus.authority, "terminal authority").state, "escalated");
583
+ assert.equal(record(nativeStatus.authority, "terminal authority").revision, closure.store_revision);
584
+ assert.equal(record(nativeStatus.next_transition, "terminal transition").reason_code, "native_stop_required");
585
+ assert.deepEqual(nativeStatus.escalation, closure.escalation);
586
+ });
587
+
588
+ // gentle-pi#998: real admitted validator rejection must survive Pi's closure decoder.
589
+ test("dev-binary: Pi preserves targeted-validator rejection evidence in an escalated correction closure", { skip: !RUNNABLE }, async (t) => {
590
+ const cwd = repository(t, "gentle-pi-validator-rejection-");
591
+ const home = mkdtempSync(join(tmpdir(), "gentle-pi-validator-rejection-home-"));
592
+ t.after(() => { rmSync(home, { recursive: true, force: true }); __testing.setReviewHostRelayRunnerForTesting(); });
593
+ const environment = { ...reviewEnvironment(home), GENTLE_PI_CONFIG_HOME: join(home, "config"), GENTLE_PI_AGENT_HOME: join(home, "agent"), PI_CODING_AGENT_DIR: join(home, "agent") };
594
+ const binary = RELAY_DEV_BINARY!;
595
+ assert.equal(realpathSync(binary), realpathSync(DEV_BINARY!));
596
+ writeFileSync(join(cwd, "app.ts"), "export const value = 2;\n");
597
+ enableGlobalReview(binary, cwd, cwd, environment);
598
+ const calls: NativeProcessCall[] = [];
599
+ const { controller, capture } = reviewToolsForNative(devNativeCli(binary, environment, calls));
600
+ const context = sessionContext(cwd);
601
+ const control = async (id: string, params: unknown) => record((await controller.execute(id, params, undefined, undefined, context)).details, id);
602
+ await control("rejection-inspect", { operation: "inspect", workspaceRoot: cwd });
603
+ const prompted = await control("rejection-start", { operation: "start", workspaceRoot: cwd, input: JSON.stringify({ mode: "ordinary" }) });
604
+ assert.equal(prompted.outcome, "native-review-consent-required");
605
+ const started = await control("rejection-consent", { operation: "answer-consent", input: JSON.stringify({ consentBinding: prompted.consent_binding, answer: "granted" }) });
606
+ const lineage = stringValue(record(started.result, "rejection start result").lineage_id, "rejection lineage");
607
+ const statusParameters = { operation: "status", workspaceRoot: cwd, lineageId: lineage };
608
+ const reviewer = fauxReviewerFor((subjectHash) => JSON.stringify({
609
+ subject_hash: subjectHash,
610
+ inspection: { status: "completed", paths: ["app.ts"] },
611
+ findings: [{ id: "R3-001", location: "app.ts:1", severity: "BLOCKER", claim: "value must be corrected", proof_refs: ["app.ts:1"], evidence_class: "deterministic", causal_disposition: "introduced" }],
612
+ evidence: ["inspected the frozen candidate"],
613
+ }));
614
+ let validator: ReturnType<typeof fauxReviewerFor> | undefined;
615
+ let emittedClosure: Record<string, unknown> | undefined;
616
+ const relayRoles: string[] = [];
617
+ __testing.setReviewHostRelayRunnerForTesting(async (request) => {
618
+ assert.equal(request.targetCwd, cwd);
619
+ const selected = request.routingKey === "review-validator" ? validator : reviewer;
620
+ assert.ok(selected, "validator fixture must be bound before capture");
621
+ relayRoles.push(request.routingKey!);
622
+ selected.enqueue();
623
+ const relay = await runReviewHostRelaySlot({ ...request, gentleAiExecutable: binary, environment, reviewerRegistry: selected.registry, selection: selected.selection, routingKey: request.routingKey });
624
+ if (request.routingKey === "review-validator") emittedClosure = record(JSON.parse(relay.submission) as unknown, "native rejection submission");
625
+ return relay;
626
+ });
627
+ const captureRole = async (id: string, binding: string) => {
628
+ const params = { lineageId: lineage, workspaceRoot: cwd, collectBinding: binding };
629
+ const modelCalls = reviewer.calls.length + (validator?.calls.length ?? 0), roleCalls = relayRoles.length;
630
+ const forecast = record((await capture.execute(`${id}-forecast`, params, undefined, undefined, context)).details, "rejection forecast");
631
+ assert.equal(forecast.outcome, "reviewer-model-run-forecast");
632
+ assert.equal(reviewer.calls.length + (validator?.calls.length ?? 0), modelCalls, "no model call before acknowledgement");
633
+ assert.equal(relayRoles.length, roleCalls, "no role replay before acknowledgement");
634
+ const result = record((await capture.execute(id, { ...params, reviewerRunAcknowledged: true }, undefined, undefined, context)).details, id);
635
+ assert.equal(reviewer.calls.length + (validator?.calls.length ?? 0), modelCalls + 1);
636
+ return result;
637
+ };
638
+ let correctionOpened = false;
639
+ for (let attempt = 0; attempt < 4; attempt += 1) {
640
+ const status = await control(`rejection-reviewer-status-${attempt}`, statusParameters);
641
+ const binding = collectBindingsFor(status, "review.capture-result")[0];
642
+ assert.ok(binding, "native STATUS must offer a reviewer slot");
643
+ const result = await captureRole(`rejection-reviewer-${attempt}`, binding);
644
+ assert.ok(["captured", "closed"].includes(stringValue(result.status, "reviewer capture status")));
645
+ if (result.closure !== undefined) {
646
+ assert.equal(record(result.closure, "reviewer closure").state, "correction_required");
647
+ correctionOpened = true;
648
+ break;
649
+ }
650
+ }
651
+ assert.equal(correctionOpened, true, "real admitted findings must open correction");
652
+ const correctionStatus = await control("rejection-correction-status", statusParameters);
653
+ const correctionBinding = collectBindingFor(correctionStatus, "review.capture-correction-plan");
654
+ const plan = record((await capture.execute("rejection-plan", { lineageId: lineage, workspaceRoot: cwd, collectBinding: correctionBinding, correctionLines: 1 }, undefined, undefined, context)).details, "correction plan");
655
+ assert.equal(record(plan.closure, "correction plan closure").state, "correction_required");
656
+ writeFileSync(join(cwd, "app.ts"), "export const value = 3;\n");
657
+ const validationStatus = await control("rejection-validation-status", statusParameters);
658
+ const binding = collectBindingFor(validationStatus, "review.capture-validation");
659
+ const request = record(parsedCollectBinding(binding).validationRequest, "native validation request");
660
+ const requestHash = collectBindingArgument(binding, "request-hash"), target = collectBindingArgument(binding, "target");
661
+ assert.equal(request.requestHash, requestHash);
662
+ assert.equal(request.correctionTargetIdentity, target);
663
+ assert.deepEqual(request.correctionPaths, ["app.ts"]);
664
+ const proof = {
665
+ targeted_validation_request_hash: requestHash,
666
+ correction_target_identity: target,
667
+ original_criteria: { passed: true, evidence: ["value correction satisfies original criteria"] },
668
+ correction_regression: { passed: false, evidence: ["corrected value breaks the consumer"], regressions: [{ id: "REG-001", location: "app.ts:1", claim: "value 3 breaks the consumer", proof_refs: ["app.ts:1", "focused regression check"] }] },
669
+ follow_ups: [{ observation: "consumer expectation needs investigation", proof_refs: ["app.ts:1"] }],
670
+ };
671
+ validator = fauxReviewerFor(() => JSON.stringify(proof));
672
+ const result = await captureRole("rejection-validation", binding);
673
+ assert.ok(emittedClosure, "real native submission must emit a closure");
674
+ t.diagnostic(`native rejection closure: ${JSON.stringify(emittedClosure)}`);
675
+ assert.equal(emittedClosure.operation, "review/capture-validation");
676
+ assert.equal(emittedClosure.state, "escalated");
677
+ assert.equal(emittedClosure.acknowledgement, undefined);
678
+ const escalation = record(emittedClosure.escalation, "native rejection escalation");
679
+ assert.equal(escalation.cause, "targeted_validator_rejected");
680
+ assert.deepEqual(emittedClosure.targeted_validator_evidence, proof);
681
+ const terminal = record(candidateJson(binary, cwd, ["review", "status", "--cwd", cwd, "--contract", "gentle-ai.review-integration/v2", "--agent", "pi", "--next-transition", "--lineage", lineage], environment), "persisted rejection STATUS");
682
+ assert.equal(record(terminal.authority, "terminal authority").state, "escalated");
683
+ assert.equal(record(terminal.authority, "terminal authority").revision, emittedClosure.store_revision);
684
+ assert.equal(record(terminal.next_transition, "terminal transition").kind, "stop");
685
+ assert.equal(record(terminal.next_transition, "terminal transition").reason_code, "native_stop_required");
686
+ assert.deepEqual(terminal.escalation, escalation);
687
+ assert.equal(validator.calls.length, 1);
688
+ assert.ok(validator.calls[0]!.promptText.includes(requestHash));
689
+ assert.equal(result.status, "closed", JSON.stringify(result));
690
+ assert.equal(result.outcome, "native-last-event-closure");
691
+ assert.equal(result.state, "escalated");
692
+ const closure = record(result.closure, "Pi rejection closure");
693
+ assert.equal(closure.operation, "review/capture-validation");
694
+ assert.equal(closure.lineage_id, lineage);
695
+ assert.equal(closure.store_revision, emittedClosure.store_revision);
696
+ assert.deepEqual(closure.escalation, escalation);
697
+ assert.deepEqual(closure.targeted_validator_evidence, proof, "Pi must preserve all native proof fields and hash bindings");
698
+ assert.equal(closure.acknowledgement, undefined);
699
+ assert.equal(result.next_action, undefined);
700
+ assert.equal(calls.some((call) => call.arguments[1] === "capture-validation"), false, "validation must use the registered host relay");
701
+ });
702
+
533
703
  // This completes the same organic A -> B path through correction evidence,
534
704
  // host-mediated targeted validation, and terminal approval. The only
535
705
  // reviewers are the fixed faux registries below; no real model, provider, or