ralph-cli-sandboxed 0.4.2 → 0.5.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/dist/commands/chat.js +45 -15
- package/dist/commands/fix-prd.js +48 -24
- package/dist/commands/help.js +11 -3
- package/dist/commands/init.js +8 -7
- package/dist/commands/prd-convert.d.ts +9 -0
- package/dist/commands/prd-convert.js +108 -0
- package/dist/commands/prd.d.ts +2 -1
- package/dist/commands/prd.js +206 -37
- package/dist/commands/prompt.js +1 -1
- package/dist/commands/run.js +42 -43
- package/dist/index.js +6 -2
- package/dist/templates/prompts.d.ts +1 -0
- package/dist/templates/prompts.js +8 -1
- package/dist/utils/config.d.ts +19 -0
- package/dist/utils/config.js +85 -5
- package/dist/utils/prd-validator.d.ts +21 -2
- package/dist/utils/prd-validator.js +61 -8
- package/docs/HOW-TO-WRITE-PRDs.md +90 -108
- package/docs/PRD-GENERATOR.md +166 -96
- package/docs/RALPH-SETUP-TEMPLATE.md +1 -1
- package/package.json +3 -2
package/dist/commands/prd.js
CHANGED
|
@@ -1,36 +1,109 @@
|
|
|
1
|
-
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
2
|
-
import { join } from "path";
|
|
3
|
-
import { promptInput, promptSelect } from "../utils/prompt.js";
|
|
4
|
-
import { getRalphDir } from "../utils/config.js";
|
|
5
|
-
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
2
|
+
import { extname, join } from "path";
|
|
3
|
+
import { promptInput, promptSelect, promptConfirm } from "../utils/prompt.js";
|
|
4
|
+
import { getRalphDir, getPrdFiles } from "../utils/config.js";
|
|
5
|
+
import { convert as prdConvert } from "./prd-convert.js";
|
|
6
|
+
import { DEFAULT_PRD_YAML } from "../templates/prompts.js";
|
|
7
|
+
import YAML from "yaml";
|
|
8
|
+
const PRD_FILE_JSON = "prd.json";
|
|
9
|
+
const PRD_FILE_YAML = "prd.yaml";
|
|
6
10
|
const CATEGORIES = ["ui", "feature", "bugfix", "setup", "development", "testing", "docs"];
|
|
11
|
+
// Track whether we've shown the migration notice in this session
|
|
12
|
+
let migrationNoticeShown = false;
|
|
7
13
|
function getPrdPath() {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const path = getPrdPath();
|
|
12
|
-
if (!existsSync(path)) {
|
|
13
|
-
throw new Error(".ralph/prd.json not found. Run 'ralph init' first.");
|
|
14
|
+
const prdFiles = getPrdFiles();
|
|
15
|
+
if (prdFiles.primary) {
|
|
16
|
+
return prdFiles.primary;
|
|
14
17
|
}
|
|
18
|
+
// Fallback to json path for backwards compatibility
|
|
19
|
+
return join(getRalphDir(), PRD_FILE_JSON);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Parses a PRD file based on its extension.
|
|
23
|
+
* Supports both JSON and YAML formats.
|
|
24
|
+
* Returns empty array if file is empty or parses to null.
|
|
25
|
+
*/
|
|
26
|
+
function parsePrdFile(path) {
|
|
15
27
|
const content = readFileSync(path, "utf-8");
|
|
28
|
+
const ext = extname(path).toLowerCase();
|
|
16
29
|
try {
|
|
17
|
-
|
|
30
|
+
let result;
|
|
31
|
+
if (ext === ".yaml" || ext === ".yml") {
|
|
32
|
+
result = YAML.parse(content);
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
result = JSON.parse(content);
|
|
36
|
+
}
|
|
37
|
+
// Handle empty files or null content
|
|
38
|
+
return result ?? [];
|
|
18
39
|
}
|
|
19
40
|
catch (err) {
|
|
20
|
-
const
|
|
21
|
-
|
|
41
|
+
const format = ext === ".yaml" || ext === ".yml" ? "YAML" : "JSON";
|
|
42
|
+
const message = err instanceof Error ? err.message : `Invalid ${format}`;
|
|
43
|
+
console.error(`Error parsing ${path}: ${message}`);
|
|
22
44
|
console.error("");
|
|
23
45
|
console.error("Common issues:");
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
46
|
+
if (format === "JSON") {
|
|
47
|
+
console.error(" - Trailing comma before ] or }");
|
|
48
|
+
console.error(" - Missing comma between entries");
|
|
49
|
+
console.error(" - Unescaped quotes in strings");
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
console.error(" - Incorrect indentation");
|
|
53
|
+
console.error(" - Missing colons after keys");
|
|
54
|
+
console.error(" - Unquoted special characters");
|
|
55
|
+
}
|
|
27
56
|
console.error("");
|
|
28
57
|
console.error("Run 'ralph fix-prd' to attempt automatic repair.");
|
|
29
58
|
process.exit(1);
|
|
30
59
|
}
|
|
31
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* Loads PRD entries from prd.yaml and/or prd.json.
|
|
63
|
+
* - If neither exists, creates prd.yaml with default content
|
|
64
|
+
* - If both exist, merges them (no deduplication)
|
|
65
|
+
* - If only prd.json exists, shows migration notice
|
|
66
|
+
* - If only prd.yaml exists, uses it (happy path)
|
|
67
|
+
*/
|
|
68
|
+
function loadPrd() {
|
|
69
|
+
const prdFiles = getPrdFiles();
|
|
70
|
+
if (prdFiles.none) {
|
|
71
|
+
// Create .ralph directory if it doesn't exist
|
|
72
|
+
const ralphDir = getRalphDir();
|
|
73
|
+
if (!existsSync(ralphDir)) {
|
|
74
|
+
mkdirSync(ralphDir, { recursive: true });
|
|
75
|
+
}
|
|
76
|
+
// Create default prd.yaml
|
|
77
|
+
const prdPath = join(ralphDir, PRD_FILE_YAML);
|
|
78
|
+
writeFileSync(prdPath, DEFAULT_PRD_YAML);
|
|
79
|
+
console.log(`Created ${prdPath}`);
|
|
80
|
+
return parsePrdFile(prdPath);
|
|
81
|
+
}
|
|
82
|
+
// If only JSON exists, show migration notice (once per session)
|
|
83
|
+
if (prdFiles.jsonOnly && !migrationNoticeShown) {
|
|
84
|
+
console.log("\x1b[33mNote: Consider migrating to YAML format with 'ralph prd convert'\x1b[0m");
|
|
85
|
+
console.log("");
|
|
86
|
+
migrationNoticeShown = true;
|
|
87
|
+
}
|
|
88
|
+
// Load primary file
|
|
89
|
+
const primary = parsePrdFile(prdFiles.primary);
|
|
90
|
+
// If both files exist, merge them
|
|
91
|
+
if (prdFiles.both && prdFiles.secondary) {
|
|
92
|
+
const secondary = parsePrdFile(prdFiles.secondary);
|
|
93
|
+
// Merge without deduplication - primary (YAML) first, then secondary (JSON)
|
|
94
|
+
return [...primary, ...secondary];
|
|
95
|
+
}
|
|
96
|
+
return primary;
|
|
97
|
+
}
|
|
32
98
|
function savePrd(entries) {
|
|
33
|
-
|
|
99
|
+
const path = getPrdPath();
|
|
100
|
+
const ext = extname(path).toLowerCase();
|
|
101
|
+
if (ext === ".yaml" || ext === ".yml") {
|
|
102
|
+
writeFileSync(path, YAML.stringify(entries));
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
writeFileSync(path, JSON.stringify(entries, null, 2) + "\n");
|
|
106
|
+
}
|
|
34
107
|
}
|
|
35
108
|
export async function prdAdd() {
|
|
36
109
|
console.log("Add new PRD entry\n");
|
|
@@ -114,7 +187,7 @@ export function prdList(category, passesFilter) {
|
|
|
114
187
|
console.log();
|
|
115
188
|
});
|
|
116
189
|
}
|
|
117
|
-
export function prdStatus() {
|
|
190
|
+
export function prdStatus(headOnly = false) {
|
|
118
191
|
const prd = loadPrd();
|
|
119
192
|
if (prd.length === 0) {
|
|
120
193
|
console.log("No PRD entries found.");
|
|
@@ -146,7 +219,7 @@ export function prdStatus() {
|
|
|
146
219
|
if (passing === total) {
|
|
147
220
|
console.log("\n \x1b[32m\u2713 All requirements complete!\x1b[0m");
|
|
148
221
|
}
|
|
149
|
-
else {
|
|
222
|
+
else if (!headOnly) {
|
|
150
223
|
const remaining = prd.filter((e) => !e.passes);
|
|
151
224
|
console.log(`\n Remaining (${remaining.length}):`);
|
|
152
225
|
remaining.forEach((entry) => {
|
|
@@ -154,6 +227,71 @@ export function prdStatus() {
|
|
|
154
227
|
});
|
|
155
228
|
}
|
|
156
229
|
}
|
|
230
|
+
/**
|
|
231
|
+
* Parses a range string like "1-18" into an array of numbers [1, 2, ..., 18].
|
|
232
|
+
* Returns null if the string is not a valid range.
|
|
233
|
+
*/
|
|
234
|
+
function parseRange(str) {
|
|
235
|
+
const match = str.match(/^(\d+)-(\d+)$/);
|
|
236
|
+
if (!match)
|
|
237
|
+
return null;
|
|
238
|
+
const start = parseInt(match[1]);
|
|
239
|
+
const end = parseInt(match[2]);
|
|
240
|
+
if (isNaN(start) || isNaN(end) || start < 1 || end < 1)
|
|
241
|
+
return null;
|
|
242
|
+
if (start > end)
|
|
243
|
+
return null;
|
|
244
|
+
const result = [];
|
|
245
|
+
for (let i = start; i <= end; i++) {
|
|
246
|
+
result.push(i);
|
|
247
|
+
}
|
|
248
|
+
return result;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Expands arguments to handle range syntax.
|
|
252
|
+
* Supports:
|
|
253
|
+
* - "1-18" (single arg with dash) → [1, 2, ..., 18]
|
|
254
|
+
* - "1", "-", "18" (three args with dash separator) → [1, 2, ..., 18]
|
|
255
|
+
* - "1", "18" (separate numbers) → [1, 18]
|
|
256
|
+
*/
|
|
257
|
+
function expandRangeArgs(args) {
|
|
258
|
+
const indices = [];
|
|
259
|
+
for (let i = 0; i < args.length; i++) {
|
|
260
|
+
const arg = args[i];
|
|
261
|
+
// Check if this arg is a range like "1-18"
|
|
262
|
+
const range = parseRange(arg);
|
|
263
|
+
if (range) {
|
|
264
|
+
indices.push(...range);
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
// Check if this is part of a "start - end" pattern (with spaces)
|
|
268
|
+
if (arg === "-" && i > 0 && i + 1 < args.length) {
|
|
269
|
+
// Look back to see if previous was a number and forward to see if next is a number
|
|
270
|
+
const prevNum = parseInt(args[i - 1]);
|
|
271
|
+
const nextNum = parseInt(args[i + 1]);
|
|
272
|
+
if (!isNaN(prevNum) && !isNaN(nextNum) && prevNum >= 1 && nextNum >= 1) {
|
|
273
|
+
// Remove the previous number we already added (it's the start of a range)
|
|
274
|
+
indices.pop();
|
|
275
|
+
if (prevNum > nextNum) {
|
|
276
|
+
return null; // Invalid range
|
|
277
|
+
}
|
|
278
|
+
// Add the full range
|
|
279
|
+
for (let j = prevNum; j <= nextNum; j++) {
|
|
280
|
+
indices.push(j);
|
|
281
|
+
}
|
|
282
|
+
i++; // Skip the next number since we already processed it
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
// Otherwise, parse as a single number
|
|
287
|
+
const num = parseInt(arg);
|
|
288
|
+
if (isNaN(num) || num < 1) {
|
|
289
|
+
return null; // Invalid argument
|
|
290
|
+
}
|
|
291
|
+
indices.push(num);
|
|
292
|
+
}
|
|
293
|
+
return indices.length > 0 ? indices : null;
|
|
294
|
+
}
|
|
157
295
|
export function prdToggle(args) {
|
|
158
296
|
const arg = args[0];
|
|
159
297
|
// Check for --all flag
|
|
@@ -170,19 +308,11 @@ export function prdToggle(args) {
|
|
|
170
308
|
console.log(`Toggled all ${prd.length} PRD entries.`);
|
|
171
309
|
return;
|
|
172
310
|
}
|
|
173
|
-
// Parse
|
|
174
|
-
const indices =
|
|
175
|
-
|
|
176
|
-
const index = parseInt(a);
|
|
177
|
-
if (!index || isNaN(index)) {
|
|
178
|
-
console.error("Usage: ralph prd toggle <number> [number2] [number3] ...");
|
|
179
|
-
console.error(" ralph prd toggle --all");
|
|
180
|
-
process.exit(1);
|
|
181
|
-
}
|
|
182
|
-
indices.push(index);
|
|
183
|
-
}
|
|
184
|
-
if (indices.length === 0) {
|
|
311
|
+
// Parse arguments with range support
|
|
312
|
+
const indices = expandRangeArgs(args);
|
|
313
|
+
if (!indices) {
|
|
185
314
|
console.error("Usage: ralph prd toggle <number> [number2] [number3] ...");
|
|
315
|
+
console.error(" ralph prd toggle <start>-<end>");
|
|
186
316
|
console.error(" ralph prd toggle --all");
|
|
187
317
|
process.exit(1);
|
|
188
318
|
}
|
|
@@ -194,8 +324,10 @@ export function prdToggle(args) {
|
|
|
194
324
|
process.exit(1);
|
|
195
325
|
}
|
|
196
326
|
}
|
|
327
|
+
// Remove duplicates and sort
|
|
328
|
+
const uniqueIndices = [...new Set(indices)].sort((a, b) => a - b);
|
|
197
329
|
// Toggle each entry
|
|
198
|
-
for (const index of
|
|
330
|
+
for (const index of uniqueIndices) {
|
|
199
331
|
const entry = prd[index - 1];
|
|
200
332
|
entry.passes = !entry.passes;
|
|
201
333
|
const statusText = entry.passes ? "PASSING" : "NOT PASSING";
|
|
@@ -216,6 +348,29 @@ export function prdClean() {
|
|
|
216
348
|
console.log(`Removed ${removed} passing ${removed === 1 ? "entry" : "entries"}.`);
|
|
217
349
|
console.log(`${filtered.length} ${filtered.length === 1 ? "entry" : "entries"} remaining.`);
|
|
218
350
|
}
|
|
351
|
+
export async function prdReset() {
|
|
352
|
+
const prd = loadPrd();
|
|
353
|
+
if (prd.length === 0) {
|
|
354
|
+
console.log("No PRD entries to reset.");
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
const alreadyPassing = prd.filter((e) => e.passes).length;
|
|
358
|
+
if (alreadyPassing === 0) {
|
|
359
|
+
console.log("All PRD entries are already incomplete (passes=false).");
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
const confirmed = await promptConfirm(`Are you sure you want to reset ${alreadyPassing} ${alreadyPassing === 1 ? "entry" : "entries"} to incomplete?`, false);
|
|
363
|
+
if (!confirmed) {
|
|
364
|
+
console.log("Reset cancelled.");
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
prd.forEach((entry) => {
|
|
368
|
+
entry.passes = false;
|
|
369
|
+
});
|
|
370
|
+
savePrd(prd);
|
|
371
|
+
console.log(`Reset ${alreadyPassing} ${alreadyPassing === 1 ? "entry" : "entries"} to incomplete.`);
|
|
372
|
+
console.log(`All ${prd.length} PRD ${prd.length === 1 ? "entry is" : "entries are"} now passes=false.`);
|
|
373
|
+
}
|
|
219
374
|
export function parseListArgs(args) {
|
|
220
375
|
let category;
|
|
221
376
|
let passesFilter;
|
|
@@ -260,29 +415,43 @@ export async function prd(args) {
|
|
|
260
415
|
prdList(category, passesFilter);
|
|
261
416
|
break;
|
|
262
417
|
}
|
|
263
|
-
case "status":
|
|
264
|
-
|
|
418
|
+
case "status": {
|
|
419
|
+
const headOnly = args.slice(1).includes("--head");
|
|
420
|
+
prdStatus(headOnly);
|
|
265
421
|
break;
|
|
422
|
+
}
|
|
266
423
|
case "toggle":
|
|
267
424
|
prdToggle(args.slice(1));
|
|
268
425
|
break;
|
|
269
426
|
case "clean":
|
|
270
427
|
prdClean();
|
|
271
428
|
break;
|
|
429
|
+
case "reset":
|
|
430
|
+
await prdReset();
|
|
431
|
+
break;
|
|
432
|
+
case "convert":
|
|
433
|
+
await prdConvert(args.slice(1));
|
|
434
|
+
break;
|
|
272
435
|
default:
|
|
273
|
-
console.error("Usage: ralph prd <add|list|status|toggle|clean>");
|
|
436
|
+
console.error("Usage: ralph prd <add|list|status|toggle|clean|reset|convert>");
|
|
274
437
|
console.error("\nSubcommands:");
|
|
275
438
|
console.error(" add Add a new PRD entry");
|
|
276
439
|
console.error(" list [options] List all PRD entries");
|
|
277
|
-
console.error(" status
|
|
440
|
+
console.error(" status [--head] Show completion status");
|
|
278
441
|
console.error(" toggle <n> ... Toggle passes status for entry n (accepts multiple)");
|
|
442
|
+
console.error(" toggle <start>-<end> Toggle a range of entries (e.g., 1-18)");
|
|
279
443
|
console.error(" toggle --all Toggle all PRD entries");
|
|
280
444
|
console.error(" clean Remove all passing entries from the PRD");
|
|
445
|
+
console.error(" reset Reset all entries to incomplete (passes=false)");
|
|
446
|
+
console.error(" convert [options] Convert prd.json to prd.yaml format");
|
|
281
447
|
console.error("\nList options:");
|
|
282
448
|
console.error(" --category, -c <cat> Filter by category");
|
|
283
449
|
console.error(" --passes Show only completed items");
|
|
284
450
|
console.error(" --no-passes Show only incomplete items");
|
|
285
451
|
console.error(" --stats Show statistics instead of entries");
|
|
452
|
+
console.error("\nConvert options:");
|
|
453
|
+
console.error(" --force, -f Overwrite existing files");
|
|
454
|
+
console.error(" --dry-run, -n Preview without making changes");
|
|
286
455
|
console.error(`\nValid categories: ${CATEGORIES.join(", ")}`);
|
|
287
456
|
process.exit(1);
|
|
288
457
|
}
|
package/dist/commands/prompt.js
CHANGED
|
@@ -13,7 +13,7 @@ USAGE:
|
|
|
13
13
|
|
|
14
14
|
DESCRIPTION:
|
|
15
15
|
Prints the complete prompt that gets sent to Claude Code, including
|
|
16
|
-
the @.ralph/prd.
|
|
16
|
+
the @.ralph/prd.yaml and @.ralph/progress.txt file references.
|
|
17
17
|
This is useful for testing the prompt manually in Claude Code.
|
|
18
18
|
|
|
19
19
|
TEMPLATE VARIABLES:
|
package/dist/commands/run.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { spawn } from "child_process";
|
|
2
2
|
import { existsSync, readFileSync, writeFileSync, unlinkSync, appendFileSync, mkdirSync } from "fs";
|
|
3
|
-
import { join } from "path";
|
|
3
|
+
import { extname, join } from "path";
|
|
4
4
|
import { checkFilesExist, loadConfig, loadPrompt, getPaths, getCliConfig, requireContainer, } from "../utils/config.js";
|
|
5
5
|
import { resolvePromptVariables, getCliProviders } from "../templates/prompts.js";
|
|
6
|
-
import { validatePrd, smartMerge, readPrdFile, writePrd, expandPrdFileReferences, } from "../utils/prd-validator.js";
|
|
6
|
+
import { validatePrd, smartMerge, readPrdFile, writePrd, writePrdAuto, expandPrdFileReferences, } from "../utils/prd-validator.js";
|
|
7
7
|
import { getStreamJsonParser } from "../utils/stream-json.js";
|
|
8
8
|
import { sendNotificationWithDaemonEvents } from "../utils/notification.js";
|
|
9
9
|
const CATEGORIES = ["ui", "feature", "bugfix", "setup", "development", "testing", "docs"];
|
|
@@ -14,24 +14,23 @@ const CATEGORIES = ["ui", "feature", "bugfix", "setup", "development", "testing"
|
|
|
14
14
|
* Returns the path to the temp file, or null if all items pass.
|
|
15
15
|
*/
|
|
16
16
|
function createFilteredPrd(prdPath, baseDir, category) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
console.error("\x1b[31mError: prd.json contains invalid JSON.\x1b[0m");
|
|
17
|
+
// Use readPrdFile to handle both JSON and YAML formats
|
|
18
|
+
const parsed = readPrdFile(prdPath);
|
|
19
|
+
if (!parsed) {
|
|
20
|
+
const ext = extname(prdPath).toLowerCase();
|
|
21
|
+
const format = ext === ".yaml" || ext === ".yml" ? "YAML" : "JSON";
|
|
22
|
+
console.error(`\x1b[31mError: PRD file contains invalid ${format}.\x1b[0m`);
|
|
24
23
|
console.error("The file may have been corrupted by an LLM.\n");
|
|
25
24
|
console.error("Run \x1b[36mralph fix-prd\x1b[0m to diagnose and repair the file.");
|
|
26
25
|
process.exit(1);
|
|
27
26
|
}
|
|
28
|
-
if (!Array.isArray(parsed)) {
|
|
29
|
-
console.error("\x1b[31mError:
|
|
27
|
+
if (!Array.isArray(parsed.content)) {
|
|
28
|
+
console.error("\x1b[31mError: PRD is corrupted - expected an array of items.\x1b[0m");
|
|
30
29
|
console.error("The file may have been modified incorrectly by an LLM.\n");
|
|
31
30
|
console.error("Run \x1b[36mralph fix-prd\x1b[0m to diagnose and repair the file.");
|
|
32
31
|
process.exit(1);
|
|
33
32
|
}
|
|
34
|
-
const items = parsed;
|
|
33
|
+
const items = parsed.content;
|
|
35
34
|
let filteredItems = items.filter((item) => item.passes === false);
|
|
36
35
|
// Apply category filter if specified
|
|
37
36
|
if (category) {
|
|
@@ -48,9 +47,9 @@ function createFilteredPrd(prdPath, baseDir, category) {
|
|
|
48
47
|
};
|
|
49
48
|
}
|
|
50
49
|
/**
|
|
51
|
-
* Syncs passes flags from prd-tasks.json back to
|
|
50
|
+
* Syncs passes flags from prd-tasks.json back to the main PRD file.
|
|
52
51
|
* If the LLM marked any item as passes: true in prd-tasks.json,
|
|
53
|
-
* find the matching item in
|
|
52
|
+
* find the matching item in the PRD and update it.
|
|
54
53
|
* Returns the number of items synced and their names.
|
|
55
54
|
*/
|
|
56
55
|
function syncPassesFromTasks(tasksPath, prdPath) {
|
|
@@ -66,22 +65,21 @@ function syncPassesFromTasks(tasksPath, prdPath) {
|
|
|
66
65
|
return { count: 0, taskNames: [] };
|
|
67
66
|
}
|
|
68
67
|
const tasks = tasksParsed;
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
console.warn("\x1b[33mWarning: prd.json contains invalid JSON - skipping sync.\x1b[0m");
|
|
68
|
+
// Use readPrdFile to handle both JSON and YAML formats
|
|
69
|
+
const prdParsed = readPrdFile(prdPath);
|
|
70
|
+
if (!prdParsed) {
|
|
71
|
+
const ext = extname(prdPath).toLowerCase();
|
|
72
|
+
const format = ext === ".yaml" || ext === ".yml" ? "YAML" : "JSON";
|
|
73
|
+
console.warn(`\x1b[33mWarning: PRD contains invalid ${format} - skipping sync.\x1b[0m`);
|
|
76
74
|
console.warn("Run \x1b[36mralph fix-prd\x1b[0m after this session to repair.\n");
|
|
77
75
|
return { count: 0, taskNames: [] };
|
|
78
76
|
}
|
|
79
|
-
if (!Array.isArray(prdParsed)) {
|
|
80
|
-
console.warn("\x1b[33mWarning:
|
|
77
|
+
if (!Array.isArray(prdParsed.content)) {
|
|
78
|
+
console.warn("\x1b[33mWarning: PRD is corrupted - skipping sync.\x1b[0m");
|
|
81
79
|
console.warn("Run \x1b[36mralph fix-prd\x1b[0m after this session to repair.\n");
|
|
82
80
|
return { count: 0, taskNames: [] };
|
|
83
81
|
}
|
|
84
|
-
const prd = prdParsed;
|
|
82
|
+
const prd = prdParsed.content;
|
|
85
83
|
let synced = 0;
|
|
86
84
|
const syncedTaskNames = [];
|
|
87
85
|
// Find tasks that were marked as passing
|
|
@@ -98,10 +96,11 @@ function syncPassesFromTasks(tasksPath, prdPath) {
|
|
|
98
96
|
}
|
|
99
97
|
}
|
|
100
98
|
}
|
|
101
|
-
// Write back if any items were synced
|
|
99
|
+
// Write back if any items were synced (using format-aware write)
|
|
102
100
|
if (synced > 0) {
|
|
103
|
-
|
|
104
|
-
|
|
101
|
+
writePrdAuto(prdPath, prd);
|
|
102
|
+
const prdFileName = prdPath.split("/").pop() || "PRD";
|
|
103
|
+
console.log(`\x1b[32mSynced ${synced} completed item(s) from prd-tasks.json to ${prdFileName}\x1b[0m`);
|
|
105
104
|
}
|
|
106
105
|
return { count: synced, taskNames: syncedTaskNames };
|
|
107
106
|
}
|
|
@@ -277,24 +276,23 @@ function formatElapsedTime(startTime, endTime) {
|
|
|
277
276
|
* Optionally filters by category if specified.
|
|
278
277
|
*/
|
|
279
278
|
function countPrdItems(prdPath, category) {
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
console.error("\x1b[31mError: prd.json contains invalid JSON.\x1b[0m");
|
|
279
|
+
// Use readPrdFile to handle both JSON and YAML formats
|
|
280
|
+
const parsed = readPrdFile(prdPath);
|
|
281
|
+
if (!parsed) {
|
|
282
|
+
const ext = extname(prdPath).toLowerCase();
|
|
283
|
+
const format = ext === ".yaml" || ext === ".yml" ? "YAML" : "JSON";
|
|
284
|
+
console.error(`\x1b[31mError: PRD contains invalid ${format}.\x1b[0m`);
|
|
287
285
|
console.error("The file may have been corrupted by an LLM.\n");
|
|
288
286
|
console.error("Run \x1b[36mralph fix-prd\x1b[0m to diagnose and repair the file.");
|
|
289
287
|
process.exit(1);
|
|
290
288
|
}
|
|
291
|
-
if (!Array.isArray(parsed)) {
|
|
292
|
-
console.error("\x1b[31mError:
|
|
289
|
+
if (!Array.isArray(parsed.content)) {
|
|
290
|
+
console.error("\x1b[31mError: PRD is corrupted - expected an array of items.\x1b[0m");
|
|
293
291
|
console.error("The file may have been modified incorrectly by an LLM.\n");
|
|
294
292
|
console.error("Run \x1b[36mralph fix-prd\x1b[0m to diagnose and repair the file.");
|
|
295
293
|
process.exit(1);
|
|
296
294
|
}
|
|
297
|
-
const items = parsed;
|
|
295
|
+
const items = parsed.content;
|
|
298
296
|
let filteredItems = items;
|
|
299
297
|
if (category) {
|
|
300
298
|
filteredItems = items.filter((item) => item.category === category);
|
|
@@ -347,15 +345,16 @@ function validateAndRecoverPrd(prdPath, validPrd) {
|
|
|
347
345
|
* Returns the validated PRD entries.
|
|
348
346
|
*/
|
|
349
347
|
function loadValidPrd(prdPath) {
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
console.error(
|
|
348
|
+
// Use readPrdFile to handle both JSON and YAML formats
|
|
349
|
+
const parsed = readPrdFile(prdPath);
|
|
350
|
+
if (!parsed || !Array.isArray(parsed.content)) {
|
|
351
|
+
const ext = extname(prdPath).toLowerCase();
|
|
352
|
+
const format = ext === ".yaml" || ext === ".yml" ? "YAML" : "JSON";
|
|
353
|
+
console.error(`\x1b[31mError: PRD contains invalid ${format}.\x1b[0m`);
|
|
356
354
|
console.error("Run \x1b[36mralph fix-prd\x1b[0m to diagnose and repair the file.");
|
|
357
355
|
process.exit(1);
|
|
358
356
|
}
|
|
357
|
+
return parsed.content;
|
|
359
358
|
}
|
|
360
359
|
export async function run(args) {
|
|
361
360
|
// Parse flags
|
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { help } from "./commands/help.js";
|
|
|
6
6
|
import { init } from "./commands/init.js";
|
|
7
7
|
import { once } from "./commands/once.js";
|
|
8
8
|
import { run } from "./commands/run.js";
|
|
9
|
-
import { prd, prdAdd, prdList, prdStatus, prdToggle, prdClean, parseListArgs, } from "./commands/prd.js";
|
|
9
|
+
import { prd, prdAdd, prdList, prdStatus, prdToggle, prdClean, prdReset, parseListArgs, } from "./commands/prd.js";
|
|
10
10
|
import { docker } from "./commands/docker.js";
|
|
11
11
|
import { prompt } from "./commands/prompt.js";
|
|
12
12
|
import { fixPrd } from "./commands/fix-prd.js";
|
|
@@ -51,9 +51,13 @@ const commands = {
|
|
|
51
51
|
const { category, passesFilter } = parseListArgs(args);
|
|
52
52
|
prdList(category, passesFilter);
|
|
53
53
|
},
|
|
54
|
-
status: () =>
|
|
54
|
+
status: (args) => {
|
|
55
|
+
const headOnly = args.includes("--head");
|
|
56
|
+
prdStatus(headOnly);
|
|
57
|
+
},
|
|
55
58
|
toggle: (args) => prdToggle(args),
|
|
56
59
|
clean: () => prdClean(),
|
|
60
|
+
reset: () => prdReset(),
|
|
57
61
|
};
|
|
58
62
|
async function main() {
|
|
59
63
|
const args = process.argv.slice(2);
|
|
@@ -75,5 +75,6 @@ export declare function resolvePromptVariables(template: string, config: {
|
|
|
75
75
|
}): string;
|
|
76
76
|
export declare function generatePrompt(config: LanguageConfig, technologies?: string[]): string;
|
|
77
77
|
export declare const DEFAULT_PRD = "[\n {\n \"category\": \"setup\",\n \"description\": \"Example: Project builds successfully\",\n \"steps\": [\n \"Run the build command\",\n \"Verify no errors occur\"\n ],\n \"passes\": false\n }\n]";
|
|
78
|
+
export declare const DEFAULT_PRD_YAML = "- category: setup\n description: \"Example: Project builds successfully\"\n steps:\n - Run the build command\n - Verify no errors occur\n passes: false\n";
|
|
78
79
|
export declare const DEFAULT_PROGRESS = "# Progress Log\n";
|
|
79
80
|
export {};
|
|
@@ -105,7 +105,7 @@ INSTRUCTIONS:
|
|
|
105
105
|
3. Verify your changes work by running:
|
|
106
106
|
- Type/build check: $checkCommand
|
|
107
107
|
- Tests: $testCommand
|
|
108
|
-
4. Update .ralph/prd.
|
|
108
|
+
4. Update .ralph/prd.yaml to set "passes": true for the completed feature
|
|
109
109
|
5. Append a brief note about what you did to .ralph/progress.txt
|
|
110
110
|
6. Create a git commit with a descriptive message for this feature
|
|
111
111
|
7. Only work on ONE feature per execution
|
|
@@ -152,4 +152,11 @@ export const DEFAULT_PRD = `[
|
|
|
152
152
|
"passes": false
|
|
153
153
|
}
|
|
154
154
|
]`;
|
|
155
|
+
export const DEFAULT_PRD_YAML = `- category: setup
|
|
156
|
+
description: "Example: Project builds successfully"
|
|
157
|
+
steps:
|
|
158
|
+
- Run the build command
|
|
159
|
+
- Verify no errors occur
|
|
160
|
+
passes: false
|
|
161
|
+
`;
|
|
155
162
|
export const DEFAULT_PROGRESS = `# Progress Log\n`;
|
package/dist/utils/config.d.ts
CHANGED
|
@@ -193,6 +193,24 @@ export interface RalphConfig {
|
|
|
193
193
|
}
|
|
194
194
|
export declare const DEFAULT_CLI_CONFIG: CliConfig;
|
|
195
195
|
export declare function getCliConfig(config: RalphConfig): CliConfig;
|
|
196
|
+
/**
|
|
197
|
+
* Gets the PRD file path(s) that exist.
|
|
198
|
+
* Returns an object with:
|
|
199
|
+
* - primary: The main PRD file path to use (yaml preferred over json)
|
|
200
|
+
* - secondary: The secondary PRD file path if both exist (for merging)
|
|
201
|
+
* - jsonOnly: True if only prd.json exists (shows migration notice)
|
|
202
|
+
* - yamlOnly: True if only prd.yaml exists (happy path)
|
|
203
|
+
* - both: True if both files exist (merge mode)
|
|
204
|
+
* - none: True if no PRD file exists
|
|
205
|
+
*/
|
|
206
|
+
export declare function getPrdFiles(): {
|
|
207
|
+
primary: string | null;
|
|
208
|
+
secondary: string | null;
|
|
209
|
+
jsonOnly: boolean;
|
|
210
|
+
yamlOnly: boolean;
|
|
211
|
+
both: boolean;
|
|
212
|
+
none: boolean;
|
|
213
|
+
};
|
|
196
214
|
export declare function getRalphDir(): string;
|
|
197
215
|
export declare function loadConfig(): RalphConfig;
|
|
198
216
|
export declare function loadPrompt(): string;
|
|
@@ -202,6 +220,7 @@ export declare function getPaths(): {
|
|
|
202
220
|
config: string;
|
|
203
221
|
prompt: string;
|
|
204
222
|
prd: string;
|
|
223
|
+
prdSecondary: string | null;
|
|
205
224
|
progress: string;
|
|
206
225
|
};
|
|
207
226
|
/**
|