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
@@ -80,7 +80,10 @@ const RISK_LEVELS = ["low", "medium", "high"] ;
80
80
  const REVIEW_LENSES = ["review-risk", "review-resilience", "review-readability", "review-reliability"] ;
81
81
  const RISK_REASON_CODES = ["configuration_change", "empty_content", "executable_change", "executable_mode", "hot_path", "large_change", "non_executable_only", "process_boundary", "process_scan_limit", "service_token", "shell_source"] ;
82
82
  const RISK_SIGNALS = ["auth", "update", "security", "payments", "permissions", "shell_process"] ;
83
- const STATUS_ACTIONS = ["start", "recover", "maintainer_action", "select_lineage", "repair_authority", "stop"] ;
83
+ // "collect" and "execute" are the v2 envelope's projection of a live
84
+ // transaction whose next_transition is mandatory: the root action names the
85
+ // transition kind instead of reading as a terminal stop.
86
+ const STATUS_ACTIONS = ["start", "recover", "maintainer_action", "select_lineage", "repair_authority", "stop", "collect", "execute"] ;
84
87
  const RECEIPT_STATUSES = ["expected_missing", "present", "publication_pending", "not_applicable"] ;
85
88
 
86
89
  export const REVIEW_STATUS_ACTION_DISPOSITION = {
@@ -404,6 +407,8 @@ const REPOSITORY_CONTEXT_OUTCOMES = ["applied", "pending", "blocked_conflict", "
404
407
 
405
408
 
406
409
 
410
+
411
+
407
412
 
408
413
 
409
414
 
@@ -541,6 +546,36 @@ export const REVIEW_PROVIDER_ROLE_CAPTURE_OPERATIONS = Object.freeze(Object.valu
541
546
 
542
547
 
543
548
 
549
+
550
+
551
+
552
+
553
+
554
+ // gentle-pi#627: the exact `gentle-ai sync` invocation that resolves a stale
555
+ // managed-asset set (STATUS next_transition stop, and START's preflight
556
+ // failure envelope both carry this same shape).
557
+
558
+
559
+
560
+
561
+
562
+
563
+
564
+ // gentle-pi#638: one selected lens slot the host declared unachievable
565
+ // through the native capture-unachievable verb, mirrored from Go's
566
+ // ReviewUnachievableLensSlot (internal/cli/review_next_transition.go). The
567
+ // withdraw form is deliberately narrower than a full execute transition:
568
+ // it is the literally runnable retraction command for exactly this slot, so
569
+ // a restart that never saw the collect offer can still take the declaration
570
+ // back from the stop alone.
571
+
572
+
573
+
574
+
575
+
576
+
577
+
578
+
544
579
 
545
580
 
546
581
 
@@ -556,6 +591,28 @@ export const REVIEW_PROVIDER_ROLE_CAPTURE_OPERATIONS = Object.freeze(Object.valu
556
591
 
557
592
 
558
593
 
594
+
595
+
596
+
597
+
598
+
599
+
600
+ const ESCALATION_CAUSES = ["unknown_causality", "insufficient_evidence", "missing_refuter_outcome", "targeted_validator_rejected", "correction_budget_exceeded", "unresolved_severe_findings"] ;
601
+ const ESCALATION_REFUTER_OUTCOMES = ["corroborated", "refuted", "inconclusive"] ;
602
+
603
+
604
+
605
+
606
+
607
+
608
+
609
+
610
+
611
+
612
+
613
+
614
+
615
+
559
616
 
560
617
 
561
618
 
@@ -706,6 +763,8 @@ const FAILURE_CAUSE_CATEGORIES = ["inventory_io_or_layout", "lock_ambiguous", "r
706
763
 
707
764
 
708
765
 
766
+
767
+
709
768
 
710
769
 
711
770
 
@@ -1640,8 +1699,11 @@ function decodeCollectInput(value , label , v5 , v6
1640
1699
  }
1641
1700
 
1642
1701
  if (captureOperation === "review.capture-result") {
1643
- if (input.artifact_subject === undefined || input.base_tree === undefined || input.candidate_tree === undefined || input.changed_path_manifest === undefined) {
1644
- throw new TypeError(`${label} requires artifact_subject, base_tree, candidate_tree, and changed_path_manifest`);
1702
+ // changed_path_manifest is optional here: the artifact subject's
1703
+ // changed_path_manifest_sha256 already binds the frozen manifest, and
1704
+ // gentle-ai stops inlining one copy per lens (gentle-ai#3922).
1705
+ if (input.artifact_subject === undefined || input.base_tree === undefined || input.candidate_tree === undefined) {
1706
+ throw new TypeError(`${label} requires artifact_subject, base_tree, and candidate_tree`);
1645
1707
  }
1646
1708
  if (schema !== "https://gentle-ai.dev/schema/review/reviewer/v1") throw new TypeError(`${label}.schema must be https://gentle-ai.dev/schema/review/reviewer/v1`);
1647
1709
  } else if (input.artifact_subject !== undefined || input.base_tree !== undefined || input.candidate_tree !== undefined || input.changed_path_manifest !== undefined) {
@@ -1674,12 +1736,39 @@ function decodeCollectInput(value , label , v5 , v6
1674
1736
  };
1675
1737
  }
1676
1738
 
1739
+ // gentle-pi#627: gentle-ai's managed-assets continuation (STATUS next_transition
1740
+ // stop reason_code=managed_assets_outdated, and START's preflight failure
1741
+ // envelope) names the exact `gentle-ai sync` invocation that resolves a stale
1742
+ // managed-asset set without abandoning the frozen candidate. Decoded verbatim
1743
+ // from internal/cli/review_operation_contract.go's ReviewManagedAssetsContinuation.
1744
+ export function decodeReviewManagedAssetsContinuationV1(value , label ) {
1745
+ const body = exactRecord(value, label, ["operation", "command"], ["agent", "stale_assets"]);
1746
+ const operation = enumeration(body.operation, ["sync"] , `${label}.operation`);
1747
+ const command = nonempty(body.command, `${label}.command`);
1748
+ const agent = body.agent === undefined ? undefined : nonempty(body.agent, `${label}.agent`);
1749
+ const staleAssets = body.stale_assets === undefined ? undefined : stringArray(body.stale_assets, `${label}.stale_assets`, { minimum: 1 });
1750
+ return { operation, command, ...(agent === undefined ? {} : { agent }), ...(staleAssets === undefined ? {} : { staleAssets }) };
1751
+ }
1752
+
1677
1753
  export function decodeReviewNextTransitionV3(value , options = {}) {
1678
1754
  const v6 = options.v6 === true;
1679
1755
  const v5 = options.v5 === true || v6;
1680
- const transition = exactRecord(value, "next_transition", ["kind", "reason_code"], ["execute", "collect", ...(v5 ? ["correction_request"] : [])]);
1756
+ const transition = exactRecord(value, "next_transition", ["kind", "reason_code"], ["execute", "collect", ...(v5 ? ["correction_request"] : []), "continuation", "unachievable_lens_slots"]);
1681
1757
  const kind = enumeration(transition.kind, ["execute", "collect", "stop"] , "next_transition.kind");
1682
1758
  const reasonCode = text(transition.reason_code, "next_transition.reason_code", { minimum: 1, pattern: /^[a-z0-9_]+$/ });
1759
+ const continuation = transition.continuation === undefined ? undefined : decodeReviewManagedAssetsContinuationV1(transition.continuation, "next_transition.continuation");
1760
+ // gentle-pi#627: continuation is optional on every status version an older
1761
+ // gentle-ai may emit a managed_assets_outdated stop with no continuation at
1762
+ // all, and decode must not refuse the whole envelope for that. When present
1763
+ // it is only valid on a stop with this exact reason_code.
1764
+ if (continuation !== undefined && !(kind === "stop" && reasonCode === "managed_assets_outdated")) throw new TypeError("next_transition.continuation is only valid for a stop transition with reason_code managed_assets_outdated");
1765
+
1766
+ // gentle-pi#638: the unachievable stop names one entry per declared slot, each carrying its complete withdraw command (schemas/status-v7.schema.json, the stop variant). Like the continuation, the field is only valid on its own exact stop reason code, and an entry is refused rather than silently dropped when the provider drifts.
1767
+ let unachievableLensSlots ;
1768
+ if (transition.unachievable_lens_slots !== undefined) {
1769
+ if (!(kind === "stop" && reasonCode === "unachievable_lens_slot")) throw new TypeError("next_transition.unachievable_lens_slots is only valid for a stop transition with reason_code unachievable_lens_slot");
1770
+ unachievableLensSlots = array(transition.unachievable_lens_slots, "next_transition.unachievable_lens_slots", decodeUnachievableLensSlot, { minimum: 1 });
1771
+ }
1683
1772
 
1684
1773
  // status/v5: the bounded correction plan request rides exactly its two
1685
1774
  // reason codes and never any other (vendored status-v5.schema.json).
@@ -1729,7 +1818,64 @@ export function decodeReviewNextTransitionV3(value , options
1729
1818
  return { kind, reasonCode, collect: { inputs }, ...(correctionRequest === undefined ? {} : { correctionRequest }) };
1730
1819
  }
1731
1820
  if (transition.execute !== undefined || transition.collect !== undefined) throw new TypeError("next_transition stop cannot carry a transition");
1732
- return { kind, reasonCode, ...(correctionRequest === undefined ? {} : { correctionRequest }) };
1821
+ return { kind, reasonCode, ...(correctionRequest === undefined ? {} : { correctionRequest }), ...(continuation === undefined ? {} : { continuation }), ...(unachievableLensSlots === undefined ? {} : { unachievableLensSlots }) };
1822
+ }
1823
+
1824
+ // Mirrors NATIVE_REVIEW_UNACHIEVABLE_LENS_DETAIL_LIMIT (lib/native-review-cli.ts) and Go's 512-byte CAPTURE_UNACHIEVABLE detail bound.
1825
+ export const REVIEW_INTEGRATION_UNACHIEVABLE_LENS_DETAIL_LIMIT = 512;
1826
+
1827
+ function decodeUnachievableLensSlot(value , label ) {
1828
+ const body = exactRecord(value, label, ["lens", "selected_order", "subject_hash", "reason", "withdraw"], ["detail"]);
1829
+ const lens = nonempty(body.lens, `${label}.lens`);
1830
+ const selectedOrder = integer(body.selected_order, `${label}.selected_order`);
1831
+ const subjectHash = sha256(body.subject_hash, `${label}.subject_hash`);
1832
+ const reason = nonempty(body.reason, `${label}.reason`);
1833
+ const detail = body.detail === undefined ? undefined : nonempty(body.detail, `${label}.detail`);
1834
+ // gentle-pi#822: Go refuses a CAPTURE_UNACHIEVABLE detail above 512 UTF-8 bytes, so the stop decoder mirrors the same bound — measured in bytes, not UTF-16 code units — and a STATUS stop can never carry a detail this client would have refused to declare.
1835
+ if (detail !== undefined && Buffer.byteLength(detail, "utf8") > REVIEW_INTEGRATION_UNACHIEVABLE_LENS_DETAIL_LIMIT) throw new TypeError(`${label}.detail exceeds ${REVIEW_INTEGRATION_UNACHIEVABLE_LENS_DETAIL_LIMIT} bytes`);
1836
+ const withdraw = exactRecord(body.withdraw, `${label}.withdraw`, ["operation", "command", "arguments", "binding"]);
1837
+ const operation = enumeration(withdraw.operation, ["review.capture-unachievable"] , `${label}.withdraw.operation`);
1838
+ const command = nonempty(withdraw.command, `${label}.withdraw.command`);
1839
+ const arguments_ = decodeTransitionArguments(withdraw.arguments, `${label}.withdraw.arguments`);
1840
+ // Withdrawal is a single affirmative native action, never an optional or
1841
+ // negatable provider argument. The general token and command checks below
1842
+ // then prove its exact --withdraw=true rendering is the complete vector.
1843
+ const withdrawArguments = arguments_.filter((argument) => argument.name === "withdraw");
1844
+ if (withdrawArguments.length !== 1) throw new TypeError(`${label}.withdraw.arguments withdraw must appear exactly once`);
1845
+ if (withdrawArguments[0] .value !== "true") throw new TypeError(`${label}.withdraw.arguments withdraw must be true`);
1846
+ // The binding keeps the execute branch's open-record discipline: the Go shape is target_identity plus optional lineage_id, revision, and repository_context, and closing it here would make Pi stricter than the contract it implements.
1847
+ const binding = exactRecord(withdraw.binding, `${label}.withdraw.binding`, ["target_identity"], ["lineage_id", "revision", "repository_context"], true);
1848
+ const targetIdentity = sha256(binding.target_identity, `${label}.withdraw.binding.target_identity`);
1849
+ const lineageId = binding.lineage_id === undefined ? undefined : lineage(binding.lineage_id, `${label}.withdraw.binding.lineage_id`);
1850
+ const revision = binding.revision === undefined ? undefined : sha256(binding.revision, `${label}.withdraw.binding.revision`);
1851
+ // gentle-pi#822: the withdraw form names the slot identity twice — as named arguments and as the binding object — and the two renderings must agree before the slot decodes. Strict equality also enforces both-or-neither on the optional lineage and revision fields, so a partially rendered binding never slips through and restart cannot withdraw a different slot. Each identity argument must appear EXACTLY once: a first-match lookup let a duplicate {name} entry smuggle a second value past the identity checks.
1852
+ const withdrawIdentityArgument = (name ) => {
1853
+ const matches = arguments_.filter((argument) => argument.name === name);
1854
+ if (matches.length > 1) throw new TypeError(`${label}.withdraw.arguments ${name} must appear exactly once`);
1855
+ return matches[0];
1856
+ };
1857
+ const withdrawRequestHash = withdrawIdentityArgument("request-hash");
1858
+ if (withdrawRequestHash?.value !== subjectHash) throw new TypeError(`${label}.withdraw.arguments request-hash does not match the slot subject_hash`);
1859
+ const withdrawTarget = withdrawIdentityArgument("target");
1860
+ if (withdrawTarget?.value !== targetIdentity) throw new TypeError(`${label}.withdraw.arguments target does not match the withdraw binding target_identity`);
1861
+ const withdrawLineage = withdrawIdentityArgument("lineage");
1862
+ if (withdrawLineage?.value !== lineageId) throw new TypeError(`${label}.withdraw.arguments lineage does not match the withdraw binding lineage_id`);
1863
+ const withdrawExpectedRevision = withdrawIdentityArgument("expected-revision");
1864
+ if (withdrawExpectedRevision?.value !== revision) throw new TypeError(`${label}.withdraw.arguments expected-revision does not match the withdraw binding revision`);
1865
+ // gentle-pi#822: command is executable authority, not a display hint. Every
1866
+ // provider-issued argument token must be the canonical --name=value rendering,
1867
+ // and command must render the canonical operation plus those tokens exactly in
1868
+ // order. This rejects omitted tokens, a second command, suffixes, and shell
1869
+ // payloads rather than merely finding identity-token substrings.
1870
+ const tokens = arguments_.map((argument, index) => {
1871
+ if (argument.token === undefined) throw new TypeError(`${label}.withdraw.arguments[${index}].token is required`);
1872
+ const expectedToken = `--${argument.name}=${argument.value}`;
1873
+ if (argument.token !== expectedToken) throw new TypeError(`${label}.withdraw.arguments[${index}].token must exactly render its name and value`);
1874
+ return argument.token;
1875
+ });
1876
+ const expectedCommand = `gentle-ai review capture-unachievable ${tokens.join(" ")}`;
1877
+ if (command !== expectedCommand) throw new TypeError(`${label}.withdraw.command must exactly render the canonical operation and withdraw arguments`);
1878
+ return { lens, selectedOrder, subjectHash, reason, ...(detail === undefined ? {} : { detail }), withdraw: { operation, command, arguments: arguments_, binding: { targetIdentity, ...(lineageId === undefined ? {} : { lineageId }), ...(revision === undefined ? {} : { revision }) } } };
1733
1879
  }
1734
1880
 
1735
1881
  // ---------------------------------------------------------------------------
@@ -1779,7 +1925,7 @@ export function decodeReviewStatusV3(value ) {
1779
1925
  // key set plus the optional forecast and the v5-only next_transition
1780
1926
  // surfaces. status/v6 adds the intended-untracked selection; status/v7
1781
1927
  // (gentle-ai v2.6.0, advertised through capabilities/v2.5 alongside v6)
1782
- // adds only the top-level optional `eligible_untracked_inventory` digest,
1928
+ // adds optional `eligible_untracked_inventory` and escalation metadata,
1783
1929
  // so it is decoded on the v6 surface. v3 keeps rejecting every v5/v6/v7-only
1784
1930
  // field.
1785
1931
  const schema = typeof value === "object" && value !== null ? (value ).schema : undefined;
@@ -1788,7 +1934,7 @@ export function decodeReviewStatusV3(value ) {
1788
1934
  const v5 = v6 || schema === "gentle-ai.review-integration.status/v5";
1789
1935
  const body = exactRecord(value, "status", [
1790
1936
  "schema", "contract", "operation", "applicability", "action", "replayability", "target_identity", "projection", "repair", "candidates",
1791
- ], ["authority", "frozen", "action_disposition", "eligibility", "next_transition", "authority_target_identity", ...(v5 ? ["receipt", "forecast", "repository_context", "validation_request"] : []), ...(v7 ? ["eligible_untracked_inventory"] : [])]);
1937
+ ], ["authority", "frozen", "action_disposition", "eligibility", "next_transition", "authority_target_identity", ...(v5 ? ["receipt", "forecast", "repository_context", "validation_request"] : []), ...(v7 ? ["eligible_untracked_inventory", "escalation"] : [])]);
1792
1938
  requireIdentity(body, v7 ? "gentle-ai.review-integration.status/v7" : v6 ? "gentle-ai.review-integration.status/v6" : v5 ? "gentle-ai.review-integration.status/v5" : "gentle-ai.review-integration.status/v3", REVIEW_INTEGRATION_OPERATION.STATUS);
1793
1939
 
1794
1940
  const applicability = enumeration(body.applicability, ["current_target", "unrelated", "ambiguous", "corrupted"] , "status.applicability");
@@ -1818,11 +1964,12 @@ export function decodeReviewStatusV3(value ) {
1818
1964
 
1819
1965
  let frozen ;
1820
1966
  if (body.frozen !== undefined) {
1821
- const source = exactRecord(body.frozen, "status.frozen", ["tier", "original_changed_lines", "correction_budget"]);
1967
+ const source = exactRecord(body.frozen, "status.frozen", ["tier", "original_changed_lines", "correction_budget"], ["changed_path_manifest_sha256"]);
1822
1968
  frozen = {
1823
1969
  tier: enumeration(source.tier, RISK_LEVELS, "status.frozen.tier"),
1824
1970
  originalChangedLines: integer(source.original_changed_lines, "status.frozen.original_changed_lines"),
1825
1971
  correctionBudget: integer(source.correction_budget, "status.frozen.correction_budget", 0, 200),
1972
+ ...(source.changed_path_manifest_sha256 === undefined ? {} : { changedPathManifestSha256: sha256(source.changed_path_manifest_sha256, "status.frozen.changed_path_manifest_sha256") }),
1826
1973
  };
1827
1974
  }
1828
1975
  if (authority?.version === REVIEW_AUTHORITY_VERSION.COMPACT_V2 && frozen === undefined) throw new TypeError("compact-v2 status requires frozen metadata");
@@ -1871,6 +2018,24 @@ export function decodeReviewStatusV3(value ) {
1871
2018
  };
1872
2019
  }
1873
2020
 
2021
+ let escalation ;
2022
+ if (Object.hasOwn(body, "escalation")) {
2023
+ const label = "status.escalation";
2024
+ const source = exactRecord(body.escalation, label, ["cause", "finding_ids"], ["refuter_outcomes"]);
2025
+ escalation = {
2026
+ cause: enumeration(source.cause, ESCALATION_CAUSES, `${label}.cause`),
2027
+ findingIds: stringArray(source.finding_ids, `${label}.finding_ids`),
2028
+ };
2029
+ if (Object.hasOwn(source, "refuter_outcomes")) escalation.refuterOutcomes = array(source.refuter_outcomes, `${label}.refuter_outcomes`, (entry, itemLabel) => {
2030
+ const row = exactRecord(entry, itemLabel, ["finding_id", "outcome", "proof"]);
2031
+ return {
2032
+ findingId: nonempty(row.finding_id, `${itemLabel}.finding_id`),
2033
+ outcome: enumeration(row.outcome, ESCALATION_REFUTER_OUTCOMES, `${itemLabel}.outcome`),
2034
+ proof: nonempty(row.proof, `${itemLabel}.proof`),
2035
+ };
2036
+ });
2037
+ }
2038
+
1874
2039
  // status/v7 top-level optional digest (gentle-ai v2.6.0): resolves #4066's
1875
2040
  // closed loop where `sdd-attempt finish` named a digest status never
1876
2041
  // published. Absent on the `staged` projection, which never resolves an
@@ -1898,6 +2063,7 @@ export function decodeReviewStatusV3(value ) {
1898
2063
  ...(forecast === undefined ? {} : { forecast }),
1899
2064
  ...(repositoryContext === undefined ? {} : { repositoryContext }),
1900
2065
  ...(validationRequest === undefined ? {} : { validationRequest }),
2066
+ ...(escalation === undefined ? {} : { escalation }),
1901
2067
  ...(eligibleUntrackedInventory === undefined ? {} : { eligibleUntrackedInventory }),
1902
2068
  raw: body,
1903
2069
  };
@@ -2049,9 +2215,10 @@ function decodeFailureContext(value , label )
2049
2215
  export function decodeReviewFailureV2(value ) {
2050
2216
  const body = exactRecord(value, "failure", [
2051
2217
  "schema", "contract", "operation", "phase", "code", "message", "mutation_outcome", "authority_applicability", "retry_safe", "replayability", "required_inputs", "next_action",
2052
- ], ["lineage_id", "request_digest", "progress_identity", "cause_category", "cause", "context"]);
2218
+ ], ["lineage_id", "request_digest", "progress_identity", "cause_category", "cause", "context", "continuation"]);
2053
2219
  requireIdentity(body, "gentle-ai.review-integration.failure/v2");
2054
2220
  const operation = enumeration(body.operation, FAILURE_OPERATIONS, "failure.operation");
2221
+ const code = text(body.code, "failure.code", { pattern: /^[a-z0-9]+(?:_[a-z0-9]+)*$/ });
2055
2222
 
2056
2223
  if (body.progress_identity !== undefined) {
2057
2224
  if (body.lineage_id === undefined || body.request_digest === undefined) throw new TypeError("failure.progress_identity requires lineage_id and request_digest");
@@ -2060,13 +2227,18 @@ export function decodeReviewFailureV2(value ) {
2060
2227
  if (body.request_digest !== undefined && operation === REVIEW_INTEGRATION_OPERATION.REPAIR && body.progress_identity === undefined) {
2061
2228
  throw new TypeError("failure.request_digest with review.repair requires progress_identity");
2062
2229
  }
2230
+ // gentle-pi#627: continuation is optional on every version — an older
2231
+ // gentle-ai may emit managed_assets_outdated with no continuation, and
2232
+ // decode must not refuse the whole envelope for that. When present it is
2233
+ // only valid on this exact code.
2234
+ if (body.continuation !== undefined && code !== "managed_assets_outdated") throw new TypeError("failure.continuation is only valid for code managed_assets_outdated");
2063
2235
 
2064
2236
  return {
2065
2237
  schema: "gentle-ai.review-integration.failure/v2",
2066
2238
  contract: REVIEW_INTEGRATION_CONTRACT,
2067
2239
  operation,
2068
2240
  phase: enumeration(body.phase, ["preflight", "pre_native", "native_running", "native_committed", "reconciliation"] , "failure.phase"),
2069
- code: text(body.code, "failure.code", { pattern: /^[a-z0-9]+(?:_[a-z0-9]+)*$/ }),
2241
+ code,
2070
2242
  message: text(body.message, "failure.message", { minimum: 1, maximum: 240, pattern: /^[^\r\n]+$/ }),
2071
2243
  mutationOutcome: enumeration(body.mutation_outcome, Object.values(REVIEW_MUTATION_OUTCOME), "failure.mutation_outcome"),
2072
2244
  authorityApplicability: enumeration(body.authority_applicability, Object.values(REVIEW_AUTHORITY_APPLICABILITY), "failure.authority_applicability"),
@@ -2080,6 +2252,7 @@ export function decodeReviewFailureV2(value ) {
2080
2252
  ...(body.cause_category === undefined ? {} : { causeCategory: text(body.cause_category, "failure.cause_category", { minimum: 1, pattern: /^[a-z0-9]+(?:_[a-z0-9]+)*$/ }) }),
2081
2253
  ...(body.cause === undefined ? {} : { cause: text(body.cause, "failure.cause", { minimum: 1, maximum: 4000, pattern: /^[^\r\n]+$/ }) }),
2082
2254
  ...(body.context === undefined ? {} : { context: decodeFailureContext(body.context, "failure.context") }),
2255
+ ...(body.continuation === undefined ? {} : { continuation: decodeReviewManagedAssetsContinuationV1(body.continuation, "failure.continuation") }),
2083
2256
  raw: body,
2084
2257
  };
2085
2258
  }
@@ -2401,6 +2574,29 @@ export function decodeReviewAcknowledgedV1(value , expected
2401
2574
  return decoded;
2402
2575
  }
2403
2576
 
2577
+ const REVIEW_LAST_EVENT_FINDING_SEVERITIES = ["BLOCKER", "CRITICAL", "WARNING", "SUGGESTION"] ;
2578
+ const REVIEW_LAST_EVENT_EVIDENCE_CLASSES = ["deterministic", "inferential", "insufficient"] ;
2579
+ const REVIEW_LAST_EVENT_CAUSAL_DISPOSITIONS = ["introduced", "behavior-activated", "worsened", "pre-existing", "base-only", "unknown"] ;
2580
+
2581
+
2582
+
2583
+
2584
+
2585
+
2586
+
2587
+
2588
+
2589
+
2590
+
2591
+
2592
+
2593
+
2594
+
2595
+
2596
+
2597
+
2598
+
2599
+
2404
2600
 
2405
2601
 
2406
2602
 
@@ -2523,8 +2719,38 @@ function decodeReviewStatusContinuationV1(value )
2523
2719
  };
2524
2720
  }
2525
2721
 
2722
+ function decodeReviewLastEventReviewerFindingV1(value , label ) {
2723
+ const finding = exactRecord(value, label, ["id", "lens", "location", "severity", "claim", "proof_refs"], ["evidence_class", "causal_disposition"]);
2724
+ const evidenceClass = finding.evidence_class === undefined
2725
+ ? undefined
2726
+ : enumeration(finding.evidence_class, REVIEW_LAST_EVENT_EVIDENCE_CLASSES, `${label}.evidence_class`);
2727
+ const causalDisposition = finding.causal_disposition === undefined
2728
+ ? undefined
2729
+ : enumeration(finding.causal_disposition, REVIEW_LAST_EVENT_CAUSAL_DISPOSITIONS, `${label}.causal_disposition`);
2730
+ return {
2731
+ id: nonempty(finding.id, `${label}.id`),
2732
+ lens: nonempty(finding.lens, `${label}.lens`),
2733
+ location: nonempty(finding.location, `${label}.location`),
2734
+ severity: enumeration(finding.severity, REVIEW_LAST_EVENT_FINDING_SEVERITIES, `${label}.severity`),
2735
+ claim: nonempty(finding.claim, `${label}.claim`),
2736
+ proofRefs: stringArray(finding.proof_refs, `${label}.proof_refs`, { minimum: 1 }),
2737
+ ...(evidenceClass === undefined ? {} : { evidenceClass }),
2738
+ ...(causalDisposition === undefined ? {} : { causalDisposition }),
2739
+ };
2740
+ }
2741
+
2742
+ function decodeReviewLastEventReviewerResultV1(value , label ) {
2743
+ const result = exactRecord(value, label, ["lens", "findings", "evidence", "result_hash"]);
2744
+ return {
2745
+ lens: nonempty(result.lens, `${label}.lens`),
2746
+ findings: array(result.findings, `${label}.findings`, decodeReviewLastEventReviewerFindingV1),
2747
+ evidence: stringArray(result.evidence, `${label}.evidence`),
2748
+ resultHash: sha256(result.result_hash, `${label}.result_hash`),
2749
+ };
2750
+ }
2751
+
2526
2752
  export function decodeReviewLastEventClosureV1(value ) {
2527
- const body = exactRecord(value, "last_event_closure", ["schema", "operation", "lineage_id", "state", "store_revision"], ["target_identity", "request_hash", "correction_lines", "action", "advisory_findings", "status_continuation", "acknowledgement"]);
2753
+ const body = exactRecord(value, "last_event_closure", ["schema", "operation", "lineage_id", "state", "store_revision"], ["target_identity", "request_hash", "correction_lines", "action", "advisory_findings", "reviewer_results", "status_continuation", "acknowledgement"]);
2528
2754
  if (body.schema !== REVIEW_LAST_EVENT_CLOSURE_SCHEMA) throw new TypeError(`last_event_closure.schema must be ${REVIEW_LAST_EVENT_CLOSURE_SCHEMA}`);
2529
2755
  const operation = enumeration(body.operation, Object.values(REVIEW_LAST_EVENT_CLOSURE_OPERATION), "last_event_closure.operation") ;
2530
2756
  const state = enumeration(body.state, REVIEW_LAST_EVENT_TERMINAL_STATES, "last_event_closure.state") ;
@@ -2536,6 +2762,7 @@ export function decodeReviewLastEventClosureV1(value )
2536
2762
  storeRevision: sha256(body.store_revision, "last_event_closure.store_revision"),
2537
2763
  };
2538
2764
  if (operation === REVIEW_LAST_EVENT_CLOSURE_OPERATION.CAPTURE_CORRECTION_PLAN) {
2765
+ if (body.reviewer_results !== undefined) throw new TypeError("last_event_closure reviewer_results requires approved state");
2539
2766
  if (body.action !== undefined || body.advisory_findings !== undefined || body.status_continuation !== undefined) throw new TypeError("last_event_closure correction-plan cannot carry action, advisory_findings, or status_continuation");
2540
2767
  if (state !== "correction_required") throw new TypeError("last_event_closure correction-plan requires correction_required state");
2541
2768
  return {
@@ -2603,10 +2830,15 @@ export function decodeReviewLastEventClosureV1(value )
2603
2830
  ? undefined
2604
2831
  : decodeReviewAdvisoryFindingsV1(body.advisory_findings, "last_event_closure.advisory_findings");
2605
2832
  if (advisoryFindings !== undefined && state !== "approved") throw new TypeError("last_event_closure advisory_findings requires approved state");
2833
+ const reviewerResults = body.reviewer_results === undefined
2834
+ ? undefined
2835
+ : array(body.reviewer_results, "last_event_closure.reviewer_results", decodeReviewLastEventReviewerResultV1);
2836
+ if (reviewerResults !== undefined && state !== "approved") throw new TypeError("last_event_closure reviewer_results requires approved state");
2606
2837
  return {
2607
2838
  ...shared,
2608
2839
  action,
2609
2840
  ...(advisoryFindings === undefined ? {} : { advisoryFindings }),
2841
+ ...(reviewerResults === undefined ? {} : { reviewerResults }),
2610
2842
  ...(statusContinuation === undefined ? {} : { statusContinuation }),
2611
2843
  ...(acknowledgement === undefined ? {} : { acknowledgement }),
2612
2844
  ...(acknowledgementUndecodable ? { acknowledgementUndecodable: true } : {}),
@@ -15,3 +15,14 @@
15
15
 
16
16
  export const GENTLE_PI_REVIEW_RELAY_CONTRACT_ENV = "GENTLE_PI_REVIEW_RELAY_CONTRACT";
17
17
  export const GENTLE_PI_REVIEW_RELAY_CONTRACT = "gentle-pi.review-relay/v1";
18
+
19
+ // The host is this extension: every process the session starts (pi's shell
20
+ // tools, Gentle Agents children) runs under it, so the handshake belongs in
21
+ // the session environment too, not only in the extension's own invocations.
22
+ // Without it the first `gentle-ai review status --agent pi` typed in the
23
+ // session shell fails closed (gentle-pi#550). Returns whether anything changed.
24
+ export function declareReviewRelayHandshake(env ) {
25
+ if (env[GENTLE_PI_REVIEW_RELAY_CONTRACT_ENV] === GENTLE_PI_REVIEW_RELAY_CONTRACT) return false;
26
+ env[GENTLE_PI_REVIEW_RELAY_CONTRACT_ENV] = GENTLE_PI_REVIEW_RELAY_CONTRACT;
27
+ return true;
28
+ }