gentle-pi 3.7.0 → 4.0.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 (316) hide show
  1. package/README.md +37 -6
  2. package/assets/agents/gentle-ai-explore.md +4 -4
  3. package/assets/agents/gentle-ai-verify.md +6 -4
  4. package/assets/agents/gentle-ai-worker.md +7 -9
  5. package/assets/orchestrator-delegation.md +40 -41
  6. package/assets/orchestrator-memory.md +1 -22
  7. package/assets/orchestrator-skills.md +1 -1
  8. package/assets/orchestrator.md +12 -24
  9. package/assets/support/strict-tdd-verify.md +4 -266
  10. package/assets/support/strict-tdd.md +8 -360
  11. package/bin/gentle-shell.mjs +254 -29
  12. package/docs/delegated-verification.md +26 -1
  13. package/docs/gentle-agents-activity.md +24 -0
  14. package/docs/gentle-shell.md +92 -28
  15. package/docs/native-authority-architecture.md +2 -2
  16. package/docs/prompt-history.md +280 -0
  17. package/docs/readme-reference.md +112 -206
  18. package/docs/telemetry.md +1 -1
  19. package/docs/yolo-mode.md +86 -0
  20. package/extensions/child-context.ts +26 -0
  21. package/extensions/child-safety.ts +23 -0
  22. package/extensions/gentle-agents.ts +429 -262
  23. package/extensions/gentle-ai.ts +781 -683
  24. package/extensions/gentle-shell.ts +1375 -88
  25. package/extensions/gentle-stats.ts +101 -0
  26. package/extensions/gentle-todo.ts +18 -7
  27. package/extensions/history/atomic-write.ts +38 -0
  28. package/extensions/history/hide-prompts.ts +183 -0
  29. package/extensions/history/index.ts +1419 -0
  30. package/extensions/history/load-shared-history.ts +39 -0
  31. package/extensions/history/selector-helpers.ts +538 -0
  32. package/extensions/history/session-scan.ts +233 -0
  33. package/extensions/history/store.ts +1119 -0
  34. package/extensions/nan-provider.ts +6 -0
  35. package/extensions/quiet-tools.ts +179 -87
  36. package/extensions/resume-hint.ts +60 -0
  37. package/extensions/skill-registry.ts +16 -12
  38. package/extensions/startup-banner.ts +60 -31
  39. package/lib/agent-assets.ts +604 -0
  40. package/lib/agent-profile-pin.ts +12 -0
  41. package/lib/agents-message-delivery.ts +181 -0
  42. package/lib/agents-protocol.ts +60 -0
  43. package/lib/agents-runner.ts +96 -98
  44. package/lib/agents-view.ts +26 -3
  45. package/lib/agents-widget.ts +16 -9
  46. package/lib/append-system-prompt.ts +21 -0
  47. package/lib/bounded-writer-admission.ts +147 -0
  48. package/lib/card-style-policy.ts +60 -0
  49. package/lib/child-context-files.ts +166 -0
  50. package/lib/codemode-renderer.ts +185 -0
  51. package/lib/command-palette-catalog.ts +3 -9
  52. package/lib/command-palette.ts +25 -14
  53. package/lib/destructive-command-guard.ts +144 -0
  54. package/lib/gentle-ai-elapsed-store.ts +87 -0
  55. package/lib/gentle-ai-renderer.ts +196 -39
  56. package/lib/gentle-shell-launcher.ts +24 -13
  57. package/lib/gentle-shell-resume-hint.ts +176 -0
  58. package/lib/history-capture-policy.ts +95 -0
  59. package/lib/model-routing-authority.ts +5 -1
  60. package/lib/nan-provider.ts +227 -0
  61. package/lib/native-review-cli.ts +49 -108
  62. package/lib/odd-phase-inference.ts +231 -0
  63. package/lib/odd-phase.ts +141 -0
  64. package/lib/overlay-repaint.ts +26 -0
  65. package/lib/pi-tui-keys.ts +53 -0
  66. package/lib/review-candidate-view-owner.ts +67 -17
  67. package/lib/review-candidate-view.ts +112 -25
  68. package/lib/review-reminder-receipt.ts +48 -8
  69. package/lib/review-risk-assessment.ts +156 -11
  70. package/lib/review-sidebar-state.ts +223 -0
  71. package/lib/selection-engine.ts +515 -0
  72. package/lib/session-messaging-grants.ts +135 -0
  73. package/lib/session-worktree-registry.ts +14 -2
  74. package/lib/shell-bar.ts +179 -24
  75. package/lib/shell-card.ts +287 -18
  76. package/lib/shell-changes-view.ts +2 -1
  77. package/lib/shell-prompt.ts +102 -6
  78. package/lib/shell-sidebar-layout.ts +78 -14
  79. package/lib/shell-sidebar.ts +15 -1
  80. package/lib/shell-todo.ts +23 -13
  81. package/lib/shell-usage-view.ts +9 -4
  82. package/lib/shell-usage.ts +66 -10
  83. package/lib/stats-collector.ts +381 -0
  84. package/lib/stats-view.ts +431 -0
  85. package/lib/theme-customization.ts +52 -0
  86. package/lib/vim-editor-adapter.ts +379 -0
  87. package/lib/vim-normal-engine.ts +154 -0
  88. package/lib/vim-operator-engine.ts +416 -0
  89. package/lib/vim-policy.ts +49 -0
  90. package/lib/vim-visual-engine.ts +107 -0
  91. package/lib/visual-customization-policy.ts +108 -0
  92. package/lib/visual-customize-view.ts +330 -0
  93. package/lib/visual-profiles.ts +228 -0
  94. package/lib/yolo-session-policy.ts +240 -0
  95. package/package.json +20 -8
  96. package/runtime/gentle-shell-launcher.mjs +23 -12
  97. package/runtime/gentle-shell-resume-hint.mjs +177 -0
  98. package/runtime/native-review-cli.mjs +49 -108
  99. package/runtime/review-risk-assessment.mjs +154 -9
  100. package/scripts/build-runtime-modules.mjs +1 -0
  101. package/scripts/gentle-ai-installer.mjs +14 -13
  102. package/scripts/mirror-odd-routing.mjs +2 -2
  103. package/scripts/run-test-suite.mjs +76 -0
  104. package/scripts/test-packed-runner.mjs +31 -14
  105. package/scripts/verify-package-files.mjs +11 -22
  106. package/skills/branch-pr/SKILL.md +24 -52
  107. package/skills/chained-pr/SKILL.md +31 -15
  108. package/skills/chained-pr/references/chaining-details.md +31 -20
  109. package/skills/gentle-ai/SKILL.md +9 -15
  110. package/skills/issue-creation/SKILL.md +8 -2
  111. package/skills/issue-creation/references/delegated-workflow-actions.md +19 -0
  112. package/skills/work-unit-commits/SKILL.md +4 -3
  113. package/tests/agent-profiles.test.ts +18 -0
  114. package/tests/agents-fake-child.ts +2 -2
  115. package/tests/agents-message-delivery.test.ts +106 -0
  116. package/tests/agents-protocol.test.ts +40 -0
  117. package/tests/agents-runner.test.ts +378 -89
  118. package/tests/agents-view-thread-identity.test.ts +169 -0
  119. package/tests/agents-view.test.ts +8 -2
  120. package/tests/agents-widget.test.ts +154 -15
  121. package/tests/append-system-prompt-route.test.ts +160 -0
  122. package/tests/append-system-prompt.test.ts +46 -0
  123. package/tests/artifact-language.test.ts +19 -213
  124. package/tests/ask-user-question.test.ts +44 -1
  125. package/tests/asset-installation-runtime.test.ts +5 -16
  126. package/tests/autonomous-guard.test.ts +69 -1
  127. package/tests/bounded-writer-admission.test.ts +95 -0
  128. package/tests/branch-pr-skill.test.ts +43 -0
  129. package/tests/card-style-policy.test.ts +55 -0
  130. package/tests/chained-pr-skill.test.ts +124 -0
  131. package/tests/child-context-files.test.ts +255 -0
  132. package/tests/child-safety.test.ts +82 -0
  133. package/tests/codemode-rendering.test.ts +491 -0
  134. package/tests/command-palette.test.ts +39 -3
  135. package/tests/delegated-key-learnings-contract.test.ts +0 -76
  136. package/tests/destructive-command-guard.test.ts +84 -0
  137. package/tests/devbinary/native-review-parity.devtest.ts +170 -2
  138. package/tests/devbinary/non-git-subagent-bootstrap.devtest.ts +193 -0
  139. package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-28T10-00-00-000Z_aaa.jsonl +7 -0
  140. package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-29T23-00-00-000Z_bbb.jsonl +3 -0
  141. package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-30T08-00-00-000Z_ddd.jsonl +2 -0
  142. package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-30T09-00-00-000Z_eee.jsonl +3 -0
  143. package/tests/fixtures/stats/sessions/--work-alpha--/run-1/session.jsonl +2 -0
  144. package/tests/fixtures/stats/sessions/--work-beta--/2026-09-01T12-00-00-000Z_ccc.jsonl +2 -0
  145. package/tests/fixtures/stats/user-pi/sessions/--work-alpha--/2026-09-28T10-00-00-000Z_aaa.jsonl +2 -0
  146. package/tests/fixtures/stats/user-pi/sessions/--work-alpha--/2026-09-29T23-00-00-000Z_bbb.jsonl +4 -0
  147. package/tests/fixtures/stats/user-pi/sessions/--work-gamma--/2026-09-20T09-00-00-000Z_fff.jsonl +3 -0
  148. package/tests/generic-agent-tools.test.ts +54 -0
  149. package/tests/gentle-agents.test.ts +1047 -252
  150. package/tests/gentle-ai-binary.test.ts +3 -3
  151. package/tests/gentle-ai-elapsed-store.test.ts +68 -0
  152. package/tests/gentle-ai-installer.test.ts +68 -54
  153. package/tests/gentle-ai-renderer.test.ts +487 -8
  154. package/tests/gentle-ai.test.ts +288 -65
  155. package/tests/gentle-card-text.ts +2 -1
  156. package/tests/gentle-shell-bin.test.ts +651 -114
  157. package/tests/gentle-shell-launcher.test.ts +99 -52
  158. package/tests/gentle-shell-resume-hint.test.ts +270 -0
  159. package/tests/gentle-shell.test.ts +3839 -187
  160. package/tests/gentle-stats.test.ts +152 -0
  161. package/tests/gentle-theme.test.ts +4 -1
  162. package/tests/gentle-todo.test.ts +80 -8
  163. package/tests/history-atomic-write.test.ts +57 -0
  164. package/tests/history-capture-policy.test.ts +102 -0
  165. package/tests/history-command-registration.test.ts +164 -0
  166. package/tests/history-dedupe-entries.test.ts +123 -0
  167. package/tests/history-delete-backfill.test.ts +190 -0
  168. package/tests/history-delete-confirm.test.ts +460 -0
  169. package/tests/history-dispatch.test.ts +180 -0
  170. package/tests/history-drain-hidden.test.ts +110 -0
  171. package/tests/history-drain-order.test.ts +98 -0
  172. package/tests/history-expanded-globals.test.ts +62 -0
  173. package/tests/history-gc.test.ts +832 -0
  174. package/tests/history-header-layout.test.ts +265 -0
  175. package/tests/history-hide-prompts.test.ts +275 -0
  176. package/tests/history-lazy-windowing.test.ts +508 -0
  177. package/tests/history-legacy-migrate-v2.test.ts +297 -0
  178. package/tests/history-load-shared-history.test.ts +53 -0
  179. package/tests/history-max-results-cap.test.ts +76 -0
  180. package/tests/history-multi-reader.test.ts +203 -0
  181. package/tests/history-off-path.test.ts +170 -0
  182. package/tests/history-openflow-integration.test.ts +173 -0
  183. package/tests/history-overlay-margin.test.ts +326 -0
  184. package/tests/history-preview-layout.test.ts +93 -0
  185. package/tests/history-registry.test.ts +143 -0
  186. package/tests/history-scope-delete.test.ts +411 -0
  187. package/tests/history-search-caret-keys.test.ts +142 -0
  188. package/tests/history-seed-bootstrap.test.ts +170 -0
  189. package/tests/history-seed-regen.test.ts +129 -0
  190. package/tests/history-selector-windowing.test.ts +94 -0
  191. package/tests/history-session-scan-directory.test.ts +87 -0
  192. package/tests/history-session-scan-extract.test.ts +583 -0
  193. package/tests/history-session-writer.test.ts +351 -0
  194. package/tests/history-store-paths.test.ts +79 -0
  195. package/tests/history-tombstone-exact.test.ts +139 -0
  196. package/tests/history-wheel-mouse.test.ts +242 -0
  197. package/tests/inprocess-reviewer.test.ts +29 -19
  198. package/tests/issue-creation-skill.test.ts +61 -0
  199. package/tests/model-routing-authority.test.ts +16 -0
  200. package/tests/nan-provider.test.ts +471 -0
  201. package/tests/native-review-capability-contract.test.ts +7 -1
  202. package/tests/native-review-cli.test.ts +6 -120
  203. package/tests/native-review-parity-runtime.test.ts +100 -3
  204. package/tests/odd-integration.test.ts +67 -0
  205. package/tests/odd-phase-inference.test.ts +213 -0
  206. package/tests/odd-phase-loader.test.ts +253 -0
  207. package/tests/odd-phase.test.ts +307 -0
  208. package/tests/odd-routing-canonical-ratchet.test.ts +11 -6
  209. package/tests/odd-routing-contract.test.ts +86 -35
  210. package/tests/orchestrator-budget.test.ts +14 -39
  211. package/tests/orchestrator-rdd-ownership.test.ts +3 -3
  212. package/tests/overlay-repaint.test.ts +74 -0
  213. package/tests/package-manifest.test.ts +252 -115
  214. package/tests/packed-runner-owned-path.test.ts +46 -0
  215. package/tests/persona-single-channel.test.ts +6 -6
  216. package/tests/provider-defect-handoff.test.ts +3 -11
  217. package/tests/quiet-bash-runtime.test.ts +76 -0
  218. package/tests/quiet-tool-rendering.test.ts +409 -184
  219. package/tests/rdd-aware-verification-contract.test.ts +76 -1
  220. package/tests/rdd-status-line.test.ts +9 -4
  221. package/tests/resume-hint-extension.test.ts +122 -0
  222. package/tests/review-agent-end-preflight.test.ts +176 -12
  223. package/tests/review-candidate-owner-retry.test.ts +22 -1
  224. package/tests/review-candidate-view.test.ts +298 -0
  225. package/tests/review-contract-prompt.test.ts +108 -43
  226. package/tests/review-controller-lock-status.test.ts +0 -1
  227. package/tests/review-controller-native-routing.test.ts +611 -5
  228. package/tests/review-controller-workspace-root.test.ts +163 -4
  229. package/tests/review-host-relay-routing.test.ts +338 -2
  230. package/tests/review-integration-v2-forward.test.ts +200 -0
  231. package/tests/review-ledger-contract.test.ts +10 -34
  232. package/tests/review-reminder-receipt.test.ts +47 -1
  233. package/tests/review-risk-assessment.test.ts +498 -6
  234. package/tests/review-sidebar-state.test.ts +402 -0
  235. package/tests/run-test-suite.test.ts +124 -0
  236. package/tests/runtime-harness.mjs +145 -786
  237. package/tests/runtime-metrics-children.test.ts +16 -23
  238. package/tests/selection-engine.test.ts +421 -0
  239. package/tests/session-messaging-grants.test.ts +255 -0
  240. package/tests/session-worktree-registry.test.ts +77 -0
  241. package/tests/shell-bar.test.ts +382 -1
  242. package/tests/shell-card.test.ts +353 -1
  243. package/tests/shell-changes-view.test.ts +52 -0
  244. package/tests/shell-prompt.test.ts +94 -2
  245. package/tests/shell-sidebar-layout.test.ts +325 -21
  246. package/tests/shell-sidebar-scroll-benchmark.test.ts +255 -0
  247. package/tests/shell-todo.test.ts +87 -1
  248. package/tests/shell-usage-view.test.ts +27 -0
  249. package/tests/shell-usage.test.ts +73 -0
  250. package/tests/skill-registry.test.ts +50 -1
  251. package/tests/startup-banner.test.ts +130 -2
  252. package/tests/stats-collector.test.ts +195 -0
  253. package/tests/stats-view.test.ts +202 -0
  254. package/tests/telemetry-trigger.test.ts +81 -20
  255. package/tests/theme-customization.test.ts +72 -0
  256. package/tests/vim-editor-adapter-host-resolution.test.ts +37 -0
  257. package/tests/vim-editor-adapter.test.ts +804 -0
  258. package/tests/vim-normal-engine.test.ts +101 -0
  259. package/tests/vim-operator-engine.test.ts +215 -0
  260. package/tests/vim-policy.test.ts +19 -0
  261. package/tests/vim-visual-engine.test.ts +52 -0
  262. package/tests/visual-customization-policy.test.ts +110 -0
  263. package/tests/visual-customize-view.test.ts +418 -0
  264. package/tests/visual-profiles.test.ts +87 -0
  265. package/tests/yolo-customize.test.ts +256 -0
  266. package/tests/yolo-mode-runtime.test.ts +161 -0
  267. package/tests/yolo-mode.test.ts +261 -0
  268. package/tests/yolo-session-policy.test.ts +59 -0
  269. package/themes/Gentle.json +2 -1
  270. package/themes/Gentleman-Cute.json +2 -1
  271. package/themes/Gentleman-Sexy.json +2 -1
  272. package/assets/agents/sdd-apply.md +0 -159
  273. package/assets/agents/sdd-archive.md +0 -228
  274. package/assets/agents/sdd-design.md +0 -49
  275. package/assets/agents/sdd-explore.md +0 -48
  276. package/assets/agents/sdd-init.md +0 -56
  277. package/assets/agents/sdd-onboard.md +0 -52
  278. package/assets/agents/sdd-proposal.md +0 -64
  279. package/assets/agents/sdd-remediate.md +0 -37
  280. package/assets/agents/sdd-research.md +0 -49
  281. package/assets/agents/sdd-spec.md +0 -192
  282. package/assets/agents/sdd-status.md +0 -54
  283. package/assets/agents/sdd-tasks.md +0 -108
  284. package/assets/agents/sdd-verify.md +0 -124
  285. package/assets/chains/sdd-full.chain.md +0 -83
  286. package/assets/chains/sdd-plan.chain.md +0 -56
  287. package/assets/chains/sdd-verify.chain.md +0 -43
  288. package/assets/sdd-orchestrator-workflow.md +0 -319
  289. package/assets/support/sdd-status-contract.md +0 -77
  290. package/docs/assets/diagrams/sdd-cycle.svg +0 -14
  291. package/extensions/sdd-init.ts +0 -816
  292. package/lib/openspec-deltas.ts +0 -156
  293. package/lib/sdd-preflight.ts +0 -1066
  294. package/lib/sdd-research-capabilities.ts +0 -94
  295. package/lib/sdd-status.ts +0 -26
  296. package/tests/fixtures/legacy/sdd-research-v2.5.0.md +0 -54
  297. package/tests/fixtures/native-review-cli/v2.1.3/bind-sdd.json +0 -25
  298. package/tests/fixtures/v0.10.7/assets/agents/sdd-apply.md +0 -132
  299. package/tests/openspec-deltas.test.ts +0 -209
  300. package/tests/sdd-agent-tools.test.ts +0 -156
  301. package/tests/sdd-archive-replay.test.ts +0 -82
  302. package/tests/sdd-classical-continuation.test.ts +0 -74
  303. package/tests/sdd-execution-routing-contract.test.ts +0 -44
  304. package/tests/sdd-managed-runtime-settlement.test.ts +0 -155
  305. package/tests/sdd-native-managed-uptake.test.ts +0 -243
  306. package/tests/sdd-no-attempts-contract.test.ts +0 -15
  307. package/tests/sdd-odd-integration.test.ts +0 -33
  308. package/tests/sdd-optional-research.test.ts +0 -124
  309. package/tests/sdd-planning-routing-contract.test.ts +0 -45
  310. package/tests/sdd-preflight-rpc-input.test.ts +0 -125
  311. package/tests/sdd-preflight.test.ts +0 -541
  312. package/tests/sdd-research-capabilities.test.ts +0 -114
  313. package/tests/sdd-research-live.test.ts +0 -241
  314. package/tests/sdd-selection-transport.test.ts +0 -653
  315. package/tests/sdd-status.test.ts +0 -9
  316. package/tests/sdd-task-truth.test.ts +0 -43
