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.
- package/README.md +51 -80
- package/assets/agents/review-readability.md +0 -2
- package/assets/agents/review-reliability.md +0 -2
- package/assets/agents/review-resilience.md +0 -2
- package/assets/agents/review-risk.md +0 -2
- package/assets/agents/sdd-apply.md +5 -3
- package/assets/agents/sdd-proposal.md +2 -0
- package/assets/agents/sdd-research.md +54 -0
- package/assets/agents/sdd-status.md +4 -4
- package/assets/agents/sdd-tasks.md +2 -3
- package/assets/agents/sdd-verify.md +21 -1
- package/assets/chains/sdd-full.chain.md +1 -1
- package/assets/chains/sdd-verify.chain.md +1 -1
- package/assets/orchestrator-delegation.md +54 -248
- package/assets/orchestrator-memory.md +2 -0
- package/assets/orchestrator.md +22 -38
- package/assets/sdd-orchestrator-workflow.md +30 -37
- package/assets/support/sdd-status-contract.md +7 -7
- package/contracts/review-integration/v1/schemas/transition-execution.schema.json +42 -0
- package/contracts/review-integration/v2/schemas/last-event-closure.schema.json +66 -0
- package/contracts/review-integration/v2/schemas/opencode-provider-role.schema.json +14 -0
- package/docs/native-authority-architecture.md +9 -11
- package/docs/review-integration.md +27 -373
- package/extensions/ask-user-choice.ts +151 -0
- package/extensions/gentle-ai.ts +1778 -3673
- package/extensions/quiet-tools.ts +515 -32
- package/extensions/sdd-init.ts +4 -8
- package/lib/gentle-ai-renderer.ts +70 -0
- package/lib/model-routing-authority.ts +133 -0
- package/lib/native-review-cli.ts +372 -940
- package/lib/opaque-pi-reviewer-adapter.ts +284 -0
- package/lib/review-candidate-view.ts +341 -132
- package/lib/review-host-relay.ts +210 -68
- package/lib/review-integration-v2.ts +839 -307
- package/lib/review-last-event-controller.ts +35 -0
- package/lib/sdd-preflight.ts +177 -66
- package/lib/sdd-status.ts +66 -111
- package/lib/terminal-theme.ts +1 -1
- package/package.json +83 -82
- package/runtime/gentle-ai-binary.mjs +1 -1
- package/runtime/native-review-cli.mjs +322 -890
- package/runtime/review-integration-v2.mjs +789 -257
- package/runtime/review-relay-contract.mjs +1 -1
- package/scripts/{build-git-commit-transaction-runner.mjs → build-runtime-modules.mjs} +4 -5
- package/scripts/gentle-ai-installer.mjs +75 -22
- package/scripts/maintainer/provider-relay-matrix.mjs +195 -11
- package/scripts/test-packed-runner.mjs +4 -7
- package/scripts/verify-package-files.mjs +12 -13
- package/skills/_shared/review-ledger-contract.md +8 -14
- package/skills/chained-pr/SKILL.md +3 -0
- package/skills/cognitive-doc-design/SKILL.md +1 -1
- package/skills/comment-writer/SKILL.md +1 -1
- package/skills/gentle-ai/SKILL.md +7 -74
- package/skills/judgment-day/SKILL.md +5 -7
- package/skills/rdd-defect-workflow/SKILL.md +3 -3
- package/skills/release/SKILL.md +3 -3
- package/skills/skill-registry/SKILL.md +1 -1
- package/skills/work-unit-commits/SKILL.md +3 -1
- package/tests/artifact-language.test.ts +24 -11
- package/tests/ask-user-choice.test.ts +264 -0
- package/tests/codegraph-tools.test.ts +3 -3
- package/tests/crosslane/cross-lane.mjs +15 -1168
- package/tests/delegated-key-learnings-contract.test.ts +8 -6
- package/tests/devbinary/native-review-parity.devtest.ts +167 -250
- package/tests/devbinary/pi-host-relay.devtest.ts +867 -0
- package/tests/fixtures/devbinary/last-event-capture-correction-plan.captured.json +10 -0
- package/tests/fixtures/devbinary/last-event-capture-refuter-approved.captured.json +20 -0
- package/tests/fixtures/devbinary/last-event-capture-refuter-correction-required.captured.json +26 -0
- package/tests/fixtures/devbinary/last-event-capture-result-approved.captured.json +8 -0
- package/tests/fixtures/devbinary/last-event-capture-result-correction-required.captured.json +26 -0
- package/tests/fixtures/devbinary/last-event-capture-validation-approved.captured.json +8 -0
- package/tests/fixtures/devbinary/last-event-closure.provenance.md +13 -0
- package/tests/fixtures/devbinary/review-acknowledged-v1.captured.json +9 -0
- package/tests/fixtures/devbinary/review-acknowledged.provenance.md +31 -0
- package/tests/fixtures/devbinary/start-v3-zero-lens-closed.captured.json +21 -0
- package/tests/fixtures/native-review-cli/v2.5.0-rc.1/PROVENANCE.txt +15 -0
- package/tests/gentle-ai-binary.test.ts +83 -13
- package/tests/gentle-ai-dev-binary.test.ts +51 -228
- package/tests/gentle-ai-installer.test.ts +137 -47
- package/tests/gentle-ai.test.ts +820 -131
- package/tests/gentle-theme.test.ts +133 -0
- package/tests/maintainer/provider-relay.maintest.ts +340 -4
- package/tests/model-routing-authority.test.ts +257 -0
- package/tests/native-review-capability-contract.test.ts +28 -1
- package/tests/native-review-cli.test.ts +586 -962
- package/tests/native-review-consent.test.ts +150 -66
- package/tests/native-review-parity-runtime.test.ts +80 -359
- package/tests/native-review-parity.test.ts +674 -925
- package/tests/opaque-pi-reviewer-adapter.test.ts +266 -0
- package/tests/orchestrator-budget.test.ts +132 -88
- package/tests/orchestrator-rdd-ownership.test.ts +103 -0
- package/tests/package-manifest.test.ts +37 -35
- package/tests/provider-defect-handoff.test.ts +15 -118
- package/tests/quiet-tool-rendering.test.ts +1055 -28
- package/tests/review-authority-recovery-docs.test.ts +1 -2
- package/tests/review-candidate-view.test.ts +409 -3
- package/tests/review-compact-contract.test.ts +4 -30
- package/tests/review-controller-lock-status.test.ts +2 -2
- package/tests/review-controller-native-recovery.test.ts +363 -1450
- package/tests/review-controller-native-routing.test.ts +1251 -5642
- package/tests/review-controller-retired-ops.test.ts +1 -1
- package/tests/review-controller-workspace-root.test.ts +222 -71
- package/tests/review-controller.test.ts +26 -816
- package/tests/review-corrected-finalize-binding.test.ts +112 -153
- package/tests/review-dispatch-hydration-gap.test.ts +1 -53
- package/tests/review-gate.test.ts +0 -45
- package/tests/review-host-relay-restart-parity.test.ts +360 -0
- package/tests/review-host-relay-routing.test.ts +117 -82
- package/tests/review-host-relay.test.ts +242 -8
- package/tests/review-integration-v2-forward.test.ts +576 -553
- package/tests/review-integration-v2.test.ts +239 -165
- package/tests/review-last-event-closure.test.ts +408 -0
- package/tests/review-ledger-contract.test.ts +97 -35
- package/tests/review-recovered-lineage-routing.test.ts +0 -47
- package/tests/review-relay-transport-agent.test.ts +119 -46
- package/tests/review-snapshot.test.ts +3 -2
- package/tests/runtime-harness.mjs +361 -196
- package/tests/sdd-agent-tools.test.ts +36 -0
- package/tests/sdd-preflight.test.ts +81 -15
- package/tests/sdd-status.test.ts +109 -110
- package/tests/skill-collision-prefixes.test.ts +5 -8
- package/tests/writer-edit-surface-scope.test.ts +230 -0
- package/themes/Gentleman-Cute.json +94 -0
- package/themes/Gentleman-Sexy.json +92 -0
- package/lib/git-commit-transaction.ts +0 -861
- package/runtime/git-commit-transaction.mjs +0 -862
- package/scripts/run-git-commit-transaction.mjs +0 -35
- package/tests/fixtures/native-review-cli/v2.1.3/sdd-status-engram.json +0 -139
- package/tests/fixtures/native-review-cli/v2.1.3/sdd-status.json +0 -200
- 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 {
|
|
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.
|
|
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
|
|
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:
|
|
297
|
+
stdout: readFileSync(join(import.meta.dirname, "..", "contracts", "review-integration", "v2", "fixtures", "status.fixture.json"), "utf8"),
|
|
231
298
|
};
|
|
232
299
|
};
|
|
233
300
|
|
|
234
|
-
await
|
|
235
|
-
|
|
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
|
|
238
|
-
assert.throws(() => new
|
|
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
|
});
|