gentle-pi 2.2.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 (160) hide show
  1. package/README.md +76 -79
  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-explore.md +1 -0
  8. package/assets/agents/sdd-proposal.md +2 -0
  9. package/assets/agents/sdd-research.md +54 -0
  10. package/assets/agents/sdd-status.md +4 -4
  11. package/assets/agents/sdd-tasks.md +2 -3
  12. package/assets/agents/sdd-verify.md +21 -1
  13. package/assets/chains/sdd-full.chain.md +1 -1
  14. package/assets/chains/sdd-verify.chain.md +1 -1
  15. package/assets/orchestrator-delegation.md +56 -249
  16. package/assets/orchestrator-memory.md +2 -0
  17. package/assets/orchestrator.md +20 -40
  18. package/assets/sdd-orchestrator-workflow.md +30 -37
  19. package/assets/support/sdd-status-contract.md +7 -7
  20. package/contracts/review-integration/v1/schemas/transition-execution.schema.json +42 -0
  21. package/contracts/review-integration/v2/schemas/last-event-closure.schema.json +66 -0
  22. package/contracts/review-integration/v2/schemas/opencode-provider-role.schema.json +14 -0
  23. package/contracts/review-provider-contract-mirror/provider-contract.lock.json +8 -7
  24. package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/README.md +10 -0
  25. package/contracts/review-provider-contract-mirror/v1.2.0/bundle/manifest.json +74 -0
  26. package/contracts/review-provider-contract-mirror/v1.2.0/bundle/orchestration/pi.md +53 -0
  27. package/contracts/review-provider-contract-mirror/v1.2.0/bundle/schemas/targeted-validator.schema.json +1 -0
  28. package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/generated/provider-capabilities.baseline.json +9 -2
  29. package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/generated/provider-roles.baseline.json +2 -2
  30. package/docs/native-authority-architecture.md +9 -11
  31. package/docs/review-integration.md +27 -373
  32. package/extensions/ask-user-choice.ts +151 -0
  33. package/extensions/gentle-ai.ts +2349 -3785
  34. package/extensions/quiet-tools.ts +515 -32
  35. package/extensions/sdd-init.ts +4 -8
  36. package/lib/agent-home.ts +8 -0
  37. package/lib/gentle-ai-renderer.ts +70 -0
  38. package/lib/model-routing-authority.ts +133 -0
  39. package/lib/native-review-cli.ts +387 -940
  40. package/lib/opaque-pi-reviewer-adapter.ts +284 -0
  41. package/lib/provider-contract-bundle.ts +88 -6
  42. package/lib/review-candidate-view.ts +341 -132
  43. package/lib/review-host-relay.ts +324 -96
  44. package/lib/review-integration-v2.ts +868 -307
  45. package/lib/review-last-event-controller.ts +35 -0
  46. package/lib/sdd-preflight.ts +179 -68
  47. package/lib/sdd-status.ts +66 -111
  48. package/lib/terminal-theme.ts +1 -1
  49. package/package.json +83 -82
  50. package/runtime/gentle-ai-binary.mjs +1 -1
  51. package/runtime/native-review-cli.mjs +337 -890
  52. package/runtime/review-integration-v2.mjs +816 -255
  53. package/runtime/review-relay-contract.mjs +1 -1
  54. package/scripts/{build-git-commit-transaction-runner.mjs → build-runtime-modules.mjs} +4 -5
  55. package/scripts/gentle-ai-installer.mjs +75 -22
  56. package/scripts/maintainer/provider-relay-matrix.mjs +195 -11
  57. package/scripts/test-packed-runner.mjs +4 -7
  58. package/scripts/verify-package-files.mjs +23 -23
  59. package/skills/_shared/review-ledger-contract.md +14 -14
  60. package/skills/chained-pr/SKILL.md +3 -0
  61. package/skills/cognitive-doc-design/SKILL.md +1 -1
  62. package/skills/comment-writer/SKILL.md +1 -1
  63. package/skills/gentle-ai/SKILL.md +7 -74
  64. package/skills/issue-creation/SKILL.md +53 -93
  65. package/skills/judgment-day/SKILL.md +5 -7
  66. package/skills/rdd-defect-workflow/SKILL.md +3 -3
  67. package/skills/release/SKILL.md +3 -3
  68. package/skills/skill-registry/SKILL.md +1 -1
  69. package/skills/work-unit-commits/SKILL.md +3 -1
  70. package/tests/artifact-language.test.ts +24 -11
  71. package/tests/ask-user-choice.test.ts +264 -0
  72. package/tests/codegraph-tools.test.ts +3 -3
  73. package/tests/crosslane/cross-lane.mjs +15 -1168
  74. package/tests/delegated-key-learnings-contract.test.ts +8 -6
  75. package/tests/devbinary/native-review-parity.devtest.ts +167 -250
  76. package/tests/devbinary/pi-host-relay.devtest.ts +867 -0
  77. package/tests/fixtures/devbinary/last-event-capture-correction-plan.captured.json +10 -0
  78. package/tests/fixtures/devbinary/last-event-capture-refuter-approved.captured.json +20 -0
  79. package/tests/fixtures/devbinary/last-event-capture-refuter-correction-required.captured.json +26 -0
  80. package/tests/fixtures/devbinary/last-event-capture-result-approved.captured.json +8 -0
  81. package/tests/fixtures/devbinary/last-event-capture-result-correction-required.captured.json +26 -0
  82. package/tests/fixtures/devbinary/last-event-capture-validation-approved.captured.json +8 -0
  83. package/tests/fixtures/devbinary/last-event-closure.provenance.md +13 -0
  84. package/tests/fixtures/devbinary/review-acknowledged-v1.captured.json +9 -0
  85. package/tests/fixtures/devbinary/review-acknowledged.provenance.md +31 -0
  86. package/tests/fixtures/devbinary/start-v3-zero-lens-closed.captured.json +21 -0
  87. package/tests/fixtures/native-review-cli/v2.5.0-rc.1/PROVENANCE.txt +15 -0
  88. package/tests/fixtures/provider-contract-bundle/v1.2.0/README.md +22 -0
  89. package/{contracts/review-provider-contract-mirror/v1.1.0/bundle → tests/fixtures/provider-contract-bundle/v1.2.0}/manifest.json +11 -2
  90. package/tests/fixtures/provider-contract-bundle/v1.2.0/orchestration/pi.md +97 -0
  91. package/tests/fixtures/provider-contract-bundle/v1.2.0/schemas/lens.schema.json +16 -0
  92. package/tests/fixtures/provider-contract-bundle/v1.2.0/schemas/refuter.schema.json +1 -0
  93. package/tests/fixtures/provider-contract-bundle/v1.2.0/vectors/lens.json +1 -0
  94. package/tests/fixtures/provider-contract-bundle/v1.2.0/vectors/refuter.json +1 -0
  95. package/tests/fixtures/provider-contract-bundle/v1.2.0/vectors/targeted-validator.json +1 -0
  96. package/tests/gentle-ai-binary.test.ts +83 -13
  97. package/tests/gentle-ai-dev-binary.test.ts +51 -228
  98. package/tests/gentle-ai-installer.test.ts +137 -47
  99. package/tests/gentle-ai.test.ts +821 -131
  100. package/tests/gentle-theme.test.ts +133 -0
  101. package/tests/issue-creation-skill.test.ts +103 -0
  102. package/tests/maintainer/provider-relay.maintest.ts +340 -4
  103. package/tests/model-routing-authority.test.ts +257 -0
  104. package/tests/native-review-capability-contract.test.ts +45 -1
  105. package/tests/native-review-cli.test.ts +586 -962
  106. package/tests/native-review-consent.test.ts +150 -66
  107. package/tests/native-review-parity-runtime.test.ts +80 -359
  108. package/tests/native-review-parity.test.ts +674 -925
  109. package/tests/opaque-pi-reviewer-adapter.test.ts +266 -0
  110. package/tests/orchestrator-budget.test.ts +132 -88
  111. package/tests/orchestrator-rdd-ownership.test.ts +103 -0
  112. package/tests/package-manifest.test.ts +152 -36
  113. package/tests/provider-contract-bundle.test.ts +76 -0
  114. package/tests/provider-contract-mirror.test.ts +19 -0
  115. package/tests/provider-defect-handoff.test.ts +15 -118
  116. package/tests/quiet-tool-rendering.test.ts +1055 -28
  117. package/tests/review-agent-end-preflight.test.ts +408 -0
  118. package/tests/review-authority-recovery-docs.test.ts +1 -2
  119. package/tests/review-candidate-view.test.ts +409 -3
  120. package/tests/review-compact-contract.test.ts +4 -30
  121. package/tests/review-contract-prompt.test.ts +139 -0
  122. package/tests/review-controller-lock-status.test.ts +2 -2
  123. package/tests/review-controller-native-recovery.test.ts +363 -1450
  124. package/tests/review-controller-native-routing.test.ts +1251 -5642
  125. package/tests/review-controller-retired-ops.test.ts +1 -1
  126. package/tests/review-controller-workspace-root.test.ts +222 -71
  127. package/tests/review-controller.test.ts +26 -816
  128. package/tests/review-corrected-finalize-binding.test.ts +112 -153
  129. package/tests/review-dispatch-hydration-gap.test.ts +1 -53
  130. package/tests/review-gate.test.ts +0 -45
  131. package/tests/review-host-relay-restart-parity.test.ts +360 -0
  132. package/tests/review-host-relay-routing.test.ts +328 -91
  133. package/tests/review-host-relay.test.ts +436 -14
  134. package/tests/review-integration-v2-forward.test.ts +623 -553
  135. package/tests/review-integration-v2.test.ts +239 -165
  136. package/tests/review-last-event-closure.test.ts +408 -0
  137. package/tests/review-ledger-contract.test.ts +97 -35
  138. package/tests/review-recovered-lineage-routing.test.ts +0 -47
  139. package/tests/review-relay-transport-agent.test.ts +119 -46
  140. package/tests/review-snapshot.test.ts +3 -2
  141. package/tests/runtime-harness.mjs +362 -196
  142. package/tests/sdd-agent-tools.test.ts +36 -0
  143. package/tests/sdd-preflight.test.ts +81 -15
  144. package/tests/sdd-status.test.ts +109 -110
  145. package/tests/skill-collision-prefixes.test.ts +5 -8
  146. package/tests/writer-edit-surface-scope.test.ts +366 -0
  147. package/themes/Gentleman-Cute.json +94 -0
  148. package/themes/Gentleman-Sexy.json +92 -0
  149. package/lib/git-commit-transaction.ts +0 -861
  150. package/runtime/git-commit-transaction.mjs +0 -862
  151. package/scripts/run-git-commit-transaction.mjs +0 -35
  152. package/tests/fixtures/native-review-cli/v2.1.3/sdd-status-engram.json +0 -139
  153. package/tests/fixtures/native-review-cli/v2.1.3/sdd-status.json +0 -200
  154. package/tests/git-commit-transaction.test.ts +0 -530
  155. /package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/schemas/lens.schema.json +0 -0
  156. /package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/schemas/refuter.schema.json +0 -0
  157. /package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/vectors/lens.json +0 -0
  158. /package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/vectors/refuter.json +0 -0
  159. /package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/vectors/targeted-validator.json +0 -0
  160. /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
