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
@@ -0,0 +1,261 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
6
+ import { captureReviewSessionIdentity, grantReviewSessionPermission, hasReviewSessionPermission, revokeReviewSessionPermission } from "../lib/review-session-standing-permission.ts";
7
+ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
8
+ import { createGentleAiExtension } from "../extensions/gentle-ai.ts";
9
+ import { YOLO_STATUS_TEXT } from "../lib/yolo-session-policy.ts";
10
+
11
+ const ON_TEXT = "🚀 YOLO ON 🔥 — destructive confirmations remain";
12
+ const USAGE = "Use /gentle:yolo enable|disable|status. State unchanged.";
13
+ const STALE_MENU = "YOLO unchanged — the session changed while the menu was open.";
14
+ const menuTitle = (state: "ON" | "OFF") => `🚀 Gentle YOLO 🔥 — full speed, destructive actions still ask (current: ${state})`;
15
+
16
+ export function harness(env: NodeJS.ProcessEnv = {}) {
17
+ const commands = new Map<string, { handler: (args: string, ctx: ExtensionContext) => Promise<void> }>();
18
+ const handlers = new Map<string, Array<(event: unknown, ctx: ExtensionContext) => unknown>>();
19
+ const tools: string[] = [];
20
+ const notices: string[] = [];
21
+ const statuses = new Map<string, string | undefined>();
22
+ const widgets = new Map<string, unknown>();
23
+ let confirmations = 0;
24
+ let sessionId = "yolo-test";
25
+ const ctx = {
26
+ cwd: process.cwd(), mode: "tui", hasUI: true,
27
+ sessionManager: { getSessionId: () => sessionId },
28
+ ui: {
29
+ notify: (text: string) => notices.push(text),
30
+ setStatus: (key: string, text?: string) => statuses.set(key, text),
31
+ setWidget: (key: string, value: unknown) => widgets.set(key, value),
32
+ confirm: async () => { confirmations++; return false; },
33
+ },
34
+ } as unknown as ExtensionContext;
35
+ const pi = {
36
+ on: (name: string, fn: (event: unknown, ctx: ExtensionContext) => unknown) => {
37
+ const list = handlers.get(name) ?? []; list.push(fn); handlers.set(name, list);
38
+ },
39
+ registerCommand: (name: string, command: { handler: (args: string, ctx: ExtensionContext) => Promise<void> }) => commands.set(name, command),
40
+ registerTool: (tool: { name: string }) => tools.push(tool.name),
41
+ registerMessageRenderer() {}, registerFlag() {},
42
+ events: { emit() {}, on: () => () => {} },
43
+ getActiveTools: () => [],
44
+ } as unknown as ExtensionAPI;
45
+ createGentleAiExtension({ nativeReviewCli: null, candidateViews: null, processEnv: env,
46
+ resolveTelemetryTriggerBinary: () => { throw new Error("disabled in test"); },
47
+ })(pi);
48
+ return { ctx, commands, tools, notices, statuses, widgets, confirmations: () => confirmations,
49
+ setSessionId: (id: string) => { sessionId = id; },
50
+ command: async (args: string) => { const cmd = commands.get("gentle:yolo"); assert.ok(cmd, "/gentle:yolo registered"); await cmd.handler(args, ctx); },
51
+ setSelect: (select: (title: string, options: string[]) => Promise<string | undefined>) => { Object.assign(ctx.ui, { select }); },
52
+ emit: async (name: string, event: unknown) => {
53
+ let result: unknown; for (const fn of handlers.get(name) ?? []) { const next = await fn(event, ctx); if (next !== undefined) result = next; } return result;
54
+ },
55
+ };
56
+ }
57
+
58
+ test("only /gentle:yolo is registered and the ON text carries the rocket and fire markers", () => {
59
+ const h = harness();
60
+ assert.equal(h.commands.has("yolo"), false, "bare yolo command is not registered");
61
+ assert.ok(h.commands.has("gentle:yolo"));
62
+ assert.equal(YOLO_STATUS_TEXT, ON_TEXT);
63
+ });
64
+
65
+ test("human command enable/disable/status/invalid and status/widget clearing", async () => {
66
+ const h = harness();
67
+ assert.equal(h.tools.some((name) => name.includes("yolo")), false);
68
+ await h.command("status"); assert.match(h.notices.at(-1)!, /OFF/);
69
+ await h.command("enable");
70
+ assert.equal(h.statuses.get("gentle:yolo"), ON_TEXT);
71
+ assert.deepEqual(h.widgets.get("gentle:yolo"), [ON_TEXT]);
72
+ for (const invalid of ["invalid", "on", "off", "toggle"]) {
73
+ await h.command(invalid);
74
+ assert.equal(h.notices.at(-1), USAGE);
75
+ assert.equal(h.statuses.get("gentle:yolo"), ON_TEXT, `${invalid} leaves state unchanged`);
76
+ }
77
+ await h.command("status"); assert.match(h.notices.at(-1)!, /ON/);
78
+ await h.command("disable"); assert.equal(h.statuses.get("gentle:yolo"), undefined);
79
+ assert.equal(h.widgets.get("gentle:yolo"), undefined);
80
+ await h.command("status"); assert.match(h.notices.at(-1)!, /OFF/);
81
+ });
82
+
83
+ test("no argument without a select-capable UI reports status and never toggles", async () => {
84
+ const h = harness();
85
+ await h.command(""); assert.equal(h.notices.at(-1), "YOLO OFF");
86
+ assert.equal(h.statuses.get("gentle:yolo"), undefined);
87
+ await h.command("enable");
88
+ await h.command(""); assert.equal(h.notices.at(-1), ON_TEXT);
89
+ assert.equal(h.statuses.get("gentle:yolo"), ON_TEXT);
90
+ let prompts = 0;
91
+ h.setSelect(async () => { prompts++; return "disable"; });
92
+ Object.assign(h.ctx, { hasUI: false });
93
+ await h.command("");
94
+ assert.equal(prompts, 0, "headless callers never see the menu");
95
+ assert.match(h.notices.at(-1)!, /OFF/, "headless status reflects fail-closed scope, not a menu choice");
96
+ });
97
+
98
+ test("no-argument menu shows the fire title and live state; only an explicit selection changes it", async () => {
99
+ const h = harness();
100
+ const prompts: Array<{ title: string; options: string[] }> = [];
101
+ let answer: string | undefined;
102
+ h.setSelect(async (title, options) => { prompts.push({ title, options }); return answer; });
103
+ const notices = h.notices.length;
104
+ await h.command("");
105
+ assert.deepEqual(prompts.at(-1), { title: menuTitle("OFF"), options: ["enable", "disable", "status"] });
106
+ assert.equal(h.notices.length, notices, "cancel does nothing");
107
+ assert.equal(h.statuses.get("gentle:yolo"), undefined);
108
+ answer = "status"; await h.command("");
109
+ assert.equal(h.notices.at(-1), "YOLO OFF"); assert.equal(h.statuses.get("gentle:yolo"), undefined);
110
+ answer = "enable"; await h.command("");
111
+ assert.equal(h.statuses.get("gentle:yolo"), ON_TEXT); assert.equal(h.notices.at(-1), ON_TEXT);
112
+ answer = undefined; await h.command("");
113
+ assert.equal(prompts.at(-1)!.title, menuTitle("ON"));
114
+ assert.equal(h.statuses.get("gentle:yolo"), ON_TEXT, "cancel leaves ON unchanged");
115
+ answer = "disable"; await h.command("");
116
+ assert.equal(h.statuses.get("gentle:yolo"), undefined); assert.equal(h.notices.at(-1), "YOLO OFF");
117
+ const shown = prompts.length;
118
+ await h.command("status"); await h.command("invalid");
119
+ assert.equal(prompts.length, shown, "explicit arguments bypass the menu");
120
+ assert.equal(h.notices.at(-1), USAGE);
121
+ });
122
+
123
+ test("revocation wins over an activation still awaiting Git identity", async () => {
124
+ for (const reason of ["off", "reload", "new", "resume", "fork", "quit"]) {
125
+ const h = harness();
126
+ const pending = h.command("enable");
127
+ if (reason === "off") await h.command("disable");
128
+ else await h.emit("session_shutdown", { reason });
129
+ await pending;
130
+ await h.command("status"); assert.match(h.notices.at(-1)!, /OFF/);
131
+ assert.equal(h.widgets.get("gentle:yolo"), undefined);
132
+ }
133
+ });
134
+
135
+ test("child, headless, RPC and unidentified repository cannot activate", async () => {
136
+ for (const variant of ["child", "headless", "rpc", "no-repository"]) {
137
+ const h = harness(variant === "child" ? { GENTLE_PI_AGENTS_CHILD: "1" } : {});
138
+ if (variant === "headless") Object.assign(h.ctx, { hasUI: false });
139
+ if (variant === "rpc") Object.assign(h.ctx, { mode: "rpc" });
140
+ if (variant === "no-repository") Object.assign(h.ctx, { cwd: "/" });
141
+ await h.command("enable"); assert.notEqual(h.statuses.get("gentle:yolo"), ON_TEXT);
142
+ h.setSelect(async () => "enable");
143
+ await h.command(""); assert.notEqual(h.statuses.get("gentle:yolo"), ON_TEXT, `${variant} menu enable fails closed`);
144
+ }
145
+ });
146
+
147
+ test("structured primary prompt gains and loses directive; named child does not inherit", async () => {
148
+ const h = harness();
149
+ const options = { appendSystemPrompt: "base" };
150
+ const event = { systemPromptOptions: options, prompt: "task", systemPrompt: "base" };
151
+ await h.command("enable"); await h.emit("before_agent_start", event);
152
+ assert.match(options.appendSystemPrompt, /YOLO session standing permission/);
153
+ await h.command("disable"); await h.emit("before_agent_start", event);
154
+ assert.doesNotMatch(options.appendSystemPrompt, /YOLO session standing permission/);
155
+ await h.command("enable");
156
+ await h.emit("before_agent_start", { ...event, agent: { name: "worker" } });
157
+ assert.doesNotMatch(options.appendSystemPrompt, /YOLO session standing permission/);
158
+ assert.ok(await h.emit("tool_call", { toolName: "bash", input: { command: "git push origin main" } }), "named child has no push waiver");
159
+ });
160
+
161
+ test("session loss and shutdown (including reload) revoke visible state", async () => {
162
+ for (const reason of ["new", "resume", "fork", "reload", "quit"]) {
163
+ const h = harness(); await h.command("enable");
164
+ const event = { prompt: "task", systemPromptOptions: { appendSystemPrompt: "base" } };
165
+ await h.emit("before_agent_start", event);
166
+ assert.match(event.systemPromptOptions.appendSystemPrompt, /YOLO session standing permission/);
167
+ await h.emit("session_shutdown", { reason });
168
+ assert.equal(h.widgets.get("gentle:yolo"), undefined);
169
+ assert.equal(h.statuses.get("gentle:yolo"), undefined);
170
+ await h.emit("before_agent_start", event);
171
+ assert.doesNotMatch(event.systemPromptOptions.appendSystemPrompt, /YOLO session standing permission/);
172
+ await h.command("status"); assert.match(h.notices.at(-1)!, /OFF/);
173
+ }
174
+ const h = harness(); await h.command("enable"); h.setSessionId("replacement");
175
+ await h.command("status"); assert.match(h.notices.at(-1)!, /OFF/);
176
+ });
177
+
178
+ test("active YOLO waives only ordinary push after full destructive/guard evaluation", async () => {
179
+ const h = harness();
180
+ const call = (command: string) => h.emit("tool_call", { toolName: "bash", input: { command } });
181
+ assert.deepEqual(await call("git push origin main"), { block: true, reason: "Gentle AI safety policy blocked the command because it was not confirmed." });
182
+ await h.command("enable");
183
+ assert.equal(await call("git push origin main"), undefined);
184
+ assert.equal(h.confirmations(), 1);
185
+ for (const command of ["git push origin main && rm -rf ./data", `sh -c "psql -c 'DROP TABLE users'"`, "git push origin main && git rebase main", "git push --force origin main", "git push origin main && npm publish", "git -C /other push origin main", "sh -c 'git push origin main'"]) {
186
+ assert.ok(await call(command), command);
187
+ }
188
+ await h.command("disable"); assert.ok(await call("git push origin main"));
189
+ });
190
+
191
+ test("active command path preserves configured push confirmations and blocks, including legacy env override", async () => {
192
+ const configHome = mkdtempSync(join(tmpdir(), "gentle-yolo-config-"));
193
+ const previousHome = process.env.GENTLE_PI_CONFIG_HOME;
194
+ const previousAutonomy = process.env.GENTLE_PI_AUTONOMOUS_MODE;
195
+ process.env.GENTLE_PI_CONFIG_HOME = configHome;
196
+ try {
197
+ for (const envOverride of [undefined, "1"]) {
198
+ if (envOverride === undefined) delete process.env.GENTLE_PI_AUTONOMOUS_MODE;
199
+ else process.env.GENTLE_PI_AUTONOMOUS_MODE = envOverride;
200
+ for (const action of ["confirm", "block"]) {
201
+ writeFileSync(join(configHome, "runtime-guardrails.json"), JSON.stringify({ autonomousMode: false, guardedCommands: { gitPush: action } }));
202
+ const h = harness(); await h.command("enable");
203
+ assert.ok(await h.emit("tool_call", { toolName: "bash", input: { command: "git push origin main" } }));
204
+ assert.equal(h.confirmations(), action === "confirm" ? 1 : 0);
205
+ }
206
+ }
207
+ } finally {
208
+ if (previousHome === undefined) delete process.env.GENTLE_PI_CONFIG_HOME; else process.env.GENTLE_PI_CONFIG_HOME = previousHome;
209
+ if (previousAutonomy === undefined) delete process.env.GENTLE_PI_AUTONOMOUS_MODE; else process.env.GENTLE_PI_AUTONOMOUS_MODE = previousAutonomy;
210
+ rmSync(configHome, { recursive: true, force: true });
211
+ }
212
+ });
213
+
214
+ test("YOLO never grants or revokes separate review standing permission and never answers ask_user", async () => {
215
+ const h = harness();
216
+ const identity = await captureReviewSessionIdentity(h.ctx, {}); assert.ok(identity);
217
+ assert.equal(hasReviewSessionPermission(identity), false);
218
+ await h.command("enable"); assert.equal(hasReviewSessionPermission(identity), false);
219
+ grantReviewSessionPermission(identity);
220
+ await h.command("disable"); assert.equal(hasReviewSessionPermission(identity), true);
221
+ await h.command("enable");
222
+ const question = { toolName: "ask_user", input: { question: "Authorize recovery?", options: ["Approve", "Decline"] } };
223
+ const original = structuredClone(question);
224
+ assert.equal(await h.emit("tool_call", question), undefined);
225
+ assert.deepEqual(question, original);
226
+ assert.equal(h.confirmations(), 0);
227
+ assert.equal(hasReviewSessionPermission(identity), true);
228
+ revokeReviewSessionPermission(identity);
229
+ });
230
+
231
+ test("tool input and task text cannot activate; scope/UI loss removes instruction and widget", async () => {
232
+ const h = harness();
233
+ await h.emit("tool_call", { toolName: "bash", input: { command: "echo harmless", yolo: "on" } });
234
+ const event = { prompt: "/gentle:yolo enable", systemPromptOptions: { appendSystemPrompt: "base" } };
235
+ await h.emit("before_agent_start", event);
236
+ assert.doesNotMatch(event.systemPromptOptions.appendSystemPrompt, /YOLO session standing permission/);
237
+ await h.command("enable");
238
+ Object.assign(h.ctx, { cwd: "/" });
239
+ await h.emit("before_agent_start", event);
240
+ assert.equal(h.widgets.get("gentle:yolo"), undefined);
241
+ assert.doesNotMatch(event.systemPromptOptions.appendSystemPrompt, /YOLO session standing permission/);
242
+ Object.assign(h.ctx, { cwd: process.cwd() });
243
+ await h.command("status"); assert.match(h.notices.at(-1)!, /OFF/);
244
+ await h.command("enable"); Object.assign(h.ctx, { hasUI: false });
245
+ await h.emit("before_agent_start", event);
246
+ assert.equal(h.statuses.get("gentle:yolo"), undefined);
247
+ });
248
+
249
+ test("a session replaced while the menu is open cannot receive the enable choice", async () => {
250
+ const h = harness();
251
+ h.setSelect(async () => { h.setSessionId("replacement-session"); return "enable"; });
252
+ await h.command("");
253
+ assert.equal(h.statuses.get("gentle:yolo"), undefined, "stale menu choice never activates the new session");
254
+ assert.equal(h.notices.at(-1), STALE_MENU, "the discarded choice is reported, not silent");
255
+ h.setSelect(async () => { h.setSessionId("another-session"); return "disable"; });
256
+ await h.command("");
257
+ assert.equal(h.notices.at(-1), STALE_MENU, "a stale disable is reported too");
258
+ h.setSelect(async () => "status");
259
+ await h.command("");
260
+ assert.equal(h.notices.at(-1), "YOLO OFF");
261
+ });
@@ -0,0 +1,59 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import { YoloSessionPolicy, YOLO_DIRECTIVE, updateYoloPrompt, registerYoloSessionPolicy, discoverYoloUiAdapter } from "../lib/yolo-session-policy.ts";
4
+ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
5
+ import type { ReviewSessionIdentity } from "../lib/review-session-standing-permission.ts";
6
+
7
+ function identity(): ReviewSessionIdentity {
8
+ return { sessionManager: { getSessionId: () => "live" }, sessionId: "live", repositoryIdentity: "clone-a", worktreeRoot: "/repo" };
9
+ }
10
+
11
+ test("YOLO defaults off; explicit activation is exact-live-session and clone bound", () => {
12
+ const policy = new YoloSessionPolicy();
13
+ const live = identity();
14
+ assert.equal(policy.active(live), false);
15
+ assert.equal(policy.set(true, live), true);
16
+ assert.equal(policy.active({ ...live, worktreeRoot: "/sibling" }), true);
17
+ assert.equal(policy.active({ ...live, repositoryIdentity: "clone-b" }), false);
18
+ assert.equal(policy.active(live), false, "scope loss revokes, not merely hides");
19
+ for (const other of [undefined, { ...live, sessionId: "resumed" }, identity()]) {
20
+ policy.set(true, live);
21
+ assert.equal(policy.active(other), false);
22
+ }
23
+ assert.equal(policy.set(true, undefined), false);
24
+ });
25
+
26
+ test("off/reset/restart revoke and invalidate pending activation", () => {
27
+ const policy = new YoloSessionPolicy();
28
+ const live = identity();
29
+ const epoch = policy.epoch;
30
+ policy.reset();
31
+ assert.equal(policy.set(true, live, epoch), false);
32
+ policy.set(true, live);
33
+ policy.set(false, live);
34
+ assert.equal(policy.active(live), false);
35
+ policy.set(true, live);
36
+ policy.reset();
37
+ assert.equal(policy.active(live), false);
38
+ assert.equal(new YoloSessionPolicy().active(live), false);
39
+ });
40
+
41
+ test("legacy hosts without an event bus retain command registration but no menu adapter", async () => {
42
+ const commands: string[] = [];
43
+ const pi = { registerCommand: (name: string) => commands.push(name) } as unknown as ExtensionAPI;
44
+ assert.ok(registerYoloSessionPolicy(pi, {}));
45
+ assert.deepEqual(commands, ["gentle:yolo"], "bare yolo command is not registered");
46
+ assert.equal(await discoverYoloUiAdapter(pi, {} as ExtensionContext), undefined);
47
+ });
48
+
49
+ test("active-only structured directive qualifies defaults and is removed from reused options", () => {
50
+ const options = { appendSystemPrompt: "existing harness" };
51
+ updateYoloPrompt(options, true);
52
+ updateYoloPrompt(options, true);
53
+ assert.equal(options.appendSystemPrompt.split(YOLO_DIRECTIVE).length, 2);
54
+ assert.match(YOLO_DIRECTIVE, /default.*commit.*push.*PR/i);
55
+ assert.match(YOLO_DIRECTIVE, /ask_user/);
56
+ assert.match(YOLO_DIRECTIVE, /credentials/);
57
+ updateYoloPrompt(options, false);
58
+ assert.equal(options.appendSystemPrompt, "existing harness");
59
+ });
@@ -5,6 +5,7 @@
5
5
  "bg": "#06080f",
