gentle-pi 3.7.0 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (590) hide show
  1. package/README.md +155 -10
  2. package/assets/agents/gentle-ai-explore.md +13 -6
  3. package/assets/agents/gentle-ai-verify.md +27 -6
  4. package/assets/agents/gentle-ai-worker.md +13 -15
  5. package/assets/install-wizard/index.html +47 -0
  6. package/assets/install-wizard/wizard.css +1279 -0
  7. package/assets/install-wizard/wizard.js +951 -0
  8. package/assets/orchestrator-delegation.md +75 -169
  9. package/assets/orchestrator-memory.md +12 -26
  10. package/assets/orchestrator-prompts.md +48 -0
  11. package/assets/orchestrator-skills.md +1 -1
  12. package/assets/orchestrator-tracking.md +53 -0
  13. package/assets/orchestrator-verification.md +28 -0
  14. package/assets/orchestrator-writer.md +38 -0
  15. package/assets/orchestrator.md +30 -38
  16. package/assets/sounds/LICENSE.md +52 -0
  17. package/assets/sounds/attention.wav +0 -0
  18. package/assets/sounds/error.wav +0 -0
  19. package/assets/sounds/success.wav +0 -0
  20. package/assets/support/strict-tdd-verify.md +4 -266
  21. package/assets/support/strict-tdd.md +8 -360
  22. package/bin/engram-guard +9 -0
  23. package/bin/gentle-shell-install.mjs +302 -0
  24. package/bin/gentle-shell.mjs +956 -50
  25. package/contracts/review-provider-contract-mirror/provider-contract.lock.json +7 -7
  26. package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/manifest.json +2 -2
  27. package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/orchestration/pi.md +16 -4
  28. package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/generated/provider-capabilities.baseline.json +2 -2
  29. package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/generated/provider-roles.baseline.json +1 -1
  30. package/docs/bundled-install.md +397 -0
  31. package/docs/delegated-verification.md +27 -4
  32. package/docs/gentle-agents-activity.md +537 -0
  33. package/docs/gentle-shell-uninstall.md +136 -0
  34. package/docs/gentle-shell.md +165 -51
  35. package/docs/install-wizard.md +2040 -0
  36. package/docs/native-authority-architecture.md +3 -3
  37. package/docs/permission-modes.md +82 -0
  38. package/docs/prompt-history.md +280 -0
  39. package/docs/readme-reference.md +364 -231
  40. package/docs/render-cache-performance.md +28 -0
  41. package/docs/sound-notifications-proposal.md +191 -0
  42. package/docs/sound-notifications.md +114 -0
  43. package/docs/telemetry.md +1 -1
  44. package/docs/yolo-mode.md +86 -0
  45. package/extensions/child-context.ts +35 -0
  46. package/extensions/child-safety.ts +25 -0
  47. package/extensions/codegraph-tools.ts +18 -5
  48. package/extensions/gentle-agents.ts +1093 -326
  49. package/extensions/gentle-ai.ts +1636 -890
  50. package/extensions/gentle-herdr-activity.ts +81 -0
  51. package/extensions/gentle-notifications.ts +104 -0
  52. package/extensions/gentle-shell.ts +1619 -136
  53. package/extensions/gentle-stats.ts +101 -0
  54. package/extensions/gentle-todo.ts +126 -20
  55. package/extensions/history/atomic-write.ts +38 -0
  56. package/extensions/history/hide-prompts.ts +183 -0
  57. package/extensions/history/index.ts +1424 -0
  58. package/extensions/history/load-shared-history.ts +39 -0
  59. package/extensions/history/selector-helpers.ts +538 -0
  60. package/extensions/history/session-scan.ts +233 -0
  61. package/extensions/history/store.ts +1119 -0
  62. package/extensions/nan-provider.ts +6 -0
  63. package/extensions/pi-pretty.ts +39 -3
  64. package/extensions/quiet-tools.ts +254 -92
  65. package/extensions/resume-hint.ts +60 -0
  66. package/extensions/skill-registry.ts +191 -58
  67. package/extensions/startup-banner.ts +71 -33
  68. package/lib/agent-assets.ts +604 -0
  69. package/lib/agent-home.ts +10 -1
  70. package/lib/agent-model-resolution.ts +80 -0
  71. package/lib/agent-profile-pin.ts +12 -0
  72. package/lib/agent-profiles.ts +13 -3
  73. package/lib/agents-completion-delivery.ts +11 -3
  74. package/lib/agents-message-delivery.ts +181 -0
  75. package/lib/agents-messaging.ts +1 -1
  76. package/lib/agents-protocol.ts +99 -0
  77. package/lib/agents-runner.ts +180 -107
  78. package/lib/agents-view.ts +26 -3
  79. package/lib/agents-widget.ts +16 -9
  80. package/lib/append-system-prompt.ts +21 -0
  81. package/lib/auto-permission-classifier.ts +280 -0
  82. package/lib/auto-permission-runtime.ts +268 -0
  83. package/lib/background-jobs-tools.ts +202 -0
  84. package/lib/background-jobs.ts +253 -0
  85. package/lib/background-monitor.ts +154 -0
  86. package/lib/bounded-writer-admission.ts +220 -0
  87. package/lib/builtin-codemode-optout.ts +166 -0
  88. package/lib/card-content-policy.ts +85 -0
  89. package/lib/card-style-policy.ts +60 -0
  90. package/lib/child-context-files.ts +166 -0
  91. package/lib/child-package-injection.ts +62 -0
  92. package/lib/codemode-renderer.ts +195 -0
  93. package/lib/command-palette-catalog.ts +5 -10
  94. package/lib/command-palette.ts +25 -14
  95. package/lib/context-threshold.ts +26 -0
  96. package/lib/destructive-command-guard.ts +144 -0
  97. package/lib/gentle-ai-binary.ts +109 -4
  98. package/lib/gentle-ai-elapsed-store.ts +87 -0
  99. package/lib/gentle-ai-renderer.ts +196 -39
  100. package/lib/gentle-shell-bundled.ts +517 -0
  101. package/lib/gentle-shell-launcher.ts +89 -27
  102. package/lib/gentle-shell-resume-hint.ts +176 -0
  103. package/lib/gentle-shell-startup-flow.ts +54 -0
  104. package/lib/gentle-shell-startup-update.ts +92 -0
  105. package/lib/gentle-shell-uninstall.ts +840 -0
  106. package/lib/gentle-shell-update-panel.ts +231 -0
  107. package/lib/herdr-activity.ts +183 -0
  108. package/lib/history-capture-policy.ts +95 -0
  109. package/lib/inprocess-reviewer.ts +3 -0
  110. package/lib/jobs-sidebar-state.ts +20 -0
  111. package/lib/jobs-view.ts +161 -0
  112. package/lib/model-price-ratio.ts +83 -0
  113. package/lib/model-routing-authority.ts +5 -1
  114. package/lib/nan-provider.ts +249 -0
  115. package/lib/native-review-cli.ts +242 -180
  116. package/lib/notification-audio-native.ts +162 -0
  117. package/lib/notification-audio-windows.ts +399 -0
  118. package/lib/notification-audio.ts +332 -0
  119. package/lib/notification-customize.ts +241 -0
  120. package/lib/notification-events.ts +113 -0
  121. package/lib/notification-policy.ts +148 -0
  122. package/lib/notification-pulse-client.ts +367 -0
  123. package/lib/notification-pulse-protocol.ts +305 -0
  124. package/lib/notification-pulse-stream.ts +213 -0
  125. package/lib/notification-pulse-worker.ts +66 -0
  126. package/lib/notification-scheduler.ts +173 -0
  127. package/lib/notification-service.ts +202 -0
  128. package/lib/notification-ui.ts +87 -0
  129. package/lib/odd-phase-inference.ts +231 -0
  130. package/lib/odd-phase.ts +141 -0
  131. package/lib/orchestrator-alias.ts +20 -0
  132. package/lib/orchestrator-catalog.ts +79 -0
  133. package/lib/orchestrator-consultation.ts +61 -0
  134. package/lib/orchestrator-discovery.ts +49 -0
  135. package/lib/orchestrator-helper-consent.ts +122 -0
  136. package/lib/orchestrator-helper.ts +167 -0
  137. package/lib/orchestrator-message-card.ts +105 -0
  138. package/lib/orchestrator-presence.ts +115 -14
  139. package/lib/orchestrator-scope.ts +80 -0
  140. package/lib/orchestrator-state.ts +107 -0
  141. package/lib/orchestrator-tool-card.ts +280 -0
  142. package/lib/orchestrator-work-search.ts +218 -0
  143. package/lib/orchestrator-work.ts +79 -0
  144. package/lib/overlay-repaint.ts +26 -0
  145. package/lib/permission-mode-policy.ts +59 -0
  146. package/lib/pi-tui-keys.ts +53 -0
  147. package/lib/review-candidate-view-owner.ts +73 -21
  148. package/lib/review-candidate-view.ts +115 -27
  149. package/lib/review-compact-contract.ts +2 -2
  150. package/lib/review-host-relay.ts +7 -2
  151. package/lib/review-integration-v2.ts +100 -22
  152. package/lib/review-publication-gate.ts +1 -1
  153. package/lib/review-reminder-receipt.ts +48 -8
  154. package/lib/review-risk-assessment.ts +227 -56
  155. package/lib/review-sidebar-state.ts +224 -0
  156. package/lib/review-transaction.ts +72 -3
  157. package/lib/runtime-metrics-children.ts +1 -1
  158. package/lib/runtime-metrics-native.ts +3 -2
  159. package/lib/selection-engine.ts +527 -0
  160. package/lib/session-change-capture.ts +5 -4
  161. package/lib/session-changes.ts +27 -22
  162. package/lib/session-messaging-grants.ts +135 -0
  163. package/lib/session-profile-binding.ts +83 -0
  164. package/lib/session-profile-freeze.ts +266 -0
  165. package/lib/session-worktree-registry.ts +87 -9
  166. package/lib/shell-bar.ts +215 -30
  167. package/lib/shell-card.ts +287 -18
  168. package/lib/shell-changes-view.ts +42 -14
  169. package/lib/shell-changes.ts +86 -71
  170. package/lib/shell-prompt.ts +102 -6
  171. package/lib/shell-sidebar-layout.ts +83 -15
  172. package/lib/shell-sidebar.ts +15 -1
  173. package/lib/shell-todo.ts +84 -31
  174. package/lib/shell-usage-view.ts +9 -4
  175. package/lib/shell-usage.ts +70 -13
  176. package/lib/stats-collector.ts +381 -0
  177. package/lib/stats-view.ts +431 -0
  178. package/lib/status-timing-diagnostics.ts +201 -0
  179. package/lib/telemetry-trigger.ts +3 -1
  180. package/lib/theme-customization.ts +52 -0
  181. package/lib/vim-editor-adapter.ts +383 -0
  182. package/lib/vim-normal-engine.ts +154 -0
  183. package/lib/vim-operator-engine.ts +416 -0
  184. package/lib/vim-policy.ts +49 -0
  185. package/lib/vim-visual-engine.ts +107 -0
  186. package/lib/visual-customization-policy.ts +119 -0
  187. package/lib/visual-customize-view.ts +481 -0
  188. package/lib/visual-profiles.ts +228 -0
  189. package/lib/writer-surfaces.ts +162 -0
  190. package/lib/yolo-session-policy.ts +317 -0
  191. package/package.json +20 -8
  192. package/runtime/child-package-injection.mjs +63 -0
  193. package/runtime/gentle-ai-binary.mjs +109 -4
  194. package/runtime/gentle-shell-bundled.mjs +518 -0
  195. package/runtime/gentle-shell-launcher.mjs +87 -25
  196. package/runtime/gentle-shell-resume-hint.mjs +177 -0
  197. package/runtime/gentle-shell-startup-flow.mjs +55 -0
  198. package/runtime/gentle-shell-startup-update.mjs +93 -0
  199. package/runtime/gentle-shell-uninstall.mjs +841 -0
  200. package/runtime/gentle-shell-update-panel.mjs +232 -0
  201. package/runtime/native-review-cli.mjs +239 -177
  202. package/runtime/review-integration-v2.mjs +100 -22
  203. package/runtime/review-risk-assessment.mjs +225 -54
  204. package/runtime/status-timing-diagnostics.mjs +202 -0
  205. package/runtime/telemetry-trigger.mjs +3 -1
  206. package/scripts/bootstrap.cmd +255 -0
  207. package/scripts/bootstrap.sh +168 -0
  208. package/scripts/build-distribution.mjs +157 -0
  209. package/scripts/build-installer-bundles.mjs +136 -0
  210. package/scripts/build-runtime-modules.mjs +8 -0
  211. package/scripts/bundled-install.mjs +1308 -0
  212. package/scripts/bundled-wizard.mjs +399 -0
  213. package/scripts/check-types.mjs +4 -6
  214. package/scripts/gentle-ai-installer.mjs +83 -35
  215. package/scripts/install-gentle-ai.mjs +48 -1
  216. package/scripts/install-tui-mode-setting.mjs +10 -1
  217. package/scripts/install-wizard-preview.mjs +154 -0
  218. package/scripts/installer-downloads.mjs +648 -0
  219. package/scripts/installer-preflight.mjs +387 -0
  220. package/scripts/installer-probes.mjs +564 -0
  221. package/scripts/installer-runner.mjs +1102 -0
  222. package/scripts/installer-server.mjs +881 -0
  223. package/scripts/installer-windows-artifacts.json +15 -0
  224. package/scripts/installer-windows.mjs +752 -0
  225. package/scripts/main-channel.mjs +452 -0
  226. package/scripts/mirror-odd-routing.mjs +2 -2
  227. package/scripts/npm-pack-result.mjs +31 -0
  228. package/scripts/render-cache-performance-worker.mjs +205 -0
  229. package/scripts/render-cache-performance.mjs +115 -0
  230. package/scripts/run-test-suite.mjs +90 -0
  231. package/scripts/test-installer-acceptance.sh +161 -0
  232. package/scripts/test-packed-runner.mjs +54 -19
  233. package/scripts/types-baseline.json +2 -92
  234. package/scripts/verify-package-files.mjs +91 -38
  235. package/skills/branch-pr/SKILL.md +24 -52
  236. package/skills/chained-pr/SKILL.md +31 -15
  237. package/skills/chained-pr/references/chaining-details.md +31 -20
  238. package/skills/gentle-ai/SKILL.md +16 -21
  239. package/skills/issue-creation/SKILL.md +8 -2
  240. package/skills/issue-creation/references/delegated-workflow-actions.md +19 -0
  241. package/skills/work-unit-commits/SKILL.md +4 -3
  242. package/tests/agent-home.test.ts +56 -3
  243. package/tests/agent-model-resolution.test.ts +113 -0
  244. package/tests/agent-profiles.test.ts +30 -0
  245. package/tests/agent-risk-escalation.test.ts +123 -0
  246. package/tests/agents-completion-delivery.test.ts +17 -0
  247. package/tests/agents-fake-child.ts +2 -2
  248. package/tests/agents-message-delivery.test.ts +106 -0
  249. package/tests/agents-protocol.test.ts +97 -0
  250. package/tests/agents-queries.test.ts +9 -9
  251. package/tests/agents-runner-process.test.ts +31 -0
  252. package/tests/agents-runner.test.ts +467 -93
  253. package/tests/agents-view-thread-identity.test.ts +169 -0
  254. package/tests/agents-view.test.ts +8 -2
  255. package/tests/agents-widget.test.ts +154 -15
  256. package/tests/append-system-prompt-route.test.ts +160 -0
  257. package/tests/append-system-prompt.test.ts +46 -0
  258. package/tests/artifact-language.test.ts +19 -213
  259. package/tests/ask-user-question.test.ts +44 -1
  260. package/tests/asset-installation-runtime.test.ts +5 -16
  261. package/tests/auto-mode-invariants.test.ts +258 -0
  262. package/tests/auto-mode-sdk.test.ts +57 -0
  263. package/tests/auto-mode.test.ts +49 -0
  264. package/tests/auto-permission-classifier.test.ts +283 -0
  265. package/tests/auto-permission-runtime.test.ts +196 -0
  266. package/tests/autonomous-guard.test.ts +69 -1
  267. package/tests/background-cache-warming.test.ts +2 -1
  268. package/tests/background-jobs.test.ts +227 -0
  269. package/tests/background-monitor.test.ts +195 -0
  270. package/tests/background-subagents-default-mode.test.ts +28 -5
  271. package/tests/bounded-stop-contract.test.ts +85 -0
  272. package/tests/bounded-writer-admission.test.ts +146 -0
  273. package/tests/branch-pr-skill.test.ts +43 -0
  274. package/tests/bridge-wake-lifecycle.test.ts +129 -0
  275. package/tests/bridge-wake-rendering.test.ts +56 -0
  276. package/tests/build-distribution.test.ts +269 -0
  277. package/tests/builtin-codemode-optout.test.ts +242 -0
  278. package/tests/bundled-install.test.ts +1319 -0
  279. package/tests/bundled-wizard.test.ts +505 -0
  280. package/tests/card-content-policy.test.ts +68 -0
  281. package/tests/card-style-policy.test.ts +87 -0
  282. package/tests/chained-pr-skill.test.ts +124 -0
  283. package/tests/child-codegraph-bootstrap.test.ts +74 -0
  284. package/tests/child-context-files.test.ts +255 -0
  285. package/tests/child-package-entrypoints.test.ts +65 -0
  286. package/tests/child-package-injection.test.ts +74 -0
  287. package/tests/child-safety.test.ts +94 -0
  288. package/tests/child-web-package.test.ts +224 -0
  289. package/tests/codegraph-tools.test.ts +35 -0
  290. package/tests/codemode-rendering.test.ts +596 -0
  291. package/tests/command-palette.test.ts +43 -7
  292. package/tests/context-threshold.test.ts +31 -0
  293. package/tests/delegate-for-reason-contract.test.ts +327 -0
  294. package/tests/delegated-key-learnings-contract.test.ts +3 -78
  295. package/tests/destructive-command-guard.test.ts +84 -0
  296. package/tests/devbinary/native-review-parity.devtest.ts +171 -3
  297. package/tests/devbinary/non-git-subagent-bootstrap.devtest.ts +193 -0
  298. package/tests/devbinary/pi-host-relay.devtest.ts +173 -3
  299. package/tests/engram-safety.test.ts +288 -0
  300. package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-28T10-00-00-000Z_aaa.jsonl +7 -0
  301. package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-29T23-00-00-000Z_bbb.jsonl +3 -0
  302. package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-30T08-00-00-000Z_ddd.jsonl +2 -0
  303. package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-30T09-00-00-000Z_eee.jsonl +3 -0
  304. package/tests/fixtures/stats/sessions/--work-alpha--/run-1/session.jsonl +2 -0
  305. package/tests/fixtures/stats/sessions/--work-beta--/2026-09-01T12-00-00-000Z_ccc.jsonl +2 -0
  306. package/tests/fixtures/stats/user-pi/sessions/--work-alpha--/2026-09-28T10-00-00-000Z_aaa.jsonl +2 -0
  307. package/tests/fixtures/stats/user-pi/sessions/--work-alpha--/2026-09-29T23-00-00-000Z_bbb.jsonl +4 -0
  308. package/tests/fixtures/stats/user-pi/sessions/--work-gamma--/2026-09-20T09-00-00-000Z_fff.jsonl +3 -0
  309. package/tests/generic-agent-tools.test.ts +69 -0
  310. package/tests/gentle-agents.test.ts +2804 -346
  311. package/tests/gentle-ai-binary-isolation.test.ts +135 -0
  312. package/tests/gentle-ai-binary.test.ts +21 -4
  313. package/tests/gentle-ai-child-guards.test.ts +202 -0
  314. package/tests/gentle-ai-dev-binary-surfacing.test.ts +125 -15
  315. package/tests/gentle-ai-elapsed-store.test.ts +68 -0
  316. package/tests/gentle-ai-installer.test.ts +566 -62
  317. package/tests/gentle-ai-prompt-model-routing.test.ts +123 -0
  318. package/tests/gentle-ai-renderer.test.ts +493 -14
  319. package/tests/gentle-ai.test.ts +1427 -236
  320. package/tests/gentle-card-text.ts +2 -1
  321. package/tests/gentle-herdr-activity.test.ts +100 -0
  322. package/tests/gentle-notifications.test.ts +427 -0
  323. package/tests/gentle-shell-bin.test.ts +896 -115
  324. package/tests/gentle-shell-bundled.test.ts +1002 -0
  325. package/tests/gentle-shell-launcher.test.ts +333 -65
  326. package/tests/gentle-shell-resume-hint.test.ts +270 -0
  327. package/tests/gentle-shell-startup-flow.test.ts +75 -0
  328. package/tests/gentle-shell-startup-update.test.ts +85 -0
  329. package/tests/gentle-shell-uninstall.test.ts +978 -0
  330. package/tests/gentle-shell-update-panel.test.ts +158 -0
  331. package/tests/gentle-shell.test.ts +4216 -119
  332. package/tests/gentle-stats.test.ts +152 -0
  333. package/tests/gentle-theme.test.ts +4 -1
  334. package/tests/gentle-todo.test.ts +333 -10
  335. package/tests/herdr-activity.test.ts +243 -0
  336. package/tests/history-agent-home.test.ts +81 -0
  337. package/tests/history-atomic-write.test.ts +57 -0
  338. package/tests/history-capture-policy.test.ts +102 -0
  339. package/tests/history-child-guard.test.ts +70 -0
  340. package/tests/history-command-registration.test.ts +164 -0
  341. package/tests/history-dedupe-entries.test.ts +123 -0
  342. package/tests/history-delete-backfill.test.ts +190 -0
  343. package/tests/history-delete-confirm.test.ts +460 -0
  344. package/tests/history-dispatch.test.ts +180 -0
  345. package/tests/history-drain-hidden.test.ts +110 -0
  346. package/tests/history-drain-order.test.ts +98 -0
  347. package/tests/history-expanded-globals.test.ts +62 -0
  348. package/tests/history-gc.test.ts +832 -0
  349. package/tests/history-header-layout.test.ts +265 -0
  350. package/tests/history-hide-prompts.test.ts +275 -0
  351. package/tests/history-lazy-windowing.test.ts +508 -0
  352. package/tests/history-legacy-migrate-v2.test.ts +297 -0
  353. package/tests/history-load-shared-history.test.ts +53 -0
  354. package/tests/history-max-results-cap.test.ts +76 -0
  355. package/tests/history-multi-reader.test.ts +203 -0
  356. package/tests/history-off-path.test.ts +170 -0
  357. package/tests/history-openflow-integration.test.ts +173 -0
  358. package/tests/history-overlay-margin.test.ts +326 -0
  359. package/tests/history-preview-layout.test.ts +93 -0
  360. package/tests/history-registry.test.ts +143 -0
  361. package/tests/history-scope-delete.test.ts +411 -0
  362. package/tests/history-search-caret-keys.test.ts +142 -0
  363. package/tests/history-seed-bootstrap.test.ts +170 -0
  364. package/tests/history-seed-regen.test.ts +129 -0
  365. package/tests/history-selector-windowing.test.ts +94 -0
  366. package/tests/history-session-scan-directory.test.ts +87 -0
  367. package/tests/history-session-scan-extract.test.ts +583 -0
  368. package/tests/history-session-writer.test.ts +351 -0
  369. package/tests/history-store-paths.test.ts +79 -0
  370. package/tests/history-tombstone-exact.test.ts +139 -0
  371. package/tests/history-wheel-mouse.test.ts +242 -0
  372. package/tests/inprocess-reviewer.test.ts +49 -19
  373. package/tests/install-gentle-ai-failure.test.ts +69 -0
  374. package/tests/install-tui-mode-setting.test.ts +15 -0
  375. package/tests/install-wizard.test.ts +1223 -0
  376. package/tests/installer-bundles.test.ts +122 -0
  377. package/tests/installer-posix-bootstrap.test.ts +1201 -0
  378. package/tests/installer-preflight.test.ts +750 -0
  379. package/tests/installer-probes.test.ts +1106 -0
  380. package/tests/installer-runner.test.ts +2158 -0
  381. package/tests/installer-server.test.ts +1567 -0
  382. package/tests/installer-windows-bootstrap.test.ts +2410 -0
  383. package/tests/issue-creation-skill.test.ts +61 -0
  384. package/tests/jobs-view.test.ts +160 -0
  385. package/tests/main-channel-upgrade.test.ts +389 -0
  386. package/tests/main-channel.test.ts +344 -0
  387. package/tests/maintainer/provider-relay.maintest.ts +13 -12
  388. package/tests/model-price-ratio.test.ts +116 -0
  389. package/tests/model-profiles-preservation.test.ts +153 -0
  390. package/tests/model-routing-authority.test.ts +16 -0
  391. package/tests/model-routing-package.test.ts +10 -0
  392. package/tests/nan-provider-runtime.test.ts +156 -0
  393. package/tests/nan-provider.test.ts +481 -0
  394. package/tests/native-binary-gate.test.ts +2 -0
  395. package/tests/native-review-capability-contract.test.ts +13 -1
  396. package/tests/native-review-cli.test.ts +239 -118
  397. package/tests/native-review-consent.test.ts +2 -2
  398. package/tests/native-review-parity-runtime.test.ts +101 -4
  399. package/tests/native-review-parity.test.ts +12 -48
  400. package/tests/native-risk-evidence-parity.test.ts +21 -0
  401. package/tests/no-self-pause-contract.test.ts +87 -0
  402. package/tests/notification-audio-native.test.ts +281 -0
  403. package/tests/notification-audio-windows.test.ts +581 -0
  404. package/tests/notification-audio.test.ts +548 -0
  405. package/tests/notification-customize.test.ts +655 -0
  406. package/tests/notification-events.test.ts +115 -0
  407. package/tests/notification-policy.test.ts +201 -0
  408. package/tests/notification-pulse-client.test.ts +356 -0
  409. package/tests/notification-pulse-protocol.test.ts +213 -0
  410. package/tests/notification-pulse-stream.test.ts +320 -0
  411. package/tests/notification-scheduler.test.ts +258 -0
  412. package/tests/notification-ui.test.ts +157 -0
  413. package/tests/npm-pack-result.test.ts +41 -0
  414. package/tests/odd-integration.test.ts +76 -0
  415. package/tests/odd-phase-inference.test.ts +213 -0
  416. package/tests/odd-phase-loader.test.ts +253 -0
  417. package/tests/odd-phase.test.ts +307 -0
  418. package/tests/odd-routing-canonical-ratchet.test.ts +60 -14
  419. package/tests/odd-routing-contract.test.ts +158 -50
  420. package/tests/orchestrator-budget.test.ts +43 -45
  421. package/tests/orchestrator-catalog.test.ts +146 -0
  422. package/tests/orchestrator-consultation-sdk.test.ts +614 -0
  423. package/tests/orchestrator-consultation.test.ts +111 -0
  424. package/tests/orchestrator-discovery.test.ts +241 -0
  425. package/tests/orchestrator-helper-consent.test.ts +171 -0
  426. package/tests/orchestrator-helper.test.ts +297 -0
  427. package/tests/orchestrator-message-card.test.ts +190 -0
  428. package/tests/orchestrator-presence.test.ts +23 -0
  429. package/tests/orchestrator-rdd-ownership.test.ts +8 -7
  430. package/tests/orchestrator-scope.test.ts +107 -0
  431. package/tests/orchestrator-state.test.ts +114 -0
  432. package/tests/orchestrator-status-publication-contract.test.ts +34 -0
  433. package/tests/orchestrator-tool-card.test.ts +398 -0
  434. package/tests/orchestrator-work-search.test.ts +318 -0
  435. package/tests/orchestrator-work.test.ts +147 -0
  436. package/tests/overlay-repaint.test.ts +74 -0
  437. package/tests/package-manifest.test.ts +344 -123
  438. package/tests/packed-runner-owned-path.test.ts +80 -0
  439. package/tests/parallel-reason-contract.test.ts +21 -0
  440. package/tests/parallel-writer-admission.test.ts +210 -0
  441. package/tests/permission-mode-policy.test.ts +194 -0
  442. package/tests/persona-single-channel.test.ts +10 -18
  443. package/tests/pi-pretty.test.ts +56 -0
  444. package/tests/provider-defect-handoff.test.ts +7 -14
  445. package/tests/quiet-bash-runtime.test.ts +76 -0
  446. package/tests/quiet-tool-rendering.test.ts +569 -187
  447. package/tests/rdd-aware-verification-contract.test.ts +103 -40
  448. package/tests/rdd-status-line.test.ts +9 -4
  449. package/tests/render-cache-performance.test.ts +48 -0
  450. package/tests/resume-hint-extension.test.ts +122 -0
  451. package/tests/review-agent-end-preflight.test.ts +237 -21
  452. package/tests/review-candidate-owner-retry.test.ts +22 -1
  453. package/tests/review-candidate-view.test.ts +399 -12
  454. package/tests/review-contract-prompt.test.ts +138 -44
  455. package/tests/review-controller-lock-status.test.ts +0 -1
  456. package/tests/review-controller-native-recovery.test.ts +327 -17
  457. package/tests/review-controller-native-routing.test.ts +866 -6
  458. package/tests/review-controller-workspace-root.test.ts +206 -6
  459. package/tests/review-controller.test.ts +2 -2
  460. package/tests/review-correction-lifecycle.test.ts +2 -1
  461. package/tests/review-gate.test.ts +7 -4
  462. package/tests/review-graph-schema.test.ts +2 -2
  463. package/tests/review-host-relay-routing.test.ts +342 -4
  464. package/tests/review-host-relay.test.ts +49 -2
  465. package/tests/review-integration-v2-forward.test.ts +221 -1
  466. package/tests/review-json-arguments.test.ts +167 -0
  467. package/tests/review-last-event-closure.test.ts +157 -8
  468. package/tests/review-ledger-contract.test.ts +14 -37
  469. package/tests/review-policy-judgment-day.test.ts +1 -1
  470. package/tests/review-policy-ordinary.test.ts +2 -2
  471. package/tests/review-relay-transport-agent.test.ts +66 -1
  472. package/tests/review-reminder-receipt.test.ts +47 -1
  473. package/tests/review-risk-assessment.test.ts +594 -26
  474. package/tests/review-session-standing-permission-controller.test.ts +16 -17
  475. package/tests/review-session-standing-permission-ipc.test.ts +6 -5
  476. package/tests/review-sidebar-state.test.ts +402 -0
  477. package/tests/review-test-fixtures.ts +7 -1
  478. package/tests/review-transaction.test.ts +18 -1
  479. package/tests/run-test-suite.test.ts +202 -0
  480. package/tests/runtime-harness.mjs +186 -800
  481. package/tests/runtime-metrics-children.test.ts +16 -23
  482. package/tests/runtime-metrics-extension.test.ts +1 -1
  483. package/tests/runtime-metrics-native.test.ts +31 -1
  484. package/tests/selection-engine.test.ts +451 -0
  485. package/tests/session-change-capture.test.ts +10 -10
  486. package/tests/session-changes-shell.test.ts +242 -48
  487. package/tests/session-changes.test.ts +17 -24
  488. package/tests/session-messaging-grants.test.ts +255 -0
  489. package/tests/session-profile-binding.test.ts +163 -0
  490. package/tests/session-profile-freeze.test.ts +381 -0
  491. package/tests/session-subject-routing-contract.test.ts +34 -0
  492. package/tests/session-worktree-registry.test.ts +134 -2
  493. package/tests/shell-bar.test.ts +422 -1
  494. package/tests/shell-card.test.ts +353 -1
  495. package/tests/shell-changes-view.test.ts +135 -32
  496. package/tests/shell-changes.test.ts +61 -62
  497. package/tests/shell-prompt.test.ts +94 -2
  498. package/tests/shell-render-cache-runtime.test.ts +69 -0
  499. package/tests/shell-render-cache.test.ts +129 -0
  500. package/tests/shell-sidebar-layout.test.ts +351 -22
  501. package/tests/shell-sidebar-scroll-benchmark.test.ts +255 -0
  502. package/tests/shell-todo.test.ts +207 -3
  503. package/tests/shell-usage-view.test.ts +28 -1
  504. package/tests/shell-usage.test.ts +108 -0
  505. package/tests/short-check-routing-contract.test.ts +49 -0
  506. package/tests/skill-registry-watcher.test.ts +119 -0
  507. package/tests/skill-registry.test.ts +357 -2
  508. package/tests/startup-banner.test.ts +246 -4
  509. package/tests/startup-home-repo.test.ts +170 -0
  510. package/tests/stats-collector.test.ts +195 -0
  511. package/tests/stats-view.test.ts +202 -0
  512. package/tests/status-timing-diagnostics.test.ts +283 -0
  513. package/tests/support/orchestrator-modules.ts +19 -0
  514. package/tests/task-size-routing-contract.test.ts +241 -0
  515. package/tests/telemetry-trigger.test.ts +107 -20
  516. package/tests/test-strength-contract.test.ts +124 -0
  517. package/tests/theme-customization.test.ts +72 -0
  518. package/tests/verify-agent-contract.test.ts +90 -0
  519. package/tests/verify-first-launch-contract.test.ts +139 -0
  520. package/tests/verify-package-files.test.ts +56 -0
  521. package/tests/vim-editor-adapter-host-resolution.test.ts +37 -0
  522. package/tests/vim-editor-adapter.test.ts +804 -0
  523. package/tests/vim-normal-engine.test.ts +101 -0
  524. package/tests/vim-operator-engine.test.ts +215 -0
  525. package/tests/vim-policy.test.ts +19 -0
  526. package/tests/vim-visual-engine.test.ts +52 -0
  527. package/tests/visual-customization-policy.test.ts +139 -0
  528. package/tests/visual-customize-view.test.ts +696 -0
  529. package/tests/visual-profiles.test.ts +87 -0
  530. package/tests/writer-edit-surface-scope.test.ts +59 -2
  531. package/tests/writer-surfaces.test.ts +199 -0
  532. package/tests/yolo-customize.test.ts +271 -0
  533. package/tests/yolo-mode-runtime.test.ts +188 -0
  534. package/tests/yolo-mode.test.ts +289 -0
  535. package/tests/yolo-session-policy.test.ts +59 -0
  536. package/themes/Gentle.json +2 -1
  537. package/themes/Gentleman-Cute.json +2 -1
  538. package/themes/Gentleman-Sexy.json +2 -1
  539. package/assets/agents/sdd-apply.md +0 -159
  540. package/assets/agents/sdd-archive.md +0 -228
  541. package/assets/agents/sdd-design.md +0 -49
  542. package/assets/agents/sdd-explore.md +0 -48
  543. package/assets/agents/sdd-init.md +0 -56
  544. package/assets/agents/sdd-onboard.md +0 -52
  545. package/assets/agents/sdd-proposal.md +0 -64
  546. package/assets/agents/sdd-remediate.md +0 -37
  547. package/assets/agents/sdd-research.md +0 -49
  548. package/assets/agents/sdd-spec.md +0 -192
  549. package/assets/agents/sdd-status.md +0 -54
  550. package/assets/agents/sdd-tasks.md +0 -108
  551. package/assets/agents/sdd-verify.md +0 -124
  552. package/assets/chains/sdd-full.chain.md +0 -83
  553. package/assets/chains/sdd-plan.chain.md +0 -56
  554. package/assets/chains/sdd-verify.chain.md +0 -43
  555. package/assets/sdd-orchestrator-workflow.md +0 -319
  556. package/assets/support/sdd-status-contract.md +0 -77
  557. package/docs/assets/diagrams/sdd-cycle.svg +0 -14
  558. package/extensions/sdd-init.ts +0 -816
  559. package/lib/openspec-deltas.ts +0 -156
  560. package/lib/sdd-preflight.ts +0 -1066
  561. package/lib/sdd-research-capabilities.ts +0 -94
  562. package/lib/sdd-status.ts +0 -26
  563. package/tests/fixtures/legacy/sdd-research-v2.5.0.md +0 -54
  564. package/tests/fixtures/native-review-cli/v2.1.3/bind-sdd.json +0 -25
  565. package/tests/fixtures/v0.10.7/assets/agents/sdd-apply.md +0 -132
  566. package/tests/openspec-deltas.test.ts +0 -209
  567. package/tests/sdd-agent-tools.test.ts +0 -156
  568. package/tests/sdd-archive-replay.test.ts +0 -82
  569. package/tests/sdd-classical-continuation.test.ts +0 -74
  570. package/tests/sdd-execution-routing-contract.test.ts +0 -44
  571. package/tests/sdd-managed-runtime-settlement.test.ts +0 -155
  572. package/tests/sdd-native-managed-uptake.test.ts +0 -243
  573. package/tests/sdd-no-attempts-contract.test.ts +0 -15
  574. package/tests/sdd-odd-integration.test.ts +0 -33
  575. package/tests/sdd-optional-research.test.ts +0 -124
  576. package/tests/sdd-planning-routing-contract.test.ts +0 -45
  577. package/tests/sdd-preflight-rpc-input.test.ts +0 -125
  578. package/tests/sdd-preflight.test.ts +0 -541
  579. package/tests/sdd-research-capabilities.test.ts +0 -114
  580. package/tests/sdd-research-live.test.ts +0 -241
  581. package/tests/sdd-selection-transport.test.ts +0 -653
  582. package/tests/sdd-status.test.ts +0 -9
  583. package/tests/sdd-task-truth.test.ts +0 -43
  584. /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/README.md +0 -0
  585. /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/schemas/lens.schema.json +0 -0
  586. /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/schemas/refuter.schema.json +0 -0
  587. /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/schemas/targeted-validator.schema.json +0 -0
  588. /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/vectors/lens.json +0 -0
  589. /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/vectors/refuter.json +0 -0
  590. /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/vectors/targeted-validator.json +0 -0
