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.
Files changed (228) hide show
  1. package/README.md +108 -79
  2. package/assets/agents/gentle-ai-worker.md +7 -3
  3. package/assets/agents/jd-fix-agent.md +1 -1
  4. package/assets/agents/jd-judge-a.md +3 -1
  5. package/assets/agents/jd-judge-b.md +3 -1
  6. package/assets/agents/review-readability.md +4 -3
  7. package/assets/agents/review-reliability.md +4 -3
  8. package/assets/agents/review-resilience.md +4 -3
  9. package/assets/agents/review-risk.md +4 -3
  10. package/assets/agents/sdd-apply.md +11 -4
  11. package/assets/agents/sdd-archive.md +6 -1
  12. package/assets/agents/sdd-design.md +6 -1
  13. package/assets/agents/sdd-explore.md +6 -2
  14. package/assets/agents/sdd-init.md +10 -2
  15. package/assets/agents/sdd-onboard.md +6 -1
  16. package/assets/agents/sdd-proposal.md +8 -1
  17. package/assets/agents/sdd-research.md +54 -0
  18. package/assets/agents/sdd-spec.md +6 -1
  19. package/assets/agents/sdd-status.md +10 -5
  20. package/assets/agents/sdd-sync.md +6 -1
  21. package/assets/agents/sdd-tasks.md +8 -4
  22. package/assets/agents/sdd-verify.md +27 -2
  23. package/assets/chains/4r-review.chain.md +2 -0
  24. package/assets/chains/sdd-full.chain.md +2 -2
  25. package/assets/chains/sdd-plan.chain.md +1 -1
  26. package/assets/chains/sdd-verify.chain.md +2 -2
  27. package/assets/orchestrator-delegation.md +145 -160
  28. package/assets/orchestrator-memory.md +2 -0
  29. package/assets/orchestrator.md +25 -48
  30. package/assets/sdd-orchestrator-workflow.md +163 -25
  31. package/assets/support/sdd-status-contract.md +24 -6
  32. package/contracts/review-integration/v1/fixtures/consent.fixture.json +3 -3
  33. package/contracts/review-integration/v1/fixtures/start-v2.fixture.json +19 -28
  34. package/contracts/review-integration/v1/fixtures/start.fixture.json +1 -10
  35. package/contracts/review-integration/v1/fixtures/status-v2.fixture.json +12 -21
  36. package/contracts/review-integration/v1/schemas/correction-plan-request.schema.json +49 -0
  37. package/contracts/review-integration/v1/schemas/operation.schema.json +76 -0
  38. package/contracts/review-integration/v1/schemas/repair.schema.json +39 -0
  39. package/contracts/review-integration/v1/schemas/status-v2.schema.json +4 -2
  40. package/contracts/review-integration/v1/schemas/status.schema.json +4 -2
  41. package/contracts/review-integration/v1/schemas/transition-execution.schema.json +42 -0
  42. package/contracts/review-integration/v2/fixtures/consent.fixture.json +1 -1
  43. package/contracts/review-integration/v2/fixtures/start.fixture.json +1 -10
  44. package/contracts/review-integration/v2/fixtures/status.fixture.json +1 -10
  45. package/contracts/review-integration/v2/schemas/failure.schema.json +5 -1
  46. package/contracts/review-integration/v2/schemas/last-event-closure.schema.json +66 -0
  47. package/contracts/review-integration/v2/schemas/opencode-provider-role.schema.json +14 -0
  48. package/contracts/review-integration/v2/schemas/operation.schema.json +6 -1
  49. package/contracts/review-integration/v2/schemas/repair.schema.json +4 -2
  50. package/contracts/review-integration/v2/schemas/start.schema.json +5 -2
  51. package/contracts/review-integration/v2/schemas/status.schema.json +4 -2
  52. package/contracts/review-provider-contract-mirror/provider-contract.lock.json +30 -0
  53. package/contracts/review-provider-contract-mirror/v1.1.0/bundle/README.md +12 -0
  54. package/contracts/review-provider-contract-mirror/v1.1.0/bundle/manifest.json +65 -0
  55. package/contracts/review-provider-contract-mirror/v1.1.0/bundle/schemas/lens.schema.json +16 -0
  56. package/contracts/review-provider-contract-mirror/v1.1.0/bundle/schemas/refuter.schema.json +1 -0
  57. package/contracts/review-provider-contract-mirror/v1.1.0/bundle/schemas/targeted-validator.schema.json +1 -0
  58. package/contracts/review-provider-contract-mirror/v1.1.0/bundle/vectors/lens.json +1 -0
  59. package/contracts/review-provider-contract-mirror/v1.1.0/bundle/vectors/refuter.json +1 -0
  60. package/contracts/review-provider-contract-mirror/v1.1.0/bundle/vectors/targeted-validator.json +1 -0
  61. package/contracts/review-provider-contract-mirror/v1.1.0/generated/provider-capabilities.baseline.json +15 -0
  62. package/contracts/review-provider-contract-mirror/v1.1.0/generated/provider-roles.baseline.json +42 -0
  63. package/docs/native-authority-architecture.md +14 -16
  64. package/docs/review-integration.md +27 -353
  65. package/extensions/ask-user-choice.ts +151 -0
  66. package/extensions/gentle-ai.ts +2710 -3211
  67. package/extensions/quiet-tools.ts +515 -32
  68. package/extensions/sdd-init.ts +21 -12
  69. package/extensions/skill-registry.ts +10 -2
  70. package/extensions/startup-banner.ts +10 -4
  71. package/lib/gentle-ai-binary.ts +173 -2
  72. package/lib/gentle-ai-renderer.ts +70 -0
  73. package/lib/model-routing-authority.ts +133 -0
  74. package/lib/native-review-cli.ts +676 -781
  75. package/lib/opaque-pi-reviewer-adapter.ts +284 -0
  76. package/lib/provider-contract-bundle.ts +704 -0
  77. package/lib/review-candidate-view.ts +836 -118
  78. package/lib/review-compact-contract.ts +59 -248
  79. package/lib/review-host-relay.ts +578 -0
  80. package/lib/review-integration-v2.ts +1254 -221
  81. package/lib/review-last-event-controller.ts +35 -0
  82. package/lib/review-relay-contract.ts +16 -0
  83. package/lib/sdd-preflight.ts +230 -67
  84. package/lib/sdd-status.ts +66 -111
  85. package/lib/terminal-theme.ts +1 -1
  86. package/package.json +83 -79
  87. package/runtime/gentle-ai-binary.mjs +174 -3
  88. package/runtime/native-review-cli.mjs +610 -715
  89. package/runtime/review-integration-v2.mjs +1223 -190
  90. package/runtime/review-relay-contract.mjs +17 -0
  91. package/scripts/{build-git-commit-transaction-runner.mjs → build-runtime-modules.mjs} +5 -5
  92. package/scripts/check-provider-contract.mjs +138 -0
  93. package/scripts/gentle-ai-installer.mjs +85 -22
  94. package/scripts/maintainer/provider-relay-matrix.mjs +403 -0
  95. package/scripts/mirror-provider-contract.mjs +143 -0
  96. package/scripts/test-packed-runner.mjs +20 -9
  97. package/scripts/verify-package-files.mjs +119 -43
  98. package/skills/_shared/review-ledger-contract.md +11 -19
  99. package/skills/chained-pr/SKILL.md +3 -0
  100. package/skills/cognitive-doc-design/SKILL.md +1 -1
  101. package/skills/comment-writer/SKILL.md +1 -1
  102. package/skills/gentle-ai/SKILL.md +7 -74
  103. package/skills/issue-creation/SKILL.md +94 -168
  104. package/skills/judgment-day/SKILL.md +9 -5
  105. package/skills/judgment-day/references/prompts-and-formats.md +2 -0
  106. package/skills/rdd-defect-workflow/SKILL.md +54 -0
  107. package/skills/release/SKILL.md +3 -3
  108. package/skills/skill-registry/SKILL.md +1 -1
  109. package/skills/work-unit-commits/SKILL.md +3 -1
  110. package/tests/artifact-language.test.ts +24 -11
  111. package/tests/ask-user-choice.test.ts +264 -0
  112. package/tests/background-subagents.test.ts +771 -0
  113. package/tests/codegraph-tools.test.ts +3 -3
  114. package/tests/crosslane/cross-lane.mjs +16 -0
  115. package/tests/delegated-key-learnings-contract.test.ts +240 -0
  116. package/tests/devbinary/native-review-parity.devtest.ts +167 -250
  117. package/tests/devbinary/pi-host-relay.devtest.ts +867 -0
  118. package/tests/fixtures/devbinary/capabilities-v2.1.derived.json +331 -0
  119. package/tests/fixtures/devbinary/capabilities-v2.2.captured.json +340 -0
  120. package/tests/fixtures/devbinary/consent-v3.captured.json +37 -0
  121. package/tests/fixtures/devbinary/failure-v2-capture-evidence.captured.json +16 -0
  122. package/tests/fixtures/devbinary/last-event-capture-correction-plan.captured.json +10 -0
  123. package/tests/fixtures/devbinary/last-event-capture-refuter-approved.captured.json +20 -0
  124. package/tests/fixtures/devbinary/last-event-capture-refuter-correction-required.captured.json +26 -0
  125. package/tests/fixtures/devbinary/last-event-capture-result-approved.captured.json +8 -0
  126. package/tests/fixtures/devbinary/last-event-capture-result-correction-required.captured.json +26 -0
  127. package/tests/fixtures/devbinary/last-event-capture-validation-approved.captured.json +8 -0
  128. package/tests/fixtures/devbinary/last-event-closure.provenance.md +13 -0
  129. package/tests/fixtures/devbinary/result-artifact-v2-path.captured.json +12 -0
  130. package/tests/fixtures/devbinary/result-artifact-v2.captured.json +12 -0
  131. package/tests/fixtures/devbinary/review-acknowledged-v1.captured.json +9 -0
  132. package/tests/fixtures/devbinary/review-acknowledged.provenance.md +31 -0
  133. package/tests/fixtures/devbinary/start-v3-consent-declined.captured.json +19 -0
  134. package/tests/fixtures/devbinary/start-v3-consent-granted.captured.json +109 -0
  135. package/tests/fixtures/devbinary/start-v3-zero-lens-closed.captured.json +21 -0
  136. package/tests/fixtures/devbinary/status-v5-capture-result-submission.captured.json +184 -0
  137. package/tests/fixtures/devbinary/status-v5-repository-context.captured.json +138 -0
  138. package/tests/fixtures/devbinary/status-v5.captured.json +88 -0
  139. package/tests/fixtures/native-review-cli/v2.5.0-rc.1/PROVENANCE.txt +15 -0
  140. package/tests/fixtures/provider-contract-bundle/v1.1.0/README.md +12 -0
  141. package/tests/fixtures/provider-contract-bundle/v1.1.0/manifest.json +65 -0
  142. package/tests/fixtures/provider-contract-bundle/v1.1.0/schemas/lens.schema.json +16 -0
  143. package/tests/fixtures/provider-contract-bundle/v1.1.0/schemas/refuter.schema.json +1 -0
  144. package/tests/fixtures/provider-contract-bundle/v1.1.0/schemas/targeted-validator.schema.json +1 -0
  145. package/tests/fixtures/provider-contract-bundle/v1.1.0/vectors/lens.json +1 -0
  146. package/tests/fixtures/provider-contract-bundle/v1.1.0/vectors/refuter.json +1 -0
  147. package/tests/fixtures/provider-contract-bundle/v1.1.0/vectors/targeted-validator.json +1 -0
  148. package/tests/gentle-ai-binary.test.ts +83 -13
  149. package/tests/gentle-ai-dev-binary-surfacing.test.ts +195 -0
  150. package/tests/gentle-ai-dev-binary.test.ts +159 -0
  151. package/tests/gentle-ai-installer.test.ts +138 -48
  152. package/tests/gentle-ai.test.ts +820 -131
  153. package/tests/gentle-theme.test.ts +133 -0
  154. package/tests/maintainer/provider-relay.maintest.ts +601 -0
  155. package/tests/model-routing-authority.test.ts +257 -0
  156. package/tests/native-review-capability-contract.test.ts +75 -2
  157. package/tests/native-review-cli.test.ts +588 -908
  158. package/tests/native-review-consent.test.ts +283 -38
  159. package/tests/native-review-parity-runtime.test.ts +108 -350
  160. package/tests/native-review-parity.test.ts +684 -732
  161. package/tests/native-sdd-attempt-authority.test.ts +235 -0
  162. package/tests/opaque-pi-reviewer-adapter.test.ts +266 -0
  163. package/tests/orchestrator-budget.test.ts +158 -89
  164. package/tests/orchestrator-rdd-ownership.test.ts +103 -0
  165. package/tests/package-manifest.test.ts +128 -100
  166. package/tests/provider-contract-bundle.test.ts +385 -0
  167. package/tests/provider-contract-mirror.test.ts +206 -0
  168. package/tests/provider-defect-handoff.test.ts +252 -0
  169. package/tests/quiet-tool-rendering.test.ts +1055 -28
  170. package/tests/review-actor-tool-deny.test.ts +12 -13
  171. package/tests/review-authority-recovery-docs.test.ts +1 -2
  172. package/tests/review-candidate-view.test.ts +898 -12
  173. package/tests/review-compact-contract.test.ts +29 -122
  174. package/tests/review-controller-lock-status.test.ts +2 -2
  175. package/tests/review-controller-native-recovery.test.ts +366 -857
  176. package/tests/review-controller-native-routing.test.ts +1253 -4199
  177. package/tests/review-controller-retired-ops.test.ts +1 -1
  178. package/tests/review-controller-workspace-root.test.ts +236 -70
  179. package/tests/review-controller.test.ts +26 -816
  180. package/tests/review-corrected-finalize-binding.test.ts +134 -0
  181. package/tests/review-dispatch-hydration-gap.test.ts +145 -0
  182. package/tests/review-gate.test.ts +0 -45
  183. package/tests/review-host-relay-restart-parity.test.ts +360 -0
  184. package/tests/review-host-relay-routing.test.ts +352 -0
  185. package/tests/review-host-relay.test.ts +754 -0
  186. package/tests/review-integration-v2-forward.test.ts +654 -0
  187. package/tests/review-integration-v2.test.ts +317 -129
  188. package/tests/review-last-event-closure.test.ts +408 -0
  189. package/tests/review-ledger-contract.test.ts +106 -60
  190. package/tests/review-recovered-lineage-routing.test.ts +199 -0
  191. package/tests/review-relay-transport-agent.test.ts +322 -0
  192. package/tests/review-snapshot.test.ts +3 -2
  193. package/tests/runtime-harness.mjs +573 -180
  194. package/tests/sdd-agent-tools.test.ts +53 -32
  195. package/tests/sdd-preflight.test.ts +81 -15
  196. package/tests/sdd-status.test.ts +109 -110
  197. package/tests/skill-collision-prefixes.test.ts +6 -8
  198. package/tests/skill-registry.test.ts +50 -1
  199. package/tests/verify-package-files.test.ts +62 -0
  200. package/tests/writer-edit-surface-scope.test.ts +230 -0
  201. package/themes/Gentleman-Cute.json +94 -0
  202. package/themes/Gentleman-Sexy.json +92 -0
  203. package/assets/agents/review-refuter.md +0 -40
  204. package/assets/agents/review-validator.md +0 -23
  205. package/lib/git-commit-transaction.ts +0 -801
  206. package/lib/native-review-remediation.ts +0 -49
  207. package/lib/review-compact.ts +0 -947
  208. package/lib/review-refuter-adapter.ts +0 -129
  209. package/lib/review-runtime-contract.ts +0 -68
  210. package/prompts/gcl.md +0 -54
  211. package/prompts/gis.md +0 -25
  212. package/prompts/gpr.md +0 -41
  213. package/prompts/gwr.md +0 -31
  214. package/runtime/git-commit-transaction.mjs +0 -802
  215. package/scripts/run-git-commit-transaction.mjs +0 -35
  216. package/tests/fixtures/native-review-cli/v2.1.2/bind-sdd.json +0 -25
  217. package/tests/fixtures/native-review-cli/v2.1.2/finalize.json +0 -8
  218. package/tests/fixtures/native-review-cli/v2.1.2/sdd-status-engram.json +0 -139
  219. package/tests/fixtures/native-review-cli/v2.1.2/sdd-status.json +0 -200
  220. package/tests/fixtures/native-review-cli/v2.1.2/start.json +0 -12
  221. package/tests/fixtures/native-review-cli/v2.1.2/validate-allow.json +0 -24
  222. package/tests/fixtures/native-review-cli/v2.1.2/validate-deny-empty-context.json +0 -20
  223. package/tests/fixtures/native-review-cli/v2.1.2/validate-deny.json +0 -28
  224. package/tests/fixtures/native-review-cli/v2.1.3/sdd-status-engram.json +0 -139
  225. package/tests/fixtures/native-review-cli/v2.1.3/sdd-status.json +0 -200
  226. package/tests/git-commit-transaction.test.ts +0 -302
  227. package/tests/review-compact.test.ts +0 -243
  228. package/tests/review-refuter-adapter.test.ts +0 -89
