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/chat.js
CHANGED
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
* Allows ralph to receive commands and send notifications via chat services.
|
|
4
4
|
*/
|
|
5
5
|
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
6
|
-
import { join, basename } from "path";
|
|
6
|
+
import { join, basename, extname } from "path";
|
|
7
7
|
import { spawn } from "child_process";
|
|
8
|
-
import
|
|
8
|
+
import YAML from "yaml";
|
|
9
|
+
import { loadConfig, getRalphDir, isRunningInContainer, getPrdFiles } from "../utils/config.js";
|
|
9
10
|
import { createTelegramClient } from "../providers/telegram.js";
|
|
10
11
|
import { createSlackClient } from "../providers/slack.js";
|
|
11
12
|
import { createDiscordClient } from "../providers/discord.js";
|
|
@@ -53,18 +54,40 @@ function getOrCreateProjectId() {
|
|
|
53
54
|
function getProjectName() {
|
|
54
55
|
return basename(process.cwd());
|
|
55
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Parse PRD file content based on file extension.
|
|
59
|
+
*/
|
|
60
|
+
function parsePrdContent(filePath, content) {
|
|
61
|
+
const ext = extname(filePath).toLowerCase();
|
|
62
|
+
try {
|
|
63
|
+
let parsed;
|
|
64
|
+
if (ext === ".yaml" || ext === ".yml") {
|
|
65
|
+
parsed = YAML.parse(content);
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
parsed = JSON.parse(content);
|
|
69
|
+
}
|
|
70
|
+
if (Array.isArray(parsed)) {
|
|
71
|
+
return parsed;
|
|
72
|
+
}
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
56
79
|
/**
|
|
57
80
|
* Get PRD status (completed/total tasks).
|
|
58
81
|
*/
|
|
59
82
|
function getPrdStatus() {
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
if (!existsSync(prdPath)) {
|
|
83
|
+
const prdFiles = getPrdFiles();
|
|
84
|
+
if (prdFiles.none) {
|
|
63
85
|
return { complete: 0, total: 0, incomplete: 0 };
|
|
64
86
|
}
|
|
65
87
|
try {
|
|
88
|
+
const prdPath = prdFiles.primary;
|
|
66
89
|
const content = readFileSync(prdPath, "utf-8");
|
|
67
|
-
const items =
|
|
90
|
+
const items = parsePrdContent(prdPath, content);
|
|
68
91
|
if (!Array.isArray(items)) {
|
|
69
92
|
return { complete: 0, total: 0, incomplete: 0 };
|
|
70
93
|
}
|
|
@@ -81,14 +104,14 @@ function getPrdStatus() {
|
|
|
81
104
|
* Returns unique categories that have at least one incomplete task.
|
|
82
105
|
*/
|
|
83
106
|
function getOpenCategories() {
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
if (!existsSync(prdPath)) {
|
|
107
|
+
const prdFiles = getPrdFiles();
|
|
108
|
+
if (prdFiles.none) {
|
|
87
109
|
return [];
|
|
88
110
|
}
|
|
89
111
|
try {
|
|
112
|
+
const prdPath = prdFiles.primary;
|
|
90
113
|
const content = readFileSync(prdPath, "utf-8");
|
|
91
|
-
const items =
|
|
114
|
+
const items = parsePrdContent(prdPath, content);
|
|
92
115
|
if (!Array.isArray(items)) {
|
|
93
116
|
return [];
|
|
94
117
|
}
|
|
@@ -109,14 +132,14 @@ function getOpenCategories() {
|
|
|
109
132
|
* Add a new task to the PRD.
|
|
110
133
|
*/
|
|
111
134
|
function addPrdTask(description) {
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
if (!existsSync(prdPath)) {
|
|
135
|
+
const prdFiles = getPrdFiles();
|
|
136
|
+
if (prdFiles.none) {
|
|
115
137
|
return false;
|
|
116
138
|
}
|
|
117
139
|
try {
|
|
140
|
+
const prdPath = prdFiles.primary;
|
|
118
141
|
const content = readFileSync(prdPath, "utf-8");
|
|
119
|
-
const items =
|
|
142
|
+
const items = parsePrdContent(prdPath, content);
|
|
120
143
|
if (!Array.isArray(items)) {
|
|
121
144
|
return false;
|
|
122
145
|
}
|
|
@@ -126,7 +149,14 @@ function addPrdTask(description) {
|
|
|
126
149
|
steps: [],
|
|
127
150
|
passes: false,
|
|
128
151
|
});
|
|
129
|
-
|
|
152
|
+
// Write back in the same format as the source file
|
|
153
|
+
const ext = extname(prdPath).toLowerCase();
|
|
154
|
+
if (ext === ".yaml" || ext === ".yml") {
|
|
155
|
+
writeFileSync(prdPath, YAML.stringify(items));
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
writeFileSync(prdPath, JSON.stringify(items, null, 2) + "\n");
|
|
159
|
+
}
|
|
130
160
|
return true;
|
|
131
161
|
}
|
|
132
162
|
catch {
|
package/dist/commands/fix-prd.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { existsSync, readFileSync, copyFileSync } from "fs";
|
|
2
|
-
import { join, isAbsolute } from "path";
|
|
3
|
-
import { getPaths, getRalphDir } from "../utils/config.js";
|
|
4
|
-
import { validatePrd, attemptRecovery, createBackup, findLatestBackup, createTemplatePrd, readPrdFile,
|
|
2
|
+
import { join, isAbsolute, extname } from "path";
|
|
3
|
+
import { getPaths, getRalphDir, getPrdFiles } from "../utils/config.js";
|
|
4
|
+
import { validatePrd, attemptRecovery, createBackup, findLatestBackup, createTemplatePrd, readPrdFile, writePrdAuto, } from "../utils/prd-validator.js";
|
|
5
|
+
import YAML from "yaml";
|
|
5
6
|
/**
|
|
6
7
|
* Resolves a backup path - can be absolute, relative, or just a filename.
|
|
7
8
|
*/
|
|
@@ -17,8 +18,21 @@ function resolveBackupPath(backupArg) {
|
|
|
17
18
|
// Otherwise treat as relative to cwd
|
|
18
19
|
return join(process.cwd(), backupArg);
|
|
19
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Parses a backup file content based on file extension.
|
|
23
|
+
* Supports both JSON and YAML formats.
|
|
24
|
+
*/
|
|
25
|
+
function parseBackupContent(backupPath, content) {
|
|
26
|
+
const ext = extname(backupPath).toLowerCase();
|
|
27
|
+
if (ext === ".yaml" || ext === ".yml") {
|
|
28
|
+
return YAML.parse(content);
|
|
29
|
+
}
|
|
30
|
+
return JSON.parse(content);
|
|
31
|
+
}
|
|
20
32
|
/**
|
|
21
33
|
* Restores PRD from a specific backup file.
|
|
34
|
+
* Supports restoring from both .json and .yaml/.yml backup files.
|
|
35
|
+
* Writes output in the same format as the target PRD file.
|
|
22
36
|
*/
|
|
23
37
|
function restoreFromBackup(prdPath, backupPath) {
|
|
24
38
|
if (!existsSync(backupPath)) {
|
|
@@ -27,7 +41,7 @@ function restoreFromBackup(prdPath, backupPath) {
|
|
|
27
41
|
}
|
|
28
42
|
try {
|
|
29
43
|
const backupContent = readFileSync(backupPath, "utf-8");
|
|
30
|
-
const backupParsed =
|
|
44
|
+
const backupParsed = parseBackupContent(backupPath, backupContent);
|
|
31
45
|
const validation = validatePrd(backupParsed);
|
|
32
46
|
if (!validation.valid) {
|
|
33
47
|
console.error("Error: Backup file contains invalid PRD structure:");
|
|
@@ -41,7 +55,8 @@ function restoreFromBackup(prdPath, backupPath) {
|
|
|
41
55
|
const currentBackup = createBackup(prdPath);
|
|
42
56
|
console.log(`Created backup of current PRD: ${currentBackup}`);
|
|
43
57
|
}
|
|
44
|
-
|
|
58
|
+
// Write in the same format as the target PRD file
|
|
59
|
+
writePrdAuto(prdPath, validation.data);
|
|
45
60
|
console.log(`\x1b[32m✓ PRD restored from: ${backupPath}\x1b[0m`);
|
|
46
61
|
console.log(` Restored ${validation.data.length} entries.`);
|
|
47
62
|
return true;
|
|
@@ -52,13 +67,16 @@ function restoreFromBackup(prdPath, backupPath) {
|
|
|
52
67
|
}
|
|
53
68
|
}
|
|
54
69
|
/**
|
|
55
|
-
* Handles the case where the PRD file contains invalid JSON.
|
|
70
|
+
* Handles the case where the PRD file contains invalid JSON/YAML.
|
|
56
71
|
* Attempts to restore from backup or reset to template.
|
|
72
|
+
* Preserves the original file format (JSON or YAML).
|
|
57
73
|
*/
|
|
58
74
|
function handleBrokenPrd(prdPath) {
|
|
59
|
-
// Create backup of the broken file (preserving raw content)
|
|
75
|
+
// Create backup of the broken file (preserving raw content and extension)
|
|
60
76
|
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
61
|
-
const
|
|
77
|
+
const ext = extname(prdPath).toLowerCase();
|
|
78
|
+
const backupExt = ext === ".yaml" || ext === ".yml" ? ext : ".json";
|
|
79
|
+
const backupPath = join(getRalphDir(), `backup.prd.${timestamp}${backupExt}`);
|
|
62
80
|
copyFileSync(prdPath, backupPath);
|
|
63
81
|
console.log(`Created backup of broken file: ${backupPath}\n`);
|
|
64
82
|
// Try to restore from a previous valid backup
|
|
@@ -67,10 +85,10 @@ function handleBrokenPrd(prdPath) {
|
|
|
67
85
|
console.log(`Found previous backup: ${latestBackup}`);
|
|
68
86
|
try {
|
|
69
87
|
const backupContent = readFileSync(latestBackup, "utf-8");
|
|
70
|
-
const backupParsed =
|
|
88
|
+
const backupParsed = parseBackupContent(latestBackup, backupContent);
|
|
71
89
|
const backupValidation = validatePrd(backupParsed);
|
|
72
90
|
if (backupValidation.valid) {
|
|
73
|
-
|
|
91
|
+
writePrdAuto(prdPath, backupValidation.data);
|
|
74
92
|
console.log("\x1b[32m✓ PRD restored from backup!\x1b[0m");
|
|
75
93
|
console.log(` Restored ${backupValidation.data.length} entries.`);
|
|
76
94
|
console.log("\x1b[33m Note: Recent changes may have been lost.\x1b[0m");
|
|
@@ -89,7 +107,7 @@ function handleBrokenPrd(prdPath) {
|
|
|
89
107
|
}
|
|
90
108
|
// Reset to template as last resort - with instructions to recover from backup
|
|
91
109
|
console.log("Resetting PRD to recovery template...");
|
|
92
|
-
|
|
110
|
+
writePrdAuto(prdPath, createTemplatePrd(backupPath));
|
|
93
111
|
console.log("\x1b[33m✓ PRD reset with recovery task.\x1b[0m");
|
|
94
112
|
console.log(" Next 'ralph run' will instruct the LLM to recover entries from backup.");
|
|
95
113
|
console.log(` Backup location: ${backupPath}`);
|
|
@@ -113,20 +131,26 @@ export async function fixPrd(args = []) {
|
|
|
113
131
|
const success = restoreFromBackup(paths.prd, resolvedPath);
|
|
114
132
|
process.exit(success ? 0 : 1);
|
|
115
133
|
}
|
|
116
|
-
|
|
117
|
-
|
|
134
|
+
// Detect which PRD files exist
|
|
135
|
+
const prdFiles = getPrdFiles();
|
|
136
|
+
if (prdFiles.none) {
|
|
137
|
+
console.error("Error: No PRD file found (.ralph/prd.yaml or .ralph/prd.json). Run 'ralph init' first.");
|
|
118
138
|
process.exit(1);
|
|
119
139
|
}
|
|
120
|
-
|
|
140
|
+
// Use the primary PRD path (YAML preferred over JSON)
|
|
141
|
+
const prdPath = paths.prd;
|
|
142
|
+
const isYamlFile = extname(prdPath).toLowerCase() === ".yaml" || extname(prdPath).toLowerCase() === ".yml";
|
|
143
|
+
const fileFormatName = isYamlFile ? "YAML" : "JSON";
|
|
144
|
+
console.log(`Checking PRD structure (${fileFormatName})...\n`);
|
|
121
145
|
// Step 1: Try to read and parse the file
|
|
122
|
-
const parsed = readPrdFile(
|
|
146
|
+
const parsed = readPrdFile(prdPath);
|
|
123
147
|
if (!parsed) {
|
|
124
|
-
//
|
|
125
|
-
console.log(
|
|
148
|
+
// Parsing failed - file is completely broken
|
|
149
|
+
console.log(`\x1b[31m✗ PRD file contains invalid ${fileFormatName}.\x1b[0m\n`);
|
|
126
150
|
if (verifyOnly) {
|
|
127
151
|
process.exit(1);
|
|
128
152
|
}
|
|
129
|
-
handleBrokenPrd(
|
|
153
|
+
handleBrokenPrd(prdPath);
|
|
130
154
|
return;
|
|
131
155
|
}
|
|
132
156
|
// Step 2: Validate the structure
|
|
@@ -149,7 +173,7 @@ export async function fixPrd(args = []) {
|
|
|
149
173
|
process.exit(1);
|
|
150
174
|
}
|
|
151
175
|
// Step 3: Create backup before any modifications
|
|
152
|
-
const backupPath = createBackup(
|
|
176
|
+
const backupPath = createBackup(prdPath);
|
|
153
177
|
console.log(`Created backup: ${backupPath}\n`);
|
|
154
178
|
// Step 4: Attempt recovery strategies
|
|
155
179
|
console.log("Attempting recovery...\n");
|
|
@@ -159,7 +183,7 @@ export async function fixPrd(args = []) {
|
|
|
159
183
|
// Validate the recovered data
|
|
160
184
|
const recoveredValidation = validatePrd(recovered);
|
|
161
185
|
if (recoveredValidation.valid) {
|
|
162
|
-
|
|
186
|
+
writePrdAuto(prdPath, recovered);
|
|
163
187
|
console.log("\x1b[32m✓ PRD recovered successfully!\x1b[0m");
|
|
164
188
|
console.log(` Recovered ${recovered.length} entries by unwrapping/remapping fields.`);
|
|
165
189
|
return;
|
|
@@ -167,15 +191,15 @@ export async function fixPrd(args = []) {
|
|
|
167
191
|
}
|
|
168
192
|
console.log(" Direct recovery failed.\n");
|
|
169
193
|
// Strategy 2: Restore from backup
|
|
170
|
-
const latestBackup = findLatestBackup(
|
|
194
|
+
const latestBackup = findLatestBackup(prdPath);
|
|
171
195
|
if (latestBackup && latestBackup !== backupPath) {
|
|
172
196
|
console.log(`Found previous backup: ${latestBackup}`);
|
|
173
197
|
try {
|
|
174
198
|
const backupContent = readFileSync(latestBackup, "utf-8");
|
|
175
|
-
const backupParsed =
|
|
199
|
+
const backupParsed = parseBackupContent(latestBackup, backupContent);
|
|
176
200
|
const backupValidation = validatePrd(backupParsed);
|
|
177
201
|
if (backupValidation.valid) {
|
|
178
|
-
|
|
202
|
+
writePrdAuto(prdPath, backupValidation.data);
|
|
179
203
|
console.log("\x1b[32m✓ PRD restored from backup!\x1b[0m");
|
|
180
204
|
console.log(` Restored ${backupValidation.data.length} entries.`);
|
|
181
205
|
console.log("\x1b[33m Note: Recent changes may have been lost.\x1b[0m");
|
|
@@ -194,7 +218,7 @@ export async function fixPrd(args = []) {
|
|
|
194
218
|
}
|
|
195
219
|
// Strategy 3: Reset to recovery template - LLM will fix it on next run
|
|
196
220
|
console.log("Resetting PRD to recovery template...");
|
|
197
|
-
|
|
221
|
+
writePrdAuto(prdPath, createTemplatePrd(backupPath));
|
|
198
222
|
console.log("\x1b[33m✓ PRD reset with recovery task.\x1b[0m");
|
|
199
223
|
console.log(" Next 'ralph run' will instruct the LLM to recover entries from backup.");
|
|
200
224
|
console.log(` Backup location: ${backupPath}`);
|
package/dist/commands/help.js
CHANGED
|
@@ -10,9 +10,10 @@ COMMANDS:
|
|
|
10
10
|
run [n] [opts] Run automation iterations (default: all tasks)
|
|
11
11
|
add Add a new PRD entry (interactive)
|
|
12
12
|
list [opts] List all PRD entries
|
|
13
|
-
status
|
|
13
|
+
status [--head] Show PRD completion status
|
|
14
14
|
toggle <n> Toggle passes status for entry n
|
|
15
15
|
clean Remove all passing entries from the PRD
|
|
16
|
+
reset Reset all entries to incomplete (passes=false)
|
|
16
17
|
fix-prd [opts] Validate and recover corrupted PRD file
|
|
17
18
|
fix-config [opts] Validate and recover corrupted config.json
|
|
18
19
|
prompt [opts] Display resolved prompt (for testing in Claude Code)
|
|
@@ -45,8 +46,12 @@ LIST OPTIONS:
|
|
|
45
46
|
--passes Show only completed items (passes=true)
|
|
46
47
|
--no-passes Show only incomplete items (passes=false)
|
|
47
48
|
|
|
49
|
+
STATUS OPTIONS:
|
|
50
|
+
--head Show only status summary without task headlines
|
|
51
|
+
|
|
48
52
|
TOGGLE OPTIONS:
|
|
49
53
|
<n> [n2] [n3]... Toggle one or more entries by number
|
|
54
|
+
<start>-<end> Toggle a range of entries (e.g., 1-18)
|
|
50
55
|
--all, -a Toggle all PRD entries
|
|
51
56
|
|
|
52
57
|
FIX-PRD OPTIONS:
|
|
@@ -105,11 +110,14 @@ EXAMPLES:
|
|
|
105
110
|
ralph list -c feature # Show only feature entries
|
|
106
111
|
ralph list --passes # Show only completed entries
|
|
107
112
|
ralph list --no-passes # Show only incomplete entries
|
|
108
|
-
ralph status # Show completion summary
|
|
113
|
+
ralph status # Show completion summary with task headlines
|
|
114
|
+
ralph status --head # Show completion summary without task headlines
|
|
109
115
|
ralph toggle 1 # Toggle entry #1
|
|
110
116
|
ralph toggle 1 2 3 # Toggle multiple entries
|
|
117
|
+
ralph toggle 1-5 # Toggle entries 1 through 5
|
|
111
118
|
ralph toggle --all # Toggle all entries
|
|
112
119
|
ralph clean # Remove passing entries
|
|
120
|
+
ralph reset # Reset all entries to incomplete
|
|
113
121
|
ralph fix-prd # Validate/recover corrupted PRD file
|
|
114
122
|
ralph fix-prd --verify # Check PRD format without fixing
|
|
115
123
|
ralph fix-prd backup.prd.2024-01-15.json # Restore from specific backup
|
|
@@ -133,7 +141,7 @@ CONFIGURATION:
|
|
|
133
141
|
.ralph/
|
|
134
142
|
├── config.json Project configuration (language, commands, cli)
|
|
135
143
|
├── prompt.md Prompt template with $variables ($language, $checkCommand, etc.)
|
|
136
|
-
├── prd.
|
|
144
|
+
├── prd.yaml Product requirements document
|
|
137
145
|
└── progress.txt Progress tracking file
|
|
138
146
|
|
|
139
147
|
CLI CONFIGURATION:
|
package/dist/commands/init.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, writeFileSync, mkdirSync, copyFileSync, chmodSync } from "fs";
|
|
2
2
|
import { join, basename, dirname } from "path";
|
|
3
3
|
import { fileURLToPath } from "url";
|
|
4
|
-
import { getLanguages, generatePromptTemplate,
|
|
4
|
+
import { getLanguages, generatePromptTemplate, DEFAULT_PRD_YAML, DEFAULT_PROGRESS, getCliProviders, getSkillsForLanguage, } from "../templates/prompts.js";
|
|
5
5
|
import { generateGenXcodeScript, hasSwiftUI, hasFastlane, generateFastfile, generateAppfile, generateFastlaneReadmeSection, } from "../templates/macos-scripts.js";
|
|
6
6
|
import { promptSelectWithArrows, promptConfirm, promptInput, promptMultiSelectWithArrows, } from "../utils/prompt.js";
|
|
7
7
|
import { dockerInit } from "./docker.js";
|
|
@@ -13,7 +13,7 @@ const PACKAGE_ROOT = join(__dirname, "..", ".."); // dist/commands -> dist -> pa
|
|
|
13
13
|
const RALPH_DIR = ".ralph";
|
|
14
14
|
const CONFIG_FILE = "config.json";
|
|
15
15
|
const PROMPT_FILE = "prompt.md";
|
|
16
|
-
const PRD_FILE = "prd.
|
|
16
|
+
const PRD_FILE = "prd.yaml";
|
|
17
17
|
const PROGRESS_FILE = "progress.txt";
|
|
18
18
|
const PRD_GUIDE_FILE = "HOW-TO-WRITE-PRDs.md";
|
|
19
19
|
export async function init(args) {
|
|
@@ -339,14 +339,15 @@ export async function init(args) {
|
|
|
339
339
|
writeFileSync(promptPath, prompt + "\n");
|
|
340
340
|
console.log(`${existsSync(promptPath) ? "Updated" : "Created"} ${RALPH_DIR}/${PROMPT_FILE}`);
|
|
341
341
|
}
|
|
342
|
-
// Create PRD if not exists
|
|
342
|
+
// Create PRD if not exists (check for both yaml and json)
|
|
343
343
|
const prdPath = join(ralphDir, PRD_FILE);
|
|
344
|
-
|
|
345
|
-
|
|
344
|
+
const prdJsonPath = join(ralphDir, "prd.json");
|
|
345
|
+
if (!existsSync(prdPath) && !existsSync(prdJsonPath)) {
|
|
346
|
+
writeFileSync(prdPath, DEFAULT_PRD_YAML);
|
|
346
347
|
console.log(`Created ${RALPH_DIR}/${PRD_FILE}`);
|
|
347
348
|
}
|
|
348
349
|
else {
|
|
349
|
-
console.log(`Skipped ${RALPH_DIR}/${PRD_FILE} (already exists)`);
|
|
350
|
+
console.log(`Skipped ${RALPH_DIR}/${PRD_FILE} (PRD already exists)`);
|
|
350
351
|
}
|
|
351
352
|
// Create progress file if not exists
|
|
352
353
|
const progressPath = join(ralphDir, PROGRESS_FILE);
|
|
@@ -453,7 +454,7 @@ docker/.config-hash
|
|
|
453
454
|
console.log("Created .ralph/docker/ files");
|
|
454
455
|
console.log("\nRalph initialized successfully!");
|
|
455
456
|
console.log("\nNext steps:");
|
|
456
|
-
console.log(" 1. Edit .ralph/prd.
|
|
457
|
+
console.log(" 1. Edit .ralph/prd.yaml to add your project requirements");
|
|
457
458
|
console.log(" 2. Run 'ralph docker run' to start (auto-builds image on first run)");
|
|
458
459
|
console.log("\nSee .ralph/HOW-TO-WRITE-PRDs.md for guidance on writing PRDs");
|
|
459
460
|
console.log("To regenerate Docker files: ralph docker init");
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts prd.json to prd.yaml format.
|
|
3
|
+
* - Reads .ralph/prd.json
|
|
4
|
+
* - Converts to YAML format
|
|
5
|
+
* - Writes to .ralph/prd.yaml
|
|
6
|
+
* - Renames original prd.json to prd.json.pre-yaml
|
|
7
|
+
*/
|
|
8
|
+
export declare function prdConvert(args: string[]): Promise<void>;
|
|
9
|
+
export declare function convert(args: string[]): Promise<void>;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { existsSync, readFileSync, renameSync, writeFileSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import YAML from "yaml";
|
|
4
|
+
import { getRalphDir } from "../utils/config.js";
|
|
5
|
+
const PRD_JSON_FILE = "prd.json";
|
|
6
|
+
const PRD_YAML_FILE = "prd.yaml";
|
|
7
|
+
const PRD_BACKUP_SUFFIX = ".pre-yaml";
|
|
8
|
+
function getPrdJsonPath() {
|
|
9
|
+
return join(getRalphDir(), PRD_JSON_FILE);
|
|
10
|
+
}
|
|
11
|
+
function getPrdYamlPath() {
|
|
12
|
+
return join(getRalphDir(), PRD_YAML_FILE);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Converts prd.json to prd.yaml format.
|
|
16
|
+
* - Reads .ralph/prd.json
|
|
17
|
+
* - Converts to YAML format
|
|
18
|
+
* - Writes to .ralph/prd.yaml
|
|
19
|
+
* - Renames original prd.json to prd.json.pre-yaml
|
|
20
|
+
*/
|
|
21
|
+
export async function prdConvert(args) {
|
|
22
|
+
const force = args.includes("--force") || args.includes("-f");
|
|
23
|
+
const dryRun = args.includes("--dry-run") || args.includes("-n");
|
|
24
|
+
const jsonPath = getPrdJsonPath();
|
|
25
|
+
const yamlPath = getPrdYamlPath();
|
|
26
|
+
const backupPath = jsonPath + PRD_BACKUP_SUFFIX;
|
|
27
|
+
// Check if prd.json exists
|
|
28
|
+
if (!existsSync(jsonPath)) {
|
|
29
|
+
console.error("Error: .ralph/prd.json not found.");
|
|
30
|
+
console.error("Run 'ralph init' first to create a project.");
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
// Check if prd.yaml already exists
|
|
34
|
+
if (existsSync(yamlPath) && !force) {
|
|
35
|
+
console.error("Error: .ralph/prd.yaml already exists.");
|
|
36
|
+
console.error("Use --force to overwrite the existing YAML file.");
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
// Check if backup already exists (previous conversion)
|
|
40
|
+
if (existsSync(backupPath) && !force) {
|
|
41
|
+
console.error("Error: .ralph/prd.json.pre-yaml already exists.");
|
|
42
|
+
console.error("It appears a previous conversion was performed.");
|
|
43
|
+
console.error("Use --force to overwrite existing files.");
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
// Read and parse JSON
|
|
47
|
+
let prdEntries;
|
|
48
|
+
try {
|
|
49
|
+
const jsonContent = readFileSync(jsonPath, "utf-8");
|
|
50
|
+
prdEntries = JSON.parse(jsonContent);
|
|
51
|
+
if (!Array.isArray(prdEntries)) {
|
|
52
|
+
throw new Error("PRD content is not an array");
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
57
|
+
console.error(`Error reading .ralph/prd.json: ${message}`);
|
|
58
|
+
console.error("Run 'ralph fix-prd' to attempt repair.");
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
// Convert to YAML
|
|
62
|
+
const yamlContent = YAML.stringify(prdEntries, {
|
|
63
|
+
indent: 2,
|
|
64
|
+
lineWidth: 0, // Don't wrap lines
|
|
65
|
+
});
|
|
66
|
+
if (dryRun) {
|
|
67
|
+
console.log("Dry run - no files will be modified.\n");
|
|
68
|
+
console.log("Would convert .ralph/prd.json to .ralph/prd.yaml:\n");
|
|
69
|
+
console.log("--- YAML Output ---");
|
|
70
|
+
console.log(yamlContent);
|
|
71
|
+
console.log("-------------------\n");
|
|
72
|
+
console.log(`Entries: ${prdEntries.length}`);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
// Write YAML file
|
|
76
|
+
writeFileSync(yamlPath, yamlContent);
|
|
77
|
+
console.log(`\x1b[32m✓\x1b[0m Created .ralph/prd.yaml`);
|
|
78
|
+
// Rename original JSON to backup
|
|
79
|
+
renameSync(jsonPath, backupPath);
|
|
80
|
+
console.log(`\x1b[32m✓\x1b[0m Renamed .ralph/prd.json to .ralph/prd.json.pre-yaml`);
|
|
81
|
+
// Success message
|
|
82
|
+
console.log(`\n\x1b[32mConversion complete!\x1b[0m`);
|
|
83
|
+
console.log(` Converted ${prdEntries.length} PRD entries to YAML format.\n`);
|
|
84
|
+
console.log("Next steps:");
|
|
85
|
+
console.log(" 1. Your PRD is now in .ralph/prd.yaml");
|
|
86
|
+
console.log(" 2. The original JSON is preserved as .ralph/prd.json.pre-yaml");
|
|
87
|
+
console.log(" 3. Ralph will automatically use the YAML file going forward");
|
|
88
|
+
console.log("");
|
|
89
|
+
console.log("To revert, simply rename the files back:");
|
|
90
|
+
console.log(" mv .ralph/prd.json.pre-yaml .ralph/prd.json");
|
|
91
|
+
console.log(" rm .ralph/prd.yaml");
|
|
92
|
+
}
|
|
93
|
+
function showHelp() {
|
|
94
|
+
console.log("Usage: ralph prd convert [options]\n");
|
|
95
|
+
console.log("Convert .ralph/prd.json to .ralph/prd.yaml format.\n");
|
|
96
|
+
console.log("Options:");
|
|
97
|
+
console.log(" --force, -f Overwrite existing prd.yaml and backup files");
|
|
98
|
+
console.log(" --dry-run, -n Show what would be converted without making changes");
|
|
99
|
+
console.log(" --help, -h Show this help message\n");
|
|
100
|
+
console.log("The original prd.json will be renamed to prd.json.pre-yaml as a backup.");
|
|
101
|
+
}
|
|
102
|
+
export async function convert(args) {
|
|
103
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
104
|
+
showHelp();
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
await prdConvert(args);
|
|
108
|
+
}
|
package/dist/commands/prd.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
export declare function prdAdd(): Promise<void>;
|
|
2
2
|
export declare function prdList(category?: string, passesFilter?: boolean): void;
|
|
3
|
-
export declare function prdStatus(): void;
|
|
3
|
+
export declare function prdStatus(headOnly?: boolean): void;
|
|
4
4
|
export declare function prdToggle(args: string[]): void;
|
|
5
5
|
export declare function prdClean(): void;
|
|
6
|
+
export declare function prdReset(): Promise<void>;
|
|
6
7
|
export declare function parseListArgs(args: string[]): {
|
|
7
8
|
category?: string;
|
|
8
9
|
passesFilter?: boolean;
|