gentle-pi 2.2.0 → 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 (130) hide show
  1. package/README.md +51 -80
  2. package/assets/agents/review-readability.md +0 -2
  3. package/assets/agents/review-reliability.md +0 -2
  4. package/assets/agents/review-resilience.md +0 -2
  5. package/assets/agents/review-risk.md +0 -2
  6. package/assets/agents/sdd-apply.md +5 -3
  7. package/assets/agents/sdd-proposal.md +2 -0
  8. package/assets/agents/sdd-research.md +54 -0
  9. package/assets/agents/sdd-status.md +4 -4
  10. package/assets/agents/sdd-tasks.md +2 -3
  11. package/assets/agents/sdd-verify.md +21 -1
  12. package/assets/chains/sdd-full.chain.md +1 -1
  13. package/assets/chains/sdd-verify.chain.md +1 -1
  14. package/assets/orchestrator-delegation.md +54 -248
  15. package/assets/orchestrator-memory.md +2 -0
  16. package/assets/orchestrator.md +22 -38
  17. package/assets/sdd-orchestrator-workflow.md +30 -37
  18. package/assets/support/sdd-status-contract.md +7 -7
  19. package/contracts/review-integration/v1/schemas/transition-execution.schema.json +42 -0
  20. package/contracts/review-integration/v2/schemas/last-event-closure.schema.json +66 -0
  21. package/contracts/review-integration/v2/schemas/opencode-provider-role.schema.json +14 -0
  22. package/docs/native-authority-architecture.md +9 -11
  23. package/docs/review-integration.md +27 -373
  24. package/extensions/ask-user-choice.ts +151 -0
  25. package/extensions/gentle-ai.ts +1778 -3673
  26. package/extensions/quiet-tools.ts +515 -32
  27. package/extensions/sdd-init.ts +4 -8
  28. package/lib/gentle-ai-renderer.ts +70 -0
  29. package/lib/model-routing-authority.ts +133 -0
  30. package/lib/native-review-cli.ts +372 -940
  31. package/lib/opaque-pi-reviewer-adapter.ts +284 -0
  32. package/lib/review-candidate-view.ts +341 -132
  33. package/lib/review-host-relay.ts +210 -68
  34. package/lib/review-integration-v2.ts +839 -307
  35. package/lib/review-last-event-controller.ts +35 -0
  36. package/lib/sdd-preflight.ts +177 -66
  37. package/lib/sdd-status.ts +66 -111
  38. package/lib/terminal-theme.ts +1 -1
  39. package/package.json +83 -82
  40. package/runtime/gentle-ai-binary.mjs +1 -1
  41. package/runtime/native-review-cli.mjs +322 -890
  42. package/runtime/review-integration-v2.mjs +789 -257
  43. package/runtime/review-relay-contract.mjs +1 -1
  44. package/scripts/{build-git-commit-transaction-runner.mjs → build-runtime-modules.mjs} +4 -5
  45. package/scripts/gentle-ai-installer.mjs +75 -22
  46. package/scripts/maintainer/provider-relay-matrix.mjs +195 -11
  47. package/scripts/test-packed-runner.mjs +4 -7
  48. package/scripts/verify-package-files.mjs +12 -13
  49. package/skills/_shared/review-ledger-contract.md +8 -14
  50. package/skills/chained-pr/SKILL.md +3 -0
  51. package/skills/cognitive-doc-design/SKILL.md +1 -1
  52. package/skills/comment-writer/SKILL.md +1 -1
  53. package/skills/gentle-ai/SKILL.md +7 -74
  54. package/skills/judgment-day/SKILL.md +5 -7
  55. package/skills/rdd-defect-workflow/SKILL.md +3 -3
  56. package/skills/release/SKILL.md +3 -3
  57. package/skills/skill-registry/SKILL.md +1 -1
  58. package/skills/work-unit-commits/SKILL.md +3 -1
  59. package/tests/artifact-language.test.ts +24 -11
  60. package/tests/ask-user-choice.test.ts +264 -0
  61. package/tests/codegraph-tools.test.ts +3 -3
  62. package/tests/crosslane/cross-lane.mjs +15 -1168
  63. package/tests/delegated-key-learnings-contract.test.ts +8 -6
  64. package/tests/devbinary/native-review-parity.devtest.ts +167 -250
  65. package/tests/devbinary/pi-host-relay.devtest.ts +867 -0
  66. package/tests/fixtures/devbinary/last-event-capture-correction-plan.captured.json +10 -0
  67. package/tests/fixtures/devbinary/last-event-capture-refuter-approved.captured.json +20 -0
  68. package/tests/fixtures/devbinary/last-event-capture-refuter-correction-required.captured.json +26 -0
  69. package/tests/fixtures/devbinary/last-event-capture-result-approved.captured.json +8 -0
  70. package/tests/fixtures/devbinary/last-event-capture-result-correction-required.captured.json +26 -0
  71. package/tests/fixtures/devbinary/last-event-capture-validation-approved.captured.json +8 -0
  72. package/tests/fixtures/devbinary/last-event-closure.provenance.md +13 -0
  73. package/tests/fixtures/devbinary/review-acknowledged-v1.captured.json +9 -0
  74. package/tests/fixtures/devbinary/review-acknowledged.provenance.md +31 -0
  75. package/tests/fixtures/devbinary/start-v3-zero-lens-closed.captured.json +21 -0
  76. package/tests/fixtures/native-review-cli/v2.5.0-rc.1/PROVENANCE.txt +15 -0
  77. package/tests/gentle-ai-binary.test.ts +83 -13
  78. package/tests/gentle-ai-dev-binary.test.ts +51 -228
  79. package/tests/gentle-ai-installer.test.ts +137 -47
  80. package/tests/gentle-ai.test.ts +820 -131
  81. package/tests/gentle-theme.test.ts +133 -0
  82. package/tests/maintainer/provider-relay.maintest.ts +340 -4
  83. package/tests/model-routing-authority.test.ts +257 -0
  84. package/tests/native-review-capability-contract.test.ts +28 -1
  85. package/tests/native-review-cli.test.ts +586 -962
  86. package/tests/native-review-consent.test.ts +150 -66
  87. package/tests/native-review-parity-runtime.test.ts +80 -359
  88. package/tests/native-review-parity.test.ts +674 -925
  89. package/tests/opaque-pi-reviewer-adapter.test.ts +266 -0
  90. package/tests/orchestrator-budget.test.ts +132 -88
  91. package/tests/orchestrator-rdd-ownership.test.ts +103 -0
  92. package/tests/package-manifest.test.ts +37 -35
  93. package/tests/provider-defect-handoff.test.ts +15 -118
  94. package/tests/quiet-tool-rendering.test.ts +1055 -28
  95. package/tests/review-authority-recovery-docs.test.ts +1 -2
  96. package/tests/review-candidate-view.test.ts +409 -3
  97. package/tests/review-compact-contract.test.ts +4 -30
  98. package/tests/review-controller-lock-status.test.ts +2 -2
  99. package/tests/review-controller-native-recovery.test.ts +363 -1450
  100. package/tests/review-controller-native-routing.test.ts +1251 -5642
  101. package/tests/review-controller-retired-ops.test.ts +1 -1
  102. package/tests/review-controller-workspace-root.test.ts +222 -71
  103. package/tests/review-controller.test.ts +26 -816
  104. package/tests/review-corrected-finalize-binding.test.ts +112 -153
  105. package/tests/review-dispatch-hydration-gap.test.ts +1 -53
  106. package/tests/review-gate.test.ts +0 -45
  107. package/tests/review-host-relay-restart-parity.test.ts +360 -0
  108. package/tests/review-host-relay-routing.test.ts +117 -82
  109. package/tests/review-host-relay.test.ts +242 -8
  110. package/tests/review-integration-v2-forward.test.ts +576 -553
  111. package/tests/review-integration-v2.test.ts +239 -165
  112. package/tests/review-last-event-closure.test.ts +408 -0
  113. package/tests/review-ledger-contract.test.ts +97 -35
  114. package/tests/review-recovered-lineage-routing.test.ts +0 -47
  115. package/tests/review-relay-transport-agent.test.ts +119 -46
  116. package/tests/review-snapshot.test.ts +3 -2
  117. package/tests/runtime-harness.mjs +361 -196
  118. package/tests/sdd-agent-tools.test.ts +36 -0
  119. package/tests/sdd-preflight.test.ts +81 -15
  120. package/tests/sdd-status.test.ts +109 -110
  121. package/tests/skill-collision-prefixes.test.ts +5 -8
  122. package/tests/writer-edit-surface-scope.test.ts +230 -0
  123. package/themes/Gentleman-Cute.json +94 -0
  124. package/themes/Gentleman-Sexy.json +92 -0
  125. package/lib/git-commit-transaction.ts +0 -861
  126. package/runtime/git-commit-transaction.mjs +0 -862
  127. package/scripts/run-git-commit-transaction.mjs +0 -35
  128. package/tests/fixtures/native-review-cli/v2.1.3/sdd-status-engram.json +0 -139
  129. package/tests/fixtures/native-review-cli/v2.1.3/sdd-status.json +0 -200
  130. package/tests/git-commit-transaction.test.ts +0 -530
