gentle-pi 4.1.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 (249) hide show
  1. package/README.md +352 -128
  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/engram-guard +1 -1
  12. package/bin/gentle-shell.mjs +188 -35
  13. package/contracts/review-provider-contract-mirror/provider-contract.lock.json +8 -7
  14. package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/README.md +7 -0
  15. package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/manifest.json +11 -2
  16. package/contracts/review-provider-contract-mirror/v1.4.0/bundle/routing/pi.md +92 -0
  17. package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/generated/provider-capabilities.baseline.json +8 -1
  18. package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/generated/provider-roles.baseline.json +1 -1
  19. package/docs/assets/diagrams/agent-orchestration.svg +54 -15
  20. package/docs/assets/diagrams/gentleman-workflow.svg +69 -11
  21. package/docs/assets/diagrams/measured-routing.svg +49 -0
  22. package/docs/assets/diagrams/native-review.svg +52 -12
  23. package/docs/assets/diagrams/odd-workflow.svg +128 -66
  24. package/docs/assets/diagrams/permission-modes.svg +51 -0
  25. package/docs/assets/features/audio-notifications.png +0 -0
  26. package/docs/assets/features/command-palette.png +0 -0
  27. package/docs/auto-permission-answers.md +25 -0
  28. package/docs/auto-permission-evidence.md +39 -0
  29. package/docs/bundled-install.md +40 -8
  30. package/docs/delegated-verification.md +10 -8
  31. package/docs/gentle-agents-activity.md +11 -5
  32. package/docs/gentle-shell-uninstall.md +8 -1
  33. package/docs/gentle-shell.md +20 -12
  34. package/docs/install-wizard.md +17 -10
  35. package/docs/permission-modes.md +51 -6
  36. package/docs/readme-reference.md +155 -31
  37. package/docs/session-profile-format.md +39 -0
  38. package/docs/yolo-mode.md +21 -7
  39. package/extensions/ask-user-choice.ts +4 -0
  40. package/extensions/ask-user-question.ts +6 -1
  41. package/extensions/codegraph-tools.ts +68 -6
  42. package/extensions/gentle-agents.ts +146 -28
  43. package/extensions/gentle-ai.ts +380 -68
  44. package/extensions/gentle-herdr-activity.ts +2 -3
  45. package/extensions/gentle-shell.ts +53 -43
  46. package/extensions/gentle-todo.ts +62 -18
  47. package/extensions/quiet-tools.ts +1 -1
  48. package/extensions/session-scratch.ts +365 -0
  49. package/extensions/startup-banner.ts +2 -3
  50. package/lib/agent-home.ts +8 -3
  51. package/lib/agents-protocol.ts +3 -0
  52. package/lib/agents-runner.ts +55 -6
  53. package/lib/agents-view.ts +3 -1
  54. package/lib/auto-permission-answers.ts +133 -0
  55. package/lib/auto-permission-classifier.ts +215 -36
  56. package/lib/auto-permission-evidence.ts +88 -0
  57. package/lib/auto-permission-memory.ts +69 -0
  58. package/lib/auto-permission-runtime.ts +251 -35
  59. package/lib/auto-permission-tool-context.ts +61 -0
  60. package/lib/bounded-writer-admission.ts +51 -0
  61. package/lib/command-palette-catalog.ts +0 -4
  62. package/lib/commit-slice-signal.ts +100 -0
  63. package/lib/context-backstop.ts +84 -0
  64. package/lib/delivered-test-lint.ts +80 -0
  65. package/lib/destructive-command-guard.ts +215 -28
  66. package/lib/gauge-theme.ts +146 -0
  67. package/lib/gentle-ai-binary.ts +28 -7
  68. package/lib/gentle-shell-bundled.ts +62 -8
  69. package/lib/gentle-shell-import-pi.ts +233 -0
  70. package/lib/gentle-shell-launcher.ts +146 -12
  71. package/lib/gentle-shell-uninstall.ts +18 -1
  72. package/lib/herdr-activity.ts +24 -0
  73. package/lib/model-routing-authority.ts +6 -1
  74. package/lib/native-review-cli.ts +9 -1
  75. package/lib/orchestrator-discovery.ts +5 -1
  76. package/lib/orchestrator-presence.ts +9 -0
  77. package/lib/orchestrator-scope.ts +21 -3
  78. package/lib/provider-contract-bundle.ts +99 -53
  79. package/lib/review-candidate-view-owner.ts +29 -19
  80. package/lib/review-candidate-view.ts +36 -2
  81. package/lib/review-risk-assessment.ts +43 -38
  82. package/lib/review-session-standing-permission.ts +3 -1
  83. package/lib/selection-engine.ts +4 -1
  84. package/lib/session-profile-freeze.ts +40 -16
  85. package/lib/session-profile-persistence.ts +187 -0
  86. package/lib/session-scratch.ts +474 -0
  87. package/lib/shell-bar.ts +25 -25
  88. package/lib/shell-changes-view.ts +80 -13
  89. package/lib/shell-changes.ts +2 -0
  90. package/lib/shell-gauge.ts +49 -6
  91. package/lib/shell-prompt.ts +6 -4
  92. package/lib/shell-todo.ts +39 -8
  93. package/lib/shell-usage-view.ts +5 -1
  94. package/lib/shell-usage.ts +7 -7
  95. package/lib/verifier-admission.ts +29 -0
  96. package/lib/vim-editor-adapter.ts +22 -4
  97. package/lib/yolo-temp-scratch.ts +69 -0
  98. package/package.json +5 -6
  99. package/runtime/gentle-ai-binary.mjs +28 -7
  100. package/runtime/gentle-shell-bundled.mjs +61 -7
  101. package/runtime/gentle-shell-import-pi.mjs +234 -0
  102. package/runtime/gentle-shell-launcher.mjs +144 -10
  103. package/runtime/gentle-shell-uninstall.mjs +18 -1
  104. package/runtime/native-review-cli.mjs +8 -0
  105. package/runtime/review-risk-assessment.mjs +43 -38
  106. package/scripts/build-runtime-modules.mjs +1 -0
  107. package/scripts/bundled-engram-migration.mjs +125 -0
  108. package/scripts/gentle-ai-installer.mjs +10 -10
  109. package/scripts/install-wizard-preview.mjs +2 -2
  110. package/scripts/installer-downloads.mjs +7 -7
  111. package/scripts/installer-preflight.mjs +1 -1
  112. package/scripts/installer-server.mjs +14 -4
  113. package/scripts/main-channel.mjs +3 -2
  114. package/scripts/pi-update.mjs +223 -0
  115. package/scripts/run-test-suite.mjs +5 -0
  116. package/scripts/verify-package-files.mjs +19 -13
  117. package/skills/gentle-ai/SKILL.md +3 -3
  118. package/skills/work-unit-commits/SKILL.md +5 -3
  119. package/tests/agent-risk-escalation.test.ts +37 -10
  120. package/tests/agents-runner.test.ts +260 -3
  121. package/tests/append-system-prompt-route.test.ts +18 -1
  122. package/tests/auto-mode-human-answers.test.ts +68 -0
  123. package/tests/auto-mode-invariants.test.ts +23 -5
  124. package/tests/auto-mode-sdk.test.ts +29 -2
  125. package/tests/auto-permission-answers.test.ts +170 -0
  126. package/tests/auto-permission-classifier.test.ts +353 -2
  127. package/tests/auto-permission-compound.test.ts +24 -0
  128. package/tests/auto-permission-evidence.test.ts +88 -0
  129. package/tests/auto-permission-memory.test.ts +46 -0
  130. package/tests/auto-permission-public-regressions.test.ts +111 -0
  131. package/tests/auto-permission-runtime.test.ts +270 -12
  132. package/tests/autonomous-guard.test.ts +208 -16
  133. package/tests/bounded-writer-admission.test.ts +11 -3
  134. package/tests/bundled-install.test.ts +3 -3
  135. package/tests/card-content-policy.test.ts +19 -9
  136. package/tests/child-safety.test.ts +39 -0
  137. package/tests/codegraph-tools.test.ts +123 -2
  138. package/tests/command-palette.test.ts +14 -4
  139. package/tests/commit-slice-signal.test.ts +162 -0
  140. package/tests/context-backstop.test.ts +159 -0
  141. package/tests/delegate-for-reason-contract.test.ts +91 -24
  142. package/tests/delivered-test-lint.test.ts +105 -0
  143. package/tests/destructive-command-guard.test.ts +85 -1
  144. package/tests/engram-safety.test.ts +35 -7
  145. package/tests/fixtures/primary-session-prompt/gentleman.golden.md +143 -0
  146. package/tests/fixtures/provider-contract-bundle/v1.4.0/README.md +29 -0
  147. package/tests/fixtures/provider-contract-bundle/v1.4.0/manifest.json +83 -0
  148. package/tests/fixtures/provider-contract-bundle/v1.4.0/orchestration/pi.md +70 -0
  149. package/tests/fixtures/provider-contract-bundle/v1.4.0/routing/pi.md +92 -0
  150. package/tests/fixtures/provider-contract-bundle/v1.4.0/schemas/lens.schema.json +16 -0
  151. package/tests/fixtures/provider-contract-bundle/v1.4.0/schemas/refuter.schema.json +1 -0
  152. package/tests/fixtures/provider-contract-bundle/v1.4.0/schemas/targeted-validator.schema.json +1 -0
  153. package/tests/fixtures/provider-contract-bundle/v1.4.0/vectors/lens.json +1 -0
  154. package/tests/fixtures/provider-contract-bundle/v1.4.0/vectors/refuter.json +1 -0
  155. package/tests/fixtures/provider-contract-bundle/v1.4.0/vectors/targeted-validator.json +1 -0
  156. package/tests/gauge-theme.test.ts +120 -0
  157. package/tests/gentle-agents.test.ts +443 -8
  158. package/tests/gentle-ai-binary.test.ts +1 -1
  159. package/tests/gentle-ai-child-guards.test.ts +61 -0
  160. package/tests/gentle-ai-doctor-herdr.test.ts +94 -0
  161. package/tests/gentle-ai-installer.test.ts +51 -51
  162. package/tests/gentle-shell-bin.test.ts +180 -17
  163. package/tests/gentle-shell-bundled.test.ts +235 -38
  164. package/tests/gentle-shell-import-pi.test.ts +262 -0
  165. package/tests/gentle-shell-launcher.test.ts +97 -13
  166. package/tests/gentle-shell-uninstall.test.ts +30 -0
  167. package/tests/gentle-shell.test.ts +113 -14
  168. package/tests/gentle-todo.test.ts +258 -27
  169. package/tests/headless-delivery-question.test.ts +58 -0
  170. package/tests/herdr-activity.test.ts +34 -1
  171. package/tests/install-wizard.test.ts +2 -2
  172. package/tests/installer-posix-bootstrap.test.ts +8 -8
  173. package/tests/installer-preflight.test.ts +3 -3
  174. package/tests/installer-probes.test.ts +8 -8
  175. package/tests/installer-runner.test.ts +14 -13
  176. package/tests/installer-server.test.ts +13 -5
  177. package/tests/installer-windows-bootstrap.test.ts +33 -4
  178. package/tests/main-channel-upgrade.test.ts +7 -1
  179. package/tests/native-review-capability-contract.test.ts +7 -1
  180. package/tests/native-review-parity.test.ts +22 -1
  181. package/tests/no-self-pause-contract.test.ts +5 -2
  182. package/tests/odd-integration.test.ts +5 -1
  183. package/tests/odd-routing-canonical-ratchet.test.ts +127 -162
  184. package/tests/odd-routing-contract.test.ts +115 -30
  185. package/tests/odd-runtime-delegation-gate.test.ts +4 -1
  186. package/tests/orchestrator-budget.test.ts +19 -8
  187. package/tests/orchestrator-discovery.test.ts +29 -0
  188. package/tests/orchestrator-rdd-ownership.test.ts +3 -1
  189. package/tests/orchestrator-scope.test.ts +66 -0
  190. package/tests/orchestrator-status-publication-contract.test.ts +12 -8
  191. package/tests/package-manifest.test.ts +28 -24
  192. package/tests/pi-update-launcher.test.ts +106 -0
  193. package/tests/pi-update.test.ts +186 -0
  194. package/tests/primary-session-prompt.test.ts +101 -0
  195. package/tests/provider-contract-bundle.test.ts +118 -0
  196. package/tests/provider-contract-mirror.test.ts +24 -0
  197. package/tests/quiet-tool-rendering.test.ts +23 -0
  198. package/tests/rdd-aware-verification-contract.test.ts +29 -11
  199. package/tests/rdd-status-line.test.ts +131 -0
  200. package/tests/review-agent-end-preflight.test.ts +6 -0
  201. package/tests/review-candidate-view.test.ts +49 -0
  202. package/tests/review-contract-prompt.test.ts +5 -2
  203. package/tests/review-controller-native-routing.test.ts +14 -1
  204. package/tests/review-controller.test.ts +1 -1
  205. package/tests/review-host-relay-routing.test.ts +59 -2
  206. package/tests/review-loadout-rdd-off-sdk.test.ts +237 -0
  207. package/tests/review-owner-identity.test.ts +132 -0
  208. package/tests/review-risk-assessment.test.ts +163 -76
  209. package/tests/review-session-standing-permission-controller.test.ts +14 -1
  210. package/tests/review-session-standing-permission.test.ts +27 -1
  211. package/tests/run-test-suite.test.ts +15 -0
  212. package/tests/runtime-harness.mjs +16 -6
  213. package/tests/selection-engine.test.ts +35 -5
  214. package/tests/session-changes-shell.test.ts +1 -1
  215. package/tests/session-profile-freeze.test.ts +37 -1
  216. package/tests/session-profile-persistence.test.ts +334 -0
  217. package/tests/session-scratch.test.ts +945 -0
  218. package/tests/session-subject-routing-contract.test.ts +2 -1
  219. package/tests/shell-bar.test.ts +7 -0
  220. package/tests/shell-changes-view.test.ts +137 -1
  221. package/tests/shell-changes.test.ts +18 -0
  222. package/tests/shell-gauge.test.ts +42 -0
  223. package/tests/shell-todo.test.ts +15 -12
  224. package/tests/small-task-inline-contract.test.ts +37 -0
  225. package/tests/standalone-config-home.test.ts +324 -0
  226. package/tests/startup-banner.test.ts +5 -4
  227. package/tests/startup-home-repo.test.ts +34 -0
  228. package/tests/support/orchestrator-modules.ts +1 -0
  229. package/tests/task-size-routing-contract.test.ts +69 -9
  230. package/tests/test-scratch-contract.test.ts +102 -0
  231. package/tests/test-strength-contract.test.ts +36 -1
  232. package/tests/tool-batching-contract.test.ts +64 -0
  233. package/tests/verification-proportionality.test.ts +46 -0
  234. package/tests/verifier-admission.test.ts +137 -0
  235. package/tests/verify-agent-contract.test.ts +8 -8
  236. package/tests/vim-editor-adapter.test.ts +41 -15
  237. package/tests/windows-hidden-processes.test.ts +5 -0
  238. package/tests/writer-edit-surface-scope.test.ts +3 -0
  239. package/tests/writer-feature-document-admission.test.ts +156 -0
  240. package/tests/yolo-mode.test.ts +24 -1
  241. package/tests/yolo-temp-scratch.test.ts +71 -0
  242. package/scripts/mirror-odd-routing.mjs +0 -242
  243. /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/orchestration/pi.md +0 -0
  244. /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/schemas/lens.schema.json +0 -0
  245. /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/schemas/refuter.schema.json +0 -0
  246. /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/schemas/targeted-validator.schema.json +0 -0
  247. /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/vectors/lens.json +0 -0
  248. /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/vectors/refuter.json +0 -0
  249. /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/vectors/targeted-validator.json +0 -0
