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
@@ -0,0 +1,39 @@
1
+ // SPDX-FileCopyrightText: 2026 ExoPro. Inspired by @jasonish/pi-prompt-history
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ import fs from "node:fs";
5
+
6
+ interface SharedHistoryEntry {
7
+ text: string;
8
+ }
9
+
10
+ function isSharedHistoryEntry(value: unknown): value is SharedHistoryEntry {
11
+ if (!value || typeof value !== "object") return false;
12
+ return typeof (value as { text?: unknown }).text === "string";
13
+ }
14
+
15
+ function toSharedHistoryValue(value: unknown): string | null {
16
+ if (typeof value === "string") return value.length > 0 ? value : null;
17
+ if (isSharedHistoryEntry(value))
18
+ return value.text.length > 0 ? value.text : null;
19
+ return null;
20
+ }
21
+
22
+ function isNonEmptyString(value: string | null): value is string {
23
+ return typeof value === "string" && value.length > 0;
24
+ }
25
+
26
+ export function loadSharedHistory(historyFile: string): string[] {
27
+ if (!fs.existsSync(historyFile)) return [];
28
+
29
+ try {
30
+ const raw = fs.readFileSync(historyFile, "utf8");
31
+ const parsed: unknown = JSON.parse(raw);
32
+
33
+ if (!Array.isArray(parsed)) return [];
34
+
35
+ return parsed.map(toSharedHistoryValue).filter(isNonEmptyString);
36
+ } catch {
37
+ return [];
38
+ }
39
+ }
@@ -0,0 +1,538 @@
1
+ export interface PromptRecord {
2
+ text: string;
3
+ searchText: string;
4
+ /**
5
+ * Provenance (spec C3): set on records built from PromptEntry inputs;
6
+ * ABSENT on records built from bare strings so the pinned deepEqual
7
+ * record shape ({text, searchText}) stays byte-compatible (design §I).
8
+ */
9
+ source?: PromptSource;
10
+ /** Session records only: the resolved ms-epoch ordering timestamp. */
11
+ ts?: number;
12
+ }
13
+
14
+ /** Provenance of a prompt record or merge-loader entry (spec C3). */
15
+ export type PromptSource = "editor" | "session";
16
+
17
+ /**
18
+ * Merge-loader currency (pre-dedup, spec C3): one editor-store or
19
+ * session-derived prompt. Session entries carry the resolved ms-epoch `ts`;
20
+ * editor entries do not (block ordering at the seam, proposal R8).
21
+ */
22
+ export interface PromptEntry {
23
+ text: string;
24
+ source: PromptSource;
25
+ ts?: number;
26
+ }
27
+
28
+ export interface VisibleRange {
29
+ start: number;
30
+ end: number;
31
+ }
32
+
33
+ export interface VisiblePromptRecord {
34
+ index: number;
35
+ record: PromptRecord;
36
+ isSelected: boolean;
37
+ }
38
+
39
+ export interface PiHistoryGlobals {
40
+ __piHistoryExpand?: () => void;
41
+ __piHistoryTrim?: () => void;
42
+ }
43
+
44
+ export function buildPromptRecords(
45
+ entries: ReadonlyArray<string | PromptEntry>,
46
+ ): PromptRecord[] {
47
+ return entries.map((entry): PromptRecord => {
48
+ if (typeof entry === "string") {
49
+ // Bare string input keeps the EXACT Change 2 runtime shape — the
50
+ // pinned deepEqual records carry only {text, searchText}.
51
+ return { text: entry, searchText: entry.toLowerCase() };
52
+ }
53
+ const record: PromptRecord = {
54
+ text: entry.text,
55
+ searchText: entry.text.toLowerCase(),
56
+ source: entry.source,
57
+ };
58
+ if (entry.ts !== undefined) {
59
+ record.ts = entry.ts;
60
+ }
61
+ return record;
62
+ });
63
+ }
64
+
65
+ export function clampSelectedIndex(
66
+ selectedIndex: number,
67
+ total: number,
68
+ ): number {
69
+ return Math.max(0, Math.min(selectedIndex, Math.max(0, total - 1)));
70
+ }
71
+
72
+ export function clampPreviewOffset(
73
+ offset: number,
74
+ totalLines: number,
75
+ viewportRows: number,
76
+ ): number {
77
+ return Math.max(0, Math.min(offset, Math.max(0, totalLines - viewportRows)));
78
+ }
79
+
80
+ export function computeVisibleRange(
81
+ selectedIndex: number,
82
+ total: number,
83
+ maxVisible: number,
84
+ ): VisibleRange {
85
+ if (total <= 0 || maxVisible <= 0) return { start: 0, end: 0 };
86
+ if (total <= maxVisible) return { start: 0, end: total };
87
+
88
+ const half = Math.floor(maxVisible / 2);
89
+ const start = Math.max(0, Math.min(selectedIndex - half, total - maxVisible));
90
+
91
+ return {
92
+ start,
93
+ end: Math.min(start + maxVisible, total),
94
+ };
95
+ }
96
+
97
+ export function moveSelectedIndex(
98
+ selectedIndex: number,
99
+ total: number,
100
+ delta: number,
101
+ ): number {
102
+ if (total === 0) return 0;
103
+ return (selectedIndex + delta + total) % total;
104
+ }
105
+
106
+ export function pageSelectedIndex(
107
+ selectedIndex: number,
108
+ total: number,
109
+ pageSize: number,
110
+ ): number {
111
+ if (total === 0) return 0;
112
+ return clampSelectedIndex(selectedIndex + pageSize, total);
113
+ }
114
+
115
+ /**
116
+ * Normalization key for read-time dedup (spec C3): byte-matches the
117
+ * APPLIED patch key in nav/patches/editor.cjs (:480-:586) — whitespace
118
+ * runs collapse, then trim, then a 120-char prefix slice, then lowercase.
119
+ * The literal is the single-backslash applied-patch form; the raw patch
120
+ * file stores \\s+ only because its code sits inside a template literal.
121
+ * Shared by contract (spec C4): hide-prompts tombstone keys and the
122
+ * merge-history session-half tombstone filter MUST byte-match this key.
123
+ */
124
+ export function promptDedupKey(entry: string): string {
125
+ return entry.replace(/\s+/g, " ").trim().slice(0, 120).toLowerCase();
126
+ }
127
+
128
+ /**
129
+ * Read-time dedup pass (spec C3): keep-first over input order (file order
130
+ * is newest-first, mirroring the patch's keep-first dedup-on-save), and
131
+ * empty-key entries (empty or whitespace-only) are skipped — excluded from
132
+ * the output and never usable as collision keys. Removes ONLY duplicate-
133
+ * normalized entries; no snapshot cap (filterPrompts still caps output).
134
+ * Generic over string | PromptEntry (WU3): over the COMBINED merged input
135
+ * the keep-first rule makes the editor copy win at the seam — objects pass
136
+ * through with provenance intact; no new dedup logic exists anywhere.
137
+ */
138
+ export function dedupePromptEntries<T extends string | PromptEntry>(
139
+ entries: readonly T[],
140
+ ): T[] {
141
+ const seen = new Set<string>();
142
+ const deduped: T[] = [];
143
+ for (const entry of entries) {
144
+ const key =
145
+ typeof entry === "string"
146
+ ? promptDedupKey(entry)
147
+ : promptDedupKey(entry.text);
148
+ if (key !== "" && !seen.has(key)) {
149
+ seen.add(key);
150
+ deduped.push(entry);
151
+ }
152
+ }
153
+ return deduped;
154
+ }
155
+
156
+ /**
157
+ * First-paint window size (spec C1, AC-L1-1): min(initialBatch, total),
158
+ * floored at 0 — small stores open fully loaded (exhausted at open),
159
+ * identical to today's behavior for R <= INITIAL_BATCH.
160
+ */
161
+ export function initialLoadedCount(
162
+ total: number,
163
+ initialBatch: number,
164
+ ): number {
165
+ return Math.max(0, Math.min(initialBatch, total));
166
+ }
167
+
168
+ /**
169
+ * Prefetch trigger (spec C2's normative expression, AC-L2-1): growth fires
170
+ * iff rows remain unloaded AND the 0-based cursor sits within the final
171
+ * preloadBuffer rows of the loaded window. Reads UNFILTERED counts only —
172
+ * filteredRecords.length appears in no trigger arithmetic (AC-L2-2).
173
+ */
174
+ export function shouldGrowWindow(
175
+ selectedIndex: number,
176
+ loadedCount: number,
177
+ totalCount: number,
178
+ preloadBuffer: number,
179
+ ): boolean {
180
+ return (
181
+ loadedCount < totalCount && selectedIndex + preloadBuffer >= loadedCount
182
+ );
183
+ }
184
+
185
+ /**
186
+ * One growth step (spec C2, AC-L2-1): min(L + max(1, batchSize), R). The
187
+ * max(1, ·) guard also keeps loadedCountForTarget's loop terminating on a
188
+ * degenerate batch size.
189
+ */
190
+ export function nextLoadedCount(
191
+ loadedCount: number,
192
+ totalCount: number,
193
+ batchSize: number,
194
+ ): number {
195
+ const step = Math.max(1, batchSize);
196
+ return Math.min(loadedCount + step, totalCount);
197
+ }
198
+
199
+ /**
200
+ * PgDn catch-up (spec C1, AC-L1-5): the smallest whole-batch count that
201
+ * strictly covers targetIndex (a 0-based master row), clamped at totalCount.
202
+ * No-op when the target is already covered or the window is exhausted.
203
+ * Terminates by construction: each step adds ≥ 1, bounded by totalCount.
204
+ */
205
+ export function loadedCountForTarget(
206
+ loadedCount: number,
207
+ totalCount: number,
208
+ targetIndex: number,
209
+ batchSize: number,
210
+ ): number {
211
+ let next = loadedCount;
212
+ while (next <= targetIndex && next < totalCount) {
213
+ next = nextLoadedCount(next, totalCount, batchSize);
214
+ }
215
+ return next;
216
+ }
217
+
218
+ /**
219
+ * Delete backfill (spec C4's two steps verbatim, AC-L4-1..3): decrement the
220
+ * window against the splice-shrunk snapshot; while unloaded rows remain,
221
+ * backfill one row (clamped) so the next unloaded record slides into the
222
+ * deleted slot and the visible list length stays stable; at exhaustion the
223
+ * decrement is the genuine shrink. Written stepwise — NOT the algebraic
224
+ * min(L, T') shortcut — so the unit tests pin the contract, not an
225
+ * equivalence. Callers guarantee the deleted row sits inside the loaded
226
+ * prefix (idx < loadedCount by construction).
227
+ */
228
+ export function loadedCountAfterDelete(
229
+ loadedCount: number,
230
+ totalCountAfterSplice: number,
231
+ ): number {
232
+ const decrement = loadedCount - 1;
233
+ if (decrement < totalCountAfterSplice) {
234
+ return Math.min(decrement + 1, totalCountAfterSplice);
235
+ }
236
+ return decrement;
237
+ }
238
+
239
+ /**
240
+ * Pure delete-flow planner (spec C4, design §F): maps a record's provenance
241
+ * to the two delete actions. "editor" deletes from the editor store on disk
242
+ * AND writes the tombstone (twin suppression — the session copy of the same
243
+ * text would otherwise resurface next open); "session" plans NOTHING —
244
+ * session-derived rows are read-only (slice-05 D1): transcripts are
245
+ * immutable and owned by Pi core, so the extension never deletes from or
246
+ * writes to them, and deleteCurrent guards the source before the flow.
247
+ * Takes source as a plain parameter (no member reads — the T23 provenance
248
+ * pin keeps overlay consumers source-agnostic outside deleteCurrent); the
249
+ * only consumer is the delete flow in history/index.ts.
250
+ */
251
+ export function deletionActionsFor(
252
+ source: PromptSource,
253
+ ): { deleteFromEditorStore: boolean; writeTombstone: boolean } {
254
+ if (source === "editor") {
255
+ return { deleteFromEditorStore: true, writeTombstone: true };
256
+ }
257
+ return { deleteFromEditorStore: false, writeTombstone: false };
258
+ }
259
+
260
+ /**
261
+ * One transition of the modal delete confirmation (PR #1393 follow-up,
262
+ * slice-05 D3). Disarmed, only the delete combo matters: it ARMS the
263
+ * confirm and executes nothing. While armed the confirm is MODAL: `y`/`Y`
264
+ * executes, `n`/`N`/Esc cancels, and every other key — including a second
265
+ * press of the combo — is swallowed with the confirm still armed (nothing
266
+ * reaches the dispatch table or the search input). The TUI keybinding
267
+ * matches (ctrl+shift+backspace, escape) are computed by the caller via
268
+ * matchesKey and passed as plain booleans so this router stays pure and
269
+ * testable without the TUI; the y/n semantics read the raw data here.
270
+ * ONE definition: the selector's handleInput routes every armed-state key
271
+ * through this function.
272
+ */
273
+ export interface DeleteConfirmStep {
274
+ /** The armed state AFTER this transition. */
275
+ armed: boolean;
276
+ /** True only when `y`/`Y` confirms the armed delete — run the flow. */
277
+ execute: boolean;
278
+ /** True when `n`/`N`/Esc cancels — disarm and resume normal input. */
279
+ cancel: boolean;
280
+ }
281
+
282
+ export function deleteConfirmStep(
283
+ armed: boolean,
284
+ isDeleteKey: boolean,
285
+ isEscapeKey: boolean,
286
+ data: string,
287
+ ): DeleteConfirmStep {
288
+ if (!armed) {
289
+ return isDeleteKey
290
+ ? { armed: true, execute: false, cancel: false }
291
+ : { armed: false, execute: false, cancel: false };
292
+ }
293
+ if (data === "y" || data === "Y") {
294
+ return { armed: false, execute: true, cancel: false };
295
+ }
296
+ if (data === "n" || data === "N" || isEscapeKey) {
297
+ return { armed: false, execute: false, cancel: true };
298
+ }
299
+ return { armed: true, execute: false, cancel: false };
300
+ }
301
+
302
+ /**
303
+ * Confirmation copy shown in the footer while a delete is armed (PR
304
+ * #1393): one line, one variant — a y/n question carrying the standing
305
+ * guarantee that the session log keeps the original either way.
306
+ */
307
+ export function deleteConfirmFooterText(): string {
308
+ return "Delete this prompt from history (y/n)? Prompt stays in session log";
309
+ }
310
+
311
+ /**
312
+ * Toast copy when the store delete THROWS (PR #1393): the flow aborts
313
+ * before any tombstone write, so nothing was removed — the store keeps the
314
+ * prompt and no tombstone is written.
315
+ */
316
+ export const STORE_DELETE_FAILED_TEXT =
317
+ "Store delete failed; nothing was removed.";
318
+
319
+ /**
320
+ * Toast copy when the tombstone write fails on the EDITOR path (PR
321
+ * #1393): the store row was already removed, so only the hide failed —
322
+ * the prompt may reappear from session transcripts.
323
+ */
324
+ export const EDITOR_HIDE_FAILED_TEXT =
325
+ "Deleted from the store, but hiding failed — the prompt may reappear from session transcripts.";
326
+
327
+ /**
328
+ * Toast copy when some store files could not be read or rewritten: copies
329
+ * may remain on disk, and only the tombstone keeps them out of the list.
330
+ */
331
+ export const STORE_DELETE_PARTIAL_TEXT =
332
+ "Some history files could not be rewritten; the prompt is hidden, but copies may remain on disk.";
333
+
334
+ /**
335
+ * Toast copy when some store files could not be rewritten AND the tombstone
336
+ * write failed: nothing hides the copies that may remain on disk.
337
+ */
338
+ export const STORE_DELETE_PARTIAL_HIDE_FAILED_TEXT =
339
+ "Some history files could not be rewritten and hiding failed — the prompt may reappear from those files or from session transcripts.";
340
+
341
+ /** The counts a scope delete reports (structural twin of store's SweepResult). */
342
+ export interface StoreSweepCounts {
343
+ filesAffected: number;
344
+ removed: number;
345
+ failed: number;
346
+ }
347
+
348
+ /**
349
+ * What the delete flow does after the store sweep: proceed to the
350
+ * tombstone when anything was removed OR any file failed (a failed file
351
+ * may still hold a copy the tombstone must hide), surfacing an error
352
+ * notice for failures; stop quietly when there was nothing to delete.
353
+ */
354
+ export function storeDeleteFollowUp(
355
+ counts: StoreSweepCounts,
356
+ ): { proceed: boolean; notice?: string } {
357
+ if (counts.failed > 0) {
358
+ return { proceed: true, notice: STORE_DELETE_PARTIAL_TEXT };
359
+ }
360
+ return { proceed: counts.removed > 0 };
361
+ }
362
+
363
+ /**
364
+ * The one error notice of an editor-path delete, chosen AFTER the tombstone
365
+ * write so it never claims the prompt is hidden when hiding failed. No
366
+ * notice for a clean sweep with a written tombstone.
367
+ */
368
+ export function storeDeleteNotice(
369
+ counts: StoreSweepCounts,
370
+ hideFailed: boolean,
371
+ ): string | undefined {
372
+ if (counts.failed > 0) {
373
+ return hideFailed
374
+ ? STORE_DELETE_PARTIAL_HIDE_FAILED_TEXT
375
+ : STORE_DELETE_PARTIAL_TEXT;
376
+ }
377
+ return hideFailed ? EDITOR_HIDE_FAILED_TEXT : undefined;
378
+ }
379
+
380
+ export function getVisiblePromptRecords(
381
+ records: PromptRecord[],
382
+ selectedIndex: number,
383
+ maxVisible: number,
384
+ ): VisiblePromptRecord[] {
385
+ const { start, end } = computeVisibleRange(
386
+ selectedIndex,
387
+ records.length,
388
+ maxVisible,
389
+ );
390
+ return records.slice(start, end).map((record, offset) => ({
391
+ index: start + offset,
392
+ record,
393
+ isSelected: start + offset === selectedIndex,
394
+ }));
395
+ }
396
+
397
+ export async function withExpandedHistoryGlobals<T>(
398
+ globals: PiHistoryGlobals,
399
+ run: () => Promise<T>,
400
+ ): Promise<T> {
401
+ globals.__piHistoryExpand?.();
402
+ try {
403
+ return await run();
404
+ } finally {
405
+ globals.__piHistoryTrim?.();
406
+ }
407
+ }
408
+
409
+ /**
410
+ * Full-snapshot visibility for non-empty queries (AC-L2-3r, user-directed
411
+ * 2026-09-08): searching must see the whole deduped snapshot, not just the
412
+ * loaded prefix. One-shot and idempotent — returns the total, never an
413
+ * incremental batch — so per-keypress growth stays impossible. Empty or
414
+ * whitespace-only queries leave the lazy window untouched.
415
+ */
416
+ export function loadedCountForQuery(
417
+ loadedCount: number,
418
+ totalCount: number,
419
+ query: string,
420
+ ): number {
421
+ return query.trim().length > 0 ? totalCount : loadedCount;
422
+ }
423
+
424
+ const MAX_RESULTS = 10000;
425
+
426
+ export function filterPrompts(
427
+ records: PromptRecord[],
428
+ query: string,
429
+ ): PromptRecord[] {
430
+ const trimmed = query.trim();
431
+ if (!trimmed) return records.slice(0, MAX_RESULTS);
432
+
433
+ const tokens = trimmed.toLowerCase().split(/\s+/).filter(Boolean);
434
+ const filtered = records.filter((record) => {
435
+ return tokens.every((token) => record.searchText.includes(token));
436
+ });
437
+
438
+ return filtered.slice(0, MAX_RESULTS);
439
+ }
440
+
441
+ /**
442
+ * Cross-extension fullscreen-sidebar state contract (gentle-shell): stored on
443
+ * the shared ProcessTerminal under a global-registry symbol so any extension
444
+ * can read it without importing gentle-shell. Shape per lib/shell-sidebar.ts:
445
+ * `{ active: boolean; ownsHost?: () => boolean; railColumns?: number; ... }`.
446
+ */
447
+ const SIDEBAR_STATE_SYMBOL = Symbol.for("gentle-pi.experimental-sidebar.state");
448
+
449
+ /**
450
+ * Visual breathing room between the picker and the sidebar rail, added on top
451
+ * of the rail reservation (user-directed: 1 column, 2026-09-21).
452
+ */
453
+ export const SIDEBAR_OVERLAY_PADDING = 1;
454
+
455
+ interface SidebarStateShape {
456
+ active?: unknown;
457
+ ownsHost?: () => unknown;
458
+ railColumns?: unknown;
459
+ }
460
+
461
+ /**
462
+ * Overlay right margin for the current terminal: the rail reservation the
463
+ * gentle-shell sidebar publishes (`railColumns`: rail width plus gap) plus
464
+ * padding while the rail is painting, else 0 (native full-window overlay).
465
+ * pi-tui resolves overlay width "100%" and the bottom-center anchor inside
466
+ * `[0, columns - margin)`, which is then exactly the editor column. Reads the
467
+ * terminal-owned state contract defensively — any absent, malformed, or
468
+ * non-owning state degrades to 0 so the picker keeps opening. Purity note:
469
+ * this returns the CURRENT margin per call; live refresh while an overlay
470
+ * stays open is the caller's job (the picker wires visible() plus a getter
471
+ * margin — pi-tui re-reads both every render).
472
+ */
473
+ export function editorOverlayMargin(terminal: unknown): number {
474
+ if (typeof terminal !== "object" || terminal === null) return 0;
475
+ const state = (terminal as Record<symbol, unknown>)[SIDEBAR_STATE_SYMBOL] as
476
+ | SidebarStateShape
477
+ | undefined;
478
+ if (typeof state !== "object" || state === null) return 0;
479
+ if (state.active !== true || typeof state.ownsHost !== "function") return 0;
480
+ const railColumns = state.railColumns;
481
+ if (
482
+ typeof railColumns !== "number" ||
483
+ !Number.isInteger(railColumns) ||
484
+ railColumns <= 0
485
+ ) {
486
+ return 0;
487
+ }
488
+ try {
489
+ return state.ownsHost() === true
490
+ ? railColumns + SIDEBAR_OVERLAY_PADDING
491
+ : 0;
492
+ } catch {
493
+ return 0;
494
+ }
495
+ }
496
+
497
+ /** Responsive picker-header mode at the current render width. */
498
+ export type HeaderLayoutMode = "inline" | "stacked" | "compact";
499
+
500
+ /**
501
+ * Fit-driven header plan (user-directed responsive header): "inline" keeps
502
+ * title + counts + right-flushed radio on one row; "stacked" (tablet) deletes
503
+ * the spacer — the radio wraps to its own row under the full counts line;
504
+ * "compact" (mobile) further splits the counts off and abbreviates the radio.
505
+ * Thresholds derive from the ACTUAL text widths, so any count size flips the
506
+ * mode at the exact column where the previous layout stops fitting.
507
+ */
508
+ export function planHeaderLayout(
509
+ width: number,
510
+ leftWidth: number,
511
+ radioWidth: number,
512
+ minGap: number,
513
+ ): HeaderLayoutMode {
514
+ if (width >= leftWidth + minGap + radioWidth) return "inline";
515
+ if (width >= leftWidth) return "stacked";
516
+ return "compact";
517
+ }
518
+
519
+ /** Full scope radio: both scope labels spelled out. */
520
+ export const SCOPE_RADIO_FULL_PROJECT = "◉ Current project | ○ All projects";
521
+ export const SCOPE_RADIO_FULL_GLOBAL = "○ Current project | ◉ All projects";
522
+ /** Abbreviated radio: the ACTIVE scope keeps its full label, the other shortens. */
523
+ export const SCOPE_RADIO_COMPACT_PROJECT = "◉ Current project | ○ All";
524
+ export const SCOPE_RADIO_COMPACT_GLOBAL = "○ Current | ◉ All projects";
525
+
526
+ /**
527
+ * Scope radio text for the current width: abbreviated only when the full
528
+ * radio cannot fit the row it would occupy (compact widths).
529
+ */
530
+ export function scopeRadioText(
531
+ scope: "project" | "global",
532
+ compact: boolean,
533
+ ): string {
534
+ if (scope === "project") {
535
+ return compact ? SCOPE_RADIO_COMPACT_PROJECT : SCOPE_RADIO_FULL_PROJECT;
536
+ }
537
+ return compact ? SCOPE_RADIO_COMPACT_GLOBAL : SCOPE_RADIO_FULL_GLOBAL;
538
+ }