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