ralph-cli-sandboxed 0.6.6 → 0.7.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/LICENSE +191 -0
- package/README.md +1 -1
- package/dist/commands/ask.d.ts +6 -0
- package/dist/commands/ask.js +140 -0
- package/dist/commands/branch.js +8 -4
- package/dist/commands/chat.js +11 -8
- package/dist/commands/docker.js +19 -4
- package/dist/commands/fix-config.js +0 -41
- package/dist/commands/help.js +10 -0
- package/dist/commands/run.js +9 -9
- package/dist/index.js +2 -0
- package/dist/providers/telegram.js +1 -1
- package/dist/responders/claude-code-responder.js +1 -0
- package/dist/responders/cli-responder.js +1 -0
- package/dist/responders/llm-responder.js +1 -1
- package/dist/templates/macos-scripts.js +18 -18
- package/dist/tui/components/JsonSnippetEditor.js +7 -7
- package/dist/tui/components/KeyValueEditor.js +5 -1
- package/dist/tui/components/LLMProvidersEditor.js +7 -9
- package/dist/tui/components/Preview.js +1 -1
- package/dist/tui/components/SectionNav.js +18 -2
- package/dist/utils/chat-client.js +1 -0
- package/dist/utils/config.d.ts +1 -0
- package/dist/utils/config.js +3 -1
- package/dist/utils/config.test.d.ts +1 -0
- package/dist/utils/config.test.js +424 -0
- package/dist/utils/notification.js +1 -1
- package/dist/utils/prd-validator.js +16 -4
- package/dist/utils/prd-validator.test.d.ts +1 -0
- package/dist/utils/prd-validator.test.js +1095 -0
- package/dist/utils/responder.js +4 -1
- package/dist/utils/stream-json.test.d.ts +1 -0
- package/dist/utils/stream-json.test.js +1007 -0
- package/docs/DOCKER.md +14 -0
- package/docs/PRD-GENERATOR.md +15 -0
- package/package.json +16 -13
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { getCliConfig, getLLMProviders, getLLMProviderApiKey, getLLMProviderBaseUrl, DEFAULT_LLM_PROVIDERS, DEFAULT_CLI_CONFIG, } from "./config.js";
|
|
3
|
+
// ─── getCliConfig ───────────────────────────────────────────────────
|
|
4
|
+
describe("getCliConfig", () => {
|
|
5
|
+
it("returns default CLI config when none specified", () => {
|
|
6
|
+
const config = {
|
|
7
|
+
language: "typescript",
|
|
8
|
+
checkCommand: "npm run build",
|
|
9
|
+
testCommand: "npm test",
|
|
10
|
+
};
|
|
11
|
+
const result = getCliConfig(config);
|
|
12
|
+
expect(result.command).toBe("claude");
|
|
13
|
+
expect(result.promptArgs).toEqual(["-p"]);
|
|
14
|
+
});
|
|
15
|
+
it("returns custom CLI config from config", () => {
|
|
16
|
+
const config = {
|
|
17
|
+
language: "typescript",
|
|
18
|
+
checkCommand: "npm run build",
|
|
19
|
+
testCommand: "npm test",
|
|
20
|
+
cli: {
|
|
21
|
+
command: "aider",
|
|
22
|
+
args: ["--yes"],
|
|
23
|
+
promptArgs: ["--message"],
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
const result = getCliConfig(config);
|
|
27
|
+
expect(result.command).toBe("aider");
|
|
28
|
+
expect(result.promptArgs).toEqual(["--message"]);
|
|
29
|
+
});
|
|
30
|
+
it("defaults promptArgs to ['-p'] when not set", () => {
|
|
31
|
+
const config = {
|
|
32
|
+
language: "typescript",
|
|
33
|
+
checkCommand: "npm run build",
|
|
34
|
+
testCommand: "npm test",
|
|
35
|
+
cli: { command: "custom-cli" },
|
|
36
|
+
};
|
|
37
|
+
const result = getCliConfig(config);
|
|
38
|
+
expect(result.promptArgs).toEqual(["-p"]);
|
|
39
|
+
});
|
|
40
|
+
it("preserves args and yoloArgs from custom cli config", () => {
|
|
41
|
+
const config = {
|
|
42
|
+
language: "typescript",
|
|
43
|
+
checkCommand: "npm run build",
|
|
44
|
+
testCommand: "npm test",
|
|
45
|
+
cli: {
|
|
46
|
+
command: "aider",
|
|
47
|
+
args: ["--yes", "--no-auto-commits"],
|
|
48
|
+
yoloArgs: ["--auto-accept"],
|
|
49
|
+
promptArgs: ["--message"],
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
const result = getCliConfig(config);
|
|
53
|
+
expect(result.args).toEqual(["--yes", "--no-auto-commits"]);
|
|
54
|
+
expect(result.yoloArgs).toEqual(["--auto-accept"]);
|
|
55
|
+
});
|
|
56
|
+
it("preserves model and modelArgs from custom cli config", () => {
|
|
57
|
+
const config = {
|
|
58
|
+
language: "typescript",
|
|
59
|
+
checkCommand: "npm run build",
|
|
60
|
+
testCommand: "npm test",
|
|
61
|
+
cli: {
|
|
62
|
+
command: "claude",
|
|
63
|
+
model: "claude-opus-4-20250514",
|
|
64
|
+
modelArgs: ["--model"],
|
|
65
|
+
promptArgs: ["-p"],
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
const result = getCliConfig(config);
|
|
69
|
+
expect(result.model).toBe("claude-opus-4-20250514");
|
|
70
|
+
expect(result.modelArgs).toEqual(["--model"]);
|
|
71
|
+
});
|
|
72
|
+
it("preserves fileArgs from custom cli config", () => {
|
|
73
|
+
const config = {
|
|
74
|
+
language: "typescript",
|
|
75
|
+
checkCommand: "npm run build",
|
|
76
|
+
testCommand: "npm test",
|
|
77
|
+
cli: {
|
|
78
|
+
command: "aider",
|
|
79
|
+
fileArgs: ["--read"],
|
|
80
|
+
promptArgs: ["--message"],
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
const result = getCliConfig(config);
|
|
84
|
+
expect(result.fileArgs).toEqual(["--read"]);
|
|
85
|
+
});
|
|
86
|
+
it("uses cliProvider to fill in promptArgs when cli.promptArgs is undefined", () => {
|
|
87
|
+
const config = {
|
|
88
|
+
language: "typescript",
|
|
89
|
+
checkCommand: "npm run build",
|
|
90
|
+
testCommand: "npm test",
|
|
91
|
+
cliProvider: "claude",
|
|
92
|
+
cli: { command: "claude" },
|
|
93
|
+
};
|
|
94
|
+
const result = getCliConfig(config);
|
|
95
|
+
// Should pick up promptArgs from the claude provider
|
|
96
|
+
expect(result.promptArgs).toBeDefined();
|
|
97
|
+
expect(Array.isArray(result.promptArgs)).toBe(true);
|
|
98
|
+
});
|
|
99
|
+
it("does not override explicit cli.promptArgs with cliProvider", () => {
|
|
100
|
+
const config = {
|
|
101
|
+
language: "typescript",
|
|
102
|
+
checkCommand: "npm run build",
|
|
103
|
+
testCommand: "npm test",
|
|
104
|
+
cliProvider: "claude",
|
|
105
|
+
cli: {
|
|
106
|
+
command: "claude",
|
|
107
|
+
promptArgs: ["--custom-prompt"],
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
const result = getCliConfig(config);
|
|
111
|
+
expect(result.promptArgs).toEqual(["--custom-prompt"]);
|
|
112
|
+
});
|
|
113
|
+
it("falls back to ['-p'] when cliProvider is set but provider not found", () => {
|
|
114
|
+
const config = {
|
|
115
|
+
language: "typescript",
|
|
116
|
+
checkCommand: "npm run build",
|
|
117
|
+
testCommand: "npm test",
|
|
118
|
+
cliProvider: "nonexistent-provider",
|
|
119
|
+
cli: { command: "custom-cli" },
|
|
120
|
+
};
|
|
121
|
+
const result = getCliConfig(config);
|
|
122
|
+
expect(result.promptArgs).toEqual(["-p"]);
|
|
123
|
+
});
|
|
124
|
+
it("returns a new object, not the same reference as DEFAULT_CLI_CONFIG", () => {
|
|
125
|
+
const config = {
|
|
126
|
+
language: "typescript",
|
|
127
|
+
checkCommand: "npm run build",
|
|
128
|
+
testCommand: "npm test",
|
|
129
|
+
};
|
|
130
|
+
const result = getCliConfig(config);
|
|
131
|
+
expect(result).not.toBe(DEFAULT_CLI_CONFIG);
|
|
132
|
+
});
|
|
133
|
+
it("handles empty cli object", () => {
|
|
134
|
+
const config = {
|
|
135
|
+
language: "typescript",
|
|
136
|
+
checkCommand: "npm run build",
|
|
137
|
+
testCommand: "npm test",
|
|
138
|
+
cli: {},
|
|
139
|
+
};
|
|
140
|
+
const result = getCliConfig(config);
|
|
141
|
+
expect(result.promptArgs).toEqual(["-p"]);
|
|
142
|
+
expect(result.command).toBeUndefined();
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
// ─── getLLMProviders ────────────────────────────────────────────────
|
|
146
|
+
describe("getLLMProviders", () => {
|
|
147
|
+
it("returns default providers when none configured", () => {
|
|
148
|
+
const config = {
|
|
149
|
+
language: "typescript",
|
|
150
|
+
checkCommand: "npm run build",
|
|
151
|
+
testCommand: "npm test",
|
|
152
|
+
};
|
|
153
|
+
const result = getLLMProviders(config);
|
|
154
|
+
expect(result.anthropic).toBeDefined();
|
|
155
|
+
expect(result.openai).toBeDefined();
|
|
156
|
+
expect(result.ollama).toBeDefined();
|
|
157
|
+
});
|
|
158
|
+
it("merges custom providers with defaults", () => {
|
|
159
|
+
const config = {
|
|
160
|
+
language: "typescript",
|
|
161
|
+
checkCommand: "npm run build",
|
|
162
|
+
testCommand: "npm test",
|
|
163
|
+
llmProviders: {
|
|
164
|
+
custom: {
|
|
165
|
+
type: "openai",
|
|
166
|
+
model: "gpt-4-turbo",
|
|
167
|
+
apiKey: "sk-test",
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
const result = getLLMProviders(config);
|
|
172
|
+
expect(result.custom).toBeDefined();
|
|
173
|
+
expect(result.custom.model).toBe("gpt-4-turbo");
|
|
174
|
+
// Defaults still present
|
|
175
|
+
expect(result.anthropic).toBeDefined();
|
|
176
|
+
});
|
|
177
|
+
it("allows overriding default providers", () => {
|
|
178
|
+
const config = {
|
|
179
|
+
language: "typescript",
|
|
180
|
+
checkCommand: "npm run build",
|
|
181
|
+
testCommand: "npm test",
|
|
182
|
+
llmProviders: {
|
|
183
|
+
anthropic: {
|
|
184
|
+
type: "anthropic",
|
|
185
|
+
model: "claude-opus-4-20250514",
|
|
186
|
+
apiKey: "sk-custom",
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
const result = getLLMProviders(config);
|
|
191
|
+
expect(result.anthropic.model).toBe("claude-opus-4-20250514");
|
|
192
|
+
expect(result.anthropic.apiKey).toBe("sk-custom");
|
|
193
|
+
});
|
|
194
|
+
it("returns all three default providers with empty llmProviders", () => {
|
|
195
|
+
const config = {
|
|
196
|
+
language: "typescript",
|
|
197
|
+
checkCommand: "npm run build",
|
|
198
|
+
testCommand: "npm test",
|
|
199
|
+
llmProviders: {},
|
|
200
|
+
};
|
|
201
|
+
const result = getLLMProviders(config);
|
|
202
|
+
expect(Object.keys(result)).toContain("anthropic");
|
|
203
|
+
expect(Object.keys(result)).toContain("openai");
|
|
204
|
+
expect(Object.keys(result)).toContain("ollama");
|
|
205
|
+
});
|
|
206
|
+
it("can add multiple custom providers at once", () => {
|
|
207
|
+
const config = {
|
|
208
|
+
language: "typescript",
|
|
209
|
+
checkCommand: "npm run build",
|
|
210
|
+
testCommand: "npm test",
|
|
211
|
+
llmProviders: {
|
|
212
|
+
"my-gpt": { type: "openai", model: "gpt-4o-mini" },
|
|
213
|
+
"my-claude": { type: "anthropic", model: "claude-haiku-4-5-20251001" },
|
|
214
|
+
"local-llm": { type: "ollama", model: "codellama", baseUrl: "http://gpu-server:11434" },
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
const result = getLLMProviders(config);
|
|
218
|
+
expect(result["my-gpt"].model).toBe("gpt-4o-mini");
|
|
219
|
+
expect(result["my-claude"].model).toBe("claude-haiku-4-5-20251001");
|
|
220
|
+
expect(result["local-llm"].baseUrl).toBe("http://gpu-server:11434");
|
|
221
|
+
// defaults still present
|
|
222
|
+
expect(result.anthropic).toBeDefined();
|
|
223
|
+
});
|
|
224
|
+
it("override completely replaces a default provider (no deep merge)", () => {
|
|
225
|
+
const config = {
|
|
226
|
+
language: "typescript",
|
|
227
|
+
checkCommand: "npm run build",
|
|
228
|
+
testCommand: "npm test",
|
|
229
|
+
llmProviders: {
|
|
230
|
+
ollama: {
|
|
231
|
+
type: "ollama",
|
|
232
|
+
model: "mistral",
|
|
233
|
+
// no baseUrl set
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
const result = getLLMProviders(config);
|
|
238
|
+
// The override replaces the whole object, so baseUrl from default is gone
|
|
239
|
+
expect(result.ollama.model).toBe("mistral");
|
|
240
|
+
expect(result.ollama.baseUrl).toBeUndefined();
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
// ─── getLLMProviderApiKey ───────────────────────────────────────────
|
|
244
|
+
describe("getLLMProviderApiKey", () => {
|
|
245
|
+
const originalEnv = process.env;
|
|
246
|
+
beforeEach(() => {
|
|
247
|
+
process.env = { ...originalEnv };
|
|
248
|
+
});
|
|
249
|
+
afterEach(() => {
|
|
250
|
+
process.env = originalEnv;
|
|
251
|
+
});
|
|
252
|
+
it("returns explicit API key when set", () => {
|
|
253
|
+
const provider = {
|
|
254
|
+
type: "anthropic",
|
|
255
|
+
model: "claude-sonnet-4-20250514",
|
|
256
|
+
apiKey: "sk-explicit",
|
|
257
|
+
};
|
|
258
|
+
expect(getLLMProviderApiKey(provider)).toBe("sk-explicit");
|
|
259
|
+
});
|
|
260
|
+
it("falls back to ANTHROPIC_API_KEY env var", () => {
|
|
261
|
+
process.env.ANTHROPIC_API_KEY = "sk-env-anthropic";
|
|
262
|
+
const provider = {
|
|
263
|
+
type: "anthropic",
|
|
264
|
+
model: "claude-sonnet-4-20250514",
|
|
265
|
+
};
|
|
266
|
+
expect(getLLMProviderApiKey(provider)).toBe("sk-env-anthropic");
|
|
267
|
+
});
|
|
268
|
+
it("falls back to OPENAI_API_KEY env var", () => {
|
|
269
|
+
process.env.OPENAI_API_KEY = "sk-env-openai";
|
|
270
|
+
const provider = {
|
|
271
|
+
type: "openai",
|
|
272
|
+
model: "gpt-4o",
|
|
273
|
+
};
|
|
274
|
+
expect(getLLMProviderApiKey(provider)).toBe("sk-env-openai");
|
|
275
|
+
});
|
|
276
|
+
it("returns undefined for ollama", () => {
|
|
277
|
+
const provider = {
|
|
278
|
+
type: "ollama",
|
|
279
|
+
model: "llama3",
|
|
280
|
+
};
|
|
281
|
+
expect(getLLMProviderApiKey(provider)).toBeUndefined();
|
|
282
|
+
});
|
|
283
|
+
it("returns undefined for anthropic when no env var set", () => {
|
|
284
|
+
delete process.env.ANTHROPIC_API_KEY;
|
|
285
|
+
const provider = {
|
|
286
|
+
type: "anthropic",
|
|
287
|
+
model: "claude-sonnet-4-20250514",
|
|
288
|
+
};
|
|
289
|
+
expect(getLLMProviderApiKey(provider)).toBeUndefined();
|
|
290
|
+
});
|
|
291
|
+
it("returns undefined for openai when no env var set", () => {
|
|
292
|
+
delete process.env.OPENAI_API_KEY;
|
|
293
|
+
const provider = {
|
|
294
|
+
type: "openai",
|
|
295
|
+
model: "gpt-4o",
|
|
296
|
+
};
|
|
297
|
+
expect(getLLMProviderApiKey(provider)).toBeUndefined();
|
|
298
|
+
});
|
|
299
|
+
it("returns undefined for unknown provider type", () => {
|
|
300
|
+
const provider = {
|
|
301
|
+
type: "unknown-provider",
|
|
302
|
+
model: "some-model",
|
|
303
|
+
};
|
|
304
|
+
expect(getLLMProviderApiKey(provider)).toBeUndefined();
|
|
305
|
+
});
|
|
306
|
+
it("explicit key takes precedence over env var", () => {
|
|
307
|
+
process.env.ANTHROPIC_API_KEY = "sk-env";
|
|
308
|
+
const provider = {
|
|
309
|
+
type: "anthropic",
|
|
310
|
+
model: "claude-sonnet-4-20250514",
|
|
311
|
+
apiKey: "sk-explicit-wins",
|
|
312
|
+
};
|
|
313
|
+
expect(getLLMProviderApiKey(provider)).toBe("sk-explicit-wins");
|
|
314
|
+
});
|
|
315
|
+
it("returns undefined for ollama even with explicit empty string", () => {
|
|
316
|
+
const provider = {
|
|
317
|
+
type: "ollama",
|
|
318
|
+
model: "llama3",
|
|
319
|
+
apiKey: "",
|
|
320
|
+
};
|
|
321
|
+
// empty string is falsy, so falls through to switch which returns undefined
|
|
322
|
+
expect(getLLMProviderApiKey(provider)).toBeUndefined();
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
// ─── getLLMProviderBaseUrl ──────────────────────────────────────────
|
|
326
|
+
describe("getLLMProviderBaseUrl", () => {
|
|
327
|
+
it("returns explicit baseUrl when set", () => {
|
|
328
|
+
const provider = {
|
|
329
|
+
type: "anthropic",
|
|
330
|
+
model: "claude-sonnet-4-20250514",
|
|
331
|
+
baseUrl: "https://custom.api.com",
|
|
332
|
+
};
|
|
333
|
+
expect(getLLMProviderBaseUrl(provider)).toBe("https://custom.api.com");
|
|
334
|
+
});
|
|
335
|
+
it("returns default URL for anthropic", () => {
|
|
336
|
+
const provider = {
|
|
337
|
+
type: "anthropic",
|
|
338
|
+
model: "claude-sonnet-4-20250514",
|
|
339
|
+
};
|
|
340
|
+
expect(getLLMProviderBaseUrl(provider)).toBe("https://api.anthropic.com");
|
|
341
|
+
});
|
|
342
|
+
it("returns default URL for openai", () => {
|
|
343
|
+
const provider = {
|
|
344
|
+
type: "openai",
|
|
345
|
+
model: "gpt-4o",
|
|
346
|
+
};
|
|
347
|
+
expect(getLLMProviderBaseUrl(provider)).toBe("https://api.openai.com/v1");
|
|
348
|
+
});
|
|
349
|
+
it("returns default URL for ollama", () => {
|
|
350
|
+
const provider = {
|
|
351
|
+
type: "ollama",
|
|
352
|
+
model: "llama3",
|
|
353
|
+
};
|
|
354
|
+
expect(getLLMProviderBaseUrl(provider)).toBe("http://localhost:11434");
|
|
355
|
+
});
|
|
356
|
+
it("returns empty string for unknown provider type", () => {
|
|
357
|
+
const provider = {
|
|
358
|
+
type: "unknown",
|
|
359
|
+
model: "some-model",
|
|
360
|
+
};
|
|
361
|
+
expect(getLLMProviderBaseUrl(provider)).toBe("");
|
|
362
|
+
});
|
|
363
|
+
it("explicit baseUrl overrides default for openai", () => {
|
|
364
|
+
const provider = {
|
|
365
|
+
type: "openai",
|
|
366
|
+
model: "gpt-4o",
|
|
367
|
+
baseUrl: "https://my-proxy.example.com/v1",
|
|
368
|
+
};
|
|
369
|
+
expect(getLLMProviderBaseUrl(provider)).toBe("https://my-proxy.example.com/v1");
|
|
370
|
+
});
|
|
371
|
+
it("explicit baseUrl overrides default for ollama", () => {
|
|
372
|
+
const provider = {
|
|
373
|
+
type: "ollama",
|
|
374
|
+
model: "llama3",
|
|
375
|
+
baseUrl: "http://remote-gpu:11434",
|
|
376
|
+
};
|
|
377
|
+
expect(getLLMProviderBaseUrl(provider)).toBe("http://remote-gpu:11434");
|
|
378
|
+
});
|
|
379
|
+
});
|
|
380
|
+
// ─── DEFAULT_LLM_PROVIDERS ─────────────────────────────────────────
|
|
381
|
+
describe("DEFAULT_LLM_PROVIDERS", () => {
|
|
382
|
+
it("has anthropic, openai, and ollama providers", () => {
|
|
383
|
+
expect(DEFAULT_LLM_PROVIDERS.anthropic.type).toBe("anthropic");
|
|
384
|
+
expect(DEFAULT_LLM_PROVIDERS.openai.type).toBe("openai");
|
|
385
|
+
expect(DEFAULT_LLM_PROVIDERS.ollama.type).toBe("ollama");
|
|
386
|
+
});
|
|
387
|
+
it("ollama has localhost baseUrl", () => {
|
|
388
|
+
expect(DEFAULT_LLM_PROVIDERS.ollama.baseUrl).toBe("http://localhost:11434");
|
|
389
|
+
});
|
|
390
|
+
it("anthropic has a valid model name", () => {
|
|
391
|
+
expect(DEFAULT_LLM_PROVIDERS.anthropic.model).toMatch(/^claude-/);
|
|
392
|
+
});
|
|
393
|
+
it("openai has a valid model name", () => {
|
|
394
|
+
expect(DEFAULT_LLM_PROVIDERS.openai.model).toMatch(/^gpt-/);
|
|
395
|
+
});
|
|
396
|
+
it("no default providers have explicit API keys", () => {
|
|
397
|
+
expect(DEFAULT_LLM_PROVIDERS.anthropic.apiKey).toBeUndefined();
|
|
398
|
+
expect(DEFAULT_LLM_PROVIDERS.openai.apiKey).toBeUndefined();
|
|
399
|
+
expect(DEFAULT_LLM_PROVIDERS.ollama.apiKey).toBeUndefined();
|
|
400
|
+
});
|
|
401
|
+
it("anthropic and openai have no explicit baseUrl (use runtime defaults)", () => {
|
|
402
|
+
expect(DEFAULT_LLM_PROVIDERS.anthropic.baseUrl).toBeUndefined();
|
|
403
|
+
expect(DEFAULT_LLM_PROVIDERS.openai.baseUrl).toBeUndefined();
|
|
404
|
+
});
|
|
405
|
+
it("has exactly three default providers", () => {
|
|
406
|
+
expect(Object.keys(DEFAULT_LLM_PROVIDERS)).toHaveLength(3);
|
|
407
|
+
});
|
|
408
|
+
});
|
|
409
|
+
// ─── DEFAULT_CLI_CONFIG ─────────────────────────────────────────────
|
|
410
|
+
describe("DEFAULT_CLI_CONFIG", () => {
|
|
411
|
+
it("uses claude as default command", () => {
|
|
412
|
+
expect(DEFAULT_CLI_CONFIG.command).toBe("claude");
|
|
413
|
+
expect(DEFAULT_CLI_CONFIG.promptArgs).toEqual(["-p"]);
|
|
414
|
+
});
|
|
415
|
+
it("has empty args array", () => {
|
|
416
|
+
expect(DEFAULT_CLI_CONFIG.args).toEqual([]);
|
|
417
|
+
});
|
|
418
|
+
it("has no yoloArgs by default", () => {
|
|
419
|
+
expect(DEFAULT_CLI_CONFIG.yoloArgs).toBeUndefined();
|
|
420
|
+
});
|
|
421
|
+
it("has no model by default", () => {
|
|
422
|
+
expect(DEFAULT_CLI_CONFIG.model).toBeUndefined();
|
|
423
|
+
});
|
|
424
|
+
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { spawn } from "child_process";
|
|
2
|
-
import { isRunningInContainer
|
|
2
|
+
import { isRunningInContainer } from "./config.js";
|
|
3
3
|
import { isDaemonAvailable, sendDaemonNotification, sendDaemonRequest, sendSlackNotification, sendTelegramNotification, sendDiscordNotification, } from "./daemon-client.js";
|
|
4
4
|
/**
|
|
5
5
|
* Send a notification using the configured notify command.
|
|
@@ -435,7 +435,7 @@ export function createTemplatePrd(backupPath) {
|
|
|
435
435
|
*/
|
|
436
436
|
function hasProblematicEmbeddedQuotes(value) {
|
|
437
437
|
// Look for "...special chars..." followed by more text
|
|
438
|
-
const regex = /"[^"]*[:{}
|
|
438
|
+
const regex = /"[^"]*[:{}[\]][^"]*"/g;
|
|
439
439
|
let match;
|
|
440
440
|
while ((match = regex.exec(value)) !== null) {
|
|
441
441
|
const afterQuote = value.substring(match.index + match[0].length);
|
|
@@ -468,7 +468,10 @@ function fixYamlEmbeddedQuotes(yaml) {
|
|
|
468
468
|
const prefix = match[1];
|
|
469
469
|
const value = match[2];
|
|
470
470
|
// Skip if already quoted
|
|
471
|
-
if (value.startsWith('"') ||
|
|
471
|
+
if (value.startsWith('"') ||
|
|
472
|
+
value.startsWith("'") ||
|
|
473
|
+
value.startsWith("|") ||
|
|
474
|
+
value.startsWith(">")) {
|
|
472
475
|
result.push(line);
|
|
473
476
|
continue;
|
|
474
477
|
}
|
|
@@ -568,7 +571,16 @@ export function robustYamlParse(content) {
|
|
|
568
571
|
* If parsed content is an object with one of these keys containing an array,
|
|
569
572
|
* we unwrap it automatically.
|
|
570
573
|
*/
|
|
571
|
-
const PRD_WRAPPER_KEYS = [
|
|
574
|
+
const PRD_WRAPPER_KEYS = [
|
|
575
|
+
"features",
|
|
576
|
+
"items",
|
|
577
|
+
"entries",
|
|
578
|
+
"prd",
|
|
579
|
+
"tasks",
|
|
580
|
+
"requirements",
|
|
581
|
+
"todo",
|
|
582
|
+
"checklist",
|
|
583
|
+
];
|
|
572
584
|
/**
|
|
573
585
|
* Unwraps PRD content if it's wrapped in a common object structure.
|
|
574
586
|
* LLMs often generate structures like:
|
|
@@ -682,7 +694,7 @@ export function writePrdAuto(prdPath, entries) {
|
|
|
682
694
|
export function expandFileReferences(text, baseDir) {
|
|
683
695
|
// Handle null/undefined text
|
|
684
696
|
if (typeof text !== "string") {
|
|
685
|
-
return text ?? "";
|
|
697
|
+
return String(text ?? "");
|
|
686
698
|
}
|
|
687
699
|
// Match @{filepath} patterns
|
|
688
700
|
const pattern = /@\{([^}]+)\}/g;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|