agentic-loop 3.14.2 → 3.16.2
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/skills/prd/SKILL.md +43 -86
- package/.claude/skills/prd-check/SKILL.md +48 -0
- package/README.md +7 -1
- package/bin/ralph.sh +3 -0
- package/dist/loopgram/prd-generator.d.ts +2 -0
- package/dist/loopgram/prd-generator.d.ts.map +1 -1
- package/dist/loopgram/prd-generator.js +2 -0
- package/dist/loopgram/prd-generator.js.map +1 -1
- package/package.json +1 -1
- package/ralph/hooks/protect-prd.sh +1 -1
- package/ralph/loop.sh +37 -17
- package/ralph/prd-check.sh +171 -3
- package/ralph/prd.sh +9 -32
- package/ralph/setup.sh +19 -0
- package/templates/checks/prd/check-example.sh +25 -0
- package/templates/prd-example.json +45 -26
- package/.claude/commands/prd.md +0 -770
package/ralph/prd-check.sh
CHANGED
|
@@ -50,6 +50,11 @@
|
|
|
50
50
|
# - Has prerequisites array with DB reset command
|
|
51
51
|
# - Prevents infinite retries on schema mismatch errors
|
|
52
52
|
#
|
|
53
|
+
# CUSTOM CHECKS (.ralph/checks/prd/ or ~/.config/ralph/checks/prd/):
|
|
54
|
+
# - User-provided scripts that receive story JSON on stdin
|
|
55
|
+
# - Output issue descriptions to stdout (one per line)
|
|
56
|
+
# - Excluded from auto-fix (reported for manual review)
|
|
57
|
+
#
|
|
53
58
|
# API configuration validation:
|
|
54
59
|
# - If api.baseUrl configured, checks health endpoint is reachable
|
|
55
60
|
# - Warns if default /health returns 404 (misconfigured healthEndpoint)
|
|
@@ -75,6 +80,9 @@
|
|
|
75
80
|
# .tests.directory - Where tests live (for requireTests check)
|
|
76
81
|
# .api.baseUrl - API base URL (enables API config validation)
|
|
77
82
|
# .api.healthEndpoint - Health check path (default: /health, empty to disable)
|
|
83
|
+
# .ralph/checks/prd/check-* - Project-level custom checks (per-story)
|
|
84
|
+
# ~/.config/ralph/checks/prd/ - User-global custom checks (per-story)
|
|
85
|
+
# .checks.custom.<name> - Enable/disable individual custom checks
|
|
78
86
|
#
|
|
79
87
|
# ============================================================================
|
|
80
88
|
# USAGE
|
|
@@ -184,6 +192,33 @@ validate_prd() {
|
|
|
184
192
|
fi
|
|
185
193
|
fi
|
|
186
194
|
|
|
195
|
+
# Deprecation warnings for old root-level fields
|
|
196
|
+
local deprecated_fields=""
|
|
197
|
+
if jq -e '.techStack' "$prd_file" >/dev/null 2>&1; then
|
|
198
|
+
deprecated_fields+="techStack "
|
|
199
|
+
fi
|
|
200
|
+
if jq -e '.globalConstraints' "$prd_file" >/dev/null 2>&1; then
|
|
201
|
+
deprecated_fields+="globalConstraints "
|
|
202
|
+
fi
|
|
203
|
+
if jq -e '.originalContext' "$prd_file" >/dev/null 2>&1; then
|
|
204
|
+
deprecated_fields+="originalContext "
|
|
205
|
+
fi
|
|
206
|
+
if jq -e '.testing' "$prd_file" >/dev/null 2>&1; then
|
|
207
|
+
deprecated_fields+="testing "
|
|
208
|
+
fi
|
|
209
|
+
if jq -e '.architecture' "$prd_file" >/dev/null 2>&1; then
|
|
210
|
+
deprecated_fields+="architecture "
|
|
211
|
+
fi
|
|
212
|
+
if jq -e '.testUsers' "$prd_file" >/dev/null 2>&1; then
|
|
213
|
+
deprecated_fields+="testUsers "
|
|
214
|
+
fi
|
|
215
|
+
if [[ -n "$deprecated_fields" ]]; then
|
|
216
|
+
echo ""
|
|
217
|
+
print_warning "Found deprecated root-level fields: $deprecated_fields"
|
|
218
|
+
echo " These should be in each story instead. Regenerate with /prd."
|
|
219
|
+
echo ""
|
|
220
|
+
fi
|
|
221
|
+
|
|
187
222
|
# Validate API smoke test configuration
|
|
188
223
|
_validate_api_config "$config"
|
|
189
224
|
|
|
@@ -191,7 +226,8 @@ validate_prd() {
|
|
|
191
226
|
fix_hardcoded_paths "$prd_file" "$config"
|
|
192
227
|
|
|
193
228
|
# Validate and fix individual stories
|
|
194
|
-
|
|
229
|
+
# $2 is optional dry_run flag — when "true", skip auto-fix
|
|
230
|
+
_validate_and_fix_stories "$prd_file" "${2:-}" || return 1
|
|
195
231
|
|
|
196
232
|
return 0
|
|
197
233
|
}
|
|
@@ -274,8 +310,10 @@ _validate_api_config() {
|
|
|
274
310
|
}
|
|
275
311
|
|
|
276
312
|
# Validate individual stories and auto-fix with Claude if needed
|
|
313
|
+
# $1: prd_file $2: optional "dry_run" — when "true", report issues but skip auto-fix
|
|
277
314
|
_validate_and_fix_stories() {
|
|
278
315
|
local prd_file="$1"
|
|
316
|
+
local dry_run="${2:-false}"
|
|
279
317
|
local needs_fix=false
|
|
280
318
|
local issues=""
|
|
281
319
|
local story_count=0
|
|
@@ -285,9 +323,13 @@ _validate_and_fix_stories() {
|
|
|
285
323
|
local cnt_frontend_tsc=0 cnt_frontend_url=0 cnt_frontend_context=0 cnt_frontend_mcp=0
|
|
286
324
|
local cnt_auth_security=0 cnt_list_pagination=0 cnt_prose_steps=0
|
|
287
325
|
local cnt_migration_prereq=0 cnt_naming_convention=0 cnt_bare_pytest=0
|
|
326
|
+
local cnt_custom=0
|
|
288
327
|
|
|
289
328
|
echo " Checking test coverage..."
|
|
290
329
|
|
|
330
|
+
# Truncate custom check log per validation pass (name says "last", keep only current run)
|
|
331
|
+
: > "$RALPH_DIR/last_custom_check.log"
|
|
332
|
+
|
|
291
333
|
# Only validate incomplete stories (skip stories that already passed)
|
|
292
334
|
local story_ids
|
|
293
335
|
story_ids=$(jq -r '.stories[] | select(.passes != true) | .id' "$prd_file" 2>/dev/null)
|
|
@@ -429,12 +471,31 @@ _validate_and_fix_stories() {
|
|
|
429
471
|
fi
|
|
430
472
|
fi
|
|
431
473
|
|
|
474
|
+
# Snapshot built-in issues before custom checks append
|
|
475
|
+
local builtin_story_issues="$story_issues"
|
|
476
|
+
|
|
477
|
+
# Check 8: User-defined custom checks (.ralph/checks/prd/ or ~/.config/ralph/checks/prd/)
|
|
478
|
+
if [[ -d ".ralph/checks/prd" ]] || [[ -d "$HOME/.config/ralph/checks/prd" ]]; then
|
|
479
|
+
local story_json
|
|
480
|
+
story_json=$(jq --arg id "$story_id" '.stories[] | select(.id==$id)' "$prd_file")
|
|
481
|
+
local custom_output
|
|
482
|
+
custom_output=$(_run_custom_prd_checks "$story_id" "$prd_file" "$story_json")
|
|
483
|
+
if [[ -n "$custom_output" ]]; then
|
|
484
|
+
story_issues+="$custom_output"
|
|
485
|
+
cnt_custom=$((cnt_custom + 1))
|
|
486
|
+
fi
|
|
487
|
+
fi
|
|
488
|
+
|
|
432
489
|
# Track this story if it has issues
|
|
433
490
|
if [[ -n "$story_issues" ]]; then
|
|
434
491
|
needs_fix=true
|
|
435
492
|
story_count=$((story_count + 1))
|
|
436
|
-
issues
|
|
493
|
+
# Only include built-in issues in auto-fix context
|
|
494
|
+
# Custom issues are user-defined rules that Claude auto-fix can't meaningfully address
|
|
495
|
+
if [[ -n "$builtin_story_issues" ]]; then
|
|
496
|
+
issues+="$story_id: ${builtin_story_issues%%, }
|
|
437
497
|
"
|
|
498
|
+
fi
|
|
438
499
|
fi
|
|
439
500
|
done <<< "$story_ids"
|
|
440
501
|
|
|
@@ -456,6 +517,12 @@ _validate_and_fix_stories() {
|
|
|
456
517
|
[[ $cnt_migration_prereq -gt 0 ]] && echo " ${cnt_migration_prereq}x migration: add prerequisites (DB reset)"
|
|
457
518
|
[[ $cnt_naming_convention -gt 0 ]] && echo " ${cnt_naming_convention}x API consumer: add camelCase transformation note"
|
|
458
519
|
[[ $cnt_bare_pytest -gt 0 ]] && echo " ${cnt_bare_pytest}x use 'uv run pytest' not bare 'pytest'"
|
|
520
|
+
[[ $cnt_custom -gt 0 ]] && echo " ${cnt_custom} stories with custom check issues"
|
|
521
|
+
|
|
522
|
+
# Skip auto-fix in dry-run mode
|
|
523
|
+
if [[ "$dry_run" == "true" ]]; then
|
|
524
|
+
return 0
|
|
525
|
+
fi
|
|
459
526
|
|
|
460
527
|
# Check if Claude is available for auto-fix
|
|
461
528
|
if command -v claude &>/dev/null; then
|
|
@@ -471,6 +538,55 @@ _validate_and_fix_stories() {
|
|
|
471
538
|
return 0
|
|
472
539
|
}
|
|
473
540
|
|
|
541
|
+
# Run user-defined custom PRD checks for a story
|
|
542
|
+
# Stdin to script: full story JSON | $1: story_id | $2: prd_file
|
|
543
|
+
# Output: issues on stdout (empty = no issues)
|
|
544
|
+
_run_custom_prd_checks() {
|
|
545
|
+
local story_id="$1"
|
|
546
|
+
local prd_file="$2"
|
|
547
|
+
local story_json="$3"
|
|
548
|
+
local custom_issues=""
|
|
549
|
+
local custom_log="$RALPH_DIR/last_custom_check.log"
|
|
550
|
+
|
|
551
|
+
local check_dirs=()
|
|
552
|
+
[[ -d ".ralph/checks/prd" ]] && check_dirs+=(".ralph/checks/prd")
|
|
553
|
+
[[ -d "$HOME/.config/ralph/checks/prd" ]] && check_dirs+=("$HOME/.config/ralph/checks/prd")
|
|
554
|
+
[[ ${#check_dirs[@]} -eq 0 ]] && return 0
|
|
555
|
+
|
|
556
|
+
for check_dir in "${check_dirs[@]}"; do
|
|
557
|
+
for check_script in "$check_dir"/check-*; do
|
|
558
|
+
[[ ! -f "$check_script" || ! -x "$check_script" ]] && continue
|
|
559
|
+
|
|
560
|
+
local check_key
|
|
561
|
+
check_key=$(basename "$check_script")
|
|
562
|
+
check_key="${check_key%.*}"
|
|
563
|
+
# Read directly instead of get_config — jq's // operator treats false as falsy
|
|
564
|
+
local enabled="true"
|
|
565
|
+
if [[ -f "$RALPH_DIR/config.json" ]]; then
|
|
566
|
+
local raw
|
|
567
|
+
raw=$(jq -r --arg key "$check_key" '.checks.custom[$key]' "$RALPH_DIR/config.json" 2>/dev/null)
|
|
568
|
+
[[ -n "$raw" && "$raw" != "null" ]] && enabled="$raw"
|
|
569
|
+
fi
|
|
570
|
+
[[ "$enabled" == "false" ]] && continue
|
|
571
|
+
|
|
572
|
+
# Run check — capture stdout for issues, stderr to log for debugging
|
|
573
|
+
local output=""
|
|
574
|
+
if ! output=$(echo "$story_json" | run_with_timeout 30 "$check_script" "$story_id" "$prd_file" 2>>"$custom_log"); then
|
|
575
|
+
# Script failed to execute — warn, don't silently swallow
|
|
576
|
+
print_warning "Custom check '$check_key' failed for story $story_id (see .ralph/last_custom_check.log)"
|
|
577
|
+
fi
|
|
578
|
+
|
|
579
|
+
if [[ -n "$output" ]]; then
|
|
580
|
+
while IFS= read -r line; do
|
|
581
|
+
[[ -n "$line" ]] && custom_issues+="${line}, "
|
|
582
|
+
done <<< "$output"
|
|
583
|
+
fi
|
|
584
|
+
done
|
|
585
|
+
done
|
|
586
|
+
|
|
587
|
+
echo "$custom_issues"
|
|
588
|
+
}
|
|
589
|
+
|
|
474
590
|
# Optimize story test coverage using Claude
|
|
475
591
|
_fix_stories_with_claude() {
|
|
476
592
|
local prd_file="$1"
|
|
@@ -499,7 +615,7 @@ RULES:
|
|
|
499
615
|
2. Backend stories MUST have apiContract with endpoint, request, response
|
|
500
616
|
3. Frontend stories MUST have testUrl set to {config.urls.frontend}/[page-path]
|
|
501
617
|
- Derive page path from story title (e.g., 'login form' → '/login', 'dashboard' → '/dashboard')
|
|
502
|
-
4. Frontend stories MUST have contextFiles array (include idea file path
|
|
618
|
+
4. Frontend stories MUST have contextFiles array (include idea file path in each story's contextFiles)
|
|
503
619
|
5. Frontend stories MUST have mcp array with browser tools: [\"playwright\", \"devtools\"]
|
|
504
620
|
6. Auth stories MUST have security acceptanceCriteria:
|
|
505
621
|
- Passwords hashed with bcrypt (cost 10+)
|
|
@@ -512,6 +628,8 @@ RULES:
|
|
|
512
628
|
Example: \"prerequisites\": [{\"name\": \"Reset test DB\", \"command\": \"npm run db:reset:test\", \"when\": \"schema changes\"}]
|
|
513
629
|
9. Frontend/general stories that consume APIs MUST have notes about naming conventions:
|
|
514
630
|
Example: \"notes\": \"Transform API responses from snake_case to camelCase. Create typed interfaces with camelCase properties and map: const user = { userName: data.user_name }\"
|
|
631
|
+
10. Each story should include its own techStack and constraints fields. Do NOT add these at the PRD root level.
|
|
632
|
+
Move any root-level techStack, globalConstraints, originalContext, testing, architecture, or testUsers into the relevant stories.
|
|
515
633
|
|
|
516
634
|
CURRENT PRD:
|
|
517
635
|
$(cat "$prd_file")
|
|
@@ -674,3 +792,53 @@ validate_stories_quick() {
|
|
|
674
792
|
|
|
675
793
|
echo "$issues"
|
|
676
794
|
}
|
|
795
|
+
|
|
796
|
+
# CLI entry point for on-demand PRD validation
|
|
797
|
+
# Usage: ralph prd-check [--dry-run] [prd-file]
|
|
798
|
+
ralph_prd_check() {
|
|
799
|
+
local dry_run=false
|
|
800
|
+
local prd_file=""
|
|
801
|
+
|
|
802
|
+
while [[ $# -gt 0 ]]; do
|
|
803
|
+
case "$1" in
|
|
804
|
+
--dry-run) dry_run=true; shift ;;
|
|
805
|
+
*) prd_file="$1"; shift ;;
|
|
806
|
+
esac
|
|
807
|
+
done
|
|
808
|
+
|
|
809
|
+
prd_file="${prd_file:-$RALPH_DIR/prd.json}"
|
|
810
|
+
|
|
811
|
+
if [[ ! -f "$prd_file" ]]; then
|
|
812
|
+
print_error "PRD not found: $prd_file"
|
|
813
|
+
echo "Generate one with: ralph prd or /prd"
|
|
814
|
+
return 1
|
|
815
|
+
fi
|
|
816
|
+
|
|
817
|
+
echo ""
|
|
818
|
+
print_info "=== PRD Validation ==="
|
|
819
|
+
echo ""
|
|
820
|
+
|
|
821
|
+
if [[ "$dry_run" == "true" ]]; then
|
|
822
|
+
# Dry run: validate structure + show issues without auto-fix
|
|
823
|
+
validate_prd "$prd_file" "true"
|
|
824
|
+
local rc=$?
|
|
825
|
+
local remaining
|
|
826
|
+
remaining=$(validate_stories_quick "$prd_file")
|
|
827
|
+
if [[ -n "$remaining" ]]; then
|
|
828
|
+
echo ""
|
|
829
|
+
echo " Remaining issues:"
|
|
830
|
+
echo "$remaining" | sed 's/^/ /'
|
|
831
|
+
fi
|
|
832
|
+
return $rc
|
|
833
|
+
else
|
|
834
|
+
if validate_prd "$prd_file"; then
|
|
835
|
+
echo ""
|
|
836
|
+
print_success "PRD validation passed!"
|
|
837
|
+
return 0
|
|
838
|
+
else
|
|
839
|
+
echo ""
|
|
840
|
+
print_error "PRD validation failed"
|
|
841
|
+
return 1
|
|
842
|
+
fi
|
|
843
|
+
fi
|
|
844
|
+
}
|
package/ralph/prd.sh
CHANGED
|
@@ -96,43 +96,18 @@ ralph_prd() {
|
|
|
96
96
|
printf '%s\n' ' "branch": "feature/feature-name",' >> "$prompt_file"
|
|
97
97
|
printf '%s\n' ' "status": "pending"' >> "$prompt_file"
|
|
98
98
|
printf '%s\n' ' },' >> "$prompt_file"
|
|
99
|
-
printf '%s\n' ' "originalContext": "PASTE THE FULL ORIGINAL NOTES/DESCRIPTION HERE - this preserves intent for Claude during implementation",' >> "$prompt_file"
|
|
100
99
|
printf '%s\n' ' "metadata": {' >> "$prompt_file"
|
|
101
|
-
printf '%s\n' ' "
|
|
102
|
-
printf '%s\n' ' "scaling": ["list of scaling concerns"],' >> "$prompt_file"
|
|
103
|
-
printf '%s\n' ' "relatedFeatures": ["list of related features"],' >> "$prompt_file"
|
|
100
|
+
printf '%s\n' ' "createdAt": "ISO timestamp",' >> "$prompt_file"
|
|
104
101
|
printf '%s\n' ' "estimatedStories": 5,' >> "$prompt_file"
|
|
105
102
|
printf '%s\n' ' "complexity": "low|medium|high"' >> "$prompt_file"
|
|
106
103
|
printf '%s\n' ' },' >> "$prompt_file"
|
|
107
|
-
printf '%s\n' ' "scalability": {' >> "$prompt_file"
|
|
108
|
-
printf '%s\n' ' "expectedScale": "100s | 1000s | 10000s+ users",' >> "$prompt_file"
|
|
109
|
-
printf '%s\n' ' "pagination": "cursor | offset | none",' >> "$prompt_file"
|
|
110
|
-
printf '%s\n' ' "caching": "none | in-memory | redis",' >> "$prompt_file"
|
|
111
|
-
printf '%s\n' ' "rateLimiting": "if needed, requests per minute"' >> "$prompt_file"
|
|
112
|
-
printf '%s\n' ' },' >> "$prompt_file"
|
|
113
|
-
printf '%s\n' ' "architecture": {' >> "$prompt_file"
|
|
114
|
-
printf '%s\n' ' "directories": {' >> "$prompt_file"
|
|
115
|
-
printf '%s\n' ' "components": "src/components/{feature}/",' >> "$prompt_file"
|
|
116
|
-
printf '%s\n' ' "api": "src/api/",' >> "$prompt_file"
|
|
117
|
-
printf '%s\n' ' "types": "src/types/",' >> "$prompt_file"
|
|
118
|
-
printf '%s\n' ' "scripts": "scripts/",' >> "$prompt_file"
|
|
119
|
-
printf '%s\n' ' "docs": "docs/"' >> "$prompt_file"
|
|
120
|
-
printf '%s\n' ' },' >> "$prompt_file"
|
|
121
|
-
printf '%s\n' ' "patterns": {' >> "$prompt_file"
|
|
122
|
-
printf '%s\n' ' "reuse": ["existing components/utils to use"],' >> "$prompt_file"
|
|
123
|
-
printf '%s\n' ' "follow": ["existing patterns to match"]' >> "$prompt_file"
|
|
124
|
-
printf '%s\n' ' },' >> "$prompt_file"
|
|
125
|
-
printf '%s\n' ' "principles": {' >> "$prompt_file"
|
|
126
|
-
printf '%s\n' ' "maxFileLines": 300,' >> "$prompt_file"
|
|
127
|
-
printf '%s\n' ' "singleResponsibility": true' >> "$prompt_file"
|
|
128
|
-
printf '%s\n' ' },' >> "$prompt_file"
|
|
129
|
-
printf '%s\n' ' "doNotCreate": ["things that already exist"]' >> "$prompt_file"
|
|
130
|
-
printf '%s\n' ' },' >> "$prompt_file"
|
|
131
104
|
printf '%s\n' ' "stories": [' >> "$prompt_file"
|
|
132
105
|
printf '%s\n' ' {' >> "$prompt_file"
|
|
133
106
|
printf '%s\n' ' "id": "TASK-001",' >> "$prompt_file"
|
|
134
107
|
printf '%s\n' ' "title": "Story title",' >> "$prompt_file"
|
|
135
108
|
printf '%s\n' ' "passes": false,' >> "$prompt_file"
|
|
109
|
+
printf '%s\n' ' "techStack": {"backend": "detected tech", "frontend": "detected tech"},' >> "$prompt_file"
|
|
110
|
+
printf '%s\n' ' "constraints": ["relevant rules for this story"],' >> "$prompt_file"
|
|
136
111
|
printf '%s\n' " \"testUrl\": \"${test_url_base}/path/to/test\"," >> "$prompt_file"
|
|
137
112
|
printf '%s\n' ' "files": {' >> "$prompt_file"
|
|
138
113
|
printf '%s\n' ' "create": ["paths to new files"],' >> "$prompt_file"
|
|
@@ -142,6 +117,7 @@ ralph_prd() {
|
|
|
142
117
|
printf '%s\n' ' "acceptanceCriteria": ["AC 1", "AC 2"],' >> "$prompt_file"
|
|
143
118
|
printf '%s\n' ' "errorHandling": ["what happens on failure"],' >> "$prompt_file"
|
|
144
119
|
printf '%s\n' ' "testSteps": ["executable shell commands only"],' >> "$prompt_file"
|
|
120
|
+
printf '%s\n' ' "contextFiles": ["docs/ideas/feature.md"],' >> "$prompt_file"
|
|
145
121
|
printf '%s\n' ' "notes": ""' >> "$prompt_file"
|
|
146
122
|
printf '%s\n' ' }' >> "$prompt_file"
|
|
147
123
|
printf '%s\n' ' ]' >> "$prompt_file"
|
|
@@ -186,10 +162,11 @@ ralph_prd() {
|
|
|
186
162
|
printf '%s\n' '- If complexity is "high" AND > 5 stories, MUST split' >> "$prompt_file"
|
|
187
163
|
printf '%s\n' "- Each story should be completable in one Claude session (~10 min)" >> "$prompt_file"
|
|
188
164
|
printf '\n%s\n' "If splitting is needed, generate ONLY phase 1 and note deferred items." >> "$prompt_file"
|
|
189
|
-
printf '\n%s\n' "##
|
|
190
|
-
printf '\n%s\n' "
|
|
191
|
-
printf '%s\n' "
|
|
192
|
-
printf '%s\n' "
|
|
165
|
+
printf '\n%s\n' "## Context in Stories - IMPORTANT" >> "$prompt_file"
|
|
166
|
+
printf '\n%s\n' "Each story must be self-contained. Include the idea file path in each story's 'contextFiles' array." >> "$prompt_file"
|
|
167
|
+
printf '%s\n' "Include 'techStack' with the relevant subset of detected technologies for that story." >> "$prompt_file"
|
|
168
|
+
printf '%s\n' "Include 'constraints' with any rules that apply to that specific story." >> "$prompt_file"
|
|
169
|
+
printf '%s\n' "Do NOT put techStack, constraints, or context at the PRD root level." >> "$prompt_file"
|
|
193
170
|
printf '\n%s\n' "## Output" >> "$prompt_file"
|
|
194
171
|
printf '\n%s\n' "After questions are answered, output the prd.json content between these exact markers:" >> "$prompt_file"
|
|
195
172
|
printf '%s\n' "--- BEGIN PRD.JSON ---" >> "$prompt_file"
|
package/ralph/setup.sh
CHANGED
|
@@ -94,6 +94,7 @@ ralph_setup() {
|
|
|
94
94
|
|
|
95
95
|
# Run all setup steps
|
|
96
96
|
setup_ralph_dir "$pkg_root"
|
|
97
|
+
setup_custom_checks
|
|
97
98
|
setup_gitignore
|
|
98
99
|
setup_claude_hooks "$pkg_root"
|
|
99
100
|
setup_slash_commands "$pkg_root"
|
|
@@ -247,6 +248,24 @@ setup_ralph_dir() {
|
|
|
247
248
|
fi
|
|
248
249
|
}
|
|
249
250
|
|
|
251
|
+
# Set up custom PRD checks directory
|
|
252
|
+
setup_custom_checks() {
|
|
253
|
+
local checks_dir=".ralph/checks/prd"
|
|
254
|
+
|
|
255
|
+
# Skip if directory already exists
|
|
256
|
+
if [[ -d "$checks_dir" ]]; then
|
|
257
|
+
local count
|
|
258
|
+
count=$(ls -1 "$checks_dir"/check-* 2>/dev/null | wc -l | tr -d ' ')
|
|
259
|
+
if [[ "$count" -gt 0 ]]; then
|
|
260
|
+
echo " Custom PRD checks: $count script(s) in $checks_dir/"
|
|
261
|
+
fi
|
|
262
|
+
return 0
|
|
263
|
+
fi
|
|
264
|
+
|
|
265
|
+
mkdir -p "$checks_dir"
|
|
266
|
+
echo " Created $checks_dir/ (add custom check scripts here)"
|
|
267
|
+
}
|
|
268
|
+
|
|
250
269
|
# Ensure .gitignore has necessary patterns
|
|
251
270
|
setup_gitignore() {
|
|
252
271
|
local patterns=(
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# shellcheck shell=bash
|
|
3
|
+
#
|
|
4
|
+
# Example custom PRD check for Ralph
|
|
5
|
+
#
|
|
6
|
+
# Place in: .ralph/checks/prd/ (project) or ~/.config/ralph/checks/prd/ (global)
|
|
7
|
+
# Must be executable: chmod +x check-example.sh
|
|
8
|
+
#
|
|
9
|
+
# Interface:
|
|
10
|
+
# stdin = story JSON object
|
|
11
|
+
# $1 = story ID
|
|
12
|
+
# $2 = PRD file path
|
|
13
|
+
# stdout = issue descriptions, one per line (empty = pass)
|
|
14
|
+
#
|
|
15
|
+
# Disable: {"checks": {"custom": {"check-example": false}}}
|
|
16
|
+
|
|
17
|
+
# story_id="$1" # available if you need the story ID
|
|
18
|
+
# prd_file="$2" # available if you need full PRD context
|
|
19
|
+
story_json=$(cat)
|
|
20
|
+
|
|
21
|
+
# Example: require all stories to have a description field
|
|
22
|
+
has_description=$(echo "$story_json" | jq -r '.description // empty')
|
|
23
|
+
if [[ -z "$has_description" ]]; then
|
|
24
|
+
echo "missing description field"
|
|
25
|
+
fi
|
|
@@ -6,34 +6,9 @@
|
|
|
6
6
|
"status": "pending"
|
|
7
7
|
},
|
|
8
8
|
|
|
9
|
-
"originalContext": "docs/ideas/auth.md",
|
|
10
|
-
|
|
11
|
-
"techStack": {
|
|
12
|
-
"frontend": "React",
|
|
13
|
-
"backend": "Node.js",
|
|
14
|
-
"database": "PostgreSQL"
|
|
15
|
-
},
|
|
16
|
-
|
|
17
|
-
"testing": {
|
|
18
|
-
"approach": "TDD",
|
|
19
|
-
"unit": {
|
|
20
|
-
"frontend": "vitest",
|
|
21
|
-
"backend": "jest"
|
|
22
|
-
},
|
|
23
|
-
"integration": "playwright",
|
|
24
|
-
"e2e": "playwright"
|
|
25
|
-
},
|
|
26
|
-
|
|
27
|
-
"globalConstraints": [
|
|
28
|
-
"All API calls must have error handling",
|
|
29
|
-
"Use existing UI components from src/components/ui",
|
|
30
|
-
"Never store passwords in plain text",
|
|
31
|
-
"Sanitize all user input before database operations"
|
|
32
|
-
],
|
|
33
|
-
|
|
34
9
|
"metadata": {
|
|
35
10
|
"createdAt": "2026-01-27T10:00:00Z",
|
|
36
|
-
"estimatedStories":
|
|
11
|
+
"estimatedStories": 3,
|
|
37
12
|
"complexity": "medium"
|
|
38
13
|
},
|
|
39
14
|
|
|
@@ -45,6 +20,17 @@
|
|
|
45
20
|
"priority": 1,
|
|
46
21
|
"passes": false,
|
|
47
22
|
|
|
23
|
+
"techStack": {
|
|
24
|
+
"backend": "Node.js",
|
|
25
|
+
"database": "PostgreSQL"
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
"constraints": [
|
|
29
|
+
"All API calls must have error handling",
|
|
30
|
+
"Never store passwords in plain text",
|
|
31
|
+
"Sanitize all user input before database operations"
|
|
32
|
+
],
|
|
33
|
+
|
|
48
34
|
"files": {
|
|
49
35
|
"create": ["src/api/users.ts", "src/api/users.test.ts"],
|
|
50
36
|
"modify": ["src/api/index.ts"],
|
|
@@ -69,6 +55,7 @@
|
|
|
69
55
|
"testing": {
|
|
70
56
|
"types": ["unit", "integration"],
|
|
71
57
|
"approach": "TDD",
|
|
58
|
+
"runner": "jest",
|
|
72
59
|
"files": {
|
|
73
60
|
"unit": ["src/api/users.test.ts"]
|
|
74
61
|
}
|
|
@@ -86,6 +73,15 @@
|
|
|
86
73
|
"response": {"id": "string", "email": "string"}
|
|
87
74
|
},
|
|
88
75
|
|
|
76
|
+
"testUsers": {
|
|
77
|
+
"admin": {"email": "admin@test.com", "password": "test123"},
|
|
78
|
+
"user": {"email": "user@test.com", "password": "test123"}
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
"contextFiles": [
|
|
82
|
+
"docs/ideas/auth.md"
|
|
83
|
+
],
|
|
84
|
+
|
|
89
85
|
"notes": "SECURITY: Use bcrypt with cost 10+. Never log passwords. Validate email format server-side even if validated client-side.",
|
|
90
86
|
"dependsOn": []
|
|
91
87
|
},
|
|
@@ -96,6 +92,14 @@
|
|
|
96
92
|
"priority": 2,
|
|
97
93
|
"passes": false,
|
|
98
94
|
|
|
95
|
+
"techStack": {
|
|
96
|
+
"frontend": "React"
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
"constraints": [
|
|
100
|
+
"Use existing UI components from src/components/ui"
|
|
101
|
+
],
|
|
102
|
+
|
|
99
103
|
"files": {
|
|
100
104
|
"create": ["src/components/RegisterForm.tsx", "src/components/RegisterForm.test.tsx"],
|
|
101
105
|
"modify": ["src/pages/index.tsx"],
|
|
@@ -122,6 +126,7 @@
|
|
|
122
126
|
"testing": {
|
|
123
127
|
"types": ["unit", "e2e"],
|
|
124
128
|
"approach": "TDD",
|
|
129
|
+
"runner": "vitest",
|
|
125
130
|
"files": {
|
|
126
131
|
"unit": ["src/components/RegisterForm.test.tsx"],
|
|
127
132
|
"e2e": ["tests/e2e/register.spec.ts"]
|
|
@@ -152,6 +157,15 @@
|
|
|
152
157
|
"priority": 3,
|
|
153
158
|
"passes": false,
|
|
154
159
|
|
|
160
|
+
"techStack": {
|
|
161
|
+
"backend": "Node.js",
|
|
162
|
+
"database": "PostgreSQL"
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
"constraints": [
|
|
166
|
+
"All API calls must have error handling"
|
|
167
|
+
],
|
|
168
|
+
|
|
155
169
|
"files": {
|
|
156
170
|
"create": [],
|
|
157
171
|
"modify": ["src/api/users.ts"],
|
|
@@ -176,6 +190,7 @@
|
|
|
176
190
|
"testing": {
|
|
177
191
|
"types": ["integration"],
|
|
178
192
|
"approach": "TDD",
|
|
193
|
+
"runner": "jest",
|
|
179
194
|
"files": {}
|
|
180
195
|
},
|
|
181
196
|
|
|
@@ -190,6 +205,10 @@
|
|
|
190
205
|
"response": {"data": "User[]", "total": "number", "page": "number", "limit": "number"}
|
|
191
206
|
},
|
|
192
207
|
|
|
208
|
+
"contextFiles": [
|
|
209
|
+
"docs/ideas/auth.md"
|
|
210
|
+
],
|
|
211
|
+
|
|
193
212
|
"notes": "SCALE: Always paginate list endpoints. Enforce max limit to prevent memory issues. Add database index for sort column.",
|
|
194
213
|
"dependsOn": ["TASK-001"]
|
|
195
214
|
}
|