gentle-pi 2.1.2 → 2.3.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 (228) hide show
  1. package/README.md +108 -79
  2. package/assets/agents/gentle-ai-worker.md +7 -3
  3. package/assets/agents/jd-fix-agent.md +1 -1
  4. package/assets/agents/jd-judge-a.md +3 -1
  5. package/assets/agents/jd-judge-b.md +3 -1
  6. package/assets/agents/review-readability.md +4 -3
  7. package/assets/agents/review-reliability.md +4 -3
  8. package/assets/agents/review-resilience.md +4 -3
  9. package/assets/agents/review-risk.md +4 -3
  10. package/assets/agents/sdd-apply.md +11 -4
  11. package/assets/agents/sdd-archive.md +6 -1
  12. package/assets/agents/sdd-design.md +6 -1
  13. package/assets/agents/sdd-explore.md +6 -2
  14. package/assets/agents/sdd-init.md +10 -2
  15. package/assets/agents/sdd-onboard.md +6 -1
  16. package/assets/agents/sdd-proposal.md +8 -1
  17. package/assets/agents/sdd-research.md +54 -0
  18. package/assets/agents/sdd-spec.md +6 -1
  19. package/assets/agents/sdd-status.md +10 -5
  20. package/assets/agents/sdd-sync.md +6 -1
  21. package/assets/agents/sdd-tasks.md +8 -4
  22. package/assets/agents/sdd-verify.md +27 -2
  23. package/assets/chains/4r-review.chain.md +2 -0
  24. package/assets/chains/sdd-full.chain.md +2 -2
  25. package/assets/chains/sdd-plan.chain.md +1 -1
  26. package/assets/chains/sdd-verify.chain.md +2 -2
  27. package/assets/orchestrator-delegation.md +145 -160
  28. package/assets/orchestrator-memory.md +2 -0
  29. package/assets/orchestrator.md +25 -48
  30. package/assets/sdd-orchestrator-workflow.md +163 -25
  31. package/assets/support/sdd-status-contract.md +24 -6
  32. package/contracts/review-integration/v1/fixtures/consent.fixture.json +3 -3
  33. package/contracts/review-integration/v1/fixtures/start-v2.fixture.json +19 -28
  34. package/contracts/review-integration/v1/fixtures/start.fixture.json +1 -10
  35. package/contracts/review-integration/v1/fixtures/status-v2.fixture.json +12 -21
  36. package/contracts/review-integration/v1/schemas/correction-plan-request.schema.json +49 -0
  37. package/contracts/review-integration/v1/schemas/operation.schema.json +76 -0
  38. package/contracts/review-integration/v1/schemas/repair.schema.json +39 -0
  39. package/contracts/review-integration/v1/schemas/status-v2.schema.json +4 -2
  40. package/contracts/review-integration/v1/schemas/status.schema.json +4 -2
  41. package/contracts/review-integration/v1/schemas/transition-execution.schema.json +42 -0
  42. package/contracts/review-integration/v2/fixtures/consent.fixture.json +1 -1
  43. package/contracts/review-integration/v2/fixtures/start.fixture.json +1 -10
  44. package/contracts/review-integration/v2/fixtures/status.fixture.json +1 -10
  45. package/contracts/review-integration/v2/schemas/failure.schema.json +5 -1
  46. package/contracts/review-integration/v2/schemas/last-event-closure.schema.json +66 -0
  47. package/contracts/review-integration/v2/schemas/opencode-provider-role.schema.json +14 -0
  48. package/contracts/review-integration/v2/schemas/operation.schema.json +6 -1
  49. package/contracts/review-integration/v2/schemas/repair.schema.json +4 -2
  50. package/contracts/review-integration/v2/schemas/start.schema.json +5 -2
  51. package/contracts/review-integration/v2/schemas/status.schema.json +4 -2
  52. package/contracts/review-provider-contract-mirror/provider-contract.lock.json +30 -0
  53. package/contracts/review-provider-contract-mirror/v1.1.0/bundle/README.md +12 -0
  54. package/contracts/review-provider-contract-mirror/v1.1.0/bundle/manifest.json +65 -0
  55. package/contracts/review-provider-contract-mirror/v1.1.0/bundle/schemas/lens.schema.json +16 -0
  56. package/contracts/review-provider-contract-mirror/v1.1.0/bundle/schemas/refuter.schema.json +1 -0
  57. package/contracts/review-provider-contract-mirror/v1.1.0/bundle/schemas/targeted-validator.schema.json +1 -0
  58. package/contracts/review-provider-contract-mirror/v1.1.0/bundle/vectors/lens.json +1 -0
  59. package/contracts/review-provider-contract-mirror/v1.1.0/bundle/vectors/refuter.json +1 -0
  60. package/contracts/review-provider-contract-mirror/v1.1.0/bundle/vectors/targeted-validator.json +1 -0
  61. package/contracts/review-provider-contract-mirror/v1.1.0/generated/provider-capabilities.baseline.json +15 -0
  62. package/contracts/review-provider-contract-mirror/v1.1.0/generated/provider-roles.baseline.json +42 -0
  63. package/docs/native-authority-architecture.md +14 -16
  64. package/docs/review-integration.md +27 -353
  65. package/extensions/ask-user-choice.ts +151 -0
  66. package/extensions/gentle-ai.ts +2710 -3211
  67. package/extensions/quiet-tools.ts +515 -32
  68. package/extensions/sdd-init.ts +21 -12
  69. package/extensions/skill-registry.ts +10 -2
  70. package/extensions/startup-banner.ts +10 -4
  71. package/lib/gentle-ai-binary.ts +173 -2
  72. package/lib/gentle-ai-renderer.ts +70 -0
  73. package/lib/model-routing-authority.ts +133 -0
  74. package/lib/native-review-cli.ts +676 -781
  75. package/lib/opaque-pi-reviewer-adapter.ts +284 -0
  76. package/lib/provider-contract-bundle.ts +704 -0
  77. package/lib/review-candidate-view.ts +836 -118
  78. package/lib/review-compact-contract.ts +59 -248
  79. package/lib/review-host-relay.ts +578 -0
  80. package/lib/review-integration-v2.ts +1254 -221
  81. package/lib/review-last-event-controller.ts +35 -0
  82. package/lib/review-relay-contract.ts +16 -0
  83. package/lib/sdd-preflight.ts +230 -67
  84. package/lib/sdd-status.ts +66 -111
  85. package/lib/terminal-theme.ts +1 -1
  86. package/package.json +83 -79
  87. package/runtime/gentle-ai-binary.mjs +174 -3
  88. package/runtime/native-review-cli.mjs +610 -715
  89. package/runtime/review-integration-v2.mjs +1223 -190
  90. package/runtime/review-relay-contract.mjs +17 -0
  91. package/scripts/{build-git-commit-transaction-runner.mjs → build-runtime-modules.mjs} +5 -5
  92. package/scripts/check-provider-contract.mjs +138 -0
  93. package/scripts/gentle-ai-installer.mjs +85 -22
  94. package/scripts/maintainer/provider-relay-matrix.mjs +403 -0
  95. package/scripts/mirror-provider-contract.mjs +143 -0
  96. package/scripts/test-packed-runner.mjs +20 -9
  97. package/scripts/verify-package-files.mjs +119 -43
  98. package/skills/_shared/review-ledger-contract.md +11 -19
  99. package/skills/chained-pr/SKILL.md +3 -0
  100. package/skills/cognitive-doc-design/SKILL.md +1 -1
  101. package/skills/comment-writer/SKILL.md +1 -1
  102. package/skills/gentle-ai/SKILL.md +7 -74
  103. package/skills/issue-creation/SKILL.md +94 -168
  104. package/skills/judgment-day/SKILL.md +9 -5
  105. package/skills/judgment-day/references/prompts-and-formats.md +2 -0
  106. package/skills/rdd-defect-workflow/SKILL.md +54 -0
  107. package/skills/release/SKILL.md +3 -3
  108. package/skills/skill-registry/SKILL.md +1 -1
  109. package/skills/work-unit-commits/SKILL.md +3 -1
  110. package/tests/artifact-language.test.ts +24 -11
  111. package/tests/ask-user-choice.test.ts +264 -0
  112. package/tests/background-subagents.test.ts +771 -0
  113. package/tests/codegraph-tools.test.ts +3 -3
  114. package/tests/crosslane/cross-lane.mjs +16 -0
  115. package/tests/delegated-key-learnings-contract.test.ts +240 -0
  116. package/tests/devbinary/native-review-parity.devtest.ts +167 -250
  117. package/tests/devbinary/pi-host-relay.devtest.ts +867 -0
  118. package/tests/fixtures/devbinary/capabilities-v2.1.derived.json +331 -0
  119. package/tests/fixtures/devbinary/capabilities-v2.2.captured.json +340 -0
  120. package/tests/fixtures/devbinary/consent-v3.captured.json +37 -0
  121. package/tests/fixtures/devbinary/failure-v2-capture-evidence.captured.json +16 -0
  122. package/tests/fixtures/devbinary/last-event-capture-correction-plan.captured.json +10 -0
  123. package/tests/fixtures/devbinary/last-event-capture-refuter-approved.captured.json +20 -0
  124. package/tests/fixtures/devbinary/last-event-capture-refuter-correction-required.captured.json +26 -0
  125. package/tests/fixtures/devbinary/last-event-capture-result-approved.captured.json +8 -0
  126. package/tests/fixtures/devbinary/last-event-capture-result-correction-required.captured.json +26 -0
  127. package/tests/fixtures/devbinary/last-event-capture-validation-approved.captured.json +8 -0
  128. package/tests/fixtures/devbinary/last-event-closure.provenance.md +13 -0
  129. package/tests/fixtures/devbinary/result-artifact-v2-path.captured.json +12 -0
  130. package/tests/fixtures/devbinary/result-artifact-v2.captured.json +12 -0
  131. package/tests/fixtures/devbinary/review-acknowledged-v1.captured.json +9 -0
  132. package/tests/fixtures/devbinary/review-acknowledged.provenance.md +31 -0
  133. package/tests/fixtures/devbinary/start-v3-consent-declined.captured.json +19 -0
  134. package/tests/fixtures/devbinary/start-v3-consent-granted.captured.json +109 -0
  135. package/tests/fixtures/devbinary/start-v3-zero-lens-closed.captured.json +21 -0
  136. package/tests/fixtures/devbinary/status-v5-capture-result-submission.captured.json +184 -0
  137. package/tests/fixtures/devbinary/status-v5-repository-context.captured.json +138 -0
  138. package/tests/fixtures/devbinary/status-v5.captured.json +88 -0
  139. package/tests/fixtures/native-review-cli/v2.5.0-rc.1/PROVENANCE.txt +15 -0
  140. package/tests/fixtures/provider-contract-bundle/v1.1.0/README.md +12 -0
  141. package/tests/fixtures/provider-contract-bundle/v1.1.0/manifest.json +65 -0
  142. package/tests/fixtures/provider-contract-bundle/v1.1.0/schemas/lens.schema.json +16 -0
  143. package/tests/fixtures/provider-contract-bundle/v1.1.0/schemas/refuter.schema.json +1 -0
  144. package/tests/fixtures/provider-contract-bundle/v1.1.0/schemas/targeted-validator.schema.json +1 -0
  145. package/tests/fixtures/provider-contract-bundle/v1.1.0/vectors/lens.json +1 -0
  146. package/tests/fixtures/provider-contract-bundle/v1.1.0/vectors/refuter.json +1 -0
  147. package/tests/fixtures/provider-contract-bundle/v1.1.0/vectors/targeted-validator.json +1 -0
  148. package/tests/gentle-ai-binary.test.ts +83 -13
  149. package/tests/gentle-ai-dev-binary-surfacing.test.ts +195 -0
  150. package/tests/gentle-ai-dev-binary.test.ts +159 -0
  151. package/tests/gentle-ai-installer.test.ts +138 -48
  152. package/tests/gentle-ai.test.ts +820 -131
  153. package/tests/gentle-theme.test.ts +133 -0
  154. package/tests/maintainer/provider-relay.maintest.ts +601 -0
  155. package/tests/model-routing-authority.test.ts +257 -0
  156. package/tests/native-review-capability-contract.test.ts +75 -2
  157. package/tests/native-review-cli.test.ts +588 -908
  158. package/tests/native-review-consent.test.ts +283 -38
  159. package/tests/native-review-parity-runtime.test.ts +108 -350
  160. package/tests/native-review-parity.test.ts +684 -732
  161. package/tests/native-sdd-attempt-authority.test.ts +235 -0
  162. package/tests/opaque-pi-reviewer-adapter.test.ts +266 -0
  163. package/tests/orchestrator-budget.test.ts +158 -89
  164. package/tests/orchestrator-rdd-ownership.test.ts +103 -0
  165. package/tests/package-manifest.test.ts +128 -100
  166. package/tests/provider-contract-bundle.test.ts +385 -0
  167. package/tests/provider-contract-mirror.test.ts +206 -0
  168. package/tests/provider-defect-handoff.test.ts +252 -0
  169. package/tests/quiet-tool-rendering.test.ts +1055 -28
  170. package/tests/review-actor-tool-deny.test.ts +12 -13
  171. package/tests/review-authority-recovery-docs.test.ts +1 -2
  172. package/tests/review-candidate-view.test.ts +898 -12
  173. package/tests/review-compact-contract.test.ts +29 -122
  174. package/tests/review-controller-lock-status.test.ts +2 -2
  175. package/tests/review-controller-native-recovery.test.ts +366 -857
  176. package/tests/review-controller-native-routing.test.ts +1253 -4199
  177. package/tests/review-controller-retired-ops.test.ts +1 -1
  178. package/tests/review-controller-workspace-root.test.ts +236 -70
  179. package/tests/review-controller.test.ts +26 -816
  180. package/tests/review-corrected-finalize-binding.test.ts +134 -0
  181. package/tests/review-dispatch-hydration-gap.test.ts +145 -0
  182. package/tests/review-gate.test.ts +0 -45
  183. package/tests/review-host-relay-restart-parity.test.ts +360 -0
  184. package/tests/review-host-relay-routing.test.ts +352 -0
  185. package/tests/review-host-relay.test.ts +754 -0
  186. package/tests/review-integration-v2-forward.test.ts +654 -0
  187. package/tests/review-integration-v2.test.ts +317 -129
  188. package/tests/review-last-event-closure.test.ts +408 -0
  189. package/tests/review-ledger-contract.test.ts +106 -60
  190. package/tests/review-recovered-lineage-routing.test.ts +199 -0
  191. package/tests/review-relay-transport-agent.test.ts +322 -0
  192. package/tests/review-snapshot.test.ts +3 -2
  193. package/tests/runtime-harness.mjs +573 -180
  194. package/tests/sdd-agent-tools.test.ts +53 -32
  195. package/tests/sdd-preflight.test.ts +81 -15
  196. package/tests/sdd-status.test.ts +109 -110
  197. package/tests/skill-collision-prefixes.test.ts +6 -8
  198. package/tests/skill-registry.test.ts +50 -1
  199. package/tests/verify-package-files.test.ts +62 -0
  200. package/tests/writer-edit-surface-scope.test.ts +230 -0
  201. package/themes/Gentleman-Cute.json +94 -0
  202. package/themes/Gentleman-Sexy.json +92 -0
  203. package/assets/agents/review-refuter.md +0 -40
  204. package/assets/agents/review-validator.md +0 -23
  205. package/lib/git-commit-transaction.ts +0 -801
  206. package/lib/native-review-remediation.ts +0 -49
  207. package/lib/review-compact.ts +0 -947
  208. package/lib/review-refuter-adapter.ts +0 -129
  209. package/lib/review-runtime-contract.ts +0 -68
  210. package/prompts/gcl.md +0 -54
  211. package/prompts/gis.md +0 -25
  212. package/prompts/gpr.md +0 -41
  213. package/prompts/gwr.md +0 -31
  214. package/runtime/git-commit-transaction.mjs +0 -802
  215. package/scripts/run-git-commit-transaction.mjs +0 -35
  216. package/tests/fixtures/native-review-cli/v2.1.2/bind-sdd.json +0 -25
  217. package/tests/fixtures/native-review-cli/v2.1.2/finalize.json +0 -8
  218. package/tests/fixtures/native-review-cli/v2.1.2/sdd-status-engram.json +0 -139
  219. package/tests/fixtures/native-review-cli/v2.1.2/sdd-status.json +0 -200
  220. package/tests/fixtures/native-review-cli/v2.1.2/start.json +0 -12
  221. package/tests/fixtures/native-review-cli/v2.1.2/validate-allow.json +0 -24
  222. package/tests/fixtures/native-review-cli/v2.1.2/validate-deny-empty-context.json +0 -20
  223. package/tests/fixtures/native-review-cli/v2.1.2/validate-deny.json +0 -28
  224. package/tests/fixtures/native-review-cli/v2.1.3/sdd-status-engram.json +0 -139
  225. package/tests/fixtures/native-review-cli/v2.1.3/sdd-status.json +0 -200
  226. package/tests/git-commit-transaction.test.ts +0 -302
  227. package/tests/review-compact.test.ts +0 -243
  228. package/tests/review-refuter-adapter.test.ts +0 -89
