gentle-pi 2.1.2 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (228) hide show
  1. package/README.md +108 -79
  2. package/assets/agents/gentle-ai-worker.md +7 -3
  3. package/assets/agents/jd-fix-agent.md +1 -1
  4. package/assets/agents/jd-judge-a.md +3 -1
  5. package/assets/agents/jd-judge-b.md +3 -1
  6. package/assets/agents/review-readability.md +4 -3
  7. package/assets/agents/review-reliability.md +4 -3
  8. package/assets/agents/review-resilience.md +4 -3
  9. package/assets/agents/review-risk.md +4 -3
  10. package/assets/agents/sdd-apply.md +11 -4
  11. package/assets/agents/sdd-archive.md +6 -1
  12. package/assets/agents/sdd-design.md +6 -1
  13. package/assets/agents/sdd-explore.md +6 -2
  14. package/assets/agents/sdd-init.md +10 -2
  15. package/assets/agents/sdd-onboard.md +6 -1
  16. package/assets/agents/sdd-proposal.md +8 -1
  17. package/assets/agents/sdd-research.md +54 -0
  18. package/assets/agents/sdd-spec.md +6 -1
  19. package/assets/agents/sdd-status.md +10 -5
  20. package/assets/agents/sdd-sync.md +6 -1
  21. package/assets/agents/sdd-tasks.md +8 -4
  22. package/assets/agents/sdd-verify.md +27 -2
  23. package/assets/chains/4r-review.chain.md +2 -0
  24. package/assets/chains/sdd-full.chain.md +2 -2
  25. package/assets/chains/sdd-plan.chain.md +1 -1
  26. package/assets/chains/sdd-verify.chain.md +2 -2
  27. package/assets/orchestrator-delegation.md +145 -160
  28. package/assets/orchestrator-memory.md +2 -0
  29. package/assets/orchestrator.md +25 -48
  30. package/assets/sdd-orchestrator-workflow.md +163 -25
  31. package/assets/support/sdd-status-contract.md +24 -6
  32. package/contracts/review-integration/v1/fixtures/consent.fixture.json +3 -3
  33. package/contracts/review-integration/v1/fixtures/start-v2.fixture.json +19 -28
  34. package/contracts/review-integration/v1/fixtures/start.fixture.json +1 -10
  35. package/contracts/review-integration/v1/fixtures/status-v2.fixture.json +12 -21
  36. package/contracts/review-integration/v1/schemas/correction-plan-request.schema.json +49 -0
  37. package/contracts/review-integration/v1/schemas/operation.schema.json +76 -0
  38. package/contracts/review-integration/v1/schemas/repair.schema.json +39 -0
  39. package/contracts/review-integration/v1/schemas/status-v2.schema.json +4 -2
  40. package/contracts/review-integration/v1/schemas/status.schema.json +4 -2
  41. package/contracts/review-integration/v1/schemas/transition-execution.schema.json +42 -0
  42. package/contracts/review-integration/v2/fixtures/consent.fixture.json +1 -1
  43. package/contracts/review-integration/v2/fixtures/start.fixture.json +1 -10
  44. package/contracts/review-integration/v2/fixtures/status.fixture.json +1 -10
  45. package/contracts/review-integration/v2/schemas/failure.schema.json +5 -1
  46. package/contracts/review-integration/v2/schemas/last-event-closure.schema.json +66 -0
  47. package/contracts/review-integration/v2/schemas/opencode-provider-role.schema.json +14 -0
  48. package/contracts/review-integration/v2/schemas/operation.schema.json +6 -1
  49. package/contracts/review-integration/v2/schemas/repair.schema.json +4 -2
  50. package/contracts/review-integration/v2/schemas/start.schema.json +5 -2
  51. package/contracts/review-integration/v2/schemas/status.schema.json +4 -2
  52. package/contracts/review-provider-contract-mirror/provider-contract.lock.json +30 -0
  53. package/contracts/review-provider-contract-mirror/v1.1.0/bundle/README.md +12 -0
  54. package/contracts/review-provider-contract-mirror/v1.1.0/bundle/manifest.json +65 -0
  55. package/contracts/review-provider-contract-mirror/v1.1.0/bundle/schemas/lens.schema.json +16 -0
  56. package/contracts/review-provider-contract-mirror/v1.1.0/bundle/schemas/refuter.schema.json +1 -0
  57. package/contracts/review-provider-contract-mirror/v1.1.0/bundle/schemas/targeted-validator.schema.json +1 -0
  58. package/contracts/review-provider-contract-mirror/v1.1.0/bundle/vectors/lens.json +1 -0
  59. package/contracts/review-provider-contract-mirror/v1.1.0/bundle/vectors/refuter.json +1 -0
  60. package/contracts/review-provider-contract-mirror/v1.1.0/bundle/vectors/targeted-validator.json +1 -0
  61. package/contracts/review-provider-contract-mirror/v1.1.0/generated/provider-capabilities.baseline.json +15 -0
  62. package/contracts/review-provider-contract-mirror/v1.1.0/generated/provider-roles.baseline.json +42 -0
  63. package/docs/native-authority-architecture.md +14 -16
  64. package/docs/review-integration.md +27 -353
  65. package/extensions/ask-user-choice.ts +151 -0
  66. package/extensions/gentle-ai.ts +2710 -3211
  67. package/extensions/quiet-tools.ts +515 -32
  68. package/extensions/sdd-init.ts +21 -12
  69. package/extensions/skill-registry.ts +10 -2
  70. package/extensions/startup-banner.ts +10 -4
  71. package/lib/gentle-ai-binary.ts +173 -2
  72. package/lib/gentle-ai-renderer.ts +70 -0
  73. package/lib/model-routing-authority.ts +133 -0
  74. package/lib/native-review-cli.ts +676 -781
  75. package/lib/opaque-pi-reviewer-adapter.ts +284 -0
  76. package/lib/provider-contract-bundle.ts +704 -0
  77. package/lib/review-candidate-view.ts +836 -118
  78. package/lib/review-compact-contract.ts +59 -248
  79. package/lib/review-host-relay.ts +578 -0
  80. package/lib/review-integration-v2.ts +1254 -221
  81. package/lib/review-last-event-controller.ts +35 -0
  82. package/lib/review-relay-contract.ts +16 -0
  83. package/lib/sdd-preflight.ts +230 -67
  84. package/lib/sdd-status.ts +66 -111
  85. package/lib/terminal-theme.ts +1 -1
  86. package/package.json +83 -79
  87. package/runtime/gentle-ai-binary.mjs +174 -3
  88. package/runtime/native-review-cli.mjs +610 -715
  89. package/runtime/review-integration-v2.mjs +1223 -190
  90. package/runtime/review-relay-contract.mjs +17 -0
  91. package/scripts/{build-git-commit-transaction-runner.mjs → build-runtime-modules.mjs} +5 -5
  92. package/scripts/check-provider-contract.mjs +138 -0
  93. package/scripts/gentle-ai-installer.mjs +85 -22
  94. package/scripts/maintainer/provider-relay-matrix.mjs +403 -0
  95. package/scripts/mirror-provider-contract.mjs +143 -0
  96. package/scripts/test-packed-runner.mjs +20 -9
  97. package/scripts/verify-package-files.mjs +119 -43
  98. package/skills/_shared/review-ledger-contract.md +11 -19
  99. package/skills/chained-pr/SKILL.md +3 -0
  100. package/skills/cognitive-doc-design/SKILL.md +1 -1
  101. package/skills/comment-writer/SKILL.md +1 -1
  102. package/skills/gentle-ai/SKILL.md +7 -74
  103. package/skills/issue-creation/SKILL.md +94 -168
  104. package/skills/judgment-day/SKILL.md +9 -5
  105. package/skills/judgment-day/references/prompts-and-formats.md +2 -0
  106. package/skills/rdd-defect-workflow/SKILL.md +54 -0
  107. package/skills/release/SKILL.md +3 -3
  108. package/skills/skill-registry/SKILL.md +1 -1
  109. package/skills/work-unit-commits/SKILL.md +3 -1
  110. package/tests/artifact-language.test.ts +24 -11
  111. package/tests/ask-user-choice.test.ts +264 -0
  112. package/tests/background-subagents.test.ts +771 -0
  113. package/tests/codegraph-tools.test.ts +3 -3
  114. package/tests/crosslane/cross-lane.mjs +16 -0
  115. package/tests/delegated-key-learnings-contract.test.ts +240 -0
  116. package/tests/devbinary/native-review-parity.devtest.ts +167 -250
  117. package/tests/devbinary/pi-host-relay.devtest.ts +867 -0
  118. package/tests/fixtures/devbinary/capabilities-v2.1.derived.json +331 -0
  119. package/tests/fixtures/devbinary/capabilities-v2.2.captured.json +340 -0
  120. package/tests/fixtures/devbinary/consent-v3.captured.json +37 -0
  121. package/tests/fixtures/devbinary/failure-v2-capture-evidence.captured.json +16 -0
  122. package/tests/fixtures/devbinary/last-event-capture-correction-plan.captured.json +10 -0
  123. package/tests/fixtures/devbinary/last-event-capture-refuter-approved.captured.json +20 -0
  124. package/tests/fixtures/devbinary/last-event-capture-refuter-correction-required.captured.json +26 -0
  125. package/tests/fixtures/devbinary/last-event-capture-result-approved.captured.json +8 -0
  126. package/tests/fixtures/devbinary/last-event-capture-result-correction-required.captured.json +26 -0
  127. package/tests/fixtures/devbinary/last-event-capture-validation-approved.captured.json +8 -0
  128. package/tests/fixtures/devbinary/last-event-closure.provenance.md +13 -0
  129. package/tests/fixtures/devbinary/result-artifact-v2-path.captured.json +12 -0
  130. package/tests/fixtures/devbinary/result-artifact-v2.captured.json +12 -0
  131. package/tests/fixtures/devbinary/review-acknowledged-v1.captured.json +9 -0
  132. package/tests/fixtures/devbinary/review-acknowledged.provenance.md +31 -0
  133. package/tests/fixtures/devbinary/start-v3-consent-declined.captured.json +19 -0
  134. package/tests/fixtures/devbinary/start-v3-consent-granted.captured.json +109 -0
  135. package/tests/fixtures/devbinary/start-v3-zero-lens-closed.captured.json +21 -0
  136. package/tests/fixtures/devbinary/status-v5-capture-result-submission.captured.json +184 -0
  137. package/tests/fixtures/devbinary/status-v5-repository-context.captured.json +138 -0
  138. package/tests/fixtures/devbinary/status-v5.captured.json +88 -0
  139. package/tests/fixtures/native-review-cli/v2.5.0-rc.1/PROVENANCE.txt +15 -0
  140. package/tests/fixtures/provider-contract-bundle/v1.1.0/README.md +12 -0
  141. package/tests/fixtures/provider-contract-bundle/v1.1.0/manifest.json +65 -0
  142. package/tests/fixtures/provider-contract-bundle/v1.1.0/schemas/lens.schema.json +16 -0
  143. package/tests/fixtures/provider-contract-bundle/v1.1.0/schemas/refuter.schema.json +1 -0
  144. package/tests/fixtures/provider-contract-bundle/v1.1.0/schemas/targeted-validator.schema.json +1 -0
  145. package/tests/fixtures/provider-contract-bundle/v1.1.0/vectors/lens.json +1 -0
  146. package/tests/fixtures/provider-contract-bundle/v1.1.0/vectors/refuter.json +1 -0
  147. package/tests/fixtures/provider-contract-bundle/v1.1.0/vectors/targeted-validator.json +1 -0
  148. package/tests/gentle-ai-binary.test.ts +83 -13
  149. package/tests/gentle-ai-dev-binary-surfacing.test.ts +195 -0
  150. package/tests/gentle-ai-dev-binary.test.ts +159 -0
  151. package/tests/gentle-ai-installer.test.ts +138 -48
  152. package/tests/gentle-ai.test.ts +820 -131
  153. package/tests/gentle-theme.test.ts +133 -0
  154. package/tests/maintainer/provider-relay.maintest.ts +601 -0
  155. package/tests/model-routing-authority.test.ts +257 -0
  156. package/tests/native-review-capability-contract.test.ts +75 -2
  157. package/tests/native-review-cli.test.ts +588 -908
  158. package/tests/native-review-consent.test.ts +283 -38
  159. package/tests/native-review-parity-runtime.test.ts +108 -350
  160. package/tests/native-review-parity.test.ts +684 -732
  161. package/tests/native-sdd-attempt-authority.test.ts +235 -0
  162. package/tests/opaque-pi-reviewer-adapter.test.ts +266 -0
  163. package/tests/orchestrator-budget.test.ts +158 -89
  164. package/tests/orchestrator-rdd-ownership.test.ts +103 -0
  165. package/tests/package-manifest.test.ts +128 -100
  166. package/tests/provider-contract-bundle.test.ts +385 -0
  167. package/tests/provider-contract-mirror.test.ts +206 -0
  168. package/tests/provider-defect-handoff.test.ts +252 -0
  169. package/tests/quiet-tool-rendering.test.ts +1055 -28
  170. package/tests/review-actor-tool-deny.test.ts +12 -13
  171. package/tests/review-authority-recovery-docs.test.ts +1 -2
  172. package/tests/review-candidate-view.test.ts +898 -12
  173. package/tests/review-compact-contract.test.ts +29 -122
  174. package/tests/review-controller-lock-status.test.ts +2 -2
  175. package/tests/review-controller-native-recovery.test.ts +366 -857
  176. package/tests/review-controller-native-routing.test.ts +1253 -4199
  177. package/tests/review-controller-retired-ops.test.ts +1 -1
  178. package/tests/review-controller-workspace-root.test.ts +236 -70
  179. package/tests/review-controller.test.ts +26 -816
  180. package/tests/review-corrected-finalize-binding.test.ts +134 -0
  181. package/tests/review-dispatch-hydration-gap.test.ts +145 -0
  182. package/tests/review-gate.test.ts +0 -45
  183. package/tests/review-host-relay-restart-parity.test.ts +360 -0
  184. package/tests/review-host-relay-routing.test.ts +352 -0
  185. package/tests/review-host-relay.test.ts +754 -0
  186. package/tests/review-integration-v2-forward.test.ts +654 -0
  187. package/tests/review-integration-v2.test.ts +317 -129
  188. package/tests/review-last-event-closure.test.ts +408 -0
  189. package/tests/review-ledger-contract.test.ts +106 -60
  190. package/tests/review-recovered-lineage-routing.test.ts +199 -0
  191. package/tests/review-relay-transport-agent.test.ts +322 -0
  192. package/tests/review-snapshot.test.ts +3 -2
  193. package/tests/runtime-harness.mjs +573 -180
  194. package/tests/sdd-agent-tools.test.ts +53 -32
  195. package/tests/sdd-preflight.test.ts +81 -15
  196. package/tests/sdd-status.test.ts +109 -110
  197. package/tests/skill-collision-prefixes.test.ts +6 -8
  198. package/tests/skill-registry.test.ts +50 -1
  199. package/tests/verify-package-files.test.ts +62 -0
  200. package/tests/writer-edit-surface-scope.test.ts +230 -0
  201. package/themes/Gentleman-Cute.json +94 -0
  202. package/themes/Gentleman-Sexy.json +92 -0
  203. package/assets/agents/review-refuter.md +0 -40
  204. package/assets/agents/review-validator.md +0 -23
  205. package/lib/git-commit-transaction.ts +0 -801
  206. package/lib/native-review-remediation.ts +0 -49
  207. package/lib/review-compact.ts +0 -947
  208. package/lib/review-refuter-adapter.ts +0 -129
  209. package/lib/review-runtime-contract.ts +0 -68
  210. package/prompts/gcl.md +0 -54
  211. package/prompts/gis.md +0 -25
  212. package/prompts/gpr.md +0 -41
  213. package/prompts/gwr.md +0 -31
  214. package/runtime/git-commit-transaction.mjs +0 -802
  215. package/scripts/run-git-commit-transaction.mjs +0 -35
  216. package/tests/fixtures/native-review-cli/v2.1.2/bind-sdd.json +0 -25
  217. package/tests/fixtures/native-review-cli/v2.1.2/finalize.json +0 -8
  218. package/tests/fixtures/native-review-cli/v2.1.2/sdd-status-engram.json +0 -139
  219. package/tests/fixtures/native-review-cli/v2.1.2/sdd-status.json +0 -200
  220. package/tests/fixtures/native-review-cli/v2.1.2/start.json +0 -12
  221. package/tests/fixtures/native-review-cli/v2.1.2/validate-allow.json +0 -24
  222. package/tests/fixtures/native-review-cli/v2.1.2/validate-deny-empty-context.json +0 -20
  223. package/tests/fixtures/native-review-cli/v2.1.2/validate-deny.json +0 -28
  224. package/tests/fixtures/native-review-cli/v2.1.3/sdd-status-engram.json +0 -139
  225. package/tests/fixtures/native-review-cli/v2.1.3/sdd-status.json +0 -200
  226. package/tests/git-commit-transaction.test.ts +0 -302
  227. package/tests/review-compact.test.ts +0 -243
  228. package/tests/review-refuter-adapter.test.ts +0 -89
