gentle-pi 3.7.0 → 4.0.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 (316) hide show
  1. package/README.md +37 -6
  2. package/assets/agents/gentle-ai-explore.md +4 -4
  3. package/assets/agents/gentle-ai-verify.md +6 -4
  4. package/assets/agents/gentle-ai-worker.md +7 -9
  5. package/assets/orchestrator-delegation.md +40 -41
  6. package/assets/orchestrator-memory.md +1 -22
  7. package/assets/orchestrator-skills.md +1 -1
  8. package/assets/orchestrator.md +12 -24
  9. package/assets/support/strict-tdd-verify.md +4 -266
  10. package/assets/support/strict-tdd.md +8 -360
  11. package/bin/gentle-shell.mjs +254 -29
  12. package/docs/delegated-verification.md +26 -1
  13. package/docs/gentle-agents-activity.md +24 -0
  14. package/docs/gentle-shell.md +92 -28
  15. package/docs/native-authority-architecture.md +2 -2
  16. package/docs/prompt-history.md +280 -0
  17. package/docs/readme-reference.md +112 -206
  18. package/docs/telemetry.md +1 -1
  19. package/docs/yolo-mode.md +86 -0
  20. package/extensions/child-context.ts +26 -0
  21. package/extensions/child-safety.ts +23 -0
  22. package/extensions/gentle-agents.ts +429 -262
  23. package/extensions/gentle-ai.ts +781 -683
  24. package/extensions/gentle-shell.ts +1375 -88
  25. package/extensions/gentle-stats.ts +101 -0
  26. package/extensions/gentle-todo.ts +18 -7
  27. package/extensions/history/atomic-write.ts +38 -0
  28. package/extensions/history/hide-prompts.ts +183 -0
  29. package/extensions/history/index.ts +1419 -0
  30. package/extensions/history/load-shared-history.ts +39 -0
  31. package/extensions/history/selector-helpers.ts +538 -0
  32. package/extensions/history/session-scan.ts +233 -0
  33. package/extensions/history/store.ts +1119 -0
  34. package/extensions/nan-provider.ts +6 -0
  35. package/extensions/quiet-tools.ts +179 -87
  36. package/extensions/resume-hint.ts +60 -0
  37. package/extensions/skill-registry.ts +16 -12
  38. package/extensions/startup-banner.ts +60 -31
  39. package/lib/agent-assets.ts +604 -0
  40. package/lib/agent-profile-pin.ts +12 -0
  41. package/lib/agents-message-delivery.ts +181 -0
  42. package/lib/agents-protocol.ts +60 -0
  43. package/lib/agents-runner.ts +96 -98
  44. package/lib/agents-view.ts +26 -3
  45. package/lib/agents-widget.ts +16 -9
  46. package/lib/append-system-prompt.ts +21 -0
  47. package/lib/bounded-writer-admission.ts +147 -0
  48. package/lib/card-style-policy.ts +60 -0
  49. package/lib/child-context-files.ts +166 -0
  50. package/lib/codemode-renderer.ts +185 -0
  51. package/lib/command-palette-catalog.ts +3 -9
  52. package/lib/command-palette.ts +25 -14
  53. package/lib/destructive-command-guard.ts +144 -0
  54. package/lib/gentle-ai-elapsed-store.ts +87 -0
  55. package/lib/gentle-ai-renderer.ts +196 -39
  56. package/lib/gentle-shell-launcher.ts +24 -13
  57. package/lib/gentle-shell-resume-hint.ts +176 -0
  58. package/lib/history-capture-policy.ts +95 -0
  59. package/lib/model-routing-authority.ts +5 -1
  60. package/lib/nan-provider.ts +227 -0
  61. package/lib/native-review-cli.ts +49 -108
  62. package/lib/odd-phase-inference.ts +231 -0
  63. package/lib/odd-phase.ts +141 -0
  64. package/lib/overlay-repaint.ts +26 -0
  65. package/lib/pi-tui-keys.ts +53 -0
  66. package/lib/review-candidate-view-owner.ts +67 -17
  67. package/lib/review-candidate-view.ts +112 -25
  68. package/lib/review-reminder-receipt.ts +48 -8
  69. package/lib/review-risk-assessment.ts +156 -11
  70. package/lib/review-sidebar-state.ts +223 -0
  71. package/lib/selection-engine.ts +515 -0
  72. package/lib/session-messaging-grants.ts +135 -0
  73. package/lib/session-worktree-registry.ts +14 -2
  74. package/lib/shell-bar.ts +179 -24
  75. package/lib/shell-card.ts +287 -18
  76. package/lib/shell-changes-view.ts +2 -1
  77. package/lib/shell-prompt.ts +102 -6
  78. package/lib/shell-sidebar-layout.ts +78 -14
  79. package/lib/shell-sidebar.ts +15 -1
  80. package/lib/shell-todo.ts +23 -13
  81. package/lib/shell-usage-view.ts +9 -4
  82. package/lib/shell-usage.ts +66 -10
  83. package/lib/stats-collector.ts +381 -0
  84. package/lib/stats-view.ts +431 -0
  85. package/lib/theme-customization.ts +52 -0
  86. package/lib/vim-editor-adapter.ts +379 -0
  87. package/lib/vim-normal-engine.ts +154 -0
  88. package/lib/vim-operator-engine.ts +416 -0
  89. package/lib/vim-policy.ts +49 -0
  90. package/lib/vim-visual-engine.ts +107 -0
  91. package/lib/visual-customization-policy.ts +108 -0
  92. package/lib/visual-customize-view.ts +330 -0
  93. package/lib/visual-profiles.ts +228 -0
  94. package/lib/yolo-session-policy.ts +240 -0
  95. package/package.json +20 -8
  96. package/runtime/gentle-shell-launcher.mjs +23 -12
  97. package/runtime/gentle-shell-resume-hint.mjs +177 -0
  98. package/runtime/native-review-cli.mjs +49 -108
  99. package/runtime/review-risk-assessment.mjs +154 -9
  100. package/scripts/build-runtime-modules.mjs +1 -0
  101. package/scripts/gentle-ai-installer.mjs +14 -13
  102. package/scripts/mirror-odd-routing.mjs +2 -2
  103. package/scripts/run-test-suite.mjs +76 -0
  104. package/scripts/test-packed-runner.mjs +31 -14
  105. package/scripts/verify-package-files.mjs +11 -22
  106. package/skills/branch-pr/SKILL.md +24 -52
  107. package/skills/chained-pr/SKILL.md +31 -15
  108. package/skills/chained-pr/references/chaining-details.md +31 -20
  109. package/skills/gentle-ai/SKILL.md +9 -15
  110. package/skills/issue-creation/SKILL.md +8 -2
  111. package/skills/issue-creation/references/delegated-workflow-actions.md +19 -0
  112. package/skills/work-unit-commits/SKILL.md +4 -3
  113. package/tests/agent-profiles.test.ts +18 -0
  114. package/tests/agents-fake-child.ts +2 -2
  115. package/tests/agents-message-delivery.test.ts +106 -0
  116. package/tests/agents-protocol.test.ts +40 -0
  117. package/tests/agents-runner.test.ts +378 -89
  118. package/tests/agents-view-thread-identity.test.ts +169 -0
  119. package/tests/agents-view.test.ts +8 -2
  120. package/tests/agents-widget.test.ts +154 -15
  121. package/tests/append-system-prompt-route.test.ts +160 -0
  122. package/tests/append-system-prompt.test.ts +46 -0
  123. package/tests/artifact-language.test.ts +19 -213
  124. package/tests/ask-user-question.test.ts +44 -1
  125. package/tests/asset-installation-runtime.test.ts +5 -16
  126. package/tests/autonomous-guard.test.ts +69 -1
  127. package/tests/bounded-writer-admission.test.ts +95 -0
  128. package/tests/branch-pr-skill.test.ts +43 -0
  129. package/tests/card-style-policy.test.ts +55 -0
  130. package/tests/chained-pr-skill.test.ts +124 -0
  131. package/tests/child-context-files.test.ts +255 -0
  132. package/tests/child-safety.test.ts +82 -0
  133. package/tests/codemode-rendering.test.ts +491 -0
  134. package/tests/command-palette.test.ts +39 -3
  135. package/tests/delegated-key-learnings-contract.test.ts +0 -76
  136. package/tests/destructive-command-guard.test.ts +84 -0
  137. package/tests/devbinary/native-review-parity.devtest.ts +170 -2
  138. package/tests/devbinary/non-git-subagent-bootstrap.devtest.ts +193 -0
  139. package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-28T10-00-00-000Z_aaa.jsonl +7 -0
  140. package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-29T23-00-00-000Z_bbb.jsonl +3 -0
  141. package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-30T08-00-00-000Z_ddd.jsonl +2 -0
  142. package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-30T09-00-00-000Z_eee.jsonl +3 -0
  143. package/tests/fixtures/stats/sessions/--work-alpha--/run-1/session.jsonl +2 -0
  144. package/tests/fixtures/stats/sessions/--work-beta--/2026-09-01T12-00-00-000Z_ccc.jsonl +2 -0
  145. package/tests/fixtures/stats/user-pi/sessions/--work-alpha--/2026-09-28T10-00-00-000Z_aaa.jsonl +2 -0
  146. package/tests/fixtures/stats/user-pi/sessions/--work-alpha--/2026-09-29T23-00-00-000Z_bbb.jsonl +4 -0
  147. package/tests/fixtures/stats/user-pi/sessions/--work-gamma--/2026-09-20T09-00-00-000Z_fff.jsonl +3 -0
  148. package/tests/generic-agent-tools.test.ts +54 -0
  149. package/tests/gentle-agents.test.ts +1047 -252
  150. package/tests/gentle-ai-binary.test.ts +3 -3
  151. package/tests/gentle-ai-elapsed-store.test.ts +68 -0
  152. package/tests/gentle-ai-installer.test.ts +68 -54
  153. package/tests/gentle-ai-renderer.test.ts +487 -8
  154. package/tests/gentle-ai.test.ts +288 -65
  155. package/tests/gentle-card-text.ts +2 -1
  156. package/tests/gentle-shell-bin.test.ts +651 -114
  157. package/tests/gentle-shell-launcher.test.ts +99 -52
  158. package/tests/gentle-shell-resume-hint.test.ts +270 -0
  159. package/tests/gentle-shell.test.ts +3839 -187
  160. package/tests/gentle-stats.test.ts +152 -0
  161. package/tests/gentle-theme.test.ts +4 -1
  162. package/tests/gentle-todo.test.ts +80 -8
  163. package/tests/history-atomic-write.test.ts +57 -0
  164. package/tests/history-capture-policy.test.ts +102 -0
  165. package/tests/history-command-registration.test.ts +164 -0
  166. package/tests/history-dedupe-entries.test.ts +123 -0
  167. package/tests/history-delete-backfill.test.ts +190 -0
  168. package/tests/history-delete-confirm.test.ts +460 -0
  169. package/tests/history-dispatch.test.ts +180 -0
  170. package/tests/history-drain-hidden.test.ts +110 -0
  171. package/tests/history-drain-order.test.ts +98 -0
  172. package/tests/history-expanded-globals.test.ts +62 -0
  173. package/tests/history-gc.test.ts +832 -0
  174. package/tests/history-header-layout.test.ts +265 -0
  175. package/tests/history-hide-prompts.test.ts +275 -0
  176. package/tests/history-lazy-windowing.test.ts +508 -0
  177. package/tests/history-legacy-migrate-v2.test.ts +297 -0
  178. package/tests/history-load-shared-history.test.ts +53 -0
  179. package/tests/history-max-results-cap.test.ts +76 -0
  180. package/tests/history-multi-reader.test.ts +203 -0
  181. package/tests/history-off-path.test.ts +170 -0
  182. package/tests/history-openflow-integration.test.ts +173 -0
  183. package/tests/history-overlay-margin.test.ts +326 -0
  184. package/tests/history-preview-layout.test.ts +93 -0
  185. package/tests/history-registry.test.ts +143 -0
  186. package/tests/history-scope-delete.test.ts +411 -0
  187. package/tests/history-search-caret-keys.test.ts +142 -0
  188. package/tests/history-seed-bootstrap.test.ts +170 -0
  189. package/tests/history-seed-regen.test.ts +129 -0
  190. package/tests/history-selector-windowing.test.ts +94 -0
  191. package/tests/history-session-scan-directory.test.ts +87 -0
  192. package/tests/history-session-scan-extract.test.ts +583 -0
  193. package/tests/history-session-writer.test.ts +351 -0
  194. package/tests/history-store-paths.test.ts +79 -0
  195. package/tests/history-tombstone-exact.test.ts +139 -0
  196. package/tests/history-wheel-mouse.test.ts +242 -0
  197. package/tests/inprocess-reviewer.test.ts +29 -19
  198. package/tests/issue-creation-skill.test.ts +61 -0
  199. package/tests/model-routing-authority.test.ts +16 -0
  200. package/tests/nan-provider.test.ts +471 -0
  201. package/tests/native-review-capability-contract.test.ts +7 -1
  202. package/tests/native-review-cli.test.ts +6 -120
  203. package/tests/native-review-parity-runtime.test.ts +100 -3
  204. package/tests/odd-integration.test.ts +67 -0
  205. package/tests/odd-phase-inference.test.ts +213 -0
  206. package/tests/odd-phase-loader.test.ts +253 -0
  207. package/tests/odd-phase.test.ts +307 -0
  208. package/tests/odd-routing-canonical-ratchet.test.ts +11 -6
  209. package/tests/odd-routing-contract.test.ts +86 -35
  210. package/tests/orchestrator-budget.test.ts +14 -39
  211. package/tests/orchestrator-rdd-ownership.test.ts +3 -3
  212. package/tests/overlay-repaint.test.ts +74 -0
  213. package/tests/package-manifest.test.ts +252 -115
  214. package/tests/packed-runner-owned-path.test.ts +46 -0
  215. package/tests/persona-single-channel.test.ts +6 -6
  216. package/tests/provider-defect-handoff.test.ts +3 -11
  217. package/tests/quiet-bash-runtime.test.ts +76 -0
  218. package/tests/quiet-tool-rendering.test.ts +409 -184
  219. package/tests/rdd-aware-verification-contract.test.ts +76 -1
  220. package/tests/rdd-status-line.test.ts +9 -4
  221. package/tests/resume-hint-extension.test.ts +122 -0
  222. package/tests/review-agent-end-preflight.test.ts +176 -12
  223. package/tests/review-candidate-owner-retry.test.ts +22 -1
  224. package/tests/review-candidate-view.test.ts +298 -0
  225. package/tests/review-contract-prompt.test.ts +108 -43
  226. package/tests/review-controller-lock-status.test.ts +0 -1
  227. package/tests/review-controller-native-routing.test.ts +611 -5
  228. package/tests/review-controller-workspace-root.test.ts +163 -4
  229. package/tests/review-host-relay-routing.test.ts +338 -2
  230. package/tests/review-integration-v2-forward.test.ts +200 -0
  231. package/tests/review-ledger-contract.test.ts +10 -34
  232. package/tests/review-reminder-receipt.test.ts +47 -1
  233. package/tests/review-risk-assessment.test.ts +498 -6
  234. package/tests/review-sidebar-state.test.ts +402 -0
  235. package/tests/run-test-suite.test.ts +124 -0
  236. package/tests/runtime-harness.mjs +145 -786
  237. package/tests/runtime-metrics-children.test.ts +16 -23
  238. package/tests/selection-engine.test.ts +421 -0
  239. package/tests/session-messaging-grants.test.ts +255 -0
  240. package/tests/session-worktree-registry.test.ts +77 -0
  241. package/tests/shell-bar.test.ts +382 -1
  242. package/tests/shell-card.test.ts +353 -1
  243. package/tests/shell-changes-view.test.ts +52 -0
  244. package/tests/shell-prompt.test.ts +94 -2
  245. package/tests/shell-sidebar-layout.test.ts +325 -21
  246. package/tests/shell-sidebar-scroll-benchmark.test.ts +255 -0
  247. package/tests/shell-todo.test.ts +87 -1
  248. package/tests/shell-usage-view.test.ts +27 -0
  249. package/tests/shell-usage.test.ts +73 -0
  250. package/tests/skill-registry.test.ts +50 -1
  251. package/tests/startup-banner.test.ts +130 -2
  252. package/tests/stats-collector.test.ts +195 -0
  253. package/tests/stats-view.test.ts +202 -0
  254. package/tests/telemetry-trigger.test.ts +81 -20
  255. package/tests/theme-customization.test.ts +72 -0
  256. package/tests/vim-editor-adapter-host-resolution.test.ts +37 -0
  257. package/tests/vim-editor-adapter.test.ts +804 -0
  258. package/tests/vim-normal-engine.test.ts +101 -0
  259. package/tests/vim-operator-engine.test.ts +215 -0
  260. package/tests/vim-policy.test.ts +19 -0
  261. package/tests/vim-visual-engine.test.ts +52 -0
  262. package/tests/visual-customization-policy.test.ts +110 -0
  263. package/tests/visual-customize-view.test.ts +418 -0
  264. package/tests/visual-profiles.test.ts +87 -0
  265. package/tests/yolo-customize.test.ts +256 -0
  266. package/tests/yolo-mode-runtime.test.ts +161 -0
  267. package/tests/yolo-mode.test.ts +261 -0
  268. package/tests/yolo-session-policy.test.ts +59 -0
  269. package/themes/Gentle.json +2 -1
  270. package/themes/Gentleman-Cute.json +2 -1
  271. package/themes/Gentleman-Sexy.json +2 -1
  272. package/assets/agents/sdd-apply.md +0 -159
  273. package/assets/agents/sdd-archive.md +0 -228
  274. package/assets/agents/sdd-design.md +0 -49
  275. package/assets/agents/sdd-explore.md +0 -48
  276. package/assets/agents/sdd-init.md +0 -56
  277. package/assets/agents/sdd-onboard.md +0 -52
  278. package/assets/agents/sdd-proposal.md +0 -64
  279. package/assets/agents/sdd-remediate.md +0 -37
  280. package/assets/agents/sdd-research.md +0 -49
  281. package/assets/agents/sdd-spec.md +0 -192
  282. package/assets/agents/sdd-status.md +0 -54
  283. package/assets/agents/sdd-tasks.md +0 -108
  284. package/assets/agents/sdd-verify.md +0 -124
  285. package/assets/chains/sdd-full.chain.md +0 -83
  286. package/assets/chains/sdd-plan.chain.md +0 -56
  287. package/assets/chains/sdd-verify.chain.md +0 -43
  288. package/assets/sdd-orchestrator-workflow.md +0 -319
  289. package/assets/support/sdd-status-contract.md +0 -77
  290. package/docs/assets/diagrams/sdd-cycle.svg +0 -14
  291. package/extensions/sdd-init.ts +0 -816
  292. package/lib/openspec-deltas.ts +0 -156
  293. package/lib/sdd-preflight.ts +0 -1066
  294. package/lib/sdd-research-capabilities.ts +0 -94
  295. package/lib/sdd-status.ts +0 -26
  296. package/tests/fixtures/legacy/sdd-research-v2.5.0.md +0 -54
  297. package/tests/fixtures/native-review-cli/v2.1.3/bind-sdd.json +0 -25
  298. package/tests/fixtures/v0.10.7/assets/agents/sdd-apply.md +0 -132
  299. package/tests/openspec-deltas.test.ts +0 -209
  300. package/tests/sdd-agent-tools.test.ts +0 -156
  301. package/tests/sdd-archive-replay.test.ts +0 -82
  302. package/tests/sdd-classical-continuation.test.ts +0 -74
  303. package/tests/sdd-execution-routing-contract.test.ts +0 -44
  304. package/tests/sdd-managed-runtime-settlement.test.ts +0 -155
  305. package/tests/sdd-native-managed-uptake.test.ts +0 -243
  306. package/tests/sdd-no-attempts-contract.test.ts +0 -15
  307. package/tests/sdd-odd-integration.test.ts +0 -33
  308. package/tests/sdd-optional-research.test.ts +0 -124
  309. package/tests/sdd-planning-routing-contract.test.ts +0 -45
  310. package/tests/sdd-preflight-rpc-input.test.ts +0 -125
  311. package/tests/sdd-preflight.test.ts +0 -541
  312. package/tests/sdd-research-capabilities.test.ts +0 -114
  313. package/tests/sdd-research-live.test.ts +0 -241
  314. package/tests/sdd-selection-transport.test.ts +0 -653
  315. package/tests/sdd-status.test.ts +0 -9
  316. package/tests/sdd-task-truth.test.ts +0 -43