6
6
  "bgPanel": "#06080f",
7
7
  "bgElement": "#06080f",
8
+ "userMessageBg": "#161D2E",
8
9
  "bgSubtle": "#0d0f14",
9
10
  "surfaceLine": "#0d0f14",
10
11
  "border": "#313342",
@@ -52,7 +53,7 @@
52
53
  "text": "text",
53
54
  "thinkingText": "muted",
54
55
  "selectedBg": "selection",
55
- "userMessageBg": "bgElement",
56
+ "userMessageBg": "userMessageBg",
56
57
  "userMessageText": "text",
57
58
  "customMessageBg": "bgSubtle",
58
59
  "customMessageText": "text",
@@ -5,6 +5,7 @@
5
5
  "bg": "#060407",
6
6
  "bgPanel": "#100A0F",
7
7
  "bgElement": "#100A0F",
8
+ "userMessageBg": "#2A1523",
8
9
  "bgSubtle": "#100A0F",
9
10
  "border": "#563040",
10
11
  "borderSubtle": "#2A1720",
@@ -45,7 +46,7 @@
45
46
  "scrollbarThumb": "border",
46
47
  "searchMatchBg": "deepPink",
47
48
  "searchMatchText": "text",
48
- "userMessageBg": "bgElement",
49
+ "userMessageBg": "userMessageBg",
49
50
  "userMessageText": "text",
