gentle-pi 2.4.0 → 2.6.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 (227) hide show
  1. package/README.md +292 -25
  2. package/assets/agents/gentle-ai-worker.md +13 -0
  3. package/assets/agents/jd-fix-agent.md +18 -0
  4. package/assets/agents/jd-judge-a.md +1 -1
  5. package/assets/agents/jd-judge-b.md +1 -1
  6. package/assets/agents/sdd-apply.md +7 -5
  7. package/assets/agents/sdd-archive.md +5 -3
  8. package/assets/agents/sdd-design.md +4 -0
  9. package/assets/agents/sdd-explore.md +4 -0
  10. package/assets/agents/sdd-init.md +4 -0
  11. package/assets/agents/sdd-onboard.md +4 -0
  12. package/assets/agents/sdd-proposal.md +4 -0
  13. package/assets/agents/sdd-remediate.md +37 -0
  14. package/assets/agents/sdd-research.md +26 -3
  15. package/assets/agents/sdd-spec.md +4 -0
  16. package/assets/agents/sdd-status.md +9 -75
  17. package/assets/agents/sdd-sync.md +4 -0
  18. package/assets/agents/sdd-tasks.md +4 -0
  19. package/assets/agents/sdd-verify.md +5 -3
  20. package/assets/chains/sdd-full.chain.md +4 -0
  21. package/assets/chains/sdd-plan.chain.md +4 -0
  22. package/assets/chains/sdd-verify.chain.md +4 -0
  23. package/assets/migrations/managed-assets-v2.5.0.json +7 -0
  24. package/assets/orchestrator-delegation.md +39 -11
  25. package/assets/orchestrator.md +5 -5
  26. package/assets/sdd-orchestrator-workflow.md +54 -21
  27. package/assets/support/sdd-status-contract.md +34 -90
  28. package/contracts/telemetry/runtime-aggregate-v1.schema.json +65 -0
  29. package/docs/delegated-verification.md +25 -0
  30. package/docs/telemetry.md +94 -0
  31. package/docs/windows-startup-console-visibility.md +18 -0
  32. package/extensions/ask-user-choice.ts +159 -25
  33. package/extensions/codegraph-tools.ts +95 -5
  34. package/extensions/gentle-agents.ts +1337 -0
  35. package/extensions/gentle-ai.ts +2916 -386
  36. package/extensions/gentle-shell.ts +650 -0
  37. package/extensions/gentle-todo.ts +234 -0
  38. package/extensions/quiet-tools.ts +2 -1
  39. package/extensions/runtime-metrics.ts +130 -0
  40. package/extensions/sdd-init.ts +2 -2
  41. package/extensions/startup-banner.ts +52 -75
  42. package/lib/agent-profiles.ts +550 -0
  43. package/lib/agents-completion-delivery.ts +72 -0
  44. package/lib/agents-config.ts +315 -0
  45. package/lib/agents-history.ts +88 -0
  46. package/lib/agents-messaging.ts +187 -0
  47. package/lib/agents-protocol.ts +501 -0
  48. package/lib/agents-runner.ts +1012 -0
  49. package/lib/agents-thread-view.ts +57 -0
  50. package/lib/agents-transcript.ts +87 -0
  51. package/lib/agents-view-layout.ts +40 -0
  52. package/lib/agents-view.ts +914 -0
  53. package/lib/agents-widget.ts +241 -0
  54. package/lib/gentle-ai-binary.ts +3 -1
  55. package/lib/gentle-ai-renderer.ts +143 -25
  56. package/lib/native-choice-list.ts +194 -0
  57. package/lib/native-fullscreen-interaction.ts +47 -0
  58. package/lib/native-pointer-region.ts +164 -0
  59. package/lib/native-review-cli.ts +371 -13
  60. package/lib/orchestrator-presence.ts +337 -0
  61. package/lib/profiles-orchestrator.ts +203 -0
  62. package/lib/review-candidate-view-owner.ts +427 -0
  63. package/lib/review-candidate-view.ts +150 -48
  64. package/lib/review-consent-component.ts +247 -0
  65. package/lib/review-consent-ui.ts +110 -0
  66. package/lib/review-host-relay.ts +28 -0
  67. package/lib/review-integration-v2.ts +243 -11
  68. package/lib/review-last-event-controller.ts +8 -4
  69. package/lib/review-relay-contract.ts +11 -0
  70. package/lib/review-reminder-receipt.ts +74 -0
  71. package/lib/review-repository.ts +2 -2
  72. package/lib/review-risk-assessment.ts +339 -0
  73. package/lib/review-session-standing-permission-ipc.ts +309 -0
  74. package/lib/review-session-standing-permission.ts +240 -0
  75. package/lib/runtime-metrics-children.ts +199 -0
  76. package/lib/runtime-metrics-delivery.ts +68 -0
  77. package/lib/runtime-metrics-native.ts +166 -0
  78. package/lib/runtime-metrics-pi-identity.ts +113 -0
  79. package/lib/runtime-metrics-policy.ts +51 -0
  80. package/lib/runtime-metrics.ts +255 -0
  81. package/lib/sdd-preflight.ts +362 -81
  82. package/lib/sdd-research-capabilities.ts +228 -0
  83. package/lib/sdd-status.ts +29 -7
  84. package/lib/session-worktree-registry.ts +118 -0
  85. package/lib/shell-bar.ts +184 -0
  86. package/lib/shell-card.ts +133 -0
  87. package/lib/shell-changes-view.ts +530 -0
  88. package/lib/shell-changes.ts +290 -0
  89. package/lib/shell-gauge.ts +40 -0
  90. package/lib/shell-prompt.ts +115 -0
  91. package/lib/shell-sidebar-banner.ts +11 -0
  92. package/lib/shell-sidebar-layout.ts +213 -0
  93. package/lib/shell-sidebar.ts +41 -0
  94. package/lib/shell-todo.ts +297 -0
  95. package/lib/shell-usage-view.ts +76 -0
  96. package/lib/shell-usage.ts +246 -0
  97. package/lib/telemetry-trigger.ts +153 -0
  98. package/package.json +8 -5
  99. package/runtime/gentle-ai-binary.mjs +3 -1
  100. package/runtime/native-review-cli.mjs +370 -12
  101. package/runtime/review-integration-v2.mjs +243 -11
  102. package/runtime/review-relay-contract.mjs +11 -0
  103. package/runtime/review-risk-assessment.mjs +340 -0
  104. package/runtime/telemetry-trigger.mjs +154 -0
  105. package/scripts/build-runtime-modules.mjs +11 -1
  106. package/scripts/check-types.mjs +125 -0
  107. package/scripts/gentle-ai-installer.mjs +10 -10
  108. package/scripts/install-gentle-ai.mjs +12 -0
  109. package/scripts/install-tui-mode-setting.mjs +114 -0
  110. package/scripts/test-packed-runner.mjs +38 -2
  111. package/scripts/types-baseline.json +99 -0
  112. package/scripts/verify-package-files.mjs +8 -2
  113. package/skills/_shared/review-ledger-contract.md +20 -2
  114. package/skills/issue-creation/SKILL.md +3 -3
  115. package/skills/judgment-day/SKILL.md +17 -3
  116. package/skills/judgment-day/references/prompts-and-formats.md +14 -3
  117. package/tests/agent-profiles.test.ts +722 -0
  118. package/tests/agents-completion-delivery.test.ts +94 -0
  119. package/tests/agents-config.test.ts +205 -0
  120. package/tests/agents-fake-child.ts +66 -0
  121. package/tests/agents-grouping.test.ts +179 -0
  122. package/tests/agents-history.test.ts +54 -0
  123. package/tests/agents-integration.test.ts +100 -0
  124. package/tests/agents-messaging.test.ts +94 -0
  125. package/tests/agents-protocol.test.ts +198 -0
  126. package/tests/agents-queries.test.ts +190 -0
  127. package/tests/agents-responsive.test.ts +43 -0
  128. package/tests/agents-runner-process.test.ts +111 -0
  129. package/tests/agents-runner.test.ts +959 -0
  130. package/tests/agents-thread-view.test.ts +45 -0
  131. package/tests/agents-transcript.test.ts +30 -0
  132. package/tests/agents-view.test.ts +685 -0
  133. package/tests/agents-widget.test.ts +141 -0
  134. package/tests/artifact-language.test.ts +25 -2
  135. package/tests/ask-user-choice.test.ts +325 -5
  136. package/tests/asset-installation-runtime.test.ts +108 -0
  137. package/tests/autonomous-guard.test.ts +116 -1
  138. package/tests/codegraph-tools.test.ts +112 -2
  139. package/tests/delegated-key-learnings-contract.test.ts +1 -1
  140. package/tests/devbinary/native-review-parity.devtest.ts +110 -0
  141. package/tests/feature-request-form.test.ts +67 -0
  142. package/tests/fixtures/agents-messaging-child.mjs +5 -0
  143. package/tests/fixtures/agents-process-child.mjs +23 -0
  144. package/tests/fixtures/runtime-metrics-native-batches.json +6 -0
  145. package/tests/gentle-agents.test.ts +2168 -0
  146. package/tests/gentle-ai-binary.test.ts +7 -2
  147. package/tests/gentle-ai-installer.test.ts +47 -47
  148. package/tests/gentle-ai-renderer.test.ts +103 -0
  149. package/tests/gentle-ai.test.ts +971 -15
  150. package/tests/gentle-card-text.ts +35 -0
  151. package/tests/gentle-shell.test.ts +818 -0
  152. package/tests/gentle-todo.test.ts +226 -0
  153. package/tests/install-tui-mode-setting.test.ts +324 -0
  154. package/tests/issue-creation-skill.test.ts +22 -0
  155. package/tests/model-routing-authority.test.ts +12 -0
  156. package/tests/native-choice-list.test.ts +202 -0
  157. package/tests/native-fullscreen-interaction.test.ts +125 -0
  158. package/tests/native-pointer-region.test.ts +245 -0
  159. package/tests/native-review-capability-contract.test.ts +27 -1
  160. package/tests/native-review-cli.test.ts +317 -3
  161. package/tests/native-review-consent.test.ts +91 -0
  162. package/tests/native-review-parity-runtime.test.ts +8 -2
  163. package/tests/native-review-parity.test.ts +43 -29
  164. package/tests/native-sdd-attempt-authority.test.ts +7 -2
  165. package/tests/orchestrator-budget.test.ts +69 -0
  166. package/tests/orchestrator-presence.test.ts +389 -0
  167. package/tests/orchestrator-rdd-ownership.test.ts +9 -0
  168. package/tests/package-manifest.test.ts +243 -7
  169. package/tests/profiles-orchestrator.test.ts +208 -0
  170. package/tests/quiet-tool-rendering.test.ts +97 -37
  171. package/tests/rdd-aware-verification-contract.test.ts +226 -0
  172. package/tests/rdd-status-line.test.ts +286 -0
  173. package/tests/review-agent-end-preflight.test.ts +332 -24
  174. package/tests/review-candidate-view.test.ts +751 -7
  175. package/tests/review-consent-ui.test.ts +352 -0
  176. package/tests/review-contract-prompt.test.ts +17 -0
  177. package/tests/review-controller-native-recovery.test.ts +29 -4
  178. package/tests/review-controller-native-routing.test.ts +884 -7
  179. package/tests/review-controller-workspace-root.test.ts +45 -2
  180. package/tests/review-controller.test.ts +26 -1
  181. package/tests/review-host-relay-restart-parity.test.ts +142 -1
  182. package/tests/review-host-relay-routing.test.ts +384 -8
  183. package/tests/review-host-relay.test.ts +29 -0
  184. package/tests/review-integration-v2-forward.test.ts +44 -0
  185. package/tests/review-integration-v2.test.ts +276 -0
  186. package/tests/review-last-event-closure.test.ts +112 -3
  187. package/tests/review-ledger-contract.test.ts +61 -6
  188. package/tests/review-relay-contract.test.ts +26 -0
  189. package/tests/review-reminder-receipt.test.ts +62 -0
  190. package/tests/review-repository.test.ts +28 -1
  191. package/tests/review-risk-assessment.test.ts +626 -0
  192. package/tests/review-session-standing-permission-controller.test.ts +656 -0
  193. package/tests/review-session-standing-permission-ipc.test.ts +233 -0
  194. package/tests/review-session-standing-permission-runtime.test.ts +212 -0
  195. package/tests/review-session-standing-permission.test.ts +156 -0
  196. package/tests/runtime-harness.mjs +447 -39
  197. package/tests/runtime-metrics-children.test.ts +206 -0
  198. package/tests/runtime-metrics-delivery.test.ts +85 -0
  199. package/tests/runtime-metrics-extension.test.ts +187 -0
  200. package/tests/runtime-metrics-native.test.ts +209 -0
  201. package/tests/runtime-metrics-pi-identity.test.ts +113 -0
  202. package/tests/runtime-metrics-policy.test.ts +62 -0
  203. package/tests/runtime-metrics.test.ts +184 -0
  204. package/tests/sdd-agent-tools.test.ts +10 -1
  205. package/tests/sdd-execution-routing-contract.test.ts +28 -0
  206. package/tests/sdd-managed-runtime-settlement.test.ts +331 -0
  207. package/tests/sdd-native-managed-uptake.test.ts +253 -0
  208. package/tests/sdd-planning-routing-contract.test.ts +45 -0
  209. package/tests/sdd-preflight.test.ts +252 -8
  210. package/tests/sdd-research-capabilities.test.ts +256 -0
  211. package/tests/sdd-research-live.test.ts +241 -0
  212. package/tests/sdd-selection-transport.test.ts +504 -0
  213. package/tests/sdd-status.test.ts +51 -0
  214. package/tests/session-worktree-registry.test.ts +135 -0
  215. package/tests/shell-bar.test.ts +176 -0
  216. package/tests/shell-card.test.ts +139 -0
  217. package/tests/shell-changes-view.test.ts +609 -0
  218. package/tests/shell-changes.test.ts +350 -0
  219. package/tests/shell-prompt.test.ts +140 -0
  220. package/tests/shell-sidebar-banner.test.ts +23 -0
  221. package/tests/shell-sidebar-layout.test.ts +387 -0
  222. package/tests/shell-sidebar.test.ts +50 -0
  223. package/tests/shell-todo.test.ts +259 -0
  224. package/tests/shell-usage-view.test.ts +62 -0
  225. package/tests/shell-usage.test.ts +197 -0
  226. package/tests/startup-banner.test.ts +126 -0
  227. package/tests/telemetry-trigger.test.ts +351 -0
