agentic-loop 1.0.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/.claude/commands/explain.md +114 -0
- package/.claude/commands/idea.md +398 -0
- package/.claude/commands/my-dna.md +122 -0
- package/.claude/commands/prd.md +286 -0
- package/.claude/commands/review.md +167 -0
- package/.claude/commands/sign.md +32 -0
- package/.claude/commands/styleguide.md +450 -0
- package/.claude/commands/tour.md +301 -0
- package/.claude/commands/vibe-check.md +116 -0
- package/.claude/commands/vibe-help.md +47 -0
- package/.claude/commands/vibe-list.md +203 -0
- package/.pre-commit-hooks.yaml +102 -0
- package/LICENSE +21 -0
- package/README.md +238 -0
- package/bin/agentic-loop.sh +24 -0
- package/bin/postinstall.sh +29 -0
- package/bin/ralph.sh +171 -0
- package/bin/vibe-check.js +19 -0
- package/dist/checks/check-any-types.d.ts +6 -0
- package/dist/checks/check-any-types.d.ts.map +1 -0
- package/dist/checks/check-any-types.js +73 -0
- package/dist/checks/check-any-types.js.map +1 -0
- package/dist/checks/check-commented-code.d.ts +6 -0
- package/dist/checks/check-commented-code.d.ts.map +1 -0
- package/dist/checks/check-commented-code.js +81 -0
- package/dist/checks/check-commented-code.js.map +1 -0
- package/dist/checks/check-console-error.d.ts +6 -0
- package/dist/checks/check-console-error.d.ts.map +1 -0
- package/dist/checks/check-console-error.js +41 -0
- package/dist/checks/check-console-error.js.map +1 -0
- package/dist/checks/check-debug-statements.d.ts +6 -0
- package/dist/checks/check-debug-statements.d.ts.map +1 -0
- package/dist/checks/check-debug-statements.js +120 -0
- package/dist/checks/check-debug-statements.js.map +1 -0
- package/dist/checks/check-deep-nesting.d.ts +6 -0
- package/dist/checks/check-deep-nesting.d.ts.map +1 -0
- package/dist/checks/check-deep-nesting.js +116 -0
- package/dist/checks/check-deep-nesting.js.map +1 -0
- package/dist/checks/check-docker-platform.d.ts +6 -0
- package/dist/checks/check-docker-platform.d.ts.map +1 -0
- package/dist/checks/check-docker-platform.js +42 -0
- package/dist/checks/check-docker-platform.js.map +1 -0
- package/dist/checks/check-dry-violations.d.ts +6 -0
- package/dist/checks/check-dry-violations.d.ts.map +1 -0
- package/dist/checks/check-dry-violations.js +124 -0
- package/dist/checks/check-dry-violations.js.map +1 -0
- package/dist/checks/check-empty-catch.d.ts +6 -0
- package/dist/checks/check-empty-catch.d.ts.map +1 -0
- package/dist/checks/check-empty-catch.js +111 -0
- package/dist/checks/check-empty-catch.js.map +1 -0
- package/dist/checks/check-function-length.d.ts +6 -0
- package/dist/checks/check-function-length.d.ts.map +1 -0
- package/dist/checks/check-function-length.js +152 -0
- package/dist/checks/check-function-length.js.map +1 -0
- package/dist/checks/check-hardcoded-ai-models.d.ts +10 -0
- package/dist/checks/check-hardcoded-ai-models.d.ts.map +1 -0
- package/dist/checks/check-hardcoded-ai-models.js +102 -0
- package/dist/checks/check-hardcoded-ai-models.js.map +1 -0
- package/dist/checks/check-hardcoded-urls.d.ts +6 -0
- package/dist/checks/check-hardcoded-urls.d.ts.map +1 -0
- package/dist/checks/check-hardcoded-urls.js +124 -0
- package/dist/checks/check-hardcoded-urls.js.map +1 -0
- package/dist/checks/check-magic-numbers.d.ts +6 -0
- package/dist/checks/check-magic-numbers.d.ts.map +1 -0
- package/dist/checks/check-magic-numbers.js +116 -0
- package/dist/checks/check-magic-numbers.js.map +1 -0
- package/dist/checks/check-secrets.d.ts +6 -0
- package/dist/checks/check-secrets.d.ts.map +1 -0
- package/dist/checks/check-secrets.js +138 -0
- package/dist/checks/check-secrets.js.map +1 -0
- package/dist/checks/check-snake-case-ts.d.ts +6 -0
- package/dist/checks/check-snake-case-ts.d.ts.map +1 -0
- package/dist/checks/check-snake-case-ts.js +78 -0
- package/dist/checks/check-snake-case-ts.js.map +1 -0
- package/dist/checks/check-todo-fixme.d.ts +6 -0
- package/dist/checks/check-todo-fixme.d.ts.map +1 -0
- package/dist/checks/check-todo-fixme.js +41 -0
- package/dist/checks/check-todo-fixme.js.map +1 -0
- package/dist/checks/check-unsafe-html.d.ts +6 -0
- package/dist/checks/check-unsafe-html.d.ts.map +1 -0
- package/dist/checks/check-unsafe-html.js +101 -0
- package/dist/checks/check-unsafe-html.js.map +1 -0
- package/dist/checks/index.d.ts +30 -0
- package/dist/checks/index.d.ts.map +1 -0
- package/dist/checks/index.js +57 -0
- package/dist/checks/index.js.map +1 -0
- package/dist/cli.d.ts +13 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +208 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/utils/file-reader.d.ts +24 -0
- package/dist/utils/file-reader.d.ts.map +1 -0
- package/dist/utils/file-reader.js +146 -0
- package/dist/utils/file-reader.js.map +1 -0
- package/dist/utils/patterns.d.ts +27 -0
- package/dist/utils/patterns.d.ts.map +1 -0
- package/dist/utils/patterns.js +84 -0
- package/dist/utils/patterns.js.map +1 -0
- package/dist/utils/reporters.d.ts +21 -0
- package/dist/utils/reporters.d.ts.map +1 -0
- package/dist/utils/reporters.js +115 -0
- package/dist/utils/reporters.js.map +1 -0
- package/dist/utils/types.d.ts +71 -0
- package/dist/utils/types.d.ts.map +1 -0
- package/dist/utils/types.js +5 -0
- package/dist/utils/types.js.map +1 -0
- package/package.json +83 -0
- package/ralph/api.sh +216 -0
- package/ralph/backup.sh +838 -0
- package/ralph/browser-verify/README.md +135 -0
- package/ralph/browser-verify/verify.ts +450 -0
- package/ralph/checks/check-fastapi-responses.py +155 -0
- package/ralph/hooks/hooks-config.json +72 -0
- package/ralph/hooks/inject-context.sh +44 -0
- package/ralph/hooks/install.sh +207 -0
- package/ralph/hooks/log-tools.sh +45 -0
- package/ralph/hooks/protect-prd.sh +27 -0
- package/ralph/hooks/save-learnings.sh +36 -0
- package/ralph/hooks/warn-debug.sh +54 -0
- package/ralph/hooks/warn-empty-catch.sh +63 -0
- package/ralph/hooks/warn-secrets.sh +89 -0
- package/ralph/hooks/warn-urls.sh +77 -0
- package/ralph/init.sh +515 -0
- package/ralph/loop.sh +730 -0
- package/ralph/playwright.sh +238 -0
- package/ralph/prd.sh +295 -0
- package/ralph/setup/feature-tour.sh +155 -0
- package/ralph/setup/quick-setup.sh +239 -0
- package/ralph/setup/tutorial.sh +159 -0
- package/ralph/setup/ui.sh +136 -0
- package/ralph/setup.sh +401 -0
- package/ralph/signs.sh +150 -0
- package/ralph/utils.sh +682 -0
- package/ralph/verify/browser.sh +324 -0
- package/ralph/verify/lint.sh +363 -0
- package/ralph/verify/review.sh +152 -0
- package/ralph/verify/tests.sh +81 -0
- package/ralph/verify.sh +268 -0
- package/templates/PROMPT.md +235 -0
- package/templates/config/fullstack.json +86 -0
- package/templates/config/go.json +81 -0
- package/templates/config/minimal.json +76 -0
- package/templates/config/node.json +81 -0
- package/templates/config/python.json +81 -0
- package/templates/config/rust.json +81 -0
- package/templates/examples/CLAUDE-django.md +174 -0
- package/templates/examples/CLAUDE-fastapi.md +270 -0
- package/templates/examples/CLAUDE-fastmcp.md +352 -0
- package/templates/examples/CLAUDE-fullstack.md +256 -0
- package/templates/examples/CLAUDE-node.md +246 -0
- package/templates/examples/CLAUDE-react.md +138 -0
- package/templates/optional/cursorrules.template +147 -0
- package/templates/optional/eslint.config.js +34 -0
- package/templates/optional/lint-staged.config.js +34 -0
- package/templates/optional/ruff.toml +125 -0
- package/templates/optional/vibe-check.yml +116 -0
- package/templates/optional/vscode-settings.json +127 -0
- package/templates/signs.json +46 -0
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# shellcheck shell=bash
|
|
3
|
+
# playwright.sh - Playwright test integration for ralph
|
|
4
|
+
|
|
5
|
+
# Ensure Playwright is installed and configured
|
|
6
|
+
ensure_playwright() {
|
|
7
|
+
# Check if playwright config exists
|
|
8
|
+
if [[ ! -f "playwright.config.ts" ]] && [[ ! -f "playwright.config.js" ]]; then
|
|
9
|
+
print_info "Playwright not configured, initializing..."
|
|
10
|
+
|
|
11
|
+
# Check if npx is available
|
|
12
|
+
if ! command -v npx &>/dev/null; then
|
|
13
|
+
print_error "npx not found - cannot install Playwright"
|
|
14
|
+
return 1
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
# Install Playwright package and browsers
|
|
18
|
+
npm install playwright 2>/dev/null && npx playwright install chromium 2>/dev/null || {
|
|
19
|
+
print_warning "Could not install Playwright - run: npm install playwright && npx playwright install chromium"
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
# Create minimal config if it doesn't exist
|
|
23
|
+
if [[ ! -f "playwright.config.ts" ]] && [[ ! -f "playwright.config.js" ]]; then
|
|
24
|
+
print_info "Creating playwright.config.ts..."
|
|
25
|
+
cat > playwright.config.ts << 'EOF'
|
|
26
|
+
import { defineConfig, devices } from '@playwright/test';
|
|
27
|
+
|
|
28
|
+
export default defineConfig({
|
|
29
|
+
testDir: './tests/e2e',
|
|
30
|
+
fullyParallel: true,
|
|
31
|
+
forbidOnly: !!process.env.CI,
|
|
32
|
+
retries: process.env.CI ? 2 : 0,
|
|
33
|
+
workers: process.env.CI ? 1 : undefined,
|
|
34
|
+
reporter: 'html',
|
|
35
|
+
use: {
|
|
36
|
+
baseURL: process.env.BASE_URL || 'http://localhost:3000',
|
|
37
|
+
trace: 'on-first-retry',
|
|
38
|
+
screenshot: 'only-on-failure',
|
|
39
|
+
},
|
|
40
|
+
projects: [
|
|
41
|
+
{
|
|
42
|
+
name: 'chromium',
|
|
43
|
+
use: { ...devices['Desktop Chrome'] },
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
name: 'mobile',
|
|
47
|
+
use: { ...devices['iPhone 13'] },
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
});
|
|
51
|
+
EOF
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
# Create test directory
|
|
55
|
+
mkdir -p tests/e2e
|
|
56
|
+
|
|
57
|
+
print_success "Playwright initialized"
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
return 0
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
# Find test file for a story - uses explicit config or story-level testFile
|
|
64
|
+
find_story_test_file() {
|
|
65
|
+
local story="$1"
|
|
66
|
+
local test_dir="$2"
|
|
67
|
+
|
|
68
|
+
# 1. Check story for explicit testFile path (preferred)
|
|
69
|
+
local explicit_file
|
|
70
|
+
explicit_file=$(jq -r --arg id "$story" '.stories[] | select(.id==$id) | .testFile // empty' "$RALPH_DIR/prd.json" 2>/dev/null)
|
|
71
|
+
if [[ -n "$explicit_file" && -f "$explicit_file" ]]; then
|
|
72
|
+
echo "$explicit_file"
|
|
73
|
+
return 0
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
# 2. Check config.json for e2e test directory pattern
|
|
77
|
+
local config_test_dir
|
|
78
|
+
config_test_dir=$(get_config '.playwright.testDir' "")
|
|
79
|
+
if [[ -n "$config_test_dir" ]]; then
|
|
80
|
+
test_dir="$config_test_dir"
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
# 3. Standard naming: {testDir}/{story-id}.spec.ts
|
|
84
|
+
if [[ -f "${test_dir}/${story}.spec.ts" ]]; then
|
|
85
|
+
echo "${test_dir}/${story}.spec.ts"
|
|
86
|
+
return 0
|
|
87
|
+
fi
|
|
88
|
+
if [[ -f "${test_dir}/${story}.spec.js" ]]; then
|
|
89
|
+
echo "${test_dir}/${story}.spec.js"
|
|
90
|
+
return 0
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
# 4. Slug-based naming from story title
|
|
94
|
+
local story_slug
|
|
95
|
+
story_slug=$(jq -r --arg id "$story" '.stories[] | select(.id==$id) | .title // ""' "$RALPH_DIR/prd.json" 2>/dev/null | \
|
|
96
|
+
tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//' | sed 's/-$//')
|
|
97
|
+
|
|
98
|
+
if [[ -n "$story_slug" && -f "${test_dir}/${story_slug}.spec.ts" ]]; then
|
|
99
|
+
echo "${test_dir}/${story_slug}.spec.ts"
|
|
100
|
+
return 0
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
return 1
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
# Run Playwright tests for a specific story or all tests
|
|
107
|
+
run_playwright_tests() {
|
|
108
|
+
local story="$1"
|
|
109
|
+
|
|
110
|
+
# Check if Playwright is enabled in config
|
|
111
|
+
local pw_enabled
|
|
112
|
+
pw_enabled=$(get_config '.playwright.enabled' "true")
|
|
113
|
+
if [[ "$pw_enabled" == "false" ]]; then
|
|
114
|
+
echo " (Playwright disabled in config, skipping)"
|
|
115
|
+
return 0
|
|
116
|
+
fi
|
|
117
|
+
|
|
118
|
+
# Ensure Playwright is set up
|
|
119
|
+
if ! ensure_playwright; then
|
|
120
|
+
return 1
|
|
121
|
+
fi
|
|
122
|
+
|
|
123
|
+
# Check if npx is available
|
|
124
|
+
if ! command -v npx &>/dev/null; then
|
|
125
|
+
print_error "npx not found - cannot run Playwright"
|
|
126
|
+
return 1
|
|
127
|
+
fi
|
|
128
|
+
|
|
129
|
+
# Get test directory from config or use default
|
|
130
|
+
local test_dir
|
|
131
|
+
test_dir=$(get_config '.playwright.testDir' "tests/e2e")
|
|
132
|
+
|
|
133
|
+
# Find the test file for this story
|
|
134
|
+
local test_file
|
|
135
|
+
test_file=$(find_story_test_file "$story" "$test_dir")
|
|
136
|
+
|
|
137
|
+
local log_file
|
|
138
|
+
log_file=$(create_temp_file ".log") || return 1
|
|
139
|
+
|
|
140
|
+
echo -n " Running Playwright tests... "
|
|
141
|
+
|
|
142
|
+
if [[ -n "$test_file" && -f "$test_file" ]]; then
|
|
143
|
+
# Run story-specific test
|
|
144
|
+
echo -n "(${test_file##*/}) "
|
|
145
|
+
if npx playwright test "$test_file" --reporter=line > "$log_file" 2>&1; then
|
|
146
|
+
print_success "passed"
|
|
147
|
+
rm -f "$log_file"
|
|
148
|
+
return 0
|
|
149
|
+
fi
|
|
150
|
+
else
|
|
151
|
+
# No story-specific test - check if e2e was expected
|
|
152
|
+
local e2e_required
|
|
153
|
+
e2e_required=$(jq -r --arg id "$story" '.stories[] | select(.id==$id) | .e2e // false' "$RALPH_DIR/prd.json" 2>/dev/null)
|
|
154
|
+
|
|
155
|
+
if [[ "$e2e_required" == "true" ]]; then
|
|
156
|
+
# Show where we looked and how to fix
|
|
157
|
+
print_warning "missing (e2e: true but no test file found)"
|
|
158
|
+
echo ""
|
|
159
|
+
echo " Looked for: ${test_dir}/${story}.spec.ts"
|
|
160
|
+
echo ""
|
|
161
|
+
echo " Fix options:"
|
|
162
|
+
echo " 1. Add 'testFile' to story: \"testFile\": \"apps/web/tests/e2e/my-test.spec.ts\""
|
|
163
|
+
echo " 2. Set testDir in .ralph/config.json: \"playwright\": {\"testDir\": \"apps/web/tests/e2e\"}"
|
|
164
|
+
echo " 3. Name test file: ${test_dir}/${story}.spec.ts"
|
|
165
|
+
rm -f "$log_file"
|
|
166
|
+
return 1 # Fail if e2e was expected but not created
|
|
167
|
+
fi
|
|
168
|
+
|
|
169
|
+
# Check if any tests exist to run
|
|
170
|
+
if [[ -z "$(find "$test_dir" -name '*.spec.ts' -o -name '*.spec.js' 2>/dev/null | head -1)" ]]; then
|
|
171
|
+
echo "skipped (not required for this story)"
|
|
172
|
+
rm -f "$log_file"
|
|
173
|
+
return 0
|
|
174
|
+
fi
|
|
175
|
+
|
|
176
|
+
# Run all tests
|
|
177
|
+
if npx playwright test --reporter=line > "$log_file" 2>&1; then
|
|
178
|
+
print_success "passed"
|
|
179
|
+
rm -f "$log_file"
|
|
180
|
+
return 0
|
|
181
|
+
fi
|
|
182
|
+
fi
|
|
183
|
+
|
|
184
|
+
# Tests failed
|
|
185
|
+
print_error "failed"
|
|
186
|
+
echo ""
|
|
187
|
+
echo " Playwright output (last $MAX_LOG_LINES lines):"
|
|
188
|
+
tail -"$MAX_LOG_LINES" "$log_file" | sed 's/^/ /'
|
|
189
|
+
|
|
190
|
+
# Save failure for context
|
|
191
|
+
cp "$log_file" "$RALPH_DIR/last_playwright_failure.log"
|
|
192
|
+
|
|
193
|
+
rm -f "$log_file"
|
|
194
|
+
return 1
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
# Run Playwright with accessibility checks
|
|
198
|
+
run_playwright_a11y() {
|
|
199
|
+
local story="$1"
|
|
200
|
+
local url="$2"
|
|
201
|
+
|
|
202
|
+
# Check if @axe-core/playwright is available
|
|
203
|
+
if ! npm list @axe-core/playwright &>/dev/null 2>&1; then
|
|
204
|
+
print_info "Installing @axe-core/playwright for accessibility testing..."
|
|
205
|
+
npm install -D @axe-core/playwright 2>/dev/null || {
|
|
206
|
+
print_warning "Could not install axe-core, skipping a11y tests"
|
|
207
|
+
return 0
|
|
208
|
+
}
|
|
209
|
+
fi
|
|
210
|
+
|
|
211
|
+
# a11y tests are typically part of the Playwright tests
|
|
212
|
+
# This function can be extended to run standalone a11y audits
|
|
213
|
+
return 0
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
# Run Playwright at specific viewport (for mobile testing)
|
|
217
|
+
run_playwright_mobile() {
|
|
218
|
+
local story="$1"
|
|
219
|
+
|
|
220
|
+
local log_file
|
|
221
|
+
log_file=$(create_temp_file ".log") || return 1
|
|
222
|
+
|
|
223
|
+
echo -n " Running mobile viewport tests... "
|
|
224
|
+
|
|
225
|
+
# Run tests with mobile project
|
|
226
|
+
if npx playwright test --project=mobile --reporter=line > "$log_file" 2>&1; then
|
|
227
|
+
print_success "passed"
|
|
228
|
+
rm -f "$log_file"
|
|
229
|
+
return 0
|
|
230
|
+
else
|
|
231
|
+
print_error "failed"
|
|
232
|
+
echo ""
|
|
233
|
+
echo " Mobile test output:"
|
|
234
|
+
tail -"$MAX_OUTPUT_PREVIEW_LINES" "$log_file" | sed 's/^/ /'
|
|
235
|
+
rm -f "$log_file"
|
|
236
|
+
return 1
|
|
237
|
+
fi
|
|
238
|
+
}
|
package/ralph/prd.sh
ADDED
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# shellcheck shell=bash
|
|
3
|
+
# prd.sh - PRD generation functions
|
|
4
|
+
|
|
5
|
+
ralph_prd() {
|
|
6
|
+
local notes=""
|
|
7
|
+
local from_file=""
|
|
8
|
+
|
|
9
|
+
# Parse arguments
|
|
10
|
+
while [[ $# -gt 0 ]]; do
|
|
11
|
+
case "$1" in
|
|
12
|
+
--file|-f)
|
|
13
|
+
from_file="$2"
|
|
14
|
+
shift 2
|
|
15
|
+
;;
|
|
16
|
+
--accept)
|
|
17
|
+
ralph_prd_accept
|
|
18
|
+
return $?
|
|
19
|
+
;;
|
|
20
|
+
*)
|
|
21
|
+
notes="$notes $1"
|
|
22
|
+
shift
|
|
23
|
+
;;
|
|
24
|
+
esac
|
|
25
|
+
done
|
|
26
|
+
|
|
27
|
+
# Read from file if specified
|
|
28
|
+
if [[ -n "$from_file" ]]; then
|
|
29
|
+
if [[ ! -f "$from_file" ]]; then
|
|
30
|
+
print_error "File not found: $from_file"
|
|
31
|
+
return 1
|
|
32
|
+
fi
|
|
33
|
+
notes=$(cat "$from_file") || { print_error "Failed to read $from_file"; return 1; }
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
# Trim leading/trailing whitespace (safer than xargs)
|
|
37
|
+
notes="${notes#"${notes%%[![:space:]]*}"}" # trim leading
|
|
38
|
+
notes="${notes%"${notes##*[![:space:]]}"}" # trim trailing
|
|
39
|
+
|
|
40
|
+
if [[ -z "$notes" ]]; then
|
|
41
|
+
print_error "No notes provided. Usage: ralph prd <description>"
|
|
42
|
+
echo ""
|
|
43
|
+
echo "Examples:"
|
|
44
|
+
echo " ralph prd 'Add user authentication with OAuth'"
|
|
45
|
+
echo " ralph prd --file docs/feature-spec.md"
|
|
46
|
+
return 1
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
# Check if PRD already exists
|
|
50
|
+
if [[ -f "$RALPH_DIR/prd.json" ]]; then
|
|
51
|
+
print_warning "A PRD already exists. Archive or delete it first."
|
|
52
|
+
echo " Current feature: $(jq -r '.feature.name' "$RALPH_DIR/prd.json")"
|
|
53
|
+
echo ""
|
|
54
|
+
echo "Options:"
|
|
55
|
+
echo " 1. Archive it: mv .ralph/prd.json .ralph/archive/"
|
|
56
|
+
echo " 2. Delete it: rm .ralph/prd.json"
|
|
57
|
+
echo " 3. Check status: ralph status"
|
|
58
|
+
return 1
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
# Check Claude CLI is available
|
|
62
|
+
if ! command -v claude &>/dev/null; then
|
|
63
|
+
print_error "Claude CLI not found. Install it with: npm install -g @anthropic-ai/claude-code"
|
|
64
|
+
return 1
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
echo ""
|
|
68
|
+
print_info "Starting interactive PRD generation..."
|
|
69
|
+
echo "Claude will ask clarifying questions to refine the PRD."
|
|
70
|
+
echo ""
|
|
71
|
+
|
|
72
|
+
# Get testUrlBase from config (default to localhost:3000)
|
|
73
|
+
local test_url_base
|
|
74
|
+
test_url_base=$(get_config "testUrlBase" "http://localhost:3000")
|
|
75
|
+
|
|
76
|
+
# Create the PRD generation prompt using printf to avoid heredoc issues
|
|
77
|
+
local prompt_file
|
|
78
|
+
prompt_file=$(create_temp_file ".md") || return 1
|
|
79
|
+
|
|
80
|
+
printf '%s\n' "You are generating a PRD (Product Requirements Document) for an autonomous coding agent called Ralph." > "$prompt_file"
|
|
81
|
+
printf '\n%s\n' "The user provided these feature notes:" >> "$prompt_file"
|
|
82
|
+
printf '\n%s\n' "---" >> "$prompt_file"
|
|
83
|
+
printf '%s\n' "$notes" >> "$prompt_file"
|
|
84
|
+
printf '%s\n' "---" >> "$prompt_file"
|
|
85
|
+
printf '\n%s\n' "## Your Task" >> "$prompt_file"
|
|
86
|
+
printf '\n%s\n' "1. First, ask up to 5 clarifying questions about:" >> "$prompt_file"
|
|
87
|
+
printf '%s\n' " - Security requirements (auth, permissions, data access)" >> "$prompt_file"
|
|
88
|
+
printf '%s\n' " - Scaling concerns (caching, pagination, rate limits)" >> "$prompt_file"
|
|
89
|
+
printf '%s\n' " - Edge cases (empty states, error handling)" >> "$prompt_file"
|
|
90
|
+
printf '%s\n' " - Related features (what existing features does this touch?)" >> "$prompt_file"
|
|
91
|
+
printf '%s\n' " - Scope boundaries (what is explicitly OUT of scope?)" >> "$prompt_file"
|
|
92
|
+
printf '\n%s\n' "2. After the user answers, generate a prd.json file with this exact structure:" >> "$prompt_file"
|
|
93
|
+
printf '\n%s\n' '{' >> "$prompt_file"
|
|
94
|
+
printf '%s\n' ' "feature": {' >> "$prompt_file"
|
|
95
|
+
printf '%s\n' ' "name": "Feature Name",' >> "$prompt_file"
|
|
96
|
+
printf '%s\n' ' "branch": "feature/feature-name",' >> "$prompt_file"
|
|
97
|
+
printf '%s\n' ' "status": "pending"' >> "$prompt_file"
|
|
98
|
+
printf '%s\n' ' },' >> "$prompt_file"
|
|
99
|
+
printf '%s\n' ' "metadata": {' >> "$prompt_file"
|
|
100
|
+
printf '%s\n' ' "security": ["list of security requirements"],' >> "$prompt_file"
|
|
101
|
+
printf '%s\n' ' "scaling": ["list of scaling concerns"],' >> "$prompt_file"
|
|
102
|
+
printf '%s\n' ' "relatedFeatures": ["list of related features"],' >> "$prompt_file"
|
|
103
|
+
printf '%s\n' ' "estimatedStories": 5,' >> "$prompt_file"
|
|
104
|
+
printf '%s\n' ' "complexity": "low|medium|high"' >> "$prompt_file"
|
|
105
|
+
printf '%s\n' ' },' >> "$prompt_file"
|
|
106
|
+
printf '%s\n' ' "scalability": {' >> "$prompt_file"
|
|
107
|
+
printf '%s\n' ' "expectedScale": "100s | 1000s | 10000s+ users",' >> "$prompt_file"
|
|
108
|
+
printf '%s\n' ' "pagination": "cursor | offset | none",' >> "$prompt_file"
|
|
109
|
+
printf '%s\n' ' "caching": "none | in-memory | redis",' >> "$prompt_file"
|
|
110
|
+
printf '%s\n' ' "rateLimiting": "if needed, requests per minute"' >> "$prompt_file"
|
|
111
|
+
printf '%s\n' ' },' >> "$prompt_file"
|
|
112
|
+
printf '%s\n' ' "architecture": {' >> "$prompt_file"
|
|
113
|
+
printf '%s\n' ' "directories": {' >> "$prompt_file"
|
|
114
|
+
printf '%s\n' ' "components": "src/components/{feature}/",' >> "$prompt_file"
|
|
115
|
+
printf '%s\n' ' "api": "src/api/",' >> "$prompt_file"
|
|
116
|
+
printf '%s\n' ' "types": "src/types/",' >> "$prompt_file"
|
|
117
|
+
printf '%s\n' ' "scripts": "scripts/",' >> "$prompt_file"
|
|
118
|
+
printf '%s\n' ' "docs": "docs/"' >> "$prompt_file"
|
|
119
|
+
printf '%s\n' ' },' >> "$prompt_file"
|
|
120
|
+
printf '%s\n' ' "patterns": {' >> "$prompt_file"
|
|
121
|
+
printf '%s\n' ' "reuse": ["existing components/utils to use"],' >> "$prompt_file"
|
|
122
|
+
printf '%s\n' ' "follow": ["existing patterns to match"]' >> "$prompt_file"
|
|
123
|
+
printf '%s\n' ' },' >> "$prompt_file"
|
|
124
|
+
printf '%s\n' ' "principles": {' >> "$prompt_file"
|
|
125
|
+
printf '%s\n' ' "maxFileLines": 300,' >> "$prompt_file"
|
|
126
|
+
printf '%s\n' ' "singleResponsibility": true' >> "$prompt_file"
|
|
127
|
+
printf '%s\n' ' },' >> "$prompt_file"
|
|
128
|
+
printf '%s\n' ' "doNotCreate": ["things that already exist"]' >> "$prompt_file"
|
|
129
|
+
printf '%s\n' ' },' >> "$prompt_file"
|
|
130
|
+
printf '%s\n' ' "stories": [' >> "$prompt_file"
|
|
131
|
+
printf '%s\n' ' {' >> "$prompt_file"
|
|
132
|
+
printf '%s\n' ' "id": "TASK-001",' >> "$prompt_file"
|
|
133
|
+
printf '%s\n' ' "title": "Story title",' >> "$prompt_file"
|
|
134
|
+
printf '%s\n' ' "passes": false,' >> "$prompt_file"
|
|
135
|
+
printf '%s\n' " \"testUrl\": \"${test_url_base}/path/to/test\"," >> "$prompt_file"
|
|
136
|
+
printf '%s\n' ' "files": {' >> "$prompt_file"
|
|
137
|
+
printf '%s\n' ' "create": ["paths to new files"],' >> "$prompt_file"
|
|
138
|
+
printf '%s\n' ' "modify": ["paths to existing files"],' >> "$prompt_file"
|
|
139
|
+
printf '%s\n' ' "reuse": ["paths to files to import from"]' >> "$prompt_file"
|
|
140
|
+
printf '%s\n' ' },' >> "$prompt_file"
|
|
141
|
+
printf '%s\n' ' "acceptanceCriteria": ["AC 1", "AC 2"],' >> "$prompt_file"
|
|
142
|
+
printf '%s\n' ' "errorHandling": ["what happens on failure"],' >> "$prompt_file"
|
|
143
|
+
printf '%s\n' ' "testSteps": ["executable shell commands only"],' >> "$prompt_file"
|
|
144
|
+
printf '%s\n' ' "notes": ""' >> "$prompt_file"
|
|
145
|
+
printf '%s\n' ' }' >> "$prompt_file"
|
|
146
|
+
printf '%s\n' ' ]' >> "$prompt_file"
|
|
147
|
+
printf '%s\n' '}' >> "$prompt_file"
|
|
148
|
+
printf '\n%s\n' "## Test Steps - CRITICAL" >> "$prompt_file"
|
|
149
|
+
printf '\n%s\n' "testSteps MUST be executable shell commands. Ralph runs them with bash." >> "$prompt_file"
|
|
150
|
+
printf '\n%s\n' "GOOD: curl -s http://localhost:3000/api/health | grep ok" >> "$prompt_file"
|
|
151
|
+
printf '%s\n' "GOOD: test -f src/components/Button.tsx" >> "$prompt_file"
|
|
152
|
+
printf '%s\n' "GOOD: grep -q 'export function' src/api/users.ts" >> "$prompt_file"
|
|
153
|
+
printf '%s\n' "GOOD: cd frontend && npx tsc --noEmit" >> "$prompt_file"
|
|
154
|
+
printf '%s\n' "GOOD: npx playwright test tests/e2e/dashboard.spec.ts" >> "$prompt_file"
|
|
155
|
+
printf '%s\n' "GOOD: npx playwright test --grep 'login flow'" >> "$prompt_file"
|
|
156
|
+
printf '\n%s\n' "For UI/visual checks, use Playwright tests that can verify:" >> "$prompt_file"
|
|
157
|
+
printf '%s\n' "- Element visibility and positioning" >> "$prompt_file"
|
|
158
|
+
printf '%s\n' "- Console errors (no errors in DevTools)" >> "$prompt_file"
|
|
159
|
+
printf '%s\n' "- Network requests completing" >> "$prompt_file"
|
|
160
|
+
printf '%s\n' "- Accessibility (axe-core)" >> "$prompt_file"
|
|
161
|
+
printf '\n%s\n' "BAD: Visit the page (not a command)" >> "$prompt_file"
|
|
162
|
+
printf '%s\n' "BAD: User can see the form (not a command)" >> "$prompt_file"
|
|
163
|
+
printf '%s\n' "BAD: Click submit button (not a command)" >> "$prompt_file"
|
|
164
|
+
printf '%s\n' "BAD: Check DevTools for errors (not a command)" >> "$prompt_file"
|
|
165
|
+
printf '\n%s\n' "If something can't be automated, put it in acceptanceCriteria instead." >> "$prompt_file"
|
|
166
|
+
printf '\n%s\n' "## Context Size Limits" >> "$prompt_file"
|
|
167
|
+
printf '\n%s\n' "Each story must be completable in ONE Claude session without context overflow:" >> "$prompt_file"
|
|
168
|
+
printf '%s\n' "- Max ~1000 tokens for story description (title + criteria + error handling)" >> "$prompt_file"
|
|
169
|
+
printf '%s\n' "- Max 3-4 files created or modified per story" >> "$prompt_file"
|
|
170
|
+
printf '%s\n' "- If a story feels too big, SPLIT IT" >> "$prompt_file"
|
|
171
|
+
printf '\n%s\n' "## UI Stories Must Include Browser Verification" >> "$prompt_file"
|
|
172
|
+
printf '\n%s\n' "For frontend stories, acceptanceCriteria MUST include:" >> "$prompt_file"
|
|
173
|
+
printf '%s\n' '- "Page loads without console errors"' >> "$prompt_file"
|
|
174
|
+
printf '%s\n' '- "Required elements render" (specify which: header, form, button)' >> "$prompt_file"
|
|
175
|
+
printf '%s\n' '- "Works on mobile viewport (375px)"' >> "$prompt_file"
|
|
176
|
+
printf '\n%s\n' "These get verified by Playwright automatically." >> "$prompt_file"
|
|
177
|
+
printf '\n%s\n' "## Notes Field" >> "$prompt_file"
|
|
178
|
+
printf '\n%s\n' 'The "notes" field starts empty. Claude fills it as it works:' >> "$prompt_file"
|
|
179
|
+
printf '%s\n' "- Files created/modified" >> "$prompt_file"
|
|
180
|
+
printf '%s\n' "- Key decisions made" >> "$prompt_file"
|
|
181
|
+
printf '%s\n' "- Context for the next story" >> "$prompt_file"
|
|
182
|
+
printf '\n%s\n' "## Context Rot Check" >> "$prompt_file"
|
|
183
|
+
printf '\n%s\n' "Before finalizing, validate:" >> "$prompt_file"
|
|
184
|
+
printf '%s\n' "- If > 10 stories, recommend splitting into phases" >> "$prompt_file"
|
|
185
|
+
printf '%s\n' '- If complexity is "high" AND > 5 stories, MUST split' >> "$prompt_file"
|
|
186
|
+
printf '%s\n' "- Each story should be completable in one Claude session (~10 min)" >> "$prompt_file"
|
|
187
|
+
printf '\n%s\n' "If splitting is needed, generate ONLY phase 1 and note deferred items." >> "$prompt_file"
|
|
188
|
+
printf '\n%s\n' "## Output" >> "$prompt_file"
|
|
189
|
+
printf '\n%s\n' "After questions are answered, output the prd.json content between these exact markers:" >> "$prompt_file"
|
|
190
|
+
printf '%s\n' "--- BEGIN PRD.JSON ---" >> "$prompt_file"
|
|
191
|
+
printf '%s\n' "{the json here}" >> "$prompt_file"
|
|
192
|
+
printf '%s\n' "--- END PRD.JSON ---" >> "$prompt_file"
|
|
193
|
+
|
|
194
|
+
# Run Claude interactively (not -p mode so user can answer questions)
|
|
195
|
+
local output_file
|
|
196
|
+
output_file=$(create_temp_file ".txt") || return 1
|
|
197
|
+
|
|
198
|
+
claude < "$prompt_file" | tee "$output_file"
|
|
199
|
+
|
|
200
|
+
# Save output for --accept command (store in RALPH_DIR for security)
|
|
201
|
+
mkdir -p "$RALPH_DIR"
|
|
202
|
+
cp "$output_file" "$RALPH_DIR/prd_output.txt"
|
|
203
|
+
|
|
204
|
+
rm -f "$prompt_file" "$output_file"
|
|
205
|
+
|
|
206
|
+
echo ""
|
|
207
|
+
echo "---"
|
|
208
|
+
echo ""
|
|
209
|
+
print_info "PRD generation complete."
|
|
210
|
+
echo ""
|
|
211
|
+
echo "To save the PRD, run: ralph prd --accept"
|
|
212
|
+
echo "Or manually copy the JSON to .ralph/prd.json"
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
# Extract PRD from Claude output and save
|
|
216
|
+
ralph_prd_accept() {
|
|
217
|
+
local output_file="$RALPH_DIR/prd_output.txt"
|
|
218
|
+
|
|
219
|
+
if [[ ! -f "$output_file" ]]; then
|
|
220
|
+
print_error "No PRD output found. Run 'ralph prd' first."
|
|
221
|
+
return 1
|
|
222
|
+
fi
|
|
223
|
+
|
|
224
|
+
# Extract JSON between markers
|
|
225
|
+
local prd_json
|
|
226
|
+
prd_json=$(sed -n '/--- BEGIN PRD.JSON ---/,/--- END PRD.JSON ---/p' "$output_file" | sed '1d;$d')
|
|
227
|
+
|
|
228
|
+
if [[ -z "$prd_json" ]]; then
|
|
229
|
+
print_error "Could not find PRD JSON in output"
|
|
230
|
+
echo ""
|
|
231
|
+
echo "The output should contain JSON between markers:"
|
|
232
|
+
echo " --- BEGIN PRD.JSON ---"
|
|
233
|
+
echo " {...}"
|
|
234
|
+
echo " --- END PRD.JSON ---"
|
|
235
|
+
return 1
|
|
236
|
+
fi
|
|
237
|
+
|
|
238
|
+
# Validate JSON syntax
|
|
239
|
+
if ! echo "$prd_json" | jq . > /dev/null 2>&1; then
|
|
240
|
+
print_error "Invalid JSON in PRD"
|
|
241
|
+
echo ""
|
|
242
|
+
echo "JSON parsing error. Content:"
|
|
243
|
+
echo "$prd_json" | head -20
|
|
244
|
+
return 1
|
|
245
|
+
fi
|
|
246
|
+
|
|
247
|
+
# Validate required fields exist
|
|
248
|
+
if ! echo "$prd_json" | jq -e '.feature.name and .stories' > /dev/null 2>&1; then
|
|
249
|
+
print_error "PRD JSON missing required fields (feature.name or stories)"
|
|
250
|
+
echo ""
|
|
251
|
+
echo "Ensure the PRD has this structure:"
|
|
252
|
+
echo ' { "feature": { "name": "..." }, "stories": [...] }'
|
|
253
|
+
return 1
|
|
254
|
+
fi
|
|
255
|
+
|
|
256
|
+
# Ensure .ralph directory exists
|
|
257
|
+
mkdir -p "$RALPH_DIR"
|
|
258
|
+
|
|
259
|
+
# Normalize the PRD before saving:
|
|
260
|
+
# - Ensure all stories have passes: false
|
|
261
|
+
# - Ensure all stories have id and title
|
|
262
|
+
# - Set status to pending
|
|
263
|
+
prd_json=$(echo "$prd_json" | jq '
|
|
264
|
+
.feature.status = "pending" |
|
|
265
|
+
.stories = [.stories[] | . + {passes: (.passes // false)}]
|
|
266
|
+
')
|
|
267
|
+
|
|
268
|
+
# Save
|
|
269
|
+
echo "$prd_json" > "$RALPH_DIR/prd.json"
|
|
270
|
+
|
|
271
|
+
# Run full validation
|
|
272
|
+
if ! validate_prd "$RALPH_DIR/prd.json"; then
|
|
273
|
+
rm -f "$RALPH_DIR/prd.json"
|
|
274
|
+
return 1
|
|
275
|
+
fi
|
|
276
|
+
|
|
277
|
+
# Show summary
|
|
278
|
+
local name stories complexity
|
|
279
|
+
name=$(echo "$prd_json" | jq -r '.feature.name')
|
|
280
|
+
stories=$(echo "$prd_json" | jq '.stories | length')
|
|
281
|
+
complexity=$(echo "$prd_json" | jq -r '.metadata.complexity // "unknown"')
|
|
282
|
+
|
|
283
|
+
print_success "PRD saved to $RALPH_DIR/prd.json"
|
|
284
|
+
echo ""
|
|
285
|
+
echo "Feature: $name"
|
|
286
|
+
echo "Stories: $stories"
|
|
287
|
+
echo "Complexity: $complexity"
|
|
288
|
+
echo ""
|
|
289
|
+
echo "Next: Run ralph run to start the autonomous loop"
|
|
290
|
+
|
|
291
|
+
# Clean up
|
|
292
|
+
rm -f "$RALPH_DIR/prd_output.txt"
|
|
293
|
+
|
|
294
|
+
return 0
|
|
295
|
+
}
|