50
51
  "customMessageBg": "bgSubtle",
51
52
  "customMessageText": "text",
@@ -5,6 +5,7 @@
5
5
  "bg": "#060407",
6
6
  "bgPanel": "#100A0F",
7
7
  "bgElement": "#100A0F",
8
+ "userMessageBg": "#2E0F1F",
8
9
  "bgSubtle": "#100A0F",
9
10
  "border": "#563040",
10
11
  "borderSubtle": "#2A1720",
@@ -43,7 +44,7 @@
43
44
  "scrollbarThumb": "border",
44
45
  "searchMatchBg": "deepPink",
45
46
  "searchMatchText": "text",
46
- "userMessageBg": "bgElement",
47
+ "userMessageBg": "userMessageBg",
47
48
  "userMessageText": "text",
48
49
  "customMessageBg": "bgSubtle",
49
50
  "customMessageText": "text",
@@ -1,159 +0,0 @@
1
- ---
2
- name: sdd-apply
3
- description: Implement SDD tasks with strict TDD evidence and review workload guard.
4
- tools:
5
- - read
6
- - grep
7
- - find
8
- - edit
9
- - write
10
- - bash
11
- - mem_search
12
- - mem_get_observation
13
- - mem_save
14
- - mem_update
15
- ---
16
-
17
- You are the SDD apply executor for Gentle AI.
18
-
19
- ## Parent Preflight Transport
20
-
21
- Consume the exact `## SDD Session Preflight` block from parent-provided context. It is parent authority, not a prompt to infer or persist defaults. If absent or malformed, return `blocked` without phase work. A delegated RPC child never confirms or persists SDD choices.
22
-
23
- ## Skill Resolution Contract
24
-
25
- Use your assigned executor/phase skill for this SDD phase. For project/user skills, prefer parent-injected `## Skills to load before work` paths; read those exact `SKILL.md` files before work. Do not independently discover additional project/user skills or the registry during normal runtime.
26
-
27
- If skill paths are missing, explicit fallback loading is allowed only as degraded self-healing. Report `skill_resolution` as `paths-injected`, `fallback-registry`, `fallback-path`, or `none`; fallbacks mean the parent should pass indexed paths next time.
28
-
29
- ## Memory Contract
30
-
31
- Read your own input artifacts directly from the active backend before doing the phase work; do not wait for the parent to inline them. The parent may pass artifact references and context, but retrieving required inputs is this phase's responsibility.
32
-
33
- Inputs to read (`engram`/`both`: use the injected Engram memory read tools for the topic key, then fetch the full observation; `openspec`: read the file under `openspec/changes/{change}/`):
34
- - Tasks (required): `sdd/{change}/tasks`
35
- - Spec (required): `sdd/{change}/spec`
36
- - Design (required): `sdd/{change}/design`
37
- - Previous apply-progress (if it exists): `sdd/{change}/apply-progress` — read and MERGE with your new progress; do NOT overwrite.
38
-
39
- Persist this phase's artifact to the active backend before returning (mandatory):
40
- - `engram`/`both`: call the injected Engram save tool with title and `topic_key` `"sdd/{change}/apply-progress"`, `type: "architecture"`, `project` from context, and `capture_prompt: false` when the tool schema supports it (omit the field if an older schema rejects it).
41
- - Also update the tasks artifact checkboxes via the injected Engram update tool (`engram`/`both`) or file edit (`openspec`/`both`).
42
- - `openspec` / `both`: write/update the apply-progress and tasks files under `openspec/changes/{change}/`.
43
- - `none`: return progress inline.
44
-
45
- For `both`, read back each selected backend and report its actual result. File and Engram writes are not atomic: retain successful writes and cumulative progress, disclose failed or divergent copies, and do not claim complete persistence or switch stores to hide a failure.
46
-
47
- Never claim persistence you did not perform.
48
-
49
- ## Status and Action Context Guard
50
-
51
- Before writing code, consume validated native `gentle-ai.sdd-status` v2 from the parent. If missing, request native read-only status for the selected change and canonical workspace. Never reconstruct readiness locally or from Engram artifacts; native `nextRecommended` and `phaseInstructions` own the route for every store. Reject malformed or unsupported actions before work, without prose inference or fallback.
52
-
53
- Read artifacts from the selected backend for implementation context, not as replacement lifecycle authority. Status grants no writes. Explicit continuation may prepare only the exact canonical marker path confirmed by the current human; denial, cancellation, missing UI, and workspace mismatch prohibit mutation. Marker preparation grants no source roots or persistent authority.
54
-
55
- Stop with `blocked` before editing if:
56
-
57
- - active change selection is missing or ambiguous;
58
- - native apply dependency is blocked;
59
- - required apply artifacts are missing (confirmed by artifact store);
60
- - `actionContext.mode: workspace-planning` and no `allowedEditRoots` are provided;
61
- - any target file is outside the authoritative workspace or allowed edit roots.
62
-
63
- If status says `applyState: all_done`, do not edit. Report that implementation is complete and return the fresh native recommendation (classically `archive`; an older provider may still require `verify`). Do not recommend apply again after all implementation tasks are complete.
64
-
65
- ## Before Writing Code
66
-
67
- Read structured status, proposal, specs, design, tasks, existing code, tests, `apply-progress.md` if present, and `openspec/config.yaml` when present.
68
-
69
- ## Review Workload Gate
70
-
71
- Before implementing, inspect `tasks.md` for `Review Workload Forecast` and these guard lines:
72
-
73
- ```text
74
- Decision needed before apply: Yes|No
75
- Chained PRs recommended: Yes|No
76
- Chain strategy: stacked-to-main|feature-branch-chain|size-exception|pending
77
- 400-line budget risk: Low|Medium|High
78
- ```
79
-
80
- If any of these are true:
81
-
82
- - `Decision needed before apply: Yes`
83
- - `Chained PRs recommended: Yes`
84
- - `400-line budget risk: High`
85
-
86
- then continue only when the parent prompt gives a resolved delivery path:
87
-
88
- - `auto-chain` or chosen chained/stacked PR mode: implement only the assigned work-unit slice and report the PR boundary.
89
- - `exception-ok` or `size:exception`: continue only if the prompt explicitly says the maintainer accepts the exception.
90
- - `single-pr` above budget: continue only after explicit `size:exception` approval.
91
-
92
- If no delivery decision is provided, STOP before writing code and return `blocked` with the exact decision needed.
93
-
94
- The budget constrains how work is sliced, never the code itself. Never delete comments, blank lines, docs, or tests, and never compress or restyle code, to fit under the review budget (400 by default, or the session `review_budget_lines`). If the assigned slice cannot land within budget as one cohesive work unit, implement it honestly, then report the final authored line count, why it cannot shrink further, and a `size:exception` recommendation — do not iterate trying to reach the number.
95
-
96
- ## Strict TDD Gate
97
-
98
- If `openspec/config.yaml` declares strict TDD and a test runner, or the parent prompt says strict TDD is active:
99
-
100
- 1. Read the global Gentle AI strict-TDD support guidance when available. If a project-local `.pi/gentle-ai/support/strict-tdd.md` exists, treat it as an override.
101
- 2. Follow RED → GREEN → TRIANGULATE → REFACTOR for every assigned task.
102
- 3. Do not write production code before a failing test or equivalent RED test is written.
103
- 4. Run relevant focused tests during GREEN and after refactors.
104
- 5. Write a `TDD Cycle Evidence` table in `apply-progress.md`.
105
-
106
- If strict TDD is active and no external support file is available, follow the RED/GREEN/TRIANGULATE/REFACTOR contract from this prompt. Do not silently fall back to standard mode.
107
-
108
- ## Task Completion Boundary
109
-
110
- Use native task progress and authorized scope to select and report the assigned work. Preserve historical ownership comments and task artifacts; do not parse their spelling or position into a local admission gate. Check off only actually completed implementation work with applicable proof, and leave unfinished work unchecked.
111
-
112
- After implementation completion, return fresh native status to the parent: classically archive, with explicitly optional verification. Never bypass an older provider that still selects verify. Archive composes applicable specs and records closure without a post-SDD RDD dependency.
113
-
114
- ## Persisted Task Checkbox Contract
115
-
116
- `sdd-apply` owns persisted task completion. In all modes, including strict TDD, mark each completed implementation task in the persisted tasks artifact immediately after completion:
117
-
118
- - `openspec` / `both`: update `openspec/changes/{change}/tasks.md` from `- [ ]` to `- [x]` for completed tasks.
119
- - `engram` / `both`: update the `sdd/{change}/tasks` observation when memory tools are explicitly available.
120
- - `none`: report task progress inline and state that no persisted task artifact was updated.
121
-
122
- Internal todos and `apply-progress.md` are not enough completion evidence.
123
-
124
- Before returning, re-read the persisted tasks artifact and confirm every task you report as completed is visibly marked `- [x]`. If the artifact still shows a completed task as `- [ ]`, fix the checkbox before returning or return `blocked` explaining why it cannot be reconciled. Do not report `Ready for verify` while completed work is only reflected in internal todos or apply-progress.
125
-
126
- ## Standard Mode
127
-
128
- If strict TDD is not active, implement assigned tasks against specs and design, update persisted task checkboxes as work completes, and record verification evidence.
129
-
130
- ## Apply Progress
131
-
132
- Update `openspec/changes/{change}/apply-progress.md` cumulatively. If previous progress exists, merge it with new progress; never overwrite completed work.
133
-
134
- Include:
135
-
136
- - completed tasks and the matching persisted task checkbox updates;
137
- - files changed;
138
- - test commands run;
139
- - TDD evidence when strict TDD is active;
140
- - deviations from design;
141
- - remaining tasks, including exact unchecked `- [ ]` lines when any remain;
142
- - workload / PR boundary;
143
- - structured status consumed or produced, including `actionContext` warnings.
144
-
145
- Do NOT launch child subagents. Parent/orchestrator owns delegation. Never commit unless the user explicitly asks.
146
-
147
- Rules:
148
-
149
- - ALWAYS consume or produce structured status before implementation; do not infer readiness from conversation alone.
150
- - STOP on unsafe `actionContext` or edit roots.
151
- - Mark completed tasks in the persisted tasks artifact as you go, not only at the end.
152
- - Before returning, re-read the persisted tasks artifact and ensure completed tasks are visibly marked `- [x]`; internal todos are not completion evidence.
153
-
154
- Return the standard phase envelope with status, executive_summary, artifacts, next_recommended, risks, and skill_resolution.
155
-
156
-
157
- ## Key Learnings Closing
158
-
159
- Close your final report text with a `## Key Learnings` block (no trailing colon). Use 1–5 numbered items, each a standalone factual sentence of at least 20 characters and at least 4 words. This applies to final report text only — not intermediate tool output or saved artifact content. The Engram memory provider automatically extracts and persists these items as passive capture; you do not parse the block or invoke passive-capture tools yourself. Omit the block when there is genuinely no reusable learning; no filler or speculation. This closing block is separate from explicit `mem_save` artifact/decision persistence.