@@ -6,6 +6,7 @@ import { dirname, isAbsolute, join, posix, win32 } from "node:path";
6
6
  import { promisify } from "node:util";
7
7
  import { PackageLocalGentleAiBinaryMissingError, resolveGentleAiBinary } from "./gentle-ai-binary.mjs";
8
8
  import { GENTLE_PI_REVIEW_RELAY_CONTRACT, GENTLE_PI_REVIEW_RELAY_CONTRACT_ENV } from "./review-relay-contract.mjs";
9
+ import { decodeReviewAssessmentV1, } from "./review-risk-assessment.mjs";
9
10
  import {
10
11
  REVIEW_INTEGRATION_CONTRACT,
11
12
  decodeReviewAcknowledgedV1,
@@ -60,11 +61,16 @@ export const NATIVE_REVIEW_OPERATION = {
60
61
  RECONCILE_AUTHORITY: "review/reconcile-authority",
61
62
  REPAIR_LEGACY_ALIAS: "review/repair-legacy-alias",
62
63
  MODE: "review/mode",
64
+ ASSESS: "review/assess",
63
65
  REPAIR: "review/repair",
64
66
  CAPTURE_RESULT: "review/capture-result",
65
67
  CAPTURE_CORRECTION_PLAN: "review/capture-correction-plan",
66
68
  CAPTURE_PROVIDER_ROLE: "review/capture-provider-role",
69
+ CAPTURE_UNACHIEVABLE: "review/capture-unachievable",
67
70
  ACKNOWLEDGE_APPROVED: "review/acknowledge-approved",
71
+ SDD_STATUS: "sdd-status",
72
+ SDD_ATTEMPT: "sdd-attempt",
73
+ SDD_CONTINUE: "sdd-continue",
68
74
  } ;
69
75
 
70
76
 
@@ -104,6 +110,8 @@ export const NATIVE_REVIEW_ERROR_CODE = {
104
110
 
105
111
 
106
112
 
113
+
114
+
107
115
 
108
116
 
109
117
 
@@ -112,6 +120,49 @@ export const NATIVE_REVIEW_ERROR_CODE = {
112
120
 
113
121
 
114
122
 
123
+
124
+
125
+
126
+
127
+
128
+
129
+
130
+
131
+
132
+
133
+
134
+
135
+
136
+
137
+
138
+
139
+
140
+
141
+
142
+
143
+
144
+
145
+
146
+
147
+
148
+
149
+ export const SDD_ATTEMPT_OUTCOME = { PASSED: "passed", FAILED: "failed", INTERRUPTED: "interrupted" } ;
150
+
151
+
152
+
153
+
154
+
155
+
156
+
157
+
158
+
159
+
160
+
161
+
162
+
163
+
164
+
165
+
115
166
  export const NATIVE_REVIEW_MODE_OPERATION = {
116
167
  STATUS: "status",
117
168
  ENABLE: "enable",
@@ -155,6 +206,46 @@ export const NATIVE_REVIEW_MODE_SCOPE = {
155
206
 
156
207
 
157
208
 
209
+ /**
210
+ * The native CLI owns this complete v2 record. The decoder validates the fields
211
+ * Pi relies on and returns the original object without adding, omitting, or
212
+ * reconciling local SDD state.
213
+ */
214
+
215
+
216
+
217
+
218
+
219
+
220
+
221
+
222
+
223
+
224
+
225
+
226
+
227
+
228
+
229
+
230
+
231
+
232
+
233
+
234
+
235
+ // Read-only risk assessment request (gentle-pi#662). `baseRef` requires
236
+ // explicit `committedOnly` acknowledgement, exactly like Native START's
237
+ // baseRef/committedOnly pairing, because both select a committed range
238
+ // instead of the ambient working tree.
239
+
240
+
241
+
242
+
243
+
244
+
245
+
246
+
247
+
248
+
158
249
 
159
250
 
160
251
 
@@ -305,7 +396,6 @@ export const NATIVE_REVIEW_LEGACY_ALIAS_REPAIR = {
305
396
 
306
397
 
307
398
 
308
-
309
399
 
310
400
 
311
401
  /** Raw audited native record; Pi relays it verbatim and never reinterprets it. */
@@ -415,6 +505,49 @@ function isNativeReviewProviderRoleCaptureOperation(operation )
415
505
 
416
506
 
417
507
 
508
+ // gentle-pi#638: the typed declaration that one bound selected lens slot cannot be completed under current conditions. Mirrored from Go's reviewUnachievableLensCaptureArtifact (internal/cli/review_capture_unachievable.go): same schema identity, same closed field set, and the same 512-byte detail bound Go enforces, so a declaration this client refuses locally can never reach a binary that would accept it, and vice versa.
509
+ export const NATIVE_REVIEW_UNACHIEVABLE_LENS_CAPTURE_SCHEMA = "gentle-ai.review-capture-unachievable/v1";
510
+ export const NATIVE_REVIEW_UNACHIEVABLE_LENS_DETAIL_LIMIT = 512;
511
+
512
+
513
+
514
+
515
+
516
+
517
+
518
+
519
+
520
+
521
+
522
+
523
+
524
+
525
+
526
+
527
+
528
+
529
+
530
+
531
+
532
+
533
+
534
+
535
+
536
+
537
+
538
+
539
+
540
+
541
+
542
+ // gentle-pi#638 fail-open capability gate: `review capture-unachievable` is younger than every released binary pinned in NATIVE_CLI_CONTRACTS, so the verb is gated invocation-adjacent instead of by a capability row. An older binary renders Go's exact `unknown review command "capture-unachievable"` refusal (internal/cli/review_facade.go) on stderr with no stdout, so the invocation rejects before any decode and the captured diagnostics are the only place that text survives. Every other failure -- a typed binding-mismatch refusal, a timeout, a decode failure -- is a real outcome the caller must surface, never a capability signal. Duck-typed on purpose: the classifier must survive a duplicated module instance exactly like the error it inspects.
543
+ const NATIVE_REVIEW_UNKNOWN_UNACHIEVABLE_VERB_REFUSAL = /unknown review command "capture-unachievable"/;
544
+
545
+ export function isNativeReviewUnachievableVerbRefused(error ) {
546
+ if (typeof error !== "object" || error === null) return false;
547
+ const stderr = (error ).diagnostics?.stderr;
548
+ return typeof stderr === "string" && NATIVE_REVIEW_UNKNOWN_UNACHIEVABLE_VERB_REFUSAL.test(stderr);
549
+ }
550
+
418
551
  /** Refuter and validator captures close only when they are the native last event. */
419
552
 
420
553
 
@@ -499,7 +632,12 @@ export const NATIVE_REVIEW_AUTHORITY_ENTRY_VERSION = {
499
632
  } ;
500
633
 
501
634
 
502
- export const NATIVE_REVIEW_AUTHORITY_ENTRY_STATUS = NATIVE_REVIEW_AUTHORITY_STATUS;
635
+ export const NATIVE_REVIEW_AUTHORITY_ENTRY_STATUS = {
636
+ ...NATIVE_REVIEW_AUTHORITY_STATUS,
637
+ INCOMPLETE_STORE_ENTRY: "incomplete-store-entry",
638
+ HISTORICAL_PRE_RECEIPT: "historical-pre-receipt",
639
+ INVALIDATED: "invalidated",
640
+ } ;
503
641
 
504
642
 
505
643
  export const NATIVE_REVIEW_LOCK_STATUS = {
@@ -570,6 +708,11 @@ export const NATIVE_REVIEW_RECOVERY_DISPOSITION = {
570
708
 
571
709
 
572
710
 
711
+
712
+
713
+
714
+
715
+
573
716
 
574
717
 
575
718
 
@@ -805,6 +948,25 @@ export const NATIVE_CLI_CONTRACTS = Object.freeze({
805
948
  // consumes did not change between 2.5.0 and 2.6.0, so the columns match
806
949
  // the 2.5.0 row.
807
950
  "2.6.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 }),
951
+ // Ground-truthed against the published v2.7.0 binary installed from its
952
+ // signed archive: `review capabilities` on the v2 lane still advertises
953
+ // capabilities/v2.5 (protocol minor 5) and answers status/v7, consent/v3,
954
+ // and the `start/v4` continuation, all of which the decoders already read.
955
+ // `review mode status --json` still answers `gentle-ai.rdd-mode-status/v1`
956
+ // and `review validate --gate pre-commit` still answers
957
+ // `gentle-ai.review-gate-result/v1` carrying `delivery`, both verified
958
+ // against the binary. riskEvidence and hint stay dark for the same reason
959
+ // they have on every row since 2.2.0: still not proven to reach the
960
+ // negotiated START path Pi reads. The closed fields Pi consumes did not
961
+ // change between 2.6.0 and 2.7.0, so the columns match the 2.6.0 row.
962
+ "2.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 }),
963
+ // Ground-truthed against the published v2.8.0 linux/amd64 binary from its
964
+ // signed release archive. The v2 lane still advertises capabilities/v2.5
965
+ // (protocol minor 5) with status/v7, consent/v3, and start/v4 schemas.
966
+ // The closed fields Pi consumes did not change between 2.7.0 and 2.8.0,
967
+ // so this row repeats 2.7.0. riskEvidence and hint remain dark because
968
+ // neither is proven to reach the negotiated START path Pi consumes.
969
+ "2.8.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 }),
808
970
  });
809
971
 
810
972
 
@@ -836,7 +998,7 @@ export class NativeReviewCliError extends Error {
836
998
  this.launchAttempted = launchAttempted;
837
999
  this.mutating = mutating;
838
1000
  this.mutationOutcome = launchAttempted && mutating ? "unknown" : "none";
839
- this.nextAction = this.mutationOutcome === "unknown" ? "review.status" : undefined;
1001
+ this.nextAction = this.mutationOutcome === "unknown" && operation !== NATIVE_REVIEW_OPERATION.SDD_ATTEMPT ? "review.status" : undefined;
840
1002
  this.diagnostics = diagnostics ?? { operation, error_code: code, timed_out: false, output_limit_exceeded: false };
841
1003
  this.auditRecord = auditRecord;
842
1004
  }
@@ -1047,8 +1209,15 @@ function decodeNativeReviewRecovery(value ) {
1047
1209
  ...(recovery.maintainer_authorization === undefined ? {} : { maintainerAuthorization: requiredString(recovery.maintainer_authorization) }),
1048
1210
  };
1049
1211
  }
1212
+ function decodeNativeReviewDiscardedWorkSummary(value ) {
1213
+ const discardedWork = exactObject(value, ["captured_lens_results", "findings_present"]);
1214
+ return {
1215
+ capturedLensResults: stringArray(discardedWork.captured_lens_results),
1216
+ findingsPresent: booleanValue(discardedWork.findings_present),
1217
+ };
1218
+ }
1050
1219
  function decodeNativeReviewStatusEntry(value ) {
1051
- const entry = exactObject(value, ["version", "path", "status", "problems"], ["lineage_id", "state", "revision", "snapshot_identity", "chain_identity", "recovery"]);
1220
+ const entry = exactObject(value, ["version", "path", "status", "problems"], ["lineage_id", "state", "revision", "snapshot_identity", "chain_identity", "recovery", "discarded_work"]);
1052
1221
  return {
1053
1222
  version: enumString(entry.version, Object.values(NATIVE_REVIEW_AUTHORITY_ENTRY_VERSION)) ,
1054
1223
  ...(entry.lineage_id === undefined ? {} : { lineageId: requiredString(entry.lineage_id) }),
@@ -1059,6 +1228,7 @@ function decodeNativeReviewStatusEntry(value )
1059
1228
  ...(entry.snapshot_identity === undefined ? {} : { snapshotIdentity: sha256Identity(entry.snapshot_identity) }),
1060
1229
  ...(entry.chain_identity === undefined ? {} : { chainIdentity: requiredString(entry.chain_identity) }),
1061
1230
  ...(entry.recovery === undefined ? {} : { recovery: decodeNativeReviewRecovery(entry.recovery) }),
1231
+ ...(entry.discarded_work === undefined ? {} : { discardedWork: decodeNativeReviewDiscardedWorkSummary(entry.discarded_work) }),
1062
1232
  problems: stringArray(entry.problems),
1063
1233
  };
1064
1234
  }
@@ -1253,6 +1423,46 @@ function nativeError(code , operation
1253
1423
 
1254
1424
 
1255
1425
 
1426
+ const NATIVE_SDD_DEPENDENCIES = ["proposal", "specs", "design", "tasks", "apply", "verify", "archive"] ;
1427
+ const NATIVE_SDD_INSTRUCTION_PHASES = ["apply", "verify", "remediate", "archive"] ;
1428
+ const NATIVE_SDD_NEXT_RECOMMENDATIONS = ["apply", "verify", "remediate", "archive", "archived", "resolve-blockers", "sdd-new", "select-change", "propose", "spec", "design", "tasks"] ;
1429
+ const NATIVE_SDD_DEPENDENCY_STATES = ["blocked", "ready", "all_done"] ;
1430
+
1431
+ /** Strictly validates the native v2 contract while preserving its whole record. */
1432
+ export function decodeNativeSddStatusV2(value , request ) {
1433
+ const status = object(value);
1434
+ if (status.schemaName !== "gentle-ai.sdd-status" || status.schemaVersion !== 2) throw new Error("wrong native SDD status schema");
1435
+ if ((request.changeName !== undefined && status.changeName !== request.changeName) || (status.changeName !== null && !isCanonicalProcessString(status.changeName))) throw new Error("native SDD status change identity mismatch");
1436
+ const artifactStore = enumString(status.artifactStore, ["openspec", "engram", "hybrid", "none"]);
1437
+ const planningHome = object(status.planningHome);
1438
+ if (planningHome.mode !== "repo-local" || !isCanonicalProcessString(planningHome.path)) throw new Error("invalid native SDD planning home");
1439
+ const expectedOpenSpecHome = join(request.workspaceRoot, "openspec");
1440
+ if (planningHome.path !== expectedOpenSpecHome && !((artifactStore === "engram" || artifactStore === "hybrid") && planningHome.path === "engram:sdd")) throw new Error("native SDD planning home escaped its workspace");
1441
+ if (status.changeRoot !== null && !isCanonicalProcessString(status.changeRoot)) throw new Error("invalid native SDD change root");
1442
+ const actionContext = object(status.actionContext);
1443
+ if (actionContext.mode !== "repo-local" || actionContext.workspaceRoot !== request.workspaceRoot || !isCanonicalProcessString(actionContext.workspaceRoot)) throw new Error("native SDD status workspace root mismatch");
1444
+ const allowedEditRoots = stringArray(actionContext.allowedEditRoots);
1445
+ if (!allowedEditRoots.includes(request.workspaceRoot) || allowedEditRoots.some((root) => !isAbsolute(root) || root !== join(root))) throw new Error("invalid native SDD allowed edit roots");
1446
+ const dependencies = object(status.dependencies);
1447
+ for (const phase of NATIVE_SDD_DEPENDENCIES) {
1448
+ if (enumString(dependencies[phase], NATIVE_SDD_DEPENDENCY_STATES) !== dependencies[phase]) throw new Error("invalid native SDD dependency");
1449
+ }
1450
+ if (Object.keys(dependencies).length !== NATIVE_SDD_DEPENDENCIES.length) throw new Error("native SDD dependencies have an unsupported shape");
1451
+ if (status.instructions !== undefined) throw new Error("native SDD status uses phaseInstructions, not instructions");
1452
+ if (status.phaseInstructions !== undefined) {
1453
+ const instructions = object(status.phaseInstructions);
1454
+ for (const phase of NATIVE_SDD_INSTRUCTION_PHASES) stringArray(instructions[phase]);
1455
+ if (Object.keys(instructions).length !== NATIVE_SDD_INSTRUCTION_PHASES.length) throw new Error("native SDD instructions have an unsupported shape");
1456
+ }
1457
+ if (status.nextRecommended === "remediate" || status.remediationState !== undefined) {
1458
+ const remediation = object(status.remediationState);
1459
+ 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");
1460
+ }
1461
+ stringArray(status.blockedReasons);
1462
+ enumString(status.nextRecommended, NATIVE_SDD_NEXT_RECOMMENDATIONS);
1463
+ return status ;
1464
+ }
1465
+
1256
1466
  class NativeReviewPlainCli {
1257
1467
  adapter ;
1258
1468
  executable ;
@@ -1373,9 +1583,7 @@ class NativeReviewPlainCli {
1373
1583
  if (!isCanonicalProcessString(value)) throw new TypeError(`Native ABANDON ${name} must be a non-empty, trimmed, NUL-free string`);
1374
1584
  }
1375
1585
  if (!Array.isArray(request.capturedLensResults) || request.capturedLensResults.some((entry) => !isCanonicalProcessString(entry))) throw new TypeError("Native ABANDON capturedLensResults must be an array of non-empty, trimmed, NUL-free strings");
1376
- for (const [name, value] of [["findingsPresent", request.findingsPresent], ["evidenceRecordsPresent", request.evidenceRecordsPresent]] ) {
1377
- if (typeof value !== "boolean") throw new TypeError(`Native ABANDON ${name} must be a boolean`);
1378
- }
1586
+ if (typeof request.findingsPresent !== "boolean") throw new TypeError("Native ABANDON findingsPresent must be a boolean");
1379
1587
  if (request.maintainerAuthorization !== nativeReviewAbandonAuthorization(request)) throw new TypeError("Native ABANDON maintainerAuthorization must match the exact lineage, revision, snapshot, reason, discarded-work, and actor binding");
1380
1588
  const execution = await this.execute(NATIVE_REVIEW_OPERATION.ABANDON, request.cwd, [
1381
1589
  "review", "abandon", "--cwd", request.cwd,
@@ -1464,7 +1672,11 @@ class NativeReviewPlainCli {
1464
1672
  }
1465
1673
  }
1466
1674
 
1467
- export function nativeReviewAbandonAuthorization(request ) {
1675
+ export function nativeReviewAbandonAuthorization(request ) {
1676
+ // gentle-ai 0ed9225f removed evidence records from the discarded-work summary,
1677
+ // so the native v2 gate verifies an exact eight-line binding (schema, lineage,
1678
+ // revision, snapshot_identity, reason, captured_lens_results, findings_present,
1679
+ // actor) — there is no evidence_records_present line to derive or relay.
1468
1680
  return [
1469
1681
  "gentle-ai.review-abandon-authorization/v2",
1470
1682
  `lineage=${request.lineage}`,
@@ -1473,7 +1685,6 @@ export function nativeReviewAbandonAuthorization(request
1473
1685
  `reason=${request.reason}`,
1474
1686
  `captured_lens_results=${request.capturedLensResults.join(",")}`,
1475
1687
  `findings_present=${request.findingsPresent}`,
1476
- `evidence_records_present=${request.evidenceRecordsPresent}`,
1477
1688
  `actor=${request.actor.trim()}`,
1478
1689
  ].join("\n");
1479
1690
  }
@@ -1596,14 +1807,20 @@ function splitNativeConsentInvocation(invocation ) {
1596
1807
  let quote ;
1597
1808
  let escaping = false;
1598
1809
  let started = false;
1599
- for (const character of invocation.trim()) {
1810
+ const source = invocation.trim();
1811
+ for (let index = 0; index < source.length; index += 1) {
1812
+ const character = source[index] ;
1600
1813
  if (escaping) {
1601
1814
  current += character;
1602
1815
  escaping = false;
1603
1816
  started = true;
1604
1817
  continue;
1605
1818
  }
1606
- if (character === "\\" && quote !== "'") {
1819
+ // Provider invocations are not shell commands. Keep every path backslash
1820
+ // verbatim, including quoted UNC and drive-root paths. Outside quotes,
1821
+ // only a backslash before whitespace joins a space-containing token.
1822
+ const next = source[index + 1];
1823
+ if (character === "\\" && quote === undefined && next !== undefined && /\s/.test(next)) {
1607
1824
  escaping = true;
1608
1825
  started = true;
1609
1826
  continue;
@@ -1694,7 +1911,9 @@ function consentInvocationArguments(request )
1694
1911
  if (words[0] !== "gentle-ai" || words[1] !== "review" || words[2] !== "start") throw new NativeReviewConsentBindingError("consent-invocation-not-start", "Native consent invocation is not a provider review START");
1695
1912
  const arguments_ = words.slice(1);
1696
1913
  if (exactConsentOption(arguments_, "--contract") !== REVIEW_INTEGRATION_CONTRACT) throw new NativeReviewConsentBindingError("consent-invocation-contract-changed", "Native consent invocation contract changed");
1697
- if (exactConsentOption(arguments_, "--cwd") !== request.cwd) throw new NativeReviewConsentBindingError("consent-invocation-cwd-changed", "Native consent invocation repository binding changed");
1914
+ const providerCwd = exactConsentOption(arguments_, "--cwd");
1915
+ const requestedCwd = normalizeNativeReviewCwd(request.cwd);
1916
+ if (normalizeNativeReviewCwd(providerCwd) !== requestedCwd) throw new NativeReviewConsentBindingError("consent-invocation-cwd-changed", "Native consent invocation repository binding changed");
1698
1917
  if (exactConsentOption(arguments_, "--target") !== request.consent.targetIdentity) throw new NativeReviewConsentBindingError("consent-invocation-target-changed", "Native consent invocation target binding changed");
1699
1918
  if (exactConsentOption(arguments_, "--projection") !== request.consent.projection) throw new NativeReviewConsentBindingError("consent-invocation-projection-changed", "Native consent invocation projection binding changed");
1700
1919
  const lineageId = optionalConsentLineageOption(arguments_);
@@ -1774,6 +1993,23 @@ function decodeNativeProviderRoleCaptureArtifact(value )
1774
1993
  });
1775
1994
  }
1776
1995
 
1996
+ // gentle-pi#638: the recorded declaration Go prints for one unachievable slot. Closed key set, exact schema identity, and the lens Go itself resolved from the request hash -- the host never asserts which lens failed, only reads back the one the provider bound.
1997
+ function decodeNativeUnachievableLensCaptureArtifact(value ) {
1998
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError("native unachievable lens capture artifact must be an object");
1999
+ const body = value ;
2000
+ const allowed = new Set(["schema", "lineage_id", "target_identity", "lens", "selected_order", "reason", "recorded"]);
2001
+ for (const key of Object.keys(body)) if (!allowed.has(key)) throw new TypeError(`native unachievable lens capture artifact carries unexpected key ${key}`);
2002
+ const text = (key ) => {
2003
+ const found = body[key];
2004
+ if (typeof found !== "string" || found.trim() !== found || found.length === 0) throw new TypeError(`native unachievable lens capture artifact ${key} must be a non-empty trimmed string`);
2005
+ return found;
2006
+ };
2007
+ if (text("schema") !== NATIVE_REVIEW_UNACHIEVABLE_LENS_CAPTURE_SCHEMA) throw new TypeError(`native unachievable lens capture artifact schema must be ${NATIVE_REVIEW_UNACHIEVABLE_LENS_CAPTURE_SCHEMA}`);
2008
+ if (typeof body.selected_order !== "number" || !Number.isSafeInteger(body.selected_order) || body.selected_order < 0) throw new TypeError("native unachievable lens capture artifact selected_order must be a non-negative integer");
2009
+ if (body.recorded !== true) throw new TypeError("native unachievable lens capture artifact must report recorded: true");
2010
+ return Object.freeze({ schema: NATIVE_REVIEW_UNACHIEVABLE_LENS_CAPTURE_SCHEMA, lineageId: text("lineage_id"), targetIdentity: text("target_identity"), lens: text("lens"), selectedOrder: body.selected_order, reason: text("reason"), recorded: true });
2011
+ }
2012
+
1777
2013
  export class NativeReviewCliV216 {
1778
2014
  plain ;
1779
2015
  adapter ;
@@ -1865,6 +2101,89 @@ export class NativeReviewCliV216 {
1865
2101
  return this.invoke(operation, cwd, arguments_, mutating, signal, this.executablePath(operation, mutating), toleratedStderr);
1866
2102
  }
1867
2103
 
2104
+ async sddAttemptAcquire(request ) {
2105
+ return this.sddAttempt("acquire", request);
2106
+ }
2107
+
2108
+ async sddAttemptSettle(request ) {
2109
+ return this.sddAttempt("settle", request);
2110
+ }
2111
+
2112
+ async sddAttempt(verb , request ) {
2113
+ const args = ["sdd-attempt", verb, "--cwd", request.workspaceRoot, "--change", request.changeName, "--request-id", request.requestId];
2114
+ if (!isAbsolute(request.workspaceRoot) || !isCanonicalProcessString(request.workspaceRoot) || !isCanonicalProcessString(request.changeName) || !/^[a-z0-9][a-z0-9._-]{0,127}$/.test(request.requestId)) throw new TypeError("Invalid SDD attempt identity");
2115
+ const text = (flag , value , max ) => {
2116
+ if (!isCanonicalProcessString(value) || /[\r\n]/.test(value) || Buffer.byteLength(value) > max) throw new TypeError(`Invalid ${flag}`);
2117
+ args.push(flag, value);
2118
+ };
2119
+ const revision = (flag , value ) => {
2120
+ if (value === undefined) return;
2121
+ if (!/^sha256:[0-9a-f]{64}$/.test(value)) throw new TypeError(`Invalid ${flag}`);
2122
+ args.push(flag, value);
2123
+ };
2124
+ if (verb === "acquire") {
2125
+ const acquire = request ;
2126
+ text("--work-unit", acquire.workUnit, 160);
2127
+ text("--evidence-goal", acquire.evidenceGoal, 240);
2128
+ for (const [flag, value, max] of [["--max-attempts", acquire.maxAttempts, 100], ["--max-changed-lines", acquire.maxChangedLines, 1_000_000]] ) {
2129
+ if (value === undefined) continue;
2130
+ if (!Number.isInteger(value) || value < 1 || value > max) throw new TypeError(`Invalid ${flag}`);
2131
+ args.push(flag, String(value));
2132
+ }
2133
+ if (acquire.expectedRevision === "") args.push("--expected-revision", "");
2134
+ else revision("--expected-revision", acquire.expectedRevision);
2135
+ if (acquire.token !== undefined) text("--token", acquire.token, 500);
2136
+ } else {
2137
+ const settle = request ;
2138
+ text("--token", settle.token, 500);
2139
+ if (!Object.values(SDD_ATTEMPT_OUTCOME).includes(settle.outcome)) throw new TypeError("Invalid outcome");
2140
+ args.push("--outcome", settle.outcome);
2141
+ if (settle.outcome === "interrupted" ? settle.evidenceRevision !== undefined || settle.remediationEvidence !== undefined : !settle.evidenceRevision && !(settle.outcome === "passed" && settle.remediationEvidence)) throw new TypeError("Invalid terminal evidence");
2142
+ revision("--evidence-revision", settle.evidenceRevision);
2143
+ text("--diagnosis", settle.diagnosis, 500);
2144
+ if (!["reused", "invalidated"].includes(settle.harnessDisposition)) throw new TypeError("Invalid harness disposition");
2145
+ args.push("--harness-disposition", settle.harnessDisposition);
2146
+ text("--cleanup-evidence", settle.cleanupEvidence, 500);
2147
+ text("--process-evidence", settle.processEvidence, 500);
2148
+ if (settle.remediationEvidence !== undefined) args.push("--remediation-evidence", settle.remediationEvidence);
2149
+ }
2150
+ revision("--remediates-evidence-revision", request.remediatesEvidenceRevision);
2151
+ args.push(...nativeUntrackedSelectionArguments(nativeUntrackedSelection(request)));
2152
+ const operation = NATIVE_REVIEW_OPERATION.SDD_ATTEMPT;
2153
+ const { body } = await this.negotiated(operation, request.workspaceRoot, args, true);
2154
+ return decode(operation, true, () => {
2155
+ const result = body ;
2156
+ if (!result || !["proceed", "blocked", "complete"].includes(result.state)) throw new TypeError("Invalid compact state");
2157
+ if (verb === "acquire" && result.state === "proceed" && !isCanonicalProcessString(result.token)) throw new TypeError("Missing compact token");
2158
+ if (result.reason !== undefined && typeof result.reason !== "string") throw new TypeError("Invalid compact reason");
2159
+ return { state: result.state, ...(result.token === undefined ? {} : { token: result.token }), ...(result.reason === undefined ? {} : { reason: result.reason }) };
2160
+ });
2161
+ }
2162
+
2163
+ async sddStatus(request ) {
2164
+ return this.sddProjection(request, false);
2165
+ }
2166
+
2167
+ async sddContinue(request ) {
2168
+ if (!isCanonicalProcessString(request.changeName)) throw new TypeError("Native SDD continuation requires an exact selected change");
2169
+ return this.sddProjection(request, true);
2170
+ }
2171
+
2172
+ async sddProjection(request , mutating ) {
2173
+ if ((request.changeName !== undefined && !isCanonicalProcessString(request.changeName)) || !isCanonicalProcessString(request.workspaceRoot) || !isAbsolute(request.workspaceRoot)) {
2174
+ throw new TypeError("Native SDD status requires a canonical change and absolute workspace root");
2175
+ }
2176
+ const operation = mutating ? NATIVE_REVIEW_OPERATION.SDD_CONTINUE : NATIVE_REVIEW_OPERATION.SDD_STATUS;
2177
+ const execution = await this.negotiated(
2178
+ operation,
2179
+ request.workspaceRoot,
2180
+ [operation, ...(request.changeName === undefined ? [] : [request.changeName]), "--cwd", request.workspaceRoot, "--json", "--instructions"],
2181
+ mutating,
2182
+ request.signal,
2183
+ );
2184
+ return decode(operation, mutating, () => decodeNativeSddStatusV2(execution.body, request));
2185
+ }
2186
+
1868
2187
  async start(request ) {
1869
2188
  if (request.baseRef !== undefined && !isCanonicalProcessString(request.baseRef)) throw new TypeError("Native START baseRef must be a non-empty, trimmed, NUL-free string");
1870
2189
  if (request.baseRef !== undefined && request.committedOnly !== true) throw new TypeError("Native START baseRef requires explicit committedOnly acknowledgement");
@@ -2139,6 +2458,25 @@ export class NativeReviewCliV216 {
2139
2458
  });
2140
2459
  }
2141
2460
 
2461
+ // gentle-pi#638: `gentle-ai review capture-unachievable` records one bound declaration that the exact slot the collect transition offered cannot be completed under current conditions. Unlike every other capture verb the host runs, this one reports a failure instead of a result, so it never takes the provider-issued tokens verbatim: the declaration's binding values are named fields re-derived from the slot, and Go verifies them against the frozen lineage, revision, target, and subject hash before recording anything (internal/cli/review_capture_unachievable.go). A binding that no longer matches is a typed refusal the caller surfaces, never a host-side reconstruction.
2462
+ async captureUnachievableLens(request ) {
2463
+ for (const [field, value] of (["lineageId", "reason"] ).map((field) => [field, request[field]] )) {
2464
+ if (!isCanonicalProcessString(value)) throw new TypeError(`Native CAPTURE_UNACHIEVABLE ${field} must be a non-empty, trimmed, NUL-free string`);
2465
+ }
2466
+ // The three identity fields are digests on every slot the provider offers, so a non-sha value means the declaration binding was parsed wrong; refuse it locally instead of invoking a doomed process.
2467
+ for (const [field, value] of (["targetIdentity", "expectedRevision", "requestHash"] ).map((field) => [field, request[field]] )) {
2468
+ if (!/^sha256:[0-9a-f]{64}$/.test(value)) throw new TypeError(`Native CAPTURE_UNACHIEVABLE ${field} must be a canonical SHA-256 identity`);
2469
+ }
2470
+ const detail = request.detail === undefined ? "" : request.detail.trim();
2471
+ // gentle-pi#822: the native limit is 512 UTF-8 bytes, not 512 UTF-16 code units — the Go side measures the encoded payload, so a multibyte detail needs Buffer.byteLength; a .length check would let 300 two-byte characters through.
2472
+ if (Buffer.byteLength(detail, "utf8") > NATIVE_REVIEW_UNACHIEVABLE_LENS_DETAIL_LIMIT) throw new TypeError(`Native CAPTURE_UNACHIEVABLE detail exceeds ${NATIVE_REVIEW_UNACHIEVABLE_LENS_DETAIL_LIMIT} bytes`);
2473
+ if (request.repositoryContext !== undefined && !isCanonicalProcessString(request.repositoryContext)) throw new TypeError("Native CAPTURE_UNACHIEVABLE repositoryContext must be a non-empty, trimmed, NUL-free string");
2474
+ const executable = this.executablePath(NATIVE_REVIEW_OPERATION.CAPTURE_UNACHIEVABLE, true);
2475
+ // gentle-pi#822: --repository-context is authoritative and mutually exclusive with a path (the captureProviderRole discipline), so --cwd rides the invocation only when no repository context names it; the process working directory stays request.cwd either way.
2476
+ const execution = await this.invoke(NATIVE_REVIEW_OPERATION.CAPTURE_UNACHIEVABLE, request.cwd, ["review", "capture-unachievable", "--lineage", request.lineageId, "--target", request.targetIdentity, "--expected-revision", request.expectedRevision, "--request-hash", request.requestHash, "--reason", request.reason, ...(detail === "" ? [] : ["--detail", detail]), ...(request.repositoryContext === undefined ? ["--cwd", request.cwd] : ["--repository-context", request.repositoryContext])], true, request.signal, executable);
2477
+ return decode(NATIVE_REVIEW_OPERATION.CAPTURE_UNACHIEVABLE, true, () => decodeNativeUnachievableLensCaptureArtifact(execution.body));
2478
+ }
2479
+
2142
2480
  // gentle-pi#311 P5: executes one provider-rendered `review.finalize`
2143
2481
  // execute transition exactly as rendered. The tokens come verbatim from
2144
2482
  // the negotiated next_transition (e.g. `--lineage=<id>
@@ -2194,6 +2532,26 @@ export class NativeReviewCliV216 {
2194
2532
  return decode(NATIVE_REVIEW_OPERATION.MODE, mutating, () => decodeNativeReviewMode(execution.body, request.operation));
2195
2533
  }
2196
2534
 
2535
+ // Read-only risk assessment (gentle-ai#4295, gentle-pi#662). Never mutates;
2536
+ // an older binary without the verb, or any other process/decode failure,
2537
+ // rejects -- callers (the `gentle_review` tool's `assess` operation) fail
2538
+ // closed to `high`.
2539
+ async assess(request ) {
2540
+ if (request.baseRef !== undefined && !isCanonicalProcessString(request.baseRef)) throw new TypeError("Native ASSESS baseRef must be a non-empty, trimmed, NUL-free string");
2541
+ if (request.baseRef !== undefined && request.committedOnly !== true) throw new TypeError("Native ASSESS baseRef requires explicit committedOnly acknowledgement");
2542
+ if (request.baseRef === undefined && request.committedOnly !== undefined) throw new TypeError("Native ASSESS committedOnly requires an explicit baseRef");
2543
+ const cwd = await canonicalNativeReviewCwd(request.cwd);
2544
+ const execution = await this.invoke(
2545
+ NATIVE_REVIEW_OPERATION.ASSESS,
2546
+ cwd,
2547
+ ["review", "assess", "--cwd", cwd, ...(request.baseRef === undefined ? [] : ["--base-ref", request.baseRef, "--committed-only"]), "--json"],
2548
+ false,
2549
+ request.signal,
2550
+ this.executablePath(NATIVE_REVIEW_OPERATION.ASSESS, false),
2551
+ );
2552
+ return decode(NATIVE_REVIEW_OPERATION.ASSESS, false, () => decodeReviewAssessmentV1(execution.body));
2553
+ }
2554
+
2197
2555
  // Recovery commands are version-gated plain CLI operations outside the
2198
2556
  // negotiated integration-v1 contract, exactly like reviewStatus.
2199
2557
  reclaim(request ) {