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
|
@@ -87,13 +87,14 @@ test("no SDD phase executor infers Key Learnings through `standard phase envelop
|
|
|
87
87
|
|
|
88
88
|
test("SDD executor coverage is exhaustive against actual agent files", () => {
|
|
89
89
|
const actual = sddAgents();
|
|
90
|
-
// Allowlist: the
|
|
90
|
+
// Allowlist: the 13 known phase executors. A new sdd-*.md without a
|
|
91
91
|
// Key Learnings Closing section fails the first test; this test proves
|
|
92
92
|
// the allowlist matches reality so coverage cannot silently drift.
|
|
93
93
|
const expected = [
|
|
94
94
|
"sdd-apply.md", "sdd-archive.md", "sdd-design.md", "sdd-explore.md",
|
|
95
|
-
"sdd-init.md", "sdd-onboard.md", "sdd-proposal.md", "sdd-
|
|
96
|
-
"sdd-
|
|
95
|
+
"sdd-init.md", "sdd-onboard.md", "sdd-proposal.md", "sdd-research.md",
|
|
96
|
+
"sdd-spec.md", "sdd-status.md", "sdd-sync.md", "sdd-tasks.md",
|
|
97
|
+
"sdd-verify.md",
|
|
97
98
|
];
|
|
98
99
|
assert.deepEqual(actual, expected, "SDD agent set must match the known allowlist");
|
|
99
100
|
});
|
|
@@ -228,9 +229,10 @@ test("modified SDD agents are packaged and installed by the existing installer",
|
|
|
228
229
|
"assets/agents/sdd-apply.md", "assets/agents/sdd-archive.md",
|
|
229
230
|
"assets/agents/sdd-design.md", "assets/agents/sdd-explore.md",
|
|
230
231
|
"assets/agents/sdd-init.md", "assets/agents/sdd-onboard.md",
|
|
231
|
-
"assets/agents/sdd-proposal.md", "assets/agents/sdd-
|
|
232
|
-
"assets/agents/sdd-
|
|
233
|
-
"assets/agents/sdd-
|
|
232
|
+
"assets/agents/sdd-proposal.md", "assets/agents/sdd-research.md",
|
|
233
|
+
"assets/agents/sdd-spec.md", "assets/agents/sdd-status.md",
|
|
234
|
+
"assets/agents/sdd-sync.md", "assets/agents/sdd-tasks.md",
|
|
235
|
+
"assets/agents/sdd-verify.md",
|
|
234
236
|
];
|
|
235
237
|
for (const path of expected) {
|
|
236
238
|
assert.match(verifier, new RegExp(path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")), `${path} must be in the package verifier`);
|
|
@@ -1,39 +1,26 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import { execFileSync } from "node:child_process";
|
|
3
|
-
import { existsSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { join } from "node:path";
|
|
6
6
|
import test from "node:test";
|
|
7
|
-
import
|
|
8
|
-
import { createGentleAiExtension } from "../../extensions/gentle-ai.ts";
|
|
7
|
+
import { __testing } from "../../extensions/gentle-ai.ts";
|
|
9
8
|
import {
|
|
10
|
-
|
|
9
|
+
NATIVE_REVIEW_ERROR_CODE,
|
|
10
|
+
NativeReviewCliError,
|
|
11
|
+
NativeReviewCliV216,
|
|
12
|
+
NativeReviewConsentRequiredError,
|
|
11
13
|
createNodeExecFileAdapter,
|
|
12
|
-
setNativeCliContractForTesting,
|
|
13
14
|
type ExecFileAdapter,
|
|
14
|
-
type NativeReviewCli,
|
|
15
15
|
} from "../../lib/native-review-cli.ts";
|
|
16
|
-
import { CandidateViewRegistry } from "../../lib/review-candidate-view.ts";
|
|
17
|
-
import { readReviewConsentLatch } from "../../lib/review-consent-latch.ts";
|
|
18
|
-
import type { AuthorityRepairAssessmentV1, ReviewStatusV3 } from "../../lib/review-integration-v2.ts";
|
|
19
16
|
import { requireDevBinary } from "../support/native-binary-gate.ts";
|
|
20
17
|
|
|
21
|
-
// Organic RDD Parity
|
|
18
|
+
// Organic RDD Parity: candidate-bound dev-binary journeys.
|
|
22
19
|
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
// 2.1.11 pins (lib/gentle-ai-binary.ts, scripts/gentle-ai-installer.mjs) —
|
|
28
|
-
// every capability stays negotiated per-process through the executable
|
|
29
|
-
// override seam below, exactly like every other native-review-cli test.
|
|
30
|
-
//
|
|
31
|
-
// Gated through the same loud-skip machinery as the two `tests/*.test.ts`
|
|
32
|
-
// binary-verified suites (Phase 4), extended here to cover this THIRD
|
|
33
|
-
// self-skipping suite (Phase 13.12/13.13 follow-up): under
|
|
34
|
-
// GENTLE_PI_REQUIRE_DEV_BINARY=1 a missing/invalid dev binary throws instead
|
|
35
|
-
// of silently reporting 5 tests / 0 pass / 0 fail, which is exactly how the
|
|
36
|
-
// v2.2.2 "receipt-driven development" terminology rot went unnoticed.
|
|
20
|
+
// This suite runs only via `pnpm run test:dev-binary` and only when
|
|
21
|
+
// GENTLE_AI_DEV_BINARY names a current, absolute candidate binary. Every START
|
|
22
|
+
// first obtains candidate-bound STATUS and executes the returned transition;
|
|
23
|
+
// consent answers replay the envelope's own invocation exactly once.
|
|
37
24
|
const DEV_BINARY = process.env.GENTLE_AI_DEV_BINARY;
|
|
38
25
|
const devBinaryGate = requireDevBinary({
|
|
39
26
|
devBinaryPath: DEV_BINARY,
|
|
@@ -42,45 +29,23 @@ const devBinaryGate = requireDevBinary({
|
|
|
42
29
|
});
|
|
43
30
|
if (!devBinaryGate.run) console.log(`tests/devbinary/native-review-parity.devtest.ts: ${devBinaryGate.reason}`);
|
|
44
31
|
const RUNNABLE = devBinaryGate.run;
|
|
32
|
+
const DEV_HOME = mkdtempSync(join(tmpdir(), "gentle-pi-dev-binary-home-"));
|
|
33
|
+
const ORIGINAL_HOME = process.env.HOME;
|
|
34
|
+
const ORIGINAL_USERPROFILE = process.env.USERPROFILE;
|
|
45
35
|
|
|
46
|
-
|
|
47
|
-
// CAPABLE_VERSION overlay. Real dev binaries never carry a pinned three-part
|
|
48
|
-
// semver (the live binary under test reports "gentle-ai dev-organic-...",
|
|
49
|
-
// confirmed live during Phase 6 ground-truthing), so `verifyVersion`'s frozen
|
|
50
|
-
// `/^gentle-ai ([0-9]+\.[0-9]+\.[0-9]+)\n$/` regex can never match it — by
|
|
51
|
-
// design, a dev build is never a pinned release. The bridge below substitutes
|
|
52
|
-
// exactly one in-memory version response and forwards every other call to the
|
|
53
|
-
// real process untouched, so argv fidelity is guaranteed: NativeReviewCliV214
|
|
54
|
-
// itself builds every argv array here, never this file.
|
|
55
|
-
const BRIDGE_VERSION = "9.8.7";
|
|
36
|
+
type NativeCall = readonly string[];
|
|
56
37
|
|
|
57
|
-
function bridgeAdapter(binary: string): ExecFileAdapter {
|
|
38
|
+
function bridgeAdapter(binary: string, calls: NativeCall[]): ExecFileAdapter {
|
|
58
39
|
const real = createNodeExecFileAdapter();
|
|
59
40
|
return async (request) => {
|
|
60
|
-
|
|
61
|
-
return { stdout: `gentle-ai ${BRIDGE_VERSION}\n`, stderr: "", exitCode: 0, signal: null, timedOut: false, outputLimitExceeded: false };
|
|
62
|
-
}
|
|
41
|
+
calls.push([...request.arguments]);
|
|
63
42
|
return real({ ...request, file: binary });
|
|
64
43
|
};
|
|
65
44
|
}
|
|
66
45
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
// contract; the plain-CLI decode path in lib/native-review-cli.ts (reviewMode,
|
|
71
|
-
// start, validate here) stays independent of the negotiated status shape.
|
|
72
|
-
function journeyNative(binary: string): NativeReviewCli {
|
|
73
|
-
const bridge = new NativeReviewCliV214(bridgeAdapter(binary), binary);
|
|
74
|
-
return {
|
|
75
|
-
start: (request) => bridge.start(request),
|
|
76
|
-
validate: (request) => bridge.validate(request),
|
|
77
|
-
reviewMode: (request) => bridge.reviewMode(request),
|
|
78
|
-
async targetStatus(request) {
|
|
79
|
-
return request.lineageId === undefined
|
|
80
|
-
? unrelatedStartTargetStatus()
|
|
81
|
-
: currentTargetStatusFixture(request.lineageId, request.cwd);
|
|
82
|
-
},
|
|
83
|
-
} as unknown as NativeReviewCli;
|
|
46
|
+
function journeyNative(binary: string): { native: NativeReviewCliV216; calls: NativeCall[] } {
|
|
47
|
+
const calls: NativeCall[] = [];
|
|
48
|
+
return { native: new NativeReviewCliV216(bridgeAdapter(binary, calls), binary), calls };
|
|
84
49
|
}
|
|
85
50
|
|
|
86
51
|
function git(cwd: string, ...args: string[]): string {
|
|
@@ -99,156 +64,69 @@ function repository(t: test.TestContext): string {
|
|
|
99
64
|
return cwd;
|
|
100
65
|
}
|
|
101
66
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
status
|
|
112
|
-
counts: {
|
|
113
|
-
lineages: 0, compact_lineages: 0, legacy_lineages: 0, events: 0, bytes: 0,
|
|
114
|
-
eligible_candidates: 0, unsupported_lineages: 0, conflicts: 0,
|
|
115
|
-
},
|
|
116
|
-
supported_operations: UNSUPPORTED_REPAIR_ASSESSMENT.supportedOperations,
|
|
117
|
-
authorization_schema: UNSUPPORTED_REPAIR_ASSESSMENT.authorizationSchema,
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
function unrelatedStartTargetStatus(): ReviewStatusV3 {
|
|
121
|
-
const sha = `sha256:${"a".repeat(64)}`;
|
|
122
|
-
const tree = "b".repeat(40);
|
|
123
|
-
const projection = {
|
|
124
|
-
schema: "gentle-ai.review-integration.projection/v1" as const, kind: "current-changes" as const, projection: "workspace" as const,
|
|
125
|
-
baseTree: tree, initialReviewTree: tree, currentCandidateTree: tree, pathsDigest: sha, paths: [], intendedUntracked: [],
|
|
126
|
-
intendedUntrackedProof: sha, initialSnapshotIdentity: sha, currentSnapshotIdentity: sha,
|
|
127
|
-
};
|
|
128
|
-
return {
|
|
129
|
-
contract: "gentle-ai.review-integration/v2", applicability: "unrelated", receipt: { status: "not_applicable" }, action: "start",
|
|
130
|
-
replayability: "not_replayable", targetIdentity: sha, projection, repair: UNSUPPORTED_REPAIR_ASSESSMENT, candidates: [],
|
|
131
|
-
raw: {
|
|
132
|
-
schema: "gentle-ai.review-integration.status/v3", contract: "gentle-ai.review-integration/v2", operation: "review.status",
|
|
133
|
-
applicability: "unrelated", receipt: { status: "not_applicable" }, action: "start", replayability: "not_replayable", target_identity: sha,
|
|
134
|
-
repair: RAW_UNSUPPORTED_REPAIR_ASSESSMENT,
|
|
135
|
-
projection: { schema: projection.schema, kind: projection.kind, projection: projection.projection, base_tree: tree, initial_review_tree: tree, current_candidate_tree: tree, paths_digest: sha, paths: [], intended_untracked: [], intended_untracked_proof: sha, initial_snapshot_identity: sha, current_snapshot_identity: sha },
|
|
136
|
-
candidates: [],
|
|
137
|
-
},
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function currentTargetStatusFixture(lineageId: string, cwd: string): ReviewStatusV3 {
|
|
142
|
-
const sha = `sha256:${"a".repeat(64)}`;
|
|
143
|
-
const baseTree = git(cwd, "rev-parse", "HEAD^{tree}");
|
|
144
|
-
const candidateTree = git(cwd, "write-tree");
|
|
145
|
-
const projection = {
|
|
146
|
-
schema: "gentle-ai.review-integration.projection/v1" as const, kind: "current-changes" as const, projection: "workspace" as const,
|
|
147
|
-
baseTree, initialReviewTree: candidateTree, currentCandidateTree: candidateTree, pathsDigest: sha, paths: ["app.ts"], intendedUntracked: [],
|
|
148
|
-
intendedUntrackedProof: sha, initialSnapshotIdentity: sha, currentSnapshotIdentity: sha,
|
|
149
|
-
};
|
|
150
|
-
return {
|
|
151
|
-
contract: "gentle-ai.review-integration/v2", applicability: "current_target",
|
|
152
|
-
authority: { version: "compact-v2", lineageId, state: "reviewing", generation: 1, revision: sha },
|
|
153
|
-
receipt: { status: "expected_missing" }, action: "finalize", replayability: "not_replayable",
|
|
154
|
-
frozen: { tier: "medium", originalChangedLines: 1, correctionBudget: 1 },
|
|
155
|
-
targetIdentity: sha, projection, repair: UNSUPPORTED_REPAIR_ASSESSMENT, candidates: [],
|
|
156
|
-
raw: {
|
|
157
|
-
schema: "gentle-ai.review-integration.status/v3", contract: "gentle-ai.review-integration/v2", operation: "review.status",
|
|
158
|
-
applicability: "current_target", receipt: { status: "expected_missing" }, action: "finalize", replayability: "not_replayable", target_identity: sha,
|
|
159
|
-
authority: { version: "compact-v2", lineage_id: lineageId, state: "reviewing", generation: 1, revision: sha },
|
|
160
|
-
frozen: { tier: "medium", original_changed_lines: 1, correction_budget: 1 },
|
|
161
|
-
repair: RAW_UNSUPPORTED_REPAIR_ASSESSMENT,
|
|
162
|
-
projection: { schema: projection.schema, kind: projection.kind, projection: projection.projection, base_tree: baseTree, initial_review_tree: candidateTree, current_candidate_tree: candidateTree, paths_digest: sha, paths: ["app.ts"], intended_untracked: [], intended_untracked_proof: sha, initial_snapshot_identity: sha, current_snapshot_identity: sha },
|
|
163
|
-
candidates: [],
|
|
164
|
-
},
|
|
165
|
-
};
|
|
67
|
+
function enableGlobalReview(cwd: string): void {
|
|
68
|
+
assert.ok(DEV_BINARY, "GENTLE_AI_DEV_BINARY is required for this devtest");
|
|
69
|
+
const enabled = JSON.parse(execFileSync(DEV_BINARY, [
|
|
70
|
+
"review", "mode", "enable", "--scope", "global", "--cwd", cwd, "--json",
|
|
71
|
+
], { encoding: "utf8" })) as { status: { effective: string } };
|
|
72
|
+
assert.equal(enabled.status.effective, "on");
|
|
73
|
+
const status = JSON.parse(execFileSync(DEV_BINARY, [
|
|
74
|
+
"review", "mode", "status", "--cwd", cwd, "--json",
|
|
75
|
+
], { encoding: "utf8" })) as { status: { effective: string } };
|
|
76
|
+
assert.equal(status.status.effective, "on");
|
|
166
77
|
}
|
|
167
78
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
|
|
79
|
+
async function enableReview(native: NativeReviewCliV216, cwd: string): Promise<void> {
|
|
80
|
+
enableGlobalReview(cwd);
|
|
81
|
+
const disabled = await native.reviewMode({ cwd, operation: "disable" });
|
|
82
|
+
assert.equal(disabled.status.effective, "off");
|
|
83
|
+
assert.equal(disabled.status.source, "clone_local");
|
|
84
|
+
const enabled = await native.reviewMode({ cwd, operation: "enable" });
|
|
85
|
+
assert.equal(enabled.status.effective, "on");
|
|
86
|
+
assert.equal(enabled.status.source, "global");
|
|
173
87
|
}
|
|
174
88
|
|
|
175
|
-
function
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
function headlessContext(cwd: string, notices: Array<{ message: string; type?: string }> = []): ExtensionContext {
|
|
190
|
-
return { cwd, hasUI: false, ui: { notify: (message: string, type?: string) => { notices.push({ message, type }); } } } as unknown as ExtensionContext;
|
|
191
|
-
}
|
|
192
|
-
function confirmContext(cwd: string, answer: boolean): ExtensionContext {
|
|
193
|
-
return { cwd, hasUI: true, ui: { confirm: async () => answer, notify: () => {} } } as unknown as ExtensionContext;
|
|
89
|
+
async function candidateConsent(native: NativeReviewCliV216, cwd: string) {
|
|
90
|
+
const status = await native.targetStatus({ cwd, agent: "pi" });
|
|
91
|
+
assert.equal(status.nextTransition?.kind, "execute");
|
|
92
|
+
assert.equal(status.nextTransition?.execute?.operation, "review.start");
|
|
93
|
+
try {
|
|
94
|
+
await native.start({ cwd });
|
|
95
|
+
assert.fail("candidate START must return consent/v3 before review authority exists");
|
|
96
|
+
} catch (error) {
|
|
97
|
+
assert.ok(error instanceof NativeReviewConsentRequiredError, error instanceof Error ? error.message : String(error));
|
|
98
|
+
assert.equal(error.consent.schema, "gentle-ai.review-integration.consent/v3");
|
|
99
|
+
assert.equal(error.consent.agent, "pi");
|
|
100
|
+
return error.consent;
|
|
101
|
+
}
|
|
194
102
|
}
|
|
195
103
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
return
|
|
104
|
+
function consentAnswerCall(calls: NativeCall[], answer: "granted" | "declined"): NativeCall {
|
|
105
|
+
const matching = calls.filter((arguments_) => arguments_.at(0) === "review" && arguments_.at(1) === "start" && arguments_.includes("--consent") && arguments_.at(arguments_.indexOf("--consent") + 1) === answer);
|
|
106
|
+
assert.equal(matching.length, 1, `${answer} must execute exactly one provider invocation`);
|
|
107
|
+
return matching[0]!;
|
|
200
108
|
}
|
|
201
109
|
|
|
202
110
|
// ---------------------------------------------------------------------------
|
|
203
|
-
// Kill switch round trip
|
|
204
|
-
// from the community guide flows).
|
|
111
|
+
// Kill switch round trip.
|
|
205
112
|
// ---------------------------------------------------------------------------
|
|
206
113
|
|
|
207
|
-
test("dev-binary:
|
|
114
|
+
test("dev-binary: global opt-in then clone disable and enable clears only the local override", { skip: !RUNNABLE }, async (t) => {
|
|
208
115
|
const cwd = repository(t);
|
|
209
|
-
const {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
await
|
|
214
|
-
assert.
|
|
215
|
-
|
|
216
|
-
await command.handler("disable", headlessContext(cwd, notices));
|
|
217
|
-
assert.match(notices.at(-1)!.message, /receipt-driven development: off \(decided by \w+\)/);
|
|
218
|
-
|
|
219
|
-
await command.handler("status", headlessContext(cwd, notices));
|
|
220
|
-
assert.match(notices.at(-1)!.message, /receipt-driven development: off \(decided by \w+\)/);
|
|
221
|
-
|
|
222
|
-
await command.handler("enable", headlessContext(cwd, notices));
|
|
223
|
-
assert.match(notices.at(-1)!.message, /receipt-driven development: on \(decided by \w+\)/);
|
|
116
|
+
const { native } = journeyNative(DEV_BINARY!);
|
|
117
|
+
enableGlobalReview(cwd);
|
|
118
|
+
assert.equal((await native.reviewMode({ cwd, operation: "status" })).status.effective, "on");
|
|
119
|
+
assert.equal((await native.reviewMode({ cwd, operation: "disable" })).status.effective, "off");
|
|
120
|
+
assert.equal((await native.reviewMode({ cwd, operation: "status" })).status.source, "clone_local");
|
|
121
|
+
assert.equal((await native.reviewMode({ cwd, operation: "enable" })).status.effective, "on");
|
|
122
|
+
assert.equal((await native.reviewMode({ cwd, operation: "status" })).status.source, "global");
|
|
224
123
|
});
|
|
225
124
|
|
|
226
125
|
// ---------------------------------------------------------------------------
|
|
227
|
-
//
|
|
228
|
-
// triggers a consent prompt or notice.
|
|
126
|
+
// Candidate-bound consent/v3 answers.
|
|
229
127
|
// ---------------------------------------------------------------------------
|
|
230
128
|
|
|
231
|
-
test("dev-binary:
|
|
232
|
-
const cwd = repository(t);
|
|
233
|
-
writeFileSync(join(cwd, "app.ts"), "export const value = 1;\n");
|
|
234
|
-
git(cwd, "add", ".");
|
|
235
|
-
git(cwd, "commit", "-qm", "initial");
|
|
236
|
-
const { controller } = runtime(journeyNative(DEV_BINARY!));
|
|
237
|
-
const notices: Array<{ message: string; type?: string }> = [];
|
|
238
|
-
const result = await execStart(controller, "tier-0", headlessContext(cwd, notices));
|
|
239
|
-
const rendered = result.result as Record<string, unknown>;
|
|
240
|
-
assert.equal(rendered.lenses_required, false);
|
|
241
|
-
assert.equal(typeof rendered.hint, "string");
|
|
242
|
-
assert.match(rendered.hint as string, /the candidate has no pending changes/);
|
|
243
|
-
assert.equal(result.consent_notice, undefined, "tier 0 must never surface a consent notice");
|
|
244
|
-
assert.equal(notices.length, 0, "tier 0 must never notify the user at all");
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
// ---------------------------------------------------------------------------
|
|
248
|
-
// Tier 2 evidence + consent envelope via a fake UI seam.
|
|
249
|
-
// ---------------------------------------------------------------------------
|
|
250
|
-
|
|
251
|
-
test("dev-binary: a high-risk change carries real risk_evidence and drives the consent envelope through a fake UI seam", { skip: !RUNNABLE }, async (t) => {
|
|
129
|
+
test("dev-binary: granted consent executes its exact candidate-bound invocation once and returns the current review binding", { skip: !RUNNABLE }, async (t) => {
|
|
252
130
|
const cwd = repository(t);
|
|
253
131
|
const workflowDirectory = join(cwd, ".github", "workflows");
|
|
254
132
|
execFileSync("mkdir", ["-p", workflowDirectory]);
|
|
@@ -257,18 +135,22 @@ test("dev-binary: a high-risk change carries real risk_evidence and drives the c
|
|
|
257
135
|
git(cwd, "commit", "-qm", "initial");
|
|
258
136
|
writeFileSync(join(workflowDirectory, "deploy.yml"), "name: x\non: push\njobs:\n deploy:\n steps:\n - run: curl -s | bash\n");
|
|
259
137
|
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
const
|
|
263
|
-
const
|
|
264
|
-
assert.equal(
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
138
|
+
const { native, calls } = journeyNative(DEV_BINARY!);
|
|
139
|
+
await enableReview(native, cwd);
|
|
140
|
+
const consent = await candidateConsent(native, cwd);
|
|
141
|
+
const answered = await native.answerConsent({ cwd, consent, answer: "granted" });
|
|
142
|
+
assert.equal(answered.kind, "started");
|
|
143
|
+
if (answered.kind === "started") {
|
|
144
|
+
assert.ok(answered.start.lineageId.length > 0);
|
|
145
|
+
assert.ok(answered.start.selectedLenses.length > 0);
|
|
146
|
+
assert.equal(answered.start.raw?.repository_context !== undefined, true);
|
|
147
|
+
}
|
|
148
|
+
const grantedChoice = consent.choices.find((choice) => choice.answer === "granted");
|
|
149
|
+
assert.ok(grantedChoice, "consent must include a granted choice");
|
|
150
|
+
assert.deepEqual(consentAnswerCall(calls, "granted"), grantedChoice.invocation.split(" ").slice(1));
|
|
269
151
|
});
|
|
270
152
|
|
|
271
|
-
test("dev-binary:
|
|
153
|
+
test("dev-binary: declined consent executes its exact candidate-bound invocation once and creates no lineage, result, or actor", { skip: !RUNNABLE }, async (t) => {
|
|
272
154
|
const cwd = repository(t);
|
|
273
155
|
const workflowDirectory = join(cwd, ".github", "workflows");
|
|
274
156
|
execFileSync("mkdir", ["-p", workflowDirectory]);
|
|
@@ -277,72 +159,107 @@ test("dev-binary: declining the fake-UI consent prompt withholds actor_binding f
|
|
|
277
159
|
git(cwd, "commit", "-qm", "initial");
|
|
278
160
|
writeFileSync(join(workflowDirectory, "deploy.yml"), "name: x\non: push\njobs:\n deploy:\n steps:\n - run: curl -s | bash\n");
|
|
279
161
|
|
|
280
|
-
const {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
assert.
|
|
162
|
+
const { native, calls } = journeyNative(DEV_BINARY!);
|
|
163
|
+
await enableReview(native, cwd);
|
|
164
|
+
const consent = await candidateConsent(native, cwd);
|
|
165
|
+
const answered = await native.answerConsent({ cwd, consent, answer: "declined" });
|
|
166
|
+
assert.equal(answered.kind, "declined");
|
|
167
|
+
if (answered.kind === "declined") {
|
|
168
|
+
assert.equal(answered.consent, "declined_this_candidate");
|
|
169
|
+
assert.equal("lineageId" in answered, false);
|
|
170
|
+
assert.equal("start" in answered, false);
|
|
171
|
+
assert.equal("actor" in answered.raw, false);
|
|
172
|
+
}
|
|
173
|
+
const declinedChoice = consent.choices.find((choice) => choice.answer === "declined");
|
|
174
|
+
assert.ok(declinedChoice, "consent must include a declined choice");
|
|
175
|
+
assert.deepEqual(consentAnswerCall(calls, "declined"), declinedChoice.invocation.split(" ").slice(1));
|
|
176
|
+
const after = await native.targetStatus({ cwd, agent: "pi" });
|
|
177
|
+
assert.equal(after.authority, undefined);
|
|
178
|
+
assert.equal("receipt" in after, false, "last-event STATUS no longer exposes receipt state");
|
|
285
179
|
});
|
|
286
180
|
|
|
287
181
|
// ---------------------------------------------------------------------------
|
|
288
|
-
//
|
|
182
|
+
// Truthful non-authority responses.
|
|
289
183
|
// ---------------------------------------------------------------------------
|
|
290
184
|
|
|
291
|
-
|
|
292
|
-
// emits the disabled/unmanaged delivery envelope through lineage
|
|
293
|
-
// AUTO-DISCOVERY (`review validate --gate <gate>` with no `--lineage`) — its
|
|
294
|
-
// Go source (internal/cli/review_facade.go:2412-2433,1904-1916) routes an
|
|
295
|
-
// explicit `--lineage <id>` through discoverCompactFacadeReview's
|
|
296
|
-
// lineage-specific branch, whose failures are plain `fmt.Errorf` values, never
|
|
297
|
-
// the `*ReviewReceiptDiscoveryError` the disabled/unmanaged check requires —
|
|
298
|
-
// confirmed live against both a nonexistent lineage id ("load compact facade
|
|
299
|
-
// review lineage: ... no such file or directory", exit 1) and a real
|
|
300
|
-
// previously-started lineage under a disabled switch ("facade review receipt
|
|
301
|
-
// is not available", exit 1). Neither is the structured JSON envelope.
|
|
302
|
-
// Pi's `gentle_review` VALIDATE operation always binds an explicit
|
|
303
|
-
// lineageId (extensions/gentle-ai.ts throws "Review controller validate
|
|
304
|
-
// requires a lineageId" otherwise), so this envelope is structurally
|
|
305
|
-
// unreachable through today's controller wiring — this is a pre-existing
|
|
306
|
-
// property of the VALIDATE contract, not something Phase 5.2 introduced.
|
|
307
|
-
// This is flagged as a risk for a future design revision, not silently
|
|
308
|
-
// papered over here. What Phase 6 CAN and does prove against the real binary
|
|
309
|
-
// is that Pi's decoder correctly decodes the envelope gentle-ai actually
|
|
310
|
-
// sends via auto-discovery; the controller-level mapping/early-return
|
|
311
|
-
// (mapNativeValidateResult + the skip response) is proven separately with a
|
|
312
|
-
// synthetic fixture in tests/review-controller-native-routing.test.ts,
|
|
313
|
-
// because gentle-ai's own explicit-lineage code path can never supply it.
|
|
314
|
-
test("dev-binary: VALIDATE via lineage auto-discovery decodes the real disabled/unmanaged delivery envelope", { skip: !RUNNABLE }, async (t) => {
|
|
185
|
+
test("dev-binary: an empty candidate exposes the current STATUS refusal and never reconstructs START", { skip: !RUNNABLE }, async (t) => {
|
|
315
186
|
const cwd = repository(t);
|
|
316
187
|
writeFileSync(join(cwd, "app.ts"), "export const value = 1;\n");
|
|
317
188
|
git(cwd, "add", ".");
|
|
318
189
|
git(cwd, "commit", "-qm", "initial");
|
|
319
|
-
writeFileSync(join(cwd, "app.ts"), "export const value = 2;\n");
|
|
320
|
-
git(cwd, "add", "--", "app.ts");
|
|
321
190
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
191
|
+
const { native, calls } = journeyNative(DEV_BINARY!);
|
|
192
|
+
await enableReview(native, cwd);
|
|
193
|
+
const status = await native.targetStatus({ cwd, agent: "pi" });
|
|
194
|
+
assert.equal(status.nextTransition?.kind, "collect");
|
|
195
|
+
assert.equal(status.nextTransition?.reasonCode, "empty_candidate_base_ref_required");
|
|
196
|
+
await assert.rejects(
|
|
197
|
+
() => native.start({ cwd }),
|
|
198
|
+
(error: unknown) => error instanceof NativeReviewCliError
|
|
199
|
+
&& error.code === NATIVE_REVIEW_ERROR_CODE.SCHEMA_INCOMPATIBLE
|
|
200
|
+
&& error.mutationOutcome === "none",
|
|
201
|
+
);
|
|
202
|
+
assert.equal(calls.filter((arguments_) => arguments_.at(0) === "review" && arguments_.at(1) === "start").length, 0);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
test("dev-binary: a low-risk START closes the review with no receipt or follow-up capture", { skip: !RUNNABLE }, async (t) => {
|
|
206
|
+
const cwd = repository(t);
|
|
207
|
+
writeFileSync(join(cwd, "guide.md"), "# Guide\n\nBase\n");
|
|
208
|
+
git(cwd, "add", "guide.md");
|
|
209
|
+
git(cwd, "commit", "-qm", "docs: base guide");
|
|
210
|
+
writeFileSync(join(cwd, "guide.md"), "# Guide\n\nBase\n\nPassive update\n");
|
|
327
211
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
const
|
|
331
|
-
|
|
332
|
-
assert.equal(
|
|
333
|
-
assert.equal(
|
|
334
|
-
assert.
|
|
212
|
+
const { native } = journeyNative(DEV_BINARY!);
|
|
213
|
+
await enableReview(native, cwd);
|
|
214
|
+
const status = await native.targetStatus({ cwd, agent: "pi" });
|
|
215
|
+
const started = await native.start({ cwd, targetIdentity: status.targetIdentity });
|
|
216
|
+
assert.equal(started.action, "closed");
|
|
217
|
+
assert.equal(started.state, "approved");
|
|
218
|
+
assert.deepEqual(started.selectedLenses, []);
|
|
219
|
+
assert.equal(started.lensesRequired, false);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// gentle-pi#518: native STATUS projects a staged exact rename as its source
|
|
223
|
+
// and its destination. The Pi controller freezes the same identity, so
|
|
224
|
+
// ordinary START reaches native admission instead of being rejected locally
|
|
225
|
+
// with candidate-target-projection-drift.
|
|
226
|
+
test("dev-binary: ordinary START through the Pi controller admits a staged exact rename plus an addition", { skip: !RUNNABLE }, async (t) => {
|
|
227
|
+
const cwd = repository(t);
|
|
228
|
+
mkdirSync(join(cwd, "active"));
|
|
229
|
+
writeFileSync(join(cwd, "active", "document.md"), "# Document\n\nline one\nline two\n");
|
|
230
|
+
git(cwd, "add", "active/document.md");
|
|
231
|
+
git(cwd, "commit", "-qm", "docs: base document");
|
|
232
|
+
mkdirSync(join(cwd, "archive"));
|
|
233
|
+
git(cwd, "mv", "active/document.md", "archive/document.md");
|
|
234
|
+
writeFileSync(join(cwd, "report.md"), "# Report\n\nPassive report\n");
|
|
235
|
+
git(cwd, "add", "-A");
|
|
236
|
+
assert.match(git(cwd, "diff", "--cached", "--name-status", "--find-renames=100%"), /^R100\tactive\/document\.md\tarchive\/document\.md$/m, "the candidate must stage an exact rename");
|
|
237
|
+
|
|
238
|
+
const { native, calls } = journeyNative(DEV_BINARY!);
|
|
239
|
+
await enableReview(native, cwd);
|
|
240
|
+
const status = await native.targetStatus({ cwd, agent: "pi" });
|
|
241
|
+
assert.deepEqual([...status.projection.paths].sort(), ["active/document.md", "archive/document.md", "report.md"], "native STATUS projects the rename as both of its paths");
|
|
242
|
+
|
|
243
|
+
const started = await __testing.executeReviewControllerOperation({ operation: "start", input: JSON.stringify({ mode: "ordinary" }) }, cwd, native);
|
|
244
|
+
assert.notEqual(started.outcome, "native-operation-failed", `START must reach native admission: ${JSON.stringify(started)}`);
|
|
245
|
+
assert.equal(started.operation, "start");
|
|
246
|
+
const result = started.result as Record<string, unknown>;
|
|
247
|
+
assert.equal(typeof result.lineage_id, "string", "native START must create a lineage");
|
|
248
|
+
// Documentation-only candidate: native admission closes it approved with
|
|
249
|
+
// no lenses, exactly as the low-risk journey above.
|
|
250
|
+
assert.equal(result.action, "closed");
|
|
251
|
+
assert.equal(result.state, "approved");
|
|
252
|
+
assert.equal(calls.filter((arguments_) => arguments_.at(0) === "review" && arguments_.at(1) === "start").length, 1, "exactly one native START runs");
|
|
335
253
|
});
|
|
336
254
|
|
|
337
255
|
test.before(() => {
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
start: true, finalize: true, validate: true, bindSdd: true, sddStatus: true, status: true, inventory: true,
|
|
341
|
-
reclaim: true, recover: true, abandon: true, quarantineLegacy: true, reconcileAuthority: true, repairLegacyAlias: true,
|
|
342
|
-
mode: true, riskEvidence: true, hint: true, delivery: true,
|
|
343
|
-
});
|
|
344
|
-
}
|
|
256
|
+
process.env.HOME = DEV_HOME;
|
|
257
|
+
process.env.USERPROFILE = DEV_HOME;
|
|
345
258
|
});
|
|
346
259
|
test.after(() => {
|
|
347
|
-
if (
|
|
260
|
+
if (ORIGINAL_HOME === undefined) delete process.env.HOME;
|
|
261
|
+
else process.env.HOME = ORIGINAL_HOME;
|
|
262
|
+
if (ORIGINAL_USERPROFILE === undefined) delete process.env.USERPROFILE;
|
|
263
|
+
else process.env.USERPROFILE = ORIGINAL_USERPROFILE;
|
|
264
|
+
rmSync(DEV_HOME, { recursive: true, force: true });
|
|
348
265
|
});
|