gentle-pi 2.2.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/README.md +51 -80
  2. package/assets/agents/review-readability.md +0 -2
  3. package/assets/agents/review-reliability.md +0 -2
  4. package/assets/agents/review-resilience.md +0 -2
  5. package/assets/agents/review-risk.md +0 -2
  6. package/assets/agents/sdd-apply.md +5 -3
  7. package/assets/agents/sdd-proposal.md +2 -0
  8. package/assets/agents/sdd-research.md +54 -0
  9. package/assets/agents/sdd-status.md +4 -4
  10. package/assets/agents/sdd-tasks.md +2 -3
  11. package/assets/agents/sdd-verify.md +21 -1
  12. package/assets/chains/sdd-full.chain.md +1 -1
  13. package/assets/chains/sdd-verify.chain.md +1 -1
  14. package/assets/orchestrator-delegation.md +54 -248
  15. package/assets/orchestrator-memory.md +2 -0
  16. package/assets/orchestrator.md +22 -38
  17. package/assets/sdd-orchestrator-workflow.md +30 -37
  18. package/assets/support/sdd-status-contract.md +7 -7
  19. package/contracts/review-integration/v1/schemas/transition-execution.schema.json +42 -0
  20. package/contracts/review-integration/v2/schemas/last-event-closure.schema.json +66 -0
  21. package/contracts/review-integration/v2/schemas/opencode-provider-role.schema.json +14 -0
  22. package/docs/native-authority-architecture.md +9 -11
  23. package/docs/review-integration.md +27 -373
  24. package/extensions/ask-user-choice.ts +151 -0
  25. package/extensions/gentle-ai.ts +1778 -3673
  26. package/extensions/quiet-tools.ts +515 -32
  27. package/extensions/sdd-init.ts +4 -8
  28. package/lib/gentle-ai-renderer.ts +70 -0
  29. package/lib/model-routing-authority.ts +133 -0
  30. package/lib/native-review-cli.ts +372 -940
  31. package/lib/opaque-pi-reviewer-adapter.ts +284 -0
  32. package/lib/review-candidate-view.ts +341 -132
  33. package/lib/review-host-relay.ts +210 -68
  34. package/lib/review-integration-v2.ts +839 -307
  35. package/lib/review-last-event-controller.ts +35 -0
  36. package/lib/sdd-preflight.ts +177 -66
  37. package/lib/sdd-status.ts +66 -111
  38. package/lib/terminal-theme.ts +1 -1
  39. package/package.json +83 -82
  40. package/runtime/gentle-ai-binary.mjs +1 -1
  41. package/runtime/native-review-cli.mjs +322 -890
  42. package/runtime/review-integration-v2.mjs +789 -257
  43. package/runtime/review-relay-contract.mjs +1 -1
  44. package/scripts/{build-git-commit-transaction-runner.mjs → build-runtime-modules.mjs} +4 -5
  45. package/scripts/gentle-ai-installer.mjs +75 -22
  46. package/scripts/maintainer/provider-relay-matrix.mjs +195 -11
  47. package/scripts/test-packed-runner.mjs +4 -7
  48. package/scripts/verify-package-files.mjs +12 -13
  49. package/skills/_shared/review-ledger-contract.md +8 -14
  50. package/skills/chained-pr/SKILL.md +3 -0
  51. package/skills/cognitive-doc-design/SKILL.md +1 -1
  52. package/skills/comment-writer/SKILL.md +1 -1
  53. package/skills/gentle-ai/SKILL.md +7 -74
  54. package/skills/judgment-day/SKILL.md +5 -7
  55. package/skills/rdd-defect-workflow/SKILL.md +3 -3
  56. package/skills/release/SKILL.md +3 -3
  57. package/skills/skill-registry/SKILL.md +1 -1
  58. package/skills/work-unit-commits/SKILL.md +3 -1
  59. package/tests/artifact-language.test.ts +24 -11
  60. package/tests/ask-user-choice.test.ts +264 -0
  61. package/tests/codegraph-tools.test.ts +3 -3
  62. package/tests/crosslane/cross-lane.mjs +15 -1168
  63. package/tests/delegated-key-learnings-contract.test.ts +8 -6
  64. package/tests/devbinary/native-review-parity.devtest.ts +167 -250
  65. package/tests/devbinary/pi-host-relay.devtest.ts +867 -0
  66. package/tests/fixtures/devbinary/last-event-capture-correction-plan.captured.json +10 -0
  67. package/tests/fixtures/devbinary/last-event-capture-refuter-approved.captured.json +20 -0
  68. package/tests/fixtures/devbinary/last-event-capture-refuter-correction-required.captured.json +26 -0
  69. package/tests/fixtures/devbinary/last-event-capture-result-approved.captured.json +8 -0
  70. package/tests/fixtures/devbinary/last-event-capture-result-correction-required.captured.json +26 -0
  71. package/tests/fixtures/devbinary/last-event-capture-validation-approved.captured.json +8 -0
  72. package/tests/fixtures/devbinary/last-event-closure.provenance.md +13 -0
  73. package/tests/fixtures/devbinary/review-acknowledged-v1.captured.json +9 -0
  74. package/tests/fixtures/devbinary/review-acknowledged.provenance.md +31 -0
  75. package/tests/fixtures/devbinary/start-v3-zero-lens-closed.captured.json +21 -0
  76. package/tests/fixtures/native-review-cli/v2.5.0-rc.1/PROVENANCE.txt +15 -0
  77. package/tests/gentle-ai-binary.test.ts +83 -13
  78. package/tests/gentle-ai-dev-binary.test.ts +51 -228
  79. package/tests/gentle-ai-installer.test.ts +137 -47
  80. package/tests/gentle-ai.test.ts +820 -131
  81. package/tests/gentle-theme.test.ts +133 -0
  82. package/tests/maintainer/provider-relay.maintest.ts +340 -4
  83. package/tests/model-routing-authority.test.ts +257 -0
  84. package/tests/native-review-capability-contract.test.ts +28 -1
  85. package/tests/native-review-cli.test.ts +586 -962
  86. package/tests/native-review-consent.test.ts +150 -66
  87. package/tests/native-review-parity-runtime.test.ts +80 -359
  88. package/tests/native-review-parity.test.ts +674 -925
  89. package/tests/opaque-pi-reviewer-adapter.test.ts +266 -0
  90. package/tests/orchestrator-budget.test.ts +132 -88
  91. package/tests/orchestrator-rdd-ownership.test.ts +103 -0
  92. package/tests/package-manifest.test.ts +37 -35
  93. package/tests/provider-defect-handoff.test.ts +15 -118
  94. package/tests/quiet-tool-rendering.test.ts +1055 -28
  95. package/tests/review-authority-recovery-docs.test.ts +1 -2
  96. package/tests/review-candidate-view.test.ts +409 -3
  97. package/tests/review-compact-contract.test.ts +4 -30
  98. package/tests/review-controller-lock-status.test.ts +2 -2
  99. package/tests/review-controller-native-recovery.test.ts +363 -1450
  100. package/tests/review-controller-native-routing.test.ts +1251 -5642
  101. package/tests/review-controller-retired-ops.test.ts +1 -1
  102. package/tests/review-controller-workspace-root.test.ts +222 -71
  103. package/tests/review-controller.test.ts +26 -816
  104. package/tests/review-corrected-finalize-binding.test.ts +112 -153
  105. package/tests/review-dispatch-hydration-gap.test.ts +1 -53
  106. package/tests/review-gate.test.ts +0 -45
  107. package/tests/review-host-relay-restart-parity.test.ts +360 -0
  108. package/tests/review-host-relay-routing.test.ts +117 -82
  109. package/tests/review-host-relay.test.ts +242 -8
  110. package/tests/review-integration-v2-forward.test.ts +576 -553
  111. package/tests/review-integration-v2.test.ts +239 -165
  112. package/tests/review-last-event-closure.test.ts +408 -0
  113. package/tests/review-ledger-contract.test.ts +97 -35
  114. package/tests/review-recovered-lineage-routing.test.ts +0 -47
  115. package/tests/review-relay-transport-agent.test.ts +119 -46
  116. package/tests/review-snapshot.test.ts +3 -2
  117. package/tests/runtime-harness.mjs +361 -196
  118. package/tests/sdd-agent-tools.test.ts +36 -0
  119. package/tests/sdd-preflight.test.ts +81 -15
  120. package/tests/sdd-status.test.ts +109 -110
  121. package/tests/skill-collision-prefixes.test.ts +5 -8
  122. package/tests/writer-edit-surface-scope.test.ts +230 -0
  123. package/themes/Gentleman-Cute.json +94 -0
  124. package/themes/Gentleman-Sexy.json +92 -0
  125. package/lib/git-commit-transaction.ts +0 -861
  126. package/runtime/git-commit-transaction.mjs +0 -862
  127. package/scripts/run-git-commit-transaction.mjs +0 -35
  128. package/tests/fixtures/native-review-cli/v2.1.3/sdd-status-engram.json +0 -139
  129. package/tests/fixtures/native-review-cli/v2.1.3/sdd-status.json +0 -200
  130. package/tests/git-commit-transaction.test.ts +0 -530
