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,460 @@
1
+ import { test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import fs from "node:fs";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+ import { hidePrompt } from "../extensions/history/hide-prompts.ts";
8
+ import {
9
+ deleteConfirmFooterText,
10
+ deleteConfirmStep,
11
+ deletionActionsFor,
12
+ EDITOR_HIDE_FAILED_TEXT,
13
+ STORE_DELETE_FAILED_TEXT,
14
+ STORE_DELETE_PARTIAL_HIDE_FAILED_TEXT,
15
+ STORE_DELETE_PARTIAL_TEXT,
16
+ storeDeleteFollowUp,
17
+ storeDeleteNotice,
18
+ } from "../extensions/history/selector-helpers.ts";
19
+
20
+ // Slice-05 delete-confirm tests (PR #1393 follow-up): the delete
21
+ // confirmation is a MODAL y/n step. The first ctrl+shift+backspace press
22
+ // ARMS the delete for the selected row (confirmation footer + highlighted
23
+ // record) and executes NOTHING; while armed, y executes, n/Esc cancels,
24
+ // and every other key is swallowed with the confirm still armed — nothing
25
+ // reaches the dispatch table or the search input. Session-derived rows
26
+ // are read-only: a delete press on one is a silent no-op.
27
+ //
28
+ // PromptHistorySelector is private to extensions/history/index.ts and
29
+ // needs the pi-tui runtime graph (openflow-integration.test.ts
30
+ // discipline), so the confirm DECISION is factored into the pure
31
+ // deleteConfirmStep router tested here directly, and the wiring semantics are pinned by source-parse on
32
+ // deleteCurrent/armDelete/executeDelete/handleInput (delete-backfill
33
+ // discipline). No test in this file touches the user's real store.
34
+
35
+ // ---------------------------------------------------------------------------
36
+ // Pure modal router: arm → y executes / n·Esc cancels / rest swallowed.
37
+ // ---------------------------------------------------------------------------
38
+
39
+ const ARM = { armed: true, execute: false, cancel: false };
40
+ const IDLE = { armed: false, execute: false, cancel: false };
41
+ const EXECUTE = { armed: false, execute: true, cancel: false };
42
+ const CANCEL = { armed: false, execute: false, cancel: true };
43
+
44
+ test("the first delete press arms only — nothing executes, nothing cancels", () => {
45
+ assert.deepEqual(deleteConfirmStep(false, true, false, ""), ARM);
46
+ });
47
+
48
+ test("an unarmed non-delete key is a no-op — the confirm stays out of the way", () => {
49
+ assert.deepEqual(deleteConfirmStep(false, false, false, "x"), IDLE);
50
+ });
51
+
52
+ test("while armed, y (and Y) executes the delete", () => {
53
+ assert.deepEqual(deleteConfirmStep(true, false, false, "y"), EXECUTE);
54
+ assert.deepEqual(deleteConfirmStep(true, false, false, "Y"), EXECUTE);
55
+ });
56
+
57
+ test("while armed, n / N / Esc cancel — the confirm disarms without executing", () => {
58
+ assert.deepEqual(deleteConfirmStep(true, false, false, "n"), CANCEL);
59
+ assert.deepEqual(deleteConfirmStep(true, false, false, "N"), CANCEL);
60
+ assert.deepEqual(deleteConfirmStep(true, false, true, "\x1b"), CANCEL);
61
+ });
62
+
63
+ test("while armed, any other key is swallowed and the confirm STAYS armed", () => {
64
+ // Plain typing, digits, empty data, arrow-key bytes, and a SECOND
65
+ // delete-combo press: none of them execute or cancel.
66
+ assert.deepEqual(deleteConfirmStep(true, false, false, "x"), ARM);
67
+ assert.deepEqual(deleteConfirmStep(true, false, false, "1"), ARM);
68
+ assert.deepEqual(deleteConfirmStep(true, false, false, ""), ARM);
69
+ assert.deepEqual(deleteConfirmStep(true, false, false, "\x1b[A"), ARM);
70
+ assert.deepEqual(deleteConfirmStep(true, true, false, "\x1b[27;6~"), ARM);
71
+ });
72
+
73
+ test("full machine: arm → y executes; a fresh arm is needed per delete", () => {
74
+ const armed = deleteConfirmStep(false, true, false, "");
75
+ assert.equal(armed.armed, true);
76
+ assert.equal(armed.execute, false);
77
+ const done = deleteConfirmStep(armed.armed, false, false, "y");
78
+ assert.equal(done.execute, true);
79
+ assert.equal(done.armed, false, "executing leaves the confirm disarmed");
80
+ // After execution the confirm is idle: typing resumes as usual.
81
+ assert.deepEqual(deleteConfirmStep(done.armed, false, false, "x"), IDLE);
82
+ });
83
+
84
+ test("full machine: arm → n cancels → disarmed without executing", () => {
85
+ const armed = deleteConfirmStep(false, true, false, "");
86
+ const cancelled = deleteConfirmStep(armed.armed, false, true, "\x1b");
87
+ assert.equal(cancelled.cancel, true);
88
+ assert.equal(cancelled.armed, false);
89
+ assert.equal(cancelled.execute, false);
90
+ });
91
+
92
+ // The executing press composes with the pure planner: an editor-source
93
+ // record deletes from the store AND tombstones; a session-source record is
94
+ // read-only — the planner plans NOTHING for it (slice-05 D1).
95
+
96
+ test("y on an editor row runs the store-delete + tombstone plan", () => {
97
+ const armed = deleteConfirmStep(false, true, false, "");
98
+ const step = deleteConfirmStep(armed.armed, false, false, "y");
99
+ assert.equal(step.execute, true);
100
+ assert.deepEqual(deletionActionsFor("editor"), {
101
+ deleteFromEditorStore: true,
102
+ writeTombstone: true,
103
+ });
104
+ });
105
+
106
+ test("session rows are read-only: the planner plans nothing for them", () => {
107
+ assert.deepEqual(deletionActionsFor("session"), {
108
+ deleteFromEditorStore: false,
109
+ writeTombstone: false,
110
+ });
111
+ });
112
+
113
+ // ---------------------------------------------------------------------------
114
+ // Copy: one confirmation line for every row; the failure toasts state
115
+ // exactly what state remains.
116
+ // ---------------------------------------------------------------------------
117
+
118
+ test("the confirmation footer is the single y/n line (PR #1393)", () => {
119
+ const text = deleteConfirmFooterText();
120
+ assert.ok(!text.includes("\n"), "the confirmation stays on one line");
121
+ assert.ok(text.includes("Delete this prompt from history (y/n)?"));
122
+ assert.ok(text.includes("Prompt stays in session log"));
123
+ });
124
+
125
+ test("failure toasts state the remaining state exactly (PR #1393)", () => {
126
+ // A thrown store delete aborts before any tombstone: nothing removed.
127
+ assert.equal(
128
+ STORE_DELETE_FAILED_TEXT,
129
+ "Store delete failed; nothing was removed.",
130
+ );
131
+ // Editor-path hide failure: the store row is gone, the prompt may
132
+ // reappear from transcripts.
133
+ assert.equal(
134
+ EDITOR_HIDE_FAILED_TEXT,
135
+ "Deleted from the store, but hiding failed — the prompt may reappear from session transcripts.",
136
+ );
137
+ });
138
+
139
+ // ---------------------------------------------------------------------------
140
+ // Source-parse: the wiring inside the selector (the class itself is not
141
+ // instantiable under node:test — see the header note).
142
+ // ---------------------------------------------------------------------------
143
+
144
+ const selectorSource = fs.readFileSync(
145
+ fileURLToPath(new URL("../extensions/history/index.ts", import.meta.url)),
146
+ "utf8",
147
+ );
148
+
149
+ /** Slice out a 2-space-indented method body by its exact signature. */
150
+ function methodBodyOf(signature: string): string {
151
+ const decl = selectorSource.indexOf(signature);
152
+ assert.ok(decl >= 0, `${signature} should exist`);
153
+ const end = selectorSource.indexOf("\n }", decl);
154
+ assert.ok(end > decl, `${signature}'s body should close`);
155
+ return selectorSource.slice(decl, end);
156
+ }
157
+
158
+ function deleteCurrentBody(): string {
159
+ return methodBodyOf("private deleteCurrent(): void {");
160
+ }
161
+
162
+ function executeDeleteBody(): string {
163
+ return methodBodyOf("private executeDelete(): void {");
164
+ }
165
+
166
+ function handleInputBody(): string {
167
+ return methodBodyOf("handleInput(data: string): void {");
168
+ }
169
+
170
+ test("deleteCurrent: session rows no-op FIRST — before any arm or mutation", () => {
171
+ const body = deleteCurrentBody();
172
+ const guardAt = body.indexOf('(selected.source ?? "editor") === "session"');
173
+ assert.ok(guardAt >= 0, "the session read-only guard must exist");
174
+ const guardReturnAt = body.indexOf("return;", guardAt);
175
+ assert.ok(guardReturnAt > guardAt, "the session guard must return");
176
+ // The guard precedes the arm/execute split and every mutation helper.
177
+ const armAt = body.indexOf("this.armDelete()");
178
+ const executeAt = body.indexOf("this.executeDelete()");
179
+ assert.ok(armAt > guardAt, "the session guard must precede arming");
180
+ assert.ok(executeAt > guardAt, "the session guard must precede executing");
181
+ // The combo entry stays two-step: unarmed arms, armed executes.
182
+ assert.ok(
183
+ body.includes("if (!this.confirmArmed)"),
184
+ "the unarmed press must arm",
185
+ );
186
+ assert.ok(
187
+ armAt < executeAt,
188
+ "armDelete is the unarmed branch, executeDelete the armed one",
189
+ );
190
+ });
191
+
192
+ test("armDelete only paints: armed + footer + rebuild — never mutates", () => {
193
+ const body = methodBodyOf("private armDelete(): void {");
194
+ assert.ok(body.includes("this.confirmArmed = true;"));
195
+ assert.ok(body.includes("this.refreshDeleteFooter()"));
196
+ assert.ok(body.includes("this.rebuildList()"));
197
+ assert.ok(!body.includes("hidePrompt("), "arming never writes a tombstone");
198
+ assert.ok(
199
+ !body.includes("this.records.splice("),
200
+ "arming never mutates rows",
201
+ );
202
+ });
203
+
204
+ test("executeDelete leaves the armed state before any mutation", () => {
205
+ const body = executeDeleteBody();
206
+ const disarmAt = body.indexOf("this.confirmArmed = false;");
207
+ assert.ok(disarmAt >= 0, "executing must leave the armed state");
208
+ assert.ok(body.includes("this.refreshDeleteFooter()"));
209
+ const actionsAt = body.indexOf("deletionActionsFor(");
210
+ const hideAt = body.indexOf("hidePrompt(");
211
+ const spliceAt = body.indexOf("this.records.splice(");
212
+ assert.ok(
213
+ disarmAt < actionsAt && actionsAt < hideAt && hideAt < spliceAt,
214
+ "disarm → plan → tombstone → splice ordering",
215
+ );
216
+ });
217
+
218
+ test("while armed, handleInput is modal: the router runs FIRST and returns", () => {
219
+ const body = handleInputBody();
220
+ const modalAt = body.indexOf("if (this.confirmArmed) {");
221
+ assert.ok(modalAt >= 0, "the modal branch must exist");
222
+ assert.ok(
223
+ body.includes("deleteConfirmStep("),
224
+ "the armed branch routes through the pure router",
225
+ );
226
+ assert.ok(
227
+ body.includes('matchesKey(data, "ctrl+shift+backspace")') &&
228
+ body.includes('matchesKey(data, "escape")'),
229
+ "the combo and escape matches come from the TUI keymap",
230
+ );
231
+ const executeAt = body.indexOf("this.executeDelete()");
232
+ const cancelAt = body.indexOf("this.disarmDeleteConfirm()");
233
+ assert.ok(executeAt > modalAt, "y must execute inside the modal branch");
234
+ assert.ok(cancelAt > modalAt, "n/Esc must disarm inside the modal branch");
235
+ // Full swallow: the modal branch RETURNS before the dispatch loop and
236
+ // the search fallthrough can see the key — esc cannot close the overlay.
237
+ const modalReturnAt = body.indexOf("return;", modalAt);
238
+ assert.ok(modalReturnAt > modalAt, "the modal branch must return");
239
+ const loopAt = body.indexOf(
240
+ "for (const { match, handler } of this.dispatch) {",
241
+ );
242
+ const fallthroughAt = body.indexOf(
243
+ "if (!handled) this.forwardToSearch(data);",
244
+ );
245
+ assert.ok(loopAt > modalReturnAt, "armed keys never reach dispatch");
246
+ assert.ok(fallthroughAt > modalReturnAt, "armed keys never reach search");
247
+ });
248
+
249
+ test("the old disarm pre-pass is superseded — disarm only on the modal cancel path", () => {
250
+ const body = handleInputBody();
251
+ assert.ok(
252
+ !body.includes('!matchesKey(data, "ctrl+shift+backspace")'),
253
+ "the unconditional disarm pre-pass must be gone",
254
+ );
255
+ const modalAt = body.indexOf("if (this.confirmArmed) {");
256
+ const disarmAt = body.indexOf("this.disarmDeleteConfirm()");
257
+ assert.ok(disarmAt > modalAt, "disarm must sit inside the modal branch");
258
+ });
259
+
260
+ test("Esc while DISARMED still cancels the overlay via the dispatch entry", () => {
261
+ const table = selectorSource.slice(
262
+ selectorSource.indexOf("private readonly dispatch"),
263
+ selectorSource.indexOf("\n ];"),
264
+ );
265
+ const cancelAt = table.indexOf('kb.matches(_d, "tui.select.cancel")');
266
+ assert.ok(cancelAt >= 0, "the cancel dispatch entry must stay");
267
+ const entry = table.slice(cancelAt, table.indexOf("},", cancelAt));
268
+ assert.ok(
269
+ entry.includes("this.onCancel()"),
270
+ "disarmed esc must still close the overlay",
271
+ );
272
+ });
273
+
274
+ test("a wheel scroll disarms (and never executes) the armed delete", () => {
275
+ const handleMouseAt = selectorSource.indexOf("override handleMouse(");
276
+ assert.ok(handleMouseAt >= 0, "handleMouse should exist");
277
+ const mouseEnd = selectorSource.indexOf("\n }", handleMouseAt);
278
+ const mouseBody = selectorSource.slice(handleMouseAt, mouseEnd);
279
+ assert.ok(
280
+ mouseBody.indexOf("this.disarmDeleteConfirm()") >= 0,
281
+ "a wheel scroll can move the selection off the armed row — it must disarm",
282
+ );
283
+ assert.ok(
284
+ !mouseBody.includes("this.executeDelete()"),
285
+ "a wheel scroll must never execute the delete",
286
+ );
287
+ });
288
+
289
+ test("the armed state drives the footer copy and the error-colored highlight", () => {
290
+ const footerBody = methodBodyOf("private refreshDeleteFooter(): void {");
291
+ assert.ok(
292
+ footerBody.includes("deleteConfirmFooterText()"),
293
+ "the armed footer uses the single pure copy (no source argument)",
294
+ );
295
+ assert.ok(
296
+ !footerBody.includes("deleteConfirmFooterText" + "(source)"),
297
+ "the footer must not route through a source variant",
298
+ );
299
+ assert.ok(
300
+ footerBody.includes("SELECTOR_FOOTER_HELP"),
301
+ "disarming restores the help line",
302
+ );
303
+
304
+ const rebuildBody = methodBodyOf(
305
+ "private rebuildListWithWidth(width: number): void {",
306
+ );
307
+ assert.ok(
308
+ rebuildBody.includes("this.confirmArmed"),
309
+ "the armed state repaints the selected row",
310
+ );
311
+ });
312
+
313
+ // ---------------------------------------------------------------------------
314
+ // Capture gate: the opt-in switch stays GENTLE_PI_HISTORY_CAPTURE (#1390).
315
+ // ---------------------------------------------------------------------------
316
+
317
+ // The contributor branch briefly renamed the switch; the rename must not
318
+ // ship. Assemble the rejected literal from parts so this file stays
319
+ // grep-clean for it.
320
+ const renamedSwitch = `GENTLE_PI_HISTORY_${"ENABLE"}`;
321
+
322
+ test("captureEnabled delegates to the shared env-then-Customize gate", () => {
323
+ const decl = selectorSource.indexOf("export function captureEnabled(");
324
+ assert.ok(decl >= 0, "captureEnabled should exist");
325
+ const end = selectorSource.indexOf("\n}", decl);
326
+ assert.ok(end > decl, "captureEnabled's body should close");
327
+ const body = selectorSource.slice(decl, end);
328
+ // The strict 1/true/on (and explicit 0/false/off) parsing lives in
329
+ // lib/history-capture-policy.ts; tests/history-capture-policy.test.ts and
330
+ // the session-writer suite pin the behavior.
331
+ assert.ok(
332
+ body.includes("historyCaptureEnabled({ env, gentlePiConfigHome: configHome })"),
333
+ "the extension must use the shared gate with its injected config home",
334
+ );
335
+ assert.ok(!body.includes(renamedSwitch), "the rename must not ship");
336
+ });
337
+
338
+ test("the history extension never mentions the renamed switch", () => {
339
+ assert.equal(selectorSource.includes(renamedSwitch), false);
340
+ });
341
+
342
+ // ---------------------------------------------------------------------------
343
+ // Partial sweep failures (PR #1393 adaptation): a store file that could not
344
+ // be read or rewritten may still hold a copy, so the delete must say so —
345
+ // and still write the tombstone that hides the remaining copies.
346
+ // ---------------------------------------------------------------------------
347
+
348
+ test("storeDeleteFollowUp: a clean sweep proceeds without a notice", () => {
349
+ assert.deepEqual(
350
+ storeDeleteFollowUp({ filesAffected: 1, removed: 2, failed: 0 }),
351
+ { proceed: true },
352
+ );
353
+ });
354
+
355
+ test("storeDeleteFollowUp: nothing removed and nothing failed stops quietly", () => {
356
+ assert.deepEqual(
357
+ storeDeleteFollowUp({ filesAffected: 0, removed: 0, failed: 0 }),
358
+ { proceed: false },
359
+ );
360
+ });
361
+
362
+ test("storeDeleteFollowUp: any failed file proceeds to hide AND surfaces an error", () => {
363
+ for (const removed of [0, 3]) {
364
+ assert.deepEqual(
365
+ storeDeleteFollowUp({ filesAffected: removed > 0 ? 1 : 0, removed, failed: 1 }),
366
+ { proceed: true, notice: STORE_DELETE_PARTIAL_TEXT },
367
+ );
368
+ }
369
+ assert.ok(STORE_DELETE_PARTIAL_TEXT.includes("could not"));
370
+ assert.ok(STORE_DELETE_PARTIAL_TEXT.includes("hidden"));
371
+ });
372
+
373
+ test("executeDelete reports a partial sweep only after the hide result is known", () => {
374
+ const body = executeDeleteBody();
375
+ const followAt = body.indexOf("storeDeleteFollowUp(");
376
+ const hideAt = body.indexOf("hidePrompt(");
377
+ const noticeAt = body.indexOf("storeDeleteNotice(");
378
+ const spliceAt = body.indexOf("this.records.splice(");
379
+ assert.ok(followAt >= 0, "the sweep result must be interpreted");
380
+ assert.ok(hideAt > followAt, "the tombstone still follows a partial failure");
381
+ assert.equal(
382
+ body.includes("followUp.notice"),
383
+ false,
384
+ "the partial notice must not claim the prompt is hidden before hidePrompt runs",
385
+ );
386
+ assert.ok(noticeAt > hideAt, "the store notice is chosen from the hide result");
387
+ assert.ok(
388
+ body.includes('this.onNotify?.(notice, "error")'),
389
+ "the chosen notice surfaces as an error",
390
+ );
391
+ assert.ok(noticeAt < spliceAt, "the notice is decided before the row leaves the list");
392
+ assert.equal(
393
+ body.includes("if (removed === 0) return;"),
394
+ false,
395
+ "a zero-removal partial failure must not skip the tombstone",
396
+ );
397
+ });
398
+
399
+ test("storeDeleteNotice states exactly what remains after the sweep and the hide", () => {
400
+ const clean = { filesAffected: 1, removed: 1, failed: 0 };
401
+ const partial = { filesAffected: 1, removed: 1, failed: 1 };
402
+ assert.equal(storeDeleteNotice(clean, false), undefined);
403
+ assert.equal(storeDeleteNotice(clean, true), EDITOR_HIDE_FAILED_TEXT);
404
+ assert.equal(storeDeleteNotice(partial, false), STORE_DELETE_PARTIAL_TEXT);
405
+ // Both halves failed: one notice, and it never claims the prompt is hidden.
406
+ assert.equal(storeDeleteNotice(partial, true), STORE_DELETE_PARTIAL_HIDE_FAILED_TEXT);
407
+ assert.equal(STORE_DELETE_PARTIAL_HIDE_FAILED_TEXT.includes("is hidden"), false);
408
+ assert.ok(STORE_DELETE_PARTIAL_HIDE_FAILED_TEXT.includes("could not be rewritten"));
409
+ assert.ok(STORE_DELETE_PARTIAL_HIDE_FAILED_TEXT.includes("hiding failed"));
410
+ assert.ok(STORE_DELETE_PARTIAL_HIDE_FAILED_TEXT.includes("may reappear"));
411
+ });
412
+
413
+ // Review advisory A2 (#1477): the non-store (session-derived) delete path
414
+ // must never claim a prompt is hidden when the tombstone write failed.
415
+ // Evidence that it does not: that branch surfaces hidePrompt's own error
416
+ // message and returns before the row leaves the list, and every error
417
+ // message hidePrompt returns says the hide did NOT happen. The branch is
418
+ // also unreachable from the UI: deleteCurrent drops session rows before
419
+ // arming, and the armed confirm is modal.
420
+ test("a failed hide on the non-store path reports the hide error and keeps the row", () => {
421
+ const body = executeDeleteBody();
422
+ const branchAt = body.indexOf('if (hide.status === "error") {');
423
+ const noticeAt = body.indexOf("if (sweep) {");
424
+ assert.ok(branchAt >= 0 && noticeAt > branchAt);
425
+ const branch = body.slice(branchAt, noticeAt);
426
+ const nonStoreAt = branch.indexOf("if (!actions.deleteFromEditorStore) {");
427
+ assert.ok(nonStoreAt >= 0, "the non-store path handles its own hide failure");
428
+ const notifyAt = branch.indexOf('this.onNotify?.(hide.message, "error");');
429
+ const returnAt = branch.indexOf("return;", notifyAt);
430
+ assert.ok(notifyAt > nonStoreAt, "it surfaces hidePrompt's own message");
431
+ assert.ok(returnAt > notifyAt, "and aborts before the splice");
432
+ assert.ok(body.indexOf("this.records.splice(") > branchAt + returnAt);
433
+
434
+ const messages: string[] = [];
435
+ const unwritable = fs.mkdtempSync(path.join(os.tmpdir(), "pi-history-a2-"));
436
+ const realRename = fs.renameSync;
437
+ fs.renameSync = ((from: fs.PathLike, to: fs.PathLike) => {
438
+ if (String(to) === path.join(unwritable, "hidden.json")) {
439
+ throw Object.assign(new Error("simulated EACCES"), { code: "EACCES" });
440
+ }
441
+ return realRename(from, to);
442
+ }) as typeof fs.renameSync;
443
+ try {
444
+ const failed = hidePrompt(unwritable, "secret");
445
+ assert.equal(failed.status, "error");
446
+ if (failed.status === "error") messages.push(failed.message);
447
+ } finally {
448
+ fs.renameSync = realRename;
449
+ }
450
+ const corrupt = fs.mkdtempSync(path.join(os.tmpdir(), "pi-history-a2-"));
451
+ fs.writeFileSync(path.join(corrupt, "hidden.json"), "{not json", "utf8");
452
+ const refused = hidePrompt(corrupt, "secret");
453
+ assert.equal(refused.status, "error");
454
+ if (refused.status === "error") messages.push(refused.message);
455
+
456
+ for (const message of messages) {
457
+ assert.doesNotMatch(message, /\b(is|was) hidden\b/i, message);
458
+ assert.match(message, /may (then )?reappear/, message);
459
+ }
460
+ });
@@ -0,0 +1,180 @@
1
+ import { describe, it } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import fs from "node:fs";
4
+ import { fileURLToPath } from "node:url";
5
+
6
+ /**
7
+ * Dispatch table structural tests — source-parsed (AC-P2-1.3, AC-P2-2.1,
8
+ * AC-P2-4.1), following the preview-layout.test.ts pattern.
9
+ *
10
+ * PromptHistorySelector is private to extensions/history/index.ts and needs
11
+ * the pi-tui runtime (Container, Input, TUI, Theme), so these tests read the
12
+ * source file and pin the normative §B2 shape instead of importing it:
13
+ * exactly 12 explicit entries in a fixed order, then the implicit
14
+ * forwardToSearch fallthrough inside handleInput.
15
+ */
16
+
17
+ const sourcePath = fileURLToPath(
18
+ new URL("../extensions/history/index.ts", import.meta.url),
19
+ );
20
+ const source = fs.readFileSync(sourcePath, "utf8");
21
+
22
+ const DISPATCH_DECL = "private readonly dispatch: readonly DispatchEntry[] = [";
23
+ const TABLE_CLOSE = "\n ];";
24
+
25
+ /** §B2 normative matcher order — the exact literal as it appears per entry. */
26
+ const EXPECTED_MATCHERS = [
27
+ 'kb.matches(_d, "tui.select.up")',
28
+ 'kb.matches(_d, "tui.select.down")',
29
+ 'kb.matches(_d, "tui.select.pageUp")',
30
+ 'kb.matches(_d, "tui.select.pageDown")',
31
+ 'd === "\\r" || kb.matches(d, "tui.select.confirm")',
32
+ 'd === "\\t"',
33
+ 'kb.matches(_d, "tui.select.cancel")',
34
+ 'matchesKey(d, "home")',
35
+ 'matchesKey(d, "end")',
36
+ 'matchesKey(d, "ctrl+shift+backspace")',
37
+ 'matchesKey(d, "ctrl+shift+up")',
38
+ 'matchesKey(d, "ctrl+shift+down")',
39
+ ];
40
+
41
+ /** Handler each entry must invoke (searched within the entry's body). */
42
+ const EXPECTED_HANDLERS = [
43
+ "this.moveUp()",
44
+ "this.moveDown()",
45
+ "this.pageListUp()",
46
+ "this.pageListDown()",
47
+ "this.selectCurrent()",
48
+ "this.toggleScope()",
49
+ "this.onCancel()",
50
+ "this.jumpToFirst()",
51
+ "this.jumpToLast()",
52
+ "this.deleteCurrent()",
53
+ "this.previewPageUp()",
54
+ "this.previewPageDown()",
55
+ ];
56
+
57
+ function dispatchTable(): string {
58
+ const start = source.indexOf(DISPATCH_DECL);
59
+ assert.notStrictEqual(
60
+ start,
61
+ -1,
62
+ "dispatch table declaration should exist in extensions/history/index.ts",
63
+ );
64
+ const end = source.indexOf(TABLE_CLOSE, start);
65
+ assert.notStrictEqual(end, -1, "dispatch table closing should exist");
66
+ return source.slice(start, end);
67
+ }
68
+
69
+ /** Entry i's body: from its matcher literal to the next matcher (or table end). */
70
+ function entryBody(table: string, index: number): string {
71
+ const start = table.indexOf(EXPECTED_MATCHERS[index]);
72
+ const next =
73
+ index + 1 < EXPECTED_MATCHERS.length
74
+ ? table.indexOf(EXPECTED_MATCHERS[index + 1])
75
+ : table.length;
76
+ return table.slice(start, next === -1 ? table.length : next);
77
+ }
78
+
79
+ function methodBody(name: string): string {
80
+ const start = source.indexOf(`private ${name}(): void {`);
81
+ assert.notStrictEqual(start, -1, `private ${name}() should exist`);
82
+ const end = source.indexOf("\n }", start);
83
+ assert.notStrictEqual(end, -1, `private ${name}() body should close`);
84
+ return source.slice(start, end);
85
+ }
86
+
87
+ describe("dispatch table (source-parsed, §B2)", () => {
88
+ it("has exactly 12 explicit match: entries (AC-P2-4.1)", () => {
89
+ const table = dispatchTable();
90
+ const matchCount = table.split("match:").length - 1;
91
+ assert.strictEqual(
92
+ matchCount,
93
+ 12,
94
+ `expected 12 explicit entries, found ${matchCount}`,
95
+ );
96
+ });
97
+
98
+ it("keeps the exact §B2 matcher order", () => {
99
+ const table = dispatchTable();
100
+ let cursor = -1;
101
+ EXPECTED_MATCHERS.forEach((matcher, i) => {
102
+ const at = table.indexOf(matcher);
103
+ assert.notStrictEqual(
104
+ at,
105
+ -1,
106
+ `entry #${i + 1} matcher missing: ${matcher}`,
107
+ );
108
+ assert.ok(
109
+ at > cursor,
110
+ `entry #${i + 1} matcher out of order: ${matcher}`,
111
+ );
112
+ cursor = at;
113
+ });
114
+ });
115
+
116
+ it("wires every entry handler per §B2", () => {
117
+ const table = dispatchTable();
118
+ EXPECTED_HANDLERS.forEach((handler, i) => {
119
+ const body = entryBody(table, i);
120
+ assert.ok(
121
+ body.includes(handler),
122
+ `entry #${i + 1} should call ${handler}`,
123
+ );
124
+ });
125
+ });
126
+
127
+ it("pages the LIST via pageSelectedIndex and resets the preview offset (AC-P2-1.3)", () => {
128
+ const up = methodBody("pageListUp");
129
+ assert.ok(
130
+ up.includes("pageSelectedIndex("),
131
+ "pageListUp must clamp via pageSelectedIndex",
132
+ );
133
+ assert.ok(
134
+ up.includes("-MAX_VISIBLE"),
135
+ "pageListUp must page up by one page",
136
+ );
137
+ assert.ok(
138
+ up.includes("previewScrollOffset = 0"),
139
+ "pageListUp must reset the preview offset",
140
+ );
141
+ const down = methodBody("pageListDown");
142
+ assert.ok(
143
+ down.includes("pageSelectedIndex("),
144
+ "pageListDown must clamp via pageSelectedIndex",
145
+ );
146
+ assert.ok(
147
+ down.includes("MAX_VISIBLE"),
148
+ "pageListDown must page down by one page",
149
+ );
150
+ assert.ok(
151
+ down.includes("previewScrollOffset = 0"),
152
+ "pageListDown must reset the preview offset",
153
+ );
154
+ });
155
+
156
+ it("runs the ctrl+shift combos before the implicit fallthrough (AC-P2-2.1)", () => {
157
+ const table = dispatchTable();
158
+ const lastMatch = table.lastIndexOf("match:");
159
+ assert.ok(
160
+ table.slice(lastMatch).includes('matchesKey(d, "ctrl+shift+down")'),
161
+ "the final table entry must be the ctrl+shift+down combo",
162
+ );
163
+ const loopAt = source.indexOf(
164
+ "for (const { match, handler } of this.dispatch) {",
165
+ );
166
+ const fallthroughAt = source.indexOf(
167
+ "if (!handled) this.forwardToSearch(data);",
168
+ );
169
+ assert.notStrictEqual(loopAt, -1, "dispatch loop should exist");
170
+ assert.notStrictEqual(
171
+ fallthroughAt,
172
+ -1,
173
+ "forwardToSearch fallthrough should exist",
174
+ );
175
+ assert.ok(
176
+ fallthroughAt > loopAt,
177
+ "fallthrough must run after the dispatch loop",
178
+ );
179
+ });
180
+ });