gentle-pi 4.0.0 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (425) hide show
  1. package/README.md +157 -18
  2. package/assets/agents/gentle-ai-explore.md +11 -4
  3. package/assets/agents/gentle-ai-verify.md +23 -4
  4. package/assets/agents/gentle-ai-worker.md +6 -6
  5. package/assets/install-wizard/index.html +47 -0
  6. package/assets/install-wizard/wizard.css +1279 -0
  7. package/assets/install-wizard/wizard.js +951 -0
  8. package/assets/orchestrator-delegation.md +70 -163
  9. package/assets/orchestrator-memory.md +11 -4
  10. package/assets/orchestrator-prompts.md +48 -0
  11. package/assets/orchestrator-tracking.md +53 -0
  12. package/assets/orchestrator-verification.md +28 -0
  13. package/assets/orchestrator-writer.md +38 -0
  14. package/assets/orchestrator.md +29 -25
  15. package/assets/sounds/LICENSE.md +52 -0
  16. package/assets/sounds/attention.wav +0 -0
  17. package/assets/sounds/error.wav +0 -0
  18. package/assets/sounds/success.wav +0 -0
  19. package/bin/engram-guard +9 -0
  20. package/bin/gentle-shell-install.mjs +302 -0
  21. package/bin/gentle-shell.mjs +708 -22
  22. package/contracts/review-provider-contract-mirror/provider-contract.lock.json +7 -7
  23. package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/manifest.json +2 -2
  24. package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/orchestration/pi.md +16 -4
  25. package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/generated/provider-capabilities.baseline.json +2 -2
  26. package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/generated/provider-roles.baseline.json +1 -1
  27. package/docs/auto-permission-answers.md +25 -0
  28. package/docs/auto-permission-evidence.md +39 -0
  29. package/docs/bundled-install.md +399 -0
  30. package/docs/delegated-verification.md +3 -5
  31. package/docs/gentle-agents-activity.md +513 -0
  32. package/docs/gentle-shell-uninstall.md +136 -0
  33. package/docs/gentle-shell.md +90 -33
  34. package/docs/install-wizard.md +2040 -0
  35. package/docs/native-authority-architecture.md +1 -1
  36. package/docs/permission-modes.md +127 -0
  37. package/docs/prompt-history.md +1 -1
  38. package/docs/readme-reference.md +310 -47
  39. package/docs/render-cache-performance.md +28 -0
  40. package/docs/session-profile-format.md +39 -0
  41. package/docs/sound-notifications-proposal.md +191 -0
  42. package/docs/sound-notifications.md +114 -0
  43. package/docs/yolo-mode.md +11 -10
  44. package/extensions/ask-user-choice.ts +4 -0
  45. package/extensions/ask-user-question.ts +6 -1
  46. package/extensions/child-context.ts +11 -2
  47. package/extensions/child-safety.ts +4 -2
  48. package/extensions/codegraph-tools.ts +18 -5
  49. package/extensions/gentle-agents.ts +718 -87
  50. package/extensions/gentle-ai.ts +991 -263
  51. package/extensions/gentle-herdr-activity.ts +81 -0
  52. package/extensions/gentle-notifications.ts +104 -0
  53. package/extensions/gentle-shell.ts +271 -80
  54. package/extensions/gentle-todo.ts +123 -18
  55. package/extensions/history/index.ts +11 -6
  56. package/extensions/pi-pretty.ts +39 -3
  57. package/extensions/quiet-tools.ts +91 -21
  58. package/extensions/skill-registry.ts +175 -46
  59. package/extensions/startup-banner.ts +11 -2
  60. package/lib/agent-home.ts +10 -1
  61. package/lib/agent-model-resolution.ts +80 -0
  62. package/lib/agent-profiles.ts +13 -3
  63. package/lib/agents-completion-delivery.ts +11 -3
  64. package/lib/agents-messaging.ts +1 -1
  65. package/lib/agents-protocol.ts +40 -1
  66. package/lib/agents-runner.ts +90 -15
  67. package/lib/auto-permission-answers.ts +133 -0
  68. package/lib/auto-permission-classifier.ts +459 -0
  69. package/lib/auto-permission-evidence.ts +88 -0
  70. package/lib/auto-permission-memory.ts +69 -0
  71. package/lib/auto-permission-runtime.ts +484 -0
  72. package/lib/auto-permission-tool-context.ts +61 -0
  73. package/lib/background-jobs-tools.ts +202 -0
  74. package/lib/background-jobs.ts +253 -0
  75. package/lib/background-monitor.ts +154 -0
  76. package/lib/bounded-writer-admission.ts +83 -10
  77. package/lib/builtin-codemode-optout.ts +166 -0
  78. package/lib/card-content-policy.ts +85 -0
  79. package/lib/child-package-injection.ts +62 -0
  80. package/lib/codemode-renderer.ts +14 -4
  81. package/lib/command-palette-catalog.ts +3 -2
  82. package/lib/context-threshold.ts +26 -0
  83. package/lib/destructive-command-guard.ts +104 -28
  84. package/lib/gentle-ai-binary.ts +109 -4
  85. package/lib/gentle-shell-bundled.ts +535 -0
  86. package/lib/gentle-shell-launcher.ts +75 -24
  87. package/lib/gentle-shell-startup-flow.ts +54 -0
  88. package/lib/gentle-shell-startup-update.ts +92 -0
  89. package/lib/gentle-shell-uninstall.ts +840 -0
  90. package/lib/gentle-shell-update-panel.ts +231 -0
  91. package/lib/herdr-activity.ts +183 -0
  92. package/lib/inprocess-reviewer.ts +3 -0
  93. package/lib/jobs-sidebar-state.ts +20 -0
  94. package/lib/jobs-view.ts +161 -0
  95. package/lib/model-price-ratio.ts +83 -0
  96. package/lib/model-routing-authority.ts +6 -1
  97. package/lib/nan-provider.ts +33 -11
  98. package/lib/native-review-cli.ts +194 -73
  99. package/lib/notification-audio-native.ts +162 -0
  100. package/lib/notification-audio-windows.ts +399 -0
  101. package/lib/notification-audio.ts +332 -0
  102. package/lib/notification-customize.ts +241 -0
  103. package/lib/notification-events.ts +113 -0
  104. package/lib/notification-policy.ts +148 -0
  105. package/lib/notification-pulse-client.ts +367 -0
  106. package/lib/notification-pulse-protocol.ts +305 -0
  107. package/lib/notification-pulse-stream.ts +213 -0
  108. package/lib/notification-pulse-worker.ts +66 -0
  109. package/lib/notification-scheduler.ts +173 -0
  110. package/lib/notification-service.ts +202 -0
  111. package/lib/notification-ui.ts +87 -0
  112. package/lib/odd-phase.ts +1 -1
  113. package/lib/orchestrator-alias.ts +20 -0
  114. package/lib/orchestrator-catalog.ts +79 -0
  115. package/lib/orchestrator-consultation.ts +61 -0
  116. package/lib/orchestrator-discovery.ts +49 -0
  117. package/lib/orchestrator-helper-consent.ts +122 -0
  118. package/lib/orchestrator-helper.ts +167 -0
  119. package/lib/orchestrator-message-card.ts +105 -0
  120. package/lib/orchestrator-presence.ts +115 -14
  121. package/lib/orchestrator-scope.ts +80 -0
  122. package/lib/orchestrator-state.ts +107 -0
  123. package/lib/orchestrator-tool-card.ts +280 -0
  124. package/lib/orchestrator-work-search.ts +218 -0
  125. package/lib/orchestrator-work.ts +79 -0
  126. package/lib/permission-mode-policy.ts +59 -0
  127. package/lib/review-candidate-view-owner.ts +8 -6
  128. package/lib/review-candidate-view.ts +3 -2
  129. package/lib/review-compact-contract.ts +2 -2
  130. package/lib/review-host-relay.ts +7 -2
  131. package/lib/review-integration-v2.ts +100 -22
  132. package/lib/review-publication-gate.ts +1 -1
  133. package/lib/review-risk-assessment.ts +77 -51
  134. package/lib/review-sidebar-state.ts +2 -1
  135. package/lib/review-transaction.ts +72 -3
  136. package/lib/runtime-metrics-children.ts +1 -1
  137. package/lib/runtime-metrics-native.ts +3 -2
  138. package/lib/selection-engine.ts +22 -7
  139. package/lib/session-change-capture.ts +5 -4
  140. package/lib/session-changes.ts +27 -22
  141. package/lib/session-profile-binding.ts +83 -0
  142. package/lib/session-profile-freeze.ts +261 -0
  143. package/lib/session-profile-persistence.ts +187 -0
  144. package/lib/session-worktree-registry.ts +74 -8
  145. package/lib/shell-bar.ts +41 -11
  146. package/lib/shell-changes-view.ts +40 -13
  147. package/lib/shell-changes.ts +88 -71
  148. package/lib/shell-prompt.ts +6 -4
  149. package/lib/shell-sidebar-layout.ts +5 -1
  150. package/lib/shell-todo.ts +81 -26
  151. package/lib/shell-usage.ts +4 -3
  152. package/lib/status-timing-diagnostics.ts +201 -0
  153. package/lib/telemetry-trigger.ts +3 -1
  154. package/lib/vim-editor-adapter.ts +27 -5
  155. package/lib/visual-customization-policy.ts +20 -9
  156. package/lib/visual-customize-view.ts +171 -20
  157. package/lib/writer-surfaces.ts +162 -0
  158. package/lib/yolo-session-policy.ts +198 -121
  159. package/package.json +5 -5
  160. package/runtime/child-package-injection.mjs +63 -0
  161. package/runtime/gentle-ai-binary.mjs +109 -4
  162. package/runtime/gentle-shell-bundled.mjs +536 -0
  163. package/runtime/gentle-shell-launcher.mjs +74 -23
  164. package/runtime/gentle-shell-startup-flow.mjs +55 -0
  165. package/runtime/gentle-shell-startup-update.mjs +93 -0
  166. package/runtime/gentle-shell-uninstall.mjs +841 -0
  167. package/runtime/gentle-shell-update-panel.mjs +232 -0
  168. package/runtime/native-review-cli.mjs +191 -70
  169. package/runtime/review-integration-v2.mjs +100 -22
  170. package/runtime/review-risk-assessment.mjs +77 -51
  171. package/runtime/status-timing-diagnostics.mjs +202 -0
  172. package/runtime/telemetry-trigger.mjs +3 -1
  173. package/scripts/bootstrap.cmd +255 -0
  174. package/scripts/bootstrap.sh +168 -0
  175. package/scripts/build-distribution.mjs +157 -0
  176. package/scripts/build-installer-bundles.mjs +136 -0
  177. package/scripts/build-runtime-modules.mjs +7 -0
  178. package/scripts/bundled-install.mjs +1308 -0
  179. package/scripts/bundled-wizard.mjs +399 -0
  180. package/scripts/check-types.mjs +4 -6
  181. package/scripts/gentle-ai-installer.mjs +79 -32
  182. package/scripts/install-gentle-ai.mjs +48 -1
  183. package/scripts/install-tui-mode-setting.mjs +10 -1
  184. package/scripts/install-wizard-preview.mjs +154 -0
  185. package/scripts/installer-downloads.mjs +648 -0
  186. package/scripts/installer-preflight.mjs +387 -0
  187. package/scripts/installer-probes.mjs +564 -0
  188. package/scripts/installer-runner.mjs +1102 -0
  189. package/scripts/installer-server.mjs +881 -0
  190. package/scripts/installer-windows-artifacts.json +15 -0
  191. package/scripts/installer-windows.mjs +752 -0
  192. package/scripts/main-channel.mjs +452 -0
  193. package/scripts/npm-pack-result.mjs +31 -0
  194. package/scripts/pi-update.mjs +223 -0
  195. package/scripts/render-cache-performance-worker.mjs +205 -0
  196. package/scripts/render-cache-performance.mjs +115 -0
  197. package/scripts/run-test-suite.mjs +22 -8
  198. package/scripts/test-installer-acceptance.sh +161 -0
  199. package/scripts/test-packed-runner.mjs +23 -5
  200. package/scripts/types-baseline.json +2 -92
  201. package/scripts/verify-package-files.mjs +82 -18
  202. package/skills/gentle-ai/SKILL.md +12 -11
  203. package/tests/agent-home.test.ts +56 -3
  204. package/tests/agent-model-resolution.test.ts +113 -0
  205. package/tests/agent-profiles.test.ts +12 -0
  206. package/tests/agent-risk-escalation.test.ts +123 -0
  207. package/tests/agents-completion-delivery.test.ts +17 -0
  208. package/tests/agents-protocol.test.ts +57 -0
  209. package/tests/agents-queries.test.ts +9 -9
  210. package/tests/agents-runner-process.test.ts +31 -0
  211. package/tests/agents-runner.test.ts +89 -4
  212. package/tests/append-system-prompt-route.test.ts +18 -1
  213. package/tests/auto-mode-human-answers.test.ts +68 -0
  214. package/tests/auto-mode-invariants.test.ts +276 -0
  215. package/tests/auto-mode-sdk.test.ts +84 -0
  216. package/tests/auto-mode.test.ts +49 -0
  217. package/tests/auto-permission-answers.test.ts +170 -0
  218. package/tests/auto-permission-classifier.test.ts +634 -0
  219. package/tests/auto-permission-compound.test.ts +24 -0
  220. package/tests/auto-permission-evidence.test.ts +88 -0
  221. package/tests/auto-permission-memory.test.ts +46 -0
  222. package/tests/auto-permission-public-regressions.test.ts +111 -0
  223. package/tests/auto-permission-runtime.test.ts +454 -0
  224. package/tests/autonomous-guard.test.ts +86 -16
  225. package/tests/background-cache-warming.test.ts +2 -1
  226. package/tests/background-jobs.test.ts +227 -0
  227. package/tests/background-monitor.test.ts +195 -0
  228. package/tests/background-subagents-default-mode.test.ts +28 -5
  229. package/tests/bounded-stop-contract.test.ts +85 -0
  230. package/tests/bounded-writer-admission.test.ts +52 -1
  231. package/tests/bridge-wake-lifecycle.test.ts +129 -0
  232. package/tests/bridge-wake-rendering.test.ts +56 -0
  233. package/tests/build-distribution.test.ts +269 -0
  234. package/tests/builtin-codemode-optout.test.ts +242 -0
  235. package/tests/bundled-install.test.ts +1319 -0
  236. package/tests/bundled-wizard.test.ts +505 -0
  237. package/tests/card-content-policy.test.ts +68 -0
  238. package/tests/card-style-policy.test.ts +34 -2
  239. package/tests/child-codegraph-bootstrap.test.ts +74 -0
  240. package/tests/child-package-entrypoints.test.ts +65 -0
  241. package/tests/child-package-injection.test.ts +74 -0
  242. package/tests/child-safety.test.ts +12 -0
  243. package/tests/child-web-package.test.ts +224 -0
  244. package/tests/codegraph-tools.test.ts +35 -0
  245. package/tests/codemode-rendering.test.ts +107 -2
  246. package/tests/command-palette.test.ts +5 -5
  247. package/tests/context-threshold.test.ts +31 -0
  248. package/tests/delegate-for-reason-contract.test.ts +327 -0
  249. package/tests/delegated-key-learnings-contract.test.ts +3 -2
  250. package/tests/devbinary/native-review-parity.devtest.ts +1 -1
  251. package/tests/devbinary/pi-host-relay.devtest.ts +173 -3
  252. package/tests/engram-safety.test.ts +316 -0
  253. package/tests/generic-agent-tools.test.ts +16 -1
  254. package/tests/gentle-agents.test.ts +1895 -76
  255. package/tests/gentle-ai-binary-isolation.test.ts +135 -0
  256. package/tests/gentle-ai-binary.test.ts +19 -2
  257. package/tests/gentle-ai-child-guards.test.ts +202 -0
  258. package/tests/gentle-ai-dev-binary-surfacing.test.ts +125 -15
  259. package/tests/gentle-ai-installer.test.ts +548 -58
  260. package/tests/gentle-ai-prompt-model-routing.test.ts +123 -0
  261. package/tests/gentle-ai-renderer.test.ts +6 -6
  262. package/tests/gentle-ai.test.ts +999 -31
  263. package/tests/gentle-herdr-activity.test.ts +100 -0
  264. package/tests/gentle-notifications.test.ts +427 -0
  265. package/tests/gentle-shell-bin.test.ts +263 -19
  266. package/tests/gentle-shell-bundled.test.ts +1036 -0
  267. package/tests/gentle-shell-launcher.test.ts +251 -25
  268. package/tests/gentle-shell-startup-flow.test.ts +75 -0
  269. package/tests/gentle-shell-startup-update.test.ts +85 -0
  270. package/tests/gentle-shell-uninstall.test.ts +978 -0
  271. package/tests/gentle-shell-update-panel.test.ts +158 -0
  272. package/tests/gentle-shell.test.ts +611 -100
  273. package/tests/gentle-todo.test.ts +317 -14
  274. package/tests/herdr-activity.test.ts +243 -0
  275. package/tests/history-agent-home.test.ts +81 -0
  276. package/tests/history-child-guard.test.ts +70 -0
  277. package/tests/inprocess-reviewer.test.ts +20 -0
  278. package/tests/install-gentle-ai-failure.test.ts +69 -0
  279. package/tests/install-tui-mode-setting.test.ts +15 -0
  280. package/tests/install-wizard.test.ts +1223 -0
  281. package/tests/installer-bundles.test.ts +122 -0
  282. package/tests/installer-posix-bootstrap.test.ts +1201 -0
  283. package/tests/installer-preflight.test.ts +750 -0
  284. package/tests/installer-probes.test.ts +1106 -0
  285. package/tests/installer-runner.test.ts +2159 -0
  286. package/tests/installer-server.test.ts +1567 -0
  287. package/tests/installer-windows-bootstrap.test.ts +2410 -0
  288. package/tests/jobs-view.test.ts +160 -0
  289. package/tests/main-channel-upgrade.test.ts +395 -0
  290. package/tests/main-channel.test.ts +344 -0
  291. package/tests/maintainer/provider-relay.maintest.ts +13 -12
  292. package/tests/model-price-ratio.test.ts +116 -0
  293. package/tests/model-profiles-preservation.test.ts +153 -0
  294. package/tests/model-routing-package.test.ts +10 -0
  295. package/tests/nan-provider-runtime.test.ts +156 -0
  296. package/tests/nan-provider.test.ts +28 -18
  297. package/tests/native-binary-gate.test.ts +2 -0
  298. package/tests/native-review-capability-contract.test.ts +7 -1
  299. package/tests/native-review-cli.test.ts +236 -1
  300. package/tests/native-review-consent.test.ts +2 -2
  301. package/tests/native-review-parity-runtime.test.ts +1 -1
  302. package/tests/native-review-parity.test.ts +12 -48
  303. package/tests/native-risk-evidence-parity.test.ts +21 -0
  304. package/tests/no-self-pause-contract.test.ts +87 -0
  305. package/tests/notification-audio-native.test.ts +281 -0
  306. package/tests/notification-audio-windows.test.ts +581 -0
  307. package/tests/notification-audio.test.ts +548 -0
  308. package/tests/notification-customize.test.ts +655 -0
  309. package/tests/notification-events.test.ts +115 -0
  310. package/tests/notification-policy.test.ts +201 -0
  311. package/tests/notification-pulse-client.test.ts +356 -0
  312. package/tests/notification-pulse-protocol.test.ts +213 -0
  313. package/tests/notification-pulse-stream.test.ts +320 -0
  314. package/tests/notification-scheduler.test.ts +258 -0
  315. package/tests/notification-ui.test.ts +157 -0
  316. package/tests/npm-pack-result.test.ts +41 -0
  317. package/tests/odd-integration.test.ts +14 -5
  318. package/tests/odd-routing-canonical-ratchet.test.ts +53 -12
  319. package/tests/odd-routing-contract.test.ts +80 -23
  320. package/tests/odd-runtime-delegation-gate.test.ts +4 -1
  321. package/tests/orchestrator-budget.test.ts +29 -6
  322. package/tests/orchestrator-catalog.test.ts +146 -0
  323. package/tests/orchestrator-consultation-sdk.test.ts +614 -0
  324. package/tests/orchestrator-consultation.test.ts +111 -0
  325. package/tests/orchestrator-discovery.test.ts +241 -0
  326. package/tests/orchestrator-helper-consent.test.ts +171 -0
  327. package/tests/orchestrator-helper.test.ts +297 -0
  328. package/tests/orchestrator-message-card.test.ts +190 -0
  329. package/tests/orchestrator-presence.test.ts +23 -0
  330. package/tests/orchestrator-rdd-ownership.test.ts +6 -5
  331. package/tests/orchestrator-scope.test.ts +107 -0
  332. package/tests/orchestrator-state.test.ts +114 -0
  333. package/tests/orchestrator-status-publication-contract.test.ts +34 -0
  334. package/tests/orchestrator-tool-card.test.ts +398 -0
  335. package/tests/orchestrator-work-search.test.ts +318 -0
  336. package/tests/orchestrator-work.test.ts +147 -0
  337. package/tests/package-manifest.test.ts +105 -21
  338. package/tests/packed-runner-owned-path.test.ts +35 -1
  339. package/tests/parallel-reason-contract.test.ts +21 -0
  340. package/tests/parallel-writer-admission.test.ts +210 -0
  341. package/tests/permission-mode-policy.test.ts +194 -0
  342. package/tests/persona-single-channel.test.ts +4 -12
  343. package/tests/pi-pretty.test.ts +56 -0
  344. package/tests/pi-update-launcher.test.ts +87 -0
  345. package/tests/pi-update.test.ts +186 -0
  346. package/tests/provider-defect-handoff.test.ts +4 -3
  347. package/tests/quiet-tool-rendering.test.ts +160 -3
  348. package/tests/rdd-aware-verification-contract.test.ts +29 -41
  349. package/tests/rdd-status-line.test.ts +52 -0
  350. package/tests/render-cache-performance.test.ts +48 -0
  351. package/tests/review-agent-end-preflight.test.ts +62 -10
  352. package/tests/review-candidate-view.test.ts +101 -12
  353. package/tests/review-contract-prompt.test.ts +33 -4
  354. package/tests/review-controller-native-recovery.test.ts +327 -17
  355. package/tests/review-controller-native-routing.test.ts +255 -1
  356. package/tests/review-controller-workspace-root.test.ts +43 -2
  357. package/tests/review-controller.test.ts +2 -2
  358. package/tests/review-correction-lifecycle.test.ts +2 -1
  359. package/tests/review-gate.test.ts +7 -4
  360. package/tests/review-graph-schema.test.ts +2 -2
  361. package/tests/review-host-relay-routing.test.ts +5 -3
  362. package/tests/review-host-relay.test.ts +49 -2
  363. package/tests/review-integration-v2-forward.test.ts +21 -1
  364. package/tests/review-json-arguments.test.ts +167 -0
  365. package/tests/review-last-event-closure.test.ts +157 -8
  366. package/tests/review-ledger-contract.test.ts +5 -4
  367. package/tests/review-policy-judgment-day.test.ts +1 -1
  368. package/tests/review-policy-ordinary.test.ts +2 -2
  369. package/tests/review-relay-transport-agent.test.ts +66 -1
  370. package/tests/review-risk-assessment.test.ts +109 -33
  371. package/tests/review-session-standing-permission-controller.test.ts +16 -17
  372. package/tests/review-session-standing-permission-ipc.test.ts +6 -5
  373. package/tests/review-test-fixtures.ts +7 -1
  374. package/tests/review-transaction.test.ts +18 -1
  375. package/tests/run-test-suite.test.ts +83 -5
  376. package/tests/runtime-harness.mjs +42 -15
  377. package/tests/runtime-metrics-extension.test.ts +1 -1
  378. package/tests/runtime-metrics-native.test.ts +31 -1
  379. package/tests/selection-engine.test.ts +65 -5
  380. package/tests/session-change-capture.test.ts +10 -10
  381. package/tests/session-changes-shell.test.ts +242 -48
  382. package/tests/session-changes.test.ts +17 -24
  383. package/tests/session-profile-binding.test.ts +163 -0
  384. package/tests/session-profile-freeze.test.ts +394 -0
  385. package/tests/session-profile-persistence.test.ts +334 -0
  386. package/tests/session-subject-routing-contract.test.ts +34 -0
  387. package/tests/session-worktree-registry.test.ts +57 -2
  388. package/tests/shell-bar.test.ts +40 -0
  389. package/tests/shell-changes-view.test.ts +85 -34
  390. package/tests/shell-changes.test.ts +79 -62
  391. package/tests/shell-render-cache-runtime.test.ts +69 -0
  392. package/tests/shell-render-cache.test.ts +129 -0
  393. package/tests/shell-sidebar-layout.test.ts +27 -2
  394. package/tests/shell-todo.test.ts +132 -11
  395. package/tests/shell-usage-view.test.ts +1 -1
  396. package/tests/shell-usage.test.ts +35 -0
  397. package/tests/short-check-routing-contract.test.ts +49 -0
  398. package/tests/skill-registry-watcher.test.ts +119 -0
  399. package/tests/skill-registry.test.ts +311 -5
  400. package/tests/startup-banner.test.ts +117 -3
  401. package/tests/startup-home-repo.test.ts +170 -0
  402. package/tests/status-timing-diagnostics.test.ts +283 -0
  403. package/tests/support/orchestrator-modules.ts +19 -0
  404. package/tests/task-size-routing-contract.test.ts +241 -0
  405. package/tests/telemetry-trigger.test.ts +27 -1
  406. package/tests/test-strength-contract.test.ts +124 -0
  407. package/tests/verify-agent-contract.test.ts +90 -0
  408. package/tests/verify-first-launch-contract.test.ts +139 -0
  409. package/tests/verify-package-files.test.ts +56 -0
  410. package/tests/vim-editor-adapter.test.ts +47 -21
  411. package/tests/visual-customization-policy.test.ts +32 -3
  412. package/tests/visual-customize-view.test.ts +288 -10
  413. package/tests/writer-edit-surface-scope.test.ts +59 -2
  414. package/tests/writer-surfaces.test.ts +199 -0
  415. package/tests/yolo-customize.test.ts +18 -3
  416. package/tests/yolo-mode-runtime.test.ts +37 -10
  417. package/tests/yolo-mode.test.ts +38 -5
  418. package/tests/yolo-session-policy.test.ts +1 -1
  419. /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/README.md +0 -0
  420. /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/schemas/lens.schema.json +0 -0
  421. /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/schemas/refuter.schema.json +0 -0
  422. /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/schemas/targeted-validator.schema.json +0 -0
  423. /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/vectors/lens.json +0 -0
  424. /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/vectors/refuter.json +0 -0
  425. /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/vectors/targeted-validator.json +0 -0