@@ -0,0 +1,257 @@
1
+ import assert from "node:assert/strict";
2
+ import { mkdirSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import test from "node:test";
6
+ import { applySavedModelConfig, readModelConfig, readModelConfigAsync } from "../extensions/gentle-ai.ts";
7
+
8
+ test("model routing authority normalizes and preserves sync/async source status", async (t) => {
9
+ const loaded = await import("../lib/model-routing-authority.ts").then(
10
+ (module) => ({ module, error: undefined }),
11
+ (error) => ({ module: undefined, error }),
12
+ );
13
+ assert.ok(
14
+ loaded.module,
15
+ `shared model routing authority must load: ${String(loaded.error)}`,
16
+ );
17
+ const authority = loaded.module;
18
+ const root = mkdtempSync(join(tmpdir(), "gentle-pi-model-routing-authority-"));
19
+ const globalDir = join(root, "global");
20
+ const projectDir = join(root, "project");
21
+ const projectConfigDir = join(projectDir, ".pi", "gentle-ai");
22
+ const agentsDir = join(root, "agents");
23
+ mkdirSync(globalDir, { recursive: true });
24
+ mkdirSync(projectConfigDir, { recursive: true });
25
+ mkdirSync(agentsDir, { recursive: true });
26
+ t.after(() => rmSync(root, { recursive: true, force: true }));
27
+
28
+ assert.equal(authority.normalizeModelId(" openai/gpt-5 "), "openai/gpt-5");
29
+ assert.equal(authority.normalizeModelId("bad model"), undefined);
30
+ assert.deepEqual(authority.normalizeRoutingEntry(" inherit "), { model: "inherit" });
31
+ assert.deepEqual(
32
+ authority.normalizeRoutingEntry({ model: " anthropic/opus ", thinking: "high" }),
33
+ { model: "anthropic/opus", thinking: "high" },
34
+ );
35
+ assert.deepEqual(authority.normalizeRoutingEntry(null), undefined);
36
+ assert.deepEqual(
37
+ authority.normalizeModelConfig({
38
+ worker: " openai/gpt-5 ",
39
+ clear: {},
40
+ "not valid": "ignored",
41
+ nullValue: null,
42
+ }),
43
+ { worker: { model: "openai/gpt-5" }, clear: {} },
44
+ );
45
+
46
+ const missingPath = join(globalDir, "missing.json");
47
+ assert.deepEqual(authority.readModelConfigFile(missingPath), { status: "missing" });
48
+ assert.deepEqual(await authority.readModelConfigFileAsync(missingPath), { status: "missing" });
49
+
50
+ const validGlobalPath = join(globalDir, "valid.json");
51
+ writeFileSync(
52
+ validGlobalPath,
53
+ JSON.stringify({ worker: "openai/gpt-5", reviewer: { thinking: "medium" }, "not valid": "openai/gpt-4" }),
54
+ );
55
+ const validSync = authority.readModelConfigFile(validGlobalPath);
56
+ const validAsync = await authority.readModelConfigFileAsync(validGlobalPath);
57
+ assert.deepEqual(validSync, {
58
+ status: "valid",
59
+ config: {
60
+ worker: { model: "openai/gpt-5" },
61
+ reviewer: { model: undefined, thinking: "medium" },
62
+ "not valid": { model: "openai/gpt-4" },
63
+ },
64
+ });
65
+ assert.deepEqual(validAsync, validSync);
66
+
67
+ const invalidGlobalPath = join(globalDir, "invalid.json");
68
+ writeFileSync(invalidGlobalPath, "[]");
69
+ assert.deepEqual(authority.readModelConfigFile(invalidGlobalPath), {
70
+ status: "invalid",
71
+ path: invalidGlobalPath,
72
+ });
73
+ assert.deepEqual(await authority.readModelConfigFileAsync(invalidGlobalPath), {
74
+ status: "invalid",
75
+ path: invalidGlobalPath,
76
+ });
77
+
78
+ const projectPath = join(projectConfigDir, "models.json");
79
+ writeFileSync(projectPath, JSON.stringify({ project: "google/gemini" }));
80
+ assert.deepEqual(
81
+ authority.readSavedModelConfig(missingPath, projectPath),
82
+ { status: "valid", config: { project: { model: "google/gemini" } } },
83
+ );
84
+ assert.deepEqual(
85
+ await authority.readSavedModelConfigAsync(missingPath, projectPath),
86
+ await authority.readSavedModelConfig(missingPath, projectPath),
87
+ );
88
+ assert.deepEqual(authority.readSavedModelConfig(invalidGlobalPath, projectPath), {
89
+ status: "invalid",
90
+ path: invalidGlobalPath,
91
+ });
92
+ assert.deepEqual(await authority.readSavedModelConfigAsync(invalidGlobalPath, projectPath), {
93
+ status: "invalid",
94
+ path: invalidGlobalPath,
95
+ });
96
+
97
+ const previousConfigHome = process.env.GENTLE_PI_CONFIG_HOME;
98
+ process.env.GENTLE_PI_CONFIG_HOME = globalDir;
99
+ t.after(() => {
100
+ if (previousConfigHome === undefined) delete process.env.GENTLE_PI_CONFIG_HOME;
101
+ else process.env.GENTLE_PI_CONFIG_HOME = previousConfigHome;
102
+ });
103
+ writeFileSync(projectPath, JSON.stringify({ project: "google/gemini" }));
104
+ assert.deepEqual(readModelConfig(projectDir), { project: { model: "google/gemini" } });
105
+ assert.deepEqual(await readModelConfigAsync(projectDir), readModelConfig(projectDir));
106
+
107
+ writeFileSync(projectPath, "[]");
108
+ assert.deepEqual(authority.readModelConfigFile(projectPath), { status: "invalid", path: projectPath });
109
+ assert.deepEqual(await authority.readModelConfigFileAsync(projectPath), { status: "invalid", path: projectPath });
110
+ assert.deepEqual(readModelConfig(projectDir), {});
111
+ assert.deepEqual(await readModelConfigAsync(projectDir), {});
112
+
113
+ writeFileSync(join(globalDir, "models.json"), JSON.stringify({ global: "openai/gpt-5" }));
114
+ assert.deepEqual(readModelConfig(projectDir), { global: { model: "openai/gpt-5" } });
115
+ assert.deepEqual(await readModelConfigAsync(projectDir), readModelConfig(projectDir));
116
+
117
+ writeFileSync(join(globalDir, "models.json"), "[]");
118
+ assert.deepEqual(readModelConfig(projectDir), {});
119
+ assert.deepEqual(await readModelConfigAsync(projectDir), {});
120
+ });
121
+
122
+ test("saved-routing apply fails closed for invalid project and global sources", async (t) => {
123
+ const root = mkdtempSync(join(tmpdir(), "gentle-pi-model-routing-apply-"));
124
+ const configHome = join(root, "global");
125
+ const projectConfigDir = join(root, ".pi", "gentle-ai");
126
+ const projectAgentsDir = join(root, ".pi", "agents");
127
+ const projectProfileDir = join(root, ".pi");
128
+ const agentHome = join(root, "agent-home");
129
+ const agentHomeAgentsDir = join(agentHome, "agents");
130
+ const agentHomeSubagentsDir = join(agentHome, "subagents");
131
+ mkdirSync(configHome, { recursive: true });
132
+ mkdirSync(projectConfigDir, { recursive: true });
133
+ mkdirSync(projectAgentsDir, { recursive: true });
134
+ mkdirSync(projectProfileDir, { recursive: true });
135
+ mkdirSync(agentHomeAgentsDir, { recursive: true });
136
+ mkdirSync(agentHomeSubagentsDir, { recursive: true });
137
+ t.after(() => rmSync(root, { recursive: true, force: true }));
138
+
139
+ const previousConfigHome = process.env.GENTLE_PI_CONFIG_HOME;
140
+ const previousAgentHome = process.env.GENTLE_PI_AGENT_HOME;
141
+ process.env.GENTLE_PI_CONFIG_HOME = configHome;
142
+ process.env.GENTLE_PI_AGENT_HOME = agentHome;
143
+ t.after(() => {
144
+ if (previousConfigHome === undefined) delete process.env.GENTLE_PI_CONFIG_HOME;
145
+ else process.env.GENTLE_PI_CONFIG_HOME = previousConfigHome;
146
+ if (previousAgentHome === undefined) delete process.env.GENTLE_PI_AGENT_HOME;
147
+ else process.env.GENTLE_PI_AGENT_HOME = previousAgentHome;
148
+ });
149
+
150
+ const agentPath = join(projectAgentsDir, "worker.md");
151
+ writeFileSync(agentPath, "---\nname: worker\ndescription: Worker\n---\nbody\n");
152
+ const profilePath = join(projectProfileDir, "subagents.json");
153
+ const profileBytes = JSON.stringify({
154
+ unrelated: { keep: true },
155
+ model_profiles: { worker: { model: "existing/model", effort: "low" } },
156
+ }, null, 2) + "\n";
157
+ writeFileSync(profilePath, profileBytes);
158
+ const context = { cwd: root } as Parameters<typeof applySavedModelConfig>[0];
159
+ const projectPath = join(projectConfigDir, "models.json");
160
+ const globalPath = join(configHome, "models.json");
161
+ let mutatorCalls = 0;
162
+ const applyConfig = async () => {
163
+ mutatorCalls += 1;
164
+ return { updated: 0, skipped: 0 };
165
+ };
166
+
167
+ for (const projectValue of ["{", "[]", "null"] as const) {
168
+ writeFileSync(projectPath, projectValue);
169
+ const before = statSync(profilePath);
170
+ const result = await applySavedModelConfig(context, applyConfig);
171
+ assert.deepEqual(result, { updated: 0, skipped: 0, invalidPath: projectPath });
172
+ assert.equal(mutatorCalls, 0);
173
+ assert.equal(readFileSync(profilePath, "utf8"), profileBytes);
174
+ assert.equal(statSync(profilePath).mtimeMs, before.mtimeMs);
175
+ }
176
+
177
+ writeFileSync(globalPath, "[]");
178
+ writeFileSync(projectPath, JSON.stringify({ worker: "new/model" }));
179
+ const before = statSync(profilePath);
180
+ const result = await applySavedModelConfig(context, applyConfig);
181
+ assert.deepEqual(result, { updated: 0, skipped: 0, invalidPath: globalPath });
182
+ assert.equal(mutatorCalls, 0);
183
+ assert.equal(readFileSync(profilePath, "utf8"), profileBytes);
184
+ assert.equal(statSync(profilePath).mtimeMs, before.mtimeMs);
185
+ });
186
+
187
+ test("saved-routing apply preserves missing, valid, null, inherit, and omission behavior", async (t) => {
188
+ const root = mkdtempSync(join(tmpdir(), "gentle-pi-model-routing-apply-valid-"));
189
+ const configHome = join(root, "global");
190
+ const projectConfigDir = join(root, ".pi", "gentle-ai");
191
+ const projectAgentsDir = join(root, ".pi", "agents");
192
+ const projectProfileDir = join(root, ".pi");
193
+ const agentHome = join(root, "agent-home");
194
+ const agentHomeAgentsDir = join(agentHome, "agents");
195
+ const agentHomeSubagentsDir = join(agentHome, "subagents");
196
+ mkdirSync(configHome, { recursive: true });
197
+ mkdirSync(projectConfigDir, { recursive: true });
198
+ mkdirSync(projectAgentsDir, { recursive: true });
199
+ mkdirSync(projectProfileDir, { recursive: true });
200
+ mkdirSync(agentHomeAgentsDir, { recursive: true });
201
+ mkdirSync(agentHomeSubagentsDir, { recursive: true });
202
+ t.after(() => rmSync(root, { recursive: true, force: true }));
203
+
204
+ const previousConfigHome = process.env.GENTLE_PI_CONFIG_HOME;
205
+ const previousAgentHome = process.env.GENTLE_PI_AGENT_HOME;
206
+ process.env.GENTLE_PI_CONFIG_HOME = configHome;
207
+ process.env.GENTLE_PI_AGENT_HOME = agentHome;
208
+ t.after(() => {
209
+ if (previousConfigHome === undefined) delete process.env.GENTLE_PI_CONFIG_HOME;
210
+ else process.env.GENTLE_PI_CONFIG_HOME = previousConfigHome;
211
+ if (previousAgentHome === undefined) delete process.env.GENTLE_PI_AGENT_HOME;
212
+ else process.env.GENTLE_PI_AGENT_HOME = previousAgentHome;
213
+ });
214
+
215
+ const agentPath = join(projectAgentsDir, "worker.md");
216
+ writeFileSync(agentPath, "---\nname: worker\ndescription: Worker\n---\nbody\n");
217
+ const profilePath = join(projectProfileDir, "subagents.json");
218
+ const initialProfile = {
219
+ unrelated: { keep: true },
220
+ model_profiles: { worker: { model: "existing/model", effort: "low" } },
221
+ };
222
+ writeFileSync(profilePath, `${JSON.stringify(initialProfile, null, 2)}\n`);
223
+ const context = { cwd: root } as Parameters<typeof applySavedModelConfig>[0];
224
+ const projectPath = join(projectConfigDir, "models.json");
225
+
226
+ const missing = await applySavedModelConfig(context);
227
+ assert.equal(missing.invalidPath, undefined);
228
+ assert.deepEqual(JSON.parse(readFileSync(profilePath, "utf8")), initialProfile);
229
+
230
+ writeFileSync(projectPath, JSON.stringify({ worker: "inherit" }));
231
+ const valid = await applySavedModelConfig(context);
232
+ assert.equal(valid.invalidPath, undefined);
233
+ const validProfile = JSON.parse(readFileSync(profilePath, "utf8")) as Record<string, any>;
234
+ assert.deepEqual(validProfile.unrelated, initialProfile.unrelated);
235
+ assert.deepEqual(validProfile.model_profiles.worker, { model: "inherit" });
236
+ assert.match(readFileSync(agentPath, "utf8"), /model: inherit\n/);
237
+ assert.doesNotMatch(readFileSync(agentPath, "utf8"), /thinking:/);
238
+
239
+ const afterValidBytes = readFileSync(profilePath, "utf8");
240
+ const afterValid = statSync(profilePath);
241
+ writeFileSync(projectPath, JSON.stringify({ worker: null }));
242
+ const nullEntry = await applySavedModelConfig(context);
243
+ assert.equal(nullEntry.invalidPath, undefined);
244
+ assert.equal(readFileSync(profilePath, "utf8"), afterValidBytes);
245
+ assert.equal(statSync(profilePath).mtimeMs, afterValid.mtimeMs);
246
+ assert.doesNotMatch(readFileSync(agentPath, "utf8"), /model: null/);
247
+
248
+ let mutatorCalls = 0;
249
+ const applyConfig = async () => {
250
+ mutatorCalls += 1;
251
+ return { updated: 0, skipped: 0 };
252
+ };
253
+ writeFileSync(projectPath, JSON.stringify({ worker: "new/model" }));
254
+ const injected = await applySavedModelConfig(context, applyConfig);
255
+ assert.equal(injected.invalidPath, undefined);
256
+ assert.equal(mutatorCalls, 1);
257
+ });
@@ -123,11 +123,38 @@ test("2.4.0 repeats 2.2.3 because the START envelope Pi consumes still omits ris
123
123
  assert.deepEqual(contract, NATIVE_CLI_CONTRACTS["2.2.3"] as Record<string, boolean>);
124
124
  });
