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,604 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
3
+ import { dirname, isAbsolute, join, relative, sep } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { resolveGentlePiAgentHome } from "./agent-home.ts";
6
+
7
+ function isRecord(value: unknown): value is Record<string, unknown> {
8
+ return typeof value === "object" && value !== null && !Array.isArray(value);
9
+ }
10
+
11
+ const PACKAGE_ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
12
+ const ASSETS_DIR = join(PACKAGE_ROOT, "assets");
13
+ const MANAGED_ASSETS_MANIFEST = "managed-assets.json";
14
+ const MANAGED_ASSETS_LOCK = "managed-assets.lock";
15
+ const MANAGED_ASSETS_SCHEMA_VERSION = 1;
16
+ const MANAGED_ASSETS_LOCK_TIMEOUT_MS = 5_000;
17
+ const MANAGED_ASSETS_LOCK_RETRY_MS = 25;
18
+ const LEGACY_MANAGED_ASSET_MANIFESTS = Object.freeze([
19
+ { path: join(ASSETS_DIR, "migrations", "managed-assets-v0.10.7.json"), version: "0.10.7" },
20
+ { path: join(ASSETS_DIR, "migrations", "managed-assets-v0.13.json"), version: "0.13.0" },
21
+ { path: join(ASSETS_DIR, "migrations", "managed-assets-v0.14.json"), version: "0.14.0" },
22
+ { path: join(ASSETS_DIR, "migrations", "managed-assets-v2.5.0.json"), version: "2.5.0" },
23
+ ]);
24
+
25
+ const ASSET_OWNER_BY_KEY = Object.freeze({
26
+ "agents/gentle-ai-explore.md": "delegation",
27
+ "agents/gentle-ai-verify.md": "delegation",
28
+ "agents/gentle-ai-worker.md": "delegation",
29
+ "agents/jd-fix-agent.md": "review",
30
+ "agents/jd-judge-a.md": "review",
31
+ "agents/jd-judge-b.md": "review",
32
+ "agents/review-readability.md": "review",
33
+ "agents/review-reliability.md": "review",
34
+ "agents/review-resilience.md": "review",
35
+ "agents/review-risk.md": "review",
36
+ "agents/review-refuter.md": "review",
37
+ "agents/review-validator.md": "review",
38
+ "chains/4r-review.chain.md": "review",
39
+ "gentle-ai/support/strict-tdd.md": "delegation",
40
+ "gentle-ai/support/strict-tdd-verify.md": "delegation",
41
+ } as const);
42
+
43
+ export type PackageAssetOwner = (typeof ASSET_OWNER_BY_KEY)[keyof typeof ASSET_OWNER_BY_KEY];
44
+
45
+ export function getPackageAssetOwner(ownershipKey: string): PackageAssetOwner | undefined {
46
+ return Object.hasOwn(ASSET_OWNER_BY_KEY, ownershipKey)
47
+ ? ASSET_OWNER_BY_KEY[ownershipKey as keyof typeof ASSET_OWNER_BY_KEY]
48
+ : undefined;
49
+ }
50
+
51
+ function gentlePiAgentHome(): string {
52
+ return resolveGentlePiAgentHome();
53
+ }
54
+
55
+
56
+ interface ManagedAssetsManifest {
57
+ schemaVersion: number;
58
+ assets: Record<string, string>;
59
+ }
60
+
61
+ interface LegacyManagedAssetsManifest extends ManagedAssetsManifest {
62
+ packageVersion: string;
63
+ }
64
+
65
+ interface ManagedAssetsLockOwner {
66
+ schemaVersion: 1;
67
+ token: string;
68
+ pid: number;
69
+ createdAtMs: number;
70
+ }
71
+
72
+ /** @internal The hold option exists only to make process-lock regression tests deterministic. */
73
+ interface PackageAssetInstallLockOptions {
74
+ timeoutMs?: number;
75
+ retryMs?: number;
76
+ holdLockMs?: number;
77
+ }
78
+
79
+
80
+ function emptyManagedAssetsManifest(): ManagedAssetsManifest {
81
+ return {
82
+ schemaVersion: MANAGED_ASSETS_SCHEMA_VERSION,
83
+ assets: {},
84
+ };
85
+ }
86
+
87
+ function readManagedAssetsManifest(path: string): ManagedAssetsManifest {
88
+ if (!existsSync(path)) return emptyManagedAssetsManifest();
89
+ try {
90
+ const parsed: unknown = JSON.parse(readFileSync(path, "utf8"));
91
+ if (
92
+ !isRecord(parsed) ||
93
+ parsed.schemaVersion !== MANAGED_ASSETS_SCHEMA_VERSION ||
94
+ !isRecord(parsed.assets)
95
+ ) {
96
+ return emptyManagedAssetsManifest();
97
+ }
98
+ const assets = Object.fromEntries(
99
+ Object.entries(parsed.assets).filter(
100
+ (entry): entry is [string, string] => typeof entry[1] === "string",
101
+ ),
102
+ );
103
+ return { schemaVersion: MANAGED_ASSETS_SCHEMA_VERSION, assets };
104
+ } catch {
105
+ return emptyManagedAssetsManifest();
106
+ }
107
+ }
108
+
109
+ function managedAssetHash(content: string): string {
110
+ return createHash("sha256").update(content).digest("hex");
111
+ }
112
+
113
+ function readManagedAssetsLockOwner(lockPath: string): ManagedAssetsLockOwner | undefined {
114
+ try {
115
+ if (!lstatSync(lockPath).isFile()) return undefined;
116
+ const parsed: unknown = JSON.parse(readFileSync(lockPath, "utf8"));
117
+ if (!isRecord(parsed) || parsed.schemaVersion !== 1 || typeof parsed.token !== "string" || parsed.token.length === 0 || (typeof parsed.pid !== "number" || !Number.isInteger(parsed.pid) || parsed.pid <= 0) || typeof parsed.createdAtMs !== "number" || !Number.isFinite(parsed.createdAtMs)) return undefined;
118
+ return parsed as unknown as ManagedAssetsLockOwner;
119
+ } catch {
120
+ return undefined;
121
+ }
122
+ }
123
+
124
+ function waitForManagedAssetsLock(milliseconds: number): void {
125
+ if (milliseconds <= 0) return;
126
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, milliseconds);
127
+ }
128
+
129
+ function normalizedLockDuration(value: number | undefined, fallback: number): number {
130
+ return typeof value === "number" && Number.isFinite(value) && value >= 0
131
+ ? Math.floor(value)
132
+ : fallback;
133
+ }
134
+
135
+ function acquireManagedAssetsLock(
136
+ agentHome: string,
137
+ options: PackageAssetInstallLockOptions = {},
138
+ ): { path: string; owner: ManagedAssetsLockOwner } {
139
+ const lockParent = join(agentHome, "gentle-ai");
140
+ const lockPath = join(lockParent, MANAGED_ASSETS_LOCK);
141
+ const timeoutMs = normalizedLockDuration(options.timeoutMs, MANAGED_ASSETS_LOCK_TIMEOUT_MS);
142
+ const retryMs = Math.max(1, normalizedLockDuration(options.retryMs, MANAGED_ASSETS_LOCK_RETRY_MS));
143
+ const deadline = Date.now() + timeoutMs;
144
+ mkdirSync(lockParent, { recursive: true });
145
+ for (;;) {
146
+ const owner: ManagedAssetsLockOwner = {
147
+ schemaVersion: 1,
148
+ token: randomUUID(),
149
+ pid: process.pid,
150
+ createdAtMs: Date.now(),
151
+ };
152
+ try {
153
+ writeFileSync(lockPath, JSON.stringify(owner), { encoding: "utf8", flag: "wx" });
154
+ return { path: lockPath, owner };
155
+ } catch (error) {
156
+ if (!isRecord(error) || error.code !== "EEXIST") throw error;
157
+ try {
158
+ if (!lstatSync(lockPath).isFile()) {
159
+ throw new Error(`Managed-assets lock path is unsafe and must be a regular file: ${lockPath}`);
160
+ }
161
+ } catch (inspectionError) {
162
+ if (isRecord(inspectionError) && inspectionError.code === "ENOENT") continue;
163
+ throw inspectionError;
164
+ }
165
+ if (Date.now() >= deadline) {
166
+ throw new Error(`Timed out acquiring managed-assets lock file ${lockPath}. Verify no installer is active before removing this exact lock file.`);
167
+ }
168
+ waitForManagedAssetsLock(retryMs);
169
+ }
170
+ }
171
+ }
172
+
173
+ function releaseManagedAssetsLock(lock: { path: string; owner: ManagedAssetsLockOwner }): void {
174
+ if (readManagedAssetsLockOwner(lock.path)?.token !== lock.owner.token) return;
175
+ try {
176
+ unlinkSync(lock.path);
177
+ } catch {
178
+ // An unreadable or replaced lock remains for an operator to inspect.
179
+ }
180
+ }
181
+
182
+ function withManagedAssetsLock<T>(
183
+ agentHome: string,
184
+ action: () => T,
185
+ options: PackageAssetInstallLockOptions | undefined,
186
+ ): T {
187
+ const lock = acquireManagedAssetsLock(agentHome, options);
188
+ try {
189
+ waitForManagedAssetsLock(normalizedLockDuration(options?.holdLockMs, 0));
190
+ return action();
191
+ } finally {
192
+ releaseManagedAssetsLock(lock);
193
+ }
194
+ }
195
+
196
+ function readLegacyManagedAssets(
197
+ path: string,
198
+ version: string,
199
+ ): LegacyManagedAssetsManifest | undefined {
200
+ try {
201
+ const parsed: unknown = JSON.parse(readFileSync(path, "utf8"));
202
+ if (
203
+ !isRecord(parsed) ||
204
+ parsed.schemaVersion !== MANAGED_ASSETS_SCHEMA_VERSION ||
205
+ parsed.packageVersion !== version ||
206
+ !isRecord(parsed.assets)
207
+ ) {
208
+ return undefined;
209
+ }
210
+ const assets = Object.fromEntries(
211
+ Object.entries(parsed.assets).filter(
212
+ (entry): entry is [string, string] => typeof entry[1] === "string",
213
+ ),
214
+ );
215
+ return {
216
+ schemaVersion: MANAGED_ASSETS_SCHEMA_VERSION,
217
+ packageVersion: version,
218
+ assets,
219
+ };
220
+ } catch {
221
+ return undefined;
222
+ }
223
+ }
224
+
225
+ function readLegacyManagedAssetHashes(): Record<string, readonly string[]> {
226
+ const hashes: Record<string, string[]> = {};
227
+ for (const manifest of LEGACY_MANAGED_ASSET_MANIFESTS) {
228
+ const assets = readLegacyManagedAssets(manifest.path, manifest.version)?.assets;
229
+ if (!assets) continue;
230
+ for (const [ownershipKey, hash] of Object.entries(assets)) {
231
+ const known = hashes[ownershipKey] ?? [];
232
+ if (!known.includes(hash)) known.push(hash);
233
+ hashes[ownershipKey] = known;
234
+ }
235
+ }
236
+ return hashes;
237
+ }
238
+
239
+ function updateAgentFrontmatterRouting(
240
+ content: string,
241
+ routingLines: readonly string[],
242
+ ): string {
243
+ if (!content.startsWith("---\n")) return content;
244
+ const endIndex = content.indexOf("\n---", 4);
245
+ if (endIndex === -1) return content;
246
+ const frontmatter = content.slice(4, endIndex);
247
+ const body = content.slice(endIndex);
248
+ const lines = frontmatter
249
+ .split("\n")
250
+ .filter((line) => !/^(?:model|thinking):/.test(line));
251
+ if (routingLines.length > 0) {
252
+ const descriptionIndex = lines.findIndex((line) =>
253
+ line.startsWith("description:"),
254
+ );
255
+ const insertIndex =
256
+ descriptionIndex >= 0 ? descriptionIndex + 1 : Math.min(1, lines.length);
257
+ lines.splice(insertIndex, 0, ...routingLines);
258
+ }
259
+ return `---\n${lines.join("\n")}${body}`;
260
+ }
261
+
262
+ function legacyComparableAssetContent(
263
+ ownershipKey: string,
264
+ content: string,
265
+ ): string {
266
+ return ownershipKey.startsWith("agents/")
267
+ ? updateAgentFrontmatterRouting(content, [])
268
+ : content;
269
+ }
270
+
271
+ function migrateLegacyAssetContent(
272
+ ownershipKey: string,
273
+ installedContent: string,
274
+ packagedContent: string,
275
+ ): string {
276
+ if (!ownershipKey.startsWith("agents/")) return packagedContent;
277
+ if (!installedContent.startsWith("---\n")) return packagedContent;
278
+ const endIndex = installedContent.indexOf("\n---", 4);
279
+ if (endIndex === -1) return packagedContent;
280
+ const routingLines = installedContent
281
+ .slice(4, endIndex)
282
+ .split("\n")
283
+ .filter((line) => /^(?:model|thinking):/.test(line));
284
+ return updateAgentFrontmatterRouting(packagedContent, routingLines);
285
+ }
286
+
287
+ function replaceManagedAssetFileAtomically(path: string, content: string): void {
288
+ const temporaryPath = join(dirname(path), `.${randomUUID()}.tmp`);
289
+ let mode: number | undefined;
290
+ try {
291
+ const stat = lstatSync(path);
292
+ if (stat.isFile()) mode = stat.mode & 0o777;
293
+ } catch (error) {
294
+ if (!isRecord(error) || error.code !== "ENOENT") throw error;
295
+ }
296
+ try {
297
+ writeFileSync(temporaryPath, content, {
298
+ encoding: "utf8",
299
+ flag: "wx",
300
+ ...(mode === undefined ? {} : { mode }),
301
+ });
302
+ renameSync(temporaryPath, path);
303
+ } finally {
304
+ try {
305
+ unlinkSync(temporaryPath);
306
+ } catch {
307
+ // A renamed or otherwise inaccessible temporary file needs no further action.
308
+ }
309
+ }
310
+ }
311
+
312
+ export function updatePackageManagedSddAgentOwnership(
313
+ installedPath: string,
314
+ previousContent: string,
315
+ nextContent: string,
316
+ lockOptions?: PackageAssetInstallLockOptions,
317
+ ): boolean {
318
+ const agentHome = gentlePiAgentHome();
319
+ const relativePath = relative(join(agentHome, "agents"), installedPath);
320
+ if (
321
+ relativePath.length === 0 ||
322
+ relativePath === ".." ||
323
+ relativePath.startsWith(`..${sep}`) ||
324
+ isAbsolute(relativePath)
325
+ ) {
326
+ return false;
327
+ }
328
+ const ownershipKey = `agents/${relativePath.split(sep).join("/")}`;
329
+ return withManagedAssetsLock(agentHome, () => {
330
+ const manifestPath = join(agentHome, "gentle-ai", MANAGED_ASSETS_MANIFEST);
331
+ const manifest = readManagedAssetsManifest(manifestPath);
332
+ if (manifest.assets[ownershipKey] !== managedAssetHash(previousContent)) {
333
+ return false;
334
+ }
335
+ const installedContent = readFileSync(installedPath, "utf8");
336
+ // The next-content branch preserves the prior internal caller contract:
337
+ // callers that wrote the file before this function still receive a managed
338
+ // manifest update. New callers take the previous-content branch below so
339
+ // the file and manifest update share this lock.
340
+ if (installedContent !== nextContent && installedContent !== previousContent) {
341
+ return false;
342
+ }
343
+ try {
344
+ if (installedContent === previousContent) {
345
+ replaceManagedAssetFileAtomically(installedPath, nextContent);
346
+ }
347
+ manifest.assets[ownershipKey] = managedAssetHash(nextContent);
348
+ replaceManagedAssetFileAtomically(
349
+ manifestPath,
350
+ JSON.stringify(manifest, null, 2),
351
+ );
352
+ } catch (error) {
353
+ try {
354
+ replaceManagedAssetFileAtomically(installedPath, previousContent);
355
+ } catch (rollbackError) {
356
+ throw new Error(
357
+ `Managed routing update failed and could not restore ${installedPath}: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`,
358
+ { cause: error },
359
+ );
360
+ }
361
+ throw error;
362
+ }
363
+ return true;
364
+ }, lockOptions);
365
+ }
366
+
367
+ export function hasPackageAssetOwnerInstallation(owner: PackageAssetOwner): boolean {
368
+ const agentHome = gentlePiAgentHome();
369
+ const manifest = readManagedAssetsManifest(join(agentHome, "gentle-ai", MANAGED_ASSETS_MANIFEST));
370
+ return Object.keys(manifest.assets).some((key) => getPackageAssetOwner(key) === owner) ||
371
+ Object.entries(ASSET_OWNER_BY_KEY).some(([key, candidate]) =>
372
+ candidate === owner && existsSync(join(agentHome, key)),
373
+ );
374
+ }
375
+
376
+ export function isPackageManagedSddAsset(
377
+ installedPath: string,
378
+ ownershipKey: string,
379
+ ): boolean {
380
+ const manifest = readManagedAssetsManifest(
381
+ join(gentlePiAgentHome(), "gentle-ai", MANAGED_ASSETS_MANIFEST),
382
+ );
383
+ const expectedHash = manifest.assets[ownershipKey];
384
+ if (expectedHash === undefined || !existsSync(installedPath)) return false;
385
+ try {
386
+ return (
387
+ managedAssetHash(readFileSync(installedPath, "utf8")) ===
388
+ expectedHash
389
+ );
390
+ } catch {
391
+ return false;
392
+ }
393
+ }
394
+
395
+
396
+ function copyDirectoryFiles(
397
+ sourceDir: string,
398
+ targetDir: string,
399
+ ownershipPrefix: string,
400
+ force: boolean,
401
+ manifest: ManagedAssetsManifest,
402
+ legacyAssetHashes: (() => Readonly<Record<string, readonly string[]>>) | undefined,
403
+ selected?: ReadonlySet<string>,
404
+ ): { copied: number; skipped: number } {
405
+ if (!existsSync(sourceDir)) return { copied: 0, skipped: 0 };
406
+ if (selected && ![...selected].some(key => key.startsWith(`${ownershipPrefix}/`))) {
407
+ return { copied: 0, skipped: 0 };
408
+ }
409
+ mkdirSync(targetDir, { recursive: true });
410
+ let copied = 0;
411
+ let skipped = 0;
412
+ for (const entry of readdirSync(sourceDir, { withFileTypes: true })) {
413
+ const sourcePath = join(sourceDir, entry.name);
414
+ const targetPath = join(targetDir, entry.name);
415
+ const ownershipKey = `${ownershipPrefix}/${entry.name}`;
416
+ if (entry.isDirectory()) {
417
+ const child = copyDirectoryFiles(
418
+ sourcePath,
419
+ targetPath,
420
+ ownershipKey,
421
+ force,
422
+ manifest,
423
+ legacyAssetHashes,
424
+ selected,
425
+ );
426
+ copied += child.copied;
427
+ skipped += child.skipped;
428
+ continue;
429
+ }
430
+ if (!entry.isFile() || (selected && !selected.has(ownershipKey))) continue;
431
+ const source = readFileSync(sourcePath, "utf8");
432
+ let nextSource = source;
433
+ if (existsSync(targetPath)) {
434
+ if (!force) {
435
+ skipped += 1;
436
+ continue;
437
+ }
438
+ const managedHash = manifest.assets[ownershipKey];
439
+ let installedContent: string | undefined;
440
+ try {
441
+ installedContent = readFileSync(targetPath, "utf8");
442
+ } catch {
443
+ installedContent = undefined;
444
+ }
445
+ const installedHash = installedContent === undefined
446
+ ? undefined
447
+ : managedAssetHash(installedContent);
448
+ if (managedHash === undefined) {
449
+ const legacyHashes = legacyAssetHashes?.()[ownershipKey];
450
+ const comparableLegacyHash = installedContent === undefined
451
+ ? undefined
452
+ : managedAssetHash(
453
+ legacyComparableAssetContent(ownershipKey, installedContent),
454
+ );
455
+ if (
456
+ legacyHashes === undefined ||
457
+ comparableLegacyHash === undefined ||
458
+ !legacyHashes.includes(comparableLegacyHash)
459
+ ) {
460
+ delete manifest.assets[ownershipKey];
461
+ skipped += 1;
462
+ continue;
463
+ }
464
+ nextSource = migrateLegacyAssetContent(
465
+ ownershipKey,
466
+ installedContent,
467
+ source,
468
+ );
469
+ } else if (installedHash !== managedHash) {
470
+ delete manifest.assets[ownershipKey];
471
+ skipped += 1;
472
+ continue;
473
+ } else if (ownershipKey === "agents/sdd-research.md" && installedContent !== undefined) {
474
+ // Keep routing adopted by the legacy migration on subsequent refreshes.
475
+ nextSource = migrateLegacyAssetContent(ownershipKey, installedContent, source);
476
+ }
477
+ }
478
+ writeFileSync(targetPath, nextSource);
479
+ manifest.assets[ownershipKey] = managedAssetHash(nextSource);
480
+ copied += 1;
481
+ }
482
+ return { copied, skipped };
483
+ }
484
+
485
+ // Assets retired by gentle-pi#311 P5: the Pi-owned adversarial review actors.
486
+ // The refuter and validator verdicts now execute through Go-owned pi
487
+ // processes via provider-rendered self-contained vectors, so these agent
488
+ // definitions have no runtime consumer. The migration manifests under
489
+ // assets/migrations are append-only legacy-hash HISTORY (adoption evidence
490
+ // for force-installs) with no removal semantics, so history stays untouched
491
+ // and retirement happens here: an installed copy is deleted only when its
492
+ // content hash proves package ownership (current manifest or legacy
493
+ // history); user-modified copies are left in place and only lose managed
494
+ // ownership.
495
+ const RETIRED_MANAGED_ASSETS = Object.freeze([
496
+ // Canonical spec composition now belongs to archive (gentle-pi#1051).
497
+ "agents/sdd-sync.md",
498
+ ...[
499
+ "apply", "archive", "design", "explore", "init", "onboard", "proposal",
500
+ "remediate", "research", "spec", "status", "tasks", "verify",
501
+ ].map(name => `agents/sdd-${name}.md`),
502
+ "chains/sdd-full.chain.md",
503
+ "chains/sdd-plan.chain.md",
504
+ "chains/sdd-verify.chain.md",
505
+ "gentle-ai/support/sdd-status-contract.md",
506
+ "agents/review-refuter.md",
507
+ "agents/review-validator.md",
508
+ ]);
509
+
510
+ function removeRetiredManagedAssets(
511
+ agentHome: string,
512
+ manifest: ManagedAssetsManifest,
513
+ selected?: ReadonlySet<string>,
514
+ ): void {
515
+ let legacyHashes: Record<string, readonly string[]> | undefined;
516
+ for (const ownershipKey of RETIRED_MANAGED_ASSETS) {
517
+ if (selected && !selected.has(ownershipKey) && !(ownershipKey.includes("sdd-") && selected.has("gentle-ai/support/strict-tdd.md"))) continue;
518
+ const installedPath = join(agentHome, ...ownershipKey.split("/"));
519
+ if (!existsSync(installedPath)) {
520
+ delete manifest.assets[ownershipKey];
521
+ continue;
522
+ }
523
+ let installedContent: string | undefined;
524
+ try {
525
+ installedContent = readFileSync(installedPath, "utf8");
526
+ } catch {
527
+ installedContent = undefined;
528
+ }
529
+ if (installedContent === undefined) continue;
530
+ const installedHash = managedAssetHash(installedContent);
531
+ const managed = manifest.assets[ownershipKey] === installedHash;
532
+ const legacy = (legacyHashes ??= readLegacyManagedAssetHashes())[ownershipKey]?.includes(
533
+ managedAssetHash(legacyComparableAssetContent(ownershipKey, installedContent)),
534
+ ) === true;
535
+ if (managed || legacy) {
536
+ try {
537
+ rmSync(installedPath);
538
+ } catch {
539
+ continue;
540
+ }
541
+ }
542
+ // Managed copies are gone; user-modified copies stay but stop being
543
+ // package-managed either way.
544
+ delete manifest.assets[ownershipKey];
545
+ }
546
+ }
547
+
548
+ export function installPackageAssets(
549
+ _cwd: string,
550
+ force: boolean,
551
+ owners?: readonly PackageAssetOwner[],
552
+ lockOptions?: PackageAssetInstallLockOptions,
553
+ ): { agents: number; chains: number; support: number; skipped: number } {
554
+ const agentHome = gentlePiAgentHome();
555
+ return withManagedAssetsLock(agentHome, () => {
556
+ const selected = owners === undefined ? undefined : new Set(
557
+ Object.entries(ASSET_OWNER_BY_KEY).filter(([, owner]) => owners.includes(owner)).map(([key]) => key),
558
+ );
559
+ const manifestPath = join(agentHome, "gentle-ai", MANAGED_ASSETS_MANIFEST);
560
+ let legacyAssetHashes: (() => Readonly<Record<string, readonly string[]>>) | undefined;
561
+ if (force) {
562
+ let cachedLegacyAssetHashes: Record<string, readonly string[]> | undefined;
563
+ legacyAssetHashes = () =>
564
+ (cachedLegacyAssetHashes ??= readLegacyManagedAssetHashes());
565
+ }
566
+ const manifest = readManagedAssetsManifest(manifestPath);
567
+ removeRetiredManagedAssets(agentHome, manifest, selected);
568
+ const agents = copyDirectoryFiles(
569
+ join(ASSETS_DIR, "agents"),
570
+ join(agentHome, "agents"),
571
+ "agents",
572
+ force,
573
+ manifest,
574
+ legacyAssetHashes,
575
+ selected,
576
+ );
577
+ const chains = copyDirectoryFiles(
578
+ join(ASSETS_DIR, "chains"),
579
+ join(agentHome, "chains"),
580
+ "chains",
581
+ force,
582
+ manifest,
583
+ legacyAssetHashes,
584
+ selected,
585
+ );
586
+ const support = copyDirectoryFiles(
587
+ join(ASSETS_DIR, "support"),
588
+ join(agentHome, "gentle-ai", "support"),
589
+ "gentle-ai/support",
590
+ force,
591
+ manifest,
592
+ legacyAssetHashes,
593
+ selected,
594
+ );
595
+ mkdirSync(dirname(manifestPath), { recursive: true });
596
+ writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
597
+ return {
598
+ agents: agents.copied,
599
+ chains: chains.copied,
600
+ support: support.copied,
601
+ skipped: agents.skipped + chains.skipped + support.skipped,
602
+ };
603
+ }, lockOptions);
604
+ }
package/lib/agent-home.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { homedir } from "node:os";
2
2
  import { join } from "node:path";