@@ -68,8 +68,6 @@ export const NATIVE_REVIEW_OPERATION = {
68
68
  CAPTURE_PROVIDER_ROLE: "review/capture-provider-role",
69
69
  CAPTURE_UNACHIEVABLE: "review/capture-unachievable",
70
70
  ACKNOWLEDGE_APPROVED: "review/acknowledge-approved",
71
- SDD_STATUS: "sdd-status",
72
- SDD_CONTINUE: "sdd-continue",
73
71
  } ;
74
72
 
75
73
 
@@ -124,11 +122,6 @@ export const NATIVE_REVIEW_ERROR_CODE = {
124
122
 
125
123
 
126
124
 
127
-
128
-
129
-
130
-
131
-
132
125
  export const NATIVE_REVIEW_MODE_OPERATION = {
133
126
  STATUS: "status",
134
127
  ENABLE: "enable",
@@ -168,35 +161,6 @@ export const NATIVE_REVIEW_MODE_SCOPE = {
168
161
 
169
162
 
170
163
 
171
-
172
-
173
-
174
-
175
- /**
176
- * The native CLI owns this complete v2 record. The decoder validates the fields
177
- * Pi relies on and returns the original object without adding, omitting, or
178
- * reconciling local SDD state.
179
- */
180
-
181
-
182
-
183
-
184
-
185
-
186
-
187
-
188
-
189
-
190
-
191
-
192
-
193
-
194
-
195
-
196
-
197
-
198
-
199
-
200
164
 
201
165
  // Read-only risk assessment request (gentle-pi#662). `baseRef` requires
202
166
  // explicit `committedOnly` acknowledgement, exactly like Native START's
@@ -748,6 +712,21 @@ function nativeUntrackedSelectionArguments(selection )
748
712
  ];
749
713
  }
750
714
 
715
+ // Fail closed on malformed or duplicate provider selectors before invoking STATUS.
716
+ function providerFlagValue(tokens , flag ) {
717
+ let found ;
718
+ for (let index = 0; index < tokens.length; index++) {
719
+ const token = tokens[index] ;
720
+ if (token !== `--${flag}` && !token.startsWith(`--${flag}=`)) continue;
721
+ if (found !== undefined) throw new TypeError(`Native intended-untracked selection repeats --${flag}`);
722
+ const value = token === `--${flag}` ? tokens[index + 1] : token.slice(`--${flag}=`.length);
723
+ if (value === undefined || value === "" || value.startsWith("--")) throw new TypeError(`Native intended-untracked selection has an invalid --${flag} value`);
724
+ found = value;
725
+ if (token === `--${flag}`) index++;
726
+ }
727
+ return found;
728
+ }
729
+
751
730
  const NATIVE_RISK_LEVEL = ["low", "medium", "high"] ;
752
731
 
753
732
  // gentle-ai's negotiated `start/v2` envelope is a closed schema
@@ -1049,6 +1028,13 @@ export const NATIVE_CLI_CONTRACTS = Object.freeze({
1049
1028
  // 547b68e172cc87aa297309d61624e5fc2c24d407a494b53eeb5a2b053904352c
1050
1029
  // at contract 1.2.0. No new negotiated capability is asserted.
1051
1030
  "3.7.0": Object.freeze({ start: true, finalize: true, validate: true, bindSdd: true, status: true, inventory: true, reclaim: true, recover: true, abandon: true, quarantineLegacy: true, reconcileAuthority: true, repairLegacyAlias: true, mode: true, riskEvidence: false, hint: false, delivery: true }),
1031
+ // v4.0.0 repeats 3.7.0: the published provider-contract tar remains SHA-256
1032
+ // 547b68e172cc87aa297309d61624e5fc2c24d407a494b53eeb5a2b053904352c
1033
+ // at contract 1.2.0, and the published binary still advertises
1034
+ // capabilities/v2.6 under review-integration/v2. The Go module path moved
1035
+ // to /v4 without a review-integration/v2 change. No new negotiated
1036
+ // capability is asserted.
1037
+ "4.0.0": Object.freeze({ start: true, finalize: true, validate: true, bindSdd: true, status: true, inventory: true, reclaim: true, recover: true, abandon: true, quarantineLegacy: true, reconcileAuthority: true, repairLegacyAlias: true, mode: true, riskEvidence: false, hint: false, delivery: true }),
1052
1038
  });
1053
1039
 
1054
1040
 
@@ -1516,52 +1502,6 @@ function nativeError(code , operation
1516
1502
 
1517
1503
 
1518
1504
 
1519
- const NATIVE_SDD_DEPENDENCIES = ["proposal", "specs", "design", "tasks", "apply", "verify", "archive"] ;
1520
- const NATIVE_SDD_INSTRUCTION_PHASES = ["apply", "verify", "archive"] ;
1521
- const NATIVE_SDD_NEXT_RECOMMENDATIONS = ["apply", "verify", "remediate", "archive", "archived", "resolve-blockers", "sdd-new", "select-change", "propose", "spec", "design", "tasks"] ;
1522
- const NATIVE_SDD_DEPENDENCY_STATES = ["blocked", "ready", "all_done"] ;
1523
-
1524
- /** Strictly validates the native v2 contract while preserving its whole record. */
1525
- export function decodeNativeSddStatusV2(value , request ) {
1526
- const status = object(value);
1527
- if (status.schemaName !== "gentle-ai.sdd-status" || status.schemaVersion !== 2) throw new Error("wrong native SDD status schema");
1528
- if ((request.changeName !== undefined && status.changeName !== request.changeName) || (status.changeName !== null && !isCanonicalProcessString(status.changeName))) throw new Error("native SDD status change identity mismatch");
1529
- const artifactStore = enumString(status.artifactStore, ["openspec", "engram", "hybrid", "none"]);
1530
- const planningHome = object(status.planningHome);
1531
- if (planningHome.mode !== "repo-local" || !isCanonicalProcessString(planningHome.path)) throw new Error("invalid native SDD planning home");
1532
- const expectedOpenSpecHome = join(request.workspaceRoot, "openspec");
1533
- if (planningHome.path !== expectedOpenSpecHome && !((artifactStore === "engram" || artifactStore === "hybrid") && planningHome.path === "engram:sdd")) throw new Error("native SDD planning home escaped its workspace");
1534
- if (status.changeRoot !== null && !isCanonicalProcessString(status.changeRoot)) throw new Error("invalid native SDD change root");
1535
- const actionContext = object(status.actionContext);
1536
- if (actionContext.mode !== "repo-local" || actionContext.workspaceRoot !== request.workspaceRoot || !isCanonicalProcessString(actionContext.workspaceRoot)) throw new Error("native SDD status workspace root mismatch");
1537
- const allowedEditRoots = stringArray(actionContext.allowedEditRoots);
1538
- if (!allowedEditRoots.includes(request.workspaceRoot) || allowedEditRoots.some((root) => !isAbsolute(root) || root !== join(root))) throw new Error("invalid native SDD allowed edit roots");
1539
- const dependencies = object(status.dependencies);
1540
- for (const phase of NATIVE_SDD_DEPENDENCIES) {
1541
- if (enumString(dependencies[phase], NATIVE_SDD_DEPENDENCY_STATES) !== dependencies[phase]) throw new Error("invalid native SDD dependency");
1542
- }
1543
- if (Object.keys(dependencies).length !== NATIVE_SDD_DEPENDENCIES.length) throw new Error("native SDD dependencies have an unsupported shape");
1544
- if (status.instructions !== undefined) throw new Error("native SDD status uses phaseInstructions, not instructions");
1545
- if (status.phaseInstructions !== undefined) {
1546
- const instructions = object(status.phaseInstructions);
1547
- for (const phase of NATIVE_SDD_INSTRUCTION_PHASES) stringArray(instructions[phase]);
1548
- // Classical SDD no longer emits a remediation phase. Keep the published
1549
- // producer's optional legacy instructions intact without inventing them
1550
- // for a newer producer or accepting unknown phase keys.
1551
- const hasRemediation = Object.hasOwn(instructions, "remediate");
1552
- if (hasRemediation) stringArray(instructions.remediate);
1553
- if (Object.keys(instructions).length !== NATIVE_SDD_INSTRUCTION_PHASES.length + Number(hasRemediation)) throw new Error("native SDD instructions have an unsupported shape");
1554
- if (status.nextRecommended === "remediate" && !hasRemediation) throw new Error("native SDD remediation instructions are missing");
1555
- }
1556
- if (status.nextRecommended === "remediate" || status.remediationState !== undefined) {
1557
- const remediation = object(status.remediationState);
1558
- if (typeof remediation.required !== "boolean" || typeof remediation.complete !== "boolean" || typeof remediation.failedEvidenceRevision !== "string" || (remediation.failedEvidenceRevision !== "" && !/^sha256:[0-9a-f]{64}$/.test(remediation.failedEvidenceRevision)) || (status.nextRecommended === "remediate" && (!remediation.required || remediation.complete || !remediation.failedEvidenceRevision))) throw new Error("Invalid native remediation state");
1559
- }
1560
- stringArray(status.blockedReasons);
1561
- enumString(status.nextRecommended, NATIVE_SDD_NEXT_RECOMMENDATIONS);
1562
- return status ;
1563
- }
1564
-
1565
1505
  class NativeReviewPlainCli {
1566
1506
  adapter ;
1567
1507
  executable ;
@@ -2200,30 +2140,6 @@ export class NativeReviewCliV216 {
2200
2140
  return this.invoke(operation, cwd, arguments_, mutating, signal, this.executablePath(operation, mutating), toleratedStderr);
2201
2141
  }
2202
2142
 
2203
- async sddStatus(request ) {
2204
- return this.sddProjection(request, false);
2205
- }
2206
-
2207
- async sddContinue(request ) {
2208
- if (!isCanonicalProcessString(request.changeName)) throw new TypeError("Native SDD continuation requires an exact selected change");
2209
- return this.sddProjection(request, true);
2210
- }
2211
-
2212
- async sddProjection(request , mutating ) {
2213
- if ((request.changeName !== undefined && !isCanonicalProcessString(request.changeName)) || !isCanonicalProcessString(request.workspaceRoot) || !isAbsolute(request.workspaceRoot)) {
2214
- throw new TypeError("Native SDD status requires a canonical change and absolute workspace root");
2215
- }
2216
- const operation = mutating ? NATIVE_REVIEW_OPERATION.SDD_CONTINUE : NATIVE_REVIEW_OPERATION.SDD_STATUS;
2217
- const execution = await this.negotiated(
2218
- operation,
2219
- request.workspaceRoot,
2220
- [operation, ...(request.changeName === undefined ? [] : [request.changeName]), "--cwd", request.workspaceRoot, "--json", "--instructions"],
2221
- mutating,
2222
- request.signal,
2223
- );
2224
- return decode(operation, mutating, () => decodeNativeSddStatusV2(execution.body, request));
2225
- }
2226
-
2227
2143
  async start(request ) {
2228
2144
  if (request.baseRef !== undefined && !isCanonicalProcessString(request.baseRef)) throw new TypeError("Native START baseRef must be a non-empty, trimmed, NUL-free string");
2229
2145
  if (request.baseRef !== undefined && request.committedOnly !== true) throw new TypeError("Native START baseRef requires explicit committedOnly acknowledgement");
@@ -2346,7 +2262,32 @@ export class NativeReviewCliV216 {
2346
2262
  const selection = nativeUntrackedSelection(request);
2347
2263
  const submitted = request.intendedUntrackedSelection;
2348
2264
  if (submitted !== undefined && submitted.argumentTokens.reduce((count, token) => count + token.split("{{value}}").length - 1, 0) !== 1) throw new TypeError("Native intended-untracked selection requires exactly one provider-issued {{value}} token");
2349
- const statusArguments = submitted === undefined ? [
2265
+ const submittedTokens = submitted === undefined ? undefined : submitted.argumentTokens.map((token) => token.replaceAll("{{value}}", submitted.value));
2266
+ // gentle-ai 3.7.1 submission argument_tokens never carry --base-ref /
2267
+ // --committed-only / --lineage, so a forwarded selector must be appended
2268
+ // after them; only bail if the provider already staked out a conflicting
2269
+ // value for the same flag. providerFlagValue also fails closed on a
2270
+ // dangling `--flag` (last token, no value) or an empty `--flag=`.
2271
+ const submittedBaseRefValue = submittedTokens === undefined ? undefined : providerFlagValue(submittedTokens, "base-ref");
2272
+ if (request.baseRef !== undefined && submittedBaseRefValue !== undefined && submittedBaseRefValue !== request.baseRef) throw new TypeError("Native intended-untracked selection already carries a conflicting base-ref");
2273
+ const submittedLineageValue = submittedTokens === undefined ? undefined : providerFlagValue(submittedTokens, "lineage");
2274
+ if (request.lineageId !== undefined && submittedLineageValue !== undefined && submittedLineageValue !== request.lineageId) throw new TypeError("Native intended-untracked selection already carries a conflicting lineage");
2275
+ let submittedHasCommittedOnly = false;
2276
+ for (const token of submittedTokens ?? []) {
2277
+ if (token !== "--committed-only" && !token.startsWith("--committed-only=")) continue;
2278
+ if (token !== "--committed-only" && token !== "--committed-only=true") throw new TypeError("Native intended-untracked selection has an invalid --committed-only value");
2279
+ if (submittedHasCommittedOnly) throw new TypeError("Native intended-untracked selection repeats --committed-only");
2280
+ submittedHasCommittedOnly = true;
2281
+ }
2282
+ // Append only the half of the base-ref/committed-only pair the provider
2283
+ // did not already carry (a present base-ref already equals
2284
+ // request.baseRef; the throw above ruled out the conflicting case).
2285
+ const forwardedBaseRef = request.baseRef === undefined ? [] : [
2286
+ ...(submittedBaseRefValue === undefined ? ["--base-ref", request.baseRef] : []),
2287
+ ...(submittedHasCommittedOnly ? [] : ["--committed-only"]),
2288
+ ];
2289
+ const forwardedLineage = request.lineageId === undefined || submittedLineageValue !== undefined ? [] : ["--lineage", request.lineageId];
2290
+ const statusArguments = submittedTokens === undefined ? [
2350
2291
  "review", "status", "--contract", REVIEW_INTEGRATION_CONTRACT, "--cwd", request.cwd,
2351
2292
  "--projection", request.projection ?? "workspace",
2352
2293
  ...nativeUntrackedSelectionArguments(selection),
@@ -2354,7 +2295,7 @@ export class NativeReviewCliV216 {
2354
2295
  ...(request.lineageId === undefined ? [] : ["--lineage", request.lineageId]),
2355
2296
  ...(request.agent === undefined ? [] : ["--agent", request.agent]),
2356
2297
  "--next-transition",
2357
- ] : ["review", "status", "--cwd", request.cwd, ...submitted.argumentTokens.map((token) => token.replaceAll("{{value}}", submitted.value))];
2298
+ ] : ["review", "status", "--cwd", request.cwd, ...submittedTokens, ...forwardedBaseRef, ...forwardedLineage];
2358
2299
  const execution = await this.negotiated(NATIVE_REVIEW_OPERATION.STATUS, request.cwd, statusArguments, false, request.signal);
2359
2300
  assertSupportedNextTransitionOperation(execution.body);
2360
2301
  return decode(NATIVE_REVIEW_OPERATION.STATUS, false, () => decodeReviewStatusV3(execution.body));
@@ -11,6 +11,11 @@
11
11
  // prints `{"schema":"gentle-ai.review-assessment/v1","risk":"passive|medium|high",
12
12
  // "reasons":[{"code":"…","path":"…","detail":"…"}],"changed_paths":1,
13
13
  // "changed_lines":1,"candidate":{"kind":"current-changes|base-diff","base_ref":"…"}}`.
14
+ // gentle-pi#1175: the native v2 `assess.schema.json` requires only `code` on a
15
+ // reason (`path`/`detail` are optional) and adds `candidate.consumed`,
16
+ // `review_due`, `review_due_reason`, and an opaque `next_transition`. Older
17
+ // binaries (for example gentle-ai v3.7.0) predate those fields, so
18
+ // they decode as optional and stay absent rather than being defaulted.
14
19
  // A non-zero exit or a failure envelope means the candidate could not be
15
20
  // assessed; hosts treat that as `high`. Older binaries without the verb (or
16
21
  // any other decode/process failure) fail closed the same way -- see
@@ -31,6 +36,41 @@ export const REVIEW_ASSESSMENT_CANDIDATE_KIND = {
31
36
  } ;
32
37
 
33
38
 
39
+ export const REVIEW_DUE_REASON = {
40
+ HIGH_RISK: "high_risk",
41
+ SLICE_BUDGET_REACHED: "slice_budget_reached",
42
+ PASSIVE: "passive",
43
+ UNDER_BUDGET: "under_budget",
44
+ ALREADY_REVIEWED: "already_reviewed",
45
+ } ;
46
+
47
+
48
+ export const REVIEW_ASSESSMENT_NEXT_TRANSITION_OPERATION = "review.status" ;
49
+
50
+
51
+
52
+
53
+
54
+
55
+
56
+
57
+
58
+
59
+
60
+
61
+
62
+
63
+
64
+
65
+
66
+
67
+
68
+
69
+ /**
70
+ * The native, literally runnable `review status ... --next-transition`
71
+ * preflight continuation. Carried verbatim as an opaque value: hosts never
72
+ * rebuild, reorder, or synthesize it.
73
+ */
34
74
 
35
75
 
36
76
 
@@ -55,25 +95,99 @@ function isRecord(value ) {
55
95
  return typeof value === "object" && value !== null && !Array.isArray(value);
56
96
  }
57
97
 
98
+ function isNonEmptyString(value ) {
99
+ return typeof value === "string" && value.length > 0;
100
+ }
101
+
58
102
  function decodeReason(value ) {
59
103
  if (!isRecord(value)) throw new TypeError("review assessment reason must be an object");
60
104
  const { code, path, detail } = value;
61
- if (typeof code !== "string" || code.length === 0) throw new TypeError("review assessment reason.code must be a non-empty string");
62
- if (typeof path !== "string") throw new TypeError("review assessment reason.path must be a string");
63
- if (typeof detail !== "string") throw new TypeError("review assessment reason.detail must be a string");
64
- return Object.freeze({ code, path, detail });
105
+ if (!isNonEmptyString(code)) throw new TypeError("review assessment reason.code must be a non-empty string");
106
+ if (path !== undefined && !isNonEmptyString(path)) throw new TypeError("review assessment reason.path must be a non-empty string when present");
107
+ if (detail !== undefined && !isNonEmptyString(detail)) throw new TypeError("review assessment reason.detail must be a non-empty string when present");
108
+ // Absent path/detail stay absent: never synthesized (gentle-pi#1175).
109
+ return Object.freeze({
110
+ code,
111
+ ...(path === undefined ? {} : { path: path }),
112
+ ...(detail === undefined ? {} : { detail: detail }),
113
+ });
65
114
  }
66
115
 
67
116
  function decodeCandidate(value ) {
68
117
  if (!isRecord(value)) throw new TypeError("review assessment candidate must be an object");
69
- const { kind, base_ref: baseRef } = value;
118
+ const { kind, base_ref: baseRef, consumed } = value;
70
119
  if (kind !== REVIEW_ASSESSMENT_CANDIDATE_KIND.CURRENT_CHANGES && kind !== REVIEW_ASSESSMENT_CANDIDATE_KIND.BASE_DIFF) {
71
120
  throw new TypeError("review assessment candidate.kind must be current-changes or base-diff");
72
121
  }
73
- if (baseRef !== undefined && (typeof baseRef !== "string" || baseRef.length === 0)) {
122
+ if (baseRef !== undefined && !isNonEmptyString(baseRef)) {
74
123
  throw new TypeError("review assessment candidate.base_ref must be a non-empty string when present");
75
124
  }
76
- return Object.freeze({ kind, baseRef: baseRef });
125
+ if (consumed !== undefined && typeof consumed !== "boolean") {
126
+ throw new TypeError("review assessment candidate.consumed must be a boolean when present");
127
+ }
128
+ // An older binary omits consumed; it stays absent, never defaulted.
129
+ return Object.freeze({ kind, baseRef: baseRef , ...(consumed === undefined ? {} : { consumed: consumed }) });
130
+ }
131
+
132
+ function isReviewDueReason(value ) {
133
+ return Object.values(REVIEW_DUE_REASON).includes(value );
134
+ }
135
+
136
+ // The native schema fixes review_due by reason: due for high_risk and
137
+ // slice_budget_reached, not due for passive, under_budget, and
138
+ // already_reviewed (gentle-pi#1175).
139
+ const DUE_REVIEW_REASONS = [REVIEW_DUE_REASON.HIGH_RISK, REVIEW_DUE_REASON.SLICE_BUDGET_REACHED];
140
+
141
+ /**
142
+ * Rejects a review_due/review_due_reason/consumed combination the native
143
+ * schema can never produce. `already_reviewed` takes precedence exactly when
144
+ * the candidate is consumed, so the two must agree in both directions. A
145
+ * contradictory envelope is never trusted, least of all as closure evidence.
146
+ * Envelopes without the pair (older binaries) are not checked here.
147
+ */
148
+ function validateReviewDueConsistency(reviewDue , reviewDueReason , candidate ) {
149
+ if (reviewDue !== DUE_REVIEW_REASONS.includes(reviewDueReason)) {
150
+ throw new TypeError(`review assessment review_due ${reviewDue} contradicts review_due_reason ${reviewDueReason}`);
151
+ }
152
+ const alreadyReviewed = reviewDueReason === REVIEW_DUE_REASON.ALREADY_REVIEWED;
153
+ if (alreadyReviewed !== (candidate.consumed === true)) {
154
+ throw new TypeError("review assessment review_due_reason already_reviewed must be reported exactly when candidate.consumed is true");
155
+ }
156
+ }
157
+
158
+ function hasOnlyKeys(value , allowed ) {
159
+ return Object.keys(value).every((key) => allowed.includes(key));
160
+ }
161
+
162
+ function validateNextTransitionArgument(value ) {
163
+ if (!isRecord(value) || !hasOnlyKeys(value, ["name", "value", "token"])) {
164
+ throw new TypeError("review assessment next_transition argument must be an object with only name, value, and token");
165
+ }
166
+ if (!isNonEmptyString(value.name)) throw new TypeError("review assessment next_transition argument name must be a non-empty string");
167
+ if (typeof value.value !== "string") throw new TypeError("review assessment next_transition argument value must be a string");
168
+ if (value.token !== undefined && !isNonEmptyString(value.token)) {
169
+ throw new TypeError("review assessment next_transition argument token must be a non-empty string when present");
170
+ }
171
+ }
172
+
173
+ /**
174
+ * Validates `next_transition` exactly as the native schema declares it and
175
+ * returns the native value itself, frozen -- validated, never rebuilt,
176
+ * reordered, or synthesized (gentle-pi#1175).
177
+ */
178
+ function decodeNextTransition(value ) {
179
+ if (!isRecord(value) || !hasOnlyKeys(value, ["operation", "command", "arguments"])) {
180
+ throw new TypeError("review assessment next_transition must be an object with only operation, command, and arguments");
181
+ }
182
+ if (value.operation !== REVIEW_ASSESSMENT_NEXT_TRANSITION_OPERATION) {
183
+ throw new TypeError(`review assessment next_transition.operation must be ${REVIEW_ASSESSMENT_NEXT_TRANSITION_OPERATION}`);
184
+ }
185
+ if (!isNonEmptyString(value.command)) throw new TypeError("review assessment next_transition.command must be a non-empty string");
186
+ if (!Array.isArray(value.arguments)) throw new TypeError("review assessment next_transition.arguments must be an array");
187
+ for (const argument of value.arguments) validateNextTransitionArgument(argument);
188
+ for (const argument of value.arguments) Object.freeze(argument);
189
+ Object.freeze(value.arguments);
190
+ return Object.freeze(value) ;
77
191
  }
78
192
 
79
193
  /**
@@ -82,24 +196,55 @@ function decodeCandidate(value ) {
82
196
  * native review CLI wrapper turns a thrown error here into the same
83
197
  * schema-incompatible failure it already produces for every other decoded
84
198
  * native response.
199
+ *
200
+ * `review_due` and `review_due_reason` were added together natively: both
201
+ * absent (an older binary) or both present are accepted; only one of them is
202
+ * rejected. When present, the pair must agree with each other and with
203
+ * `candidate.consumed` (see `validateReviewDueConsistency`).
204
+ * `next_transition` is accepted only alongside `review_due: true`.
205
+ * Unknown extra top-level fields are ignored and never projected, so a newer
206
+ * binary stays decodable (gentle-pi#1175).
85
207
  */
86
208
  export function decodeReviewAssessmentV1(value ) {
87
209
  if (!isRecord(value)) throw new TypeError("review assessment must be an object");
88
210
  if (value.schema !== REVIEW_ASSESSMENT_SCHEMA) throw new TypeError(`review assessment schema must be ${REVIEW_ASSESSMENT_SCHEMA}`);
89
- const { risk, reasons, changed_paths: changedPaths, changed_lines: changedLines, candidate } = value;
211
+ const {
212
+ risk,
213
+ reasons,
214
+ changed_paths: changedPaths,
215
+ changed_lines: changedLines,
216
+ candidate,
217
+ review_due: reviewDue,
218
+ review_due_reason: reviewDueReason,
219
+ next_transition: nextTransition,
220
+ } = value;
90
221
  if (risk !== REVIEW_ASSESSMENT_RISK.PASSIVE && risk !== REVIEW_ASSESSMENT_RISK.MEDIUM && risk !== REVIEW_ASSESSMENT_RISK.HIGH) {
91
222
  throw new TypeError("review assessment risk must be passive, medium, or high");
92
223
  }
93
224
  if (!Array.isArray(reasons)) throw new TypeError("review assessment reasons must be an array");
94
225
  if (!Number.isSafeInteger(changedPaths) || (changedPaths ) < 0) throw new TypeError("review assessment changed_paths must be a non-negative integer");
95
226
  if (!Number.isSafeInteger(changedLines) || (changedLines ) < 0) throw new TypeError("review assessment changed_lines must be a non-negative integer");
227
+ if ((reviewDue === undefined) !== (reviewDueReason === undefined)) {
228
+ throw new TypeError("review assessment review_due and review_due_reason must be both present or both absent");
229
+ }
230
+ if (reviewDue !== undefined && typeof reviewDue !== "boolean") throw new TypeError("review assessment review_due must be a boolean when present");
231
+ if (reviewDueReason !== undefined && !isReviewDueReason(reviewDueReason)) {
232
+ throw new TypeError(`review assessment review_due_reason must be one of ${Object.values(REVIEW_DUE_REASON).join(", ")}`);
233
+ }
234
+ if (nextTransition !== undefined && reviewDue !== true) {
235
+ throw new TypeError("review assessment next_transition is only valid when review_due is true");
236
+ }
237
+ const decodedCandidate = decodeCandidate(candidate);
238
+ if (reviewDue !== undefined) validateReviewDueConsistency(reviewDue , reviewDueReason , decodedCandidate);
96
239
  return Object.freeze({
97
240
  schema: REVIEW_ASSESSMENT_SCHEMA,
98
241
  risk,
99
242
  reasons: Object.freeze(reasons.map(decodeReason)),
100
243
  changedPaths: changedPaths ,
101
244
  changedLines: changedLines ,
102
- candidate: decodeCandidate(candidate),
245
+ candidate: decodedCandidate,
246
+ ...(reviewDue === undefined ? {} : { reviewDue: reviewDue , reviewDueReason: reviewDueReason }),
247
+ ...(nextTransition === undefined ? {} : { nextTransition: decodeNextTransition(nextTransition) }),
103
248
  });
104
249
  }
105
250
 
@@ -14,6 +14,7 @@ const sources = [
14
14
  "native-review-cli",
15
15
  "telemetry-trigger",
16
16
  "gentle-shell-launcher",
17
+ "gentle-shell-resume-hint",
17
18
  ];
18
19
  const header = "// Generated by scripts/build-runtime-modules.mjs. Do not edit.\n";
19
20
 
@@ -36,19 +36,20 @@ const WINDOWS_SYSTEM_ROOT = "C:\\Windows";
36
36
  // version check below) derives from this constant instead of repeating the
37
37
  // literal, so a pin bump cannot leave a stale copy behind. See
38
38
  // scripts/install-gentle-ai.mjs for the incident that motivated this.
39
- export const INSTALLER_VERSION = "3.7.0";
39
+ export const INSTALLER_VERSION = "4.0.0";
40
40
  export const RELEASE_BASE_URL = `https://github.com/Gentleman-Programming/gentle-ai/releases/download/v${INSTALLER_VERSION}/`;
41
41
  export const GENTLE_AI_INSTALL_METHOD = Object.freeze({
42
42
  SIGNED_RELEASE_ASSET: "signed-release-asset",
43
43
  GO_SUMDB_SOURCE_BUILD: "go-sumdb-source-build",
44
44
  });
45
- export const GENTLE_AI_WINDOWS_SOURCE_PACKAGE_PATH = "github.com/gentleman-programming/gentle-ai/v3/cmd/gentle-ai";
46
- export const GENTLE_AI_WINDOWS_SOURCE_MODULE = "github.com/gentleman-programming/gentle-ai/v3";
45
+ export const GENTLE_AI_WINDOWS_SOURCE_PACKAGE_PATH = "github.com/gentleman-programming/gentle-ai/v4/cmd/gentle-ai";
46
+ export const GENTLE_AI_WINDOWS_SOURCE_MODULE = "github.com/gentleman-programming/gentle-ai/v4";
47
47
  export const GENTLE_AI_WINDOWS_SOURCE_TAG = `v${INSTALLER_VERSION}`;
48
- // `go mod download -json github.com/gentleman-programming/gentle-ai/v3@v3.7.0`
48
+ // `go mod download -json github.com/gentleman-programming/gentle-ai/v4@v4.0.0`
49
49
  // with GOSUMDB=sum.golang.org reports this exact module SumDB checksum, and the
50
- // tag resolves to commit 6dee8f833aec9e46015759c5065a9035795d9af1, the published v3.7.0 release head.
51
- export const GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM = "h1:MQbzHlLdPklUQn0rVE9Mz94UygHsN2OPe7xMfPn9aGw=";
50
+ // tag resolves to commit ff77164d4f56f1665b22fb6fac51c2ccbb769400, the published v4.0.0 release head.
51
+ // v4.0.0 moved the Go module path to the /v4 major-version suffix.
52
+ export const GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM = "h1:pZ/XZ2Pk3U9lgXigOTY62zlxxFOHnc9CjQhLgaV/Hfc=";
52
53
  export const GENTLE_AI_WINDOWS_SOURCE_PACKAGE = `${GENTLE_AI_WINDOWS_SOURCE_PACKAGE_PATH}@${GENTLE_AI_WINDOWS_SOURCE_TAG}`;
53
54
  export const GENTLE_AI_WINDOWS_MINIMUM_GO_VERSION = "1.25.10";
54
55
  export const GENTLE_AI_GO_TOOLCHAIN_UNAVAILABLE_CODE = "GENTLE_AI_GO_TOOLCHAIN_UNAVAILABLE";
@@ -67,7 +68,7 @@ export class GentleAiInstallerError extends Error {
67
68
  // Sentinel used while a re-pinned gentle-ai release is not yet published. A
68
69
  // sentinel digest can never match a real SHA-256, so installation fails closed,
69
70
  // and verify-package-files.mjs refuses to pack/publish while any digest below
70
- // still holds it. The v3.7.0 digests are pinned from the published release:
71
+ // still holds it. The v4.0.0 digests are pinned from the published release:
71
72
  // archive sha256 values verified against the minisign-signed checksums.txt and
72
73
  // freshly computed hashes; binary sha256 values computed from the extracted
73
74
  // executables.
@@ -109,15 +110,15 @@ async function downloadPinnedGentleAiAsset(asset, destination, options) {
109
110
  }
110
111
 
111
112
  // Windows is absent from signed release archives on purpose. gentle-ai stopped
112
- // distributing unsigned Windows builds in c4b764d0, so v3.7.0 publishes signed
113
+ // distributing unsigned Windows builds in c4b764d0, so v4.0.0 publishes signed
113
114
  // Darwin/Linux archives only. Windows x64/arm64 uses the separately verified
114
115
  // exact-tag Go SumDB source-build path below; restore archive rows only when
115
116
  // upstream ships signed Windows assets.
116
117
  export const GENTLE_AI_RELEASE_ASSETS = Object.freeze({
117
- "darwin/amd64": asset("gentle-ai_3.7.0_darwin_amd64.tar.gz", "e55ff3ee06258a90e9195c0e3a593b85f6d79cc439e9e6c04b2596725120a610", "aa30a940e3b75ac218249b3f92d17afd2b972f833258e8212175a73d8e76d5bf", "gentle-ai"),
118
- "darwin/arm64": asset("gentle-ai_3.7.0_darwin_arm64.tar.gz", "66eb5740c4506cb2cfd1cc5207439c61cfe07678854f4ac7c83027a95a0e666a", "ca726cf3f9a523dc0112aa113beb634cda389e8b47edc5851254c32979bee89e", "gentle-ai"),
119
- "linux/amd64": asset("gentle-ai_3.7.0_linux_amd64.tar.gz", "a730a61a43758f04cc9a4ac644945cc0e8652a1e33d6997a0a3d3f0044d2fff5", "002d09fd2b9628a29986a660c1f51f8a5042ff7fd54c8ab15b7b27de96c6cccc", "gentle-ai"),
120
- "linux/arm64": asset("gentle-ai_3.7.0_linux_arm64.tar.gz", "a3a3d3a974f3d9b67d935fe9e306ae83c305da4ec1baed4a5319c10b044cd0eb", "e29546c51d8d65528bf565239ed3fc174da73c5fa3e861e64f20f84b16f28f26", "gentle-ai"),
118
+ "darwin/amd64": asset("gentle-ai_4.0.0_darwin_amd64.tar.gz", "b5b74f22b38ec3339b38e8c68f797dc76ff12ed6580826a6e425d5c718da80c1", "d4a5b16ff70e65331e17a62356941bb0c75ecb9dbe3a0d98a6b54cfbd76cd6b0", "gentle-ai"),
119
+ "darwin/arm64": asset("gentle-ai_4.0.0_darwin_arm64.tar.gz", "d2159caf6d68f367b18830ece6af71ef26963d5f5320d7df6a794773f45cc7e9", "18a9f7fae55d85c95684b6d512a4a148d0cb24a856325f72573c34caf65159eb", "gentle-ai"),
120
+ "linux/amd64": asset("gentle-ai_4.0.0_linux_amd64.tar.gz", "5f4417cf29c969c86da4799942fd673368840901be1bb09c779a12d7ed6096ea", "50ba217b5138c1a9c7d5bf2f79931b1bb89b89c4cf650dcd7ee037657c88158d", "gentle-ai"),
121
+ "linux/arm64": asset("gentle-ai_4.0.0_linux_arm64.tar.gz", "1383b040c95cfc69206660d73c21907b14ad70ab913f410917c54f43d3147e57", "6703704f0c4a5b70c36fbdc44db641e810871cab16bc28040d06aa1d10704ad3", "gentle-ai"),
121
122
  });
122
123
 
123
124
  // A pinned asset is either a signed archive or, for a prerelease pin only,
@@ -402,7 +403,7 @@ async function safeRemoveDirectory(path) {
402
403
  try {
403
404
  const details = await lstat(path);
404
405
  if (!details.isDirectory() || details.isSymbolicLink()) throw new Error("not a real directory");
405
- await rm(path, { recursive: true, force: true });
406
+ await rm(path, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
406
407
  } catch (error) { if (!(error && typeof error === "object" && error.code === "ENOENT")) throw error; }
407
408
  }
408
409
 
@@ -38,8 +38,8 @@ import (
38
38
  "fmt"
39
39
  "os"
40
40
 
41
- "github.com/gentleman-programming/gentle-ai/v3/internal/components/agentguidance"
42
- "github.com/gentleman-programming/gentle-ai/v3/internal/model"
41
+ "github.com/gentleman-programming/gentle-ai/v4/internal/components/agentguidance"
42
+ "github.com/gentleman-programming/gentle-ai/v4/internal/model"
43
43
  )
44
44
 
45
45
  func main() {
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env node
2
+ // Sequential test-suite runner for `pnpm test` (gentle-pi#1285).
3
+ //
4
+ // `pnpm test` used to chain its three stages with `&&`, so any stage-1
5
+ // failure — including a known-flaky unit test — silently suppressed the
6
+ // provider-contract check and the runtime harness while reporting a plain
7
+ // "test failed". This runner executes every stage unconditionally, prints a
8
+ // per-stage header and a final summary, and exits non-zero when any stage
9
+ // fails. Stages are plain shell commands, so behavior stays identical on
10
+ // POSIX and Windows CI.
11
+
12
+ import { spawn } from "node:child_process";
13
+ import { readFileSync, realpathSync } from "node:fs";
14
+ import { fileURLToPath } from "node:url";
15
+
16
+ export const DEFAULT_STAGES = Object.freeze([
17
+ { name: "unit-tests", command: "node --experimental-strip-types --test tests/*.test.ts" },
18
+ { name: "provider-contract", command: "pnpm run check:provider-contract" },
19
+ { name: "runtime-harness", command: "pnpm run test:harness" },
20
+ ]);
21
+
22
+ export async function runStage(stage) {
23
+ return await new Promise((resolve) => {
24
+ const child = spawn(stage.command, { shell: true, stdio: "inherit" });
25
+ child.on("close", (code) => resolve({ name: stage.name, code: code ?? 1 }));
26
+ child.on("error", (error) => {
27
+ console.error(`\n[${stage.name}] spawn failed: ${error.message}`);
28
+ resolve({ name: stage.name, code: 1 });
29
+ });
30
+ });
31
+ }
32
+
33
+ export async function runTestSuite(stages = DEFAULT_STAGES, { runStageImpl = runStage, write = (line) => console.log(line) } = {}) {
34
+ const results = [];
35
+ for (const stage of stages) {
36
+ write(`\n=== [${stage.name}] ${stage.command} ===`);
37
+ results.push(await runStageImpl(stage));
38
+ }
39
+ write("\n=== test suite summary ===");
40
+ for (const result of results) write(`${result.code === 0 ? "PASS" : "FAIL"} ${result.name}`);
41
+ const failures = results.filter((result) => result.code !== 0);
42
+ write(failures.length === 0 ? "all stages passed" : `${failures.length} stage(s) failed: ${failures.map((f) => f.name).join(", ")}`);
43
+ return results;
44
+ }
45
+
46
+ function readStagesFromJsonPath(jsonPath) {
47
+ const stages = JSON.parse(readFileSync(jsonPath, "utf8"));
48
+ if (!Array.isArray(stages) || stages.some((stage) => typeof stage?.name !== "string" || typeof stage?.command !== "string")) {
49
+ throw new Error(`stages file must be an array of { name, command } objects: ${jsonPath}`);
50
+ }
51
+ return stages;
52
+ }
53
+
54
+ // Direct-invocation detection must survive symlinks: Node real-paths
55
+ // `import.meta.url` but keeps `process.argv[1]` logical, so a plain equality
56
+ // check silently no-ops the whole runner (zero stages run, exit 0) when the
57
+ // script is reached through a file symlink.
58
+ function isDirectRun(argv1) {
59
+ if (!argv1) return false;
60
+ try {
61
+ return realpathSync(argv1) === realpathSync(fileURLToPath(import.meta.url));
62
+ } catch {
63
+ return false;
64
+ }
65
+ }
66
+
67
+ if (isDirectRun(process.argv[1])) {
68
+ try {
69
+ const stages = process.argv[2] ? readStagesFromJsonPath(process.argv[2]) : DEFAULT_STAGES;
70
+ const results = await runTestSuite(stages);
71
+ process.exitCode = results.some((result) => result.code !== 0) ? 1 : 0;
72
+ } catch (error) {
73
+ console.error(error instanceof Error ? error.message : error);
74
+ process.exitCode = 1;
75
+ }
76
+ }