gentle-pi 2.3.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/README.md +30 -4
  2. package/assets/agents/sdd-explore.md +1 -0
  3. package/assets/orchestrator-delegation.md +2 -1
  4. package/assets/orchestrator.md +6 -10
  5. package/contracts/review-provider-contract-mirror/provider-contract.lock.json +8 -7
  6. package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/README.md +10 -0
  7. package/contracts/review-provider-contract-mirror/v1.2.0/bundle/manifest.json +74 -0
  8. package/contracts/review-provider-contract-mirror/v1.2.0/bundle/orchestration/pi.md +53 -0
  9. package/contracts/review-provider-contract-mirror/v1.2.0/bundle/schemas/targeted-validator.schema.json +1 -0
  10. package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/generated/provider-capabilities.baseline.json +9 -2
  11. package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/generated/provider-roles.baseline.json +2 -2
  12. package/docs/review-integration.md +1 -1
  13. package/extensions/gentle-ai.ts +530 -71
  14. package/lib/agent-home.ts +8 -0
  15. package/lib/native-review-cli.ts +15 -0
  16. package/lib/provider-contract-bundle.ts +88 -6
  17. package/lib/review-host-relay.ts +146 -60
  18. package/lib/review-integration-v2.ts +34 -5
  19. package/lib/sdd-preflight.ts +2 -2
  20. package/package.json +1 -1
  21. package/runtime/native-review-cli.mjs +15 -0
  22. package/runtime/review-integration-v2.mjs +34 -5
  23. package/scripts/gentle-ai-installer.mjs +10 -10
  24. package/scripts/verify-package-files.mjs +14 -13
  25. package/skills/_shared/review-ledger-contract.md +6 -0
  26. package/skills/issue-creation/SKILL.md +53 -93
  27. package/tests/fixtures/provider-contract-bundle/v1.2.0/README.md +22 -0
  28. package/{contracts/review-provider-contract-mirror/v1.1.0/bundle → tests/fixtures/provider-contract-bundle/v1.2.0}/manifest.json +11 -2
  29. package/tests/fixtures/provider-contract-bundle/v1.2.0/orchestration/pi.md +97 -0
  30. package/tests/fixtures/provider-contract-bundle/v1.2.0/schemas/lens.schema.json +16 -0
  31. package/tests/fixtures/provider-contract-bundle/v1.2.0/schemas/refuter.schema.json +1 -0
  32. package/tests/fixtures/provider-contract-bundle/v1.2.0/vectors/lens.json +1 -0
  33. package/tests/fixtures/provider-contract-bundle/v1.2.0/vectors/refuter.json +1 -0
  34. package/tests/fixtures/provider-contract-bundle/v1.2.0/vectors/targeted-validator.json +1 -0
  35. package/tests/gentle-ai-binary.test.ts +1 -1
  36. package/tests/gentle-ai-installer.test.ts +46 -46
  37. package/tests/gentle-ai.test.ts +3 -2
  38. package/tests/issue-creation-skill.test.ts +103 -0
  39. package/tests/native-review-capability-contract.test.ts +18 -1
  40. package/tests/orchestrator-budget.test.ts +2 -2
  41. package/tests/orchestrator-rdd-ownership.test.ts +1 -1
  42. package/tests/package-manifest.test.ts +123 -9
  43. package/tests/provider-contract-bundle.test.ts +76 -0
  44. package/tests/provider-contract-mirror.test.ts +19 -0
  45. package/tests/review-agent-end-preflight.test.ts +408 -0
  46. package/tests/review-contract-prompt.test.ts +139 -0
  47. package/tests/review-controller.test.ts +1 -1
  48. package/tests/review-host-relay-routing.test.ts +211 -9
  49. package/tests/review-host-relay.test.ts +195 -7
  50. package/tests/review-integration-v2-forward.test.ts +47 -0
  51. package/tests/review-ledger-contract.test.ts +1 -1
  52. package/tests/runtime-harness.mjs +1 -0
  53. package/tests/writer-edit-surface-scope.test.ts +153 -17
  54. /package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/schemas/lens.schema.json +0 -0
  55. /package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/schemas/refuter.schema.json +0 -0
  56. /package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/vectors/lens.json +0 -0
  57. /package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/vectors/refuter.json +0 -0
  58. /package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/vectors/targeted-validator.json +0 -0
  59. /package/{contracts/review-provider-contract-mirror/v1.1.0/bundle → tests/fixtures/provider-contract-bundle/v1.2.0}/schemas/targeted-validator.schema.json +0 -0