3
+ import { bundledGentlePiConfigHome, runningBundledPrefix } from "./gentle-shell-bundled.ts";
3
4
 
4
5
  // Pi Subagents resolves its global directory as `PI_CODING_AGENT_DIR || ~/.pi/agent`,
5
6
  // so an empty value must fall through here too or the two homes diverge again.
@@ -11,6 +12,14 @@ export function resolveGentlePiAgentHome(env: NodeJS.ProcessEnv = process.env):
11
12
  // commands own lives here. It is defined once because the launch-time profile pin
12
13
  // resolver reads the same profiles store the `/gentle:profiles` panel writes, and
13
14
  // two spellings of the override would silently read two different stores.
15
+ // A bundled Gentle Shell keeps it inside its prefix (S9, bundledGentlePiConfigHome).
14
16
  export function gentlePiConfigHome(env: NodeJS.ProcessEnv = process.env): string {
15
- return env.GENTLE_PI_CONFIG_HOME || join(homedir(), ".pi", "gentle-ai");
17
+ return bundledGentlePiConfigHome(env) ?? (env.GENTLE_PI_CONFIG_HOME || join(homedir(), ".pi", "gentle-ai"));
18
+ }
19
+
20
+ // The agent home Pi runs with, only in a bundled Gentle Shell (S9): there the
21
+ // extensions that otherwise write under ~/.pi/agent keep their files in it.
22
+ // Undefined elsewhere, where they keep their existing locations.
23
+ export function bundledGentlePiAgentHome(env: NodeJS.ProcessEnv = process.env): string | undefined {
24
+ return runningBundledPrefix(env) === undefined ? undefined : resolveGentlePiAgentHome(env);
16
25
  }