@@ -0,0 +1,134 @@
1
+ import assert from "node:assert/strict";
2
+ import { readFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import test from "node:test";
5
+ import { __testing } from "../extensions/gentle-ai.ts";
6
+ import type { NativeReviewCli } from "../lib/native-review-cli.ts";
7
+ import { assertReviewLastEventClosureBinding, decodeReviewLastEventClosureV1, type ReviewStatusV3 } from "../lib/review-integration-v2.ts";
8
+
9
+ const CAPTURED_FIXTURES = join(process.cwd(), "tests", "fixtures", "devbinary");
10
+
11
+ function captured(name: string): Record<string, unknown> {
12
+ return JSON.parse(readFileSync(join(CAPTURED_FIXTURES, name), "utf8")) as Record<string, unknown>;
13
+ }
14
+
15
+ test("captured correction-required result preserves the provider status continuation", () => {
16
+ const fixture = captured("last-event-capture-result-correction-required.captured.json");
17
+ const closure = decodeReviewLastEventClosureV1(fixture);
18
+ assert.equal(closure.operation, "review/capture-result");
19
+ assert.equal(closure.state, "correction_required");
20
+ assert.match(closure.storeRevision, /^sha256:[a-f0-9]{64}$/);
21
+ assert.equal(closure.statusContinuation?.operation, "review.status");
22
+ assert.deepEqual(
23
+ closure.statusContinuation?.arguments.map((argument) => argument.token),
24
+ [
25
+ "--cwd=/tmp/repository",
26
+ "--contract=gentle-ai.review-integration/v2",
27
+ "--next-transition=true",
28
+ "--lineage=review-afeed0d5a9aa24a1",
29
+ "--agent=pi",
30
+ ],
31
+ );
32
+ assert.deepEqual(closure.statusContinuation?.raw, fixture.status_continuation);
33
+ });
34
+
35
+ test("correction status continuation stays bound to its enclosing closure and provider target", () => {
36
+ const mismatch = `sha256:${"b".repeat(64)}`;
37
+ const cases = [
38
+ {
39
+ name: "missing binding lineage",
40
+ mutate: (binding: Record<string, unknown>) => { delete binding.lineage_id; },
41
+ error: /lineage does not match its enclosing closure/,
42
+ },
43
+ {
44
+ name: "different binding lineage",
45
+ mutate: (binding: Record<string, unknown>) => { binding.lineage_id = "review-different"; },
46
+ error: /lineage does not match its enclosing closure/,
47
+ },
48
+ {
49
+ name: "missing binding revision",
50
+ mutate: (binding: Record<string, unknown>) => { delete binding.revision; },
51
+ error: /revision does not match its enclosing closure/,
52
+ },
53
+ {
54
+ name: "different binding revision",
55
+ mutate: (binding: Record<string, unknown>) => { binding.revision = mismatch; },
56
+ error: /revision does not match its enclosing closure/,
57
+ },
58
+ ];
59
+ for (const scenario of cases) {
60
+ const fixture = captured("last-event-capture-result-correction-required.captured.json");
61
+ const continuation = fixture.status_continuation as Record<string, unknown>;
62
+ scenario.mutate(continuation.binding as Record<string, unknown>);
63
+ assert.throws(() => decodeReviewLastEventClosureV1(fixture), scenario.error, scenario.name);
64
+ }
65
+
66
+ const argumentMismatch = captured("last-event-capture-result-correction-required.captured.json");
67
+ const continuation = argumentMismatch.status_continuation as Record<string, unknown>;
68
+ const argumentsList = continuation.arguments as Array<Record<string, unknown>>;
69
+ const lineageArgument = argumentsList.find((argument) => argument.name === "lineage")!;
70
+ lineageArgument.value = "review-different";
71
+ lineageArgument.token = "--lineage=review-different";
72
+ assert.throws(() => decodeReviewLastEventClosureV1(argumentMismatch), /lineage argument does not match its enclosing closure/);
73
+
74
+ const closure = decodeReviewLastEventClosureV1(captured("last-event-capture-result-correction-required.captured.json"));
75
+ assert.throws(
76
+ () => assertReviewLastEventClosureBinding(closure, { lineageId: closure.lineageId, targetIdentity: mismatch }),
77
+ /status continuation target does not match its provider binding/,
78
+ );
79
+ });
80
+
81
+ test("correction-required result and refuter closures require one status continuation", () => {
82
+ for (const name of [
83
+ "last-event-capture-result-correction-required.captured.json",
84
+ "last-event-capture-refuter-correction-required.captured.json",
85
+ ]) {
86
+ const fixture = captured(name);
87
+ delete fixture.status_continuation;
88
+ assert.throws(() => decodeReviewLastEventClosureV1(fixture), /requires status_continuation/, name);
89
+ }
90
+ });
91
+
92
+ test("closures outside result or refuter correction-required forbid a status continuation", () => {
93
+ const approved = captured("last-event-capture-result-approved.captured.json");
94
+ approved.status_continuation = captured("last-event-capture-result-correction-required.captured.json").status_continuation;
95
+ assert.throws(() => decodeReviewLastEventClosureV1(approved), /status_continuation is only valid/, "approved result closure");
96
+ });
97
+
98
+ test("current capture fails closed when fresh STATUS no longer offers the corrected candidate binding", async () => {
99
+ const lineageId = "corrected-candidate";
100
+ const sha = `sha256:${"a".repeat(64)}`;
101
+ const binding = {
102
+ name: "provider_targeted_validator",
103
+ schema: "https://gentle-ai.dev/schema/review/targeted-validator/v1",
104
+ captureOperation: "review.capture-validation",
105
+ arguments: [
106
+ { name: "lineage", value: lineageId, token: `--lineage=${lineageId}` },
107
+ { name: "target", value: sha, token: `--target=${sha}` },
108
+ { name: "agent", value: "pi", token: "--agent=pi" },
109
+ { name: "execute", value: "true", token: "--execute=true" },
110
+ ],
111
+ };
112
+ let captureCalls = 0;
113
+ const native = {
114
+ targetStatus: async () => ({
115
+ contract: "gentle-ai.review-integration/v2",
116
+ applicability: "current_target",
117
+ authority: { version: "compact-v2", lineageId, state: "correction_required", generation: 1, revision: sha },
118
+ receipt: { status: "expected_missing" },
119
+ action: "stop",
120
+ replayability: "not_replayable",
121
+ targetIdentity: sha,
122
+ projection: { schema: "gentle-ai.review-candidate-projection/v1", kind: "current-changes", projection: "workspace", baseTree: "b".repeat(40), initialReviewTree: "b".repeat(40), currentCandidateTree: "c".repeat(40), pathsDigest: sha, paths: ["corrected.ts"], intendedUntracked: [], intendedUntrackedProof: sha, initialSnapshotIdentity: sha, currentSnapshotIdentity: sha },
123
+ repair: { schema: "gentle-ai.review-authority-repair-assessment/v1", status: "unsupported", counts: { lineages: 0, compactLineages: 0, legacyLineages: 0, events: 0, bytes: 0, eligibleCandidates: 0, unsupportedLineages: 0, conflicts: 0 }, supportedOperations: [], authorizationSchema: "gentle-ai.review-repair-authorization/v1" },
124
+ candidates: [],
125
+ nextTransition: { kind: "stop", reasonCode: "corrected_candidate_unavailable" },
126
+ raw: { schema: "gentle-ai.review-integration.status/v5" },
127
+ } as unknown as ReviewStatusV3),
128
+ captureProviderRole: async () => { captureCalls += 1; throw new Error("must not capture"); },
129
+ } as unknown as NativeReviewCli;
130
+ const result = await __testing.executeReviewCaptureOperation({ lineageId, collectBinding: JSON.stringify(binding) }, process.cwd(), native);
131
+ assert.equal(result.outcome, "capture-binding-rejected");
132
+ assert.equal(result.mutation_outcome, "none");
133
+ assert.equal(captureCalls, 0);
134
+ });
@@ -0,0 +1,145 @@
1
+ import assert from "node:assert/strict";
2
+ import { execFileSync } from "node:child_process";
3
+ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
6
+ import test from "node:test";
7
+ import { __testing } from "../extensions/gentle-ai.ts";
8
+ import type { NativeReviewCli } from "../lib/native-review-cli.ts";
9
+ import { CandidateViewError, CandidateViewRegistry, injectReviewCandidateView } from "../lib/review-candidate-view.ts";
10
+ import type { ReviewCollectInputV3, ReviewStatusV3 } from "../lib/review-integration-v2.ts";
11
+
12
+ // Field report (2026-08-16, gentle-pi main 402f9f77 + gentle-ai
13
+ // 2.4.0-main.20278905): after #340 the FINALIZE routing defect was fixed but
14
+ // the Pi relay's candidate-view registration still refused for an externally
15
+ // recovered successor. Reproduced against the live binary: hydration itself
16
+ // works for that shape (a dirty LINKED worktree hydrates fine) — the gap is
17
+ // that hydration was wired into the STATUS controller operation ONLY, while
18
+ // the maintainer's flow is `finalize` (now correctly blocked with
19
+ // review.capture-result) followed by a reviewer dispatch. The FINALIZE lane
20
+ // decodes the same authoritative status and never hydrated from it.
21
+ //
22
+ // Second defect in the same area: every hydration failure was swallowed by a
23
+ // bare catch, so the later dispatch refusal claimed no binding existed
24
+ // without ever admitting hydration had been attempted and why it failed.
25
+
26
+ const SHA = `sha256:${"1".repeat(64)}`;
27
+
28
+ function git(cwd: string, ...arguments_: string[]): string {
29
+ return execFileSync("git", arguments_, { cwd, encoding: "utf8" }).trim();
30
+ }
31
+
32
+ /** A LINKED worktree (not the primary checkout) with dirty tracked files. */
33
+ function linkedDirtyWorktree(t: test.TestContext): string {
34
+ const root = mkdtempSync(join(tmpdir(), "gentle-pi-hydration-gap-"));
35
+ t.after(() => rmSync(root, { recursive: true, force: true }));
36
+ const primary = join(root, "primary");
37
+ const linked = join(root, "linked");
38
+ mkdirSync(primary, { recursive: true });
39
+ git(primary, "init", "-b", "main");
40
+ git(primary, "config", "user.name", "Hydration Test");
41
+ git(primary, "config", "user.email", "hydration@example.invalid");
42
+ mkdirSync(join(primary, "internal"), { recursive: true });
43
+ writeFileSync(join(primary, "internal", "parser.go"), "package internal\n\nfunc Parse(s string) string {\n\treturn s\n}\n");
44
+ git(primary, "add", "-A");
45
+ git(primary, "commit", "-m", "base");
46
+ git(primary, "worktree", "add", linked, "-b", "feature/fence-info");
47
+ // Uncommitted tracked modification, exactly the field shape.
48
+ writeFileSync(join(linked, "internal", "parser.go"), "package internal\n\nimport \"strings\"\n\nfunc Parse(s string) string {\n\treturn strings.TrimSpace(s)\n}\n");
49
+ return linked;
50
+ }
51
+
52
+ function reviewerResultCollectInput(lineageId: string, lens: string): ReviewCollectInputV3 {
53
+ return {
54
+ name: "reviewer_result",
55
+ schema: "https://gentle-ai.dev/schema/review/reviewer/v1",
56
+ captureOperation: "review.capture-result",
57
+ arguments: [
58
+ { name: "lineage", value: lineageId, token: `--lineage=${lineageId}` },
59
+ { name: "expected-revision", value: SHA, token: `--expected-revision=${SHA}` },
60
+ { name: "lens", value: lens, token: `--lens=${lens}` },
61
+ { name: "order", value: "0", token: "--order=0" },
62
+ ],
63
+ artifactSubject: {
64
+ schema: "gentle-ai.review-artifact-subject/v2",
65
+ subjectHash: SHA,
66
+ lineageId,
67
+ authorityRevision: SHA,
68
+ targetIdentity: SHA,
69
+ baseTree: "3".repeat(40),
70
+ candidateTree: "4".repeat(40),
71
+ changedPathManifestSha256: SHA,
72
+ lens,
73
+ selectedOrder: 0,
74
+ },
75
+ } as unknown as ReviewCollectInputV3;
76
+ }
77
+
78
+ function successorStatus(lineageId: string, projection: { baseTree: string; currentCandidateTree: string; paths: readonly string[] }): ReviewStatusV3 {
79
+ return {
80
+ contract: "gentle-ai.review-integration/v2",
81
+ applicability: "current_target",
82
+ authority: { version: "compact-v2", lineageId, state: "reviewer_results_required", generation: 2, revision: SHA },
83
+ receipt: { status: "expected_missing" },
84
+ action: "finalize",
85
+ replayability: "not_replayable",
86
+ targetIdentity: SHA,
87
+ projection: {
88
+ schema: "gentle-ai.review-integration.projection/v1",
89
+ kind: "current-changes",
90
+ projection: "workspace",
91
+ baseTree: projection.baseTree,
92
+ initialReviewTree: projection.currentCandidateTree,
93
+ currentCandidateTree: projection.currentCandidateTree,
94
+ pathsDigest: SHA,
95
+ paths: [...projection.paths],
96
+ intendedUntracked: [],
97
+ intendedUntrackedProof: SHA,
98
+ initialSnapshotIdentity: SHA,
99
+ currentSnapshotIdentity: SHA,
100
+ },
101
+ candidates: [],
102
+ nextTransition: { kind: "collect", reasonCode: "reviewer_results_required", collect: { inputs: [reviewerResultCollectInput(lineageId, "review-reliability")] } },
103
+ raw: { schema: "gentle-ai.review-integration.status/v5", action: "finalize", lineage_id: lineageId },
104
+ } as unknown as ReviewStatusV3;
105
+ }
106
+
107
+ /** The live frozen identity of the dirty linked worktree candidate. */
108
+ function liveProjection(cwd: string): { baseTree: string; currentCandidateTree: string; paths: readonly string[] } {
109
+ const probe = new CandidateViewRegistry();
110
+ const view = probe.create({ contributorRoot: cwd });
111
+ try {
112
+ return { baseTree: view.baseTree, currentCandidateTree: view.candidateTree, paths: view.paths };
113
+ } finally {
114
+ probe.cleanup(view.token);
115
+ }
116
+ }
117
+
118
+ function statusNative(status: ReviewStatusV3): NativeReviewCli {
119
+ return {
120
+ targetStatus: async () => status,
121
+ finalize: async () => { throw new Error("native finalize must not run while reviewer results are outstanding"); },
122
+ } as unknown as NativeReviewCli;
123
+ }
124
+
125
+ async function runController(parameters: Record<string, unknown>, cwd: string, native: NativeReviewCli, candidateViews: CandidateViewRegistry): Promise<Record<string, unknown>> {
126
+ return await __testing.executeReviewControllerOperation(
127
+ parameters, cwd, native, undefined, candidateViews,
128
+ ) as Record<string, unknown>;
129
+ }
130
+
131
+ test("STATUS keeps hydrating and stays read-only when hydration fails", async (t) => {
132
+ const cwd = linkedDirtyWorktree(t);
133
+ const lineageId = "status-drifted-successor";
134
+ const registry = new CandidateViewRegistry();
135
+ const statusLive = liveProjection(cwd);
136
+ const drifted = { ...statusLive, currentCandidateTree: statusLive.baseTree };
137
+ // STATUS must never fail because hydration failed.
138
+ const envelope = await runController({ operation: "status", lineageId }, cwd, statusNative(successorStatus(lineageId, drifted)), registry);
139
+ assert.equal(envelope.operation, "status");
140
+ assert.equal(registry.hasCurrentBinding(), false);
141
+ assert.throws(
142
+ () => injectReviewCandidateView({ agent: "review-reliability", task: "review", mode: "task" }, registry),
143
+ (error: unknown) => error instanceof CandidateViewError && error.reason === "current-binding-hydration-failed",
144
+ );
145
+ });
@@ -4,7 +4,6 @@ import { chmodSync, mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:f
4
4
  import { tmpdir } from "node:os";
5
5
  import { join } from "node:path";
6
6
  import test from "node:test";
7
- import { __testing } from "../extensions/gentle-ai.ts";
8
7
  import {
9
8
  EXTERNAL_RELEASE_EVIDENCE,
10
9
  GATE_RESULT,
@@ -175,14 +174,6 @@ function temporaryAuthority(t: test.TestContext): GateRepository & {
175
174
  return { ...repository, store, receipt: receiptFor(state), authoritativeReceipt: store.createAuthoritativeReceipt("approved-lineage") };
176
175
  }
177
176
 
178
- test("lifecycle command classification identifies gates but never runs review routing", () => {
179
- assert.equal(__testing.classifyReviewEvent("git commit -m fix"), "pre-commit");
180
- assert.equal(__testing.classifyReviewEvent("git -C /repo push origin main"), "pre-push");
181
- assert.equal(__testing.classifyReviewEvent("gh pr create --draft"), "pre-pr");
182
- assert.equal(__testing.classifyReviewEvent("gh release create v1.2.3"), "pre-release");
183
- assert.equal(__testing.classifyReviewEvent("git status"), null);
184
- });
185
-
186
177
  test("unbranded receipts are rejected before lifecycle gate evaluation", (t) => {
187
178
  const { repository, finalTree, store, receipt } = temporaryAuthority(t);
188
179
  execFileSync("git", ["read-tree", finalTree], { cwd: repository });
@@ -589,42 +580,6 @@ test("scope child claim and parent gate journal publish atomically across faults
589
580
  assert.equal(store.read(receipt.body.lineage_id).child_claims?.length, 1);
590
581
  });
591
582
 
592
- test("receipt gate cannot bypass independent dangerous-command safety", async () => {
593
- let safetyCalls = 0;
594
- let gateCalls = 0;
595
- const safetyBlock = { block: true, reason: "dangerous command denied" };
596
- const result = await __testing.enforceReviewGateAndCommandSafety(
597
- "git push origin main",
598
- () => {
599
- gateCalls += 1;
600
- return undefined;
601
- },
602
- async () => {
603
- safetyCalls += 1;
604
- return safetyBlock;
605
- },
606
- );
607
- assert.deepEqual(result, safetyBlock);
608
- assert.equal(safetyCalls, 1);
609
- assert.equal(gateCalls, 0);
610
-
611
- const gateBlock = { block: true, reason: "exact receipt required" };
612
- const blocked = await __testing.enforceReviewGateAndCommandSafety(
613
- "git push origin main",
614
- () => {
615
- gateCalls += 1;
616
- return gateBlock;
617
- },
618
- async () => {
619
- safetyCalls += 1;
620
- return undefined;
621
- },
622
- );
623
- assert.deepEqual(blocked, gateBlock);
624
- assert.equal(safetyCalls, 2);
625
- assert.equal(gateCalls, 1);
626
- });
627
-
628
583
  function releaseTarget(repository: GateRepository): GateTargetV1 {
629
584
  return {
630
585
  kind: GATE_TARGET_KIND.RELEASE,