gentle-pi 4.2.0 → 4.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 (202) hide show
  1. package/README.md +336 -137
  2. package/assets/agents/gentle-ai-explore.md +1 -0
  3. package/assets/agents/gentle-ai-verify.md +6 -4
  4. package/assets/agents/gentle-ai-worker.md +7 -1
  5. package/assets/orchestrator-delegation-reference.md +163 -0
  6. package/assets/orchestrator-delegation.md +25 -176
  7. package/assets/orchestrator-tracking.md +1 -1
  8. package/assets/orchestrator-verification.md +14 -8
  9. package/assets/orchestrator-writer.md +5 -1
  10. package/assets/orchestrator.md +9 -9
  11. package/bin/gentle-shell.mjs +170 -22
  12. package/contracts/review-provider-contract-mirror/provider-contract.lock.json +8 -7
  13. package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/README.md +7 -0
  14. package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/manifest.json +11 -2
  15. package/contracts/review-provider-contract-mirror/v1.4.0/bundle/routing/pi.md +92 -0
  16. package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/generated/provider-capabilities.baseline.json +8 -1
  17. package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/generated/provider-roles.baseline.json +1 -1
  18. package/docs/assets/diagrams/agent-orchestration.svg +54 -15
  19. package/docs/assets/diagrams/gentleman-workflow.svg +69 -11
  20. package/docs/assets/diagrams/measured-routing.svg +49 -0
  21. package/docs/assets/diagrams/native-review.svg +52 -12
  22. package/docs/assets/diagrams/odd-workflow.svg +128 -66
  23. package/docs/assets/diagrams/permission-modes.svg +51 -0
  24. package/docs/assets/features/audio-notifications.png +0 -0
  25. package/docs/assets/features/command-palette.png +0 -0
  26. package/docs/bundled-install.md +33 -3
  27. package/docs/delegated-verification.md +10 -8
  28. package/docs/gentle-agents-activity.md +11 -5
  29. package/docs/gentle-shell-uninstall.md +8 -1
  30. package/docs/gentle-shell.md +5 -4
  31. package/docs/install-wizard.md +8 -1
  32. package/docs/permission-modes.md +1 -1
  33. package/docs/readme-reference.md +107 -19
  34. package/docs/yolo-mode.md +21 -8
  35. package/extensions/codegraph-tools.ts +68 -6
  36. package/extensions/gentle-agents.ts +99 -12
  37. package/extensions/gentle-ai.ts +284 -52
  38. package/extensions/gentle-herdr-activity.ts +2 -3
  39. package/extensions/gentle-shell.ts +27 -12
  40. package/extensions/gentle-todo.ts +43 -9
  41. package/extensions/quiet-tools.ts +1 -1
  42. package/extensions/session-scratch.ts +365 -0
  43. package/extensions/startup-banner.ts +2 -3
  44. package/lib/agent-home.ts +8 -3
  45. package/lib/agents-protocol.ts +3 -0
  46. package/lib/agents-runner.ts +55 -6
  47. package/lib/agents-view.ts +3 -1
  48. package/lib/bounded-writer-admission.ts +51 -0
  49. package/lib/command-palette-catalog.ts +0 -4
  50. package/lib/commit-slice-signal.ts +100 -0
  51. package/lib/context-backstop.ts +84 -0
  52. package/lib/delivered-test-lint.ts +80 -0
  53. package/lib/destructive-command-guard.ts +120 -9
  54. package/lib/gauge-theme.ts +146 -0
  55. package/lib/gentle-ai-binary.ts +28 -7
  56. package/lib/gentle-shell-bundled.ts +39 -3
  57. package/lib/gentle-shell-import-pi.ts +233 -0
  58. package/lib/gentle-shell-launcher.ts +145 -11
  59. package/lib/gentle-shell-uninstall.ts +18 -1
  60. package/lib/herdr-activity.ts +24 -0
  61. package/lib/native-review-cli.ts +9 -1
  62. package/lib/orchestrator-discovery.ts +5 -1
  63. package/lib/orchestrator-presence.ts +9 -0
  64. package/lib/orchestrator-scope.ts +21 -3
  65. package/lib/provider-contract-bundle.ts +99 -53
  66. package/lib/review-candidate-view-owner.ts +29 -19
  67. package/lib/review-candidate-view.ts +36 -2
  68. package/lib/review-risk-assessment.ts +43 -38
  69. package/lib/review-session-standing-permission.ts +3 -1
  70. package/lib/session-profile-freeze.ts +30 -1
  71. package/lib/session-scratch.ts +474 -0
  72. package/lib/shell-bar.ts +25 -25
  73. package/lib/shell-changes-view.ts +80 -13
  74. package/lib/shell-gauge.ts +49 -6
  75. package/lib/shell-todo.ts +23 -4
  76. package/lib/shell-usage-view.ts +5 -1
  77. package/lib/shell-usage.ts +7 -7
  78. package/lib/verifier-admission.ts +29 -0
  79. package/lib/yolo-temp-scratch.ts +69 -0
  80. package/package.json +1 -2
  81. package/runtime/gentle-ai-binary.mjs +28 -7
  82. package/runtime/gentle-shell-bundled.mjs +38 -2
  83. package/runtime/gentle-shell-import-pi.mjs +234 -0
  84. package/runtime/gentle-shell-launcher.mjs +143 -9
  85. package/runtime/gentle-shell-uninstall.mjs +18 -1
  86. package/runtime/native-review-cli.mjs +8 -0
  87. package/runtime/review-risk-assessment.mjs +43 -38
  88. package/scripts/build-runtime-modules.mjs +1 -0
  89. package/scripts/bundled-engram-migration.mjs +125 -0
  90. package/scripts/gentle-ai-installer.mjs +10 -10
  91. package/scripts/installer-server.mjs +14 -4
  92. package/scripts/main-channel.mjs +3 -2
  93. package/scripts/run-test-suite.mjs +5 -0
  94. package/scripts/verify-package-files.mjs +19 -13
  95. package/skills/gentle-ai/SKILL.md +3 -3
  96. package/skills/work-unit-commits/SKILL.md +5 -3
  97. package/tests/agent-risk-escalation.test.ts +37 -10
  98. package/tests/agents-runner.test.ts +260 -3
  99. package/tests/autonomous-guard.test.ts +122 -0
  100. package/tests/bounded-writer-admission.test.ts +11 -3
  101. package/tests/card-content-policy.test.ts +19 -9
  102. package/tests/child-safety.test.ts +39 -0
  103. package/tests/codegraph-tools.test.ts +123 -2
  104. package/tests/command-palette.test.ts +14 -4
  105. package/tests/commit-slice-signal.test.ts +162 -0
  106. package/tests/context-backstop.test.ts +159 -0
  107. package/tests/delegate-for-reason-contract.test.ts +91 -24
  108. package/tests/delivered-test-lint.test.ts +105 -0
  109. package/tests/destructive-command-guard.test.ts +85 -1
  110. package/tests/engram-safety.test.ts +1 -1
  111. package/tests/fixtures/primary-session-prompt/gentleman.golden.md +143 -0
  112. package/tests/fixtures/provider-contract-bundle/v1.4.0/README.md +29 -0
  113. package/tests/fixtures/provider-contract-bundle/v1.4.0/manifest.json +83 -0
  114. package/tests/fixtures/provider-contract-bundle/v1.4.0/orchestration/pi.md +70 -0
  115. package/tests/fixtures/provider-contract-bundle/v1.4.0/routing/pi.md +92 -0
  116. package/tests/fixtures/provider-contract-bundle/v1.4.0/schemas/lens.schema.json +16 -0
  117. package/tests/fixtures/provider-contract-bundle/v1.4.0/schemas/refuter.schema.json +1 -0
  118. package/tests/fixtures/provider-contract-bundle/v1.4.0/schemas/targeted-validator.schema.json +1 -0
  119. package/tests/fixtures/provider-contract-bundle/v1.4.0/vectors/lens.json +1 -0
  120. package/tests/fixtures/provider-contract-bundle/v1.4.0/vectors/refuter.json +1 -0
  121. package/tests/fixtures/provider-contract-bundle/v1.4.0/vectors/targeted-validator.json +1 -0
  122. package/tests/gauge-theme.test.ts +120 -0
  123. package/tests/gentle-agents.test.ts +282 -3
  124. package/tests/gentle-ai-binary.test.ts +1 -1
  125. package/tests/gentle-ai-child-guards.test.ts +61 -0
  126. package/tests/gentle-ai-doctor-herdr.test.ts +94 -0
  127. package/tests/gentle-ai-installer.test.ts +51 -51
  128. package/tests/gentle-shell-bin.test.ts +163 -0
  129. package/tests/gentle-shell-bundled.test.ts +169 -6
  130. package/tests/gentle-shell-import-pi.test.ts +262 -0
  131. package/tests/gentle-shell-launcher.test.ts +79 -0
  132. package/tests/gentle-shell-uninstall.test.ts +30 -0
  133. package/tests/gentle-shell.test.ts +42 -9
  134. package/tests/gentle-todo.test.ts +179 -0
  135. package/tests/headless-delivery-question.test.ts +58 -0
  136. package/tests/herdr-activity.test.ts +34 -1
  137. package/tests/installer-server.test.ts +10 -2
  138. package/tests/installer-windows-bootstrap.test.ts +33 -4
  139. package/tests/native-review-capability-contract.test.ts +7 -1
  140. package/tests/native-review-parity.test.ts +22 -1
  141. package/tests/no-self-pause-contract.test.ts +5 -2
  142. package/tests/odd-integration.test.ts +5 -1
  143. package/tests/odd-routing-canonical-ratchet.test.ts +127 -162
  144. package/tests/odd-routing-contract.test.ts +115 -30
  145. package/tests/orchestrator-budget.test.ts +19 -8
  146. package/tests/orchestrator-discovery.test.ts +29 -0
  147. package/tests/orchestrator-rdd-ownership.test.ts +3 -1
  148. package/tests/orchestrator-scope.test.ts +66 -0
  149. package/tests/orchestrator-status-publication-contract.test.ts +12 -8
  150. package/tests/package-manifest.test.ts +24 -20
  151. package/tests/pi-update-launcher.test.ts +21 -2
  152. package/tests/primary-session-prompt.test.ts +101 -0
  153. package/tests/provider-contract-bundle.test.ts +118 -0
  154. package/tests/provider-contract-mirror.test.ts +24 -0
  155. package/tests/quiet-tool-rendering.test.ts +23 -0
  156. package/tests/rdd-aware-verification-contract.test.ts +29 -11
  157. package/tests/rdd-status-line.test.ts +79 -0
  158. package/tests/review-agent-end-preflight.test.ts +6 -0
  159. package/tests/review-candidate-view.test.ts +49 -0
  160. package/tests/review-contract-prompt.test.ts +5 -2
  161. package/tests/review-controller-native-routing.test.ts +14 -1
  162. package/tests/review-controller.test.ts +1 -1
  163. package/tests/review-host-relay-routing.test.ts +59 -2
  164. package/tests/review-loadout-rdd-off-sdk.test.ts +237 -0
  165. package/tests/review-owner-identity.test.ts +132 -0
  166. package/tests/review-risk-assessment.test.ts +163 -76
  167. package/tests/review-session-standing-permission-controller.test.ts +14 -1
  168. package/tests/review-session-standing-permission.test.ts +27 -1
  169. package/tests/run-test-suite.test.ts +15 -0
  170. package/tests/runtime-harness.mjs +16 -6
  171. package/tests/session-changes-shell.test.ts +1 -1
  172. package/tests/session-profile-freeze.test.ts +23 -0
  173. package/tests/session-scratch.test.ts +945 -0
  174. package/tests/session-subject-routing-contract.test.ts +2 -1
  175. package/tests/shell-bar.test.ts +7 -0
  176. package/tests/shell-changes-view.test.ts +137 -1
  177. package/tests/shell-gauge.test.ts +42 -0
  178. package/tests/small-task-inline-contract.test.ts +37 -0
  179. package/tests/standalone-config-home.test.ts +324 -0
  180. package/tests/startup-banner.test.ts +5 -4
  181. package/tests/startup-home-repo.test.ts +34 -0
  182. package/tests/support/orchestrator-modules.ts +1 -0
  183. package/tests/task-size-routing-contract.test.ts +69 -9
  184. package/tests/test-scratch-contract.test.ts +102 -0
  185. package/tests/test-strength-contract.test.ts +36 -1
  186. package/tests/tool-batching-contract.test.ts +64 -0
  187. package/tests/verification-proportionality.test.ts +46 -0
  188. package/tests/verifier-admission.test.ts +137 -0
  189. package/tests/verify-agent-contract.test.ts +8 -8
  190. package/tests/windows-hidden-processes.test.ts +5 -0
  191. package/tests/writer-edit-surface-scope.test.ts +3 -0
  192. package/tests/writer-feature-document-admission.test.ts +156 -0
  193. package/tests/yolo-mode.test.ts +18 -0
  194. package/tests/yolo-temp-scratch.test.ts +71 -0
  195. package/scripts/mirror-odd-routing.mjs +0 -242
  196. /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/orchestration/pi.md +0 -0
  197. /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/schemas/lens.schema.json +0 -0
  198. /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/schemas/refuter.schema.json +0 -0
  199. /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/schemas/targeted-validator.schema.json +0 -0
  200. /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/vectors/lens.json +0 -0
  201. /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/vectors/refuter.json +0 -0
  202. /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/vectors/targeted-validator.json +0 -0