@@ -5,12 +5,16 @@ import {
5
5
  createFindTool,
6
6
  createGrepTool,
7
7
  createLsTool,
8
- createReadTool,
8
+ createReadToolDefinition,
9
9
  createWriteTool,
10
+ keyHint,
10
11
  } from "@earendil-works/pi-coding-agent";
11
- import { Text } from "@earendil-works/pi-tui";
12
+ import { Text, type Component } from "@earendil-works/pi-tui";
12
13
  import { homedir } from "node:os";
14
+ import { isAbsolute } from "node:path";
15
+ import { resolveGentleAiDevBinaryOverride, type GentleAiDevBinaryOverride } from "../lib/gentle-ai-binary.ts";
13
16
  import { quietToolsEnabled } from "../lib/quiet-tools-config.ts";
17
+ import { getGentleAiRenderState, renderGentleAiLifecycleCall, renderGentleAiResult, type GentleAiRenderContext } from "../lib/gentle-ai-renderer.ts";
14
18
  import { sanitizeTerminalText } from "../lib/terminal-theme.ts";
15
19
 
16
20
  type QuietToolName = "read" | "bash" | "grep" | "find" | "ls" | "edit" | "write";
@@ -20,7 +24,7 @@ type ThemeLike = {
20
24
  };
21
25
 
