gentle-pi 2.2.0 → 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.
- package/README.md +51 -80
- package/assets/agents/review-readability.md +0 -2
- package/assets/agents/review-reliability.md +0 -2
- package/assets/agents/review-resilience.md +0 -2
- package/assets/agents/review-risk.md +0 -2
- package/assets/agents/sdd-apply.md +5 -3
- package/assets/agents/sdd-proposal.md +2 -0
- package/assets/agents/sdd-research.md +54 -0
- package/assets/agents/sdd-status.md +4 -4
- package/assets/agents/sdd-tasks.md +2 -3
- package/assets/agents/sdd-verify.md +21 -1
- package/assets/chains/sdd-full.chain.md +1 -1
- package/assets/chains/sdd-verify.chain.md +1 -1
- package/assets/orchestrator-delegation.md +54 -248
- package/assets/orchestrator-memory.md +2 -0
- package/assets/orchestrator.md +22 -38
- package/assets/sdd-orchestrator-workflow.md +30 -37
- package/assets/support/sdd-status-contract.md +7 -7
- package/contracts/review-integration/v1/schemas/transition-execution.schema.json +42 -0
- package/contracts/review-integration/v2/schemas/last-event-closure.schema.json +66 -0
- package/contracts/review-integration/v2/schemas/opencode-provider-role.schema.json +14 -0
- package/docs/native-authority-architecture.md +9 -11
- package/docs/review-integration.md +27 -373
- package/extensions/ask-user-choice.ts +151 -0
- package/extensions/gentle-ai.ts +1778 -3673
- package/extensions/quiet-tools.ts +515 -32
- package/extensions/sdd-init.ts +4 -8
- package/lib/gentle-ai-renderer.ts +70 -0
- package/lib/model-routing-authority.ts +133 -0
- package/lib/native-review-cli.ts +372 -940
- package/lib/opaque-pi-reviewer-adapter.ts +284 -0
- package/lib/review-candidate-view.ts +341 -132
- package/lib/review-host-relay.ts +210 -68
- package/lib/review-integration-v2.ts +839 -307
- package/lib/review-last-event-controller.ts +35 -0
- package/lib/sdd-preflight.ts +177 -66
- package/lib/sdd-status.ts +66 -111
- package/lib/terminal-theme.ts +1 -1
- package/package.json +83 -82
- package/runtime/gentle-ai-binary.mjs +1 -1
- package/runtime/native-review-cli.mjs +322 -890
- package/runtime/review-integration-v2.mjs +789 -257
- package/runtime/review-relay-contract.mjs +1 -1
- package/scripts/{build-git-commit-transaction-runner.mjs → build-runtime-modules.mjs} +4 -5
- package/scripts/gentle-ai-installer.mjs +75 -22
- package/scripts/maintainer/provider-relay-matrix.mjs +195 -11
- package/scripts/test-packed-runner.mjs +4 -7
- package/scripts/verify-package-files.mjs +12 -13
- package/skills/_shared/review-ledger-contract.md +8 -14
- package/skills/chained-pr/SKILL.md +3 -0
- package/skills/cognitive-doc-design/SKILL.md +1 -1
- package/skills/comment-writer/SKILL.md +1 -1
- package/skills/gentle-ai/SKILL.md +7 -74
- package/skills/judgment-day/SKILL.md +5 -7
- package/skills/rdd-defect-workflow/SKILL.md +3 -3
- package/skills/release/SKILL.md +3 -3
- package/skills/skill-registry/SKILL.md +1 -1
- package/skills/work-unit-commits/SKILL.md +3 -1
- package/tests/artifact-language.test.ts +24 -11
- package/tests/ask-user-choice.test.ts +264 -0
- package/tests/codegraph-tools.test.ts +3 -3
- package/tests/crosslane/cross-lane.mjs +15 -1168
- package/tests/delegated-key-learnings-contract.test.ts +8 -6
- package/tests/devbinary/native-review-parity.devtest.ts +167 -250
- package/tests/devbinary/pi-host-relay.devtest.ts +867 -0
- package/tests/fixtures/devbinary/last-event-capture-correction-plan.captured.json +10 -0
- package/tests/fixtures/devbinary/last-event-capture-refuter-approved.captured.json +20 -0
- package/tests/fixtures/devbinary/last-event-capture-refuter-correction-required.captured.json +26 -0
- package/tests/fixtures/devbinary/last-event-capture-result-approved.captured.json +8 -0
- package/tests/fixtures/devbinary/last-event-capture-result-correction-required.captured.json +26 -0
- package/tests/fixtures/devbinary/last-event-capture-validation-approved.captured.json +8 -0
- package/tests/fixtures/devbinary/last-event-closure.provenance.md +13 -0
- package/tests/fixtures/devbinary/review-acknowledged-v1.captured.json +9 -0
- package/tests/fixtures/devbinary/review-acknowledged.provenance.md +31 -0
- package/tests/fixtures/devbinary/start-v3-zero-lens-closed.captured.json +21 -0
- package/tests/fixtures/native-review-cli/v2.5.0-rc.1/PROVENANCE.txt +15 -0
- package/tests/gentle-ai-binary.test.ts +83 -13
- package/tests/gentle-ai-dev-binary.test.ts +51 -228
- package/tests/gentle-ai-installer.test.ts +137 -47
- package/tests/gentle-ai.test.ts +820 -131
- package/tests/gentle-theme.test.ts +133 -0
- package/tests/maintainer/provider-relay.maintest.ts +340 -4
- package/tests/model-routing-authority.test.ts +257 -0
- package/tests/native-review-capability-contract.test.ts +28 -1
- package/tests/native-review-cli.test.ts +586 -962
- package/tests/native-review-consent.test.ts +150 -66
- package/tests/native-review-parity-runtime.test.ts +80 -359
- package/tests/native-review-parity.test.ts +674 -925
- package/tests/opaque-pi-reviewer-adapter.test.ts +266 -0
- package/tests/orchestrator-budget.test.ts +132 -88
- package/tests/orchestrator-rdd-ownership.test.ts +103 -0
- package/tests/package-manifest.test.ts +37 -35
- package/tests/provider-defect-handoff.test.ts +15 -118
- package/tests/quiet-tool-rendering.test.ts +1055 -28
- package/tests/review-authority-recovery-docs.test.ts +1 -2
- package/tests/review-candidate-view.test.ts +409 -3
- package/tests/review-compact-contract.test.ts +4 -30
- package/tests/review-controller-lock-status.test.ts +2 -2
- package/tests/review-controller-native-recovery.test.ts +363 -1450
- package/tests/review-controller-native-routing.test.ts +1251 -5642
- package/tests/review-controller-retired-ops.test.ts +1 -1
- package/tests/review-controller-workspace-root.test.ts +222 -71
- package/tests/review-controller.test.ts +26 -816
- package/tests/review-corrected-finalize-binding.test.ts +112 -153
- package/tests/review-dispatch-hydration-gap.test.ts +1 -53
- package/tests/review-gate.test.ts +0 -45
- package/tests/review-host-relay-restart-parity.test.ts +360 -0
- package/tests/review-host-relay-routing.test.ts +117 -82
- package/tests/review-host-relay.test.ts +242 -8
- package/tests/review-integration-v2-forward.test.ts +576 -553
- package/tests/review-integration-v2.test.ts +239 -165
- package/tests/review-last-event-closure.test.ts +408 -0
- package/tests/review-ledger-contract.test.ts +97 -35
- package/tests/review-recovered-lineage-routing.test.ts +0 -47
- package/tests/review-relay-transport-agent.test.ts +119 -46
- package/tests/review-snapshot.test.ts +3 -2
- package/tests/runtime-harness.mjs +361 -196
- package/tests/sdd-agent-tools.test.ts +36 -0
- package/tests/sdd-preflight.test.ts +81 -15
- package/tests/sdd-status.test.ts +109 -110
- package/tests/skill-collision-prefixes.test.ts +5 -8
- package/tests/writer-edit-surface-scope.test.ts +230 -0
- package/themes/Gentleman-Cute.json +94 -0
- package/themes/Gentleman-Sexy.json +92 -0
- package/lib/git-commit-transaction.ts +0 -861
- package/runtime/git-commit-transaction.mjs +0 -862
- package/scripts/run-git-commit-transaction.mjs +0 -35
- package/tests/fixtures/native-review-cli/v2.1.3/sdd-status-engram.json +0 -139
- package/tests/fixtures/native-review-cli/v2.1.3/sdd-status.json +0 -200
- package/tests/git-commit-transaction.test.ts +0 -530
|
@@ -5,12 +5,16 @@ import {
|
|
|
5
5
|
createFindTool,
|
|
6
6
|
createGrepTool,
|
|
7
7
|
createLsTool,
|
|
8
|
-
|
|
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:
|
|
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
|
|
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
|
|
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 (
|
|
125
|
-
|
|
126
|
-
|
|
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 (
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
|
|
183
|
-
|
|
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
|
|
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
|
-
|
|
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,
|
|
674
|
+
const output = formatToolResultOutput(toolName, safeResult, {
|
|
203
675
|
expanded: options.expanded,
|
|
204
|
-
isError
|
|
205
|
-
args:
|
|
676
|
+
isError,
|
|
677
|
+
args: renderContext?.args,
|
|
206
678
|
});
|
|
207
|
-
const
|
|
208
|
-
|
|
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
|
}
|
package/extensions/sdd-init.ts
CHANGED
|
@@ -771,15 +771,11 @@ function ensureOpenSpecDirs(cwd: string): void {
|
|
|
771
771
|
}
|
|
772
772
|
|
|
773
773
|
export default function (pi: ExtensionAPI) {
|
|
774
|
-
pi.registerCommand("sdd-init", {
|
|
774
|
+
pi.registerCommand("gentle-sdd-init", {
|
|
775
775
|
description:
|
|
776
776
|
"Auto-detect project stack and bootstrap openspec/config.yaml for SDD.",
|
|
777
777
|
handler: async (_args: unknown, ctx: any) => {
|
|
778
|
-
const prefs = await ensureSddPreflight(ctx, {
|
|
779
|
-
pi,
|
|
780
|
-
installAssets: (cwd) => installSddAssets(cwd, false),
|
|
781
|
-
applyModelConfig: () => applySavedModelConfig(ctx),
|
|
782
|
-
});
|
|
778
|
+
const prefs = await ensureSddPreflight(ctx, { pi, installAssets: (cwd) => installSddAssets(cwd, false), applyModelConfig: () => applySavedModelConfig(ctx) }, { promptFields: [] });
|
|
783
779
|
|
|
784
780
|
const detection = detectProject(ctx.cwd);
|
|
785
781
|
const testSummary = detection.testCommand
|
|
@@ -789,7 +785,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
789
785
|
|
|
790
786
|
const shouldCreateOpenSpec =
|
|
791
787
|
prefs.artifactStore === "openspec" ||
|
|
792
|
-
prefs.artifactStore === "
|
|
788
|
+
prefs.artifactStore === "hybrid";
|
|
793
789
|
if (!shouldCreateOpenSpec) {
|
|
794
790
|
ctx.ui.notify(
|
|
795
791
|
`SDD initialized for ${prefs.artifactStore}: detected ${detection.stack.join(", ") || "project"}; ${testSummary}; tests found: ${layerSummary}.`,
|
|
@@ -801,7 +797,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
801
797
|
const configPath = join(ctx.cwd, CONFIG_REL_PATH);
|
|
802
798
|
if (existsSync(configPath)) {
|
|
803
799
|
ctx.ui.notify(
|
|
804
|
-
`${CONFIG_REL_PATH} already exists. Edit it manually or remove it before re-running /sdd-init.`,
|
|
800
|
+
`${CONFIG_REL_PATH} already exists. Edit it manually or remove it before re-running /gentle-sdd-init.`,
|
|
805
801
|
"warning",
|
|
806
802
|
);
|
|
807
803
|
return;
|