@@ -48,7 +48,11 @@ test("applicability, fallback and honest evidence flow through ODD actors", () =
48
48
  test("retired SDD routes and assets are absent while ODD entry and generic workers remain", () => {
49
49
  const core = read("assets/orchestrator.md");
50
50
  const delegation = readDelegationDetail();
51
- assert.match(core, /ODD \(Default Workflow, harness section above\) is mandatory on every request/);
51
+ // S11 T3: deduplicated. The core's "ODD (Default Workflow, harness section
52
+ // above) is mandatory on every request" restated the harness; the mandatory
53
+ // ODD entry is stated once, in the harness that renders above the core.
54
+ assert.match(read("extensions/gentle-ai.ts"), /Default workflow: Organic Driven Development \(MANDATORY\)\nOrganic Driven Development \(ODD\) is the predefined workflow of this orchestrator\. Every request enters it/);
55
+ assert.doesNotMatch(core, /ODD \(Default Workflow, harness section above\)/);
52
56
  assert.match(delegation, /generic writer chain is unavailable/);
53
57
  for (const path of [
54
58
  "assets/agents/gentle-ai-worker.md",
@@ -1,46 +1,36 @@
1
1
  import assert from "node:assert/strict";
2
- import { existsSync, readFileSync } from "node:fs";
2
+ import { readFileSync } from "node:fs";
3
3
  import { join } from "node:path";
4
4
  import test from "node:test";
5
- import {
6
- ODD_ROUTING_BLOCK_PREFIX,
7
- ODD_ROUTING_FIXTURE_RELATIVE,
8
- ODD_ROUTING_SOURCE_PATH,
9
- ODD_ROUTING_SOURCE_REPO,
10
- assertCleanGentleAiCheckout,
11
- parseOddRoutingFixture,
12
- renderOddRoutingFixture,
13
- resolveOddRoutingProvenance,
14
- sha256Hex,
15
- } from "../scripts/mirror-odd-routing.mjs";
5
+ import { __testing } from "../extensions/gentle-ai.ts";
6
+ import { PI_RUNTIME_IDENTITY, ROUTING_REQUIRED_SINCE_MINOR, verifyProviderContractBundleTree } from "../lib/provider-contract-bundle.ts";
7
+ import { PROVIDER_CONTRACT_LOCK_FILE, PROVIDER_CONTRACT_MIRROR_ROOT } from "../scripts/mirror-provider-contract.mjs";
16
8
 
17
9
  // ---------------------------------------------------------------------------
18
10
  // ODD routing drift ratchet (gentle-pi#1147 follow-up)
19
11
  //
20
- // gentle-pi hand-mirrors the always-on ODD routing block rendered by gentle-ai
21
- // `internal/components/agentguidance/routing.go` (RenderRouting). There is no
22
- // automated sync, so a canonical change leaves the pi mirror stale with nothing
23
- // catching it. This ratchet vendored-snapshots the canonical block into
24
- // `fixtures/odd-routing-canonical.md` (regenerated via `npm run
25
- // mirror:odd-routing`) and fails when a mandatory-delegation clause is dropped
26
- // either from the canonical fixture or from a pi mirror surface.
27
- //
28
- // Regeneration writes ONLY the fixture; this ratchet never auto-rewrites mirror
29
- // assets, so drift stays visible as a deliberate diff.
12
+ // gentle-ai renders the always-on ODD routing block for Pi
13
+ // (`internal/components/agentguidance/routing.go`) and publishes it in the
14
+ // provider contract bundle as `routing/pi.md` (contract 1.4.0). The mirrored
15
+ // bundle (`contracts/review-provider-contract-mirror/`, refreshed with
16
+ // `scripts/mirror-provider-contract.mjs`) is the canonical reference: the
17
+ // harness ODD steps are rendered from it, and this ratchet fails when a
18
+ // mandatory-delegation clause is dropped either from the canonical block or
19
+ // from a Pi surface that restates it (the `assets/orchestrator*.md` modules
20
+ // stay Pi's own and keep their anchors).
30
21
  // ---------------------------------------------------------------------------
31
22
 
32
23
  const REPO_ROOT = join(import.meta.dirname, "..");
33
- const FIXTURE_PATH = join(REPO_ROOT, ...ODD_ROUTING_FIXTURE_RELATIVE.split("/"));
24
+ const MIRROR_ROOT = join(REPO_ROOT, ...PROVIDER_CONTRACT_MIRROR_ROOT.split("/"));
34
25
  const DELEGATION_PATH = join(REPO_ROOT, "assets", "orchestrator-delegation.md");
35
26
  const CORE_PATH = join(REPO_ROOT, "assets", "orchestrator.md");
36
- const EXTENSION_PATH = join(REPO_ROOT, "extensions", "gentle-ai.ts");
37
27
 
38
28
  const readRepo = (absolutePath: string): string => readFileSync(absolutePath, "utf8");
39
29
 
40
30
  // A mandatory-delegation semantic anchor: the clause the canonical block must
41
31
  // still carry, plus the wording each pi mirror surface is expected to carry.
42
- // A canonical change that drops `canonical` is caught here on regeneration
43
- // because the fixture no longer carries it.
32
+ // A canonical change that drops `canonical` is caught here once the bundle is re-mirrored
33
+ // because the mirrored routing block no longer carries it.
44
34
  interface RoutingAnchor {
45
35
  label: string;
46
36
  canonical: string;
@@ -49,23 +39,33 @@ interface RoutingAnchor {
49
39
 
50
40
  const DELEGATION = "assets/orchestrator-delegation.md";
51
41
  const CORE = "assets/orchestrator.md";
52
- const EXTENSION = "extensions/gentle-ai.ts";
42
+ // The harness prompt rendered by extensions/gentle-ai.ts, including the ODD
43
+ // steps it takes from the mirrored routing/pi.md.
44
+ const EXTENSION = "rendered harness prompt";
53
45
 
54
46
  const ANCHORS: readonly RoutingAnchor[] = [
55
47
  {
56
- label: "mandatory delegation triggers heading",
57
- canonical: "### Mandatory Delegation Triggers",
48
+ // S9 canon change (gentle-ai 87a3b2eb): the heading dropped "Mandatory";
49
+ // a trigger is mandatory only once its condition truly holds.
50
+ label: "delegation triggers heading",
51
+ canonical: "### Delegation Triggers",
58
52
  mirrors: [
59
- { surface: DELEGATION, includes: "#### Mandatory Delegation Triggers" },
60
- { surface: CORE, includes: "Mandatory Delegation Triggers" },
53
+ { surface: DELEGATION, includes: "#### Delegation Triggers" },
54
+ // Writer-rule fix: the core names them "Delegation Triggers" and says
55
+ // each holds only when its condition truly holds; the lazy port keeps
56
+ // the canonical heading.
57
+ { surface: CORE, includes: "Delegation Triggers — each mechanism turns on only when its own trigger condition truly holds" },
61
58
  ],
62
59
  },
63
60
  {
64
- label: "triggers are mandatory, not advisory",
65
- canonical: "These triggers are mandatory, not advisory.",
61
+ // S9 canon change: "These triggers are mandatory, not advisory." read as a
62
+ // default to delegate (L8); a trigger now holds only when its condition
63
+ // truly holds, none by default, inline when in doubt.
64
+ label: "triggers hold only when their condition truly holds",
65
+ canonical: "Each trigger holds only when its condition truly holds, and only then is it mandatory, not advisory; no trigger holds by default, and when in doubt work inline.",
66
66
  mirrors: [
67
- { surface: DELEGATION, includes: "These triggers are mandatory, not advisory." },
68
- { surface: EXTENSION, includes: "These triggers are mandatory, not advisory" },
67
+ { surface: DELEGATION, includes: "Each trigger holds only when its condition truly holds, and only then is it mandatory, not advisory; no trigger holds by default, and when in doubt work inline." },
68
+ { surface: EXTENSION, includes: "No trigger holds by default and, when in doubt, work inline" },
69
69
  ],
70
70
  },
71
71
  {
@@ -94,17 +94,64 @@ const ANCHORS: readonly RoutingAnchor[] = [
94
94
  ],
95
95
  },
96
96
  {
97
- label: "writer trigger fires only for a large task, never by file count",
98
- canonical: "**Writer trigger:** a large task delegates one bounded writer per task; file count never fires this trigger",
99
- // Gentle Shell intentionally leads the canon here (gentle-shell#1731): the
100
- // writer fires on named reasons (parallelism, context), never on size
101
- // alone; T24 turned the price-ratio reason off. The canon port is G1 in
102
- // odd/tasks/delegate-for-reason.md.
97
+ // S9 canon change: exploration never reads files an inline write reads anyway.
98
+ label: "never explore to read files an inline write reads anyway",
99
+ canonical: "Never explore to read files you will read anyway before writing inline.",
100
+ mirrors: [
101
+ { surface: DELEGATION, includes: "Never explore to read files you will read anyway before writing inline." },
102
+ { surface: CORE, includes: "never for reading before an inline write" },
103
+ ],
104
+ },
105
+ {
106
+ label: "writer trigger fires only for a named reason, never by size or file count",
107
+ // S9 canon change (gentle-ai 87a3b2eb): the canon caught up with Gentle
108
+ // Shell (gentle-shell#1731): the writer fires on named reasons
109
+ // (parallelism, context), never on size alone or a price ratio.
110
+ canonical: "**Writer trigger:** a writer is delegated only for a named reason, one bounded writer per unit",
103
111
  mirrors: [
104
112
  { surface: DELEGATION, includes: "**Writer trigger (Writer rule):** a large task alone never delegates, and file count never fires this trigger" },
105
113
  { surface: CORE, includes: "**Writer rule** — never by file count or a large task alone" },
106
114
  ],
107
115
  },
116
+ {
117
+ // S9 canon change: a writer unit is one independent behavior with its
118
+ // code, tests, and docs together, never split across writers.
119
+ label: "a writer unit is one independent behavior",
120
+ canonical: "A unit is one independent piece of behavior with its code, tests, and docs together",
121
+ mirrors: [
122
+ { surface: DELEGATION, includes: "A unit is one independent piece of behavior with its code, tests, and docs together" },
123
+ { surface: CORE, includes: "Tests, docs, and code of the same behavior are one unit, never separate writers" },
124
+ ],
125
+ },
126
+ {
127
+ // S9 canon change: a unit qualifies only with several minutes of work,
128
+ // because a subagent spends one to two minutes reading before it writes.
129
+ label: "a writer unit needs several minutes of work",
130
+ canonical: "several files or roughly 100+ changed lines",
131
+ mirrors: [
132
+ { surface: DELEGATION, includes: "several files or ~100+ changed lines; a subagent spends 1-2 minutes reading before it writes" },
133
+ { surface: CORE, includes: "several files or roughly 100+ changed lines" },
134
+ ],
135
+ },
136
+ {
137
+ // S9 canon change (gentle-ai c3763aa7, C3): whoever writes reuses the
138
+ // existing shared-state helper and keeps its invariants.
139
+ label: "writers reuse the existing shared-state function",
140
+ canonical: "reuses the existing function for shared state",
141
+ mirrors: [
142
+ { surface: DELEGATION, includes: "reuses the existing function for shared state and keeps its invariants (ordering, uniqueness, validation, normalization)" },
143
+ ],
144
+ },
145
+ {
146
+ // S9 canon change (gentle-ai c3763aa7, C8): PRESERVE diffs each existing
147
+ // command that shares the changed code against the previous code.
148
+ label: "differential check against the previous code",
149
+ canonical: "run a differential check",
150
+ mirrors: [
151
+ { surface: DELEGATION, includes: "**Differential check (PRESERVE):**" },
152
+ { surface: EXTENSION, includes: "run a differential check" },
153
+ ],
154
+ },
108
155
  {
109
156
  label: "task size decides the route (gentle-shell#1494)",
110
157
  canonical: "### Task Size",
@@ -114,11 +161,14 @@ const ANCHORS: readonly RoutingAnchor[] = [
114
161
  ],
115
162
  },
116
163
  {
117
- label: "verification trigger is reserved for high risk",
118
- canonical: "**Verification trigger:** a high-risk change gets an independent verifier",
164
+ label: "verification trigger is reserved for a named high-risk item",
165
+ // S9 canon change (gentle-ai 6b9a71ef): only a change whose Risk line
166
+ // names a high-risk item gets the independent verifier.
167
+ canonical: "**Verification trigger:** only a change whose Risk line names a high-risk item gets an independent verifier",
119
168
  mirrors: [
120
- { surface: CORE, includes: "**Verification rule** — high risk → independent `gentle-ai-verify`" },
121
- { surface: DELEGATION, includes: "a high-risk change (Task Size) gets an independent `gentle-ai-verify` run" },
169
+ // Verification by named risk: the core row names the escalate-only verifier.
170
+ { surface: CORE, includes: "**Verification rule** — only a named high-risk item (assess `escalate`) → independent `gentle-ai-verify`" },
171
+ { surface: DELEGATION, includes: "only a change whose Risk line names a high-risk item (Task Size, assess `escalate`) gets an independent `gentle-ai-verify` run" },
122
172
  ],
123
173
  },
124
174
  {
@@ -138,10 +188,12 @@ const ANCHORS: readonly RoutingAnchor[] = [
138
188
  label: "long-session backstop",
139
189
  canonical: "**Long-session backstop:**",
140
190
  // Gentle Shell intentionally leads the canon here: a parent-context token
141
- // backstop replaces the tool-call count (tracked by gentle-ai#5139).
191
+ // backstop replaces the tool-call count (tracked by gentle-ai#5139), and the
192
+ // runtime measures it and signals it instead of the canon's advisory ~150k
193
+ // estimate (harness roadmap R1), so the Pi anchor names the runtime signal.
142
194
  mirrors: [
143
- { surface: DELEGATION, includes: "**Context backstop:** when the parent context passes ~150k tokens" },
144
- { surface: CORE, includes: "**Context backstop** — parent context past ~150k tokens" },
195
+ { surface: DELEGATION, includes: "**Context backstop:** when the runtime signals it" },
196
+ { surface: CORE, includes: "**Context backstop** — when the runtime signals it" },
145
197
  ],
146
198
  },
147
199
  {
@@ -173,58 +225,52 @@ const ANCHORS: readonly RoutingAnchor[] = [
173
225
  ],
174
226
  },
175
227
  {
176
- label: "ODD step 6 honors its mandatory delegation triggers",
177
- canonical: "honoring its mandatory delegation triggers",
178
- mirrors: [{ surface: EXTENSION, includes: "honoring its mandatory delegation triggers" }],
228
+ // S9 canon change: step 6 honors its triggers only when their conditions truly hold.
229
+ label: "ODD step 6 honors its delegation triggers when their conditions hold",
230
+ canonical: "honoring its delegation triggers when their conditions truly hold",
231
+ mirrors: [{ surface: EXTENSION, includes: "honoring its delegation triggers when their conditions truly hold" }],
179
232
  },
180
233
  ];
181
234
 
182
235
  // Condensed trigger rows that live only in the always-on core prompt; the
183
236
  // canonical RenderRouting block does not carry the incident or verification
184
- // rows, so they are mirror-only and not fixture-derived anchors.
237
+ // rows, so they are mirror-only and not anchors of the canonical routing block.
185
238
  const CORE_ONLY_TRIGGERS = [
186
239
  "**Incident rule** — diagnose wrong cwd/worktree/git/tooling incidents separately",
187
- "**Verification rule** — high risk → independent `gentle-ai-verify`",
240
+ "**Verification rule** — only a named high-risk item (assess `escalate`) → independent `gentle-ai-verify`",
188
241
  ] as const;
189
242
 
190
- function fixtureBody(): string {
191
- assert.ok(
192
- existsSync(FIXTURE_PATH),
193
- `missing canonical fixture ${ODD_ROUTING_FIXTURE_RELATIVE}; regenerate it with \`npm run mirror:odd-routing\``,
194
- );
195
- return parseOddRoutingFixture(readRepo(FIXTURE_PATH)).body;
243
+ // The verified mirrored routing/pi.md: the bundle tree must pass the strict
244
+ // decoder, so a hand edit to the canonical block fails here as well as in
245
+ // `pnpm run check:provider-contract`.
246
+ function canonicalRoutingBlock(): string {
247
+ const lock = JSON.parse(readFileSync(join(MIRROR_ROOT, PROVIDER_CONTRACT_LOCK_FILE), "utf8")) as { contract_semver: string };
248
+ const bundle = verifyProviderContractBundleTree(join(MIRROR_ROOT, `v${lock.contract_semver}`, "bundle"));
249
+ const block = bundle.routing.get(PI_RUNTIME_IDENTITY);
250
+ assert.ok(block !== undefined, `the mirrored provider contract ${bundle.contractSemver} carries no routing/${PI_RUNTIME_IDENTITY}.md`);
251
+ return block;
196
252
  }
197
253
 
198
254
  // ---------------------------------------------------------------------------
199
- // 1 — Fixture provenance readback
255
+ // 1 — The canonical reference is the verified mirrored bundle
200
256
  // ---------------------------------------------------------------------------
201
257
 
202
- test("the canonical routing fixture exists with managed provenance (source repo, commit, digest)", () => {
203
- assert.ok(
204
- existsSync(FIXTURE_PATH),
205
- `missing canonical fixture ${ODD_ROUTING_FIXTURE_RELATIVE}; regenerate it with \`npm run mirror:odd-routing\``,
206
- );
207
- const { header } = parseOddRoutingFixture(readRepo(FIXTURE_PATH));
208
- assert.equal(header.source_repo, ODD_ROUTING_SOURCE_REPO);
209
- assert.equal(header.source_path, ODD_ROUTING_SOURCE_PATH);
210
- assert.match(header.source_commit ?? "", /^[0-9a-f]{40}$/, "fixture must record the gentle-ai source commit");
211
- assert.match(header.generated_at ?? "", /^\d{4}-\d{2}-\d{2}T/, "fixture must record a generation date");
212
- assert.match(header.block_sha256 ?? "", /^[0-9a-f]{64}$/, "fixture must record the block body digest");
213
- });
214
-
215
- test("the fixture digest matches its block body byte-for-byte", () => {
216
- const { header, body } = parseOddRoutingFixture(readRepo(FIXTURE_PATH));
217
- assert.equal(header.block_sha256, sha256Hex(body), "the fixture body was hand-edited without regenerating its digest");
258
+ test("the canonical routing reference is the verified mirrored routing/pi.md (contract >= 1.4.0)", () => {
259
+ const lock = JSON.parse(readFileSync(join(MIRROR_ROOT, PROVIDER_CONTRACT_LOCK_FILE), "utf8")) as { contract_semver: string; entries: Record<string, string> };
260
+ const [major, minor] = lock.contract_semver.split(".").map(Number);
261
+ assert.ok(major === 1 && (minor ?? 0) >= ROUTING_REQUIRED_SINCE_MINOR, `mirrored contract ${lock.contract_semver} predates routing`);
262
+ assert.match(lock.entries[`routing/${PI_RUNTIME_IDENTITY}.md`] ?? "", /^[0-9a-f]{64}$/);
263
+ assert.ok(canonicalRoutingBlock().startsWith("## Implementation Routing\n"));
218
264
  });
219
265
 
220
266
  // ---------------------------------------------------------------------------
221
- // 2 — Canonical fixture carries every mandatory anchor
267
+ // 2 — The canonical block carries every mandatory anchor
222
268
  // ---------------------------------------------------------------------------
223
269
 
224
- test("the canonical fixture carries every mandatory-delegation anchor", () => {
225
- const body = fixtureBody();
270
+ test("the canonical routing block carries every mandatory-delegation anchor", () => {
271
+ const body = canonicalRoutingBlock();
226
272
  for (const anchor of ANCHORS) {
227
- assert.ok(body.includes(anchor.canonical), `canonical fixture dropped anchor: ${anchor.label}`);
273
+ assert.ok(body.includes(anchor.canonical), `canonical routing block dropped anchor: ${anchor.label}`);
228
274
  }
229
275
  });
230
276
 
@@ -236,7 +282,7 @@ test("the pi mirror surfaces carry every mapped mandatory-delegation anchor", ()
236
282
  const surfaces: Record<string, string> = {
237
283
  [DELEGATION]: readRepo(DELEGATION_PATH),
238
284
  [CORE]: readRepo(CORE_PATH),
239
- [EXTENSION]: readRepo(EXTENSION_PATH),
285
+ [EXTENSION]: __testing.buildGentlePrompt("gentleman"),
240
286
  };
241
287
  for (const anchor of ANCHORS) {
242
288
  for (const mirror of anchor.mirrors) {
@@ -256,84 +302,3 @@ test("the always-on core prompt carries the condensed incident and verification
256
302
  assert.ok(core.includes(row), `assets/orchestrator.md is missing condensed trigger row: ${JSON.stringify(row)}`);
257
303
  }
258
304
  });
259
-
260
- // ---------------------------------------------------------------------------
261
- // 4 — Regeneration provenance: idempotent and fail-closed
262
- // ---------------------------------------------------------------------------
263
-
264
- // The fixture header claims provenance from the source commit, so regeneration
265
- // must be byte-reproducible: the same gentle-ai commit must not produce a
266
- // date-only diff. `generated_at` therefore tracks the source commit's committer
267
- // date instead of wall-clock time.
268
- test("fixture rendering is idempotent and derives generated_at from the source commit", () => {
269
- const sourceCommit = "e7729359fd9d6cb691ed2a88e8f72b1372f7c92e";
270
- const committerDate = "2026-09-18T13:49:03+02:00";
271
- const fakeGit = (args: readonly string[]): string => {
272
- switch (args[0]) {
273
- case "status":
274
- return "";
275
- case "rev-parse":
276
- return `${sourceCommit}\n`;
277
- case "show":
278
- return `${committerDate}\n`;
279
- default:
280
- throw new Error(`unexpected git invocation: ${args.join(" ")}`);
281
- }
282
- };
283
- const block = `${ODD_ROUTING_BLOCK_PREFIX}\n\n- a canonical clause\n`;
284
- const first = renderOddRoutingFixture(block, resolveOddRoutingProvenance("/fake/gentle-ai", fakeGit));
285
- const second = renderOddRoutingFixture(block, resolveOddRoutingProvenance("/fake/gentle-ai", fakeGit));
286
- assert.equal(first, second, "rendering the same source commit twice must be byte-identical");
287
- const { header } = parseOddRoutingFixture(first);
288
- assert.equal(header.source_commit, sourceCommit);
289
- assert.equal(
290
- header.generated_at,
291
- committerDate,
292
- "generated_at must track the source commit's committer date, not wall-clock time",
293
- );
294
- });
295
-
296
- // A dirty checkout would render uncommitted content under the committed
297
- // provenance claim, so the mirror fails closed before rendering and names the
298
- // tracked paths. Untracked files (the odd/tasks notes) must never block.
299
- test("the mirror fails closed on a dirty source checkout and names the tracked paths", () => {
300
- const recordedCalls: string[][] = [];
301
- const cleanGit = (args: readonly string[]): string => {
302
- recordedCalls.push([...args]);
303
- switch (args[0]) {
304
- case "status":
305
- return "";
306
- case "rev-parse":
307
- return `${"a".repeat(40)}\n`;
308
- case "show":
309
- return "2026-09-18T13:49:03+02:00\n";
310
- default:
311
- throw new Error(`unexpected git invocation: ${args.join(" ")}`);
312
- }
313
- };
314
- resolveOddRoutingProvenance("/fake/gentle-ai", cleanGit);
315
- assert.deepEqual(
316
- recordedCalls.find((args) => args[0] === "status"),
317
- ["status", "--porcelain", "--untracked-files=no"],
318
- "the dirty guard must ignore untracked files",
319
- );
320
-
321
- assert.doesNotThrow(() => assertCleanGentleAiCheckout(""));
322
- const dirtyGit = (args: readonly string[]): string => {
323
- if (args[0] === "status") {
324
- return " M internal/components/agentguidance/routing.go\n M scripts/other.mjs\n";
325
- }
326
- throw new Error(`unexpected git invocation: ${args.join(" ")}`);
327
- };
328
- assert.throws(
329
- () => resolveOddRoutingProvenance("/fake/gentle-ai", dirtyGit),
330
- (error: unknown) => {
331
- assert.ok(error instanceof Error);
332
- assert.match(
333
- error.message,
334
- /gentle-ai checkout is dirty; provenance would be unverifiable: internal\/components\/agentguidance\/routing\.go, scripts\/other\.mjs\. Commit or stash first\./,
335
- );
336
- return true;
337
- },
338
- );
339
- });