22
26
  const TOOL_CREATORS = {
23
- read: createReadTool,
27
+ read: createReadToolDefinition,
24
28
  bash: createBashTool,
25
29
  grep: createGrepTool,
26
30
  find: createFindTool,
@@ -35,14 +39,14 @@ const COLLAPSED_COUNT_LABELS: Partial<Record<QuietToolName, string>> = {
35
39
  ls: "entries",
36
40
  };
37
41
 
38
- const NO_COLLAPSED_RESULT_TOOLS = new Set<QuietToolName>(["read", "bash"]);
39
- const COLLAPSED_TAIL_TOOLS = new Set<QuietToolName>(["edit", "write"]);
40
42
  const COLLAPSED_TAIL_LINE_LIMIT = 10;
43
+ const PREVIEW_LINE_LIMIT = 3;
41
44
 
42
45
  const EMPTY_RESULT_MESSAGES: Partial<Record<QuietToolName, string[]>> = {
43
46
  grep: ["No matches found"],
44
47
  find: ["No files found matching pattern"],
45
- ls: ["Directory is empty"],
48
+ ls: ["Directory is empty", "(empty directory)"],
49
+ bash: ["(no output)"],
46
50
  };
47
51
 
48
52
  const toolCache = new Map<string, Record<QuietToolName, any>>();
@@ -83,6 +87,43 @@ export function tailLines(text: string, limit: number): string {
83
87
  return lines.slice(Math.max(0, lines.length - limit)).join("\n");
84
88
  }
85
89
 
90
+ function outputLines(text: string): string[] {
91
+ const normalized = text.replace(/\r\n/g, "\n").replace(/\n$/, "");
92
+ return normalized.length > 0 ? normalized.split("\n") : [];
93
+ }
94
+
95
+ function firstLines(text: string, limit: number): string {
96
+ return outputLines(text).slice(0, limit).join("\n");
97
+ }
98
+
99
+ function lastOutputLines(text: string, limit: number): string {
100
+ return outputLines(text).slice(-limit).join("\n");
101
+ }
102
+
103
+ function lastMeaningfulOutputLines(text: string, limit: number): string {
104
+ return outputLines(text)
105
+ .filter((line) => line.trim().length > 0)
106
+ .slice(-limit)
107
+ .join("\n");
108
+ }
109
+
110
+ function semanticJsonPreview(text: string): string | undefined {
111
+ const trimmed = text.trim();
112
+ if (!/^[\[{]/.test(trimmed)) return undefined;
113
+ let parsed: unknown;
114
+ try {
115
+ parsed = JSON.parse(trimmed);
116
+ } catch {
117
+ return undefined;
118
+ }
119
+ const normalized = JSON.stringify(parsed, null, 2);
120
+ if (normalized === undefined) return undefined;
121
+ return outputLines(normalized)
122
+ .filter((line) => !/^[\s{}\[\],]*$/.test(line))
123
+ .slice(0, PREVIEW_LINE_LIMIT)
124
+ .join("\n");
125
+ }
126
+
86
127
  export function extractTextContent(result: AgentToolResult<unknown>): string {
87
128
  return result.content
88
129
  .flatMap((content) => (content.type === "text" ? [content.text] : []))
@@ -93,9 +134,32 @@ function safeText(value: string): string {
93
134
  return sanitizeTerminalText(value);
94
135
  }
95
136
 
137
+ function sanitizeValue(value: unknown): unknown {
138
+ if (typeof value === "string") return safeText(value);
139
+ return value;
140
+ }
141
+
142
+ function sanitizedArgs(args: Record<string, unknown> | undefined): Record<string, unknown> {
143
+ return (sanitizeValue(args ?? {}) as Record<string, unknown>) ?? {};
144
+ }
145
+
146
+ function sanitizedResult(result: AgentToolResult<unknown>): AgentToolResult<unknown> {
147
+ return {
148
+ ...result,
149
+ content: result.content.map((content) => content.type === "text" ? { ...content, text: safeText(content.text) } : content.type === "image" ? { ...content, mimeType: safeText(content.mimeType) } : content),
150
+ details: sanitizeValue(result.details),
151
+ };
152
+ }
153
+
96
154
  function isEmptyResultMessage(toolName: QuietToolName, text: string): boolean {
97
155
  const normalized = text.trim();
98
- return EMPTY_RESULT_MESSAGES[toolName]?.some((message) => normalized.startsWith(message)) ?? false;
156
+ return EMPTY_RESULT_MESSAGES[toolName]?.some((message) => normalized === message) ?? false;
157
+ }
158
+
159
+ function grepMatchCount(text: string, args: Record<string, unknown> | undefined): number {
160
+ const context = args?.context;
161
+ if (typeof context !== "number" || context <= 0) return countNonEmptyLines(text);
162
+ return outputLines(text).filter((line) => /^\s*.+:\d+:\s?/.test(line)).length;
99
163
  }
100
164
 
101
165
  function isGitCommand(args: Record<string, unknown> | undefined): boolean {
@@ -103,36 +167,311 @@ function isGitCommand(args: Record<string, unknown> | undefined): boolean {
103
167
  return /^(?:env\s+\S+=\S+\s+|command\s+|\w+=\S+\s+)*git(?:\s|$)/.test(command);
104
168
  }
105
169
 
170
+ export type GentleAiRoutineCommand = "sdd-status" | "sdd-continue" | "sdd-attempt" | "review";
171
+
172
+ const GENTLE_AI_EXECUTABLE = String.raw`(?:gentle-ai(?:\.exe)?|(?:\.{1,2}[\\/]|(?:[A-Za-z]:)?(?:[\\/][^\\/\r\n]+)*[\\/])\.gentle-ai[\\/]v\d+\.\d+\.\d+[\\/]gentle-ai(?:\.exe)?)`;
173
+ const GENTLE_AI_COMMAND_ARGUMENTS = new RegExp(`^${GENTLE_AI_EXECUTABLE}$`);
174
+
175
+ function createGentleAiCommandArguments(activeDevBinaryPath?: string): RegExp {
176
+ if (!activeDevBinaryPath) return GENTLE_AI_COMMAND_ARGUMENTS;
177
+ const escapedPath = activeDevBinaryPath.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
178
+ return new RegExp(`^(?:${GENTLE_AI_EXECUTABLE}|${escapedPath})$`);
179
+ }
180
+
181
+ type ShellTokenization =
182
+ | { kind: "complete" | "incomplete"; tokens: string[] }
183
+ | { kind: "generic" };
184
+
185
+ function shellTokens(command: string): ShellTokenization {
186
+ const tokens: string[] = [];
187
+ let token = "";
188
+ let quote: "single" | "double" | undefined;
189
+ let tokenStarted = false;
190
+ const push = () => {
191
+ if (tokenStarted) tokens.push(token);
192
+ token = "";
193
+ tokenStarted = false;
194
+ };
195
+ for (let index = 0; index < command.length; index += 1) {
196
+ const character = command[index]!;
197
+ if (character === "\r" || character === "\n") return { kind: "generic" };
198
+ if (quote === "single") {
199
+ if (character === "'") quote = undefined;
200
+ else token += character;
201
+ continue;
202
+ }
203
+ if (quote === "double") {
204
+ if (character === '"') { quote = undefined; continue; }
205
+ if (character === "\\") {
206
+ const next = command[++index];
207
+ if (next === undefined || next === "\r" || next === "\n") return { kind: "incomplete", tokens };
208
+ token += "$`\"\\".includes(next) ? next : `\\${next}`;
209
+ continue;
210
+ }
211
+ if (character === "$" || character === "`") return { kind: "generic" };
212
+ token += character;
213
+ continue;
214
+ }
215
+ if (/\s/.test(character)) { push(); continue; }
216
+ if (character === "'") { quote = "single"; tokenStarted = true; continue; }
217
+ if (character === '"') { quote = "double"; tokenStarted = true; continue; }
218
+ if (character === "\\") {
219
+ const next = command[++index];
220
+ if (next === undefined || next === "\r" || next === "\n") return { kind: "incomplete", tokens };
221
+ const windowsPath = token === "." || /^[A-Za-z]:$/.test(token) || token.includes("\\");
222
+ token += windowsPath ? `\\${next}` : next;
223
+ tokenStarted = true;
224
+ continue;
225
+ }
226
+ if ("*?~{}".includes(character)) return { kind: "generic" };
227
+ if (character === "[" && command.indexOf("]", index + 1) >= 0) return { kind: "generic" };
228
+ if (";&|<>`()".includes(character) || character === "$" || character === "`") return { kind: "generic" };
229
+ if (character === "#" && !tokenStarted) return { kind: "generic" };
230
+ token += character;
231
+ tokenStarted = true;
232
+ }
233
+ if (quote) return { kind: "incomplete", tokens };
234
+ push();
235
+ return { kind: "complete", tokens };
236
+ }
237
+
238
+ function isAssignment(token: string): boolean {
239
+ return /^[A-Za-z_][A-Za-z0-9_]*=/.test(token);
240
+ }
241
+ const SDD_ATTEMPT_VERBS = new Set(["acquire", "settle", "grant"]);
242
+
243
+ const REVIEW_DIRECT_OPERATIONS = new Set([
244
+ "capabilities",
245
+ "start",
246
+ "finalize",
247
+ "status",
248
+ "repair",
249
+ "invalidate",
250
+ "abandon",
251
+ "recover",
252
+ "reclaim",
253
+ "validate",
254
+ "capture-result",
255
+ "capture-refuter",
256
+ "capture-validation",
257
+ "capture-evidence",
258
+ "preserve-result",
259
+ "lens-context",
260
+ "retry-final-verification",
261
+ "store-reset",
262
+ "inspect-authority",
263
+ "inspect-candidate",
264
+ "dispose-result",
265
+ "reopen-results",
266
+ "opencode-transport",
267
+ "bind-sdd",
268
+ ]);
269
+ const REVIEW_MODE_VALUES = new Set(["enable", "disable", "status"]);
270
+ const REVIEW_VALIDATE_GATES = new Set(["post-apply", "pre-commit", "pre-push", "pre-pr", "release"]);
271
+ const REVIEW_SCHEMA_NAMES = new Set([
272
+ "capture-result-dry-run",
273
+ "final-verification-incident",
274
+ "refuter",
275
+ "reviewer",
276
+ "validator",
277
+ "verification-evidence",
278
+ "verification-evidence-record",
279
+ ]);
280
+
281
+ function gentleAiCommandTokensFrom(tokens: string[], commandArguments: RegExp): string[] | undefined {
282
+ let index = 0;
283
+ while (isAssignment(tokens[index] ?? "")) index += 1;
284
+ if (tokens[index] === "command") {
285
+ index += 1;
286
+ if (tokens[index] === "--") index += 1;
287
+ else if ((tokens[index] ?? "").startsWith("-")) return undefined;
288
+ }
289
+ if (tokens[index] === "env") {
290
+ index += 1;
291
+ while (isAssignment(tokens[index] ?? "")) index += 1;
292
+ if ((tokens[index] ?? "").startsWith("-")) return undefined;
293
+ }
294
+ if (!commandArguments.test(tokens[index] ?? "")) return undefined;
295
+ return tokens.slice(index + 1);
296
+ }
297
+
298
+ function gentleAiCommandTokens(args: Record<string, unknown> | undefined, commandArguments = GENTLE_AI_COMMAND_ARGUMENTS): string[] | undefined {
299
+ const shell = shellTokens(typeof args?.command === "string" ? args.command : "");
300
+ return shell.kind === "complete" ? gentleAiCommandTokensFrom(shell.tokens, commandArguments) : undefined;
301
+ }
302
+
303
+ function displayToken(token: string): string {
304
+ return token.replace(/-/g, " ");
305
+ }
306
+
307
+ function authorizationRootCount(tokens: string[]): number {
308
+ let count = 0;
309
+ for (let index = 0; index < tokens.length; index += 1) {
310
+ const token = tokens[index]!;
311
+ if (token === "--authorization-root") {
312
+ const value = tokens[index + 1];
313
+ if (value !== undefined && !value.startsWith("-")) count += 1;
314
+ continue;
315
+ }
316
+ if (token.startsWith("--authorization-root=") && token.slice("--authorization-root=".length).length > 0) {
317
+ count += 1;
318
+ }
319
+ }
320
+ return count;
321
+ }
322
+
323
+ function validateGate(tokens: string[]): string | undefined {
324
+ const gateFlag = tokens.findIndex((token) => token === "--gate" || token.startsWith("--gate="));
325
+ if (gateFlag < 0) return undefined;
326
+ const gate = tokens[gateFlag]!.startsWith("--gate=")
327
+ ? tokens[gateFlag]!.slice("--gate=".length)
328
+ : tokens[gateFlag + 1];
329
+ return gate !== undefined && REVIEW_VALIDATE_GATES.has(gate) ? gate : "";
330
+ }
331
+
332
+ /**
333
+ * Matches only supported routine Gentle AI CLI calls, including bounded
334
+ * package-local paths, not arbitrary shell output that merely mentions
335
+ * gentle-ai. These commands otherwise emit machine-readable SDD/RDD data.
336
+ */
337
+ export function isGentleAiDirectCommand(args: Record<string, unknown> | undefined, commandArguments = GENTLE_AI_COMMAND_ARGUMENTS): boolean {
338
+ return gentleAiCommandTokens(args, commandArguments) !== undefined;
339
+ }
340
+
341
+ export function gentleAiRoutineCommand(args: Record<string, unknown> | undefined, commandArguments = GENTLE_AI_COMMAND_ARGUMENTS): GentleAiRoutineCommand | undefined {
342
+ const tokens = gentleAiCommandTokens(args, commandArguments);
343
+ if (!tokens) return undefined;
344
+ if (tokens[0] === "sdd-status") return "sdd-status";
345
+ if (tokens[0] === "sdd-continue") return "sdd-continue";
346
+ if (tokens[0] === "sdd-attempt" && SDD_ATTEMPT_VERBS.has(tokens[1] ?? "")) return "sdd-attempt";
347
+ if (tokens[0] === "review") return "review";
348
+ return undefined;
349
+ }
350
+
351
+ function gentleAiOperationPathFrom(tokens: string[]): string {
352
+ if (tokens[0] === "sdd-status") return "sdd status";
353
+ if (tokens[0] === "sdd-continue") return "sdd continue";
354
+ if (tokens[0] === "sdd-attempt") {
355
+ const verb = tokens[1] ?? "";
356
+ if (!SDD_ATTEMPT_VERBS.has(verb)) return "sdd attempt";
357
+ if (verb !== "grant") return `sdd attempt ${verb}`;
358
+ const rootCount = authorizationRootCount(tokens);
359
+ return rootCount > 0
360
+ ? `sdd attempt grant · ${rootCount} root${rootCount === 1 ? "" : "s"}`
361
+ : "sdd attempt grant";
362
+ }
363
+ if (tokens[0] === "version") return "version";
364
+ if (tokens[0] !== "review") return "command";
365
+
366
+ const operation = tokens[1];
367
+ if (operation === undefined) return "review";
368
+ if (operation === "mode") {
369
+ const mode = tokens[2];
370
+ return mode !== undefined && REVIEW_MODE_VALUES.has(mode) ? `review mode ${displayToken(mode)}` : "review";
371
+ }
372
+ if (operation === "validate") {
373
+ const gate = validateGate(tokens);
374
+ if (gate === "") return "review";
375
+ return gate === undefined ? "review validate" : `review validate ${displayToken(gate)}`;
376
+ }
377
+ if (operation === "schema") {
378
+ const schema = tokens[2];
379
+ return schema !== undefined && REVIEW_SCHEMA_NAMES.has(schema) ? `review schema ${displayToken(schema)}` : "review schema";
380
+ }
381
+ return REVIEW_DIRECT_OPERATIONS.has(operation) ? `review ${displayToken(operation)}` : "review";
382
+ }
383
+
384
+ export function gentleAiOperationPath(args: Record<string, unknown> | undefined, commandArguments = GENTLE_AI_COMMAND_ARGUMENTS): string | undefined {
385
+ const tokens = gentleAiCommandTokens(args, commandArguments);
386
+ return tokens ? gentleAiOperationPathFrom(tokens) : undefined;
387
+ }
388
+
389
+ export function isGentleAiGrantCommand(args: Record<string, unknown> | undefined, commandArguments = GENTLE_AI_COMMAND_ARGUMENTS): boolean {
390
+ const tokens = gentleAiCommandTokens(args, commandArguments);
391
+ return tokens?.[0] === "sdd-attempt" && tokens[1] === "grant";
392
+ }
393
+
106
394
  interface ToolResultFormatOptions {
107
395
  expanded: boolean;
108
396
  isError?: boolean;
109
397
  args?: Record<string, unknown>;
110
398
  }
111
399
 
400
+ function detailsRecord(result: AgentToolResult<unknown>): Record<string, unknown> {
401
+ const details = sanitizeValue(result.details);
402
+ return details && typeof details === "object" && !Array.isArray(details)
403
+ ? details as Record<string, unknown>
404
+ : {};
405
+ }
406
+
407
+ function diffStats(diff: string): { additions: number; removals: number } {
408
+ let additions = 0;
409
+ let removals = 0;
410
+ for (const line of diff.split("\n")) {
411
+ if (line.startsWith("+") && !line.startsWith("+++")) additions++;
412
+ if (line.startsWith("-") && !line.startsWith("---")) removals++;
413
+ }
414
+ return { additions, removals };
415
+ }
416
+
417
+ function editSummary(result: AgentToolResult<unknown>): string {
418
+ const diff = detailsRecord(result).diff;
419
+ if (typeof diff === "string") {
420
+ const stats = diffStats(diff);
421
+ return `✓ +${stats.additions} / -${stats.removals}`;
422
+ }
423
+ return "✓ applied";
424
+ }
425
+
426
+ function writeSummary(text: string): string {
427
+ const bytes = text.match(/(?:Successfully )?wrote\s+(\d+)\s+bytes/i)?.[1];
428
+ return `✓ ${bytes ? `wrote ${bytes} bytes` : "written"}`;
429
+ }
430
+
431
+ function expandedResultText(toolName: QuietToolName, result: AgentToolResult<unknown>, text: string): string {
432
+ if (toolName === "edit") {
433
+ const diff = detailsRecord(result).diff;
434
+ if (typeof diff === "string") return safeText(diff);
435
+ }
436
+ return text;
437
+ }
438
+
112
439
  export function formatToolResultOutput(
113
440
  toolName: QuietToolName,
114
441
  result: AgentToolResult<unknown>,
115
442
  { expanded, isError = false, args }: ToolResultFormatOptions,
116
443
  ): string {
117
444
  const text = safeText(extractTextContent(result));
118
- if (expanded || isError) return text ? `\n${text}` : "";
119
-
445
+ if (expanded) {
446
+ const detail = expandedResultText(toolName, result, text);
447
+ return detail ? `\n${detail}` : "";
448
+ }
449
+ if (isError) {
450
+ const tail = lastMeaningfulOutputLines(text, PREVIEW_LINE_LIMIT);
451
+ return tail ? `\n${tail}` : "";
452
+ }
453
+ const summaryLabel = COLLAPSED_COUNT_LABELS[toolName];
454
+ if (summaryLabel) {
455
+ if (isEmptyResultMessage(toolName, text)) return "";
456
+ const count = toolName === "grep" ? grepMatchCount(text, args) : countNonEmptyLines(text);
457
+ return count > 0 ? ` → ${count} ${summaryLabel}` : "";
458
+ }
120
459
  if (toolName === "bash" && isGitCommand(args)) {
121
460
  const tail = tailLines(text, COLLAPSED_TAIL_LINE_LIMIT);
122
461
  return tail ? `\n${tail}` : "";
123
462
  }
124
- if (NO_COLLAPSED_RESULT_TOOLS.has(toolName)) return "";
125
- if (COLLAPSED_TAIL_TOOLS.has(toolName)) {
126
- const tail = tailLines(text, COLLAPSED_TAIL_LINE_LIMIT);
463
+ if (toolName === "read") {
464
+ const head = firstLines(text, PREVIEW_LINE_LIMIT);
465
+ return head ? `\n${head}` : "";
466
+ }
467
+ if (toolName === "bash") {
468
+ const preview = semanticJsonPreview(text);
469
+ const tail = preview ?? lastOutputLines(text, PREVIEW_LINE_LIMIT);
127
470
  return tail ? `\n${tail}` : "";
128
471
  }
129
- if (isEmptyResultMessage(toolName, text)) return "";
130
-
131
- const summaryLabel = COLLAPSED_COUNT_LABELS[toolName];
132
- if (!summaryLabel) return "";
133
-
134
- const count = countNonEmptyLines(text);
135
- return count > 0 ? ` → ${count} ${summaryLabel}` : "";
472
+ if (toolName === "edit") return `\n${editSummary(result)}`;
473
+ if (toolName === "write") return `\n${writeSummary(text)}`;
474
+ return "";
136
475
  }
137
476
 
138
477
  function lineRangeSuffix(args: Record<string, unknown>, theme: ThemeLike): string {
@@ -142,6 +481,18 @@ function lineRangeSuffix(args: Record<string, unknown>, theme: ThemeLike): strin
142
481
  return theme.fg("warning", `:${startLine}${endLine === undefined ? "" : `-${endLine}`}`);
143
482
  }
144
483
 
484
+ interface ToolRenderContextLike {
485
+ args?: Record<string, unknown>;
486
+ argsComplete?: boolean;
487
+ executionStarted?: boolean;
488
+ isPartial?: boolean;
489
+ isError?: boolean;
490
+ lastComponent?: unknown;
491
+ state?: unknown;
492
+ cwd?: string;
493
+ [key: string]: unknown;
494
+ }
495
+
145
496
  function formatToolCall(toolName: QuietToolName, args: Record<string, unknown>, theme: ThemeLike): string {
146
497
  switch (toolName) {
147
498
  case "read": {
@@ -179,12 +530,98 @@ function formatToolCall(toolName: QuietToolName, args: Record<string, unknown>,
179
530
  }
180
531
  }
181
532
 
182
- function partialLabel(toolName: QuietToolName): string {
183
- return toolName === "bash" ? "Running..." : `${toolName}...`;
533
+ interface BoundedRowSection {
534
+ text: string;
535
+ rows: number;
536
+ tail?: boolean;
537
+ }
538
+
539
+ class BoundedRows implements Component {
540
+ private readonly sections: readonly BoundedRowSection[];
541
+
542
+ constructor(sections: readonly BoundedRowSection[]) {
543
+ this.sections = sections;
544
+ }
545
+
546
+ render(width: number): string[] {
547
+ return this.sections.flatMap(({ text, rows, tail = false }) => {
548
+ if (rows <= 0) return [];
549
+ const rendered = new Text(text, 0, 0).render(width);
550
+ return tail ? rendered.slice(-rows) : rendered.slice(0, rows);
551
+ });
552
+ }
553
+
554
+ invalidate(): void {}
555
+ }
556
+
557
+ function shouldRenderPreviewTail(
558
+ toolName: QuietToolName,
559
+ text: string,
560
+ isError: boolean,
561
+ args: Record<string, unknown> | undefined,
562
+ ): boolean {
563
+ if (isError) return true;
564
+ if (toolName !== "bash") return false;
565
+ return isGitCommand(args) || semanticJsonPreview(text) === undefined;
566
+ }
567
+
568
+ function partialLabel(toolName: QuietToolName, text: string): string {
569
+ const lineCount = countNonEmptyLines(text);
570
+ return lineCount === 0
571
+ ? `… ${toolName}`
572
+ : `… ${toolName} · ${lineCount} ${lineCount === 1 ? "line" : "lines"}`;
573
+ }
574
+
575
+ function hasImageContent(result: AgentToolResult<unknown>): boolean {
576
+ return result.content.some((content) => content.type === "image");
577
+ }
578
+
579
+ function hasExpandableContent(toolName: QuietToolName, result: AgentToolResult<unknown>, text: string): boolean {
580
+ if (isEmptyResultMessage(toolName, text)) return false;
581
+ if (text.length > 0 || hasImageContent(result)) return true;
582
+ const diff = detailsRecord(result).diff;
583
+ return toolName === "edit" && typeof diff === "string" && diff.length > 0;
584
+ }
585
+
586
+ function sanitizedRenderContext(context: ToolRenderContextLike | undefined): ToolRenderContextLike {
587
+ if (!context) return { args: {} };
588
+ return {
589
+ ...context,
590
+ args: sanitizedArgs(context.args),
591
+ cwd: typeof context.cwd === "string" ? safeText(context.cwd) : context.cwd,
592
+ };
593
+ }
594
+
595
+ type GentleAiDevBinaryOverrideResolver = () => GentleAiDevBinaryOverride | undefined;
596
+ function resolveQuietToolsDevBinaryPath(resolveOverride: GentleAiDevBinaryOverrideResolver): string | undefined {
597
+ try { const path = resolveOverride()?.path; return typeof path === "string" && isAbsolute(path) ? path : undefined; }
598
+ catch { return undefined; }
184
599
  }
185
600
 
186
- function registerQuietTool(pi: ExtensionAPI, toolName: QuietToolName): void {
601
+ function gentleAiRenderTransition(
602
+ args: Record<string, unknown> | undefined, context: ToolRenderContextLike | undefined,
603
+ commandArguments: RegExp, options: { result?: boolean; isPartial?: boolean } = {},
604
+ ): { operationPath?: string; directResult: boolean } {
605
+ const state = getGentleAiRenderState(context?.state);
606
+ const tokenization = shellTokens(typeof args?.command === "string" ? args.command : "");
607
+ const directTokens = tokenization.kind === "generic" ? undefined : gentleAiCommandTokensFrom(tokenization.tokens, commandArguments);
608
+ const operationPath = directTokens ? gentleAiOperationPathFrom(directTokens) : undefined;
609
+ const argsComplete = context?.argsComplete === true;
610
+ const forResult = options.result === true;
611
+ const isPartial = options.isPartial === true || context?.isPartial === true;
612
+ if (operationPath && (tokenization.kind === "complete" || !argsComplete) && state?.genericLocked !== true) {
613
+ return { operationPath, directResult: forResult };
614
+ }
615
+ if (forResult && state?.lifecycleComponent === true && state.genericLocked !== true) return { directResult: true };
616
+ if (state && (tokenization.kind === "generic" || argsComplete || (forResult && !isPartial))) {
617
+ state.genericLocked = true; state.lifecycleComponent = false;
618
+ }
619
+ return { directResult: false };
620
+ }
621
+
622
+ function registerQuietTool(pi: ExtensionAPI, toolName: QuietToolName, commandArguments: () => RegExp): void {
187
623
  const registrationTool = getBuiltInTools(process.cwd())[toolName];
624
+ const officialRenderResult = registrationTool.renderResult;
188
625
 
189
626
  pi.registerTool({
190
627
  ...registrationTool,
@@ -192,27 +629,73 @@ function registerQuietTool(pi: ExtensionAPI, toolName: QuietToolName): void {
192
629
  const runtimeTool = getBuiltInTools(ctx.cwd)[toolName];
193
630
  return runtimeTool.execute(toolCallId, params, signal, onUpdate, ctx);
194
631
  },
195
- renderCall(args, theme) {
196
- return new Text(formatToolCall(toolName, args as Record<string, unknown>, theme), 0, 0);
632
+ renderCall(args, theme, context) {
633
+ const callArgs = args as Record<string, unknown>;
634
+ const renderContext = sanitizedRenderContext(context as ToolRenderContextLike | undefined);
635
+ const operationPath = toolName === "bash"
636
+ ? gentleAiRenderTransition(callArgs, renderContext, commandArguments()).operationPath
637
+ : undefined;
638
+ if (operationPath) {
639
+ const detail = renderContext.expanded === true && typeof callArgs.command === "string" ? `$ ${callArgs.command}` : undefined;
640
+ return renderGentleAiLifecycleCall(operationPath, theme, renderContext as GentleAiRenderContext, detail);
641
+ }
642
+ return new Text(formatToolCall(toolName, callArgs, theme), 0, 0);
197
643
  },
198
644
  renderResult(result, options, theme, context) {
645
+ const renderContext = context as ToolRenderContextLike | undefined;
646
+ const safeResult = sanitizedResult(result);
647
+ const text = safeText(extractTextContent(safeResult));
648
+ const isError = renderContext?.isError ?? options.isError ?? false;
649
+ const directResult = toolName === "bash" && gentleAiRenderTransition(
650
+ renderContext?.args,
651
+ renderContext,
652
+ commandArguments(),
653
+ { result: true, isPartial: options.isPartial },
654
+ ).directResult;
655
+ if (directResult) {
656
+ return renderGentleAiResult(safeResult, { expanded: options.expanded });
657
+ }
199
658
  if (options.isPartial) {
200
- return new Text(theme.fg("warning", partialLabel(toolName)), 0, 0);
659
+ if (options.expanded) return new Text(`${theme.fg("warning", partialLabel(toolName, text))}\n${theme.fg("muted", text)}`, 0, 0);
660
+ const visible = lastOutputLines(text, PREVIEW_LINE_LIMIT);
661
+ return new BoundedRows([
662
+ { text: theme.fg("warning", partialLabel(toolName, text)), rows: 1 },
663
+ ...(visible ? [{ text: theme.fg("muted", visible), rows: PREVIEW_LINE_LIMIT, tail: true }] : []),
664
+ ]);
665
+ }
666
+ if (options.expanded && toolName === "read" && hasImageContent(safeResult) && officialRenderResult) {
667
+ return officialRenderResult(
668
+ safeResult,
669
+ options,
670
+ theme,
671
+ sanitizedRenderContext(renderContext) as any,
672
+ );
201
673
  }
202
- const output = formatToolResultOutput(toolName, result, {
674
+ const output = formatToolResultOutput(toolName, safeResult, {
203
675
  expanded: options.expanded,
204
- isError: options.isError,
205
- args: context.args as Record<string, unknown> | undefined,
676
+ isError,
677
+ args: renderContext?.args,
206
678
  });
207
- const color = options.expanded ? "toolOutput" : options.isError ? "error" : "muted";
208
- return new Text(output ? theme.fg(color, output) : "", 0, 0);
679
+ const hint = !options.expanded && !directResult && hasExpandableContent(toolName, safeResult, text)
680
+ ? `\n${keyHint("app.tools.expand", "to expand")}`
681
+ : "";
682
+ const color = options.expanded ? "toolOutput" : isError ? "error" : "muted";
683
+ if (options.expanded) return new Text(output ? theme.fg(color, output) : "", 0, 0);
684
+ if (output) {
685
+ const tail = shouldRenderPreviewTail(toolName, text, isError, renderContext?.args);
686
+ return new BoundedRows([
687
+ { text: theme.fg(color, output.replace(/^\n/, "")), rows: PREVIEW_LINE_LIMIT, tail },
688
+ ...(hint ? [{ text: theme.fg(color, hint.slice(1)), rows: 1 }] : []),
689
+ ]);
690
+ }
691
+ return new Text(hint ? theme.fg(color, hint.slice(1)) : "", 0, 0);
209
692
  },
210
693
  });
211
694
  }
212
695
 
213
- export default function quietTools(pi: ExtensionAPI): void {
696
+ export default function quietTools(pi: ExtensionAPI, resolveOverride: GentleAiDevBinaryOverrideResolver = () => resolveGentleAiDevBinaryOverride()): void {
214
697
  if (!quietToolsEnabled()) return;
215
698
  for (const toolName of Object.keys(TOOL_CREATORS) as QuietToolName[]) {
216
- registerQuietTool(pi, toolName);
699
+ registerQuietTool(pi, toolName, () => createGentleAiCommandArguments(resolveQuietToolsDevBinaryPath(resolveOverride)));
217
700
  }
218
701
  }