@@ -0,0 +1,80 @@
1
+ import { join, resolve } from "node:path";
2
+ import { resolveGentlePiAgentHome } from "./agent-home.ts";
3
+ import { resolveProfilePin } from "./agent-profile-pin.ts";
4
+ import {
5
+ discoverAgents,
6
+ loadAgentsConfig,
7
+ resolveAgentProfile,
8
+ withPinnedModelProfiles,
9
+ type AgentDefinition,
10
+ type DiscoveryRoots,
11
+ type ModelRef,
12
+ type ResolvedProfile,
13
+ } from "./agents-config.ts";
14
+ import type { WorktreeResolver } from "./session-worktree-registry.ts";
15
+
16
+ // gentle-shell#1731: subagent launches and the harness model-routing fact share
17
+ // one resolution of the effective subagent model, so the fact the orchestrator
18
+ // reads can never disagree with the model a launch would actually use.
19
+
20
+ export interface AgentHomeSelection {
21
+ env: NodeJS.ProcessEnv;
22
+ home: string;
23
+ /** An explicit agent home, kept literal. */
24
+ agentHome?: string;
25
+ /** An explicit home selects `<home>/.pi/agent` and ignores the environment. */
26
+ homeOverridden?: boolean;
27
+ }
28
+
29
+ /** The agent home Gentle Agents discovers definitions and `subagents.json` from. */
30
+ export function resolveAgentHomeDirectory(selection: AgentHomeSelection): string {
31
+ const selectedHome = selection.agentHome ?? (selection.homeOverridden ? join(selection.home, ".pi", "agent") : resolveGentlePiAgentHome(selection.env));
32
+ // Expand environment tildes like Pi, but leave explicit path APIs literal.
33
+ const environmentHome = selection.agentHome === undefined && !selection.homeOverridden;
34
+ const expandedHome = environmentHome && selectedHome === "~" ? selection.home
35
+ : environmentHome && (selectedHome.startsWith("~/") || (process.platform === "win32" && selectedHome.startsWith("~\\"))) ? join(selection.home, selectedHome.slice(2)) : selectedHome;
36
+ return resolve(expandedHome);
37
+ }
38
+
39
+ export interface AgentProfileScope {
40
+ roots: DiscoveryRoots;
41
+ /** The directory whose repository profile pin applies to the launch. */
42
+ pinCwd: string;
43
+ configHome: string;
44
+ resolveWorktree?: WorktreeResolver;
45
+ }
46
+
47
+ /**
48
+ * The launch profile for an agent: global and project `subagents.json`, with a
49
+ * repository profile pin replacing `modelProfiles` wholesale when one applies.
50
+ */
51
+ export function resolvePinnedAgentProfile(agent: AgentDefinition, scope: AgentProfileScope): ResolvedProfile {
52
+ const config = withPinnedModelProfiles(
53
+ loadAgentsConfig(scope.roots),
54
+ resolveProfilePin({ cwd: scope.pinCwd, configHome: scope.configHome, resolveWorktree: scope.resolveWorktree })?.modelProfiles,
55
+ );
56
+ return resolveAgentProfile(agent, config);
57
+ }
58
+
59
+ export interface EffectiveAgentModel {
60
+ agent: string;
61
+ model: ModelRef | undefined;
62
+ /** No profile, definition, or default routes the agent: it inherits the session model. */
63
+ inherited: boolean;
64
+ }
65
+
66
+ /**
67
+ * The model a launch of `agentName` would use, falling back to the session model
68
+ * when nothing routes it; `undefined` when the agent is not discovered.
69
+ */
70
+ export function resolveEffectiveAgentModel(
71
+ agentName: string,
72
+ scope: AgentProfileScope & { fallback?: ModelRef },
73
+ ): EffectiveAgentModel | undefined {
74
+ const agent = discoverAgents(scope.roots).agents.find((candidate) => candidate.name === agentName);
75
+ if (!agent) return undefined;
76
+ const profile = resolvePinnedAgentProfile(agent, scope);
77
+ return profile.model === undefined
78
+ ? { agent: agentName, model: scope.fallback, inherited: true }
79
+ : { agent: agentName, model: profile.model, inherited: false };
80
+ }