@@ -1,6 +1,6 @@
1
1
  import assert from "node:assert/strict";
2
2
  import { spawnSync } from "node:child_process";
3
- import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
3
+ import { copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
4
4
  import { tmpdir } from "node:os";
5
5
  import { join } from "node:path";
6
6
  import test, { after } from "node:test";
@@ -9,8 +9,8 @@ import test, { after } from "node:test";
9
9
  // orchestrator-lazy-diet migration tests
10
10
  //
11
11
  // Locks the split of the always-on `assets/orchestrator.md` into a thin core
12
- // plus three path-substituted lazy reference files (see design.md "Core
13
- // budget rebuilt from measured drafts" and "Appendix: drafted core texts").
12
+ // plus lazy reference files (see design.md "Core budget rebuilt from measured
13
+ // drafts" and "Appendix: drafted core texts").
14
14
  //
15
15
  // `getOrchestratorPrompt`'s rendered return value is memoized in a
16
16
  // module-level cache (first-read-wins for the process lifetime — see design.md
@@ -19,17 +19,19 @@ import test, { after } from "node:test";
19
19
  // ambient environment variables, so production runtime asset resolution stays
20
20
  // deterministic. The representative production assets directory below is
21
21
  // populated by COPYING the real repo assets (dynamically, at test-run time)
22
- // into a short-path tmpdir this isolates byte-budget measurement from the
23
- // real repo's absolute path length while keeping content representative of
24
- // production. Tests that need to inspect the real repo files directly (the
25
- // disposition-mapped union sweep, the core-alone token assertions) read
26
- // `assets/*.md` directly via `readFileSync`, sidestepping the cache entirely.
22
+ // into short and deliberately long tmpdir paths. This isolates byte-budget
23
+ // measurement from the real repo's absolute path length while keeping content
24
+ // representative of production. Tests that need to inspect the real repo files
25
+ // directly (the disposition-mapped union sweep, the core-alone token
26
+ // assertions) read `assets/*.md` directly via `readFileSync`, sidestepping the
27
+ // cache entirely.
27
28
  // ---------------------------------------------------------------------------
28
29
 
29
30
  const REPO_ROOT = join(import.meta.dirname, "..");
30
31
  const REAL_ASSETS_DIR = join(REPO_ROOT, "assets");
31
32
  const FIXTURE_PATH = join(import.meta.dirname, "fixtures", "orchestrator.pre-diet.md");
32
- const BUDGET_BYTES = 10240;
33
+ const BUDGET_BYTES = 8192;
34
+ const MIN_CONTROLLED_LONG_ASSETS_ROOT_CHARS = 93;
33
35
 
34
36
  const LAZY_ASSET_NAMES = [
35
37
  "orchestrator.md",
@@ -38,42 +40,56 @@ const LAZY_ASSET_NAMES = [
38
40
  "orchestrator-memory.md",
39
41
  "orchestrator-skills.md",
40
42
  ] as const;
43
+ const LAZY_REFERENCE_FILE_NAMES = LAZY_ASSET_NAMES.slice(1);
41
44
 
42
- const representativeProductionAssetsDir = mkdtempSync(join(tmpdir(), "gp-b-"));
43
- for (const name of LAZY_ASSET_NAMES) {
44
- const src = join(REAL_ASSETS_DIR, name);
45
- writeFileSync(
46
- join(representativeProductionAssetsDir, name),
47
- existsSync(src) ? readFileSync(src) : `stub placeholder for ${name} (not authored yet)\n`,
48
- );
45
+ function copyRequiredLazyAssets(destination: string): void {
46
+ for (const name of LAZY_ASSET_NAMES) {
47
+ const source = join(REAL_ASSETS_DIR, name);
48
+ assert.ok(existsSync(source), `missing packaged lazy asset: ${name}`);
49
+ copyFileSync(source, join(destination, name));
50
+ }
49
51
  }
50
- const { __testing } = await import("../extensions/gentle-ai.ts");
51
52
 
52
- const MIN_REALISTIC_INSTALL_ASSETS_PATH_CHARS = 59;
53
+ const representativeProductionAssetsDir = mkdtempSync(join(tmpdir(), "gp-b-"));
54
+ copyRequiredLazyAssets(representativeProductionAssetsDir);
55
+ const { __testing } = await import("../extensions/gentle-ai.ts");
53
56
 
54
- // Realistic-length assets path: mirrors an actual installed path such as
55
- // `~/.pi/agent/npm/node_modules/gentle-pi/assets`, so the budget assertion is
56
- // not laundered through an artificially short mkdtemp path. The helper is also
57
- // exercised in a fresh child process (`tests/fixtures/measure-orchestrator-prompt.mjs`)
58
- // to keep production cache behavior separate from fixture measurements.
59
- const realisticBaseDir = mkdtempSync(join(tmpdir(), "gp-realistic-"));
60
- const realisticDir = join(realisticBaseDir, ".pi", "agent", "npm", "node_modules", "gentle-pi", "assets");
61
- mkdirSync(realisticDir, { recursive: true });
57
+ // A controlled long assets root proves the parent prompt remains within the
58
+ // canonical budget independently of the checkout or installed-package path.
59
+ // The child-process measurement keeps production cache behavior separate from
60
+ // fixture measurements.
61
+ // The root is built to one exact length rather than "tmpdir plus a long
62
+ // suffix". The prompt declares the absolute root once, so it grows one byte
63
+ // per root character, and a 48-character macOS tmpdir pushed the previous
64
+ // construction to 164 characters where a 4-character Linux /tmp gave 121.
65
+ // A fixed length makes every platform measure the same budget claim.
66
+ const CONTROLLED_LONG_ASSETS_ROOT_CHARS = 128;
67
+ const controlledLongBaseDir = mkdtempSync(join(tmpdir(), "gp-long-"));
68
+ const controlledLongSegmentChars = CONTROLLED_LONG_ASSETS_ROOT_CHARS - join(controlledLongBaseDir, "x", "assets").length + 1;
62
69
  assert.ok(
63
- realisticDir.length >= MIN_REALISTIC_INSTALL_ASSETS_PATH_CHARS,
64
- `realistic scratch assets path is only ${realisticDir.length} chars, need >= ${MIN_REALISTIC_INSTALL_ASSETS_PATH_CHARS} to mirror a real install path`,
70
+ controlledLongSegmentChars >= 1,
71
+ `tmpdir ${tmpdir()} is too long to build a ${CONTROLLED_LONG_ASSETS_ROOT_CHARS}-char controlled assets root`,
65
72
  );
66
- for (const name of LAZY_ASSET_NAMES) {
67
- const src = join(REAL_ASSETS_DIR, name);
68
- writeFileSync(
69
- join(realisticDir, name),
70
- existsSync(src) ? readFileSync(src) : `stub placeholder for ${name} (not authored yet)\n`,
71
- );
72
- }
73
+ const controlledLongAssetsDir = join(
74
+ controlledLongBaseDir,
75
+ "path-independent-prompt-budget-".repeat(Math.ceil(controlledLongSegmentChars / 31)).slice(0, controlledLongSegmentChars),
76
+ "assets",
77
+ );
78
+ mkdirSync(controlledLongAssetsDir, { recursive: true });
79
+ assert.equal(
80
+ controlledLongAssetsDir.length,
81
+ CONTROLLED_LONG_ASSETS_ROOT_CHARS,
82
+ `controlled long assets root is ${controlledLongAssetsDir.length} chars, want exactly ${CONTROLLED_LONG_ASSETS_ROOT_CHARS}`,
83
+ );
84
+ assert.ok(
85
+ controlledLongAssetsDir.length >= MIN_CONTROLLED_LONG_ASSETS_ROOT_CHARS,
86
+ `controlled long assets root is only ${controlledLongAssetsDir.length} chars, need >= ${MIN_CONTROLLED_LONG_ASSETS_ROOT_CHARS}`,
87
+ );
88
+ copyRequiredLazyAssets(controlledLongAssetsDir);
73
89
 
74
90
  after(() => {
75
91
  rmSync(representativeProductionAssetsDir, { recursive: true, force: true });
76
- rmSync(realisticBaseDir, { recursive: true, force: true });
92
+ rmSync(controlledLongBaseDir, { recursive: true, force: true });
77
93
  });
78
94
 
79
95
  function readRealAsset(name: string): string {
@@ -98,7 +114,7 @@ function measureOrchestratorPromptBytes(assetsDir: string): number {
98
114
  // 2.2 — Byte budget (Spec: Always-On Injection Byte Budget)
99
115
  // ---------------------------------------------------------------------------
100
116
 
101
- test("getOrchestratorPrompt return value stays within the 10,240 B budget (short-path stub sanity check)", () => {
117
+ test("getOrchestratorPrompt return value stays within the canonical 8,192 B budget at a short assets root", () => {
102
118
  const rendered = __testing.renderOrchestratorPrompt(representativeProductionAssetsDir);
103
119
  const bytes = Buffer.byteLength(rendered, "utf8");
104
120
  assert.ok(
@@ -107,33 +123,46 @@ test("getOrchestratorPrompt return value stays within the 10,240 B budget (short
107
123
  );
108
124
  });
109
125
 
110
- test(`getOrchestratorPrompt return value stays within the 10,240 B budget at a realistic (>= ${MIN_REALISTIC_INSTALL_ASSETS_PATH_CHARS} char) install path length`, () => {
111
- const bytes = measureOrchestratorPromptBytes(realisticDir);
126
+ test(`getOrchestratorPrompt keeps a controlled long (>= ${MIN_CONTROLLED_LONG_ASSETS_ROOT_CHARS} char) assets root within the canonical budget`, () => {
127
+ const rendered = __testing.renderOrchestratorPrompt(controlledLongAssetsDir);
128
+ const bytes = measureOrchestratorPromptBytes(controlledLongAssetsDir);
129
+ assert.equal(bytes, Buffer.byteLength(rendered, "utf8"), "child-process and direct render byte counts must match");
112
130
  assert.ok(
113
131
  bytes <= BUDGET_BYTES,
114
- `getOrchestratorPrompt() returned ${bytes} B at a realistic ${realisticDir.length}-char ASSETS_DIR path, exceeds the ${BUDGET_BYTES} B budget`,
132
+ `getOrchestratorPrompt() returned ${bytes} B at controlled ${controlledLongAssetsDir.length}-char assets root, exceeds the ${BUDGET_BYTES} B budget`,
115
133
  );
134
+ assert.equal(
135
+ rendered.split(controlledLongAssetsDir).length - 1,
136
+ 1,
137
+ "the absolute assets root must be declared exactly once",
138
+ );
139
+ assert.ok(
140
+ rendered.includes(`Package assets root: \`${controlledLongAssetsDir}\`. Lazy asset paths below are relative to this root.`),
141
+ "the parent prompt must declare how to resolve relative lazy asset paths",
142
+ );
143
+ for (const name of LAZY_REFERENCE_FILE_NAMES) {
144
+ assert.ok(rendered.includes(`\`${name}\``), `lazy asset filename is missing: ${name}`);
145
+ }
146
+ assert.doesNotMatch(rendered, /\{\{/, "unresolved {{...}} placeholder leaked into the rendered prompt");
116
147
  });
117
148
 
118
149
  // ---------------------------------------------------------------------------
119
150
  // 2.3 — Disposition-mapped union sweep (Spec: No Normative Content Loss +
120
151
  // Pointer reachability)
121
152
  //
122
- // Every normative line of the frozen pre-diet fixture is assigned to exactly
123
- // one documented disposition: CORE_VERBATIM (byte-identical in the new
124
- // core), LAZY_VERBATIM (byte-identical in one specific lazy file), or OBSOLETE
125
- // (intentionally absent from every live model-facing asset). Section headings
126
- // that are reused unchanged as the new core's summary heading are
127
- // CORE_VERBATIM; section bodies that are condensed away in core are
128
- // LAZY_VERBATIM against their one target lazy file — never a blanket union
129
- // across all three.
153
+ // Every normative line of the frozen pre-diet fixture is assigned to a
154
+ // documented disposition: CORE_VERBATIM (byte-identical in the core),
155
+ // LAZY_VERBATIM (byte-identical in one specific lazy file), OBSOLETE
156
+ // (intentionally absent), or REPLACED (superseded by the focused #3417 asset
157
+ // policy ratchets below). REPLACED preserves the historical fixture without
158
+ // treating a retired prompt mirror as a current normative source.
130
159
  // ---------------------------------------------------------------------------
131
160
 
132
161
  type Target = "core" | "delegation" | "memory" | "skills";
133
162
 
134
163
  interface DispositionRange {
135
164
  lines: [number, number];
136
- target: Target | "obsolete";
165
+ target: Target | "obsolete" | "replaced";
137
166
  label: string;
138
167
  }
139
168
 
@@ -158,13 +187,37 @@ const DISPOSITION_MAP: DispositionRange[] = [
158
187
  { lines: [25, 29], target: "delegation", label: "Language Boundary LB5 (exceptions)" },
159
188
  { lines: [31, 40], target: "core", label: "Mental Model" },
160
189
  { lines: [42, 42], target: "core", label: "Work Routing Ladder heading" },
161
- { lines: [44, 110], target: "delegation", label: "Work Routing Ladder body + Pi Subagent Model Routing" },
190
+ {
191
+ lines: [44, 97],
192
+ target: "replaced",
193
+ label: "Pre-RDD routing detail replaced by focused direct-delegation guidance (#3417)",
194
+ },
195
+ {
196
+ lines: [98, 107],
197
+ target: "obsolete",
198
+ label: "Size/risk-selected SDD tier replaced by explicit-request/accepted-proposal selection (#312)",
199
+ },
200
+ {
201
+ lines: [108, 108],
202
+ target: "replaced",
203
+ label: "Earlier SDD trigger wording replaced by the focused SDD boundary (#3417)",
204
+ },
205
+ {
206
+ lines: [109, 110],
207
+ target: "obsolete",
208
+ label: "Size-gated SDD entry replaced by explicit-selection gating (#312)",
209
+ },
162
210
  { lines: [112, 112], target: "core", label: "Delegation Rules heading" },
163
211
  { lines: [114, 114], target: "core", label: "Delegation Rules core question" },
164
212
  {
165
- lines: [116, 132],
166
- target: "delegation",
167
- label: "Delegation Rules table + Mandatory Triggers preamble",
213
+ lines: [116, 126],
214
+ target: "obsolete",
215
+ label: "Pre-canon delegation table replaced by the mirrored gentle-ai canon table (#312)",
216
+ },
217
+ {
218
+ lines: [128, 132],
219
+ target: "replaced",
220
+ label: "Pre-RDD trigger wording replaced by focused direct-delegation guidance (#3417)",
168
221
  },
169
222
  {
170
223
  lines: [133, 133],
@@ -172,14 +225,27 @@ const DISPOSITION_MAP: DispositionRange[] = [
172
225
  label: "Superseded no-runtime inline exception",
173
226
  },
174
227
  {
175
- lines: [134, 181],
176
- target: "delegation",
177
- label: "Mandatory Triggers remainder + Cost/Context Balance + Canonical Workflows + Review Lens Selection",
228
+ lines: [134, 167],
229
+ target: "replaced",
230
+ label: "Pre-RDD trigger and workflow wording replaced by focused delegation guidance (#3417)",
231
+ },
232
+ {
233
+ lines: [169, 181],
234
+ target: "obsolete",
235
+ label: "Parent-selected review lens table replaced by native RAR lens ownership (#312)",
178
236
  },
179
237
  { lines: [183, 191], target: "core", label: "SDD Workflow pointer" },
180
238
  { lines: [193, 193], target: "core", label: "Memory Contract heading" },
181
- { lines: [195, 195], target: "core", label: "Memory Contract intro" },
182
- { lines: [197, 201], target: "core", label: "Memory Contract Non-SDD delegation" },
239
+ {
240
+ lines: [195, 195],
241
+ target: "replaced",
242
+ label: "Verbose memory introduction replaced by compact parent/subagent ownership (#3417)",
243
+ },
244
+ {
245
+ lines: [197, 201],
246
+ target: "replaced",
247
+ label: "Verbose non-SDD memory forwarding replaced by compact ownership (#3417)",
248
+ },
183
249
  { lines: [203, 230], target: "memory", label: "Memory Contract SDD phases table + artifact keys + lifecycle rule" },
184
250
  { lines: [232, 232], target: "core", label: "Skill Registry Protocol heading" },
185
251
  { lines: [234, 253], target: "skills", label: "Skill Registry Protocol detail" },
@@ -198,9 +264,18 @@ function isNormativeLine(line: string): boolean {
198
264
  }
199
265
 
200
266
  const fixtureLines = readFileSync(FIXTURE_PATH, "utf8").split("\n");
267
+ // Fixture lines 187 and 191 predate the root-relative lazy-asset contract and
268
+ // canonical-authority resolution. Keep their coverage by asserting the
269
+ // intentionally updated production wording instead of weakening the range.
270
+ const CURRENT_SDD_WORKFLOW_PATH = "`sdd-orchestrator-workflow.md`";
271
+ const CURRENT_HARD_PREFLIGHT_INVARIANT = "Hard preflight invariant: `openspec/config.yaml`, existing SDD changes, installed `.pi`/global SDD assets, or a todo named \"preflight\" are not session preflight. Do not mark SDD preflight complete, start `sdd-init`, launch SDD subagents/chains, or move to explore/proposal/spec/design/tasks until this session has an injected `## SDD Session Preflight` block or a canonical-authority resolution. Defaults and capability constraints may resolve fields without confirmation prompts; preserve unresolved-choice and safety gates.";
201
272
  const SUPERSEDED_LIFECYCLE_REVIEW_LINES = new Set([
202
273
  70,
274
+ // 74/77: the loose mode-choice background lines were replaced by the
275
+ // marked gentle-pi:background-subagents policy block (issue #256).
276
+ 74,
203
277
  76,
278
+ 77,
204
279
  92,
205
280
  125,
206
281
  126,
@@ -215,6 +290,7 @@ const SUPERSEDED_LIFECYCLE_REVIEW_LINES = new Set([
215
290
  ]);
216
291
 
217
292
  for (const range of DISPOSITION_MAP) {
293
+ if (range.target === "replaced") continue;
218
294
  test(
219
295
  `disposition-mapped union: ${range.label} (fixture:${range.lines[0]}-${range.lines[1]}) -> ${range.target}`,
220
296
  () => {
@@ -226,6 +302,12 @@ for (const range of DISPOSITION_MAP) {
226
302
  const raw = fixtureLines[ln - 1];
227
303
  if (raw === undefined || !isNormativeLine(raw)) continue;
228
304
  const trimmed = raw.trim();
305
+ const expected =
306
+ ln === 187
307
+ ? CURRENT_SDD_WORKFLOW_PATH
308
+ : ln === 191
309
+ ? CURRENT_HARD_PREFLIGHT_INVARIANT
310
+ : trimmed;
229
311
  if (SUPERSEDED_LIFECYCLE_REVIEW_LINES.has(ln)) {
230
312
  assert.ok(
231
313
  !targetContent.includes(trimmed),
@@ -241,8 +323,8 @@ for (const range of DISPOSITION_MAP) {
241
323
  continue;
242
324
  }
243
325
  assert.ok(
244
- targetContent.includes(trimmed),
245
- `normative line lost: fixture:${ln} "${trimmed}" not found verbatim in ${TARGET_FILE[range.target]} (disposition: ${range.target}, section: ${range.label})`,
326
+ targetContent.includes(expected),
327
+ `normative line lost: fixture:${ln} "${expected}" not found verbatim in ${TARGET_FILE[range.target]} (disposition: ${range.target}, section: ${range.label})`,
246
328
  );
247
329
  }
248
330
  },
@@ -254,32 +336,28 @@ for (const range of DISPOSITION_MAP) {
254
336
  // string alone, no lazy union.
255
337
  // ---------------------------------------------------------------------------
256
338
 
257
- test("core-alone: load-bearing delegation tokens present without lazy union", () => {
339
+ test("core-alone: load-bearing direct-delegation tokens remain without lazy union", () => {
258
340
  const core = readRealAsset("orchestrator.md");
259
341
  assert.match(core, /4-file rule/);
260
342
  assert.match(core, /Multi-file write rule/);
261
- assert.match(core, /Lifecycle gate rule/);
262
343
  assert.match(core, /Incident rule/);
344
+ assert.match(core, /Verification rule/);
263
345
  assert.match(core, /Long-session rule/);
264
- assert.match(core, /Review actor rule/);
265
346
  });
266
347
 
267
- test("core-alone: receipt-only lifecycle and independent safety are present without lazy union", () => {
348
+ test("core-alone: dynamic Gentle AI ownership replaces package lifecycle instructions", () => {
268
349
  const core = readRealAsset("orchestrator.md");
269
- assert.match(core, /start -> finalize -> validate/i);
270
- assert.match(core, /Compact gates use zero actors/i);
271
- assert.match(core, /Release from protected `main` may bypass receipt validation only when/i);
272
- assert.match(core, /Major and post-incident releases require explicit extraordinary review/i);
273
- assert.match(core, /Dangerous-command safety remains independent and authoritative/i);
274
- assert.match(core, /SDD completion adds no review or Judgment Day pass/i);
350
+ assert.match(core, /dynamically supplies runtime-specific RDD instructions via generated Pi APPEND_SYSTEM composition/);
351
+ assert.match(core, /if absent or unsupported, this package does not invent or fall back/);
352
+ assert.doesNotMatch(core, /start -> finalize -> validate/i);
353
+ assert.doesNotMatch(core, /receipt validation/i);
275
354
  });
276
355
 
277
- test("lazy bounded-review contract lists all four review lens names", () => {
278
- const content = `${readRealAsset("orchestrator.md")}\n${readRealAsset("orchestrator-delegation.md")}`;
279
- assert.match(content, /review-risk/);
280
- assert.match(content, /review-reliability/);
281
- assert.match(content, /review-resilience/);
282
- assert.match(content, /review-readability/);
356
+ test("lazy delegation detail has no native RDD controller markers", () => {
357
+ const delegation = readRealAsset("orchestrator-delegation.md");
358
+ for (const marker of ["next_transition", "review.capture-result", "reconcile-terminal-mirrors"]) {
359
+ assert.ok(!delegation.includes(marker), `stale RDD marker retained: ${marker}`);
360
+ }
283
361
  });
284
362
 
285
363
  test("live orchestrator assets remove the stale strong-gate retry contract", () => {
@@ -320,20 +398,11 @@ test("relocated lazy bodies are not double-delivered in the always-on core", ()
320
398
  );
321
399
  });
322
400
 
323
- test("relocated lazy files are reachable via in-core pointer paths", () => {
401
+ test("relocated lazy files are reachable via root-relative in-core filenames", () => {
324
402
  const rendered = __testing.getOrchestratorPrompt();
325
- assert.ok(
326
- rendered.includes(join(REAL_ASSETS_DIR, "orchestrator-delegation.md")),
327
- "core is missing a reachable pointer to orchestrator-delegation.md",
328
- );
329
- assert.ok(
330
- rendered.includes(join(REAL_ASSETS_DIR, "orchestrator-memory.md")),
331
- "core is missing a reachable pointer to orchestrator-memory.md",
332
- );
333
- assert.ok(
334
- rendered.includes(join(REAL_ASSETS_DIR, "orchestrator-skills.md")),
335
- "core is missing a reachable pointer to orchestrator-skills.md",
336
- );
403
+ for (const name of LAZY_REFERENCE_FILE_NAMES) {
404
+ assert.ok(rendered.includes(`\`${name}\``), `core is missing a reachable pointer to ${name}`);
405
+ }
337
406
  });
338
407
 
339
408
  // ---------------------------------------------------------------------------
@@ -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
+ "Gentle AI dynamically supplies runtime-specific RDD instructions via generated Pi APPEND_SYSTEM composition. Follow only those exact native instructions; if absent or unsupported, this package does not invent or fall back.";
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
+ });