gentle-pi 2.1.2 → 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 +108 -79
- package/assets/agents/gentle-ai-worker.md +7 -3
- package/assets/agents/jd-fix-agent.md +1 -1
- package/assets/agents/jd-judge-a.md +3 -1
- package/assets/agents/jd-judge-b.md +3 -1
- package/assets/agents/review-readability.md +4 -3
- package/assets/agents/review-reliability.md +4 -3
- package/assets/agents/review-resilience.md +4 -3
- package/assets/agents/review-risk.md +4 -3
- package/assets/agents/sdd-apply.md +11 -4
- package/assets/agents/sdd-archive.md +6 -1
- package/assets/agents/sdd-design.md +6 -1
- package/assets/agents/sdd-explore.md +6 -2
- package/assets/agents/sdd-init.md +10 -2
- package/assets/agents/sdd-onboard.md +6 -1
- package/assets/agents/sdd-proposal.md +8 -1
- package/assets/agents/sdd-research.md +54 -0
- package/assets/agents/sdd-spec.md +6 -1
- package/assets/agents/sdd-status.md +10 -5
- package/assets/agents/sdd-sync.md +6 -1
- package/assets/agents/sdd-tasks.md +8 -4
- package/assets/agents/sdd-verify.md +27 -2
- package/assets/chains/4r-review.chain.md +2 -0
- package/assets/chains/sdd-full.chain.md +2 -2
- package/assets/chains/sdd-plan.chain.md +1 -1
- package/assets/chains/sdd-verify.chain.md +2 -2
- package/assets/orchestrator-delegation.md +145 -160
- package/assets/orchestrator-memory.md +2 -0
- package/assets/orchestrator.md +25 -48
- package/assets/sdd-orchestrator-workflow.md +163 -25
- package/assets/support/sdd-status-contract.md +24 -6
- package/contracts/review-integration/v1/fixtures/consent.fixture.json +3 -3
- package/contracts/review-integration/v1/fixtures/start-v2.fixture.json +19 -28
- package/contracts/review-integration/v1/fixtures/start.fixture.json +1 -10
- package/contracts/review-integration/v1/fixtures/status-v2.fixture.json +12 -21
- package/contracts/review-integration/v1/schemas/correction-plan-request.schema.json +49 -0
- package/contracts/review-integration/v1/schemas/operation.schema.json +76 -0
- package/contracts/review-integration/v1/schemas/repair.schema.json +39 -0
- package/contracts/review-integration/v1/schemas/status-v2.schema.json +4 -2
- package/contracts/review-integration/v1/schemas/status.schema.json +4 -2
- package/contracts/review-integration/v1/schemas/transition-execution.schema.json +42 -0
- package/contracts/review-integration/v2/fixtures/consent.fixture.json +1 -1
- package/contracts/review-integration/v2/fixtures/start.fixture.json +1 -10
- package/contracts/review-integration/v2/fixtures/status.fixture.json +1 -10
- package/contracts/review-integration/v2/schemas/failure.schema.json +5 -1
- 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-integration/v2/schemas/operation.schema.json +6 -1
- package/contracts/review-integration/v2/schemas/repair.schema.json +4 -2
- package/contracts/review-integration/v2/schemas/start.schema.json +5 -2
- package/contracts/review-integration/v2/schemas/status.schema.json +4 -2
- package/contracts/review-provider-contract-mirror/provider-contract.lock.json +30 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/bundle/README.md +12 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/bundle/manifest.json +65 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/bundle/schemas/lens.schema.json +16 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/bundle/schemas/refuter.schema.json +1 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/bundle/schemas/targeted-validator.schema.json +1 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/bundle/vectors/lens.json +1 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/bundle/vectors/refuter.json +1 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/bundle/vectors/targeted-validator.json +1 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/generated/provider-capabilities.baseline.json +15 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/generated/provider-roles.baseline.json +42 -0
- package/docs/native-authority-architecture.md +14 -16
- package/docs/review-integration.md +27 -353
- package/extensions/ask-user-choice.ts +151 -0
- package/extensions/gentle-ai.ts +2710 -3211
- package/extensions/quiet-tools.ts +515 -32
- package/extensions/sdd-init.ts +21 -12
- package/extensions/skill-registry.ts +10 -2
- package/extensions/startup-banner.ts +10 -4
- package/lib/gentle-ai-binary.ts +173 -2
- package/lib/gentle-ai-renderer.ts +70 -0
- package/lib/model-routing-authority.ts +133 -0
- package/lib/native-review-cli.ts +676 -781
- package/lib/opaque-pi-reviewer-adapter.ts +284 -0
- package/lib/provider-contract-bundle.ts +704 -0
- package/lib/review-candidate-view.ts +836 -118
- package/lib/review-compact-contract.ts +59 -248
- package/lib/review-host-relay.ts +578 -0
- package/lib/review-integration-v2.ts +1254 -221
- package/lib/review-last-event-controller.ts +35 -0
- package/lib/review-relay-contract.ts +16 -0
- package/lib/sdd-preflight.ts +230 -67
- package/lib/sdd-status.ts +66 -111
- package/lib/terminal-theme.ts +1 -1
- package/package.json +83 -79
- package/runtime/gentle-ai-binary.mjs +174 -3
- package/runtime/native-review-cli.mjs +610 -715
- package/runtime/review-integration-v2.mjs +1223 -190
- package/runtime/review-relay-contract.mjs +17 -0
- package/scripts/{build-git-commit-transaction-runner.mjs → build-runtime-modules.mjs} +5 -5
- package/scripts/check-provider-contract.mjs +138 -0
- package/scripts/gentle-ai-installer.mjs +85 -22
- package/scripts/maintainer/provider-relay-matrix.mjs +403 -0
- package/scripts/mirror-provider-contract.mjs +143 -0
- package/scripts/test-packed-runner.mjs +20 -9
- package/scripts/verify-package-files.mjs +119 -43
- package/skills/_shared/review-ledger-contract.md +11 -19
- 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 +94 -168
- package/skills/judgment-day/SKILL.md +9 -5
- package/skills/judgment-day/references/prompts-and-formats.md +2 -0
- package/skills/rdd-defect-workflow/SKILL.md +54 -0
- 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/background-subagents.test.ts +771 -0
- package/tests/codegraph-tools.test.ts +3 -3
- package/tests/crosslane/cross-lane.mjs +16 -0
- package/tests/delegated-key-learnings-contract.test.ts +240 -0
- package/tests/devbinary/native-review-parity.devtest.ts +167 -250
- package/tests/devbinary/pi-host-relay.devtest.ts +867 -0
- package/tests/fixtures/devbinary/capabilities-v2.1.derived.json +331 -0
- package/tests/fixtures/devbinary/capabilities-v2.2.captured.json +340 -0
- package/tests/fixtures/devbinary/consent-v3.captured.json +37 -0
- package/tests/fixtures/devbinary/failure-v2-capture-evidence.captured.json +16 -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/result-artifact-v2-path.captured.json +12 -0
- package/tests/fixtures/devbinary/result-artifact-v2.captured.json +12 -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-consent-declined.captured.json +19 -0
- package/tests/fixtures/devbinary/start-v3-consent-granted.captured.json +109 -0
- package/tests/fixtures/devbinary/start-v3-zero-lens-closed.captured.json +21 -0
- package/tests/fixtures/devbinary/status-v5-capture-result-submission.captured.json +184 -0
- package/tests/fixtures/devbinary/status-v5-repository-context.captured.json +138 -0
- package/tests/fixtures/devbinary/status-v5.captured.json +88 -0
- package/tests/fixtures/native-review-cli/v2.5.0-rc.1/PROVENANCE.txt +15 -0
- package/tests/fixtures/provider-contract-bundle/v1.1.0/README.md +12 -0
- package/tests/fixtures/provider-contract-bundle/v1.1.0/manifest.json +65 -0
- package/tests/fixtures/provider-contract-bundle/v1.1.0/schemas/lens.schema.json +16 -0
- package/tests/fixtures/provider-contract-bundle/v1.1.0/schemas/refuter.schema.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.1.0/schemas/targeted-validator.schema.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.1.0/vectors/lens.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.1.0/vectors/refuter.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.1.0/vectors/targeted-validator.json +1 -0
- package/tests/gentle-ai-binary.test.ts +83 -13
- package/tests/gentle-ai-dev-binary-surfacing.test.ts +195 -0
- package/tests/gentle-ai-dev-binary.test.ts +159 -0
- package/tests/gentle-ai-installer.test.ts +138 -48
- package/tests/gentle-ai.test.ts +820 -131
- package/tests/gentle-theme.test.ts +133 -0
- package/tests/maintainer/provider-relay.maintest.ts +601 -0
- package/tests/model-routing-authority.test.ts +257 -0
- package/tests/native-review-capability-contract.test.ts +75 -2
- package/tests/native-review-cli.test.ts +588 -908
- package/tests/native-review-consent.test.ts +283 -38
- package/tests/native-review-parity-runtime.test.ts +108 -350
- package/tests/native-review-parity.test.ts +684 -732
- package/tests/native-sdd-attempt-authority.test.ts +235 -0
- package/tests/opaque-pi-reviewer-adapter.test.ts +266 -0
- package/tests/orchestrator-budget.test.ts +158 -89
- package/tests/orchestrator-rdd-ownership.test.ts +103 -0
- package/tests/package-manifest.test.ts +128 -100
- package/tests/provider-contract-bundle.test.ts +385 -0
- package/tests/provider-contract-mirror.test.ts +206 -0
- package/tests/provider-defect-handoff.test.ts +252 -0
- package/tests/quiet-tool-rendering.test.ts +1055 -28
- package/tests/review-actor-tool-deny.test.ts +12 -13
- package/tests/review-authority-recovery-docs.test.ts +1 -2
- package/tests/review-candidate-view.test.ts +898 -12
- package/tests/review-compact-contract.test.ts +29 -122
- package/tests/review-controller-lock-status.test.ts +2 -2
- package/tests/review-controller-native-recovery.test.ts +366 -857
- package/tests/review-controller-native-routing.test.ts +1253 -4199
- package/tests/review-controller-retired-ops.test.ts +1 -1
- package/tests/review-controller-workspace-root.test.ts +236 -70
- package/tests/review-controller.test.ts +26 -816
- package/tests/review-corrected-finalize-binding.test.ts +134 -0
- package/tests/review-dispatch-hydration-gap.test.ts +145 -0
- 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 +352 -0
- package/tests/review-host-relay.test.ts +754 -0
- package/tests/review-integration-v2-forward.test.ts +654 -0
- package/tests/review-integration-v2.test.ts +317 -129
- package/tests/review-last-event-closure.test.ts +408 -0
- package/tests/review-ledger-contract.test.ts +106 -60
- package/tests/review-recovered-lineage-routing.test.ts +199 -0
- package/tests/review-relay-transport-agent.test.ts +322 -0
- package/tests/review-snapshot.test.ts +3 -2
- package/tests/runtime-harness.mjs +573 -180
- package/tests/sdd-agent-tools.test.ts +53 -32
- package/tests/sdd-preflight.test.ts +81 -15
- package/tests/sdd-status.test.ts +109 -110
- package/tests/skill-collision-prefixes.test.ts +6 -8
- package/tests/skill-registry.test.ts +50 -1
- package/tests/verify-package-files.test.ts +62 -0
- 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/assets/agents/review-refuter.md +0 -40
- package/assets/agents/review-validator.md +0 -23
- package/lib/git-commit-transaction.ts +0 -801
- package/lib/native-review-remediation.ts +0 -49
- package/lib/review-compact.ts +0 -947
- package/lib/review-refuter-adapter.ts +0 -129
- package/lib/review-runtime-contract.ts +0 -68
- package/prompts/gcl.md +0 -54
- package/prompts/gis.md +0 -25
- package/prompts/gpr.md +0 -41
- package/prompts/gwr.md +0 -31
- package/runtime/git-commit-transaction.mjs +0 -802
- package/scripts/run-git-commit-transaction.mjs +0 -35
- package/tests/fixtures/native-review-cli/v2.1.2/bind-sdd.json +0 -25
- package/tests/fixtures/native-review-cli/v2.1.2/finalize.json +0 -8
- package/tests/fixtures/native-review-cli/v2.1.2/sdd-status-engram.json +0 -139
- package/tests/fixtures/native-review-cli/v2.1.2/sdd-status.json +0 -200
- package/tests/fixtures/native-review-cli/v2.1.2/start.json +0 -12
- package/tests/fixtures/native-review-cli/v2.1.2/validate-allow.json +0 -24
- package/tests/fixtures/native-review-cli/v2.1.2/validate-deny-empty-context.json +0 -20
- package/tests/fixtures/native-review-cli/v2.1.2/validate-deny.json +0 -28
- 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 -302
- package/tests/review-compact.test.ts +0 -243
- package/tests/review-refuter-adapter.test.ts +0 -89
|
@@ -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
|
+
});
|
|
@@ -80,8 +80,81 @@ test("2.2.2 repeats 2.2.1 because the lane Pi speaks still did not move", () =>
|
|
|
80
80
|
assert.deepEqual(contract, NATIVE_CLI_CONTRACTS["2.2.1"] as Record<string, boolean>);
|
|
81
81
|
});
|
|
82
82
|
|
|
83
|
+
test("2.2.3 repeats 2.2.2 because Pi's consumed capability columns did not move", () => {
|
|
84
|
+
// Ground-truthed against the released v2.2.3 binary: the v2 lane reports
|
|
85
|
+
// protocol 2.0, the same eight negotiated operations, and the same START
|
|
86
|
+
// fields represented by this table.
|
|
87
|
+
const contract = NATIVE_CLI_CONTRACTS["2.2.3"] as Record<string, boolean>;
|
|
88
|
+
assert.equal(contract.mode, true);
|
|
89
|
+
assert.equal(contract.delivery, true);
|
|
90
|
+
assert.equal(contract.riskEvidence, false);
|
|
91
|
+
assert.equal(contract.hint, false);
|
|
92
|
+
assert.deepEqual(contract, NATIVE_CLI_CONTRACTS["2.2.2"] as Record<string, boolean>);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("2.4.0 repeats 2.2.3 because the START envelope Pi consumes still omits risk_evidence and hint", () => {
|
|
96
|
+
// Ground-truthed against the published v2.4.0 linux_amd64 release binary
|
|
97
|
+
// (sha256:0be4467... , captured 2026-08-17), not against a main build:
|
|
98
|
+
//
|
|
99
|
+
// mode `gentle-ai review mode status --json` still answers
|
|
100
|
+
// `gentle-ai.rdd-mode-status/v1`. The RESOLVED value moved --
|
|
101
|
+
// an unconfigured install now reports effective "off" with
|
|
102
|
+
// source "default" instead of "on" -- but that is a decision
|
|
103
|
+
// the envelope carries, not a capability it lost.
|
|
104
|
+
// delivery `review validate --gate pre-commit` still answers
|
|
105
|
+
// `gentle-ai.review-gate-result/v1` with
|
|
106
|
+
// `delivery: "disabled/unmanaged"` at exit 0. Under opt-in RDD
|
|
107
|
+
// this is now the DEFAULT delivery answer for an install that
|
|
108
|
+
// never enabled review, so the column matters more, not less.
|
|
109
|
+
//
|
|
110
|
+
// riskEvidence and hint stay dark for exactly the reason the 2.2.0 comment
|
|
111
|
+
// gives, re-proven rather than inherited: the negotiated
|
|
112
|
+
// `gentle-ai.review-integration.start/v3` envelope captured from this
|
|
113
|
+
// binary carries `risk_reasons: [{ code: "non_executable_only" }]` and no
|
|
114
|
+
// `risk_evidence` and no `hint`. `risk_evidence` does appear in v2.4.0, but
|
|
115
|
+
// only on the `gentle-ai.review-integration.consent/v3` blocking envelope,
|
|
116
|
+
// which is a different envelope on a different branch of START and is not
|
|
117
|
+
// what these columns describe.
|
|
118
|
+
const contract = NATIVE_CLI_CONTRACTS["2.4.0"] as Record<string, boolean>;
|
|
119
|
+
assert.equal(contract.mode, true);
|
|
120
|
+
assert.equal(contract.delivery, true);
|
|
121
|
+
assert.equal(contract.riskEvidence, false);
|
|
122
|
+
assert.equal(contract.hint, false);
|
|
123
|
+
assert.deepEqual(contract, NATIVE_CLI_CONTRACTS["2.2.3"] as Record<string, boolean>);
|
|
124
|
+
});
|
|
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
|
+
|
|
83
153
|
test("no shipped version key was added beyond the pin bump", () => {
|
|
84
154
|
// Rows are promises to consumers, so a new key only ever appears in a
|
|
85
|
-
// dedicated commit alongside a pin bump, never as a side effect.
|
|
86
|
-
|
|
155
|
+
// dedicated commit alongside a pin bump, never as a side effect. v2.2.4 and
|
|
156
|
+
// v2.3.0 shipped upstream while Pi stayed on 2.2.3 and were never pinned,
|
|
157
|
+
// so they get no row: a row asserts ground truth measured against a binary
|
|
158
|
+
// Pi actually ran, and the table only has to be ascending, not gapless.
|
|
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"]);
|
|
87
160
|
});
|