125
125
 
126
+ test("2.5.0-rc.3 repeats 2.4.0 because the negotiated lane Pi consumes is unchanged", () => {
127
+ // Ground-truthed by driving the exact v2.5.0-rc.3 tagged build through the
128
+ // gentle-ai-bench driven journey corpus (exit 0), which exercises the
129
+ // start/status/capture/validate/mode/delivery lifecycles Pi consumes. The
130
+ // lane now advertises capabilities/v2.3 and the reviewing START is the
131
+ // `start/v4` continuation envelope that #499 already decodes; the closed
132
+ // fields Pi reads did not change. riskEvidence and hint stay dark: still
133
+ // not proven to reach the negotiated START path Pi consumes.
134
+ const contract = NATIVE_CLI_CONTRACTS["2.5.0-rc.3"] as Record<string, boolean>;
135
+ assert.equal(contract.riskEvidence, false);
136
+ assert.equal(contract.hint, false);
137
+ assert.deepEqual(contract, NATIVE_CLI_CONTRACTS["2.4.0"] as Record<string, boolean>);
138
+ });
139
+
140
+ test("2.5.0 repeats 2.5.0-rc.3 because the stable lane Pi consumes is unchanged", () => {
141
+ // Ground-truthed against the published v2.5.0 binary installed from its
142
+ // signed archive: the v2 lane advertises capabilities/v2.4 and answers
143
+ // status/v6, consent/v3, and the `start/v4` continuation, all already
144
+ // decoded. The closed fields Pi reads did not change between rc.3 and
145
+ // stable. riskEvidence and hint stay dark: still not proven to reach the
146
+ // negotiated START path Pi consumes.
147
+ const contract = NATIVE_CLI_CONTRACTS["2.5.0"] as Record<string, boolean>;
148
+ assert.equal(contract.riskEvidence, false);
149
+ assert.equal(contract.hint, false);
150
+ assert.deepEqual(contract, NATIVE_CLI_CONTRACTS["2.5.0-rc.3"] as Record<string, boolean>);
151
+ });
152
+
126
153
  test("no shipped version key was added beyond the pin bump", () => {
127
154
  // Rows are promises to consumers, so a new key only ever appears in a
128
155
  // dedicated commit alongside a pin bump, never as a side effect. v2.2.4 and
129
156
  // v2.3.0 shipped upstream while Pi stayed on 2.2.3 and were never pinned,
130
157
  // so they get no row: a row asserts ground truth measured against a binary
131
158
  // Pi actually ran, and the table only has to be ascending, not gapless.
132
- assert.deepEqual(Object.keys(NATIVE_CLI_CONTRACTS), [...DARK_VERSIONS, "2.2.0", "2.2.1", "2.2.2", "2.2.3", "2.4.0"]);
159
+ assert.deepEqual(Object.keys(NATIVE_CLI_CONTRACTS), [...DARK_VERSIONS, "2.2.0", "2.2.1", "2.2.2", "2.2.3", "2.4.0", "2.5.0-rc.3", "2.5.0"]);
133
160
  });