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
@@ -17,6 +17,7 @@ import {
17
17
  decodeReviewStartV4,
18
18
  decodeReviewStatusV3,
19
19
  } from "../lib/review-integration-v2.ts";
20
+ import { NativeReviewCliV216, type ExecFileAdapter } from "../lib/native-review-cli.ts";
20
21
 
21
22
  const DEV_FIXTURES = join(process.cwd(), "tests", "fixtures", "devbinary");
22
23
  const V2_FIXTURES = join(process.cwd(), "contracts", "review-integration", "v2", "fixtures");
@@ -556,6 +557,205 @@ test("status/v6 decodes and enforces the intended-untracked selection submission
556
557
  }
557
558
  });
558
559
 
560
+ function queuedTargetStatusAdapter(): { adapter: ExecFileAdapter; calls: string[][] } {
561
+ const calls: string[][] = [];
562
+ return {
563
+ calls,
564
+ adapter: async (request) => {
565
+ calls.push([...request.arguments]);
566
+ return { stdout: JSON.stringify(currentStatusFixture("status-v5.captured.json")), stderr: "", exitCode: 0, signal: null, timedOut: false, outputLimitExceeded: false };
567
+ },
568
+ };
569
+ }
570
+
571
+ function targetStatusClient(adapter: ExecFileAdapter): NativeReviewCliV216 {
572
+ return new NativeReviewCliV216(adapter, "/package/.gentle-ai/gentle-ai", 30_000, 1024 * 1024);
573
+ }
574
+
575
+ const SUBMITTED_PROVIDER_TOKENS = ["--contract=gentle-ai.review-integration/v2", "--next-transition=true", "--agent=pi", "--projection=workspace", "--intended-untracked-selection={{value}}"];
576
+
577
+ test("targetStatus appends the forwarded base-ref/committed-only pair after intended-untracked submission tokens", async () => {
578
+ const queue = queuedTargetStatusAdapter();
579
+ await targetStatusClient(queue.adapter).targetStatus({
580
+ cwd: "/repo",
581
+ baseRef: "deadbeef",
582
+ committedOnly: true,
583
+ intendedUntrackedSelection: { argumentTokens: SUBMITTED_PROVIDER_TOKENS, value: '{"selection":true}' },
584
+ });
585
+ assert.deepEqual(queue.calls[0], [
586
+ "review", "status", "--cwd", "/repo",
587
+ "--contract=gentle-ai.review-integration/v2", "--next-transition=true", "--agent=pi", "--projection=workspace", '--intended-untracked-selection={"selection":true}',
588
+ "--base-ref", "deadbeef", "--committed-only",
589
+ ]);
590
+ });
591
+
592
+ test("targetStatus leaves the intended-untracked submission argv unchanged when no baseRef is forwarded", async () => {
593
+ const queue = queuedTargetStatusAdapter();
594
+ await targetStatusClient(queue.adapter).targetStatus({
595
+ cwd: "/repo",
596
+ intendedUntrackedSelection: { argumentTokens: SUBMITTED_PROVIDER_TOKENS, value: '{"selection":true}' },
597
+ });
598
+ assert.deepEqual(queue.calls[0], [
599
+ "review", "status", "--cwd", "/repo",
600
+ "--contract=gentle-ai.review-integration/v2", "--next-transition=true", "--agent=pi", "--projection=workspace", '--intended-untracked-selection={"selection":true}',
601
+ ]);
602
+ });
603
+
604
+ test("targetStatus rejects a forwarded baseRef that conflicts with a base-ref the submission already carries", async () => {
605
+ const queue = queuedTargetStatusAdapter();
606
+ const tokensWithBaseRef = [...SUBMITTED_PROVIDER_TOKENS, "--base-ref=cafebabe"];
607
+ await assert.rejects(
608
+ () => targetStatusClient(queue.adapter).targetStatus({
609
+ cwd: "/repo", baseRef: "deadbeef", committedOnly: true,
610
+ intendedUntrackedSelection: { argumentTokens: tokensWithBaseRef, value: '{"selection":true}' },
611
+ }),
612
+ TypeError,
613
+ );
614
+ assert.equal(queue.calls.length, 0);
615
+
616
+ // Same value on both sides is not a conflict, but the submission still lacks
617
+ // --committed-only: T3 appends it so the selector stays complete.
618
+ const sameValueQueue = queuedTargetStatusAdapter();
619
+ await targetStatusClient(sameValueQueue.adapter).targetStatus({
620
+ cwd: "/repo", baseRef: "cafebabe", committedOnly: true,
621
+ intendedUntrackedSelection: { argumentTokens: tokensWithBaseRef, value: '{"selection":true}' },
622
+ });
623
+ assert.deepEqual(sameValueQueue.calls[0], [
624
+ "review", "status", "--cwd", "/repo",
625
+ "--contract=gentle-ai.review-integration/v2", "--next-transition=true", "--agent=pi", "--projection=workspace", '--intended-untracked-selection={"selection":true}',
626
+ "--base-ref=cafebabe",
627
+ "--committed-only",
628
+ ]);
629
+ });
630
+
631
+ test("targetStatus appends --committed-only when the submission's same base-ref is missing it, and skips it when already present", async () => {
632
+ const missingQueue = queuedTargetStatusAdapter();
633
+ const tokensWithBaseRefOnly = [...SUBMITTED_PROVIDER_TOKENS, "--base-ref", "cafebabe"];
634
+ await targetStatusClient(missingQueue.adapter).targetStatus({
635
+ cwd: "/repo", baseRef: "cafebabe", committedOnly: true,
636
+ intendedUntrackedSelection: { argumentTokens: tokensWithBaseRefOnly, value: '{"selection":true}' },
637
+ });
638
+ assert.deepEqual(missingQueue.calls[0], [
639
+ "review", "status", "--cwd", "/repo",
640
+ "--contract=gentle-ai.review-integration/v2", "--next-transition=true", "--agent=pi", "--projection=workspace", '--intended-untracked-selection={"selection":true}',
641
+ "--base-ref", "cafebabe",
642
+ "--committed-only",
643
+ ]);
644
+
645
+ const presentQueue = queuedTargetStatusAdapter();
646
+ const tokensWithBoth = [...SUBMITTED_PROVIDER_TOKENS, "--base-ref=cafebabe", "--committed-only=true"];
647
+ await targetStatusClient(presentQueue.adapter).targetStatus({
648
+ cwd: "/repo", baseRef: "cafebabe", committedOnly: true,
649
+ intendedUntrackedSelection: { argumentTokens: tokensWithBoth, value: '{"selection":true}' },
650
+ });
651
+ assert.deepEqual(presentQueue.calls[0], [
652
+ "review", "status", "--cwd", "/repo",
653
+ "--contract=gentle-ai.review-integration/v2", "--next-transition=true", "--agent=pi", "--projection=workspace", '--intended-untracked-selection={"selection":true}',
654
+ "--base-ref=cafebabe", "--committed-only=true",
655
+ ]);
656
+
657
+ const committedOnlyQueue = queuedTargetStatusAdapter();
658
+ const tokensWithCommittedOnly = [...SUBMITTED_PROVIDER_TOKENS, "--committed-only"];
659
+ await targetStatusClient(committedOnlyQueue.adapter).targetStatus({
660
+ cwd: "/repo", baseRef: "cafebabe", committedOnly: true,
661
+ intendedUntrackedSelection: { argumentTokens: tokensWithCommittedOnly, value: '{"selection":true}' },
662
+ });
663
+ assert.deepEqual(committedOnlyQueue.calls[0], [
664
+ "review", "status", "--cwd", "/repo",
665
+ "--contract=gentle-ai.review-integration/v2", "--next-transition=true", "--agent=pi", "--projection=workspace", '--intended-untracked-selection={"selection":true}',
666
+ "--committed-only",
667
+ "--base-ref", "cafebabe",
668
+ ]);
669
+ });
670
+
671
+ test("targetStatus forwards --lineage on the submitted branch, skips a matching value, and rejects a conflicting one", async () => {
672
+ const forwardedQueue = queuedTargetStatusAdapter();
673
+ await targetStatusClient(forwardedQueue.adapter).targetStatus({
674
+ cwd: "/repo", lineageId: "review-abc123",
675
+ intendedUntrackedSelection: { argumentTokens: SUBMITTED_PROVIDER_TOKENS, value: '{"selection":true}' },
676
+ });
677
+ assert.deepEqual(forwardedQueue.calls[0], [
678
+ "review", "status", "--cwd", "/repo",
679
+ "--contract=gentle-ai.review-integration/v2", "--next-transition=true", "--agent=pi", "--projection=workspace", '--intended-untracked-selection={"selection":true}',
680
+ "--lineage", "review-abc123",
681
+ ]);
682
+
683
+ const sameValueQueue = queuedTargetStatusAdapter();
684
+ const tokensWithLineage = [...SUBMITTED_PROVIDER_TOKENS, "--lineage=review-abc123"];
685
+ await targetStatusClient(sameValueQueue.adapter).targetStatus({
686
+ cwd: "/repo", lineageId: "review-abc123",
687
+ intendedUntrackedSelection: { argumentTokens: tokensWithLineage, value: '{"selection":true}' },
688
+ });
689
+ assert.deepEqual(sameValueQueue.calls[0], [
690
+ "review", "status", "--cwd", "/repo",
691
+ "--contract=gentle-ai.review-integration/v2", "--next-transition=true", "--agent=pi", "--projection=workspace", '--intended-untracked-selection={"selection":true}',
692
+ "--lineage=review-abc123",
693
+ ]);
694
+
695
+ const conflictQueue = queuedTargetStatusAdapter();
696
+ await assert.rejects(
697
+ () => targetStatusClient(conflictQueue.adapter).targetStatus({
698
+ cwd: "/repo", lineageId: "review-other",
699
+ intendedUntrackedSelection: { argumentTokens: tokensWithLineage, value: '{"selection":true}' },
700
+ }),
701
+ TypeError,
702
+ );
703
+ assert.equal(conflictQueue.calls.length, 0);
704
+ });
705
+
706
+ test("targetStatus forwards base-ref and lineage together on the submitted branch, base-ref first", async () => {
707
+ const queue = queuedTargetStatusAdapter();
708
+ await targetStatusClient(queue.adapter).targetStatus({
709
+ cwd: "/repo", baseRef: "deadbeef", committedOnly: true, lineageId: "review-abc123",
710
+ intendedUntrackedSelection: { argumentTokens: SUBMITTED_PROVIDER_TOKENS, value: '{"selection":true}' },
711
+ });
712
+ assert.deepEqual(queue.calls[0], [
713
+ "review", "status", "--cwd", "/repo",
714
+ "--contract=gentle-ai.review-integration/v2", "--next-transition=true", "--agent=pi", "--projection=workspace", '--intended-untracked-selection={"selection":true}',
715
+ "--base-ref", "deadbeef", "--committed-only",
716
+ "--lineage", "review-abc123",
717
+ ]);
718
+ });
719
+
720
+ test("targetStatus rejects a dangling or empty --base-ref/--lineage in the submitted tokens", async () => {
721
+ for (const tokens of [
722
+ [...SUBMITTED_PROVIDER_TOKENS, "--base-ref"],
723
+ [...SUBMITTED_PROVIDER_TOKENS, "--base-ref="],
724
+ [...SUBMITTED_PROVIDER_TOKENS, "--lineage"],
725
+ [...SUBMITTED_PROVIDER_TOKENS, "--lineage="],
726
+ ]) {
727
+ const queue = queuedTargetStatusAdapter();
728
+ await assert.rejects(
729
+ () => targetStatusClient(queue.adapter).targetStatus({
730
+ cwd: "/repo",
731
+ intendedUntrackedSelection: { argumentTokens: tokens, value: '{"selection":true}' },
732
+ }),
733
+ TypeError,
734
+ JSON.stringify(tokens),
735
+ );
736
+ assert.equal(queue.calls.length, 0, JSON.stringify(tokens));
737
+ }
738
+ });
739
+
740
+ test("targetStatus rejects ambiguous or malformed provider selectors before executing STATUS", async () => {
741
+ for (const tokens of [
742
+ [...SUBMITTED_PROVIDER_TOKENS, "--committed-only=false"],
743
+ [...SUBMITTED_PROVIDER_TOKENS, "--committed-only=garbage"],
744
+ [...SUBMITTED_PROVIDER_TOKENS, "--committed-only=true", "--committed-only=true"],
745
+ [...SUBMITTED_PROVIDER_TOKENS, "--committed-only", "--committed-only=true"],
746
+ [...SUBMITTED_PROVIDER_TOKENS, "--base-ref=deadbeef", "--base-ref", "deadbeef"],
747
+ [...SUBMITTED_PROVIDER_TOKENS, "--lineage=review-a", "--lineage", "review-a"],
748
+ [...SUBMITTED_PROVIDER_TOKENS, "--base-ref", "--lineage=review-a"],
749
+ [...SUBMITTED_PROVIDER_TOKENS, "--lineage", "--base-ref=deadbeef"],
750
+ ]) {
751
+ const queue = queuedTargetStatusAdapter();
752
+ await assert.rejects(() => targetStatusClient(queue.adapter).targetStatus({
753
+ cwd: "/repo", intendedUntrackedSelection: { argumentTokens: tokens, value: '{"selection":true}' },
754
+ }), TypeError, JSON.stringify(tokens));
755
+ assert.equal(queue.calls.length, 0, JSON.stringify(tokens));
756
+ }
757
+ });
758
+
559
759
  test("START/v4 accepts only its reviewing status continuation and preserves v3 strictness", () => {
560
760
  for (const action of ["created", "replayed"] as const) {
561
761
  const start = reviewingStartV4(action);
@@ -21,16 +21,7 @@ const GENTLE_SKILL = "skills/gentle-ai/SKILL.md";
21
21
  const README = "README.md";
22
22
  const TECHNICAL_REFERENCE = "docs/readme-reference.md";
23
23
  const CHAIN = "assets/chains/4r-review.chain.md";
24
- const SDD_WORKFLOW = "assets/sdd-orchestrator-workflow.md";
25
24
  const WORKER = "assets/agents/gentle-ai-worker.md";
26
- const CANONICAL_LIFECYCLE_SPECS = [
27
- "openspec/specs/review-orchestration/spec.md",
28
- "openspec/specs/review-transaction/spec.md",
29
- ] as const;
30
- const HISTORICAL_LIFECYCLE_SPECS = [
31
- "openspec/changes/complete-native-review-lifecycle/specs/review-orchestration/spec.md",
32
- "openspec/changes/complete-native-review-lifecycle/specs/review-transaction/spec.md",
33
- ] as const;
34
25
 
35
26
  function read(path: string): string {
36
27
  return readFileSync(join(ROOT, path), "utf8");
@@ -85,7 +76,6 @@ const JUDGMENT_DAY_SEMANTIC_SURFACES = [
85
76
  ...JUDGES,
86
77
  FIX_AGENT,
87
78
  "assets/orchestrator-delegation.md",
88
- SDD_WORKFLOW,
89
79
  ] as const;
90
80
 
91
81
  const JUDGMENT_DAY_REJUDGMENT_PATTERNS = [
@@ -240,25 +230,14 @@ test("ordinary lens prompts contain the literal compact-v2 native result envelop
240
230
  }
241
231
  });
242
232
 
243
- test("canonical ordinary review specs preserve the negotiated one-correction contract", () => {
244
- for (const path of CANONICAL_LIFECYCLE_SPECS) {
245
- const content = read(path);
246
- assert.match(content, /one correction transaction/i, path);
247
- assert.match(content, /original.*budget|budget.*original/i, path);
248
- assert.match(content, /never reruns initial lenses|without rerunning initial (?:lenses|review)/i, path);
249
- assert.match(content, /correction_required/, path);
250
- assert.match(content, /failure escalates|failed.*escalates|MUST escalate/i, path);
251
- assert.match(content, /forecast/i, path);
252
- assert.doesNotMatch(content, /up to three failed targeted attempts|third failed attempt/i, path);
253
- }
254
- });
255
-
256
- test("historical lifecycle change specs preserve their completed one-attempt design context", () => {
257
- for (const path of HISTORICAL_LIFECYCLE_SPECS) {
258
- const content = read(path);
259
- assert.match(content, /at most one correction|one correction batch|After the one correction|GIVEN one exact ordinary correction|one validator and one final verification/i, path);
260
- assert.doesNotMatch(content, /up to three failed targeted attempts/i, path);
261
- }
233
+ test("retained provider review contract preserves bounded correction without declaring approval", () => {
234
+ const content = read(CANONICAL);
235
+ assert.match(content, /one correction transaction/i);
236
+ assert.match(content, /original budget/i);
237
+ assert.match(content, /`correction_required`/);
238
+ assert.doesNotMatch(content, /up to three failed targeted attempts|third failed attempt/i);
239
+ assert.match(content, /`reviewing`, `correction_required`, `validating`, `approved`, and `escalated`/);
240
+ assert.match(content, /Only severe `introduced`, `behavior-activated`, or `worsened` findings with valid proof can enter `correction_ids`/);
262
241
  });
263
242
 
264
243
  test("risk lens distinguishes trusted orchestration from concrete boundary bypasses", () => {
@@ -355,15 +334,13 @@ test("Judgment Day fix routing has one canonical shape and never falls back to g
355
334
  [JD_SKILL, read(JD_SKILL)],
356
335
  [JD_PROMPTS, fencedBlock(JD_PROMPTS, "## Fix Agent Prompt")],
357
336
  ["assets/orchestrator-delegation.md", read("assets/orchestrator-delegation.md")],
358
- [SDD_WORKFLOW, read(SDD_WORKFLOW)],
359
337
  ] as const) {
360
338
  assert.ok(content.includes(canonicalShape), `${path} must carry the canonical Judgment Day fix shape`);
361
339
  assert.match(content, /requires no graph-v1 or native review lineage/i);
362
340
  }
363
- const routing = `${read("assets/orchestrator-delegation.md")}\n${read(SDD_WORKFLOW)}`;
341
+ const routing = read("assets/orchestrator-delegation.md");
364
342
  assert.match(routing, /Judgment Day phase roles are never generic fallbacks\./);
365
343
  assert.match(routing, /If the generic writer chain is unavailable, use the documented native generic fallback or stop\./);
366
- assert.match(read(SDD_WORKFLOW), /\| default\s+\| balanced\s+\| SDD phase fallback; never a Judgment Day role\s+\|/);
367
344
  });
368
345
 
369
346
  test("orchestrator, injected skill, and technical reference defer RDD lifecycle ownership to Gentle AI", () => {
@@ -399,13 +376,12 @@ test("technical reference documents the dynamic runtime authority boundary witho
399
376
  });
400
377
 
401
378
  test("managed contracts retain no fresh lifecycle review directive", () => {
402
- const managed = union([...ORCHESTRATOR, SDD_WORKFLOW, WORKER, GENTLE_SKILL, README]);
379
+ const managed = union([...ORCHESTRATOR, WORKER, GENTLE_SKILL, README]);
403
380
  for (const obsolete of [
404
381
  "A fresh review still follows delegated implementation.",
405
382
  "run a fresh-context review lens unless",
406
383
  "Run a fresh review before pushing a code release",
407
384
  ]) assert.ok(!managed.includes(obsolete), `managed contracts retain ${obsolete}`);
408
- assert.match(read(SDD_WORKFLOW), /SDD phase validation does not start ordinary review or Judgment Day/);
409
385
  });
410
386
 
411
387
  test("static 4R chain runs each selected lens once and owns no orchestration", () => {
@@ -1,6 +1,6 @@
1
1
  import assert from "node:assert/strict";
2
2
  import test from "node:test";
3
- import { consumeReviewMutation, pendingReviewMutation, recordReviewMutation, REVIEW_REMINDER_RECEIPT } from "../lib/review-reminder-receipt.ts";
3
+ import { consumeReviewMutation, pendingReviewMutation, pendingReviewMutationProfiles, recordReviewMutation, REVIEW_REMINDER_RECEIPT } from "../lib/review-reminder-receipt.ts";
4
4
 
5
5
  function fixture() {
6
6
  let sessionId = "parent";
@@ -60,3 +60,49 @@ test("malformed, unknown-version, and unmatched consumption entries do not estab
60
60
  consumeReviewMutation(f.host, f.session, "/repo", "unknown", "acknowledged", "target");
61
61
  assert.equal(pendingReviewMutation(f.session, "/repo"), captured);
62
62
  });
63
+
64
+ // gentle-pi#1175 (T2): mutation receipts optionally carry the writer profile
65
+ // the runtime resolved, so ASSESS never has to trust a model re-declaration.
66
+ test("mutation receipts validate with or without runtime writer profile fields, never with empty ones", () => {
67
+ const f = fixture();
68
+ recordReviewMutation(f.host, f.session, "/repo", { source: "direct", toolName: "write", toolCallId: "plain" });
69
+ recordReviewMutation(f.host, f.session, "/repo", { source: "direct", toolName: "edit", toolCallId: "profiled", writerModelId: "anthropic/claude-sonnet-5", writerEffort: "high" });
70
+ recordReviewMutation(f.host, f.session, "/repo", { source: "subagent", taskId: "task-1", toolName: "write", toolCallId: "model-only", writerModelId: "openai/gpt-5-mini" });
71
+ assert.equal(f.entries.length, 3);
72
+ assert.deepEqual(pendingReviewMutationProfiles(f.session, "/repo"), [
73
+ {},
74
+ { writerModelId: "anthropic/claude-sonnet-5", writerEffort: "high" },
75
+ { writerModelId: "openai/gpt-5-mini" },
76
+ ]);
77
+ const good = f.entries[1];
78
+ const data = good.data as Record<string, unknown>;
79
+ f.entries.length = 0;
80
+ for (const patch of [{ writerModelId: "" }, { writerEffort: " " }, { writerModelId: 1 }, { writerEffort: null }, { writerModel: "x" }]) {
81
+ f.entries.push({ ...good, data: { ...data, ...patch } });
82
+ }
83
+ assert.equal(pendingReviewMutation(f.session, "/repo"), undefined, "a present-but-empty or malformed profile field invalidates the receipt");
84
+ assert.deepEqual(pendingReviewMutationProfiles(f.session, "/repo"), []);
85
+ });
86
+
87
+ test("recording drops unknown or blank profile fields instead of dropping the mutation evidence", () => {
88
+ const f = fixture();
89
+ recordReviewMutation(f.host, f.session, "/repo", { source: "direct", toolName: "write", toolCallId: "a", writerModelId: "", writerEffort: undefined });
90
+ assert.equal(f.entries.length, 1);
91
+ assert.deepEqual(Object.keys(f.entries[0].data as object).sort(), ["id", "kind", "root", "sessionId", "source", "toolCallId", "toolName"]);
92
+ assert.ok(pendingReviewMutation(f.session, "/repo"));
93
+ assert.deepEqual(pendingReviewMutationProfiles(f.session, "/repo"), [{}]);
94
+ });
95
+
96
+ test("pending writer profiles follow the same consumption watermark as pendingReviewMutation", () => {
97
+ const f = fixture();
98
+ recordReviewMutation(f.host, f.session, "/repo", { source: "direct", toolName: "write", toolCallId: "a", writerModelId: "gpt-5-mini" });
99
+ const captured = pendingReviewMutation(f.session, "/repo");
100
+ recordReviewMutation(f.host, f.session, "/repo", { source: "direct", toolName: "write", toolCallId: "b", writerModelId: "claude-sonnet-5", writerEffort: "high" });
101
+ consumeReviewMutation(f.host, f.session, "/repo", captured, "acknowledged", "target");
102
+ assert.deepEqual(pendingReviewMutationProfiles(f.session, "/repo"), [{ writerModelId: "claude-sonnet-5", writerEffort: "high" }]);
103
+ assert.deepEqual(pendingReviewMutationProfiles(f.session, "/sibling"), []);
104
+ consumeReviewMutation(f.host, f.session, "/repo", pendingReviewMutation(f.session, "/repo"), "nudged", "target");
105
+ assert.deepEqual(pendingReviewMutationProfiles(f.session, "/repo"), []);
106
+ f.switchSession("other");
107
+ assert.deepEqual(pendingReviewMutationProfiles(f.session, "/repo"), []);
108
+ });