@@ -9,11 +9,12 @@ import {
9
9
  rmSync,
10
10
  writeFileSync,
11
11
  } from "node:fs";
12
- import { tmpdir } from "node:os";
12
+ import { homedir, tmpdir } from "node:os";
13
13
  import { dirname, join } from "node:path";
14
14
  import test from "node:test";
15
15
  import { fileURLToPath } from "node:url";
16
16
  import { applyModelConfig } from "../extensions/gentle-ai.ts";
17
+ import { resolveGentlePiAgentHome } from "../lib/agent-home.ts";
17
18
  import { installSddAssets } from "../lib/sdd-preflight.ts";
18
19
 
19
20
  const PACKAGE_ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
@@ -261,20 +262,20 @@ test("package manifest installs pi-pretty through a wrapper without bundling nat
261
262
  );
262
263
  });
263
264
 
264
- test("package verification binds the published Gentle AI v2.5.0 runtime pin", () => {
265
+ test("package verification binds the published Gentle AI v2.6.0 runtime pin", () => {
265
266
  const installer = readFileSync(join(PACKAGE_ROOT, "scripts", "gentle-ai-installer.mjs"), "utf8");
266
267
  const binary = readFileSync(join(PACKAGE_ROOT, "lib", "gentle-ai-binary.ts"), "utf8");
267
268
  const verifier = readFileSync(join(PACKAGE_ROOT, "scripts", "verify-package-files.mjs"), "utf8");
268
269
 
269
- assert.match(installer, /INSTALLER_VERSION = "2\.5\.0"/);
270
+ assert.match(installer, /INSTALLER_VERSION = "2\.6\.0"/);
270
271
  assert.match(installer, /GENTLE_AI_WINDOWS_SOURCE_PACKAGE.*GENTLE_AI_WINDOWS_SOURCE_MODULE/);
271
- assert.match(installer, /GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM = "h1:0nvTLJFAf9ruBDwTvi\+aDrWt\/dt\/GKvUP2cFVB6c2DI="/);
272
+ assert.match(installer, /GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM = "h1:scGoZYnPHh4oCVqlcfQAOSpm22Ii5lmB0kBbiBKvNZk="/);
272
273
  assert.match(installer, /GOTOOLCHAIN: "local"/);
273
274
  assert.match(installer, /GOSUMDB: "sum\.golang\.org"/);
274
275
  assert.match(binary, /GENTLE_AI_VERSION = INSTALLER_VERSION/);
275
276
  assert.match(binary, /GO_SUMDB_SOURCE_BUILD/);
276
277
  assert.match(binary, /GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM/);
277
- assert.match(verifier, /v2\.5\.0/);
278
+ assert.match(verifier, /v2\.6\.0/);
278
279
  });
279
280
 
280
281
 
@@ -895,6 +896,22 @@ test("jd-fix-agent packaged allowlist includes write tools", () => {
895
896
  }
896
897
  });
897
898
 
899
+ test("sdd-explore packages its CodeGraph-enabled exploration allowlist", () => {
900
+ const agentPath = join(PACKAGE_ROOT, "assets", "agents", "sdd-explore.md");
901
+ const { name, tools } = readAgentDefinition(agentPath);
902
+
903
+ assert.equal(name, "sdd-explore");
904
+ assert.deepEqual(tools, [
905
+ "read",
906
+ "grep",
907
+ "find",
908
+ "codegraph",
909
+ "edit",
910
+ "write",
911
+ "mem_save",
912
+ ]);
913
+ });
914
+
898
915
  test("gentle-ai-worker packages the exact scoped writer contract", () => {
899
916
  const agentsDir = join(PACKAGE_ROOT, "assets", "agents");
900
917
  const agentPath = join(agentsDir, "gentle-ai-worker.md");
@@ -1055,6 +1072,103 @@ test("installSddAssets installs gentle-ai-worker with a loader-compatible scoped
1055
1072
  );
1056
1073
  });
1057
1074
 
1075
+ test("agent home resolver centralizes Gentle and Pi agent-dir precedence", () => {
1076
+ const explicitGentleHome = mkdtempSync(join(tmpdir(), "gentle-pi-resolver-explicit-"));
1077
+ const piAgentDir = mkdtempSync(join(tmpdir(), "gentle-pi-resolver-pi-dir-"));
1078
+
1079
+ try {
1080
+ assert.equal(
1081
+ resolveGentlePiAgentHome({
1082
+ GENTLE_PI_AGENT_HOME: explicitGentleHome,
1083
+ PI_CODING_AGENT_DIR: piAgentDir,
1084
+ }),
1085
+ explicitGentleHome,
1086
+ );
1087
+ assert.equal(resolveGentlePiAgentHome({ PI_CODING_AGENT_DIR: piAgentDir }), piAgentDir);
1088
+ assert.equal(resolveGentlePiAgentHome({}), join(homedir(), ".pi", "agent"));
1089
+ assert.equal(
1090
+ resolveGentlePiAgentHome({ GENTLE_PI_AGENT_HOME: "", PI_CODING_AGENT_DIR: piAgentDir }),
1091
+ piAgentDir,
1092
+ "an empty explicit override falls through like Pi Subagents does",
1093
+ );
1094
+ assert.equal(
1095
+ resolveGentlePiAgentHome({ PI_CODING_AGENT_DIR: "" }),
1096
+ join(homedir(), ".pi", "agent"),
1097
+ "an empty PI_CODING_AGENT_DIR falls through like Pi Subagents does",
1098
+ );
1099
+ } finally {
1100
+ rmSync(explicitGentleHome, { recursive: true, force: true });
1101
+ rmSync(piAgentDir, { recursive: true, force: true });
1102
+ }
1103
+ });
1104
+
1105
+ test("asset installation uses PI_CODING_AGENT_DIR as the Pi agent home when no explicit Gentle override is set", () => {
1106
+ const previousAgentHome = process.env.GENTLE_PI_AGENT_HOME;
1107
+ const previousPiAgentDir = process.env.PI_CODING_AGENT_DIR;
1108
+ const temporaryPiAgentDir = mkdtempSync(join(tmpdir(), "gentle-pi-agent-dir-"));
1109
+ const explicitGentleHome = mkdtempSync(join(tmpdir(), "gentle-pi-explicit-home-"));
1110
+
1111
+ try {
1112
+ delete process.env.GENTLE_PI_AGENT_HOME;
1113
+ process.env.PI_CODING_AGENT_DIR = temporaryPiAgentDir;
1114
+
1115
+ installSddAssets(PACKAGE_ROOT, true);
1116
+
1117
+ const installedPath = join(temporaryPiAgentDir, "agents", "gentle-ai-explore.md");
1118
+ assert.ok(existsSync(installedPath), "managed agents must install where Pi Subagents reads global definitions");
1119
+ assert.deepEqual(readAgentDefinition(installedPath).tools, MANAGED_EXEMPLAR_TOOLS);
1120
+ assert.ok(
1121
+ !existsSync(join(explicitGentleHome, "agents", "gentle-ai-explore.md")),
1122
+ "the explicit override fixture must still be untouched before it is selected",
1123
+ );
1124
+
1125
+ process.env.GENTLE_PI_AGENT_HOME = explicitGentleHome;
1126
+ installSddAssets(PACKAGE_ROOT, true);
1127
+ assert.ok(
1128
+ existsSync(join(explicitGentleHome, "agents", "gentle-ai-explore.md")),
1129
+ "GENTLE_PI_AGENT_HOME remains the explicit test/operator override",
1130
+ );
1131
+ } finally {
1132
+ if (previousAgentHome === undefined) delete process.env.GENTLE_PI_AGENT_HOME;
1133
+ else process.env.GENTLE_PI_AGENT_HOME = previousAgentHome;
1134
+ if (previousPiAgentDir === undefined) delete process.env.PI_CODING_AGENT_DIR;
1135
+ else process.env.PI_CODING_AGENT_DIR = previousPiAgentDir;
1136
+ rmSync(temporaryPiAgentDir, { recursive: true, force: true });
1137
+ rmSync(explicitGentleHome, { recursive: true, force: true });
1138
+ }
1139
+ });
1140
+
1141
+ test("global model routing uses PI_CODING_AGENT_DIR for package-installed agents", () => {
1142
+ const previousAgentHome = process.env.GENTLE_PI_AGENT_HOME;
1143
+ const previousPiAgentDir = process.env.PI_CODING_AGENT_DIR;
1144
+ const temporaryPiAgentDir = mkdtempSync(join(tmpdir(), "gentle-pi-model-agent-dir-"));
1145
+ const temporaryProject = mkdtempSync(join(tmpdir(), "gentle-pi-model-project-"));
1146
+
1147
+ try {
1148
+ delete process.env.GENTLE_PI_AGENT_HOME;
1149
+ process.env.PI_CODING_AGENT_DIR = temporaryPiAgentDir;
1150
+ installSddAssets(PACKAGE_ROOT, true);
1151
+
1152
+ const result = applyModelConfig(temporaryProject, {
1153
+ "gentle-ai-explore": { model: "provider/model", thinking: "high" },
1154
+ });
1155
+
1156
+ assert.equal(result.updated, 2);
1157
+ const config = JSON.parse(readFileSync(join(temporaryPiAgentDir, "subagents.json"), "utf8"));
1158
+ assert.deepEqual(config.model_profiles["gentle-ai-explore"], {
1159
+ model: "provider/model",
1160
+ effort: "high",
1161
+ });
1162
+ } finally {
1163
+ if (previousAgentHome === undefined) delete process.env.GENTLE_PI_AGENT_HOME;
1164
+ else process.env.GENTLE_PI_AGENT_HOME = previousAgentHome;
1165
+ if (previousPiAgentDir === undefined) delete process.env.PI_CODING_AGENT_DIR;
1166
+ else process.env.PI_CODING_AGENT_DIR = previousPiAgentDir;
1167
+ rmSync(temporaryPiAgentDir, { recursive: true, force: true });
1168
+ rmSync(temporaryProject, { recursive: true, force: true });
1169
+ }
1170
+ });
1171
+
1058
1172
  test("normal and forced installation copy generic agents with complete role contracts", () => {
1059
1173
  const previousAgentHome = process.env.GENTLE_PI_AGENT_HOME;
1060
1174
  const expectedTools = {
@@ -1146,8 +1260,8 @@ test("orchestrator routes generic roles without static RDD lens routing", () =>
1146
1260
  }
1147
1261
 
1148
1262
  const core = readFileSync(join(PACKAGE_ROOT, "assets", "orchestrator.md"), "utf8");
1149
- assert.match(core, /Gentle AI dynamically supplies runtime-specific RDD instructions/);
1150
- assert.match(core, /this package does not invent or fall back/);
1263
+ assert.match(core, /injects the mirrored provider-bundle review execution contract/);
1264
+ assert.match(core, /this package invents no lifecycle instructions/);
1151
1265
  });
1152
1266
 
1153
1267
  test("pi-pretty wrapper uses real package path resolution for pnpm symlink installs", () => {
@@ -1163,9 +1277,9 @@ test("pi-pretty wrapper uses real package path resolution for pnpm symlink insta
1163
1277
  assert.match(wrapper, /quietToolsEnabled/);
1164
1278
  });
1165
1279
 
1166
- test("v2.3.0 release package and runtime stop before publication", () => {
1280
+ test("v2.4.0 release package and runtime stop before publication", () => {
1167
1281
  const packageJson = readPackageJson();
1168
- assert.equal(packageJson.version, "2.3.0", "the release manifest must remain explicitly pinned to v2.3.0");
1282
+ assert.equal(packageJson.version, "2.4.0", "the release manifest must remain explicitly pinned to v2.4.0");
1169
1283
  assert.equal(
1170
1284
  packageJson.scripts?.test,
1171
1285
  "node --experimental-strip-types --test tests/*.test.ts && pnpm run check:provider-contract && pnpm run test:harness",
@@ -35,6 +35,16 @@ function fixtureEntries(): Map<string, Buffer> {
35
35
  return new Map(FIXTURE_PATHS.map((path) => [path, readFileSync(join(FIXTURE_ROOT, path))]));
36
36
  }
37
37
 
38
+ // Fixture provenance: the real gentle-ai provider contract bundle 1.2.0
39
+ // release archive (contract semver 1.2.0), extracted. Adds `orchestration`
40
+ // to the manifest: gentle-pi#560 / gentle-ai#4056, #4057.
41
+ const FIXTURE_1_2_0_ROOT = join(import.meta.dirname, "fixtures", "provider-contract-bundle", "v1.2.0");
42
+ const FIXTURE_1_2_0_PATHS = [...FIXTURE_PATHS, "orchestration/pi.md"] as const;
43
+
44
+ function fixture120Entries(): Map<string, Buffer> {
45
+ return new Map(FIXTURE_1_2_0_PATHS.map((path) => [path, readFileSync(join(FIXTURE_1_2_0_ROOT, path))]));
46
+ }
47
+
38
48
  function sha256Hex(bytes: Buffer): string {
39
49
  return createHash("sha256").update(bytes).digest("hex");
40
50
  }
@@ -177,11 +187,25 @@ test("generated baselines project roles, capabilities, and runtime registration
177
187
  assert.equal(capabilities.transport_capability, PROVIDER_TRANSPORT_CAPABILITY);
178
188
  assert.deepEqual(capabilities.mandatory_capabilities, [PROVIDER_TRANSPORT_CAPABILITY]);
179
189
  assert.equal(capabilities.pi_registered, true);
190
+ assert.deepEqual(capabilities.orchestration, []);
180
191
  // Byte determinism: regenerating from the same bundle is identical.
181
192
  const again = generateProviderContractBaselines(verifyProviderContractBundleTree(FIXTURE_ROOT));
182
193
  assert.deepEqual([...again.entries()], [...baselines.entries()]);
183
194
  });
184
195
 
196
+ test("generated 1.2.0 baselines record the mirrored orchestration entry", () => {
197
+ const bundle = verifyProviderContractBundleTree(FIXTURE_1_2_0_ROOT);
198
+ const baselines = generateProviderContractBaselines(bundle);
199
+ const capabilities = JSON.parse(baselines.get("provider-capabilities.baseline.json") as string) as { orchestration: { runtime: string; path: string; sha256: string }[] };
200
+ assert.deepEqual(capabilities.orchestration, [
201
+ {
202
+ runtime: PI_RUNTIME_IDENTITY,
203
+ path: "orchestration/pi.md",
204
+ sha256: bundle.entrySha256.get("orchestration/pi.md"),
205
+ },
206
+ ]);
207
+ });
208
+
185
209
  // --- inventory and hash rejections ---------------------------------------------
186
210
 
187
211
  test("rejects a ninth file in the inventory", () => {
@@ -315,6 +339,58 @@ test("rejects a schema whose $id does not match the manifest result schema id",
315
339
  );
316
340
  });
317
341
 
342
+ // --- orchestration (contract 1.2.0) -------------------------------------------------
343
+
344
+ test("verifies the 1.2.0 fixture tree and exposes the mirrored pi orchestration text", () => {
345
+ const bundle = verifyProviderContractBundleTree(FIXTURE_1_2_0_ROOT);
346
+ assert.equal(bundle.contractSemver, "1.2.0");
347
+ assert.deepEqual([bundle.major, bundle.minor, bundle.patch], [1, 2, 0]);
348
+ assert.equal(bundle.entries.size, 9);
349
+ assert.deepEqual([...bundle.orchestration.keys()], [PI_RUNTIME_IDENTITY]);
350
+ const piText = bundle.orchestration.get(PI_RUNTIME_IDENTITY);
351
+ assert.equal(piText, readFileSync(join(FIXTURE_1_2_0_ROOT, "orchestration/pi.md"), "utf8"));
352
+ assert.equal(bundle.entrySha256.get("orchestration/pi.md"), sha256Hex(readFileSync(join(FIXTURE_1_2_0_ROOT, "orchestration/pi.md"))));
353
+ });
354
+
355
+ test("rejects a 1.2.0 manifest without the required orchestration array", () => {
356
+ assertRejects(
357
+ withManifest(fixture120Entries(), (manifest) => {
358
+ delete manifest.orchestration;
359
+ }),
360
+ "manifest.orchestration is required from contract 1.2.0",
361
+ );
362
+ });
363
+
364
+ test("rejects a 1.2.0 orchestration entry whose runtime is not registered in manifest.runtimes", () => {
365
+ assertRejects(
366
+ withManifest(fixture120Entries(), (manifest) => {
367
+ const orchestration = manifest.orchestration as { runtime: string; file: { path: string } }[];
368
+ orchestration[0]!.runtime = "aider";
369
+ orchestration[0]!.file.path = "orchestration/aider.md";
370
+ }),
371
+ 'manifest.orchestration[0].runtime "aider" is not registered in manifest.runtimes',
372
+ );
373
+ });
374
+
375
+ test("rejects a 1.2.0 orchestration entry whose file bytes do not match its manifest SHA-256", () => {
376
+ const entries = fixture120Entries();
377
+ const tampered = Buffer.from(entries.get("orchestration/pi.md") as Buffer);
378
+ tampered[tampered.length - 2] = 0x20;
379
+ entries.set("orchestration/pi.md", tampered);
380
+ assertRejects(entries, "does not match its manifest SHA-256");
381
+ });
382
+
383
+ test("rejects an orchestration array on a 1.1.0 manifest", () => {
384
+ assertRejects(
385
+ withManifest(fixtureEntries(), (manifest) => {
386
+ manifest.orchestration = [
387
+ { runtime: PI_RUNTIME_IDENTITY, file: { path: "orchestration/pi.md", sha256: "0".repeat(64) } },
388
+ ];
389
+ }),
390
+ "manifest.orchestration is not part of contract 1.1.0",
391
+ );
392
+ });
393
+
318
394
  // --- archive safety ------------------------------------------------------------------
319
395
 
320
396
  test("rejects a tar entry that escapes the bundle root", () => {
@@ -27,6 +27,10 @@ const FIXTURE_PATHS = [
27
27
  "vectors/targeted-validator.json",
28
28
  ] as const;
29
29
 
30
+ // Fixture provenance: the real gentle-ai provider contract bundle 1.2.0
31
+ // release archive, extracted. gentle-pi#560 / gentle-ai#4056, #4057.
32
+ const FIXTURE_1_2_0_ROOT = join(import.meta.dirname, "fixtures", "provider-contract-bundle", "v1.2.0");
33
+
30
34
  interface LockRecord {
31
35
  schema: string;
32
36
  acquisition: string;
@@ -105,6 +109,21 @@ test("mirrors a verified local bundle tree and the offline drift check passes",
105
109
  });
106
110
  });
107
111
 
112
+ test("mirrors a verified local 1.2.0 bundle tree, including its orchestration entry, and the offline drift check passes", () => {
113
+ withTemporaryRoot((packageRoot) => {
114
+ mirrorProviderContractBundle(FIXTURE_1_2_0_ROOT, packageRoot);
115
+ const lockPath = join(packageRoot, PROVIDER_CONTRACT_MIRROR_ROOT, PROVIDER_CONTRACT_LOCK_FILE);
116
+ const lock = JSON.parse(readFileSync(lockPath, "utf8")) as LockRecord;
117
+ assert.equal(lock.contract_semver, "1.2.0");
118
+ assert.equal(Object.keys(lock.entries).length, 9);
119
+ assert.ok("orchestration/pi.md" in lock.entries, "the mirrored lock must record orchestration/pi.md");
120
+ const mirrored = join(packageRoot, PROVIDER_CONTRACT_MIRROR_ROOT, "v1.2.0", "bundle", "orchestration", "pi.md");
121
+ assert.ok(existsSync(mirrored), "orchestration/pi.md must be mirrored");
122
+ assert.deepEqual(readFileSync(mirrored), readFileSync(join(FIXTURE_1_2_0_ROOT, "orchestration", "pi.md")));
123
+ assert.deepEqual(checkProviderContractMirror(packageRoot), []);
124
+ });
125
+ });
126
+
108
127
  test("mirrors a verified local archive and records the archive digest in the lock", () => {
109
128
  withTemporaryRoot((packageRoot) => {
110
129
  const archivePath = join(packageRoot, "gentle-ai-review-provider-contract-1.1.0.tar.gz");