@@ -1,4 +1,5 @@
1
1
  import assert from "node:assert/strict";
2
+ import { execFileSync } from "node:child_process";
2
3
  import { createHash } from "node:crypto";
3
4
  import {
4
5
  existsSync,
@@ -16,7 +17,10 @@ import ts from "typescript";
16
17
  import { fileURLToPath } from "node:url";
17
18
  import { applyModelConfig } from "../extensions/gentle-ai.ts";
18
19
  import { resolveGentlePiAgentHome } from "../lib/agent-home.ts";
19
- import { getPackageAssetOwner, installPackageAssets, installSddAssets, type PackageAssetOwner } from "../lib/sdd-preflight.ts";
20
+ import { getPackageAssetOwner, installPackageAssets, type PackageAssetOwner } from "../lib/agent-assets.ts";
21
+ import { AUDITED_PI_EDITOR_VERSIONS } from "../lib/vim-editor-adapter.ts";
22
+ import { resolveProjectPiSdkVersion } from "../scripts/test-packed-runner.mjs";
23
+ // Package installation is owned by lib/agent-assets.ts, not the retired SDD preflight.
20
24
 
21
25
  const PACKAGE_ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
22
26
  const MANAGED_EXEMPLAR_FILE = "gentle-ai-explore.md";
@@ -76,13 +80,22 @@ interface PackageJsonPiManifest {
76
80
  extensions?: string[];
77
81
  }
78
82
 
83
+ interface PackageJsonPeerMetadata {
84
+ optional?: boolean;
85
+ }
86
+
79
87
  interface PackageJson {
88
+ description?: string;
89
+ keywords?: string[];
80
90
  version?: string;
81
91
  files?: string[];
82
92
  scripts?: Record<string, string>;
83
93
  dependencies?: Record<string, string>;
84
94
  peerDependencies?: Record<string, string>;
95
+ peerDependenciesMeta?: Record<string, PackageJsonPeerMetadata>;
96
+ optionalDependencies?: Record<string, string>;
85
97
  devDependencies?: Record<string, string>;
98
+ engines?: Record<string, string>;
86
99
  bundledDependencies?: string[];
87
100
  bundleDependencies?: string[];
88
101
  repository?: {
@@ -102,16 +115,106 @@ function readPackageJson(): PackageJson {
102
115
  }
103
116
  }
104
117
 
118
+ test("public docs and metadata advertise ODD and review without retired phase workflow", () => {
119
+ const manifest = readPackageJson();
120
+ assert.match(manifest.description ?? "", /ODD|Organic Driven Development/);
121
+ assert.match(manifest.description ?? "", /review/i);
122
+ assert.ok(manifest.keywords?.includes("odd"));
123
+ assert.ok(manifest.keywords?.includes("code-review"));
124
+ assert.ok(manifest.keywords?.every(keyword => !/sdd|openspec/i.test(keyword)));
125
+ for (const path of ["README.md", "docs/gentle-shell.md", "docs/readme-reference.md"]) {
126
+ const source = readFileSync(join(PACKAGE_ROOT, path), "utf8");
127
+ assert.match(source, /ODD|Organic Driven Development/, path);
128
+ assert.match(source, /review/i, path);
129
+ assert.doesNotMatch(source, /\bSDD\b|OpenSpec|\/gentle-sdd-init|\/gentle:install-sdd|\/gentle:sdd-preflight|\/sdd-/i, path);
130
+ }
131
+ });
132
+
105
133
  test("technical reference declares the tested Pi minimum required for agent_settled", () => {
106
134
  const manifest = readPackageJson();
107
- assert.equal(manifest.peerDependencies?.["@earendil-works/pi-coding-agent"], ">=0.85.1");
108
- assert.equal(manifest.devDependencies?.["@earendil-works/pi-coding-agent"], "0.85.1");
135
+ assert.equal(manifest.peerDependencies?.["@earendil-works/pi-coding-agent"], ">=0.99.1");
136
+ assert.equal(manifest.devDependencies?.["@earendil-works/pi-coding-agent"], ">=1.0.0");
137
+ assert.equal(manifest.peerDependenciesMeta?.["@earendil-works/pi-coding-agent"]?.optional, true);
138
+ assert.equal(manifest.engines?.node, ">=22.19.0");
139
+ for (const path of ["docs/readme-reference.md", "docs/gentle-shell.md"]) {
140
+ const source = readFileSync(join(PACKAGE_ROOT, path), "utf8");
141
+ assert.match(source, /Pi 0\.99\.1 or newer/, path);
142
+ assert.match(source, /open `>=1\.0\.0` development range/, path);
143
+ assert.doesNotMatch(source, /development tests pin Pi/, path);
144
+ // Docs name exactly the audited Vim editor releases, never a future one.
145
+ const auditedReleases = new Intl.ListFormat("en", { style: "long", type: "conjunction" })
146
+ .format(AUDITED_PI_EDITOR_VERSIONS.map(v => `\`${v.replace(/\./g, "\\.")}\``));
147
+ assert.match(source, new RegExp(`audited Pi ${auditedReleases}`), path);
148
+ }
109
149
  const reference = readFileSync(join(PACKAGE_ROOT, "docs", "readme-reference.md"), "utf8");
110
- assert.match(reference, /Pi 0\.85\.1 or newer/);
111
150
  assert.match(reference, /agent_settled/);
112
151
  assert.match(readFileSync(join(PACKAGE_ROOT, "README.md"), "utf8"), /\]\(docs\/readme-reference\.md(?:#[^)]+)?\)/);
113
152
  });
114
153
 
154
+ test("packed runtime uses optional Pi host peers with one open development range and no duplicate direct dependencies", () => {
155
+ const manifest = readPackageJson();
156
+ for (const name of ["@earendil-works/pi-ai", "@earendil-works/pi-tui"]) {
157
+ assert.equal(manifest.peerDependencies?.[name], "*", name);
158
+ assert.equal(manifest.peerDependenciesMeta?.[name]?.optional, true, name);
159
+ }
160
+ // The devDependency specifier is policy (a range); the resolved install is
161
+ // one exact release shared by every Pi host package.
162
+ const installed = new Set<string>();
163
+ for (const name of ["@earendil-works/pi-coding-agent", "@earendil-works/pi-ai", "@earendil-works/pi-tui"]) {
164
+ assert.equal(manifest.devDependencies?.[name], ">=1.0.0", name);
165
+ const metadata = JSON.parse(readFileSync(join(PACKAGE_ROOT, "node_modules", name, "package.json"), "utf8")) as { name: string; version: string };
166
+ assert.equal(metadata.name, name);
167
+ installed.add(metadata.version);
168
+ }
169
+ assert.deepEqual([...installed], [resolveProjectPiSdkVersion(PACKAGE_ROOT)]);
170
+ for (const name of ["@earendil-works/pi-coding-agent", "@earendil-works/pi-ai", "@earendil-works/pi-tui"]) {
171
+ assert.equal(manifest.dependencies?.[name], undefined, name);
172
+ assert.equal(manifest.optionalDependencies?.[name], undefined, name);
173
+ }
174
+ });
175
+
176
+ // Fixture project roots let the resolver's range policy be tested without
177
+ // touching the real install.
178
+ function withPiSdkProject(range: unknown, installed: unknown, run: (root: string) => void): void {
179
+ const root = mkdtempSync(join(tmpdir(), "gentle-pi-sdk-version-"));
180
+ try {
181
+ const sdk = join(root, "node_modules", "@earendil-works", "pi-coding-agent");
182
+ mkdirSync(sdk, { recursive: true });
183
+ writeFileSync(join(root, "package.json"), JSON.stringify({ devDependencies: { "@earendil-works/pi-coding-agent": range } }));
184
+ writeFileSync(join(sdk, "package.json"), JSON.stringify(installed));
185
+ run(root);
186
+ } finally {
187
+ rmSync(root, { recursive: true, force: true });
188
+ }
189
+ }
190
+
191
+ test("packed probes install the project-resolved Pi SDK release, never the devDependency specifier", () => {
192
+ const sdk = (version: unknown) => ({ name: "@earendil-works/pi-coding-agent", version });
193
+ for (const [range, version] of [[">=0.99.2", "0.99.2"], [">=0.99.2", "0.99.10"], [">=0.99.2", "1.0.0"], ["0.99.2", "0.99.2"]]) {
194
+ withPiSdkProject(range, sdk(version), (root) => assert.equal(resolveProjectPiSdkVersion(root), version, `${range} ${version}`));
195
+ }
196
+ for (const [range, installed, error] of [
197
+ [">=0.99.2", sdk("0.99.1"), /0\.99\.1 does not satisfy >=0\.99\.2/],
198
+ ["0.99.1", sdk("0.99.2"), /0\.99\.2 does not satisfy 0\.99\.1/],
199
+ ["*", sdk("0.99.2"), /exact or >= Pi SDK development range/],
200
+ ["^0.99.2", sdk("0.99.2"), /exact or >= Pi SDK development range/],
201
+ [undefined, sdk("0.99.2"), /exact or >= Pi SDK development range/],
202
+ [">=0.99.2", sdk(">=0.99.2"), /exact release version/],
203
+ [">=0.99.2", sdk("0.99.3-rc.1"), /exact release version/],
204
+ [">=0.99.2", { name: "impostor", version: "0.99.2" }, /exact release version/],
205
+ ] as const) {
206
+ withPiSdkProject(range, installed, (root) => assert.throws(() => resolveProjectPiSdkVersion(root), error, String(range)));
207
+ }
208
+ const installed = JSON.parse(readFileSync(join(PACKAGE_ROOT, "node_modules", "@earendil-works", "pi-coding-agent", "package.json"), "utf8")) as { version: string };
209
+ assert.equal(resolveProjectPiSdkVersion(PACKAGE_ROOT), installed.version);
210
+ const packedRunner = readFileSync(join(PACKAGE_ROOT, "scripts", "test-packed-runner.mjs"), "utf8");
211
+ for (const name of ["testSdkLifecyclePackedSession", "testWindowsStartupTimingPackedHelper", "testWindowsStartupTimingEnvironmentExperiment", "testUnhookedPackedImports"]) {
212
+ const probe = readNamedFunction(packedRunner, name);
213
+ assert.match(probe, /const sdkVersion = resolveProjectPiSdkVersion\(root, "[^"]+"\);/, name);
214
+ assert.doesNotMatch(probe, /devDependencies/, name);
215
+ }
216
+ });
217
+
115
218
  test("package manifest has no obsolete native activation build surface", () => {
116
219
  const packageJson = readPackageJson();
117
220
  const manifest = JSON.stringify(packageJson);
@@ -123,6 +226,16 @@ test("package manifest has no obsolete native activation build surface", () => {
123
226
  assert.doesNotMatch(packageJson.scripts?.prepublishOnly ?? "", /native:build/);
124
227
  });
125
228
 
229
+ test("package verification excludes the retired init extension while retaining ODD and review resources", () => {
230
+ const verifier = readFileSync(join(PACKAGE_ROOT, "scripts", "verify-package-files.mjs"), "utf8");
231
+ assert.equal(existsSync(join(PACKAGE_ROOT, "extensions", "sdd-init.ts")), false);
232
+ assert.doesNotMatch(verifier, /^\s*"extensions\/sdd-init\.ts",?$/m);
233
+ assert.match(verifier, /existsSync\(join\(root, "extensions\/sdd-init\.ts"\)\)/);
234
+ for (const resource of ["extensions/gentle-ai.ts", "extensions/skill-registry.ts", "assets/orchestrator.md", "assets/orchestrator-delegation.md", "assets/agents/gentle-ai-worker.md", "assets/agents/review-risk.md", "assets/chains/4r-review.chain.md"]) {
235
+ assert.ok(verifier.includes(`"${resource}"`), `${resource} must remain required`);
236
+ }
237
+ });
238
+
126
239
  test("package verification names the native review runtime boundary and packaged fixtures", () => {
127
240
  const verifier = readFileSync(join(PACKAGE_ROOT, "scripts", "verify-package-files.mjs"), "utf8");
128
241
  const manifest = readPackageJson();
@@ -298,20 +411,20 @@ test("package manifest installs pi-pretty through a wrapper without bundling nat
298
411
  );
299
412
  });
300
413
 
301
- test("package verification binds the published Gentle AI v3.7.0 runtime pin", () => {
414
+ test("package verification binds the published Gentle AI v4.0.0 runtime pin", () => {
302
415
  const installer = readFileSync(join(PACKAGE_ROOT, "scripts", "gentle-ai-installer.mjs"), "utf8");
303
416
  const binary = readFileSync(join(PACKAGE_ROOT, "lib", "gentle-ai-binary.ts"), "utf8");
304
417
  const verifier = readFileSync(join(PACKAGE_ROOT, "scripts", "verify-package-files.mjs"), "utf8");
305
418
 
306
- assert.match(installer, /INSTALLER_VERSION = "3\.7\.0"/);
419
+ assert.match(installer, /INSTALLER_VERSION = "4\.0\.0"/);
307
420
  assert.match(installer, /GENTLE_AI_WINDOWS_SOURCE_PACKAGE.*GENTLE_AI_WINDOWS_SOURCE_MODULE/);
308
- assert.match(installer, /GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM = "h1:MQbzHlLdPklUQn0rVE9Mz94UygHsN2OPe7xMfPn9aGw="/);
421
+ assert.match(installer, /GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM = "h1:pZ\/XZ2Pk3U9lgXigOTY62zlxxFOHnc9CjQhLgaV\/Hfc="/);
309
422
  assert.match(installer, /GOTOOLCHAIN: "local"/);
310
423
  assert.match(installer, /GOSUMDB: "sum\.golang\.org"/);
311
424
  assert.match(binary, /GENTLE_AI_VERSION = INSTALLER_VERSION/);
312
425
  assert.match(binary, /GO_SUMDB_SOURCE_BUILD/);
313
426
  assert.match(binary, /GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM/);
314
- assert.match(verifier, /v3\.7\.0/);
427
+ assert.match(verifier, /v4\.0\.0/);
315
428
  });
316
429
 
317
430
 
@@ -551,6 +664,36 @@ function installedAssetManifest(agentHome: string): ManagedAssetsManifest {
551
664
  return JSON.parse(readFileSync(join(agentHome, "gentle-ai", "managed-assets.json"), "utf8"));
552
665
  }
553
666
 
667
+ test("ODD delegation assets retain applicable test-first checks and independent review boundaries without SDD routing", () => {
668
+ const support = readFileSync(join(PACKAGE_ROOT, "assets/support/strict-tdd.md"), "utf8");
669
+ const explorer = readFileSync(join(PACKAGE_ROOT, "assets/agents/gentle-ai-explore.md"), "utf8");
670
+ const verifier = readFileSync(join(PACKAGE_ROOT, "assets/agents/gentle-ai-verify.md"), "utf8");
671
+ const worker = readFileSync(join(PACKAGE_ROOT, "assets/agents/gentle-ai-worker.md"), "utf8");
672
+ for (const source of [support, explorer, verifier, worker]) {
673
+ assert.doesNotMatch(source, /openspec\/config\.yaml|sdd-init|SDD phase protocols|generic non-SDD|sdd-verify|sdd\/\{project\}/i);
674
+ }
675
+ assert.match(support, /RED[\s\S]*GREEN[\s\S]*REFACTOR/);
676
+ assert.match(support, /behavior changes with applicable runnable deterministic tests and a clear expected outcome/i);
677
+ assert.match(support, /A test file merely existing is not proof of applicability or RED/i);
678
+ assert.match(support, /no meaningful RED[\s\S]*proportionate ordinary functional or structural verification/i);
679
+ assert.match(support, /Use only exact commands authorized by the parent/i);
680
+ assert.match(explorer, /read-only explorer for generic ODD work/);
681
+ assert.match(verifier, /read-only technical verifier for generic ODD work/);
682
+ assert.match(worker, /allowed edit surfaces/i);
683
+ assert.match(worker, /work-unit commit/i);
684
+ assert.match(worker, /review lifecycle.*parent/i);
685
+ });
686
+
687
+ test("delegation installation retains strict TDD support without installing SDD assets", () => {
688
+ withIsolatedAssetHome((agentHome) => {
689
+ installPackageAssets(agentHome, false, ["delegation"]);
690
+ const keys = Object.keys(installedAssetManifest(agentHome).assets);
691
+ assert.ok(keys.includes("gentle-ai/support/strict-tdd.md"));
692
+ assert.ok(keys.includes("gentle-ai/support/strict-tdd-verify.md"));
693
+ assert.ok(keys.every(key => !key.startsWith("agents/sdd-") && !key.startsWith("chains/sdd-") && !key.includes("sdd-status-contract")));
694
+ });
695
+ });
696
+
554
697
  test("selective delegation installation owns only generic agents", () => {
555
698
  withIsolatedAssetHome((agentHome) => {
556
699
  const result = installPackageAssets(agentHome, false, ["delegation"]);
@@ -558,19 +701,21 @@ test("selective delegation installation owns only generic agents", () => {
558
701
  "agents/gentle-ai-explore.md",
559
702
  "agents/gentle-ai-verify.md",
560
703
  "agents/gentle-ai-worker.md",
704
+ "gentle-ai/support/strict-tdd-verify.md",
705
+ "gentle-ai/support/strict-tdd.md",
561
706
  ]);
562
- assert.deepEqual(result, { agents: 3, chains: 0, support: 0, skipped: 0 });
707
+ assert.deepEqual(result, { agents: 3, chains: 0, support: 2, skipped: 0 });
563
708
  assert.deepEqual(readdirSync(join(agentHome, "agents")).sort(), [
564
709
  "gentle-ai-explore.md", "gentle-ai-verify.md", "gentle-ai-worker.md",
565
710
  ]);
566
711
  assert.equal(existsSync(join(agentHome, "chains")), false);
567
- assert.equal(existsSync(join(agentHome, "gentle-ai", "support")), false);
712
+ assert.deepEqual(readdirSync(join(agentHome, "gentle-ai", "support")).sort(), ["strict-tdd-verify.md", "strict-tdd.md"]);
568
713
  });
569
714
  });
570
715
 
571
716
  test("selective installation retires only assets belonging to the selected owner", () => {
572
717
  withIsolatedAssetHome((agentHome) => {
573
- installSddAssets(agentHome, false);
718
+ installPackageAssets(agentHome, false);
574
719
  const manifestPath = join(agentHome, "gentle-ai", "managed-assets.json");
575
720
  const manifest = installedAssetManifest(agentHome);
576
721
  for (const name of RETIRED_ADVERSARIAL_AGENTS) {
@@ -578,7 +723,7 @@ test("selective installation retires only assets belonging to the selected owner
578
723
  manifest.assets[`agents/${name}`] = sha256("Previously managed review agent\n");
579
724
  }
580
725
  writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
581
- for (const owner of ["delegation", "sdd"] as const) {
726
+ for (const owner of ["delegation"] as const) {
582
727
  installPackageAssets(agentHome, true, [owner]);
583
728
  assert.deepEqual(installedAssetManifest(agentHome), manifest);
584
729
  for (const name of RETIRED_ADVERSARIAL_AGENTS) {
@@ -598,21 +743,13 @@ test("selective installation retires only assets belonging to the selected owner
598
743
  const EXPECTED_OWNER_ASSETS: Record<PackageAssetOwner, readonly string[]> = {
599
744
  delegation: [
600
745
  "agents/gentle-ai-explore.md", "agents/gentle-ai-verify.md", "agents/gentle-ai-worker.md",
746
+ "gentle-ai/support/strict-tdd.md", "gentle-ai/support/strict-tdd-verify.md",
601
747
  ],
602
748
  review: [
603
749
  "agents/jd-fix-agent.md", "agents/jd-judge-a.md", "agents/jd-judge-b.md",
604
750
  "agents/review-readability.md", "agents/review-reliability.md",
605
751
  "agents/review-resilience.md", "agents/review-risk.md", "chains/4r-review.chain.md",
606
752
  ],
607
- sdd: [
608
- "agents/sdd-apply.md", "agents/sdd-archive.md", "agents/sdd-design.md",
609
- "agents/sdd-explore.md", "agents/sdd-init.md", "agents/sdd-onboard.md",
610
- "agents/sdd-proposal.md", "agents/sdd-remediate.md", "agents/sdd-research.md", "agents/sdd-spec.md",
611
- "agents/sdd-status.md", "agents/sdd-tasks.md", "agents/sdd-verify.md",
612
- "chains/sdd-full.chain.md", "chains/sdd-plan.chain.md", "chains/sdd-verify.chain.md",
613
- "gentle-ai/support/sdd-status-contract.md", "gentle-ai/support/strict-tdd-verify.md",
614
- "gentle-ai/support/strict-tdd.md",
615
- ],
616
753
  };
617
754
 
618
755
  function assetFileKeys(root: string, prefix = ""): string[] {
@@ -641,7 +778,7 @@ for (const owner of Object.keys(EXPECTED_OWNER_ASSETS) as PackageAssetOwner[]) {
641
778
  });
642
779
  }
643
780
 
644
- test("legacy all-assets installation covers every packaged file with explicit ownership", () => {
781
+ test("all-assets installation covers every retained packaged file with explicit ownership", () => {
645
782
  const packaged = ["agents", "chains", "support"].flatMap(group =>
646
783
  assetFileKeys(join(PACKAGE_ROOT, "assets", group), group === "support" ? "gentle-ai/support/" : `${group}/`),
647
784
  ).sort();
@@ -651,24 +788,24 @@ test("legacy all-assets installation covers every packaged file with explicit ow
651
788
  assert.equal(getPackageAssetOwner(key), undefined, "unknown assets must not default to SDD");
652
789
  }
653
790
  withIsolatedAssetHome((agentHome) => {
654
- assert.deepEqual(installSddAssets(agentHome, false), { agents: 23, chains: 4, support: 3, skipped: 0 });
791
+ assert.deepEqual(installPackageAssets(agentHome, false), { agents: 10, chains: 1, support: 2, skipped: 0 });
655
792
  assert.deepEqual(Object.keys(installedAssetManifest(agentHome).assets).sort(), packaged);
656
- assert.deepEqual(installSddAssets(agentHome, false), { agents: 0, chains: 0, support: 0, skipped: 30 });
657
- assert.deepEqual(installSddAssets(agentHome, true), { agents: 23, chains: 4, support: 3, skipped: 0 });
793
+ assert.deepEqual(installPackageAssets(agentHome, false), { agents: 0, chains: 0, support: 0, skipped: 13 });
794
+ assert.deepEqual(installPackageAssets(agentHome, true), { agents: 10, chains: 1, support: 2, skipped: 0 });
658
795
  });
659
796
  });
660
797
 
661
798
  test("selective refresh preserves unselected ownership and selected user changes after an all-assets install", () => {
662
799
  withIsolatedAssetHome((agentHome) => {
663
- installSddAssets(agentHome, false);
800
+ installPackageAssets(agentHome, false);
664
801
  const manifest = installedAssetManifest(agentHome);
665
802
  const selectedUserKey = "agents/gentle-ai-explore.md";
666
- const unselectedUserKey = "agents/sdd-apply.md";
803
+ const unselectedUserKey = "agents/review-risk.md";
667
804
  for (const key of [selectedUserKey, unselectedUserKey, "agents/custom.md"]) {
668
805
  writeFileSync(join(agentHome, key), "User-authored instructions\n");
669
806
  }
670
807
  const before = new Map(assetFileKeys(agentHome).map(key => [key, readFileSync(join(agentHome, key), "utf8")]));
671
- assert.deepEqual(installPackageAssets(agentHome, true, ["delegation"]), { agents: 2, chains: 0, support: 0, skipped: 1 });
808
+ assert.deepEqual(installPackageAssets(agentHome, true, ["delegation"]), { agents: 2, chains: 0, support: 2, skipped: 1 });
672
809
  delete manifest.assets[selectedUserKey];
673
810
  assert.deepEqual(installedAssetManifest(agentHome), manifest);
674
811
  for (const [key, content] of before) {
@@ -705,42 +842,45 @@ test("selective review migration adopts only untouched legacy copies and preserv
705
842
  assert.equal(manifest.assets["agents/review-risk.md"], sha256(actual));
706
843
  }
707
844
  assert.equal(existsSync(join(agentHome, "agents", "sdd-apply.md")), false);
708
- assert.equal(existsSync(join(agentHome, "gentle-ai", "support")), false);
845
+ assert.equal(existsSync(join(agentHome, "gentle-ai", "support", "strict-tdd.md")), true);
709
846
  });
710
847
  }
711
848
  });
712
849
 
713
- test("unowned legacy research migrates by exact normalized hash, preserving routing and user edits", () => {
714
- // Historical bytes must not be reconstructed from the evolving current agent.
715
- const legacy = readFileSync(join(PACKAGE_ROOT, "tests/fixtures/legacy/sdd-research-v2.5.0.md"), "utf8");
716
- const manifest = JSON.parse(readFileSync(join(PACKAGE_ROOT, "assets", "migrations", "managed-assets-v2.5.0.json"), "utf8"));
717
- assert.equal(sha256(legacy), manifest.assets["agents/sdd-research.md"], "fixture reconstruction must match observed old package bytes");
718
- const temporary = mkdtempSync(join(tmpdir(), "gentle-research-migration-"));
719
- const previous = process.env.GENTLE_PI_AGENT_HOME;
850
+ test("packed tarball excludes retired workflow paths while source retains legacy migration proof", () => {
851
+ const fixture = "tests/fixtures/legacy/sdd-research-v2.5.0.md";
852
+ assert.ok(existsSync(join(PACKAGE_ROOT, fixture)), "the historical source fixture must remain available to migration tests");
853
+ const destination = mkdtempSync(join(tmpdir(), "gentle-pi-pack-manifest-"));
720
854
  try {
721
- for (const edited of [false, true]) {
722
- const agentHome = join(temporary, edited ? "edited" : "legacy");
723
- process.env.GENTLE_PI_AGENT_HOME = agentHome;
855
+ const output = execFileSync("npm", ["pack", "--ignore-scripts", "--offline", "--json", "--pack-destination", destination], {
856
+ cwd: PACKAGE_ROOT,
857
+ encoding: "utf8",
858
+ maxBuffer: 8 * 1024 * 1024,
859
+ });
860
+ const [packed] = JSON.parse(output) as [{ files: { path: string }[] }];
861
+ assert.ok(packed?.files?.length, "npm pack must return a nonempty tar manifest");
862
+ assert.ok(packed.files.some(file => file.path === "tests/package-manifest.test.ts"), "other tests remain packed");
863
+ assert.deepEqual(packed.files.filter(file => /sdd|openspec/i.test(file.path)).map(file => file.path), []);
864
+ } finally {
865
+ rmSync(destination, { recursive: true, force: true });
866
+ }
867
+ });
868
+
869
+ test("legacy research retirement requires exact ownership and preserves edited copies", () => {
870
+ const legacy = readFileSync(join(PACKAGE_ROOT, "tests/fixtures/legacy/sdd-research-v2.5.0.md"), "utf8");
871
+ const history = JSON.parse(readFileSync(join(PACKAGE_ROOT, "assets", "migrations", "managed-assets-v2.5.0.json"), "utf8"));
872
+ assert.equal(sha256(legacy), history.assets["agents/sdd-research.md"]);
873
+ for (const edited of [false, true]) {
874
+ withIsolatedAssetHome((agentHome) => {
724
875
  mkdirSync(join(agentHome, "agents"), { recursive: true });
725
876
  const target = join(agentHome, "agents", "sdd-research.md");
726
- const routed = legacy.replace("name: sdd-research\n", "name: sdd-research\nmodel: custom/model\nthinking: high\n") + (edited ? "\nUser research restrictions.\n" : "");
727
- writeFileSync(target, routed);
728
- installSddAssets(temporary, true);
729
- const actual = readFileSync(target, "utf8");
730
- if (edited) assert.equal(actual, routed);
731
- else {
732
- assert.match(actual, / - fetch_content/);
733
- assert.match(actual, /model: custom\/model\nthinking: high/);
734
- const ownership = JSON.parse(readFileSync(join(agentHome, "gentle-ai", "managed-assets.json"), "utf8"));
735
- assert.equal(ownership.assets["agents/sdd-research.md"], sha256(actual));
736
- installSddAssets(temporary, true);
737
- assert.equal(readFileSync(target, "utf8"), actual, "subsequent refresh keeps adopted model routing");
738
- }
739
- }
740
- } finally {
741
- if (previous === undefined) delete process.env.GENTLE_PI_AGENT_HOME;
742
- else process.env.GENTLE_PI_AGENT_HOME = previous;
743
- rmSync(temporary, { recursive: true, force: true });
877
+ const content = edited ? `${legacy}\nUser research restrictions.\n` : legacy;
878
+ writeFileSync(target, content);
879
+ installPackageAssets(agentHome, true);
880
+ assert.equal(existsSync(target), edited);
881
+ if (edited) assert.equal(readFileSync(target, "utf8"), content);
882
+ assert.equal(installedAssetManifest(agentHome).assets["agents/sdd-research.md"], undefined);
883
+ });
744
884
  }
745
885
  });
746
886
 
@@ -783,7 +923,7 @@ test("forced package installation preserves same-path user-authored agents and s
783
923
  mkdirSync(dirname(samePathUserAgent), { recursive: true });
784
924
  writeFileSync(samePathUserAgent, userAgentSource);
785
925
 
786
- installSddAssets(temporaryProject, true);
926
+ installPackageAssets(temporaryProject, true);
787
927
 
788
928
  assert.deepEqual(
789
929
  readFileSync(samePathUserAgent),
@@ -861,7 +1001,7 @@ test("first forced sync migrates untouched v0.13 assets, preserves routing, and
861
1001
  writeFileSync(installedReviewRisk, routedLegacySource);
862
1002
  assert.equal(existsSync(managedAssetsManifest), false, "v0.13 had no ownership manifest");
863
1003
 
864
- installSddAssets(PACKAGE_ROOT, true);
1004
+ installPackageAssets(PACKAGE_ROOT, true);
865
1005
 
866
1006
  const migrated = readFileSync(installedReviewRisk, "utf8");
867
1007
  const currentPackageSource = readFileSync(
@@ -897,7 +1037,7 @@ test("first forced sync migrates untouched v0.13 assets, preserves routing, and
897
1037
  );
898
1038
  assert.notEqual(userEditedMigration, migrated, "the fixture must exercise post-migration drift");
899
1039
  writeFileSync(installedReviewRisk, userEditedMigration);
900
- installSddAssets(PACKAGE_ROOT, true);
1040
+ installPackageAssets(PACKAGE_ROOT, true);
901
1041
  assert.deepEqual(
902
1042
  readFileSync(installedReviewRisk),
903
1043
  Buffer.from(userEditedMigration),
@@ -932,7 +1072,7 @@ test("first forced sync migrates untouched v0.14 review contracts and preserves
932
1072
  mkdirSync(dirname(installedReviewRisk), { recursive: true });
933
1073
  writeFileSync(installedReviewRisk, routedLegacySource);
934
1074
 
935
- installSddAssets(PACKAGE_ROOT, true);
1075
+ installPackageAssets(PACKAGE_ROOT, true);
936
1076
 
937
1077
  const migrated = readFileSync(installedReviewRisk, "utf8");
938
1078
  assert.notEqual(migrated, routedLegacySource);
@@ -969,7 +1109,7 @@ test("first forced sync preserves a body-edited v0.13 asset byte-for-byte", () =
969
1109
  mkdirSync(dirname(installedReviewRisk), { recursive: true });
970
1110
  writeFileSync(installedReviewRisk, editedLegacySource);
971
1111
 
972
- installSddAssets(PACKAGE_ROOT, true);
1112
+ installPackageAssets(PACKAGE_ROOT, true);
973
1113
 
974
1114
  assert.deepEqual(readFileSync(installedReviewRisk), Buffer.from(editedLegacySource));
975
1115
  const manifest = JSON.parse(
@@ -1004,7 +1144,7 @@ test("forced package installation refreshes an asset recorded as package-managed
1004
1144
 
1005
1145
  try {
1006
1146
  process.env.GENTLE_PI_AGENT_HOME = temporaryAgentHome;
1007
- installSddAssets(PACKAGE_ROOT, true);
1147
+ installPackageAssets(PACKAGE_ROOT, true);
1008
1148
  assert.ok(existsSync(installedExemplar), "a missing package asset must install");
1009
1149
  assert.ok(
1010
1150
  existsSync(managedAssetsManifest),
@@ -1020,7 +1160,7 @@ test("forced package installation refreshes an asset recorded as package-managed
1020
1160
  );
1021
1161
  writeFileSync(managedAssetsManifest, JSON.stringify(manifest, null, 2));
1022
1162
 
1023
- installSddAssets(PACKAGE_ROOT, true);
1163
+ installPackageAssets(PACKAGE_ROOT, true);
1024
1164
 
1025
1165
  const refreshed = readAgentDefinition(installedExemplar);
1026
1166
  assert.deepEqual(refreshed.tools, MANAGED_EXEMPLAR_TOOLS);
@@ -1050,13 +1190,13 @@ function assertManagedAgentUserEditIsPreserved(
1050
1190
 
1051
1191
  try {
1052
1192
  process.env.GENTLE_PI_AGENT_HOME = temporaryAgentHome;
1053
- installSddAssets(PACKAGE_ROOT, true);
1193
+ installPackageAssets(PACKAGE_ROOT, true);
1054
1194
  const installedSource = readFileSync(installedExemplar, "utf8");
1055
1195
  const userEditedSource = editSource(installedSource);
1056
1196
  assert.notEqual(userEditedSource, installedSource, `${editLabel} must alter the asset`);
1057
1197
  writeFileSync(installedExemplar, userEditedSource);
1058
1198
 
1059
- installSddAssets(PACKAGE_ROOT, true);
1199
+ installPackageAssets(PACKAGE_ROOT, true);
1060
1200
 
1061
1201
  assert.deepEqual(
1062
1202
  readFileSync(installedExemplar),
@@ -1102,8 +1242,8 @@ test("forced package installation preserves a thinking-only edit to a managed ag
1102
1242
  test("forced package installation preserves an ordinary body edit to a managed agent", () => {
1103
1243
  assertManagedAgentUserEditIsPreserved("an ordinary body edit", (source) =>
1104
1244
  source.replace(
1105
- "You are the read-only explorer for generic non-SDD work.",
1106
- "Preserve this user-authored body change. You are the read-only explorer for generic non-SDD work.",
1245
+ "You are the read-only explorer for generic ODD work.",
1246
+ "Preserve this user-authored body change. You are the read-only explorer for generic ODD work.",
1107
1247
  ),
1108
1248
  );
1109
1249
  });
@@ -1122,7 +1262,7 @@ test("package model assignment keeps only package-managed agents owned", () => {
1122
1262
 
1123
1263
  try {
1124
1264
  process.env.GENTLE_PI_AGENT_HOME = temporaryAgentHome;
1125
- installSddAssets(PACKAGE_ROOT, true);
1265
+ installPackageAssets(PACKAGE_ROOT, true);
1126
1266
  writeFileSync(userAgent, userAgentSource);
1127
1267
 
1128
1268
  applyModelConfig(PACKAGE_ROOT, {
@@ -1151,7 +1291,7 @@ test("package model assignment keeps only package-managed agents owned", () => {
1151
1291
  "routing an arbitrary user agent must not relabel it as package-owned",
1152
1292
  );
1153
1293
 
1154
- installSddAssets(PACKAGE_ROOT, true);
1294
+ installPackageAssets(PACKAGE_ROOT, true);
1155
1295
  assert.equal(
1156
1296
  readFileSync(installedExemplar, "utf8"),
1157
1297
  readFileSync(join(PACKAGE_ROOT, "assets", "agents", MANAGED_EXEMPLAR_FILE), "utf8"),
@@ -1182,20 +1322,23 @@ test("jd-fix-agent packaged allowlist includes write tools", () => {
1182
1322
  }
1183
1323
  });
1184
1324
 
1185
- test("sdd-explore packages its CodeGraph-enabled exploration allowlist", () => {
1186
- const agentPath = join(PACKAGE_ROOT, "assets", "agents", "sdd-explore.md");
1187
- const { name, tools } = readAgentDefinition(agentPath);
1188
-
1189
- assert.equal(name, "sdd-explore");
1190
- assert.deepEqual(tools, [
1191
- "read",
1192
- "grep",
1193
- "find",
1194
- "codegraph",
1195
- "edit",
1196
- "write",
1197
- "mem_save",
1198
- ]);
1325
+ test("deleted SDD definitions remain absent from the installed catalog", () => {
1326
+ const retired = [
1327
+ ...[
1328
+ "apply", "archive", "design", "explore", "init", "onboard", "proposal",
1329
+ "remediate", "research", "spec", "status", "tasks", "verify",
1330
+ ].map(name => `agents/sdd-${name}.md`),
1331
+ "chains/sdd-full.chain.md", "chains/sdd-plan.chain.md", "chains/sdd-verify.chain.md",
1332
+ "gentle-ai/support/sdd-status-contract.md",
1333
+ ];
1334
+ withIsolatedAssetHome(agentHome => {
1335
+ installPackageAssets(agentHome, false);
1336
+ for (const key of retired) {
1337
+ assert.equal(getPackageAssetOwner(key), undefined, key);
1338
+ assert.equal(installedAssetManifest(agentHome).assets[key], undefined, key);
1339
+ assert.equal(existsSync(join(agentHome, key)), false, key);
1340
+ }
1341
+ });
1199
1342
  });
1200
1343
 
1201
1344
  test("gentle-ai-worker packages the exact scoped writer contract", () => {
@@ -1296,8 +1439,11 @@ test("gentle-ai-worker packages the exact scoped writer contract", () => {
1296
1439
  assert.match(memorySafety, /raw untrusted repository/);
1297
1440
 
1298
1441
  const testDiscipline = readMarkdownSection(source, "Test discipline");
1299
- assert.match(testDiscipline, /Strict TDD is active/);
1300
- assert.match(testDiscipline, /not active/);
1442
+ assert.match(testDiscipline, /Apply the ODD test-first policy by default for behavior changes with applicable runnable deterministic tests and a clear expected outcome/);
1443
+ assert.match(testDiscipline, /Test presence alone does not establish applicability; no TUI toggle or per-task chat choice is needed/);
1444
+ assert.match(testDiscipline, /RED[\s\S]*GREEN[\s\S]*TRIANGULATE[\s\S]*REFACTOR/);
1445
+ assert.match(testDiscipline, /no meaningful RED[\s\S]*proportionate ordinary functional or structural verification/);
1446
+ assert.match(testDiscipline, /Never claim RED\/GREEN evidence that was not observed/);
1301
1447
  assert.match(
1302
1448
  testDiscipline,
1303
1449
  /Broad suites, builds, formatters, or linters may run only when explicitly authorized by the parent\./,
@@ -1306,13 +1452,13 @@ test("gentle-ai-worker packages the exact scoped writer contract", () => {
1306
1452
  assert.doesNotMatch(testDiscipline, /clearly required by the repository contract/);
1307
1453
  });
1308
1454
 
1309
- test("installSddAssets installs gentle-ai-worker with a loader-compatible scoped identity", () => {
1455
+ test("package installation gives gentle-ai-worker a loader-compatible scoped identity", () => {
1310
1456
  const temporaryAgentHome = mkdtempSync(join(tmpdir(), "gentle-pi-agent-home-"));
1311
1457
  const previousAgentHome = process.env.GENTLE_PI_AGENT_HOME;
1312
1458
 
1313
1459
  try {
1314
1460
  process.env.GENTLE_PI_AGENT_HOME = temporaryAgentHome;
1315
- installSddAssets(PACKAGE_ROOT, true);
1461
+ installPackageAssets(PACKAGE_ROOT, true, ["delegation"]);
1316
1462
 
1317
1463
  const installedAgentsDir = join(temporaryAgentHome, "agents");
1318
1464
  const installedAgentPath = join(installedAgentsDir, "gentle-ai-worker.md");
@@ -1398,7 +1544,7 @@ test("asset installation uses PI_CODING_AGENT_DIR as the Pi agent home when no e
1398
1544
  delete process.env.GENTLE_PI_AGENT_HOME;
1399
1545
  process.env.PI_CODING_AGENT_DIR = temporaryPiAgentDir;
1400
1546
 
1401
- installSddAssets(PACKAGE_ROOT, true);
1547
+ installPackageAssets(PACKAGE_ROOT, true, ["delegation"]);
1402
1548
 
1403
1549
  const installedPath = join(temporaryPiAgentDir, "agents", "gentle-ai-explore.md");
1404
1550
  assert.ok(existsSync(installedPath), "managed agents must install where Pi Subagents reads global definitions");
@@ -1409,7 +1555,7 @@ test("asset installation uses PI_CODING_AGENT_DIR as the Pi agent home when no e
1409
1555
  );
1410
1556
 
1411
1557
  process.env.GENTLE_PI_AGENT_HOME = explicitGentleHome;
1412
- installSddAssets(PACKAGE_ROOT, true);
1558
+ installPackageAssets(PACKAGE_ROOT, true, ["delegation"]);
1413
1559
  assert.ok(
1414
1560
  existsSync(join(explicitGentleHome, "agents", "gentle-ai-explore.md")),
1415
1561
  "GENTLE_PI_AGENT_HOME remains the explicit test/operator override",
@@ -1433,7 +1579,7 @@ test("global model routing uses PI_CODING_AGENT_DIR for package-installed agents
1433
1579
  try {
1434
1580
  delete process.env.GENTLE_PI_AGENT_HOME;
1435
1581
  process.env.PI_CODING_AGENT_DIR = temporaryPiAgentDir;
1436
- installSddAssets(PACKAGE_ROOT, true);
1582
+ installPackageAssets(PACKAGE_ROOT, true, ["delegation"]);
1437
1583
 
1438
1584
  const result = applyModelConfig(temporaryProject, {
1439
1585
  "gentle-ai-explore": { model: "provider/model", thinking: "high" },
@@ -1467,7 +1613,7 @@ test("normal and forced installation copy generic agents with complete role cont
1467
1613
  const temporaryAgentHome = mkdtempSync(join(tmpdir(), "gentle-pi-generic-agents-"));
1468
1614
  process.env.GENTLE_PI_AGENT_HOME = temporaryAgentHome;
1469
1615
  try {
1470
- installSddAssets(PACKAGE_ROOT, force);
1616
+ installPackageAssets(PACKAGE_ROOT, force, ["delegation"]);
1471
1617
 
1472
1618
  for (const [name, tools] of Object.entries(expectedTools)) {
1473
1619
  const packagedPath = join(PACKAGE_ROOT, "assets", "agents", `${name}.md`);
@@ -1476,7 +1622,7 @@ test("normal and forced installation copy generic agents with complete role cont
1476
1622
  assert.equal(source, readFileSync(packagedPath, "utf8"));
1477
1623
  assert.equal(installedName, name);
1478
1624
  assert.deepEqual(installedTools, tools);
1479
- assert.match(source, /generic non-SDD work/);
1625
+ assert.match(source, /generic ODD work/);
1480
1626
  assert.match(source, /Do not (?:fix findings, delegate to child agents|delegate to child agents, commit)/);
1481
1627
  if (name === "gentle-ai-explore") {
1482
1628
  assert.match(source, /cwd-scoped `codegraph` tool/);
@@ -1488,7 +1634,7 @@ test("normal and forced installation copy generic agents with complete role cont
1488
1634
  }
1489
1635
  assert.match(source, /Do not (?:edit, write|edit, write, or fix findings)/);
1490
1636
  assert.match(source, /compressed (?:handoff|evidence handoff)/);
1491
- assert.match(source, /Do not use SDD phase protocols or review lenses\./);
1637
+ assert.match(source, /Do not use review lenses\. RDD review remains independent and parent-owned\./);
1492
1638
  if (name === "gentle-ai-verify") {
1493
1639
  assert.match(source, /exact test, build, or lint commands explicitly authorized by the parent/);
1494
1640
  assert.match(source, /only outputs the parent explicitly identified as expected/);
@@ -1531,14 +1677,10 @@ test("bounded implementation routing uses the same explicit fallback in both pol
1531
1677
  test("orchestrator routes generic roles without static RDD lens routing", () => {
1532
1678
  for (const file of ["orchestrator.md", "orchestrator-delegation.md"]) {
1533
1679
  const routing = readFileSync(join(PACKAGE_ROOT, "assets", file), "utf8");
1534
- assert.match(routing, /generic non-SDD exploration[\s\S]*`gentle-ai-explore`/);
1535
- assert.match(
1536
- routing,
1537
- /bounded (?:non-SDD )?(?:implementation|multi-file writes)[\s\S]*`gentle-ai-worker`/,
1538
- );
1539
- assert.match(routing, /generic non-SDD (?:technical )?verification[\s\S]*`gentle-ai-verify`/);
1540
- assert.match(routing, /SDD roles stay inside SDD|Use `sdd-explore` and `sdd-verify` only inside SDD/);
1541
- assert.match(routing, /(?:truly local )?read-only check(?:ing)? of (?:known )?1[-–]3 known files|1[-–]3-file read-only check/);
1680
+ assert.match(routing, /`gentle-ai-explore`/);
1681
+ assert.match(routing, /`gentle-ai-worker`/);
1682
+ assert.match(routing, /`gentle-ai-verify`/);
1683
+ assert.match(routing, /read-only check within the evidence budget/);
1542
1684
  assert.match(routing, /(?:verification that |verification commands →).*executes? or delegates?|executing\/delegating verification commands/);
1543
1685
  assert.match(routing, /missing(?: or |\/)unusable[\s\S]*native `Agent`[\s\S]*(?:the )?same read-only/);
1544
1686
  assert.match(routing, /report (?:the )?fallback/);
@@ -1563,13 +1705,10 @@ test("pi-pretty wrapper uses cached ESM loading for compiled and pnpm symlink in
1563
1705
  assert.match(wrapper, /quietToolsEnabled/);
1564
1706
  });
1565
1707
 
1566
- test("Gentle Shell v3.7.0 package manifest declares the release version", () => {
1708
+ test("Gentle Shell v4.0.0 package manifest declares the release version", () => {
1567
1709
  const packageJson = readPackageJson();
1568
- assert.equal(packageJson.version, "3.7.0", "the release manifest must be explicitly pinned to v3.7.0");
1569
- assert.equal(
1570
- packageJson.scripts?.test,
1571
- "node --experimental-strip-types --test tests/*.test.ts && pnpm run check:provider-contract && pnpm run test:harness",
1572
- );
1710
+ assert.equal(packageJson.version, "4.0.0", "the release manifest must be explicitly pinned to v4.0.0");
1711
+ assert.equal(packageJson.scripts?.test, "node scripts/run-test-suite.mjs");
1573
1712
  assert.ok(packageJson.files?.includes("assets/"));
1574
1713
  assert.ok(packageJson.files?.includes("contracts/"));
1575
1714
 
@@ -1610,13 +1749,13 @@ test("technical reference documents dynamic Gentle AI RDD ownership and the inst
1610
1749
  });
1611
1750
 
1612
1751
 
1613
- test("package verification explicitly requires the managed remediation actor", () => {
1614
- assert.match(readFileSync(join(PACKAGE_ROOT, "scripts/verify-package-files.mjs"), "utf8"), /assets\/agents\/sdd-remediate\.md/);
1752
+ test("package verification no longer requires the retired remediation actor", () => {
1753
+ assert.doesNotMatch(readFileSync(join(PACKAGE_ROOT, "scripts/verify-package-files.mjs"), "utf8"), /^\s*"assets\/agents\/sdd-remediate\.md",?$/m);
1615
1754
  });
1616
1755
 
1617
- test("SDD installation retires owned sync but preserves modified copies and unrelated owners", () => {
1756
+ test("package installation retires owned sync but preserves modified copies and unrelated owners", () => {
1618
1757
  withIsolatedAssetHome((agentHome) => {
1619
- installSddAssets(agentHome, false);
1758
+ installPackageAssets(agentHome, false);
1620
1759
  const path = join(agentHome, "agents/sdd-sync.md");
1621
1760
  const manifestPath = join(agentHome, "gentle-ai/managed-assets.json");
1622
1761
  const legacy = "Previously managed sync executor\n";
@@ -1625,13 +1764,11 @@ test("SDD installation retires owned sync but preserves modified copies and unre
1625
1764
  writeFileSync(path, legacy);
1626
1765
  writeFileSync(manifestPath, JSON.stringify(manifest));
1627
1766
  installPackageAssets(agentHome, true, ["delegation"]);
1628
- assert.equal(readFileSync(path, "utf8"), legacy);
1629
- installPackageAssets(agentHome, true, ["sdd"]);
1630
1767
  assert.equal(existsSync(path), false);
1631
1768
  assert.equal(installedAssetManifest(agentHome).assets["agents/sdd-sync.md"], undefined);
1632
1769
  writeFileSync(path, "User-modified sync instructions\n");
1633
1770
  writeFileSync(manifestPath, JSON.stringify(manifest));
1634
- installPackageAssets(agentHome, true, ["sdd"]);
1771
+ installPackageAssets(agentHome, true, ["delegation"]);
1635
1772
  assert.equal(readFileSync(path, "utf8"), "User-modified sync instructions\n");
1636
1773
  assert.equal(installedAssetManifest(agentHome).assets["agents/sdd-sync.md"], undefined);
1637
1774
  });