@@ -0,0 +1,103 @@
1
+ import assert from "node:assert/strict";
2
+ import { readFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import test from "node:test";
5
+
6
+ const ROOT = join(import.meta.dirname, "..");
7
+ const ASSETS = join(ROOT, "assets");
8
+ const BOUNDARY =
9
+ "This package injects the mirrored provider-bundle review execution contract into this session's system prompt at start; Gentle AI writes nothing into the Pi system prompt, and this package owns everything else here. Absent that mirrored contract, this package invents no lifecycle instructions.";
10
+
11
+ function read(relativePath: string): string {
12
+ return readFileSync(join(ROOT, relativePath), "utf8");
13
+ }
14
+
15
+ const core = read("assets/orchestrator.md");
16
+ const delegation = read("assets/orchestrator-delegation.md");
17
+ const staticPrompts = `${core}\n${delegation}`;
18
+
19
+ test("static prompts omit stale native RDD lifecycle mirrors", () => {
20
+ for (const marker of [
21
+ "Authority-First Terminal Procedure",
22
+ "reconcile-terminal-mirrors",
23
+ "Native Bounded Review Orchestration",
24
+ "Continue after a stop reason code",
25
+ "gentle-ai review status",
26
+ "next_transition",
27
+ "start -> finalize -> validate",
28
+ "review.capture-result",
29
+ "review.validate",
30
+ "reviewGate.result",
31
+ ]) {
32
+ assert.ok(!staticPrompts.includes(marker), `stale RDD marker remains: ${marker}`);
33
+ }
34
+ });
35
+
36
+ test("lossless blocking prompts route every closed single-select envelope through the native closed tool", () => {
37
+ for (const clause of [
38
+ "For every strictly closed single-select envelope",
39
+ "ask_user_choice",
40
+ "envelope-owned canonical option token as opaque `value`",
41
+ "returns exactly one `value`",
42
+ "externally owned open/free-text questionnaire",
43
+ "never for a closed domain",
44
+ "exact captured provider-owned choice invocation",
45
+ ]) {
46
+ assert.ok(delegation.includes(clause), `lossless prompt is missing: ${clause}`);
47
+ }
48
+ });
49
+
50
+ test("static prompts declare one dynamic Gentle AI RDD ownership boundary", () => {
51
+ assert.equal(staticPrompts.split(BOUNDARY).length - 1, 1, "expected one dynamic RDD ownership boundary");
52
+ assert.ok(core.includes(BOUNDARY), "the Pi parent prompt owns the single boundary");
53
+ assert.ok(!delegation.includes(BOUNDARY), "generic delegation detail must not gain RDD text");
54
+ });
55
+
56
+ test("rendered parent prompt keeps the RDD boundary while omitting lifecycle mirrors", async () => {
57
+ const { __testing } = await import("../extensions/gentle-ai.ts");
58
+ const rendered = __testing.getOrchestratorPrompt();
59
+ assert.ok(rendered.includes(BOUNDARY));
60
+ for (const marker of ["Authority-First Terminal Procedure", "reconcile-terminal-mirrors", "next_transition"]) {
61
+ assert.ok(!rendered.includes(marker), `rendered parent prompt leaked: ${marker}`);
62
+ }
63
+ assert.ok(Buffer.byteLength(rendered, "utf8") <= 8192, "the rendered parent prompt must stay below the reduced 8 KiB budget");
64
+ });
65
+
66
+ test("static prompts retain normal SDD and delegated-work guidance", () => {
67
+ for (const heading of ["## SDD Workflow (lazy-loaded)", "## Memory Contract"]) {
68
+ assert.ok(core.includes(heading), `core lost ${heading}`);
69
+ }
70
+ for (const heading of [
71
+ "### Delegation Rules",
72
+ "#### Background Subagent Policy",
73
+ "#### Allowed edit surfaces (MANDATORY)",
74
+ "### 3. SDD (optional)",
75
+ ]) {
76
+ assert.ok(delegation.includes(heading), `delegation lost ${heading}`);
77
+ }
78
+ });
79
+
80
+ test("always-on parent prompt requires a narrow writer edit surface before launch", () => {
81
+ assert.match(core, /Before launching (?:a )?bounded writer/i);
82
+ assert.match(core, /`gentle-ai-worker`/);
83
+ assert.match(core, /`worker`/);
84
+ assert.match(core, /## Allowed edit surfaces/);
85
+ assert.match(core, /repository-relative/i);
86
+ assert.match(core, /never `\.`|never a bare repository root/i);
87
+ assert.match(core, /do not ask the human to author paths or globs/i);
88
+ });
89
+
90
+ test("review integration documents the opaque Pi adapter and Go-owned authority boundary", () => {
91
+ const docs = read("docs/review-integration.md");
92
+ for (const marker of [
93
+ "Buffer → Buffer/error",
94
+ "exact Go-issued materialize/submission tokens",
95
+ "typed Pi transport refusal fails closed",
96
+ "Go owns worktree, lineage, candidate freeze, lens selection, correction, validator, approval burn, and review semantics",
97
+ "Delivery commands remain ordinary repository-policy operations.",
98
+ "package has no durable receipt or policy authority",
99
+ "static assets intentionally omit lifecycle instructions",
100
+ ]) {
101
+ assert.ok(docs.includes(marker), `review integration doc is missing: ${marker}`);
102
+ }
103
+ });
@@ -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)));
@@ -114,10 +115,10 @@ test("package verification names the native review runtime boundary and packaged
114
115
  const manifest = readPackageJson();
115
116
 
116
117
  assert.ok(manifest.files?.includes("lib/"), "the published package must include the native review runtime module directory");
117
- assert.ok(manifest.files?.includes("runtime/"), "the published package must include the generated JavaScript transaction runtime");
118
+ assert.ok(manifest.files?.includes("runtime/"), "the published package must include generated JavaScript runtime modules");
118
119
  assert.match(verifier, /"lib\/native-review-cli\.ts"/, "package verification must require the native review adapter from the packaged runtime");
119
- assert.match(verifier, /"runtime\/git-commit-transaction\.mjs"/, "package verification must require the installed JavaScript transaction runtime");
120
- assert.match(verifier, /build-git-commit-transaction-runner\.mjs.*--check/s, "package verification must reject generated-runtime drift");
120
+ assert.match(verifier, /"runtime\/native-review-cli\.mjs"/, "package verification must require the generated native review adapter");
121
+ assert.match(verifier, /build-runtime-modules\.mjs.*--check/s, "package verification must reject generated-runtime drift");
121
122
  assert.match(verifier, /"tests\/fixtures\/native-review-cli\/v2\.1\.3\/start\.json"/, "package verification must retain the pinned native decoder fixture");
122
123
  assert.match(
123
124
  readFileSync(join(PACKAGE_ROOT, "extensions", "gentle-ai.ts"), "utf8"),
@@ -187,36 +188,31 @@ test("npm publication is bound to the exact package tag and triggering commit",
187
188
  assert.doesNotMatch(releaseSkill, /--ref "\$\{tag\}"|-f dist-tag=/);
188
189
  });
189
190
 
190
- test("publication gate is isolated from graph-v1 authority storage", () => {
191
+ test("Pi delivery relay is absent from the packaged extension", () => {
191
192
  const extension = readFileSync(join(PACKAGE_ROOT, "extensions", "gentle-ai.ts"), "utf8");
192
- const gate = readFileSync(join(PACKAGE_ROOT, "lib", "review-publication-gate.ts"), "utf8");
193
- const transaction = readFileSync(join(PACKAGE_ROOT, "lib", "review-transaction.ts"), "utf8");
194
193
 
195
- assert.match(extension, /from "\.\.\/lib\/review-publication-gate\.ts"/);
196
- assert.match(transaction, /from "\.\/review-publication-gate\.ts"/);
197
- assert.doesNotMatch(gate, /review-(?:transaction|object-store|graph-schema|lock|snapshot)/);
198
- assert.doesNotMatch(transaction, /export function evaluateReleaseFastPathV1|export function resolveConfiguredPushDestinationV1/);
194
+ assert.doesNotMatch(extension, /review-publication-gate/);
199
195
  });
200
196
 
201
- test("installed commit transaction runner loads JavaScript only and has deterministic build checks", () => {
197
+ test("generated runtime modules and packed-package checks are deterministic", () => {
202
198
  const packageJson = readPackageJson();
203
- const runner = readFileSync(join(PACKAGE_ROOT, "scripts", "run-git-commit-transaction.mjs"), "utf8");
199
+ const generator = readFileSync(join(PACKAGE_ROOT, "scripts", "build-runtime-modules.mjs"), "utf8");
204
200
  const packedRunner = readFileSync(join(PACKAGE_ROOT, "scripts", "test-packed-runner.mjs"), "utf8");
205
201
  const ci = readFileSync(join(PACKAGE_ROOT, ".github", "workflows", "ci.yml"), "utf8");
206
- assert.equal(packageJson.scripts?.["build:transaction-runner"], "node scripts/build-git-commit-transaction-runner.mjs --write");
207
- assert.equal(packageJson.scripts?.["check:transaction-runner"], "node scripts/build-git-commit-transaction-runner.mjs --check");
208
- assert.equal(packageJson.scripts?.["test:packed-runner"], "node scripts/test-packed-runner.mjs");
209
- assert.match(packageJson.scripts?.prepublishOnly ?? "", /pnpm run test:packed-runner/);
210
- assert.match(ci, /pnpm run test:packed-runner/);
202
+ assert.equal(packageJson.scripts?.["build:runtime-modules"], "node scripts/build-runtime-modules.mjs --write");
203
+ assert.equal(packageJson.scripts?.["check:runtime-modules"], "node scripts/build-runtime-modules.mjs --check");
204
+ assert.equal(packageJson.scripts?.["test:packed-package"], "node scripts/test-packed-runner.mjs");
205
+ assert.match(packageJson.scripts?.prepublishOnly ?? "", /pnpm run test:packed-package/);
206
+ assert.match(ci, /pnpm run check:runtime-modules/);
207
+ assert.match(ci, /pnpm run test:packed-package/);
208
+ assert.match(generator, /Generated by scripts\/build-runtime-modules\.mjs/);
211
209
  assert.match(packedRunner, /\["install"[^\]]*"--ignore-scripts=false"/s, "packed install must explicitly enable postinstall");
212
210
  assert.doesNotMatch(packedRunner, /\["install"[^\]]*"--ignore-scripts"(?!\=false)/s);
213
211
  assert.match(packedRunner, /execFileSync\("where\.exe", \["npm"\]/);
214
212
  assert.match(packedRunner, /could not resolve npm-cli\.js without a command shell/);
215
213
  assert.doesNotMatch(packedRunner, /ComSpec|cmd\.exe/);
216
214
  assert.match(packedRunner, /review", "capabilities", "--contract", "gentle-ai\.review-integration\/v2"/);
217
- assert.match(runner, /\.\.\/runtime\/git-commit-transaction\.mjs/);
218
- assert.doesNotMatch(runner, /\.ts["']/);
219
- assert.doesNotMatch(runner, /experimental-strip-types/);
215
+ assert.doesNotMatch(packedRunner, /git-commit-transaction|transaction runner/i);
220
216
  });
221
217
 
222
218
  test("package manifest ships and runs the checked-in package-local Gentle AI installer", () => {
@@ -266,20 +262,20 @@ test("package manifest installs pi-pretty through a wrapper without bundling nat
266
262
  );
267
263
  });
268
264
 
269
- test("package verification binds the published Gentle AI v2.4.0 runtime pin", () => {
265
+ test("package verification binds the published Gentle AI v2.6.0 runtime pin", () => {
270
266
  const installer = readFileSync(join(PACKAGE_ROOT, "scripts", "gentle-ai-installer.mjs"), "utf8");
271
267
  const binary = readFileSync(join(PACKAGE_ROOT, "lib", "gentle-ai-binary.ts"), "utf8");
272
268
  const verifier = readFileSync(join(PACKAGE_ROOT, "scripts", "verify-package-files.mjs"), "utf8");
273
269
 
274
- assert.match(installer, /INSTALLER_VERSION = "2\.4\.0"/);
270
+ assert.match(installer, /INSTALLER_VERSION = "2\.6\.0"/);
275
271
  assert.match(installer, /GENTLE_AI_WINDOWS_SOURCE_PACKAGE.*GENTLE_AI_WINDOWS_SOURCE_MODULE/);
276
- assert.match(installer, /GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM = "h1:XKkcy\+t76cyUKQnRwL1UnqHbhh8o416I8HQ1UsdvWSI="/);
272
+ assert.match(installer, /GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM = "h1:scGoZYnPHh4oCVqlcfQAOSpm22Ii5lmB0kBbiBKvNZk="/);
277
273
  assert.match(installer, /GOTOOLCHAIN: "local"/);
278
274
  assert.match(installer, /GOSUMDB: "sum\.golang\.org"/);
279
275
  assert.match(binary, /GENTLE_AI_VERSION = INSTALLER_VERSION/);
280
276
  assert.match(binary, /GO_SUMDB_SOURCE_BUILD/);
281
277
  assert.match(binary, /GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM/);
282
- assert.match(verifier, /v2\.4\.0/);
278
+ assert.match(verifier, /v2\.6\.0/);
283
279
  });
284
280
 
285
281
 
@@ -900,6 +896,22 @@ test("jd-fix-agent packaged allowlist includes write tools", () => {
900
896
  }
901
897
  });
902
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
+
903
915
  test("gentle-ai-worker packages the exact scoped writer contract", () => {
904
916
  const agentsDir = join(PACKAGE_ROOT, "assets", "agents");
905
917
  const agentPath = join(agentsDir, "gentle-ai-worker.md");
@@ -981,6 +993,10 @@ test("gentle-ai-worker packages the exact scoped writer contract", () => {
981
993
  const contextContract = readMarkdownSection(source, "Context contract");
982
994
  assert.match(contextContract, /pre-existing untracked targets explicitly listed by the parent/);
983
995
  assert.match(contextContract, /new files required by the delegated task/);
996
+ assert.match(contextContract, /derived candidate set the human can approve or narrow/);
997
+ assert.match(contextContract, /never an open request for the human to author paths or globs/);
998
+ assert.match(interactionContract, /closed set the human can approve, decline, or select from/);
999
+ assert.match(interactionContract, /never ask the human to author paths, globs, identifiers, or commands as free text/);
984
1000
 
985
1001
  const implementationRules = readMarkdownSection(source, "Implementation rules");
986
1002
  assert.match(implementationRules, /`blocked` only for a non-human technical blocker/);
@@ -1056,6 +1072,103 @@ test("installSddAssets installs gentle-ai-worker with a loader-compatible scoped
1056
1072
  );
1057
1073
  });
1058
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
+
1059
1172
  test("normal and forced installation copy generic agents with complete role contracts", () => {
1060
1173
  const previousAgentHome = process.env.GENTLE_PI_AGENT_HOME;
1061
1174
  const expectedTools = {
@@ -1129,7 +1242,7 @@ test("bounded implementation routing uses the same explicit fallback in both pol
1129
1242
  );
1130
1243
  });
1131
1244
 
1132
- test("orchestrator routes generic roles without reusing SDD or review agents", () => {
1245
+ test("orchestrator routes generic roles without static RDD lens routing", () => {
1133
1246
  for (const file of ["orchestrator.md", "orchestrator-delegation.md"]) {
1134
1247
  const routing = readFileSync(join(PACKAGE_ROOT, "assets", file), "utf8");
1135
1248
  assert.match(routing, /generic non-SDD exploration[\s\S]*`gentle-ai-explore`/);
@@ -1139,12 +1252,16 @@ test("orchestrator routes generic roles without reusing SDD or review agents", (
1139
1252
  );
1140
1253
  assert.match(routing, /generic non-SDD (?:technical )?verification[\s\S]*`gentle-ai-verify`/);
1141
1254
  assert.match(routing, /SDD roles stay inside SDD|Use `sdd-explore` and `sdd-verify` only inside SDD/);
1142
- assert.match(routing, /review lenses inside reviews|Use review lenses only inside explicit review transactions/);
1143
- assert.match(routing, /(?:truly local )?read-only check(?:ing)? of (?:known )?1-3 known files|1-3-file read-only check/);
1255
+ assert.match(routing, /(?:truly local )?read-only check(?:ing)? of (?:known )?1[-–]3 known files|1[-–]3-file read-only check/);
1144
1256
  assert.match(routing, /(?:verification that |verification commands →).*executes? or delegates?|executing\/delegating verification commands/);
1145
1257
  assert.match(routing, /missing(?: or |\/)unusable[\s\S]*native `Agent`[\s\S]*(?:the )?same read-only/);
1146
1258
  assert.match(routing, /report (?:the )?fallback/);
1259
+ assert.doesNotMatch(routing, /review lenses? (?:inside|only inside)|review lens routing/i);
1147
1260
  }
1261
+
1262
+ const core = readFileSync(join(PACKAGE_ROOT, "assets", "orchestrator.md"), "utf8");
1263
+ assert.match(core, /injects the mirrored provider-bundle review execution contract/);
1264
+ assert.match(core, /this package invents no lifecycle instructions/);
1148
1265
  });
1149
1266
 
1150
1267
  test("pi-pretty wrapper uses real package path resolution for pnpm symlink installs", () => {
@@ -1160,9 +1277,9 @@ test("pi-pretty wrapper uses real package path resolution for pnpm symlink insta
1160
1277
  assert.match(wrapper, /quietToolsEnabled/);
1161
1278
  });
1162
1279
 
1163
- test("v2.2.0 release package and runtime stop before publication", () => {
1280
+ test("v2.4.0 release package and runtime stop before publication", () => {
1164
1281
  const packageJson = readPackageJson();
1165
- assert.equal(packageJson.version, "2.2.0", "the release manifest must remain explicitly pinned to v2.2.0");
1282
+ assert.equal(packageJson.version, "2.4.0", "the release manifest must remain explicitly pinned to v2.4.0");
1166
1283
  assert.equal(
1167
1284
  packageJson.scripts?.test,
1168
1285
  "node --experimental-strip-types --test tests/*.test.ts && pnpm run check:provider-contract && pnpm run test:harness",
@@ -1192,17 +1309,16 @@ test("bounded review keeps the Judgment Day skill contract at canon metadata ver
1192
1309
  assert.doesNotMatch(frontmatter, /^ version: "1\.4"$/m);
1193
1310
  });
1194
1311
 
1195
- test("README documents bounded review transactions and the honest installed permission boundary", () => {
1312
+ test("README documents dynamic Gentle AI RDD ownership and the installed permission boundary", () => {
1196
1313
  const readme = readFileSync(join(PACKAGE_ROOT, "README.md"), "utf8");
1197
1314
  for (const clause of [
1198
- "New ordinary review uses compact `gentle_review` `start -> finalize -> validate`.",
1199
- "Native compact gate validation is read-only.",
1200
- "Release from protected `main` may bypass receipt validation only when the tag targets the current immutable `origin/main` SHA, required CI for that exact SHA is successful, the remote head is rechecked before tag push, and no fresh risk evidence exists; otherwise release fails closed through native receipt validation.",
1315
+ "Gentle AI dynamically supplies runtime-specific RDD instructions",
1316
+ "does not define an RDD lifecycle",
1201
1317
  "Dangerous-command safety remains independent and authoritative.",
1202
- "Adversarial review roles (the refuter and the targeted validator) are never Pi-authored",
1203
1318
  "package-managed isolated installation",
1204
1319
  "Project and user overrides may shadow a package asset",
1205
1320
  ]) {
1206
- assert.ok(readme.includes(clause), `README missing review v2 clause: ${clause}`);
1321
+ assert.ok(readme.includes(clause), `README missing dynamic RDD clause: ${clause}`);
1207
1322
  }
1323
+ assert.doesNotMatch(readme, /New ordinary review uses compact `gentle_review` `start -> finalize -> validate`\./);
1208
1324
  });
@@ -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");