@@ -1,10 +1,10 @@
1
1
  // Generated by scripts/build-runtime-modules.mjs. Do not edit.
2
- import { execFile } from "node:child_process";
2
+ import { execFile, spawn } from "node:child_process";
3
3
  import { chmod, mkdtemp, realpath, rm, writeFile } from "node:fs/promises";
4
4
  import { tmpdir } from "node:os";
5
5
  import { dirname, isAbsolute, join, posix, win32 } from "node:path";
6
6
  import { promisify } from "node:util";
7
- import { PackageLocalGentleAiBinaryMissingError, resolveGentleAiBinary } from "./gentle-ai-binary.mjs";
7
+ import { gentleAiIsolationEnv, PackageLocalGentleAiBinaryMissingError, resolveGentleAiBinary } from "./gentle-ai-binary.mjs";
8
8
  import { GENTLE_PI_REVIEW_RELAY_CONTRACT, GENTLE_PI_REVIEW_RELAY_CONTRACT_ENV } from "./review-relay-contract.mjs";
9
9
  import { decodeReviewAssessmentV1, } from "./review-risk-assessment.mjs";
10
10
  import {
@@ -32,6 +32,8 @@ import {
32
32
 
33
33
  } from "./review-integration-v2.mjs";
34
34
 
35
+ import { observeNativeStatus, measureStatusSync, measureStatusAsync } from "./status-timing-diagnostics.mjs";
36
+
35
37
  const execFileAsync = promisify(execFile);
36
38
 
37
39
  // Negotiated review/status responses can carry a complete authority inventory.
@@ -68,8 +70,6 @@ export const NATIVE_REVIEW_OPERATION = {
68
70
  CAPTURE_PROVIDER_ROLE: "review/capture-provider-role",
69
71
  CAPTURE_UNACHIEVABLE: "review/capture-unachievable",
70
72
  ACKNOWLEDGE_APPROVED: "review/acknowledge-approved",
71
- SDD_STATUS: "sdd-status",
72
- SDD_CONTINUE: "sdd-continue",
73
73
  } ;
74
74
 
75
75
 
@@ -124,11 +124,6 @@ export const NATIVE_REVIEW_ERROR_CODE = {
124
124
 
125
125
 
126
126
 
127
-
128
-
129
-
130
-
131
-
132
127
  export const NATIVE_REVIEW_MODE_OPERATION = {
133
128
  STATUS: "status",
134
129
  ENABLE: "enable",
@@ -168,35 +163,6 @@ export const NATIVE_REVIEW_MODE_SCOPE = {
168
163
 
169
164
 
170
165
 
171
-
172
-
173
-
174
-
175
- /**
176
- * The native CLI owns this complete v2 record. The decoder validates the fields
177
- * Pi relies on and returns the original object without adding, omitting, or
178
- * reconciling local SDD state.
179
- */
180
-
181
-
182
-
183
-
184
-
185
-
186
-
187
-
188
-
189
-
190
-
191
-
192
-
193
-
194
-
195
-
196
-
197
-
198
-
199
-
200
166
 
201
167
  // Read-only risk assessment request (gentle-pi#662). `baseRef` requires
202
168
  // explicit `committedOnly` acknowledgement, exactly like Native START's
@@ -294,6 +260,8 @@ export const NATIVE_REVIEW_RECOVER_DISPOSITION = ["scope_changed", "invalidated"
294
260
 
295
261
 
296
262
 
263
+
264
+
297
265
 
298
266
 
299
267
 
@@ -312,6 +280,13 @@ export const NATIVE_REVIEW_LEGACY_ALIAS_REPAIR = {
312
280
  } ;
313
281
 
314
282
 
283
+ export const NATIVE_REVIEW_ABANDON_REASONS = ["operator_disposition", "retired_schema"] ;
284
+
285
+
286
+ export function isNativeReviewAbandonReason(value ) {
287
+ return value === "operator_disposition" || value === "retired_schema";
288
+ }
289
+
315
290
 
316
291
 
317
292
 
@@ -508,6 +483,24 @@ export const NATIVE_REVIEW_UNACHIEVABLE_LENS_DETAIL_LIMIT = 512;
508
483
  // gentle-pi#638 fail-open capability gate: `review capture-unachievable` is younger than every released binary pinned in NATIVE_CLI_CONTRACTS, so the verb is gated invocation-adjacent instead of by a capability row. An older binary renders Go's exact `unknown review command "capture-unachievable"` refusal (internal/cli/review_facade.go) on stderr with no stdout, so the invocation rejects before any decode and the captured diagnostics are the only place that text survives. Every other failure -- a typed binding-mismatch refusal, a timeout, a decode failure -- is a real outcome the caller must surface, never a capability signal. Duck-typed on purpose: the classifier must survive a duplicated module instance exactly like the error it inspects.
509
484
  const NATIVE_REVIEW_UNKNOWN_UNACHIEVABLE_VERB_REFUSAL = /unknown review command "capture-unachievable"/;
510
485
 
486
+ // A provider older than START lens selection refuses the STATUS preflight
487
+ // flags with this exact typed preflight cause, before reading the repository.
488
+ const NATIVE_REVIEW_UNKNOWN_LENS_FLAG_CAUSE = /^flag provided but not defined: -lenses(?:-reason)?$/;
489
+
490
+ /** Reports a not-started STATUS refusal caused only by an unknown --lenses flag. */
491
+ export function isNativeReviewLensSelectionUnsupported(error ) {
492
+ if (!(error instanceof NativeReviewIntegrationError)) return false;
493
+ const failure = error.failureEnvelope;
494
+ return failure.code === "invalid_request" && failure.mutationOutcome === "not_started" &&
495
+ typeof failure.cause === "string" && NATIVE_REVIEW_UNKNOWN_LENS_FLAG_CAUSE.test(failure.cause);
496
+ }
497
+
498
+ function nativeLensSelectionArguments(request ) {
499
+ if (request.lenses === undefined && request.lensesReason === undefined) return [];
500
+ if (request.lenses === undefined || request.lenses.length === 0 || request.lensesReason === undefined) throw new TypeError("Native lens selection requires lenses and lensesReason together");
501
+ return ["--lenses", request.lenses.join(","), "--lenses-reason", request.lensesReason];
502
+ }
503
+
511
504
  export function isNativeReviewUnachievableVerbRefused(error ) {
512
505
  if (typeof error !== "object" || error === null) return false;
513
506
  const stderr = (error ).diagnostics?.stderr;
@@ -534,6 +527,16 @@ export const NATIVE_UNTRACKED_SCOPE = {
534
527
 
535
528
 
536
529
 
530
+ /**
531
+ * The agent's own review lens choice (gentle-ai START --lenses): canonical
532
+ * lens names in 4R order and one reason. Providers that predate it refuse
533
+ * the flag, and START then falls back to the tier default.
534
+ */
535
+
536
+
537
+
538
+
539
+
537
540
 
538
541
 
539
542
 
@@ -748,6 +751,21 @@ function nativeUntrackedSelectionArguments(selection )
748
751
  ];
749
752
  }
750
753
 
754
+ // Fail closed on malformed or duplicate provider selectors before invoking STATUS.
755
+ function providerFlagValue(tokens , flag ) {
756
+ let found ;
757
+ for (let index = 0; index < tokens.length; index++) {
758
+ const token = tokens[index] ;
759
+ if (token !== `--${flag}` && !token.startsWith(`--${flag}=`)) continue;
760
+ if (found !== undefined) throw new TypeError(`Native intended-untracked selection repeats --${flag}`);
761
+ const value = token === `--${flag}` ? tokens[index + 1] : token.slice(`--${flag}=`.length);
762
+ if (value === undefined || value === "" || value.startsWith("--")) throw new TypeError(`Native intended-untracked selection has an invalid --${flag} value`);
763
+ found = value;
764
+ if (token === `--${flag}`) index++;
765
+ }
766
+ return found;
767
+ }
768
+
751
769
  const NATIVE_RISK_LEVEL = ["low", "medium", "high"] ;
752
770
 
753
771
  // gentle-ai's negotiated `start/v2` envelope is a closed schema
@@ -774,6 +792,8 @@ const REVIEW_RISK_SUBJECT_BY_CODE = Object.fre
774
792
  shell_source: "shell scripting",
775
793
  process_boundary: "code that starts other processes",
776
794
  process_scan_limit: "code that starts other processes",
795
+ dangerous_sink: "a dangerous code pattern",
796
+ agent_escalation: "the agent that made this change flagged it as high risk",
777
797
  executable_mode: "an executable permission change",
778
798
  executable_change: "an executable change",
779
799
  configuration_change: "a configuration change",
@@ -1049,6 +1069,20 @@ export const NATIVE_CLI_CONTRACTS = Object.freeze({
1049
1069
  // 547b68e172cc87aa297309d61624e5fc2c24d407a494b53eeb5a2b053904352c
1050
1070
  // at contract 1.2.0. No new negotiated capability is asserted.
1051
1071
  "3.7.0": Object.freeze({ start: true, finalize: true, validate: true, bindSdd: true, status: true, inventory: true, reclaim: true, recover: true, abandon: true, quarantineLegacy: true, reconcileAuthority: true, repairLegacyAlias: true, mode: true, riskEvidence: false, hint: false, delivery: true }),
1072
+ // v4.0.0 repeats 3.7.0: the published provider-contract tar remains SHA-256
1073
+ // 547b68e172cc87aa297309d61624e5fc2c24d407a494b53eeb5a2b053904352c
1074
+ // at contract 1.2.0, and the published binary still advertises
1075
+ // capabilities/v2.6 under review-integration/v2. The Go module path moved
1076
+ // to /v4 without a review-integration/v2 change. No new negotiated
1077
+ // capability is asserted.
1078
+ "4.0.0": Object.freeze({ start: true, finalize: true, validate: true, bindSdd: true, status: true, inventory: true, reclaim: true, recover: true, abandon: true, quarantineLegacy: true, reconcileAuthority: true, repairLegacyAlias: true, mode: true, riskEvidence: false, hint: false, delivery: true }),
1079
+ // v4.1.1 repeats 4.0.0: the published binary still advertises
1080
+ // capabilities/v2.6 under review-integration/v2. The provider contract moved
1081
+ // from 1.2.0 to 1.3.0 (additive agent lens selection, gentle-ai#5348), with
1082
+ // published tarball SHA-256
1083
+ // b04b6b75d1a446c942a816fbf92932d16f8c3d3001f93159dee2a92bbf2ebe44.
1084
+ // No new negotiated capability identity is asserted.
1085
+ "4.1.1": Object.freeze({ start: true, finalize: true, validate: true, bindSdd: true, status: true, inventory: true, reclaim: true, recover: true, abandon: true, quarantineLegacy: true, reconcileAuthority: true, repairLegacyAlias: true, mode: true, riskEvidence: false, hint: false, delivery: true }),
1052
1086
  });
1053
1087
 
1054
1088
 
@@ -1090,21 +1124,98 @@ export class NativeReviewCliError extends Error {
1090
1124
  // makes. It declares the Pi host relay handshake on each spawn: gentle-ai
1091
1125
  // refuses pi admission pre-authority without it (gentle-pi#311 P4), and a
1092
1126
  // single injection point keeps the declaration impossible to forget on any
1093
- // individual operation.
1127
+ // individual operation. In a bundled install it also keeps Gentle AI's state
1128
+ // inside the prefix (gentleAiIsolationEnv, S9).
1094
1129
  export function gentleAiProcessEnvironment(base = process.env) {
1095
- return { ...base, [GENTLE_PI_REVIEW_RELAY_CONTRACT_ENV]: GENTLE_PI_REVIEW_RELAY_CONTRACT };
1130
+ return { ...gentleAiIsolationEnv(base), [GENTLE_PI_REVIEW_RELAY_CONTRACT_ENV]: GENTLE_PI_REVIEW_RELAY_CONTRACT };
1131
+ }
1132
+
1133
+
1134
+
1135
+
1136
+
1137
+ // A bare "taskkill" resolves from the current directory before PATH on
1138
+ // Windows, so a repository shipping its own taskkill.exe would run on any
1139
+ // abort. Always use the System32 binary, from System32 itself.
1140
+ export function windowsTaskkillPath(env = process.env) {
1141
+ const systemRoot = env.SystemRoot || env.windir;
1142
+ return win32.join(systemRoot && win32.isAbsolute(systemRoot) ? systemRoot : "C:\\Windows", "System32", "taskkill.exe");
1143
+ }
1144
+
1145
+ // taskkill walks the tree from a live parent, so it must run before the
1146
+ // parent dies; Node's own abort/timeout kill would orphan the git children.
1147
+ export function createWindowsProcessTreeKiller(env = process.env, spawnProcess = spawn) {
1148
+ const taskkill = windowsTaskkillPath(env);
1149
+ return (pid) => {
1150
+ try {
1151
+ spawnProcess(taskkill, ["/pid", String(pid), "/T", "/F"], { cwd: win32.dirname(taskkill), stdio: "ignore", windowsHide: true }).on("error", () => {});
1152
+ } catch { /* Best effort: the call still settles through its own child. */ }
1153
+ };
1154
+ }
1155
+
1156
+ export const windowsProcessTreeKiller = (pid) => createWindowsProcessTreeKiller()(pid);
1157
+
1158
+ const WINDOWS_TREE_KILL_GRACE_MS = 1_000;
1159
+
1160
+ function abortError(signal ) {
1161
+ return Object.assign(new Error("The operation was aborted", { cause: signal.reason }), { name: "AbortError", code: "ABORT_ERR" });
1162
+ }
1163
+
1164
+ function execFileResultFromError(error , timedOut ) {
1165
+ const detail = error ;
1166
+ if (detail.code === "ENOENT" || detail.code === "EACCES" || detail.name === "AbortError") throw error;
1167
+ const outputLimitExceeded = detail.code === "ENOBUFS" || detail.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER";
1168
+ return { stdout: detail.stdout ?? "", stderr: detail.stderr ?? "", exitCode: typeof detail.code === "number" ? detail.code : 1, signal: detail.signal ?? null, timedOut: !outputLimitExceeded && (timedOut || detail.killed === true), outputLimitExceeded };
1169
+ }
1170
+
1171
+ // Windows: abort and timeout kill the whole process tree instead of only
1172
+ // gentle-ai, with the same observable results as the POSIX path.
1173
+ function windowsExecFile(request , killProcessTree ) {
1174
+ const { signal } = request;
1175
+ if (signal?.aborted) return Promise.reject(abortError(signal));
1176
+ return new Promise((resolve, reject) => {
1177
+ let timedOut = false, aborted = false, pipesCut = false;
1178
+ let timer ;
1179
+ let fallback ;
1180
+ const child = execFile(request.file, [...request.arguments], { cwd: request.cwd, encoding: "utf8", shell: false, windowsHide: true, maxBuffer: request.maxBufferBytes, env: gentleAiProcessEnvironment() }, (error, stdout, stderr) => {
1181
+ if (timer !== undefined) clearTimeout(timer);
1182
+ if (fallback !== undefined) clearTimeout(fallback);
1183
+ signal?.removeEventListener("abort", onAbort);
1184
+ if (aborted && signal) { reject(abortError(signal)); return; }
1185
+ if (error === null) { resolve({ stdout, stderr, exitCode: 0, signal: null, timedOut: pipesCut, outputLimitExceeded: false }); return; }
1186
+ try { resolve(execFileResultFromError(Object.assign(error, { stdout, stderr }), timedOut)); } catch (failure) { reject(failure); }
1187
+ });
1188
+ // If taskkill cannot run (AppLocker, a broken PATH), still end gentle-ai
1189
+ // itself so the deadline holds; its children may then outlive it. A
1190
+ // surviving descendant can still hold the inherited stdout/stderr open,
1191
+ // which would keep execFile from calling back, so cut the pipes too.
1192
+ const killTree = () => {
1193
+ if (child.pid === undefined) { child.kill(); return; }
1194
+ killProcessTree(child.pid);
1195
+ fallback = setTimeout(() => {
1196
+ if (child.exitCode === null && child.signalCode === null) child.kill();
1197
+ pipesCut = true;
1198
+ child.stdout?.destroy();
1199
+ child.stderr?.destroy();
1200
+ }, WINDOWS_TREE_KILL_GRACE_MS);
1201
+ };
1202
+ function onAbort() { if (aborted || timedOut) return; aborted = true; killTree(); }
1203
+ signal?.addEventListener("abort", onAbort, { once: true });
1204
+ if (request.timeoutMs !== undefined && request.timeoutMs > 0) timer = setTimeout(() => { if (aborted) return; timedOut = true; killTree(); }, request.timeoutMs);
1205
+ });
1096
1206
  }
1097
1207
 
1098
- export function createNodeExecFileAdapter() {
1208
+ export function createNodeExecFileAdapter(options = {}) {
1209
+ if ((options.platform ?? process.platform) === "win32") {
1210
+ const killProcessTree = options.killProcessTree ?? windowsProcessTreeKiller;
1211
+ return (request) => windowsExecFile(request, killProcessTree);
1212
+ }
1099
1213
  return async (request) => {
1100
1214
  try {
1101
1215
  const output = await execFileAsync(request.file, [...request.arguments], { cwd: request.cwd, encoding: "utf8", shell: false, windowsHide: true, timeout: request.timeoutMs, maxBuffer: request.maxBufferBytes, signal: request.signal, env: gentleAiProcessEnvironment() });
1102
1216
  return { stdout: output.stdout, stderr: output.stderr, exitCode: 0, signal: null, timedOut: false, outputLimitExceeded: false };
1103
1217
  } catch (error) {
1104
- const detail = error ;
1105
- if (detail.code === "ENOENT" || detail.code === "EACCES" || detail.name === "AbortError") throw error;
1106
- const outputLimitExceeded = detail.code === "ENOBUFS" || detail.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER";
1107
- return { stdout: detail.stdout ?? "", stderr: detail.stderr ?? "", exitCode: typeof detail.code === "number" ? detail.code : 1, signal: detail.signal ?? null, timedOut: !outputLimitExceeded && detail.killed === true, outputLimitExceeded };
1218
+ return execFileResultFromError(error, false);
1108
1219
  }
1109
1220
  };
1110
1221
  }
@@ -1201,8 +1312,10 @@ function nativeProcessDiagnostics(operation , code
1201
1312
  }
1202
1313
 
1203
1314
  function parseJson(stdout , operation , mutating , diagnostics ) {
1204
- if (stdout.length === 0) throw new NativeReviewCliError(NATIVE_REVIEW_ERROR_CODE.EMPTY_OUTPUT, operation, true, mutating, "native command returned empty output", { ...diagnostics, error_code: NATIVE_REVIEW_ERROR_CODE.EMPTY_OUTPUT });
1205
- try { return object(JSON.parse(stdout)); } catch { throw new NativeReviewCliError(NATIVE_REVIEW_ERROR_CODE.MALFORMED_JSON, operation, true, mutating, "native command returned malformed JSON", { ...diagnostics, error_code: NATIVE_REVIEW_ERROR_CODE.MALFORMED_JSON }); }
1315
+ return measureStatusSync("decode", () => {
1316
+ if (stdout.length === 0) throw new NativeReviewCliError(NATIVE_REVIEW_ERROR_CODE.EMPTY_OUTPUT, operation, true, mutating, "native command returned empty output", { ...diagnostics, error_code: NATIVE_REVIEW_ERROR_CODE.EMPTY_OUTPUT });
1317
+ try { return object(JSON.parse(stdout)); } catch { throw new NativeReviewCliError(NATIVE_REVIEW_ERROR_CODE.MALFORMED_JSON, operation, true, mutating, "native command returned malformed JSON", { ...diagnostics, error_code: NATIVE_REVIEW_ERROR_CODE.MALFORMED_JSON }); }
1318
+ });
1206
1319
  }
1207
1320
  function decodeNativeMaintenanceResult(value , expectedOperation ) {
1208
1321
  const body = exactObject(value, ["operation", "record"]);
@@ -1233,7 +1346,7 @@ function assertSupportedNextTransitionOperation(body )
1233
1346
  }
1234
1347
  }
1235
1348
  function decode (operation , mutating , callback , diagnostics = nativeProcessDiagnostics(operation, NATIVE_REVIEW_ERROR_CODE.SCHEMA_INCOMPATIBLE)) {
1236
- try { return callback(); } catch (error) { if (error instanceof NativeReviewCliError) throw error; throw new NativeReviewCliError(NATIVE_REVIEW_ERROR_CODE.SCHEMA_INCOMPATIBLE, operation, true, mutating, "native response is schema incompatible", { ...diagnostics, error_code: NATIVE_REVIEW_ERROR_CODE.SCHEMA_INCOMPATIBLE }); }
1349
+ try { return measureStatusSync("decode", callback); } catch (error) { if (error instanceof NativeReviewCliError) throw error; throw new NativeReviewCliError(NATIVE_REVIEW_ERROR_CODE.SCHEMA_INCOMPATIBLE, operation, true, mutating, "native response is schema incompatible", { ...diagnostics, error_code: NATIVE_REVIEW_ERROR_CODE.SCHEMA_INCOMPATIBLE }); }
1237
1350
  }
1238
1351
  function decodeReviewStartResponse(value ) {
1239
1352
  const body = object(value);
@@ -1246,50 +1359,6 @@ function decodeReleaseEvidence(value ) {
1246
1359
  for (const field of ["release_tree", "configuration_hash", "generated_artifact_hash", "provenance_hash", "publication_boundary_hash", "evidence_freshness_hash"]) requiredString(release[field]);
1247
1360
  if (release.publication_state !== "sealed" || release.evidence_freshness_state !== "current") throw new Error("invalid release evidence");
1248
1361
  }
1249
- function decodeNonDecidingGateContext(value , expectedGate ) {
1250
- const context = exactObject(value, ["gate"]);
1251
- const gate = enumString(context.gate, NATIVE_GATE);
1252
- if (gate !== expectedGate) throw new Error("native non-deciding gate context does not match the requested gate");
1253
- return { lineageId: "", storeRevision: "", raw: context };
1254
- }
1255
- function decodeGateContext(value ) {
1256
- const context = exactObject(
1257
- value,
1258
- ["gate", "lineage_id", "generation", "base_tree", "candidate_tree", "paths_digest", "fix_delta_hash", "policy_hash", "ledger_hash", "evidence_hash", "base_relationship_valid"],
1259
- ["store_revision", "genesis_revision", "chain_identity", "bundle_digest", "external_evidence", "base_advanced_compatible", "release", "pre_pr_boundary", "denial"],
1260
- );
1261
- const gate = stringValue(context.gate);
1262
- if (gate !== "" && !(NATIVE_GATE ).includes(gate)) throw new Error("invalid gate context gate");
1263
- for (const field of ["lineage_id", "base_tree", "candidate_tree", "paths_digest", "fix_delta_hash", "policy_hash", "ledger_hash", "evidence_hash"]) stringValue(context[field]);
1264
- for (const field of ["store_revision", "genesis_revision", "chain_identity", "bundle_digest"]) if (context[field] !== undefined) stringValue(context[field]);
1265
- nonNegativeInteger(context.generation);
1266
- booleanValue(context.base_relationship_valid);
1267
- if (context.external_evidence !== undefined) enumString(context.external_evidence, ["invalidating", "escalating"]);
1268
- let sanitizedContext = context;
1269
- if (context.denial !== undefined) {
1270
- const denial = exactObject(context.denial, ["stage", "code"]);
1271
- const stage = sanitizeNativeDiagnosticText(requiredString(denial.stage), NATIVE_REVIEW_DENIAL_TEXT_LIMIT);
1272
- const code = sanitizeNativeDiagnosticText(requiredString(denial.code), NATIVE_REVIEW_DENIAL_TEXT_LIMIT);
1273
- if (!isCanonicalProcessString(stage) || !isCanonicalProcessString(code)) throw new Error("non-canonical denial evidence");
1274
- sanitizedContext = { ...context, denial: { stage, code } };
1275
- }
1276
- if (context.pre_pr_boundary !== undefined) {
1277
- const boundary = exactObject(context.pre_pr_boundary, ["source", "selector", "commit"], ["remote", "remote_ref", "remote_identity"]);
1278
- enumString(boundary.source, ["explicit", "publication-default"]); requiredString(boundary.selector); stringValue(boundary.commit);
1279
- for (const field of ["remote", "remote_ref", "remote_identity"]) if (boundary[field] !== undefined) requiredString(boundary[field]);
1280
- }
1281
- if (context.base_advanced_compatible !== undefined) {
1282
- const proof = exactObject(context.base_advanced_compatible, ["status", "compatible", "old_base_tree", "new_base_tree", "original_patch_identity", "delivered_patch_identity", "delivered_paths_digest", "base_advance_paths_digest", "paths_disjoint", "merged_result_tree", "ci_attestation_artifact_hash", "ci_attestation_issuer", "ci_status"]);
1283
- for (const field of ["status", "old_base_tree", "new_base_tree", "original_patch_identity", "delivered_patch_identity", "delivered_paths_digest", "base_advance_paths_digest", "merged_result_tree", "ci_attestation_artifact_hash", "ci_attestation_issuer", "ci_status"]) requiredString(proof[field]);
1284
- booleanValue(proof.compatible); booleanValue(proof.paths_disjoint);
1285
- }
1286
- if (context.release !== undefined) decodeReleaseEvidence(context.release);
1287
- return {
1288
- lineageId: stringValue(context.lineage_id),
1289
- storeRevision: context.store_revision === undefined ? "" : stringValue(context.store_revision),
1290
- raw: sanitizedContext,
1291
- };
1292
- }
1293
1362
  function decodeNativeReviewRecovery(value ) {
1294
1363
  const recovery = exactObject(value, ["predecessor_lineage_id", "predecessor_revision", "disposition", "reason", "actor", "recovered_at"], ["maintainer_authorization"]);
1295
1364
  return {
@@ -1380,7 +1449,7 @@ function decodeNativeReviewStatus(value ) {
1380
1449
  const complete = booleanValue(body.complete);
1381
1450
  const authoritative = booleanValue(body.authoritative);
1382
1451
  if (authoritative && !complete) throw new Error("incomplete inventory cannot be authoritative");
1383
- if (!Array.isArray(body.entries) || !Array.isArray(body.locks)) throw new Error("invalid native status inventory");
1452
+ if (!Array.isArray(body.entries) || !Array.isArray(body.locks) || !Array.isArray(body.diagnostics)) throw new Error("invalid native status inventory");
1384
1453
  return {
1385
1454
  repository: requiredString(body.repository),
1386
1455
  complete,
@@ -1516,52 +1585,6 @@ function nativeError(code , operation
1516
1585
 
1517
1586
 
1518
1587
 
1519
- const NATIVE_SDD_DEPENDENCIES = ["proposal", "specs", "design", "tasks", "apply", "verify", "archive"] ;
1520
- const NATIVE_SDD_INSTRUCTION_PHASES = ["apply", "verify", "archive"] ;
1521
- const NATIVE_SDD_NEXT_RECOMMENDATIONS = ["apply", "verify", "remediate", "archive", "archived", "resolve-blockers", "sdd-new", "select-change", "propose", "spec", "design", "tasks"] ;
1522
- const NATIVE_SDD_DEPENDENCY_STATES = ["blocked", "ready", "all_done"] ;
1523
-
1524
- /** Strictly validates the native v2 contract while preserving its whole record. */
1525
- export function decodeNativeSddStatusV2(value , request ) {
1526
- const status = object(value);
1527
- if (status.schemaName !== "gentle-ai.sdd-status" || status.schemaVersion !== 2) throw new Error("wrong native SDD status schema");
1528
- if ((request.changeName !== undefined && status.changeName !== request.changeName) || (status.changeName !== null && !isCanonicalProcessString(status.changeName))) throw new Error("native SDD status change identity mismatch");
1529
- const artifactStore = enumString(status.artifactStore, ["openspec", "engram", "hybrid", "none"]);
1530
- const planningHome = object(status.planningHome);
1531
- if (planningHome.mode !== "repo-local" || !isCanonicalProcessString(planningHome.path)) throw new Error("invalid native SDD planning home");
1532
- const expectedOpenSpecHome = join(request.workspaceRoot, "openspec");
1533
- if (planningHome.path !== expectedOpenSpecHome && !((artifactStore === "engram" || artifactStore === "hybrid") && planningHome.path === "engram:sdd")) throw new Error("native SDD planning home escaped its workspace");
1534
- if (status.changeRoot !== null && !isCanonicalProcessString(status.changeRoot)) throw new Error("invalid native SDD change root");
1535
- const actionContext = object(status.actionContext);
1536
- if (actionContext.mode !== "repo-local" || actionContext.workspaceRoot !== request.workspaceRoot || !isCanonicalProcessString(actionContext.workspaceRoot)) throw new Error("native SDD status workspace root mismatch");
1537
- const allowedEditRoots = stringArray(actionContext.allowedEditRoots);
1538
- if (!allowedEditRoots.includes(request.workspaceRoot) || allowedEditRoots.some((root) => !isAbsolute(root) || root !== join(root))) throw new Error("invalid native SDD allowed edit roots");
1539
- const dependencies = object(status.dependencies);
1540
- for (const phase of NATIVE_SDD_DEPENDENCIES) {
1541
- if (enumString(dependencies[phase], NATIVE_SDD_DEPENDENCY_STATES) !== dependencies[phase]) throw new Error("invalid native SDD dependency");
1542
- }
1543
- if (Object.keys(dependencies).length !== NATIVE_SDD_DEPENDENCIES.length) throw new Error("native SDD dependencies have an unsupported shape");
1544
- if (status.instructions !== undefined) throw new Error("native SDD status uses phaseInstructions, not instructions");
1545
- if (status.phaseInstructions !== undefined) {
1546
- const instructions = object(status.phaseInstructions);
1547
- for (const phase of NATIVE_SDD_INSTRUCTION_PHASES) stringArray(instructions[phase]);
1548
- // Classical SDD no longer emits a remediation phase. Keep the published
1549
- // producer's optional legacy instructions intact without inventing them
1550
- // for a newer producer or accepting unknown phase keys.
1551
- const hasRemediation = Object.hasOwn(instructions, "remediate");
1552
- if (hasRemediation) stringArray(instructions.remediate);
1553
- if (Object.keys(instructions).length !== NATIVE_SDD_INSTRUCTION_PHASES.length + Number(hasRemediation)) throw new Error("native SDD instructions have an unsupported shape");
1554
- if (status.nextRecommended === "remediate" && !hasRemediation) throw new Error("native SDD remediation instructions are missing");
1555
- }
1556
- if (status.nextRecommended === "remediate" || status.remediationState !== undefined) {
1557
- const remediation = object(status.remediationState);
1558
- if (typeof remediation.required !== "boolean" || typeof remediation.complete !== "boolean" || typeof remediation.failedEvidenceRevision !== "string" || (remediation.failedEvidenceRevision !== "" && !/^sha256:[0-9a-f]{64}$/.test(remediation.failedEvidenceRevision)) || (status.nextRecommended === "remediate" && (!remediation.required || remediation.complete || !remediation.failedEvidenceRevision))) throw new Error("Invalid native remediation state");
1559
- }
1560
- stringArray(status.blockedReasons);
1561
- enumString(status.nextRecommended, NATIVE_SDD_NEXT_RECOMMENDATIONS);
1562
- return status ;
1563
- }
1564
-
1565
1588
  class NativeReviewPlainCli {
1566
1589
  adapter ;
1567
1590
  executable ;
@@ -1598,7 +1621,10 @@ class NativeReviewPlainCli {
1598
1621
  // membership in the frozen set.
1599
1622
  async execute(operation , cwd , arguments_ , mutating , signal , toleratedStderr = []) {
1600
1623
  let result ;
1601
- try { result = await this.adapter({ file: this.executablePath(operation, mutating), arguments: arguments_, cwd, timeoutMs: mutating ? undefined : this.timeoutMs, maxBufferBytes: this.maxBufferBytes, signal }); }
1624
+ try {
1625
+ const file = measureStatusSync("resolution", () => this.executablePath(operation, mutating));
1626
+ result = await measureStatusAsync("adapter", () => this.adapter({ file, arguments: arguments_, cwd, timeoutMs: mutating ? undefined : this.timeoutMs, maxBufferBytes: this.maxBufferBytes, signal }));
1627
+ }
1602
1628
  catch (error) {
1603
1629
  if (error instanceof NativeReviewCliError) throw nativeError(error.code, operation, mutating, error.message, undefined, error.launchAttempted);
1604
1630
  if (error instanceof Error && error.name === "AbortError") throw nativeError(NATIVE_REVIEW_ERROR_CODE.CANCELLED, operation, mutating, "native process was cancelled");
@@ -1608,7 +1634,7 @@ class NativeReviewPlainCli {
1608
1634
  if (result.outputLimitExceeded) throw nativeError(NATIVE_REVIEW_ERROR_CODE.OUTPUT_LIMIT, operation, mutating, "native process output exceeded limit", result, true, undefined, this.maxBufferBytes);
1609
1635
  if (result.timedOut) throw nativeError(NATIVE_REVIEW_ERROR_CODE.TIMEOUT, operation, mutating, "native process timed out", result);
1610
1636
  if (result.signal) throw nativeError(NATIVE_REVIEW_ERROR_CODE.SIGNAL, operation, mutating, "native process was signalled", result);
1611
- const maintenancePartialFailure = [NATIVE_REVIEW_OPERATION.ABANDON, NATIVE_REVIEW_OPERATION.QUARANTINE_LEGACY, NATIVE_REVIEW_OPERATION.RECONCILE_AUTHORITY, NATIVE_REVIEW_OPERATION.REPAIR_LEGACY_ALIAS].includes(operation) && result.exitCode !== 0;
1637
+ const maintenancePartialFailure = new Set ([NATIVE_REVIEW_OPERATION.ABANDON, NATIVE_REVIEW_OPERATION.QUARANTINE_LEGACY, NATIVE_REVIEW_OPERATION.RECONCILE_AUTHORITY, NATIVE_REVIEW_OPERATION.REPAIR_LEGACY_ALIAS]).has(operation) && result.exitCode !== 0;
1612
1638
  const toleratedNotice = stderrIsTolerated(result.stderr, toleratedStderr);
1613
1639
  if (result.exitCode !== 0 && !maintenancePartialFailure) throw nativeError(NATIVE_REVIEW_ERROR_CODE.NON_ZERO, operation, mutating, "native process failed", result);
1614
1640
  if (result.stderr.trim().length > 0 && !maintenancePartialFailure && !toleratedNotice) throw nativeError(NATIVE_REVIEW_ERROR_CODE.UNEXPECTED_STDERR, operation, mutating, "native process wrote stderr", result);
@@ -1616,6 +1642,10 @@ class NativeReviewPlainCli {
1616
1642
  }
1617
1643
 
1618
1644
  async reviewStatus(request ) {
1645
+ return observeNativeStatus(this.timeoutMs, () => this.observedReviewStatus(request));
1646
+ }
1647
+
1648
+ async observedReviewStatus(request ) {
1619
1649
  const { body: result } = await this.execute(NATIVE_REVIEW_OPERATION.STATUS, request.cwd, ["review", "status", "--cwd", request.cwd], false, request.signal);
1620
1650
  const status = decode(NATIVE_REVIEW_OPERATION.STATUS, false, () => decodeNativeReviewStatus(result));
1621
1651
  if (!await repositoriesMatch(request.cwd, status.repository)) throw nativeError(NATIVE_REVIEW_ERROR_CODE.IDENTITY_MISMATCH, NATIVE_REVIEW_OPERATION.STATUS, false, "native review status repository mismatch");
@@ -1663,9 +1693,13 @@ class NativeReviewPlainCli {
1663
1693
  if (request.maintainerAuthorization !== undefined && (request.maintainerAuthorization.length === 0 || /[\u0000-\u0009\u000b-\u001f\u007f]/.test(request.maintainerAuthorization))) {
1664
1694
  throw new TypeError("Native RECOVER maintainerAuthorization must be a non-empty LF-only binding");
1665
1695
  }
1696
+ if (request.baseRef !== undefined && !isCanonicalProcessString(request.baseRef)) throw new TypeError("Native RECOVER baseRef must be a non-empty, trimmed, NUL-free string");
1697
+ if (request.baseRef !== undefined && request.committedOnly !== true) throw new TypeError("Native RECOVER baseRef requires explicit committedOnly acknowledgement");
1698
+ if (request.baseRef === undefined && request.committedOnly !== undefined) throw new TypeError("Native RECOVER committedOnly requires an explicit baseRef");
1666
1699
  if (!(NATIVE_REVIEW_RECOVER_DISPOSITION ).includes(request.disposition)) throw new TypeError("Native RECOVER disposition must be scope_changed, invalidated, or escalated");
1667
1700
  const { body } = await this.execute(NATIVE_REVIEW_OPERATION.RECOVER, request.cwd, [
1668
1701
  "review", "recover", "--cwd", request.cwd,
1702
+ ...(request.baseRef === undefined ? [] : ["--base-ref", request.baseRef, "--committed-only"]),
1669
1703
  "--predecessor-lineage", request.predecessorLineage,
1670
1704
  "--expected-predecessor-revision", request.expectedPredecessorRevision,
1671
1705
  "--successor-lineage", request.successorLineage,
@@ -1678,6 +1712,7 @@ class NativeReviewPlainCli {
1678
1712
  }
1679
1713
 
1680
1714
  async abandon(request ) {
1715
+ if (!isNativeReviewAbandonReason(request.reason)) throw new TypeError('Native ABANDON reason must be "operator_disposition" or "retired_schema"');
1681
1716
  for (const [name, value] of [["lineage", request.lineage], ["expectedRevision", request.expectedRevision], ["snapshotIdentity", request.snapshotIdentity], ["actor", request.actor], ["reason", request.reason]] ) {
1682
1717
  if (!isCanonicalProcessString(value)) throw new TypeError(`Native ABANDON ${name} must be a non-empty, trimmed, NUL-free string`);
1683
1718
  }
@@ -1776,6 +1811,10 @@ export function nativeReviewAbandonAuthorization(request
1776
1811
  // so the native v2 gate verifies an exact eight-line binding (schema, lineage,
1777
1812
  // revision, snapshot_identity, reason, captured_lens_results, findings_present,
1778
1813
  // actor) — there is no evidence_records_present line to derive or relay.
1814
+ // capturedLensResults must arrive verbatim from the native authority
1815
+ // inventory projection (issue #1159): the gate recomputes this line from its
1816
+ // own ordered record, whose entries carry the ordinal prefix, so caller- or
1817
+ // facade-authored lens names cannot reproduce it.
1779
1818
  return [
1780
1819
  "gentle-ai.review-abandon-authorization/v2",
1781
1820
  `lineage=${request.lineage}`,
@@ -1916,10 +1955,11 @@ function splitNativeConsentInvocation(invocation ) {
1916
1955
  continue;
1917
1956
  }
1918
1957
  // Provider invocations are not shell commands. Keep every path backslash
1919
- // verbatim, including quoted UNC and drive-root paths. Outside quotes,
1920
- // only a backslash before whitespace joins a space-containing token.
1958
+ // verbatim, including quoted UNC and drive-root paths. Outside quotes, a
1959
+ // backslash escapes only whitespace or a quote, so a space-containing token
1960
+ // and a POSIX-quoted apostrophe (`'it'\''s'`) split exactly.
1921
1961
  const next = source[index + 1];
1922
- if (character === "\\" && quote === undefined && next !== undefined && /\s/.test(next)) {
1962
+ if (character === "\\" && quote === undefined && next !== undefined && (/\s/.test(next) || next === "'" || next === '"')) {
1923
1963
  escaping = true;
1924
1964
  started = true;
1925
1965
  continue;
@@ -2162,7 +2202,7 @@ export class NativeReviewCliV216 {
2162
2202
  ) {
2163
2203
  let result ;
2164
2204
  try {
2165
- result = await this.adapter({ file: path, arguments: arguments_, cwd, timeoutMs: mutating ? undefined : this.timeoutMs, maxBufferBytes: this.maxBufferBytes, signal });
2205
+ result = await measureStatusAsync("adapter", () => this.adapter({ file: path, arguments: arguments_, cwd, timeoutMs: mutating ? undefined : this.timeoutMs, maxBufferBytes: this.maxBufferBytes, signal }));
2166
2206
  } catch (error) {
2167
2207
  if (error instanceof Error && error.name === "AbortError") throw nativeError(NATIVE_REVIEW_ERROR_CODE.CANCELLED, operation, mutating, "native process was cancelled");
2168
2208
  throw nativeError(NATIVE_REVIEW_ERROR_CODE.UNAVAILABLE, operation, mutating, "native process could not start");
@@ -2178,7 +2218,7 @@ export class NativeReviewCliV216 {
2178
2218
  const body = parseJson(result.stdout, operation, mutating, diagnostics);
2179
2219
  if (result.exitCode !== 0) {
2180
2220
  try {
2181
- throw new NativeReviewIntegrationError(decodeReviewFailureV2(body));
2221
+ throw new NativeReviewIntegrationError(measureStatusSync("decode", () => decodeReviewFailureV2(body)));
2182
2222
  } catch (error) {
2183
2223
  if (error instanceof NativeReviewIntegrationError) throw error;
2184
2224
  throw nativeError(NATIVE_REVIEW_ERROR_CODE.NON_ZERO, operation, mutating, "native negotiated operation failed without a valid failure envelope", result);
@@ -2197,31 +2237,7 @@ export class NativeReviewCliV216 {
2197
2237
  signal ,
2198
2238
  toleratedStderr = [],
2199
2239
  ) {
2200
- return this.invoke(operation, cwd, arguments_, mutating, signal, this.executablePath(operation, mutating), toleratedStderr);
2201
- }
2202
-
2203
- async sddStatus(request ) {
2204
- return this.sddProjection(request, false);
2205
- }
2206
-
2207
- async sddContinue(request ) {
2208
- if (!isCanonicalProcessString(request.changeName)) throw new TypeError("Native SDD continuation requires an exact selected change");
2209
- return this.sddProjection(request, true);
2210
- }
2211
-
2212
- async sddProjection(request , mutating ) {
2213
- if ((request.changeName !== undefined && !isCanonicalProcessString(request.changeName)) || !isCanonicalProcessString(request.workspaceRoot) || !isAbsolute(request.workspaceRoot)) {
2214
- throw new TypeError("Native SDD status requires a canonical change and absolute workspace root");
2215
- }
2216
- const operation = mutating ? NATIVE_REVIEW_OPERATION.SDD_CONTINUE : NATIVE_REVIEW_OPERATION.SDD_STATUS;
2217
- const execution = await this.negotiated(
2218
- operation,
2219
- request.workspaceRoot,
2220
- [operation, ...(request.changeName === undefined ? [] : [request.changeName]), "--cwd", request.workspaceRoot, "--json", "--instructions"],
2221
- mutating,
2222
- request.signal,
2223
- );
2224
- return decode(operation, mutating, () => decodeNativeSddStatusV2(execution.body, request));
2240
+ return this.invoke(operation, cwd, arguments_, mutating, signal, measureStatusSync("resolution", () => this.executablePath(operation, mutating)), toleratedStderr);
2225
2241
  }
2226
2242
 
2227
2243
  async start(request ) {
@@ -2234,7 +2250,7 @@ export class NativeReviewCliV216 {
2234
2250
  // drift; Pi never rebuilds that vector from request fields.
2235
2251
  const projection = request.projection ?? "workspace";
2236
2252
  const selection = nativeUntrackedSelection(request);
2237
- const status = await this.targetStatus({
2253
+ const statusRequest = {
2238
2254
  cwd: request.cwd,
2239
2255
  projection,
2240
2256
  ...(request.baseRef === undefined ? {} : { baseRef: request.baseRef, committedOnly: true }),
@@ -2243,7 +2259,19 @@ export class NativeReviewCliV216 {
2243
2259
  ...(request.intendedUntrackedSelection === undefined ? {} : { intendedUntrackedSelection: request.intendedUntrackedSelection }),
2244
2260
  agent: "pi",
2245
2261
  ...(request.signal === undefined ? {} : { signal: request.signal }),
2246
- });
2262
+ };
2263
+ const lensSelection = request.lenses === undefined && request.lensesReason === undefined
2264
+ ? {}
2265
+ : { lenses: request.lenses, lensesReason: request.lensesReason };
2266
+ let status ;
2267
+ try {
2268
+ status = await this.targetStatus({ ...statusRequest, ...lensSelection });
2269
+ } catch (error) {
2270
+ // Phase A: a provider that predates lens selection still reviews,
2271
+ // with the tier default, instead of refusing the candidate.
2272
+ if (Object.keys(lensSelection).length === 0 || !isNativeReviewLensSelectionUnsupported(error)) throw error;
2273
+ status = await this.targetStatus(statusRequest);
2274
+ }
2247
2275
  const transition = status.nextTransition?.kind === "execute" && status.nextTransition.execute?.operation === "review.start"
2248
2276
  ? status.nextTransition.execute
2249
2277
  : undefined;
@@ -2340,23 +2368,53 @@ export class NativeReviewCliV216 {
2340
2368
  }
2341
2369
 
2342
2370
  async targetStatus(request ) {
2371
+ return observeNativeStatus(this.timeoutMs, () => this.observedTargetStatus(request));
2372
+ }
2373
+
2374
+ async observedTargetStatus(request ) {
2343
2375
  if (request.baseRef !== undefined && !isCanonicalProcessString(request.baseRef)) throw new TypeError("Native STATUS baseRef must be a non-empty, trimmed, NUL-free string");
2344
2376
  if (request.baseRef !== undefined && request.committedOnly !== true) throw new TypeError("Native STATUS baseRef requires explicit committedOnly acknowledgement");
2345
2377
  if (request.baseRef === undefined && request.committedOnly !== undefined) throw new TypeError("Native STATUS committedOnly requires an explicit baseRef");
2346
2378
  const selection = nativeUntrackedSelection(request);
2347
2379
  const submitted = request.intendedUntrackedSelection;
2348
2380
  if (submitted !== undefined && submitted.argumentTokens.reduce((count, token) => count + token.split("{{value}}").length - 1, 0) !== 1) throw new TypeError("Native intended-untracked selection requires exactly one provider-issued {{value}} token");
2349
- const statusArguments = submitted === undefined ? [
2381
+ const submittedTokens = submitted === undefined ? undefined : submitted.argumentTokens.map((token) => token.replaceAll("{{value}}", submitted.value));
2382
+ // gentle-ai 3.7.1 submission argument_tokens never carry --base-ref /
2383
+ // --committed-only / --lineage, so a forwarded selector must be appended
2384
+ // after them; only bail if the provider already staked out a conflicting
2385
+ // value for the same flag. providerFlagValue also fails closed on a
2386
+ // dangling `--flag` (last token, no value) or an empty `--flag=`.
2387
+ const submittedBaseRefValue = submittedTokens === undefined ? undefined : providerFlagValue(submittedTokens, "base-ref");
2388
+ if (request.baseRef !== undefined && submittedBaseRefValue !== undefined && submittedBaseRefValue !== request.baseRef) throw new TypeError("Native intended-untracked selection already carries a conflicting base-ref");
2389
+ const submittedLineageValue = submittedTokens === undefined ? undefined : providerFlagValue(submittedTokens, "lineage");
2390
+ if (request.lineageId !== undefined && submittedLineageValue !== undefined && submittedLineageValue !== request.lineageId) throw new TypeError("Native intended-untracked selection already carries a conflicting lineage");
2391
+ let submittedHasCommittedOnly = false;
2392
+ for (const token of submittedTokens ?? []) {
2393
+ if (token !== "--committed-only" && !token.startsWith("--committed-only=")) continue;
2394
+ if (token !== "--committed-only" && token !== "--committed-only=true") throw new TypeError("Native intended-untracked selection has an invalid --committed-only value");
2395
+ if (submittedHasCommittedOnly) throw new TypeError("Native intended-untracked selection repeats --committed-only");
2396
+ submittedHasCommittedOnly = true;
2397
+ }
2398
+ // Append only the half of the base-ref/committed-only pair the provider
2399
+ // did not already carry (a present base-ref already equals
2400
+ // request.baseRef; the throw above ruled out the conflicting case).
2401
+ const forwardedBaseRef = request.baseRef === undefined ? [] : [
2402
+ ...(submittedBaseRefValue === undefined ? ["--base-ref", request.baseRef] : []),
2403
+ ...(submittedHasCommittedOnly ? [] : ["--committed-only"]),
2404
+ ];
2405
+ const forwardedLineage = request.lineageId === undefined || submittedLineageValue !== undefined ? [] : ["--lineage", request.lineageId];
2406
+ const statusArguments = submittedTokens === undefined ? [
2350
2407
  "review", "status", "--contract", REVIEW_INTEGRATION_CONTRACT, "--cwd", request.cwd,
2351
2408
  "--projection", request.projection ?? "workspace",
2352
2409
  ...nativeUntrackedSelectionArguments(selection),
2353
2410
  ...(request.baseRef === undefined ? [] : ["--base-ref", request.baseRef, "--committed-only"]),
2354
2411
  ...(request.lineageId === undefined ? [] : ["--lineage", request.lineageId]),
2355
2412
  ...(request.agent === undefined ? [] : ["--agent", request.agent]),
2413
+ ...nativeLensSelectionArguments(request),
2356
2414
  "--next-transition",
2357
- ] : ["review", "status", "--cwd", request.cwd, ...submitted.argumentTokens.map((token) => token.replaceAll("{{value}}", submitted.value))];
2415
+ ] : ["review", "status", "--cwd", request.cwd, ...submittedTokens, ...forwardedBaseRef, ...forwardedLineage, ...nativeLensSelectionArguments(request)];
2358
2416
  const execution = await this.negotiated(NATIVE_REVIEW_OPERATION.STATUS, request.cwd, statusArguments, false, request.signal);
2359
- assertSupportedNextTransitionOperation(execution.body);
2417
+ measureStatusSync("decode", () => assertSupportedNextTransitionOperation(execution.body));
2360
2418
  return decode(NATIVE_REVIEW_OPERATION.STATUS, false, () => decodeReviewStatusV3(execution.body));
2361
2419
  }
2362
2420
 
@@ -2545,13 +2603,17 @@ export class NativeReviewCliV216 {
2545
2603
  // mutually exclusive with a path.
2546
2604
 
2547
2605
  async reviewStatus(request ) {
2606
+ return observeNativeStatus(this.timeoutMs, () => this.observedReviewStatus(request));
2607
+ }
2608
+
2609
+ async observedReviewStatus(request ) {
2548
2610
  const execution = await this.invoke(
2549
2611
  NATIVE_REVIEW_OPERATION.STATUS,
2550
2612
  request.cwd,
2551
2613
  ["review", "status", "--cwd", request.cwd],
2552
2614
  false,
2553
2615
  request.signal,
2554
- this.executablePath(NATIVE_REVIEW_OPERATION.STATUS, false),
2616
+ measureStatusSync("resolution", () => this.executablePath(NATIVE_REVIEW_OPERATION.STATUS, false)),
2555
2617
  );
2556
2618
  const status = decode(NATIVE_REVIEW_OPERATION.STATUS, false, () => decodeNativeReviewStatus(execution.body));
2557
2619
  if (!await repositoriesMatch(request.cwd, status.repository)) throw nativeError(NATIVE_REVIEW_ERROR_CODE.IDENTITY_MISMATCH, NATIVE_REVIEW_OPERATION.STATUS, false, "native review status repository mismatch");