@@ -0,0 +1,10 @@
1
+ {
2
+ "schema": "gentle-ai.review-last-event-closure/v1",
3
+ "operation": "review.capture-correction-plan",
4
+ "lineage_id": "review-afeed0d5a9aa24a1",
5
+ "target_identity": "sha256:ccbd92bd891a944ceeb21be0982a5f0dc515ed94a0f15da0a64d21608329aa59",
6
+ "request_hash": "sha256:0f6dd21a64f76566fdcd8c8f4e87157e1658d207798c2dbd1510d7001af9d243",
7
+ "correction_lines": 2,
8
+ "state": "correction_required",
9
+ "store_revision": "sha256:5a5d22f4f65b0adeca5e2c3a3da0509ced6197a067aa7ffe4b9b040acad4194e"
10
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "schema": "gentle-ai.review-last-event-closure/v1",
3
+ "operation": "review.capture-refuter",
4
+ "lineage_id": "review-61da8af1a89ff96a",
5
+ "state": "approved",
6
+ "action": "the approved review completed on the last admitted event and burned; delivery follows ordinary repository policy",
7
+ "advisory_findings": {
8
+ "statement": "This review is approved and its receipt stands. Every finding listed here is non-blocking: none opened a correction, none reopens this review, and no correction transition is offered for this candidate. Treat them as separate later work, never as a reason to re-run review on this candidate.",
9
+ "findings": [
10
+ {
11
+ "id": "R3-001",
12
+ "lens": "reliability",
13
+ "location": "src/value.js:2",
14
+ "severity": "BLOCKER",
15
+ "disposition": "refuted"
16
+ }
17
+ ]
18
+ },
19
+ "store_revision": "sha256:05588b2784a07c5619af879b97f6e023e35f5112185b42c173ed8f5064cf1b34"
20
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "schema": "gentle-ai.review-last-event-closure/v1",
3
+ "operation": "review.capture-refuter",
4
+ "lineage_id": "review-2c1f3ae8139e6fe3",
5
+ "state": "correction_required",
6
+ "action": "candidate-caused severe findings require one bounded correction",
7
+ "status_continuation": {
8
+ "operation": "review.status",
9
+ "arguments": [
10
+ {"name": "cwd", "value": "/tmp/repository", "token": "--cwd=/tmp/repository"},
11
+ {"name": "contract", "value": "gentle-ai.review-integration/v2", "token": "--contract=gentle-ai.review-integration/v2"},
12
+ {"name": "next-transition", "value": "true", "token": "--next-transition=true"},
13
+ {"name": "lineage", "value": "review-2c1f3ae8139e6fe3", "token": "--lineage=review-2c1f3ae8139e6fe3"},
14
+ {"name": "agent", "value": "pi", "token": "--agent=pi"}
15
+ ],
16
+ "preconditions": [
17
+ {"name": "state", "value": "correction_required"}
18
+ ],
19
+ "binding": {
20
+ "lineage_id": "review-2c1f3ae8139e6fe3",
21
+ "revision": "sha256:de7f54f5c7eca7c1095ab6d8fcd8a3eb05f4a01733129d3e2fa0f32db30b5457",
22
+ "target_identity": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
23
+ }
24
+ },
25
+ "store_revision": "sha256:de7f54f5c7eca7c1095ab6d8fcd8a3eb05f4a01733129d3e2fa0f32db30b5457"
26
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "schema": "gentle-ai.review-last-event-closure/v1",
3
+ "operation": "review/capture-result",
4
+ "lineage_id": "review-c7c923a031112dd7",
5
+ "state": "approved",
6
+ "action": "the approved review completed on the last admitted event and burned; delivery follows ordinary repository policy",
7
+ "store_revision": "sha256:108e0ba4f5af3f69b44d2fb1057a5054e968cc38f050d2e9a30f58c00bbf7e88"
8
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "schema": "gentle-ai.review-last-event-closure/v1",
3
+ "operation": "review/capture-result",
4
+ "lineage_id": "review-afeed0d5a9aa24a1",
5
+ "state": "correction_required",
6
+ "action": "candidate-caused severe findings require one bounded correction",
7
+ "status_continuation": {
8
+ "operation": "review.status",
9
+ "arguments": [
10
+ {"name": "cwd", "value": "/tmp/repository", "token": "--cwd=/tmp/repository"},
11
+ {"name": "contract", "value": "gentle-ai.review-integration/v2", "token": "--contract=gentle-ai.review-integration/v2"},
12
+ {"name": "next-transition", "value": "true", "token": "--next-transition=true"},
13
+ {"name": "lineage", "value": "review-afeed0d5a9aa24a1", "token": "--lineage=review-afeed0d5a9aa24a1"},
14
+ {"name": "agent", "value": "pi", "token": "--agent=pi"}
15
+ ],
16
+ "preconditions": [
17
+ {"name": "state", "value": "correction_required"}
18
+ ],
19
+ "binding": {
20
+ "lineage_id": "review-afeed0d5a9aa24a1",
21
+ "revision": "sha256:3c274f44e4d05c748bae28978396f28a4d833500b41f00942f315261e8bc9749",
22
+ "target_identity": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
23
+ }
24
+ },
25
+ "store_revision": "sha256:3c274f44e4d05c748bae28978396f28a4d833500b41f00942f315261e8bc9749"
26
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "schema": "gentle-ai.review-last-event-closure/v1",
3
+ "operation": "review/capture-validation",
4
+ "lineage_id": "review-afeed0d5a9aa24a1",
5
+ "state": "approved",
6
+ "action": "the approved review completed on the last admitted event and burned; delivery follows ordinary repository policy",
7
+ "store_revision": "sha256:dddb4347e0a391d434a95a2612f22a16a8d9cc5af03a8a7b8546c3936cf88043"
8
+ }
@@ -0,0 +1,13 @@
1
+ # Last-event closure fixture provenance
2
+
3
+ Captured 2026-08-23 from `gentle-ai dev` at the maintainer-provided disposable sandbox candidate `gentle-ai-dev`.
4
+
5
+ Commands were run against disposable Git repositories with isolated `HOME` and `TMPDIR` beneath `.scratch/issue-404-fixtures`:
6
+
7
+ - `gentle-ai review start ... --contract=gentle-ai.review-integration/v2 --agent=pi --consent=relay`
8
+ - `gentle-ai review capture-result ... --input=<reviewer-result>`
9
+ - `gentle-ai review capture-correction-plan ... --correction-lines=2`
10
+ - `gentle-ai review capture-refuter ... --agent=pi --execute=true`
11
+ - `gentle-ai review capture-validation ... --agent=pi --execute=true`
12
+
13
+ The sandbox was removed after the captures. The JSON fixtures are byte copies of the native stdout responses.
@@ -0,0 +1,9 @@
1
+ {
2
+ "schema": "gentle-ai.review-acknowledged/v1",
3
+ "operation": "review/acknowledge-approved",
4
+ "action": "acknowledged",
5
+ "lineage_id": "review-3ec95251db75f626",
6
+ "target_identity": "sha256:b505dcd8d82395c053c9786935e11e0e235cbdecca4f1f46f98c768ea6248d3d",
7
+ "consumed_revision": "sha256:9732b1c3526bfecd3851093239241145c970cc126acea59bfaf14133214b60ee",
8
+ "authority": "burned"
9
+ }
@@ -0,0 +1,31 @@
1
+ # review-acknowledged/v1 fixture provenance
2
+
3
+ `review-acknowledged-v1.captured.json` is a byte copy of the stdout that
4
+ `gentle-ai review acknowledge-approved` printed when it burned one approved
5
+ compact authority. gentle-ai #3947 (main commit bc9f74d2) made that command
6
+ print this `gentle-ai.review-acknowledged/v1` envelope; every published
7
+ release up to and including v2.5.0-rc.3 burns the same authority with EMPTY
8
+ stdout (verified below).
9
+
10
+ - Captured 2026-08-31T08:51:50Z with the installed `gentle-ai 2.5.0-main.bc9f74d2`
11
+ (`gentle-ai --version` banner) in a disposable Git repository under an isolated
12
+ `HOME`; RDD was enabled only in that isolated HOME
13
+ (`gentle-ai review mode enable --scope global`).
14
+ - Candidate: one uncommitted line appended to a committed `README.md`
15
+ (`non_executable_only`, zero-lens, START closed `approved`).
16
+ - Commands, each the exact vector the previous envelope returned
17
+ (`GENTLE_PI_REVIEW_RELAY_CONTRACT=gentle-pi.review-relay/v1` set for the Pi
18
+ transport handshake):
19
+ 1. `gentle-ai review status --cwd <repo> --contract gentle-ai.review-integration/v2 --agent pi --next-transition`
20
+ 2. `gentle-ai review start --cwd=<repo> --contract=gentle-ai.review-integration/v2 --target=sha256:b505dcd8… --projection=workspace --lineage=review-3ec95251db75f626 --agent=pi --consent=relay`
21
+ 3. `gentle-ai review status … --lineage review-3ec95251db75f626 --next-transition` (offered `review.acknowledge-approved`)
22
+ 4. `gentle-ai review acknowledge-approved --cwd=<repo> --lineage=review-3ec95251db75f626 --target=sha256:b505dcd8… --expected-revision=sha256:9732b1c3… --token=<provider-issued>` (exit 0, empty stderr; stdout is the fixture)
23
+ - Replaying step 4 refused with exit 1, empty stdout, and the stderr line
24
+ `approved acknowledgement names no live compact authority; it was already acknowledged and burned, …` (unchanged refusal shape).
25
+ - Pinned control: the same four steps against the pinned published
26
+ `gentle-ai 2.5.0-rc.3` binary (sha256 b69da0a51b03f326147498ae465fc1ec52eff8427d579964eefad714c3f9bd87,
27
+ the recorded asset digest) burned the authority with exit 0 and ZERO bytes on
28
+ stdout and stderr, which is the silent path `acknowledgeApproved` keeps
29
+ accepting byte-for-byte.
30
+
31
+ The disposable repositories and HOME were removed after the captures.
@@ -0,0 +1,21 @@
1
+ {
2
+ "schema": "gentle-ai.review-integration.start/v3",
3
+ "contract": "gentle-ai.review-integration/v2",
4
+ "operation": "review.start",
5
+ "action": "closed",
6
+ "lenses_required": false,
7
+ "lineage_id": "review-95b756a05f2e82b1",
8
+ "state": "approved",
9
+ "risk_level": "low",
10
+ "selected_lenses": [],
11
+ "projection": "workspace",
12
+ "changed_files": 1,
13
+ "changed_lines": 2,
14
+ "correction_budget": 1,
15
+ "risk_reasons": [
16
+ {
17
+ "code": "non_executable_only"
18
+ }
19
+ ],
20
+ "artifact_subjects": []
21
+ }
@@ -0,0 +1,15 @@
1
+ Captured from a real gentle-ai binary, not hand-authored.
2
+
3
+ binary: gentle-ai 2.5.0-rc.1.0.20260827194839-f654764e231f (published prerelease channel)
4
+ date: 2026-08-28
5
+ command: gentle-ai sdd-status agent-builder --cwd <repo> --json --instructions
6
+ repo: a gentle-ai checkout with an OpenSpec-backed change present
7
+
8
+ Why this fixture exists: gentle-ai commit 3c4c7fb6 (2026-08-24, "refactor(sdd)!:
9
+ replace status v1 with clean v2") moved SchemaVersion from 1 to 2. gentle-pi had
10
+ no v2 decoder, so lib/native-review-cli.ts rejected every current status with
11
+ IDENTITY_MISMATCH on `body.schemaVersion !== 1`.
12
+
13
+ The v2 object shape is key-compatible with the v1 decoder's exactObject list:
14
+ 17 required keys plus the optional phaseInstructions. Only the version value and
15
+ the widened artifactStore domain (which gains "hybrid") differ.
@@ -1,17 +1,23 @@
1
1
  import assert from "node:assert/strict";
2
2
  import { createHash } from "node:crypto";
3
- import { existsSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
4
4
  import { chmod, mkdir, mkdtemp, rm, symlink, writeFile } from "node:fs/promises";
5
5
  import { tmpdir } from "node:os";
6
- import { basename, isAbsolute, join } from "node:path";
6
+ import { basename, dirname, isAbsolute, join } from "node:path";
7
7
  import test from "node:test";
8
8
  import {
9
9
  GENTLE_AI_BINARY_MISSING_CODE,
10
+ GENTLE_AI_DEV_BINARY_ENV,
10
11
  GENTLE_AI_VERSION,
12
+ gentleAiDevBinaryRegistrationPath,
11
13
  PackageLocalGentleAiBinaryMissingError,
14
+ registerGentleAiDevBinary,
12
15
  resolveGentleAiBinary,
16
+ setGentleAiDevBinaryEnvironmentForTesting,
17
+ unregisterGentleAiDevBinary,
18
+ type GentleAiDevBinaryEnvironment,
13
19
  } from "../lib/gentle-ai-binary.ts";
14
- import { NativeReviewCliV213, createNativeReviewCli, type ExecFileAdapter } from "../lib/native-review-cli.ts";
20
+ import { NativeReviewCliV216, createNativeReviewCli, type ExecFileAdapter } from "../lib/native-review-cli.ts";
15
21
  import { GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM, resolveGentleAiReleaseAsset } from "../scripts/gentle-ai-installer.mjs";
16
22
  import { requireNativeBinary } from "./support/native-binary-gate.ts";
17
23
 
@@ -29,6 +35,47 @@ const nativeBinaryGate = requireNativeBinary({
29
35
  if (!nativeBinaryGate.run) console.log(`gentle-ai-binary: ${nativeBinaryGate.reason}`);
30
36
  const verifiedBinaryTest = nativeBinaryGate.run && process.platform !== "win32" ? test : test.skip;
31
37
 
38
+ interface PinnedBinaryIsolation {
39
+ environment: GentleAiDevBinaryEnvironment;
40
+ savedEnvironmentValue: string | undefined;
41
+ savedRegistration: string | undefined;
42
+ }
43
+
44
+ let pinnedBinaryIsolation: PinnedBinaryIsolation | undefined;
45
+
46
+ test.beforeEach((t) => {
47
+ const home = mkdtempSync(join(tmpdir(), "gentle-pi-pinned-binary-home-"));
48
+ const environment: GentleAiDevBinaryEnvironment = { env: { ...process.env }, home };
49
+ delete environment.env.GENTLE_PI_CONFIG_HOME;
50
+ const savedEnvironmentValue = environment.env[GENTLE_AI_DEV_BINARY_ENV];
51
+ const registrationPath = gentleAiDevBinaryRegistrationPath(environment);
52
+ const savedRegistration = existsSync(registrationPath)
53
+ ? readFileSync(registrationPath, "utf8")
54
+ : undefined;
55
+
56
+ delete environment.env[GENTLE_AI_DEV_BINARY_ENV];
57
+ unregisterGentleAiDevBinary(environment);
58
+ setGentleAiDevBinaryEnvironmentForTesting(environment);
59
+ pinnedBinaryIsolation = { environment, savedEnvironmentValue, savedRegistration };
60
+
61
+ t.after(() => {
62
+ if (savedEnvironmentValue === undefined) {
63
+ delete environment.env[GENTLE_AI_DEV_BINARY_ENV];
64
+ } else {
65
+ environment.env[GENTLE_AI_DEV_BINARY_ENV] = savedEnvironmentValue;
66
+ }
67
+ if (savedRegistration === undefined) {
68
+ unregisterGentleAiDevBinary(environment);
69
+ } else {
70
+ mkdirSync(dirname(registrationPath), { recursive: true });
71
+ writeFileSync(registrationPath, savedRegistration);
72
+ }
73
+ setGentleAiDevBinaryEnvironmentForTesting(undefined);
74
+ pinnedBinaryIsolation = undefined;
75
+ rmSync(home, { recursive: true, force: true });
76
+ });
77
+ });
78
+
32
79
  async function writeVerifiedBinary(packageRoot: string, platform = process.platform): Promise<string> {
33
80
  const asset = resolveGentleAiReleaseAsset(platform, process.arch);
34
81
  const binaryPath = join(packageRoot, ".gentle-ai", RUNTIME_DIRECTORY, asset.executable);
@@ -50,7 +97,7 @@ async function writeWindowsSourceBinary(packageRoot: string): Promise<{ binaryPa
50
97
  method: "go-sumdb-source-build",
51
98
  package: "github.com/gentleman-programming/gentle-ai/v2/cmd/gentle-ai",
52
99
  module: "github.com/gentleman-programming/gentle-ai/v2",
53
- tag: "v2.4.0",
100
+ tag: "v2.5.0",
54
101
  architecture: process.arch === "x64" ? "x64" : "arm64",
55
102
  binarySha256: createHash("sha256").update(binary).digest("hex"),
56
103
  moduleChecksum: GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM,
@@ -64,13 +111,34 @@ async function writeWindowsSourceBinary(packageRoot: string): Promise<{ binaryPa
64
111
  return { binaryPath, manifestPath };
65
112
  }
66
113
 
67
- verifiedBinaryTest("runtime resolves an absolute package-local binary path without PATH fallback", async () => {
114
+ verifiedBinaryTest("runtime resolves an absolute package-local binary path without PATH fallback or ambient dev contamination", async () => {
68
115
  const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-binary-"));
69
116
  const executable = process.platform === "win32" ? "gentle-ai.exe" : "gentle-ai";
70
117
  const binaryPath = await writeVerifiedBinary(packageRoot);
118
+ const devBinary = join(packageRoot, "maintainer-dev-binary");
119
+ await writeFile(devBinary, "maintainer dev binary");
120
+ if (process.platform !== "win32") await chmod(devBinary, 0o700);
121
+ const isolation = pinnedBinaryIsolation;
122
+ assert.ok(isolation, "the pinned-binary fixture must install its isolated environment");
123
+
124
+ const ambientValue = process.env[GENTLE_AI_DEV_BINARY_ENV];
125
+ process.env[GENTLE_AI_DEV_BINARY_ENV] = devBinary;
126
+ try {
127
+ assert.equal(resolveGentleAiBinary(packageRoot, process.platform), binaryPath, "a maintainer's ambient override must not contaminate a pinned-package case");
128
+ } finally {
129
+ if (ambientValue === undefined) delete process.env[GENTLE_AI_DEV_BINARY_ENV];
130
+ else process.env[GENTLE_AI_DEV_BINARY_ENV] = ambientValue;
131
+ }
132
+
133
+ isolation.environment.env[GENTLE_AI_DEV_BINARY_ENV] = devBinary;
134
+ assert.equal(resolveGentleAiBinary(packageRoot, process.platform), devBinary, "an explicit dev-binary test may opt in through the isolated environment seam");
135
+ delete isolation.environment.env[GENTLE_AI_DEV_BINARY_ENV];
136
+ registerGentleAiDevBinary(devBinary, isolation.environment);
137
+ assert.equal(resolveGentleAiBinary(packageRoot, process.platform), devBinary, "an explicit persistent dev registration may opt in through the isolated environment seam");
138
+ assert.equal(unregisterGentleAiDevBinary(isolation.environment), true);
71
139
 
72
140
  const resolved = resolveGentleAiBinary(packageRoot, process.platform);
73
- assert.equal(resolved, binaryPath);
141
+ assert.equal(resolved, binaryPath, "clearing the explicit registration restores the pinned resolver");
74
142
  assert.equal(isAbsolute(resolved), true);
75
143
  assert.equal(basename(resolved), executable);
76
144
  assert.doesNotMatch(resolved, /(^|[/\\])PATH($|[/\\])/i);
@@ -124,7 +192,7 @@ test("runtime rejects an unverified binary, a symlinked manifest, and ambient ex
124
192
  await writeFile(manifestTarget, `${JSON.stringify({ version: GENTLE_AI_VERSION, asset: `gentle-ai_${GENTLE_AI_VERSION}_${process.platform}_${process.arch === "x64" ? "amd64" : process.arch}.tar.gz`, assetSha256: "a".repeat(64), binarySha256 })}\n`);
125
193
  await symlink(manifestTarget, manifestPath);
126
194
  assert.throws(() => resolveGentleAiBinary(packageRoot, process.platform), /package-local-binary-missing/);
127
- assert.throws(() => new NativeReviewCliV213(async () => VERSION, "gentle-ai"), /absolute package-local executable/);
195
+ assert.throws(() => new NativeReviewCliV216(async () => VERSION, "gentle-ai"), /absolute package-local executable/);
128
196
  });
129
197
 
130
198
  verifiedBinaryTest("runtime rejects malformed, unknown, wrong, and symlinked integrity paths", async () => {
@@ -224,16 +292,18 @@ verifiedBinaryTest("production native client never invokes a global gentle-ai ex
224
292
  const calls: string[] = [];
225
293
  const adapter: ExecFileAdapter = async (request) => {
226
294
  calls.push(request.file);
227
- if (request.arguments[0] === "version") return VERSION;
228
295
  return {
229
296
  ...VERSION,
230
- stdout: JSON.stringify({ operation: "review/start", lineage_id: "lineage", state: "reviewing", risk_level: "low", selected_lenses: [], changed_files: 0, changed_lines: 0, correction_budget: 0, action: "created", lenses_required: false, projection: "workspace" }),
297
+ stdout: readFileSync(join(import.meta.dirname, "..", "contracts", "review-integration", "v2", "fixtures", "status.fixture.json"), "utf8"),
231
298
  };
232
299
  };
233
300
 
234
- await createNativeReviewCli(adapter, () => resolveGentleAiBinary(packageRoot, process.platform)).start({ cwd: packageRoot });
235
- assert.deepEqual(calls, [binaryPath, binaryPath]);
301
+ await assert.rejects(
302
+ () => createNativeReviewCli(adapter, () => resolveGentleAiBinary(packageRoot, process.platform)).targetStatus!({ cwd: packageRoot, lineageId: "review-status-fixture" }),
303
+ /schema incompatible/,
304
+ );
305
+ assert.deepEqual(calls, [binaryPath]);
236
306
  assert.ok(calls.every((file) => file !== "gentle-ai"));
237
- assert.throws(() => new NativeReviewCliV213(adapter, "gentle-ai"), /absolute package-local executable/);
238
- assert.throws(() => new NativeReviewCliV213(adapter, "./gentle-ai"), /absolute package-local executable/);
307
+ assert.throws(() => new NativeReviewCliV216(adapter, "gentle-ai"), /absolute package-local executable/);
308
+ assert.throws(() => new NativeReviewCliV216(adapter, "./gentle-ai"), /absolute package-local executable/);
239
309
  });