@@ -0,0 +1,283 @@
1
+ import assert from "node:assert/strict";
2
+ import { readFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import test from "node:test";
5
+ import type { ExtensionAPI, ExtensionContext, ExtensionToolContext, ToolDefinition } from "@earendil-works/pi-coding-agent";
6
+ import { createGentleAiExtension } from "../extensions/gentle-ai.ts";
7
+ import { createReviewSidebarPublisher } from "../lib/review-sidebar-state.ts";
8
+ import {
9
+ StatusTimingDiagnostics, observeNativeStatus, measureStatusSync, measureStatusAsync, measureStatusSidebar,
10
+ } from "../lib/status-timing-diagnostics.ts";
11
+ import { NativeReviewCliV216, createNodeExecFileAdapter, type ExecFileAdapter, type ExecFileRequest } from "../lib/native-review-cli.ts";
12
+
13
+ function context() {
14
+ let id = "private-session";
15
+ const notices: string[] = [];
16
+ const ctx = {
17
+ cwd: process.cwd(),
18
+ sessionManager: { getSessionId: () => id },
19
+ ui: { notify: (message: string) => notices.push(message) },
20
+ } as unknown as ExtensionToolContext;
21
+ return { ctx, notices, replace: () => { id = "replacement-session"; } };
22
+ }
23
+
24
+ function wrapped<T>(timing: StatusTimingDiagnostics, run: () => Promise<T>) {
25
+ const definition = { name: "gentle_review", execute: run } as unknown as ToolDefinition;
26
+ return (ctx: ExtensionToolContext) => timing.tool(definition).execute("private-call", { operation: "inspect" }, undefined, undefined, ctx);
27
+ }
28
+
29
+ function summary(timing: StatusTimingDiagnostics, host: ReturnType<typeof context>) {
30
+ timing.command("show", host.ctx);
31
+ return JSON.parse(host.notices.at(-1)!) as {
32
+ host_total_ms: number; dispatch_ms: number; sidebar_ms: number; completion_ms: number;
33
+ truncated: boolean; statuses: Array<{ timeout_ms: number; total_ms: number; resolution_ms: number; adapter_ms: number; decode_ms: number; outcome: Record<string, unknown> }>;
34
+ };
35
+ }
36
+
37
+ const statusBytes = readFileSync(join(process.cwd(), "tests/fixtures/devbinary/status-v5.captured.json"), "utf8");
38
+
39
+ test("disabled diagnostics do not consult the clock or alter protocol, invocations, or deadlines", async () => {
40
+ const host = context();
41
+ let clockCalls = 0;
42
+ const timing = new StatusTimingDiagnostics(() => { clockCalls++; throw new Error("observer failed"); });
43
+ const requests: ExecFileRequest[] = [];
44
+ const adapter: ExecFileAdapter = async (request) => {
45
+ requests.push(request);
46
+ return { stdout: statusBytes, stderr: "", exitCode: 0, signal: null, timedOut: false, outputLimitExceeded: false };
47
+ };
48
+ const cli = new NativeReviewCliV216(adapter, process.execPath, 30_000);
49
+ const baseline = await cli.targetStatus({ cwd: process.cwd(), agent: "pi" });
50
+ const actual = await wrapped(timing, () => cli.targetStatus({ cwd: process.cwd(), agent: "pi" }))(host.ctx);
51
+ assert.deepEqual(actual, baseline);
52
+ assert.equal(clockCalls, 0);
53
+ assert.deepEqual(requests[1], requests[0]);
54
+ timing.command("show", host.ctx);
55
+ assert.match(host.notices.at(-1)!, /No completed STATUS timing/);
56
+ });
57
+
58
+ test("opt-in captures monotonic stage durations once and excludes all identifying or raw data", async () => {
59
+ const host = context();
60
+ let now = 0;
61
+ const timing = new StatusTimingDiagnostics(() => now);
62
+ timing.reset(host.ctx);
63
+ timing.command("enable", host.ctx);
64
+ const value = { details: { secret: "raw-output" } };
65
+ const actual = await wrapped(timing, async () => {
66
+ now += 7;
67
+ await observeNativeStatus(30_000, async () => {
68
+ measureStatusSync("resolution", () => { now += 3; });
69
+ await measureStatusAsync("adapter", async () => { now += 19; });
70
+ measureStatusSync("decode", () => { now += 5; });
71
+ });
72
+ measureStatusSidebar(() => { now += 2; });
73
+ now += 11;
74
+ return value;
75
+ })(host.ctx);
76
+ assert.equal(actual, value);
77
+ assert.deepEqual(summary(timing, host), {
78
+ schema: "gentle-pi.status-timing/v1", host_total_ms: 47, dispatch_ms: 7,
79
+ sidebar_ms: 2, completion_ms: 13, truncated: false,
80
+ statuses: [{ timeout_ms: 30_000, total_ms: 27, resolution_ms: 3, adapter_ms: 19, decode_ms: 5, outcome: { kind: "success" } }],
81
+ });
82
+ await wrapped(timing, () => observeNativeStatus(99, async () => { now += 99; }))(host.ctx);
83
+ assert.equal(summary(timing, host).statuses[0].timeout_ms, 30_000, "arming is consumed without retrying anything");
84
+ const report = host.notices.at(-1)!;
85
+ for (const forbidden of [host.ctx.cwd, "private-session", "private-call", "raw-output"]) assert.equal(report.includes(forbidden), false);
86
+ });
87
+
88
+ test("enabled fake adapter preserves success and typed failure bytes without extra execution", async () => {
89
+ for (const fail of [false, true]) {
90
+ const host = context();
91
+ const timing = new StatusTimingDiagnostics();
92
+ const failure = { schema: "gentle-ai.review-integration.failure/v2", contract: "gentle-ai.review-integration/v2", required_inputs: [], operation: "review.status", code: "operation_timeout", phase: "pre_native", message: "private raw error", cause: "/private/path", mutation_outcome: "not_started", authority_applicability: "not_evaluated", retry_safe: false, replayability: "manual_action_required", next_action: "stop" };
93
+ const requests: ExecFileRequest[] = [];
94
+ const adapter: ExecFileAdapter = async (request) => {
95
+ requests.push(request);
96
+ return { stdout: fail ? JSON.stringify(failure) : statusBytes, stderr: "", exitCode: fail ? 1 : 0, signal: null, timedOut: false, outputLimitExceeded: false };
97
+ };
98
+ const cli = new NativeReviewCliV216(adapter, process.execPath, 321);
99
+ const invoke = () => cli.targetStatus({ cwd: process.cwd(), agent: "pi" });
100
+ let expected: unknown;
101
+ try { expected = await invoke(); } catch (error) { expected = error; }
102
+ timing.command("enable", host.ctx);
103
+ let actual: unknown;
104
+ try { actual = await wrapped(timing, invoke)(host.ctx); } catch (error) { actual = error; }
105
+ assert.deepEqual(actual, expected);
106
+ assert.deepEqual(requests[1], requests[0]);
107
+ assert.equal(requests.length, 2);
108
+ const report = summary(timing, host);
109
+ assert.equal(report.statuses[0].timeout_ms, 321);
110
+ assert.equal(report.statuses[0].outcome.kind, fail ? "native_failure" : "success");
111
+ if (fail) {
112
+ assert.deepEqual(report.statuses[0].outcome, { kind: "native_failure", code: "operation_timeout", phase: "pre_native", mutation_outcome: "not_started", retry_safe: false, next_action: "stop" });
113
+ assert.equal(host.notices.at(-1)!.includes("private"), false);
114
+ }
115
+ }
116
+ });
117
+
118
+ test("public plain STATUS facade consumes the arm and measures resolution without changing execution", async () => {
119
+ const host = context();
120
+ let now = 0;
121
+ const timing = new StatusTimingDiagnostics(() => now);
122
+ const requests: ExecFileRequest[] = [];
123
+ const body = {
124
+ schema: "gentle-ai.review-authority-status/v1", operation: "review/status", repository: process.cwd(),
125
+ complete: true, authoritative: true, status: "clean", entries: [], locks: [], diagnostics: [],
126
+ };
127
+ const cli = new NativeReviewCliV216(async (request) => {
128
+ requests.push(request);
129
+ now += 17;
130
+ return { stdout: JSON.stringify(body), stderr: "", exitCode: 0, signal: null, timedOut: false, outputLimitExceeded: false };
131
+ }, () => { now += 5; return process.execPath; }, 321, 654);
132
+ const signal = new AbortController().signal;
133
+ const baseline = await cli.reviewStatus({ cwd: process.cwd(), signal });
134
+ timing.command("enable", host.ctx);
135
+ const actual = await wrapped(timing, () => cli.reviewStatus({ cwd: process.cwd(), signal }))(host.ctx);
136
+ assert.deepEqual(actual, baseline);
137
+ assert.deepEqual(requests[1], requests[0]);
138
+ assert.equal(requests.length, 2);
139
+ assert.deepEqual(requests[1].arguments, ["review", "status", "--cwd", process.cwd()]);
140
+ assert.equal(requests[1].signal, signal);
141
+ assert.equal(requests[1].timeoutMs, 321);
142
+ assert.equal(requests[1].maxBufferBytes, 654);
143
+ const report = summary(timing, host);
144
+ assert.equal(report.statuses.length, 1);
145
+ assert.equal(report.statuses[0].resolution_ms, 5);
146
+ assert.equal(report.statuses[0].adapter_ms, 17);
147
+ assert.equal(report.statuses[0].timeout_ms, 321);
148
+ assert.deepEqual(report.statuses[0].outcome, { kind: "success" });
149
+ await wrapped(timing, () => cli.reviewStatus({ cwd: process.cwd(), signal }))(host.ctx);
150
+ assert.deepEqual(summary(timing, host), report, "the facade consumes the arm only once");
151
+ });
152
+
153
+ test("observer clock failures cannot hide a result or original thrown error", async () => {
154
+ const host = context();
155
+ const timing = new StatusTimingDiagnostics(() => { throw new Error("clock failed"); });
156
+ timing.command("enable", host.ctx);
157
+ const sentinel = new Error("original error");
158
+ await assert.rejects(wrapped(timing, () => observeNativeStatus(30, () => measureStatusAsync("adapter", async () => { throw sentinel; })))(host.ctx), (error) => error === sentinel);
159
+ timing.command("enable", host.ctx);
160
+ const value = { ok: true };
161
+ assert.equal(await wrapped(timing, () => observeNativeStatus(30, async () => measureStatusSync("decode", () => value)))(host.ctx), value);
162
+ });
163
+
164
+ test("disable, shutdown, and replacement clear retained and late observations", async () => {
165
+ for (const action of ["disable", "shutdown", "replacement"] as const) {
166
+ const host = context();
167
+ const timing = new StatusTimingDiagnostics();
168
+ timing.command("enable", host.ctx);
169
+ let release!: () => void;
170
+ const gate = new Promise<void>((resolve) => { release = resolve; });
171
+ const run = wrapped(timing, () => observeNativeStatus(30, () => measureStatusAsync("adapter", () => gate)))(host.ctx);
172
+ if (action === "disable") timing.command("disable", host.ctx);
173
+ if (action === "shutdown") timing.reset();
174
+ if (action === "replacement") host.replace();
175
+ release();
176
+ await run;
177
+ timing.command("show", host.ctx);
178
+ assert.match(host.notices.at(-1)!, /No completed STATUS timing/);
179
+ }
180
+ });
181
+
182
+ test("overlapping calls claim only one armed capture and never mix stages", async () => {
183
+ const host = context();
184
+ let now = 0;
185
+ const timing = new StatusTimingDiagnostics(() => now);
186
+ timing.command("enable", host.ctx);
187
+ let release!: () => void;
188
+ const gate = new Promise<void>((resolve) => { release = resolve; });
189
+ const first = wrapped(timing, async () => {
190
+ await gate;
191
+ return observeNativeStatus(11, async () => measureStatusSync("decode", () => { now += 3; }));
192
+ })(host.ctx);
193
+ await wrapped(timing, () => observeNativeStatus(22, () => measureStatusAsync("adapter", async () => { now += 9; })))(host.ctx);
194
+ release();
195
+ await first;
196
+ assert.equal(summary(timing, host).statuses.length, 1);
197
+ assert.equal(summary(timing, host).statuses[0].timeout_ms, 22);
198
+ assert.equal(summary(timing, host).statuses[0].decode_ms, 0);
199
+ });
200
+
201
+ test("summaries are bounded and commands never invoke native or persist settings", async () => {
202
+ const host = context();
203
+ const timing = new StatusTimingDiagnostics();
204
+ timing.command("invalid", host.ctx);
205
+ assert.match(host.notices.at(-1)!, /Use enable, disable, or show/);
206
+ timing.command("enable", host.ctx);
207
+ await wrapped(timing, async () => {
208
+ for (let index = 0; index < 100; index++) await observeNativeStatus(30, async () => {});
209
+ })(host.ctx);
210
+ const report = summary(timing, host);
211
+ assert.equal(report.statuses.length, 8);
212
+ assert.equal(report.truncated, true);
213
+ timing.command("disable", host.ctx);
214
+ timing.command("show", host.ctx);
215
+ assert.match(host.notices.at(-1)!, /No completed STATUS timing/);
216
+ });
217
+
218
+ test("registered command observes controller dispatch and sidebar without changing its result", async () => {
219
+ const host = context();
220
+ const commands = new Map<string, { handler: (args: string, ctx: ExtensionContext) => Promise<void> }>();
221
+ const tools = new Map<string, ToolDefinition>();
222
+ const hooks = new Map<string, Array<(event: unknown, ctx: ExtensionContext) => unknown>>();
223
+ let calls = 0;
224
+ const cli = new NativeReviewCliV216(async () => {
225
+ calls++;
226
+ return { stdout: statusBytes, stderr: "", exitCode: 0, signal: null, timedOut: false, outputLimitExceeded: false };
227
+ }, process.execPath);
228
+ createGentleAiExtension({ nativeReviewCli: cli })({
229
+ on: (name: string, handler: (event: unknown, ctx: ExtensionContext) => unknown) => hooks.set(name, [...(hooks.get(name) ?? []), handler]),
230
+ registerTool: (definition: ToolDefinition) => tools.set(definition.name, definition),
231
+ registerCommand: (name: string, definition: { handler: (args: string, ctx: ExtensionContext) => Promise<void> }) => commands.set(name, definition),
232
+ events: { emit: () => { throw new Error("sidebar observer failed"); } },
233
+ } as unknown as ExtensionAPI);
234
+ const command = commands.get("gentle:status-timing")!;
235
+ assert.ok(command);
236
+ await command.handler("enable", host.ctx);
237
+ assert.equal(calls, 0, "arming never runs native STATUS");
238
+ const tool = tools.get("gentle_review")!;
239
+ const actual = await tool.execute("private-call", { operation: "inspect" }, undefined, undefined, host.ctx);
240
+ assert.equal(calls, 1);
241
+ assert.equal(typeof actual.details, "object");
242
+ await command.handler("show", host.ctx);
243
+ const report = JSON.parse(host.notices.at(-1)!);
244
+ assert.equal(report.statuses.length, 1);
245
+ assert.equal(report.statuses[0].outcome.kind, "success");
246
+ assert.equal(typeof report.sidebar_ms, "number");
247
+ assert.equal(calls, 1, "consulting never runs another STATUS");
248
+ for (const handler of hooks.get("session_tree") ?? []) await handler({}, host.ctx);
249
+ await command.handler("show", host.ctx);
250
+ assert.match(host.notices.at(-1)!, /No completed STATUS timing/);
251
+ assert.equal(calls, 1);
252
+ });
253
+
254
+ test("real sidebar publication is timed and throwing listeners cannot hide results", async () => {
255
+ const host = context();
256
+ let now = 0;
257
+ const timing = new StatusTimingDiagnostics(() => now);
258
+ const publisher = createReviewSidebarPublisher({ events: { emit: () => { now += 4; throw new Error("listener failed"); } } } as unknown as ExtensionAPI);
259
+ publisher.reset(host.ctx);
260
+ timing.command("enable", host.ctx);
261
+ const value = { content: [], details: {} };
262
+ const definition = { name: "gentle_review", execute: () => observeNativeStatus(30, async () => value) } as unknown as ToolDefinition;
263
+ const actual = await timing.tool(publisher.tool(definition)).execute("private-call", { operation: "inspect" }, undefined, undefined, host.ctx);
264
+ assert.equal(actual, value);
265
+ assert.equal(summary(timing, host).sidebar_ms, 8);
266
+ });
267
+
268
+ test("isolated Node child measures adapter settlement separately from host completion", async () => {
269
+ const host = context();
270
+ const timing = new StatusTimingDiagnostics();
271
+ timing.command("enable", host.ctx);
272
+ const adapter = createNodeExecFileAdapter();
273
+ const actual = await wrapped(timing, async () => {
274
+ const result = await observeNativeStatus(2000, () => measureStatusAsync("adapter", () => adapter({ file: process.execPath, arguments: ["-e", "setTimeout(() => process.stdout.write('isolated-child'), 30)"], cwd: process.cwd(), timeoutMs: 2000, maxBufferBytes: 1024 })));
275
+ await new Promise((resolve) => setTimeout(resolve, 50));
276
+ return result;
277
+ })(host.ctx);
278
+ assert.equal((actual as unknown as { stdout: string }).stdout, "isolated-child");
279
+ const report = summary(timing, host);
280
+ assert.ok(report.statuses[0].adapter_ms >= 20);
281
+ assert.ok(report.completion_ms >= 35);
282
+ assert.ok(report.host_total_ms >= report.statuses[0].adapter_ms + 35);
283
+ });
@@ -0,0 +1,19 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+
4
+ // gentle-shell#1494 split the lazy delegation detail into per-mechanism
5
+ // modules. Contract tests that assert delegation-scoped clauses read the union
6
+ // so a moved clause stays pinned instead of silently disappearing.
7
+ export const DELEGATION_MODULES = [
8
+ "orchestrator-delegation.md",
9
+ "orchestrator-tracking.md",
10
+ "orchestrator-verification.md",
11
+ "orchestrator-writer.md",
12
+ "orchestrator-prompts.md",
13
+ ] as const;
14
+
15
+ const DEFAULT_ASSETS_DIR = join(import.meta.dirname, "..", "..", "assets");
16
+
17
+ export function readDelegationDetail(assetsDir: string = DEFAULT_ASSETS_DIR): string {
18
+ return DELEGATION_MODULES.map((file) => readFileSync(join(assetsDir, file), "utf8")).join("\n\n");
19
+ }
@@ -0,0 +1,241 @@
1
+ import assert from "node:assert/strict";
2
+ import { readFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import test from "node:test";
5
+ import { __testing } from "../extensions/gentle-ai.ts";
6
+ import { readDelegationDetail } from "./support/orchestrator-modules.ts";
7
+
8
+ // gentle-shell#1494: task size is decided by understanding, risk, and whether
9
+ // the work can be resumed from the diff, never by counting files, commands,
10
+ // fixes, or a requested todo list. Each mechanism (ask, explore, verify,
11
+ // track, writer) turns on only by its own trigger.
12
+
13
+ const REPO_ROOT = join(import.meta.dirname, "..");
14
+ const read = (relative: string): string => readFileSync(join(REPO_ROOT, relative), "utf8");
15
+
16
+ const core = read("assets/orchestrator.md");
17
+ const delegation = readDelegationDetail();
18
+ const skill = read("skills/gentle-ai/SKILL.md");
19
+ const extension = read("extensions/gentle-ai.ts");
20
+
21
+ function sectionOf(text: string, heading: string): string {
22
+ const start = text.indexOf(heading);
23
+ assert.ok(start !== -1, `missing section: ${heading}`);
24
+ const next = text.indexOf("\n## ", start + heading.length);
25
+ return next === -1 ? text.slice(start) : text.slice(start, next);
26
+ }
27
+
28
+ test("AC1: the always-on core defines task size once with the three criteria", () => {
29
+ const size = sectionOf(core, "## Task Size");
30
+ for (const clause of [
31
+ "**Understood**",
32
+ "no product or design decision is open",
33
+ "one bounded read batch (at most 3 calls, ~10k tokens",
34
+ "**Contained risk**",
35
+ "**Resumable from the diff**",
36
+ "the original request and `git diff` alone",
37
+ "A task is **large** only when the resume test fails",
38
+ ]) {
39
+ assert.ok(size.includes(clause), `task size section is missing: ${clause}`);
40
+ }
41
+ for (const [path, text] of Object.entries({ delegation, skill })) {
42
+ assert.ok(!text.includes("**Resumable from the diff**"), `${path} restates the task-size criteria instead of referencing them`);
43
+ assert.ok(text.includes("Task Size"), `${path} must reference the always-on Task Size section`);
44
+ }
45
+ });
46
+
47
+ test("AC2: counts never classify, and the step-count classifier is gone", () => {
48
+ const size = sectionOf(core, "## Task Size");
49
+ assert.ok(
50
+ size.includes("The number of files, commands or tests, fixes, or a requested `todo` list never decides size"),
51
+ "task size must say counts never classify",
52
+ );
53
+ for (const [path, text] of Object.entries({ core, delegation, skill, extension, readme: read("docs/readme-reference.md") })) {
54
+ assert.ok(!text.includes("two or more meaningful implementation steps"), `${path} keeps the step-count classifier`);
55
+ }
56
+ const prompt = __testing.buildGentlePrompt("gentleman");
57
+ const classify = prompt.slice(prompt.indexOf("4. **Classify.**"), prompt.indexOf("5. **Track before the first write.**"));
58
+ assert.ok(classify.includes("Task Size"), "ODD step 4 must classify by the Task Size section");
59
+ });
60
+
61
+ test("AC3: small work runs its checks inline and keeps test-first", () => {
62
+ const size = sectionOf(core, "## Task Size");
63
+ for (const clause of [
64
+ "run the focused test and the suite inline, once each",
65
+ "observe RED inline before the fix",
66
+ "No explore, worker, or verifier",
67
+ "no feature document, mirror, or commits unless the user asks",
68
+ "needs no lazy asset",
69
+ ]) {
70
+ assert.ok(size.includes(clause), `small path is missing: ${clause}`);
71
+ }
72
+ for (const [path, text] of Object.entries({ core, delegation, skill })) {
73
+ assert.ok(!text.includes("only a read-only check within the evidence budget stays inline"), `${path} keeps the read-only-only inline check`);
74
+ assert.ok(!text.includes("Only a truly local read-only check within the evidence budget stays inline"), `${path} keeps the read-only-only inline check`);
75
+ assert.ok(!text.includes("running focused tests/builds"), `${path} still delegates focused test runs`);
76
+ assert.ok(!text.includes("command-running verification → `gentle-ai-verify`"), `${path} still routes every command-running check to a verifier`);
77
+ }
78
+ });
79
+
80
+ test("AC4: each mechanism turns on only by its own trigger and size is re-evaluated after", () => {
81
+ const triggers = sectionOf(core, "## Mechanisms");
82
+ for (const clause of [
83
+ "each mechanism turns on only by its own trigger",
84
+ "re-evaluate task size",
85
+ "1. **Ask**",
86
+ "2. **Evidence-budget rule**",
87
+ "3. **Verification rule**",
88
+ "4. **Track**",
89
+ "5. **Writer rule**",
90
+ "6. **Incident rule**",
91
+ "7. **Context backstop**",
92
+ "open product or design decision",
93
+ "high risk",
94
+ "never by file count",
95
+ // gentle-shell#1731: the writer fires on reasons, not on size alone.
96
+ "never by file count or a large task alone",
97
+ ]) {
98
+ assert.ok(triggers.includes(clause), `mechanism list is missing: ${clause}`);
99
+ }
100
+ for (const [path, text] of Object.entries({ core, delegation, skill })) {
101
+ assert.ok(!/2\+ non-trivial files|2 or more non-trivial files|Multi-file write rule/.test(text), `${path} keeps the file-count writer trigger`);
102
+ }
103
+ });
104
+
105
+ test("AC5: the high-risk list lives once in the core, native tier wins, unclear is bounded", () => {
106
+ const size = sectionOf(core, "## Task Size");
107
+ for (const clause of [
108
+ "**High risk**",
109
+ "hard to detect, hard to undo, or reaches beyond the change",
110
+ "(1) data or irreversible effects",
111
+ // gentle-shell#1731 T10: items 1 and 3 cover changing or breaking existing things, not adding.
112
+ "rewriting or deleting stored data, format changes, writing data without validation; not saving new records",
113
+ "(2) security",
114
+ "(3) changing or removing contracts others already consume",
115
+ "; not adding a flag, command or optional field",
116
+ "(4) concurrency",
117
+ "(5) delivery or environment",
118
+ "(6) no test would catch a regression",
119
+ "only when a bounded look cannot tell whether (1)-(5) apply",
120
+ "When RDD is on and native assess returns a tier, that tier wins",
121
+ ]) {
122
+ assert.ok(size.includes(clause), `high-risk definition is missing: ${clause}`);
123
+ }
124
+ for (const broad of ["(1) data or irreversible effects (migrations, persisted data or formats)", "(3) contracts others consume"]) {
125
+ assert.ok(!size.includes(broad), `high-risk list keeps the broad form: ${broad}`);
126
+ }
127
+ for (const [path, text] of Object.entries({ delegation, skill })) {
128
+ assert.ok(!text.includes("(1) data or irreversible effects"), `${path} restates the high-risk list`);
129
+ }
130
+ });
131
+
132
+ // AC7 (S7): the small path reads no lazy rule file, and each lazy module stays
133
+ // small enough that one mechanism costs one bounded read instead of 49 KB.
134
+ const MODULE_BUDGETS: Record<string, number> = {
135
+ // gentle-shell#1731 merge with main: main's session subject/state guidance plus the reason-based Writer rule (20,000 -> 20,500 B).
136
+ // gentle-shell#1269: capability-compatible routing rule checked before delegation (20,500 -> 20,800 B).
137
+ "orchestrator-delegation.md": 20_800,
138
+ // gentle-shell#1731 T23: Close gate accepts one Needs your decision result as a stop (12,500 -> 12,600 B).
139
+ "orchestrator-tracking.md": 12_600,
140
+ // gentle-shell#1731 T3: parallel review protocol; lazy (delegation or high risk only), core and normative rule untouched.
141
+ // gentle-shell#1731 T11/T12: verify-per-unit timing and the every-S# verify handoff (6,000 -> 6,400 B).
142
+ // gentle-shell#1731 T15: one end-of-feature verify for same-model inline deliveries (6,400 -> 6,500 B).
143
+ // gentle-shell#1731 T23: bounded self-review and partial/blocked writer -> Needs your decision (7,000 -> 7,300 B).
144
+ "orchestrator-verification.md": 7_300,
145
+ "orchestrator-writer.md": 4_500,
146
+ "orchestrator-prompts.md": 13_000,
147
+ };
148
+
149
+ test("AC7: each delegation module stays under its byte budget and is loaded by its mechanism only", () => {
150
+ for (const [file, budget] of Object.entries(MODULE_BUDGETS)) {
151
+ const bytes = Buffer.byteLength(read(`assets/${file}`), "utf8");
152
+ assert.ok(bytes <= budget, `${file} is ${bytes} B, over its ${budget} B budget`);
153
+ assert.ok(core.includes(`\`${file}\``), `the always-on core must point at ${file}`);
154
+ assert.ok(read("scripts/verify-package-files.mjs").includes(`assets/${file}`), `${file} must be a required package file`);
155
+ }
156
+ const smallPath = sectionOf(core, "## Task Size").split("\n").find((line) => line.startsWith("Small path:")) ?? "";
157
+ assert.ok(smallPath.includes("It needs no lazy asset"), "the small path must say it needs no lazy asset");
158
+ assert.doesNotMatch(smallPath, /`orchestrator-[a-z]+\.md`/, "the small path must not point at any lazy module");
159
+ const mechanisms = sectionOf(core, "## Mechanisms");
160
+ for (const [label, file] of [
161
+ ["3. **Verification rule**", "orchestrator-verification.md"],
162
+ ["4. **Track**", "orchestrator-tracking.md"],
163
+ ["5. **Writer rule**", "orchestrator-writer.md"],
164
+ ] as const) {
165
+ const line = mechanisms.split("\n").find((entry) => entry.startsWith(label)) ?? "";
166
+ assert.ok(line.includes(`\`${file}\``), `${label} must load ${file}`);
167
+ }
168
+ });
169
+
170
+ // gentle-shell#1269/#1598: the capability rule is the substantive part of the fix,
171
+ // so losing it must fail a test instead of quietly passing the AC7 budget gate.
172
+ // Focused on the two claims that carry the fix, not on the exact wording around them.
173
+ test("delegation reports a capability gap instead of substituting inline work for a fired trigger", () => {
174
+ assert.match(delegation, /declared `tools` in `subagent_list_agents` before launching/);
175
+ assert.match(delegation, /never give a read-only explorer shell, HTTP or MCP work/);
176
+ assert.match(delegation, /report the capability gap; a fired trigger is never replaced by inline work/);
177
+ assert.equal(delegation.split("report the capability gap").length - 1, 1);
178
+ });
179
+
180
+ test("work usage stays complete in the human guide while routing precedence stays canonical", () => {
181
+ const asset = read("assets/orchestrator-delegation.md");
182
+ const guide = read("docs/gentle-agents-activity.md");
183
+ assert.ok(asset.includes("tool schemas and `docs/gentle-agents-activity.md`"));
184
+ for (const clause of [
185
+ '`{"area":"Auth","topic":"Login","tags":["Review"],"refs":[{"kind":"issue","repository":"github.com/Owner/Repo","id":"12"}]}`',
186
+ '`filter: {"related_to":{"session_id":"<stable owner ID>"}}`',
187
+ "Never publish private history as metadata.",
188
+ "not inherited. Keep the returned actual task ID: it is **not** a child session ID.",
189
+ "never automatically page. Source unavailable means no related rows, not refusal.",
190
+ "confer no ownership, consent or permission. Querying needs no helper/model call.",
191
+ ]) assert.ok(guide.includes(clause), `human guide must retain: ${clause}`);
192
+ assert.equal(asset.split("For a large task's bounded writes, prefer").length - 1, 1);
193
+ assert.equal(asset.split("Route generic exploration first to the installed package-owned `gentle-ai-explore`").length - 1, 1);
194
+ assert.ok(asset.includes("Judgment Day phase roles are never generic fallbacks."));
195
+ assert.ok(asset.includes("same read-only mapping task and report the fallback."));
196
+ });
197
+
198
+ // S7 axis 2 (more input than output): tracking writes are mechanical; the
199
+ // model edits instead of rewriting and never re-emits the whole document just
200
+ // to mirror it.
201
+ test("T4: tracking updates edit in place and mirror without re-emitting the document", () => {
202
+ const memory = read("assets/orchestrator-memory.md");
203
+ for (const clause of [
204
+ "Update the feature document with targeted edits; never rewrite the whole file to change a task line or append a log entry.",
205
+ "When a code-execution tool such as `codemode` is available, refresh the Engram mirror inside one script that reads the file and saves its content, so the document is never re-emitted as output tokens.",
206
+ "projection for large ODD,",
207
+ ]) {
208
+ assert.ok(memory.includes(clause), `orchestrator-memory.md is missing: ${clause}`);
209
+ }
210
+ assert.ok(!memory.includes("For substantial authorized organic implementation"), "memory detail must size by Task Size, not 'substantial'");
211
+ });
212
+
213
+ // RDD follow-ups (lineages review-6d2889a73f541791, review-* slice 2): every
214
+ // module must be self-contained for the mechanism that loads it, and each
215
+ // moved clause must live in the module its pointer names.
216
+ test("T6: each delegation module carries its own clauses and names the modules it depends on", () => {
217
+ const placement: Record<string, readonly string[]> = {
218
+ "orchestrator-tracking.md": ["#### Authorization and progress", "Delivery follows work units", "Raise a candidate you know is high risk"],
219
+ "orchestrator-verification.md": [
220
+ "| Native risk tier | Verification (any RDD state) |",
221
+ "## Agent escalation (gentle-shell#1494)",
222
+ "## Parallel review protocol (gentle-shell#1731)",
223
+ ],
224
+ "orchestrator-writer.md": ["#### Allowed edit surfaces (MANDATORY)", "#### Judgment Day fix dispatch"],
225
+ "orchestrator-prompts.md": ["### Lossless Blocking Prompts (MANDATORY)", "#### Gentle AI Provider Defect Handoff (MANDATORY)"],
226
+ };
227
+ for (const [file, clauses] of Object.entries(placement)) {
228
+ const text = read(`assets/${file}`);
229
+ for (const clause of clauses) assert.ok(text.includes(clause), `${file} must carry: ${clause}`);
230
+ }
231
+ const writer = read("assets/orchestrator-writer.md");
232
+ assert.ok(!/Lossless Blocking Prompts rules above/.test(writer), "the writer module must not point at rules 'above' that live in another module");
233
+ assert.ok(writer.includes("`orchestrator-prompts.md`"), "the writer module must name the module that holds the blocking-prompt rules");
234
+ assert.ok(!delegation.includes("precedence in rule 2"), "cross-references name rules, not ambiguous numbers");
235
+ });
236
+
237
+ test("T6: the readme no longer sends every suite to a verifier or tracks 'substantial' work", () => {
238
+ const readme = read("docs/readme-reference.md");
239
+ assert.ok(!readme.includes("send full suites and builds to a verifier"), "readme still delegates every suite");
240
+ assert.ok(!/track substantial work|for substantial work|substantial authorized implementation/i.test(readme), "readme still sizes by 'substantial'");
241
+ });
@@ -1,11 +1,12 @@
1
1
  import assert from "node:assert/strict";
2
2
  import test, { after, before } from "node:test";
3
- import { mkdirSync, mkdtempSync, rmSync } from "node:fs";
3
+ import { mkdirSync, mkdtempSync, realpathSync, rmSync } from "node:fs";
4
4
  import { tmpdir } from "node:os";
5
5
  import { join } from "node:path";
6
6
  import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
7
7
  import { __testing, createGentleAiExtension } from "../extensions/gentle-ai.ts";
8
8
  import type { ExecFileAdapter, ExecFileResult } from "../lib/native-review-cli.ts";
9
+ import { setBundledInstallForTesting } from "../lib/gentle-shell-bundled.ts";
9
10
  import {
10
11
  decodeTelemetryTriggerDecision,
11
12
  shouldTriggerTelemetry,
@@ -410,3 +411,28 @@ test("/gentle:telemetry rejects an unknown sub-action without calling the binary
410
411
  assert.equal(notifications.length, 1);
411
412
  assert.equal(notifications[0].severity, "warning");
412
413
  });
414
+
415
+ // S9: this package running from a bundled install (the testing overlay stands
416
+ // in for its location; the detection itself is tested with real installs in
417
+ // gentle-ai-binary-isolation.test.ts) and the launcher's GENTLE_SHELL_PREFIX.
418
+ function bundledIsolation(t: { after: (fn: () => void) => void }): { prefix: string; env: NodeJS.ProcessEnv } {
419
+ const prefix = realpathSync(mkdtempSync(join(tmpdir(), "gentle-ai-isolation-site-")));
420
+ setBundledInstallForTesting({ root: prefix });
421
+ t.after(() => {
422
+ setBundledInstallForTesting(undefined);
423
+ rmSync(prefix, { recursive: true, force: true });
424
+ });
425
+ return { prefix, env: { PATH: "/usr/bin", GENTLE_SHELL_PREFIX: prefix } };
426
+ }
427
+
428
+ test("spawnTelemetryTrigger: gentle-ai gets Gentle AI's isolation variables only in a bundled install", (t) => {
429
+ const { prefix, env } = bundledIsolation(t);
430
+ const bundled = fakeSpawn();
431
+ spawnTelemetryTrigger({ executable: "/opt/gentle-ai/gentle-ai", cwd: "/work", env, spawn: bundled.spawn });
432
+ assert.equal(bundled.calls[0].options.env?.GENTLE_AI_HOME, join(prefix, "gentle-ai"));
433
+ assert.equal(bundled.calls[0].options.env?.GENTLE_AI_NO_SELF_UPDATE, "1");
434
+ const ordinary = fakeSpawn();
435
+ const plain = { PATH: "/usr/bin" };
436
+ spawnTelemetryTrigger({ executable: "/opt/gentle-ai/gentle-ai", cwd: "/work", env: plain, spawn: ordinary.spawn });
437
+ assert.equal(ordinary.calls[0].options.env, plain);
438
+ });