gentle-pi 2.2.0 → 2.4.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 +76 -79
- 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-explore.md +1 -0
- 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 +56 -249
- package/assets/orchestrator-memory.md +2 -0
- package/assets/orchestrator.md +20 -40
- 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/contracts/review-provider-contract-mirror/provider-contract.lock.json +8 -7
- package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/README.md +10 -0
- package/contracts/review-provider-contract-mirror/v1.2.0/bundle/manifest.json +74 -0
- package/contracts/review-provider-contract-mirror/v1.2.0/bundle/orchestration/pi.md +53 -0
- package/contracts/review-provider-contract-mirror/v1.2.0/bundle/schemas/targeted-validator.schema.json +1 -0
- package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/generated/provider-capabilities.baseline.json +9 -2
- package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/generated/provider-roles.baseline.json +2 -2
- 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 +2349 -3785
- package/extensions/quiet-tools.ts +515 -32
- package/extensions/sdd-init.ts +4 -8
- package/lib/agent-home.ts +8 -0
- package/lib/gentle-ai-renderer.ts +70 -0
- package/lib/model-routing-authority.ts +133 -0
- package/lib/native-review-cli.ts +387 -940
- package/lib/opaque-pi-reviewer-adapter.ts +284 -0
- package/lib/provider-contract-bundle.ts +88 -6
- package/lib/review-candidate-view.ts +341 -132
- package/lib/review-host-relay.ts +324 -96
- package/lib/review-integration-v2.ts +868 -307
- package/lib/review-last-event-controller.ts +35 -0
- package/lib/sdd-preflight.ts +179 -68
- 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 +337 -890
- package/runtime/review-integration-v2.mjs +816 -255
- 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 +23 -23
- package/skills/_shared/review-ledger-contract.md +14 -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/issue-creation/SKILL.md +53 -93
- 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/fixtures/provider-contract-bundle/v1.2.0/README.md +22 -0
- package/{contracts/review-provider-contract-mirror/v1.1.0/bundle → tests/fixtures/provider-contract-bundle/v1.2.0}/manifest.json +11 -2
- package/tests/fixtures/provider-contract-bundle/v1.2.0/orchestration/pi.md +97 -0
- package/tests/fixtures/provider-contract-bundle/v1.2.0/schemas/lens.schema.json +16 -0
- package/tests/fixtures/provider-contract-bundle/v1.2.0/schemas/refuter.schema.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.2.0/vectors/lens.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.2.0/vectors/refuter.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.2.0/vectors/targeted-validator.json +1 -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 +821 -131
- package/tests/gentle-theme.test.ts +133 -0
- package/tests/issue-creation-skill.test.ts +103 -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 +45 -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 +152 -36
- package/tests/provider-contract-bundle.test.ts +76 -0
- package/tests/provider-contract-mirror.test.ts +19 -0
- package/tests/provider-defect-handoff.test.ts +15 -118
- package/tests/quiet-tool-rendering.test.ts +1055 -28
- package/tests/review-agent-end-preflight.test.ts +408 -0
- 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-contract-prompt.test.ts +139 -0
- 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 +328 -91
- package/tests/review-host-relay.test.ts +436 -14
- package/tests/review-integration-v2-forward.test.ts +623 -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 +362 -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 +366 -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
- /package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/schemas/lens.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/schemas/refuter.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/vectors/lens.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/vectors/refuter.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/vectors/targeted-validator.json +0 -0
- /package/{contracts/review-provider-contract-mirror/v1.1.0/bundle → tests/fixtures/provider-contract-bundle/v1.2.0}/schemas/targeted-validator.schema.json +0 -0
|
@@ -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.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Gentle AI review provider contract
|
|
2
|
+
|
|
3
|
+
This data-only bundle describes the provider result contracts admitted by Gentle AI.
|
|
4
|
+
|
|
5
|
+
## Activation
|
|
6
|
+
|
|
7
|
+
1. Verify the signed release checksum manifest before using this archive.
|
|
8
|
+
2. Verify every listed file hash and the transport capability before activation.
|
|
9
|
+
3. Confirm your runtime identity appears in the manifest's registered runtimes before trusting the layout.
|
|
10
|
+
4. Pass the Go-materialized opaque prompt to the provider and return only raw output or an error.
|
|
11
|
+
|
|
12
|
+
## Orchestration
|
|
13
|
+
|
|
14
|
+
manifest.json's orchestration array lists, for closed runtimes only, one
|
|
15
|
+
orchestration/<runtime>.md file: the exact review execution contract text
|
|
16
|
+
Gentle AI's own installer would have spliced into that runtime's system
|
|
17
|
+
prompt, for a runtime whose adapter has no system prompt to splice it into.
|
|
18
|
+
It carries no executable content, same as every other file in this bundle: a
|
|
19
|
+
runtime mirrors and delivers the text as-is and still relies on Go for every
|
|
20
|
+
review decision, prompt, receipt, and delivery gate.
|
|
21
|
+
|
|
22
|
+
Go remains the admission authority for prompts, results, receipts, and delivery gates.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema": "gentle-ai.review-provider-contract-bundle/v1",
|
|
3
|
-
"contract_semver": "1.
|
|
3
|
+
"contract_semver": "1.2.0",
|
|
4
4
|
"transport_capability": "gentle-ai.provider-transport/v1",
|
|
5
5
|
"runtimes": [
|
|
6
6
|
"claude-code",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
],
|
|
11
11
|
"readme": {
|
|
12
12
|
"path": "README.md",
|
|
13
|
-
"sha256": "
|
|
13
|
+
"sha256": "18baab5ee79aefd0bc62a28da0dadcf2162544f57a940c5b859cd6bc4932a085"
|
|
14
14
|
},
|
|
15
15
|
"roles": [
|
|
16
16
|
{
|
|
@@ -61,5 +61,14 @@
|
|
|
61
61
|
"sha256": "7b5a5165e3a913863fb98d2ba5387811860c21333b5c37e6ed3b4e0d121cf2fa"
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
|
+
],
|
|
65
|
+
"orchestration": [
|
|
66
|
+
{
|
|
67
|
+
"runtime": "pi",
|
|
68
|
+
"file": {
|
|
69
|
+
"path": "orchestration/pi.md",
|
|
70
|
+
"sha256": "54fba57b11b280f56f7f15e91ddad3d844c70e0127e5c1adeff0a9d1b7a98e64"
|
|
71
|
+
}
|
|
72
|
+
}
|
|
64
73
|
]
|
|
65
74
|
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Native Compact Review Orchestration
|
|
2
|
+
|
|
3
|
+
**When a final capture returns `status_continuation`, execute its operation and ordered argument tokens unchanged. Do not reconstruct lifecycle selectors from retained prose or transcript state.**
|
|
4
|
+
|
|
5
|
+
The parent orchestrator coordinates one native transaction; reviewers, refuters, correction actors, and validators receive only their provider-issued role input. Prompt prose never creates authority or decides delivery.
|
|
6
|
+
|
|
7
|
+
## Entry rule
|
|
8
|
+
|
|
9
|
+
Enter this lifecycle once per candidate, after an authorized source-mutating implementation is complete and normalized and before reporting it complete, whenever the user-owned review switch is enabled (`gentle-ai review mode status` reads it without changing it). Run the selectorless STATUS in step 1 and route only from its returned `next_transition`; the START consent envelope lets the human decide this candidate, so never skip the preflight because the user did not ask for a review. Skip it only for a trivial passive documentation-only edit, when the user explicitly left this candidate unreviewed, or while a transaction is already bound to it. A runtime that runs this preflight itself hands the agent the exact returned START tokens and never runs START.
|
|
10
|
+
|
|
11
|
+
## Atomic lifecycle
|
|
12
|
+
|
|
13
|
+
1. **Preflight only.** Selectorless STATUS only preflights the current worktree candidate and returns one exact START invocation. It never discovers, resumes, recovers, or evaluates ambient authority from another lineage or worktree: `gentle-ai review status --cwd <repo> --contract gentle-ai.review-integration/v2 --agent pi --next-transition`.
|
|
14
|
+
|
|
15
|
+
2. **Freeze once.** Invoke only the returned START operation and its ordered tokens unchanged. START freezes one compact atomic transaction with an explicit lineage, worktree, and target binding. It ignores every other lineage and worktree. Capture the returned lineage, revision, and target tokens. An exact replay of an active START may return `replayed`; a genuinely new START is independent.
|
|
16
|
+
|
|
17
|
+
3. **Stay bound.** A reviewing START carries `next_transition.execute(review.status)` — the provider-issued re-entry for its frozen binding: run that provider-issued command verbatim, with the repository as process cwd, and satisfy every later STATUS and collection call only with the exact tokens each returned transition names. Route only from that transaction's returned `next_transition`; the root `action` field is informational. Never infer a command from prose, ambient state, a gate, or a stale reply. Do not start another lineage, reuse an acknowledged-and-burned lineage, or perform ambient recovery. For `execute`, run the exact operation and ordered arguments. For `collect`, satisfy only the named inputs and their exact capture operations, then ask STATUS again with the same binding. For `stop`, run no lifecycle operation.
|
|
18
|
+
|
|
19
|
+
4. **Acknowledge exactly.** Native Go owns frozen lenses, provider context and admission, refutation, one bounded correction, repository evidence, targeted validation, and approved closure. A final approved capture commits one pending acknowledgement token and returns `review.acknowledge-approved`; restarted STATUS returns the same operation, ordered arguments, token, and live revision. Only that exact invocation burns authority and artifacts, and on success it prints one `gentle-ai.review-acknowledged/v1` envelope; report the burn from that envelope, never from a later STATUS. Wrong, stale, or replayed acknowledgement refuses without creating a receipt, tombstone, witness, mirror, sidecar, or delivery authority. Unrelated transactions survive unchanged.
|
|
20
|
+
|
|
21
|
+
The final reviewer, refuter, or targeted-validator capture owns closure. A malformed, incomplete, or unavailable capture never reaches acknowledgement: issue one retained target-bound read-only STATUS and relaunch only when it reoffers the same bound slot. Never invent a binding or retry from prose.
|
|
22
|
+
|
|
23
|
+
When v2 returns `forecast`, relay it losslessly in the user's language: preserve every step's order and fields (`step`, `kind`, `reason_code`, `description`) and the horizon. Forecast is informational; route only from `next_transition`.
|
|
24
|
+
|
|
25
|
+
### Cross-repository lifecycle root
|
|
26
|
+
|
|
27
|
+
A session in repository A may review an explicitly selected nested target in unrelated repository B only after explicit user authorization. Native Go resolves the requested path to the canonical B worktree root; adapters never parse authorization or roots.
|
|
28
|
+
|
|
29
|
+
- After B is selected, retain canonical B as the lifecycle working root from selectorless STATUS through consent, collection, correction, targeted validation, acknowledgement, and burn. Do not fall back to A.
|
|
30
|
+
- Run provider-issued command tokens exactly. Never append, remove, or rebuild provider-issued command tokens. When a command omits `--cwd`, run it with process cwd B.
|
|
31
|
+
- Opaque `repository_context` can capture or materialize from any process cwd, but the host still retains B for lifecycle continuity. Go owns repository binding; adapters never parse authorization or roots.
|
|
32
|
+
- The same lineage text in A and B is independent. Approval awaits acknowledgement in B; exact acknowledgement burns B only, and A remains untouched.
|
|
33
|
+
|
|
34
|
+
This lifecycle is rendered for exactly Claude Code, Codex, OpenCode, and Pi. Unsupported runtimes remain unavailable before repository or authority mutation.
|
|
35
|
+
|
|
36
|
+
## Capture and correction
|
|
37
|
+
|
|
38
|
+
For each returned `review.capture-result` input, run the exact capture operation once. The reviewer prompt begins with the exact literal prefix `GENTLE_AI_REVIEW_BINDING ` (trailing space, never `=`), followed by one-line JSON assembled only from that input: `lineage`, `target`, `lens`, `order`, `revision` from `expected-revision`, `repository_context`, and `subject_hash` from `artifact_subject.subject_hash`; omit only provider-omitted fields. Return one JSON object that echoes `subject_hash`, reports completed inspection of every manifest path in order, and contains findings/evidence with severe evidence class and causality. Access failure is incomplete inspection, never completion.
|
|
39
|
+
|
|
40
|
+
After an empty, malformed, schema-invalid, access/provider-failed, or incomplete capture, query the same exact-lineage STATUS. Relaunch only if its fresh `next_transition` reoffers the same bound slot. Never infer a retry from transcript text. A relayed capture passes its result through `--input <path|->`, one per lens in lens order; BOM-less UTF-8 is required on Windows PowerShell 5.1. Tokens carrying `--agent` capture in process with no `--input`.
|
|
41
|
+
|
|
42
|
+
Only candidate-caused severe findings block. Pre-existing/base-only findings are follow-ups; unknown causality escalates. A deterministic blocker needs no refuter; inferential blockers share one read-only refuter batch. A four-lens review is long work: before its first lens, give one forecast covering four reviewer runs, the frozen correction budget, and the at-most-one bounded correction.
|
|
43
|
+
|
|
44
|
+
A correction is native-scoped. When the final reviewer or refuter capture opens `correction_required`, its `status_continuation` is the only re-entry: run its `review.status` operation and ordered tokens unchanged before acting again. Native Go maps edits only to corroborated frozen findings, owns repository evidence and the targeted validator, and permits at most one bounded correction. A validator that cannot inspect the immutable trees produced no verdict: surface one blocked human decision and submit nothing. Do not route it to a refuter or another actor without read-only immutable-tree access. Independent requirements/runtime verification never starts another reviewer, refuter, correction, or validator.
|
|
45
|
+
|
|
46
|
+
## Consent and immutable inspection
|
|
47
|
+
|
|
48
|
+
If exact provider-returned START returns the typed `gentle-ai.review-integration.consent/v3` envelope, relay it as a Lossless Blocking Prompt. Global RDD enabled permits review; it never grants consent for this candidate. For medium/high candidates, faithfully translate the headline, reason, `value`, risk evidence, choice labels, every choice `effect`, and the off-path note while preserving original groups/order, selection mode, allowed-answer domain, answer tokens, commands, target IDs, and invocations. Project `value` as benefits and every `effect` as consequences. Do not translate machine answer tokens (`granted`, `declined`). Run exactly the invocation selected by the human; a decline is candidate-scoped and is not the kill switch.
|
|
49
|
+
|
|
50
|
+
Claude Code, OpenCode, Codex, and Pi use the shared Go provider contract. Go owns frozen evidence, binding, schema, byte bounds, validation, admission, and capture; runtime adapters transport opaque provider output. Claude uses a tool-free fresh reviewer; OpenCode relays one host Task through its live Go transport; Codex uses its provider-bound subprocess; Pi uses its gentle-pi-owned relay. Compiled capability is authoritative before repository, target, authority, collection, or process work.
|
|
51
|
+
|
|
52
|
+
Reviewers inspect only the provider-bound immutable trees. Never hand candidate bytes through `/tmp`, an external file, a repository scratch file, or `GENTLE_AI_FROZEN_CANDIDATE_CONTEXT`. Use the provider-issued inspection path, never the live worktree, index, `HEAD`, or an unbound revision. Never pass `--binary`, change checkout, or substitute live files.
|
|
53
|
+
|
|
54
|
+
<!-- authority-first-terminal-procedure:start -->
|
|
55
|
+
### Authority-First Terminal Procedure
|
|
56
|
+
|
|
57
|
+
| Order | Operation | Required result |
|
|
58
|
+
| --- | --- | --- |
|
|
59
|
+
| 01 | canonical initial STATUS above | exactly one current-worktree START preflight; no authority discovery |
|
|
60
|
+
| 02 | exact returned START | one compact lineage/worktree/target binding; retain lineage, revision, and target |
|
|
61
|
+
| 03 | exact-lineage STATUS and collect | only returned transaction actions; no ambient resume, reuse, or delivery gate |
|
|
62
|
+
| 04 | final admitted capture | native readback, approved authority, and one exact acknowledgement continuation |
|
|
63
|
+
| 05 | STATUS restart + exact acknowledgement | replayed operation/token/revision; only exact acknowledgement burns authority |
|
|
64
|
+
| 06 | terminal lifecycle stop | ordinary repository policy owns any later delivery decision |
|
|
65
|
+
|
|
66
|
+
<!-- authority-first-terminal-procedure:end -->
|
|
67
|
+
|
|
68
|
+
### Continue after a stop reason code
|
|
69
|
+
|
|
70
|
+
A `stop` ends its transition, never approves delivery. Complete atomic inventory: B stays target root; gates stay unmanaged. `D` means `gentle-ai review mode disable --scope clone --cwd <B>`; B ordinary policy decides delivery. `S` means re-query the exact captured target-root STATUS command with lineage and target.
|
|
71
|
+
|
|
72
|
+
| Reason codes | Continuation |
|
|
73
|
+
| --- | --- |
|
|
74
|
+
| `captured_artifacts_unverifiable` | Terminal — maintainer inspects B authority, or `D`. |
|
|
75
|
+
| `captured_result_selection_unavailable` | Terminal — maintainer inspects lineage, or `D`. |
|
|
76
|
+
| `missing_authority_binding` | Terminal — file a bounded defect with lineage, or `D`. |
|
|
77
|
+
| `corrupted_or_unverifiable_authority`, `manual_intervention_required`, `native_stop_required` | Terminal — maintainer inspects authority/lineage, or `D`. |
|
|
78
|
+
| `empty_base_diff_bootstrap_required` | Terminal — authorized empty-root bootstrap for a new target, or `D`. |
|
|
79
|
+
| `lens_context_budget_exceeded` | Terminal — reduce B scope and start a new transaction, or `D`. |
|
|
80
|
+
| `staged_workspace_overlay_recovery_unavailable` | Terminal — pass `--lineage <id>` to recover, or drop `--workspace-overlay` and start fresh; otherwise `D`. |
|
|
81
|
+
| `corrected_candidate_unavailable` | Change B correction candidate, then `S`; do not reuse the pre-correction target. |
|
|
82
|
+
| `recovery_scope_unchanged` | Change B target identity, then retry the exact returned `gentle-ai review recover`. |
|
|
83
|
+
| `rdd_disabled` | `--scope clone` only clears a clone-local off; `gentle-ai review mode enable --scope global`, then `S`. |
|
|
84
|
+
|
|
85
|
+
## Delivery follows ordinary repository policy
|
|
86
|
+
|
|
87
|
+
Shipped `review validate` and gate commands are compatibility/informational only. They never discover authority or decide delivery: enabled gates return `invalidated/unmanaged`; disabled gates return `disabled/unmanaged`. They never allow, approve, block, commit, push, open a PR, or govern release.
|
|
88
|
+
|
|
89
|
+
After exact acknowledgement burns terminal `approved` authority, the review lifecycle stops. Commit, push, PR, and release remain separate human decisions under ordinary repository policy. A review outcome is informational and never authorizes delivery, including when the selected repository is B.
|
|
90
|
+
|
|
91
|
+
Historical compatibility commands may read older artifacts manually, but they are never the ordinary lifecycle and never restore delivery authority.
|
|
92
|
+
|
|
93
|
+
### Concurrent Reviewer Group (MANDATORY)
|
|
94
|
+
|
|
95
|
+
When one fresh `collect.inputs` set contains multiple distinct independent `review.capture-result` reviewer slots, launch every returned capture operation concurrently in provider order: start all without waiting between launches, then wait for every result. For canonical 4R, preserve `review-risk`, `review-resilience`, `review-readability`, `review-reliability` order.
|
|
96
|
+
|
|
97
|
+
Each launch runs only its own provider-issued `review.capture-result` argument tokens exactly as returned. Completion order is not authority: shared Go admission/election owns reduction and semantics. The final admitted capture owns reduction and closure. On `approved`, authority is already burned: do not FINALIZE or issue a trailing STATUS. On `correction_required`, continue only through exact bound STATUS and the provider-issued `review.capture-correction-plan` binding. After a malformed or nonterminal capture, reconcile through exact bound STATUS and retry only an identically reoffered slot.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://gentle-ai.dev/schema/review/reviewer/v1",
|
|
4
|
+
"title": "Gentle AI reviewer result",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": ["subject_hash", "inspection", "findings", "evidence"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"subject_hash": {"type": "string", "pattern": "^sha256:[0-9a-f]{64}$"},
|
|
10
|
+
"inspection": {"type": "object", "additionalProperties": false, "required": ["status", "paths"], "properties": {"status": {"const": "completed"}, "paths": {"type": "array", "description": "Complete unique unordered set of every changed_path_manifest.path.", "uniqueItems": true, "items": {"type": "string", "minLength": 1}}}},
|
|
11
|
+
"lens": {"type": "string", "description": "Optional selected lens binding. Omission canonicalizes to the selected subject lens.", "enum": ["risk", "resilience", "readability", "reliability", "review-risk", "review-resilience", "review-readability", "review-reliability"]},
|
|
12
|
+
"findings": {"type": "array", "items": {"type": "object", "additionalProperties": false, "required": ["location", "severity", "claim", "proof_refs"], "allOf": [{"if": {"properties": {"severity": {"enum": ["BLOCKER", "CRITICAL"]}}, "required": ["severity"]}, "then": {"required": ["evidence_class", "causal_disposition"]}}], "properties": {"id": {"type": "string", "pattern": "^R[1-4]-[A-Za-z0-9][A-Za-z0-9._-]*$", "description": "Optional explicit ID; omit it to receive a native-assigned ID. When present it must carry the prefix bound to the selected lens, not the selection order: review-risk=R1-, review-readability=R2-, review-reliability=R3-, review-resilience=R4-."}, "lens": {"type": "string", "enum": ["risk", "resilience", "readability", "reliability", "review-risk", "review-resilience", "review-readability", "review-reliability"]}, "location": {"type": "string", "description": "One canonical repository-relative path:line or inclusive path:start-end span.", "pattern": "^.+:[1-9][0-9]*(?:-[1-9][0-9]*)?$"}, "severity": {"type": "string", "enum": ["BLOCKER", "CRITICAL", "WARNING", "SUGGESTION"]}, "claim": {"type": "string", "minLength": 1}, "proof_refs": {"type": "array", "minItems": 1, "items": {"type": "string", "pattern": "\\S", "not": {"pattern": "^\\s*(?:[nN]/[aA]|[nN][aA]|[nN][oO][nN][eE]|[tT][oO][dD][oO]|[tT][bB][dD]|[pP][aA][sS][sS]|[pP][aA][sS][sS][eE][dD]|[sS][uU][cC][cC][eE][sS][sS]|[pP][lL][aA][cC][eE][hH][oO][lL][dD][eE][rR])\\s*$"}}}, "evidence_class": {"type": "string", "enum": ["deterministic", "inferential", "insufficient"]}, "causal_disposition": {"type": "string", "enum": ["introduced", "behavior-activated", "worsened", "pre-existing", "base-only", "unknown"]}}}},
|
|
13
|
+
"evidence": {"type": "array", "minItems": 1, "items": {"type": "string", "pattern": "\\S", "not": {"pattern": "^\\s*(?:[nN]/[aA]|[nN][aA]|[nN][oO][nN][eE]|[tT][oO][dD][oO]|[tT][bB][dD]|[pP][aA][sS][sS]|[pP][aA][sS][sS][eE][dD]|[sS][uU][cC][cC][eE][sS][sS]|[pP][lL][aA][cC][eE][hH][oO][lL][dD][eE][rR])\\s*$"}}}
|
|
14
|
+
},
|
|
15
|
+
"examples": [{"subject_hash": "sha256:0000000000000000000000000000000000000000000000000000000000000000", "inspection": {"status": "completed", "paths": ["internal/example.go"]}, "findings": [], "evidence": ["reviewed the complete candidate scope"]}]
|
|
16
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://gentle-ai.dev/schema/review/refuter/v1","title":"Gentle AI refuter result","type":"object","additionalProperties":false,"required":["refuter_request_hash","results"],"properties":{"refuter_request_hash":{"$ref":"#/$defs/sha256"},"results":{"type":"array","items":{"type":"object","additionalProperties":false,"required":["finding_id","outcome","proof_refs"],"properties":{"finding_id":{"type":"string"},"outcome":{"type":"string","enum":["corroborated","refuted","inconclusive"]},"proof_refs":{"type":"array","minItems":1,"items":{"type":"string","pattern":"\\S"}}}}}},"$defs":{"sha256":{"type":"string","pattern":"^sha256:[0-9a-f]{64}$"}},"examples":[{"refuter_request_hash":"sha256:0000000000000000000000000000000000000000000000000000000000000000","results":[]}]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"subject_hash":"sha256:0000000000000000000000000000000000000000000000000000000000000000","inspection":{"status":"completed","paths":["internal/example.go"]},"findings":[],"evidence":["reviewed the complete candidate scope"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"refuter_request_hash":"sha256:0000000000000000000000000000000000000000000000000000000000000000","results":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"targeted_validation_request_hash":"sha256:0000000000000000000000000000000000000000000000000000000000000000","correction_target_identity":"sha256:1111111111111111111111111111111111111111111111111111111111111111","original_criteria":{"passed":true,"evidence":["acceptance test passed"]},"correction_regression":{"passed":true,"evidence":["regression test passed"]},"follow_ups":[]}
|
|
@@ -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.6.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
|
});
|