@@ -10,7 +10,9 @@ const read = (path: string) => readFileSync(join(import.meta.dirname, "..", path
10
10
  const core = read("assets/orchestrator.md");
11
11
  const delegation = readDelegationDetail();
12
12
  const memory = read("assets/orchestrator-memory.md");
13
- const wrapper = read("extensions/gentle-ai.ts");
13
+ // The harness as rendered by extensions/gentle-ai.ts, including the ODD steps
14
+ // it takes from the mirrored provider bundle's routing/pi.md.
15
+ const wrapper = __testing.buildGentlePrompt("gentleman");
14
16
 
15
17
  function containsAll(text: string, clauses: readonly string[]): void {
16
18
  for (const clause of clauses) assert.ok(text.includes(clause), `missing contract: ${clause}`);
@@ -19,10 +21,17 @@ function containsAll(text: string, clauses: readonly string[]): void {
19
21
  test("organic entry stays read-only without authorization and loads detail before work", () => {
20
22
  containsAll(core, [
21
23
  "large tasks get ODD tracking and workers only by the Writer rule, else inline",
22
- "ODD (Default Workflow, harness section above) is mandatory on every request",
23
24
  "orchestrator-delegation.md",
24
25
  "orchestrator-memory.md",
25
26
  ]);
27
+ // S11 T3: deduplicated. The core's "ODD (Default Workflow, harness section
28
+ // above) is mandatory on every request" pointer restated the harness; the
29
+ // mandatory workflow and its detail pointers are stated once in the harness.
30
+ containsAll(wrapper, [
31
+ "Default workflow: Organic Driven Development (MANDATORY)",
32
+ "Every request enters it",
33
+ "Detail for steps 3–7: `orchestrator-delegation.md` and `orchestrator-memory.md`.",
34
+ ]);
26
35
  containsAll(delegation, [
27
36
  "Investigation, explanation, review, comparison, and proposal-only requests remain read-only",
28
37
  "without a task or storage permission prompt",
@@ -108,15 +117,45 @@ test("assumption challenge and task checks do not activate or duplicate native r
108
117
  ]);
109
118
  });
110
119
 
111
- test("ODD closes each task with a work-unit commit and reviews the commit or PR slice", () => {
120
+ // S14: commit per reviewable slice. The exact canon sentence for ODD step 6,
121
+ // identical in gentle-ai routing.go and this harness.
122
+ const S14_COMMIT_SENTENCE =
123
+ "Commit work units in reviewable slices of about 400 authored changed lines on the feature branch (branch first when on the default branch), with tests and docs alongside the behavior and a Conventional Commit message; a unit whose Risk line names a high-risk item gets its own commit so native review can take it alone, and the last slice closes the feature. Record the commit that holds each task in the feature document as evidence.";
124
+ const PER_TASK_COMMIT_SENTENCE =
125
+ "Every tracked task closes with at least one work-unit commit on the feature branch, branch first when on the default branch, with tests and docs alongside the behavior, using a Conventional Commit message; record the commit identity in the feature document as evidence.";
126
+
127
+ test("S14: the rendered parent prompt commits per reviewable slice, not per task", () => {
128
+ for (const persona of ["gentleman", "neutral"] as const) {
129
+ const prompt = __testing.buildGentlePrompt(persona);
130
+ assert.equal(prompt.split(S14_COMMIT_SENTENCE).length - 1, 1, `[${persona}] the parent prompt must carry the S14 sentence once`);
131
+ assert.ok(!prompt.includes(PER_TASK_COMMIT_SENTENCE), `[${persona}] the parent prompt keeps the per-task commit sentence`);
132
+ assert.ok(
133
+ prompt.includes(`${S14_COMMIT_SENTENCE} Work-unit commits on the feature branch are part of authorized large ODD implementation;`),
134
+ `[${persona}] step 6 must keep its authorization sentence right after the S14 sentence`,
135
+ );
136
+ }
137
+ });
138
+
139
+ // S14: commit per reviewable slice. Step 6 commits slices of about 400 authored
140
+ // changed lines (a high-risk unit alone) instead of closing every task with a commit.
141
+ test("ODD commits work units in reviewable slices and reviews the commit or PR slice", () => {
112
142
  containsAll(wrapper, [
113
- "Every tracked task closes with at least one work-unit commit on the feature branch, branch first when on the default branch",
114
- "with tests and docs alongside the behavior, using a Conventional Commit message",
115
- "record the commit identity in the feature document as evidence",
143
+ "Commit work units in reviewable slices of about 400 authored changed lines on the feature branch (branch first when on the default branch)",
144
+ "with tests and docs alongside the behavior and a Conventional Commit message",
145
+ "a unit whose Risk line names a high-risk item gets its own commit so native review can take it alone",
146
+ "Record the commit that holds each task in the feature document as evidence",
116
147
  "Work-unit commits on the feature branch are part of authorized large ODD implementation; push, pull request creation, and merge remain the user's decisions",
117
148
  "The native review candidate is a work-unit commit or a PR slice, never a TODO checkbox and never the accumulated feature branch",
118
- "close each tracked task with a work-unit commit",
119
149
  ]);
150
+ // S11 T3: deduplicated. The harness principle that re-listed the ODD steps
151
+ // ("close each tracked task with a work-unit commit") is gone; step 6 above is
152
+ // the single statement. Core Safety allows exactly those commits and no others.
153
+ assert.ok(!wrapper.includes("close each tracked task with a work-unit commit"), "the ODD steps are re-listed outside step 6");
154
+ assert.ok(
155
+ core.includes("- Never commit unless the user explicitly asks, except the work-unit commits authorized large ODD implementation makes on its feature branch (ODD step 6)."),
156
+ "core Safety must allow only the ODD step 6 work-unit commits without an explicit ask",
157
+ );
158
+ assert.ok(!core.includes("- Never commit unless the user explicitly asks."), "core Safety contradicts ODD step 6 work-unit commits");
120
159
  containsAll(delegation, [
121
160
  'after each work-unit commit, assess it with that same call and `{"baseRef":"<last reviewed boundary>","committedOnly":true}`',
122
161
  "Passive/low: silent structural checks, no reviewer or consent ceremony, and the boundary advances",
@@ -124,7 +163,8 @@ test("ODD closes each task with a work-unit commit and reviews the commit or PR
124
163
  "Medium: defer to the PR slice",
125
164
  "bounded by the delivery budget of about 400 authored changed lines",
126
165
  "The first boundary is the branch point, and every reviewed boundary becomes the next base",
127
- "Record per task the assessed tier and outcome: granted, declined, passive, deferred to slice, or unavailable",
166
+ // S14: commit per reviewable slice; one commit may hold several tasks, so outcomes are recorded per commit.
167
+ "Record per commit the assessed tier and outcome: granted, declined, passive, deferred to slice, or unavailable",
128
168
  "Delivery follows work units",
129
169
  "forecast authored changed lines (additions plus deletions, generated files excluded) from the task list",
130
170
  "keep a running count from work-unit commits",
@@ -137,18 +177,28 @@ test("ODD closes each task with a work-unit commit and reviews the commit or PR
137
177
  "never hardcode their paths",
138
178
  "The delivery budget below reads the accumulated branch, not this per-task heuristic",
139
179
  ]);
180
+ assert.ok(!wrapper.includes("Every tracked task closes with at least one work-unit commit"), "step 6 keeps the per-task commit rule");
140
181
  const docs = read("docs/readme-reference.md");
141
182
  containsAll(docs, [
142
- "Every task closes with at least one work-unit commit on the feature branch (branch first when on the default branch)",
143
- "the feature document records the commit identity as evidence",
183
+ "Commit work units in reviewable slices of about 400 authored changed lines on the feature branch (branch first when on the default branch)",
184
+ "a unit whose Risk line names a high-risk item gets its own commit so native review can take it alone",
185
+ "the feature document records the commit that holds each task as evidence",
144
186
  "The native review candidate is a work-unit commit or a PR slice, never a TODO checkbox and never the accumulated feature branch",
145
187
  "**Delivery:**",
146
- "Close task with a work-unit commit",
188
+ "Commit at a slice boundary; a high-risk unit alone",
147
189
  "RDD enabled at work-unit commit boundary",
148
190
  "Running authored lines over 400",
149
191
  "Apply selected delivery strategy",
150
192
  "Single PR; no chain artifacts",
151
193
  ]);
194
+ assert.ok(!docs.includes("Every task closes with at least one work-unit commit"), "docs keep the per-task commit rule");
195
+ const skill = read("skills/work-unit-commits/SKILL.md");
196
+ containsAll(skill, [
197
+ "Committing ODD work in reviewable slices of about 400 authored changed lines.",
198
+ "ODD commits work units in reviewable slices of about 400 authored changed lines, not one commit per task:",
199
+ "A unit whose Risk line names a high-risk item gets its own commit, so native review takes it alone.",
200
+ ]);
201
+ assert.ok(!skill.includes("Every ODD task closes with at least one work-unit commit"), "the skill keeps the per-task commit rule");
152
202
  });
153
203
 
154
204
  test("delivery instructions and docs preserve localized ordered outcomes and override chaining", () => {
@@ -267,13 +317,17 @@ test("ODD defaults to applicable test-first without chat or TUI activation", ()
267
317
  const verify = read("assets/agents/gentle-ai-verify.md");
268
318
  const support = read("assets/support/strict-tdd.md") + read("assets/support/strict-tdd-verify.md");
269
319
  const skill = read("skills/gentle-ai/SKILL.md");
270
- for (const text of [core, delegation, worker, verify, support, skill, wrapper]) {
320
+ // S11 T3: deduplicated. The core sits after the harness in the same system
321
+ // prompt; its pointer sentence restated the harness test-first policy, so the
322
+ // rendered parent prompt (harness + core) now carries that policy exactly
323
+ // once, from the harness, instead of the core carrying its own copy.
324
+ const rendered = __testing.buildGentlePrompt("gentleman");
325
+ for (const text of [rendered, delegation, worker, verify, support, skill, wrapper]) {
271
326
  containsAll(text, ["applicable", "RED", "GREEN"]);
272
327
  assert.doesNotMatch(text, /(?:configured TDD mode|Strict TDD Mode is enabled|explicit user choice|test presence does not enable it|tests existing does not activate it)/i);
273
328
  }
274
- // The core sits after the harness in the same system prompt, so it points at
275
- // the harness test-first policy instead of restating it (gentle-shell#1494).
276
- assert.ok(core.includes("the harness's applicable test-first policy"), "core must point at the harness test-first policy");
329
+ assert.equal(rendered.split("use test-first by default").length - 1, 1, "the parent prompt states the test-first policy once");
330
+ assert.ok(!core.includes("GREEN"), "core restates the harness test-first policy");
277
331
  for (const [actor, text] of [["delegation", delegation], ["extension", wrapper], ["skill", skill]] as const) {
278
332
  assert.match(text, /behavior changes with applicable runnable deterministic tests and a clear expected outcome/i, `${actor} must require applicability, not test presence`);
279
333
  assert.match(text, /passive documentation/i, `${actor} must handle passive docs`);
@@ -287,9 +341,12 @@ test("ODD defaults to applicable test-first without chat or TUI activation", ()
287
341
  assert.doesNotMatch(core + delegation + worker + support + skill + wrapper, /If tests exist, use strict TDD/i);
288
342
  });
289
343
 
290
- test("mandatory delegation triggers are behavioral in the lazy canonical port and the always-on ODD step", () => {
344
+ // S9 canon change (gentle-ai 87a3b2eb): "These triggers are mandatory, not
345
+ // advisory." read as a default to delegate (L8). A trigger is mandatory only
346
+ // once its condition truly holds; none holds by default, inline when in doubt.
347
+ test("delegation triggers are behavioral in the lazy canonical port and the always-on ODD step", () => {
291
348
  for (const clause of [
292
- "These triggers are mandatory, not advisory.",
349
+ "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.",
293
350
  "stop and delegate through the runtime's subagent mechanism before continuing",
294
351
  "executing past a fired trigger inline is a routing defect even if the work succeeds",
295
352
  "**Mapping trigger",
@@ -302,12 +359,15 @@ test("mandatory delegation triggers are behavioral in the lazy canonical port an
302
359
  "so skipped delegation is observable instead of silent",
303
360
  "These triggers only choose between direct inline and delegated direct inside ODD",
304
361
  ]) {
305
- assert.ok(delegation.includes(clause), `lazy canonical port is missing mandatory delegation clause: ${clause}`);
362
+ assert.ok(delegation.includes(clause), `lazy canonical port is missing delegation clause: ${clause}`);
306
363
  }
364
+ assert.ok(!delegation.includes("These triggers are mandatory, not advisory"), "the lazy port must not read the triggers as mandatory by default");
307
365
  assert.ok(
308
- wrapper.includes("honoring its mandatory delegation triggers"),
309
- "the always-on ODD step 6 must honor its mandatory delegation triggers",
366
+ wrapper.includes("honoring its delegation triggers when their conditions truly hold"),
367
+ "the always-on ODD step 6 must honor its delegation triggers only when their conditions truly hold",
310
368
  );
369
+ assert.ok(wrapper.includes("No trigger holds by default and, when in doubt, work inline"), "the always-on ODD step 6 must default to inline");
370
+ assert.ok(!wrapper.includes("These triggers are mandatory, not advisory"), "the always-on ODD step 6 must not read the triggers as mandatory by default");
311
371
  assert.ok(
312
372
  wrapper.includes("executing past a fired trigger inline is a routing defect"),
313
373
  "the always-on ODD protocol must state that skipping a fired trigger is a routing defect",
@@ -363,7 +423,6 @@ test("evidence-budget numbers agree across routing surfaces and retired triggers
363
423
  "~10k tokens",
364
424
  "at most ~2k tokens",
365
425
  "`path:line`",
366
- "~150k tokens",
367
426
  "Context backstop",
368
427
  ]);
369
428
  assert.doesNotMatch(text, /4-file rule|20 tool calls|5 exploratory (?:file )?reads/, `${path} keeps a retired trigger`);
@@ -373,6 +432,20 @@ test("evidence-budget numbers agree across routing surfaces and retired triggers
373
432
  }
374
433
  });
375
434
 
435
+ // Harness roadmap R1: the backstop is a measured runtime signal (min(absolute cap, fraction x window)), so the
436
+ // prompt modules stop stating a fixed token count and only a unit needing several steps is worth delegating.
437
+ test("context backstop prompt text defers to the runtime signal instead of a fixed token count", () => {
438
+ const reference = read("assets/orchestrator-delegation-reference.md");
439
+ for (const [path, text] of Object.entries({ "assets/orchestrator.md": core, "assets/orchestrator-delegation.md": read("assets/orchestrator-delegation.md"), "assets/orchestrator-delegation-reference.md": reference })) {
440
+ assert.doesNotMatch(text, /~?150k tokens/, `${path} still states a fixed backstop`);
441
+ assert.match(text, /\*\*Context backstop(?::\*\*|\*\*:?)[^\n]*when the runtime signals/, `${path} must defer to the runtime signal`);
442
+ assert.ok(text.includes("several steps"), `${path} must say only a unit needing several steps is delegated`);
443
+ }
444
+ const docs = read("docs/readme-reference.md");
445
+ containsAll(docs, ["GENTLE_PI_CONTEXT_BACKSTOP_TOKENS", "GENTLE_PI_CONTEXT_BACKSTOP_FRACTION", "min(", "`getContextUsage()`", "re-armed"]);
446
+ assert.doesNotMatch(docs, /~150k tokens|150,000-token context backstop/, "docs still describe the fixed backstop");
447
+ });
448
+
376
449
  test("ODD protocol is always-on in the rendered system prompt and runs by default", () => {
377
450
  const orderedClauses = [
378
451
  "Default workflow: Organic Driven Development (MANDATORY)",
@@ -406,15 +479,27 @@ test("ODD protocol is always-on in the rendered system prompt and runs by defaul
406
479
  }
407
480
  }
408
481
 
409
- assert.ok(
410
- wrapper.includes("Organic Driven Development (ODD) is the predefined workflow for every request"),
411
- "missing contract: extensions/gentle-ai.ts harness principle",
412
- );
413
- // The parent owns the injected identity; its migration is outside this suite's edit surface.
414
- assert.ok(
415
- core.includes("ODD (Default Workflow, harness section above) is mandatory on every request"),
416
- "missing contract: assets/orchestrator.md pointer sentence",
417
- );
482
+ // S11 T3: deduplicated. The harness principle and the core pointer sentence
483
+ // both restated the mandatory workflow; the steps' own header (asserted in
484
+ // order above as "predefined workflow of this orchestrator") is the one statement.
485
+ for (const persona of ["gentleman", "neutral"] as const) {
486
+ const prompt = __testing.buildGentlePrompt(persona);
487
+ assert.equal(prompt.split("is the predefined workflow").length - 1, 1, `[${persona}] the ODD workflow is declared more than once`);
488
+ assert.ok(!prompt.includes("ODD (Default Workflow, harness section above)"), `[${persona}] the core restates the ODD workflow`);
489
+ }
418
490
  containsAll(core, ["orchestrator-delegation.md", "orchestrator-memory.md"]);
419
491
  assert.doesNotMatch(core + delegation + memory, /SDD|sdd-|OpenSpec|openspec/i);
420
492
  });
493
+
494
+ // S14: commit per reviewable slice. Mirrors gentle-ai 06ad2776: a high-risk unit that follows pending medium commits
495
+ // closes the open slice first, so native review takes the high-risk commit alone; outcomes are recorded per commit,
496
+ // since one commit may hold several tasks.
497
+ test("S14: RDD tracking closes the open slice before a high-risk commit and records outcomes per commit", () => {
498
+ const tracking = readFileSync(join(import.meta.dirname, "..", "assets", "orchestrator-tracking.md"), "utf8");
499
+ assert.ok(
500
+ tracking.includes("When a high-risk unit follows pending medium commits still under the slice budget, close the open slice first, assessing and reviewing it as the slice, so the high-risk commit is then reviewed alone against the new boundary."),
501
+ "tracking must close the open slice before a high-risk commit",
502
+ );
503
+ assert.ok(tracking.includes("Record per commit the assessed tier and outcome"), "tracking must record outcomes per commit");
504
+ assert.ok(!tracking.includes("Record per task the assessed tier and outcome"), "tracking must not record outcomes per task");
505
+ });
@@ -17,7 +17,10 @@ test("a second direct file is not refused based on session write history", async
17
17
  events: { emit() {} }, registerCommand() {}, registerTool() {}, getFlag: () => undefined,
18
18
  getActiveTools: () => ["edit", "write"],
19
19
  } as unknown as ExtensionAPI;
20
- createGentleAiExtension({ nativeReviewCli: null, processEnv: {}, resolveTelemetryTriggerBinary: () => "/usr/bin/true", telemetryTriggerSpawn: (() => undefined) as never })(pi);
20
+ // An isolated config home keeps the host's permission mode (for example Auto)
21
+ // out of a test that only exercises the ODD write gate.
22
+ const processEnv = { GENTLE_PI_CONFIG_HOME: join(cwd, ".config-home") };
23
+ createGentleAiExtension({ nativeReviewCli: null, processEnv, resolveTelemetryTriggerBinary: () => "/usr/bin/true", telemetryTriggerSpawn: (() => undefined) as never })(pi);
21
24
  const ctx = {
22
25
  cwd, hasUI: false, mode: "interactive", ui: { notify() {} },
23
26
  sessionManager: { getSessionId: () => "odd-direct" },
@@ -31,12 +31,15 @@ const REPO_ROOT = join(import.meta.dirname, "..");
31
31
  const REAL_ASSETS_DIR = join(REPO_ROOT, "assets");
32
32
  const FIXTURE_PATH = join(import.meta.dirname, "fixtures", "orchestrator.pre-diet.md");
33
33
  // gentle-shell#1731 T10 (user decision): 8,192 -> 8,400 B for the narrowed high-risk items 1 and 3.
34
- const BUDGET_BYTES = 8400;
34
+ // Verification by named risk (escalate-only rule 3) and the writer-rule fix (triggers hold only when
35
+ // their condition holds; a unit is independent behavior over a size floor), user decision 2026-10-14: 8,400 -> 9,100 B.
36
+ const BUDGET_BYTES = 9100;
35
37
  const MIN_CONTROLLED_LONG_ASSETS_ROOT_CHARS = 93;
36
38
 
37
39
  const LAZY_ASSET_NAMES = [
38
40
  "orchestrator.md",
39
41
  "orchestrator-delegation.md",
42
+ "orchestrator-delegation-reference.md",
40
43
  "orchestrator-memory.md",
41
44
  "orchestrator-skills.md",
42
45
  ] as const;
@@ -123,7 +126,7 @@ function measureOrchestratorPromptBytes(assetsDir: string): number {
123
126
  // measuring a shorter render again.
124
127
  const RDD_WORST_CASE_LINE = "Receipt-driven development: unknown (native status unavailable)";
125
128
 
126
- test("getOrchestratorPrompt return value stays within the canonical 8,400 B budget at a short assets root", () => {
129
+ test("getOrchestratorPrompt return value stays within the canonical 9,100 B budget at a short assets root", () => {
127
130
  const rendered = __testing.renderOrchestratorPrompt(representativeProductionAssetsDir);
128
131
  assert.ok(
129
132
  rendered.includes(RDD_WORST_CASE_LINE),
@@ -303,6 +306,10 @@ const SUPERSEDED_LIFECYCLE_REVIEW_LINES = new Set([
303
306
  154,
304
307
  160,
305
308
  166,
309
+ // 280: S11 T3 made the Safety commit line consistent with ODD step 6: it now
310
+ // excepts the work-unit commits of authorized large ODD implementation
311
+ // (asserted verbatim in tests/odd-routing-contract.test.ts).
312
+ 280,
306
313
  // 282: gentle-shell#1731 T4 relaxed the single-writer Safety line to disjoint
307
314
  // Allowed edit surfaces (runtime-enforced) or isolated worktrees.
308
315
  282,
@@ -363,10 +370,12 @@ test("core-alone: load-bearing direct-delegation tokens remain without lazy unio
363
370
 
364
371
  test("lazy coordination detail retains subject guidance without inflating the core", () => {
365
372
  const core = readRealAsset("orchestrator.md");
366
- const detail = readRealAsset("orchestrator-delegation.md");
373
+ // S11 T2: the card binds on delegation; the session subject rules are reference material.
374
+ const card = readRealAsset("orchestrator-delegation.md");
375
+ const detail = readRealAsset("orchestrator-delegation-reference.md");
367
376
  assert.match(core, /Bind this to the parent Pi session only/);
368
377
  assert.match(core, /orchestrator-delegation\.md/);
369
- assert.match(detail, /on delegation or routing triggers/);
378
+ assert.match(card, /on delegation or routing triggers/);
370
379
  assert.match(detail, /Once a meaningful task subject is clear, before delegation or cross-session coordination, call `orchestrator_session_id`/);
371
380
  assert.match(detail, /short, non-sensitive `subject`/);
372
381
  assert.match(detail, /Batch with setup if possible; no extra model call/);
@@ -384,14 +393,14 @@ test("core-alone: dynamic Gentle AI ownership replaces package lifecycle instruc
384
393
  });
385
394
 
386
395
  test("lazy delegation detail has no native RDD controller markers", () => {
387
- const delegation = readRealAsset("orchestrator-delegation.md");
396
+ const delegation = `${readRealAsset("orchestrator-delegation.md")}\n${readRealAsset("orchestrator-delegation-reference.md")}`;
388
397
  for (const marker of ["next_transition", "review.capture-result", "reconcile-terminal-mirrors"]) {
389
398
  assert.ok(!delegation.includes(marker), `stale RDD marker retained: ${marker}`);
390
399
  }
391
400
  });
392
401
 
393
402
  test("live orchestrator assets remove the stale strong-gate retry contract", () => {
394
- const content = `${readRealAsset("orchestrator.md")}\n${readRealAsset("orchestrator-delegation.md")}`;
403
+ const content = `${readRealAsset("orchestrator.md")}\n${readRealAsset("orchestrator-delegation.md")}\n${readRealAsset("orchestrator-delegation-reference.md")}`;
395
404
  assert.doesNotMatch(content, /strong gate/i);
396
405
  assert.doesNotMatch(content, /extension blocks.*gh pr create/i);
397
406
  assert.doesNotMatch(content, /before the user retries the PR command/i);
@@ -464,14 +473,16 @@ test("every compressed lazy-file pointer in the core still names the material it
464
473
  // byte budget -- each must keep naming what is over there, not collapse
465
474
  // to an opaque "Detail: `file.md`.".
466
475
  const namedPointers: ReadonlyArray<{ file: string; mustName: readonly string[] }> = [
476
+ // S11 T2: this reference material moved out of the delegation card.
467
477
  {
468
- file: "orchestrator-delegation.md",
478
+ file: "orchestrator-delegation-reference.md",
469
479
  mustName: ["Per-action table", "Work Routing Ladder", "Canonical Workflows"],
470
480
  },
471
481
  // gentle-shell#1494 per-mechanism modules.
472
482
  { file: "orchestrator-prompts.md", mustName: ["blocking-prompt relays", "provider defects"] },
473
483
  { file: "orchestrator-tracking.md", mustName: ["Track", "feature document"] },
474
- { file: "orchestrator-verification.md", mustName: ["Verification rule", "high risk"] },
484
+ // Verification by named risk: rule 3 names the escalated high-risk item.
485
+ { file: "orchestrator-verification.md", mustName: ["Verification rule", "high-risk item"] },
475
486
  { file: "orchestrator-writer.md", mustName: ["Writer rule", "large task"] },
476
487
  {
477
488
  file: "orchestrator-memory.md",
@@ -57,6 +57,35 @@ test("published headers retain the frozen legacy schema-1 contract after metadat
57
57
  assert.equal(listPresence(profile).entries.length, 1);
58
58
  assert.equal(discoverOrchestrators(profile, [peer])[0].freshness, "unknown");
59
59
  });
60
+ test("a live peer that predates discovery publication keeps its legacy header label (#2305)", (t) => {
61
+ const profile = fixture(t);
62
+ // A long-lived peer started before discovery publication shipped heartbeats
63
+ // the schema-1 header only and never writes a discovery sidecar.
64
+ const publisher = PresencePublisher.start({ profile, sessionId: "peer", label: "gentle-shell", activity: [] });
65
+ t.after(() => publisher.dispose());
66
+ const [candidate] = discoverOrchestrators(profile, [peer]);
67
+ assert.equal(candidate.freshness, "unknown");
68
+ assert.equal(candidate.legacyLabel, "gentle-shell");
69
+ assert.equal(candidate.workspace, undefined);
70
+ assert.equal(candidate.publication, undefined);
71
+ // An old heartbeat is not a live legacy peer.
72
+ assert.equal(discoverOrchestrators(profile, [peer], Date.now() + 20_000)[0].legacyLabel, undefined);
73
+ });
74
+ test("a present but invalid or foreign discovery sidecar never yields a legacy label", (t) => {
75
+ const profile = fixture(t);
76
+ const publisher = PresencePublisher.start({ profile, sessionId: "peer", label: "gentle-shell", activity: [] });
77
+ t.after(() => publisher.dispose());
78
+ publisher.updateDiscovery(peer, { workspace: "/repo", tasks: [] });
79
+ // Metadata bound to another activation fails closed, without a label.
80
+ const foreign = discoverOrchestrators(profile, [{ ...peer, endpoint: "/socket/replacement" }])[0];
81
+ assert.equal(foreign.freshness, "unknown");
82
+ assert.equal(foreign.legacyLabel, undefined);
83
+ const sidecar = join(profile, "gentle-agents", "presence", `${publisher.target.sessionHash}.${publisher.target.incarnation}.discovery.json`);
84
+ writeFileSync(sidecar, "{broken");
85
+ const broken = discoverOrchestrators(profile, [peer])[0];
86
+ assert.equal(broken.freshness, "unknown");
87
+ assert.equal(broken.legacyLabel, undefined);
88
+ });
60
89
  test("joins activation-bound metadata without exporting prompts or thread output", (t) => {
61
90
  const profile = fixture(t);
62
91
  const publisher = PresencePublisher.start({ profile, sessionId: peer.sessionId, label: "Review auth", activity: [] });
@@ -66,11 +66,13 @@ test("rendered parent prompt keeps the RDD boundary while omitting lifecycle mir
66
66
  // the three renderable forms -- so this IS the worst-case render the core
67
67
  // budget below must cover, not a smaller placeholder production later
68
68
  // exceeds. gentle-shell#1731 T10 (user decision): budget 8,192 -> 8,400 B.
69
+ // Verification by named risk and the writer-rule fix: 8,400 -> 9,100 B
70
+ // (the canonical budget in orchestrator-budget.test.ts).
69
71
  assert.ok(
70
72
  rendered.includes("Receipt-driven development: unknown (native status unavailable)"),
71
73
  "the default render must include the worst-case RDD status line",
72
74
  );
73
- assert.ok(Buffer.byteLength(rendered, "utf8") <= 8400, "the rendered parent prompt must stay within the 8,400 B core budget");
75
+ assert.ok(Buffer.byteLength(rendered, "utf8") <= 9100, "the rendered parent prompt must stay within the 9,100 B core budget");
74
76
  });
75
77
 
76
78
  test("static prompts retain ODD and delegated-work guidance without SDD", () => {
@@ -59,6 +59,72 @@ test("recorded host/owned child/registered scope correlates clones, isolates amb
59
59
  assert.equal(new OrchestratorScopeCache(() => ({ root: "/repo\nother", commonDir: "/clone" })).project(repo, [], []).host.root, null);
60
60
  });
61
61
 
62
+ test("recency reorders preserve repository facts but retain caller order", () => {
63
+ const host = join(tmpdir(), "scope-host"), a = join(tmpdir(), "scope-a"), b = join(tmpdir(), "scope-b");
64
+ let calls = 0, now = 1;
65
+ const cache = new OrchestratorScopeCache(path => {
66
+ calls++;
67
+ if (path === b) throw new Error("unavailable");
68
+ return { root: path, commonDir: host };
69
+ }, () => now++);
70
+ const tasks = [{ id: "a", cwd: a }, { id: "b", cwd: b }];
71
+ const first = cache.project(host, tasks, [a, b]);
72
+ assert.equal(calls, 3);
73
+ const reordered = cache.project(host, [...tasks].reverse(), [b, a]);
74
+ assert.equal(calls, 3, "pure reorders must not probe Git, including cached unknowns");
75
+ assert.deepEqual(reordered.tasks, [...first.tasks].reverse());
76
+ assert.deepEqual(reordered.registered, [...first.registered].reverse());
77
+ assert.deepEqual(reordered.host, first.host);
78
+ assert.equal(reordered.tasks[0].repository.root, null);
79
+ assert.equal(validRecordedScope(reordered), true);
80
+ assert.strictEqual(cache.project(host, [...tasks].reverse(), [b, a]), reordered);
81
+
82
+ const added = cache.project(host, [...tasks, { id: "c", cwd: host }], [a, b]);
83
+ assert.equal(calls, 6, "membership changes refresh even existing facts");
84
+ assert.ok(added.host.resolvedAt > first.host.resolvedAt);
85
+ cache.project(host, tasks, [a, b]);
86
+ assert.equal(calls, 9, "removal refreshes facts");
87
+ cache.project(host, [{ id: "a", cwd: host }, tasks[1]], [a, b]);
88
+ assert.equal(calls, 12, "cwd changes refresh facts");
89
+ cache.project(host, [{ id: "a", cwd: host }, tasks[1]], [a]);
90
+ assert.equal(calls, 15, "registered membership changes refresh facts");
91
+ cache.clear();
92
+ cache.project(host, tasks, [a, b]);
93
+ assert.equal(calls, 18, "session replacement clears facts");
94
+ });
95
+
96
+ test("first-eight visibility evicts hidden facts and retains visible unknowns", () => {
97
+ const host = join(tmpdir(), "scope-visible-host");
98
+ const tasks = Array.from({ length: 9 }, (_, i) => ({ id: `t${i}`, cwd: join(tmpdir(), `scope-visible-task-${i}`) }));
99
+ const calls: string[] = [];
100
+ const cache = new OrchestratorScopeCache(path => {
101
+ calls.push(path);
102
+ if (path === tasks[0].cwd) throw new Error("unavailable");
103
+ return { root: path, commonDir: host };
104
+ });
105
+ const first = cache.project(host, tasks, []);
106
+ assert.deepEqual(calls, [host, ...tasks.slice(0, 8).map(t => t.cwd)]);
107
+ assert.equal(first.tasks[0].repository.root, null);
108
+ const unknown = first.tasks[0].repository;
109
+ const ninthVisible = [tasks[8], ...tasks.slice(0, 8)];
110
+ const expectedCalls = [...calls];
111
+ for (const [order, newlyVisible] of [[ninthVisible, tasks[8]], [tasks, tasks[7]], [ninthVisible, tasks[8]]] as const) {
112
+ const snapshot = cache.project(host, order, []);
113
+ expectedCalls.push(newlyVisible.cwd);
114
+ assert.deepEqual(calls, expectedCalls, "each visibility transition resolves only its evicted/newly visible path");
115
+ assert.deepEqual(snapshot.tasks.map(t => t.id), order.slice(0, 8).map(t => t.id));
116
+ assert.equal(snapshot.tasks.length, 8);
117
+ assert.equal(snapshot.omittedTasks, 1);
118
+ assert.equal(snapshot.complete, false);
119
+ assert.equal(validRecordedScope(snapshot), true);
120
+ assert.strictEqual(snapshot.tasks.find(t => t.id === "t0")!.repository, unknown, "visible unknown facts survive every reorder");
121
+ assert.strictEqual(cache.project(host, order, []), snapshot);
122
+ assert.deepEqual(calls, expectedCalls, "stable snapshots never resolve again");
123
+ }
124
+ assert.equal(calls.filter(path => path === tasks[8].cwd).length, 2, "ninth path is resolved again after eviction");
125
+ assert.equal(calls.filter(path => path === tasks[0].cwd).length, 1, "visible unknown is not retried");
126
+ });
127
+
62
128
  test("literal recorded roots never alias Unicode separators to another real repository", (t) => {
63
129
  const base = realpathSync(mkdtempSync(join(tmpdir(), "scope-literal-")));
64
130
  t.after(() => rmSync(base, { recursive: true, force: true }));
@@ -8,16 +8,20 @@ const source = readFileSync(join(root, "extensions/gentle-agents.ts"), "utf8");
8
8
  const sessionTool = source.match(/name: "orchestrator_session_id",\s*label: "[^"]+",\s*description: ("(?:[^"\\]|\\.)*")/);
9
9
  assert.ok(sessionTool, "Registered session tool exposes its publication contract");
10
10
  const description: string = JSON.parse(sessionTool[1]);
11
- const guidance = readFileSync(join(root, "assets/orchestrator-delegation.md"), "utf8");
11
+ // S11 T2: state publication is reference material; the delegation card only points at it.
12
+ const guidance = readFileSync(join(root, "assets/orchestrator-delegation-reference.md"), "utf8");
12
13
  const docs = readFileSync(join(root, "docs/gentle-agents-activity.md"), "utf8");
13
14
 
14
- test("shipped guidance publishes status before work and refreshes task transitions", () => {
15
- assert.match(description, /For work beyond small direct tasks, publish subject and current\/next status here; refresh at task changes\/completion/);
15
+ // Harness roadmap T6: the runtime publishes todo progress, so guidance stops
16
+ // asking the model to refresh status at every task transition.
17
+ test("shipped guidance declares a subject and leaves progress to the todo list", () => {
18
+ assert.match(description, /Progress is published automatically from the todo list/);
19
+ assert.doesNotMatch(description, /refresh at task changes/);
16
20
  assert.doesNotMatch(description, /Before meaningful work, publish subject/);
17
- assert.match(guidance, /Before work beyond small direct tasks, publish own `state` with `subject`/);
21
+ assert.match(guidance, /Progress is published automatically from the todo list: never call `orchestrator_session_id` just to report status/);
22
+ assert.match(guidance, /Before work beyond small direct tasks, declare a `subject`/);
18
23
  assert.doesNotMatch(guidance, /Before meaningful work or delegation, publish own `state`/);
19
- assert.match(guidance, /current\/next scope and status/);
20
- assert.match(guidance, /Refresh at task changes\/completion/);
24
+ assert.doesNotMatch(guidance, /Refresh at task changes\/completion/);
21
25
  assert.doesNotMatch(guidance, /when helpful, publish short explicit own `state`/);
22
26
  assert.match(guidance, /Do not require a subject declaration for small direct tasks/);
23
27
  assert.match(guidance, /no extra model turn, repeated reads or per-token\/tool updates/);
@@ -25,10 +29,10 @@ test("shipped guidance publishes status before work and refreshes task transitio
25
29
  });
26
30
 
27
31
  test("hub documentation separates busy-owner snapshots from replies and negative ownership claims", () => {
28
- assert.match(docs, /before work beyond small direct tasks/);
32
+ assert.match(docs, /before work beyond\s+small direct tasks/);
29
33
  assert.match(docs, /while the owner is busy/);
30
34
  assert.match(docs, /current and next scope/);
31
- assert.match(docs, /update at task changes and completion/);
35
+ assert.match(docs, /Progress is published automatically: after each todo write/);
32
36
  assert.match(docs, /A search with no matches does not establish that nobody else is working on the feature/);
33
37
  assert.match(docs, /state `recordedAt` and scope `resolvedAt` keep their historical meaning/);
34
38
  });