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.
Files changed (162) hide show
  1. package/.claude/commands/explain.md +114 -0
  2. package/.claude/commands/idea.md +398 -0
  3. package/.claude/commands/my-dna.md +122 -0
  4. package/.claude/commands/prd.md +286 -0
  5. package/.claude/commands/review.md +167 -0
  6. package/.claude/commands/sign.md +32 -0
  7. package/.claude/commands/styleguide.md +450 -0
  8. package/.claude/commands/tour.md +301 -0
  9. package/.claude/commands/vibe-check.md +116 -0
  10. package/.claude/commands/vibe-help.md +47 -0
  11. package/.claude/commands/vibe-list.md +203 -0
  12. package/.pre-commit-hooks.yaml +102 -0
  13. package/LICENSE +21 -0
  14. package/README.md +238 -0
  15. package/bin/agentic-loop.sh +24 -0
  16. package/bin/postinstall.sh +29 -0
  17. package/bin/ralph.sh +171 -0
  18. package/bin/vibe-check.js +19 -0
  19. package/dist/checks/check-any-types.d.ts +6 -0
  20. package/dist/checks/check-any-types.d.ts.map +1 -0
  21. package/dist/checks/check-any-types.js +73 -0
  22. package/dist/checks/check-any-types.js.map +1 -0
  23. package/dist/checks/check-commented-code.d.ts +6 -0
  24. package/dist/checks/check-commented-code.d.ts.map +1 -0
  25. package/dist/checks/check-commented-code.js +81 -0
  26. package/dist/checks/check-commented-code.js.map +1 -0
  27. package/dist/checks/check-console-error.d.ts +6 -0
  28. package/dist/checks/check-console-error.d.ts.map +1 -0
  29. package/dist/checks/check-console-error.js +41 -0
  30. package/dist/checks/check-console-error.js.map +1 -0
  31. package/dist/checks/check-debug-statements.d.ts +6 -0
  32. package/dist/checks/check-debug-statements.d.ts.map +1 -0
  33. package/dist/checks/check-debug-statements.js +120 -0
  34. package/dist/checks/check-debug-statements.js.map +1 -0
  35. package/dist/checks/check-deep-nesting.d.ts +6 -0
  36. package/dist/checks/check-deep-nesting.d.ts.map +1 -0
  37. package/dist/checks/check-deep-nesting.js +116 -0
  38. package/dist/checks/check-deep-nesting.js.map +1 -0
  39. package/dist/checks/check-docker-platform.d.ts +6 -0
  40. package/dist/checks/check-docker-platform.d.ts.map +1 -0
  41. package/dist/checks/check-docker-platform.js +42 -0
  42. package/dist/checks/check-docker-platform.js.map +1 -0
  43. package/dist/checks/check-dry-violations.d.ts +6 -0
  44. package/dist/checks/check-dry-violations.d.ts.map +1 -0
  45. package/dist/checks/check-dry-violations.js +124 -0
  46. package/dist/checks/check-dry-violations.js.map +1 -0
  47. package/dist/checks/check-empty-catch.d.ts +6 -0
  48. package/dist/checks/check-empty-catch.d.ts.map +1 -0
  49. package/dist/checks/check-empty-catch.js +111 -0
  50. package/dist/checks/check-empty-catch.js.map +1 -0
  51. package/dist/checks/check-function-length.d.ts +6 -0
  52. package/dist/checks/check-function-length.d.ts.map +1 -0
  53. package/dist/checks/check-function-length.js +152 -0
  54. package/dist/checks/check-function-length.js.map +1 -0
  55. package/dist/checks/check-hardcoded-ai-models.d.ts +10 -0
  56. package/dist/checks/check-hardcoded-ai-models.d.ts.map +1 -0
  57. package/dist/checks/check-hardcoded-ai-models.js +102 -0
  58. package/dist/checks/check-hardcoded-ai-models.js.map +1 -0
  59. package/dist/checks/check-hardcoded-urls.d.ts +6 -0
  60. package/dist/checks/check-hardcoded-urls.d.ts.map +1 -0
  61. package/dist/checks/check-hardcoded-urls.js +124 -0
  62. package/dist/checks/check-hardcoded-urls.js.map +1 -0
  63. package/dist/checks/check-magic-numbers.d.ts +6 -0
  64. package/dist/checks/check-magic-numbers.d.ts.map +1 -0
  65. package/dist/checks/check-magic-numbers.js +116 -0
  66. package/dist/checks/check-magic-numbers.js.map +1 -0
  67. package/dist/checks/check-secrets.d.ts +6 -0
  68. package/dist/checks/check-secrets.d.ts.map +1 -0
  69. package/dist/checks/check-secrets.js +138 -0
  70. package/dist/checks/check-secrets.js.map +1 -0
  71. package/dist/checks/check-snake-case-ts.d.ts +6 -0
  72. package/dist/checks/check-snake-case-ts.d.ts.map +1 -0
  73. package/dist/checks/check-snake-case-ts.js +78 -0
  74. package/dist/checks/check-snake-case-ts.js.map +1 -0
  75. package/dist/checks/check-todo-fixme.d.ts +6 -0
  76. package/dist/checks/check-todo-fixme.d.ts.map +1 -0
  77. package/dist/checks/check-todo-fixme.js +41 -0
  78. package/dist/checks/check-todo-fixme.js.map +1 -0
  79. package/dist/checks/check-unsafe-html.d.ts +6 -0
  80. package/dist/checks/check-unsafe-html.d.ts.map +1 -0
  81. package/dist/checks/check-unsafe-html.js +101 -0
  82. package/dist/checks/check-unsafe-html.js.map +1 -0
  83. package/dist/checks/index.d.ts +30 -0
  84. package/dist/checks/index.d.ts.map +1 -0
  85. package/dist/checks/index.js +57 -0
  86. package/dist/checks/index.js.map +1 -0
  87. package/dist/cli.d.ts +13 -0
  88. package/dist/cli.d.ts.map +1 -0
  89. package/dist/cli.js +208 -0
  90. package/dist/cli.js.map +1 -0
  91. package/dist/index.d.ts +9 -0
  92. package/dist/index.d.ts.map +1 -0
  93. package/dist/index.js +10 -0
  94. package/dist/index.js.map +1 -0
  95. package/dist/utils/file-reader.d.ts +24 -0
  96. package/dist/utils/file-reader.d.ts.map +1 -0
  97. package/dist/utils/file-reader.js +146 -0
  98. package/dist/utils/file-reader.js.map +1 -0
  99. package/dist/utils/patterns.d.ts +27 -0
  100. package/dist/utils/patterns.d.ts.map +1 -0
  101. package/dist/utils/patterns.js +84 -0
  102. package/dist/utils/patterns.js.map +1 -0
  103. package/dist/utils/reporters.d.ts +21 -0
  104. package/dist/utils/reporters.d.ts.map +1 -0
  105. package/dist/utils/reporters.js +115 -0
  106. package/dist/utils/reporters.js.map +1 -0
  107. package/dist/utils/types.d.ts +71 -0
  108. package/dist/utils/types.d.ts.map +1 -0
  109. package/dist/utils/types.js +5 -0
  110. package/dist/utils/types.js.map +1 -0
  111. package/package.json +83 -0
  112. package/ralph/api.sh +216 -0
  113. package/ralph/backup.sh +838 -0
  114. package/ralph/browser-verify/README.md +135 -0
  115. package/ralph/browser-verify/verify.ts +450 -0
  116. package/ralph/checks/check-fastapi-responses.py +155 -0
  117. package/ralph/hooks/hooks-config.json +72 -0
  118. package/ralph/hooks/inject-context.sh +44 -0
  119. package/ralph/hooks/install.sh +207 -0
  120. package/ralph/hooks/log-tools.sh +45 -0
  121. package/ralph/hooks/protect-prd.sh +27 -0
  122. package/ralph/hooks/save-learnings.sh +36 -0
  123. package/ralph/hooks/warn-debug.sh +54 -0
  124. package/ralph/hooks/warn-empty-catch.sh +63 -0
  125. package/ralph/hooks/warn-secrets.sh +89 -0
  126. package/ralph/hooks/warn-urls.sh +77 -0
  127. package/ralph/init.sh +515 -0
  128. package/ralph/loop.sh +730 -0
  129. package/ralph/playwright.sh +238 -0
  130. package/ralph/prd.sh +295 -0
  131. package/ralph/setup/feature-tour.sh +155 -0
  132. package/ralph/setup/quick-setup.sh +239 -0
  133. package/ralph/setup/tutorial.sh +159 -0
  134. package/ralph/setup/ui.sh +136 -0
  135. package/ralph/setup.sh +401 -0
  136. package/ralph/signs.sh +150 -0
  137. package/ralph/utils.sh +682 -0
  138. package/ralph/verify/browser.sh +324 -0
  139. package/ralph/verify/lint.sh +363 -0
  140. package/ralph/verify/review.sh +152 -0
  141. package/ralph/verify/tests.sh +81 -0
  142. package/ralph/verify.sh +268 -0
  143. package/templates/PROMPT.md +235 -0
  144. package/templates/config/fullstack.json +86 -0
  145. package/templates/config/go.json +81 -0
  146. package/templates/config/minimal.json +76 -0
  147. package/templates/config/node.json +81 -0
  148. package/templates/config/python.json +81 -0
  149. package/templates/config/rust.json +81 -0
  150. package/templates/examples/CLAUDE-django.md +174 -0
  151. package/templates/examples/CLAUDE-fastapi.md +270 -0
  152. package/templates/examples/CLAUDE-fastmcp.md +352 -0
  153. package/templates/examples/CLAUDE-fullstack.md +256 -0
  154. package/templates/examples/CLAUDE-node.md +246 -0
  155. package/templates/examples/CLAUDE-react.md +138 -0
  156. package/templates/optional/cursorrules.template +147 -0
  157. package/templates/optional/eslint.config.js +34 -0
  158. package/templates/optional/lint-staged.config.js +34 -0
  159. package/templates/optional/ruff.toml +125 -0
  160. package/templates/optional/vibe-check.yml +116 -0
  161. package/templates/optional/vscode-settings.json +127 -0
  162. package/templates/signs.json +46 -0
package/ralph/loop.sh ADDED
@@ -0,0 +1,730 @@
1
+ #!/usr/bin/env bash
2
+ # shellcheck shell=bash
3
+ # loop.sh - The autonomous development loop
4
+
5
+ # Pre-flight checks to catch common issues before wasting iterations
6
+ preflight_checks() {
7
+ echo "--- Pre-flight Checks ---"
8
+ local warnings=0
9
+
10
+ # Check API connectivity if configured
11
+ local api_url
12
+ api_url=$(get_config '.api.baseUrl' "")
13
+ if [[ -n "$api_url" ]]; then
14
+ printf " API connectivity ($api_url)... "
15
+ if curl -sf --connect-timeout 5 "$api_url" >/dev/null 2>&1 || \
16
+ curl -sf --connect-timeout 5 "${api_url}/health" >/dev/null 2>&1 || \
17
+ curl -sf --connect-timeout 5 "${api_url}/api/health" >/dev/null 2>&1; then
18
+ print_success "ok"
19
+ else
20
+ print_warning "unreachable"
21
+ echo " Is your API server running?"
22
+ ((warnings++))
23
+ fi
24
+ fi
25
+
26
+ # Check frontend connectivity if configured
27
+ local test_url
28
+ test_url=$(get_config '.testUrlBase' "")
29
+ if [[ -n "$test_url" ]]; then
30
+ printf " Frontend connectivity ($test_url)... "
31
+ if curl -sf --connect-timeout 5 "$test_url" >/dev/null 2>&1; then
32
+ print_success "ok"
33
+ else
34
+ print_warning "unreachable"
35
+ echo " Is your frontend dev server running?"
36
+ ((warnings++))
37
+ fi
38
+ fi
39
+
40
+ # Check for common migration issues in Python projects
41
+ local backend_dir
42
+ backend_dir=$(get_config '.directories.backend' "")
43
+ if [[ -n "$backend_dir" && -d "$backend_dir" ]]; then
44
+ # Check for alembic migrations
45
+ if [[ -d "$backend_dir/alembic" ]] || [[ -d "$backend_dir/migrations" ]]; then
46
+ printf " Database migrations... "
47
+ # Detect Python runner
48
+ local py_runner="python"
49
+ if [[ -f "$backend_dir/uv.lock" ]]; then
50
+ py_runner="uv run"
51
+ elif [[ -f "$backend_dir/poetry.lock" ]]; then
52
+ py_runner="poetry run"
53
+ fi
54
+ # Try to verify DB connection via alembic
55
+ if [[ -f "$backend_dir/alembic.ini" ]]; then
56
+ if (cd "$backend_dir" && $py_runner alembic current >/dev/null 2>&1); then
57
+ print_success "ok"
58
+ elif docker compose exec -T api alembic current >/dev/null 2>&1; then
59
+ # Try via Docker if local fails
60
+ print_success "ok (via docker)"
61
+ else
62
+ print_warning "check DB connection"
63
+ echo " Run: cd $backend_dir && $py_runner alembic current"
64
+ ((warnings++))
65
+ fi
66
+ fi
67
+ fi
68
+ fi
69
+
70
+ # Check Docker if docker-compose exists
71
+ for compose_file in "docker-compose.yml" "docker-compose.yaml" "compose.yml"; do
72
+ if [[ -f "$compose_file" ]]; then
73
+ printf " Docker services... "
74
+ if docker compose ps --quiet 2>/dev/null | grep -q .; then
75
+ print_success "running"
76
+ elif docker-compose ps --quiet 2>/dev/null | grep -q .; then
77
+ print_success "running"
78
+ else
79
+ print_warning "not running"
80
+ echo " Run: docker compose up -d"
81
+ ((warnings++))
82
+ fi
83
+ break
84
+ fi
85
+ done
86
+
87
+ echo ""
88
+ if [[ $warnings -gt 0 ]]; then
89
+ print_warning "$warnings pre-flight warning(s) - loop may fail on connectivity issues"
90
+ echo ""
91
+ read -r -p "Continue anyway? [Y/n] " response
92
+ if [[ "$response" =~ ^[Nn] ]]; then
93
+ echo "Aborted. Fix the issues and try again."
94
+ exit 1
95
+ fi
96
+ fi
97
+ }
98
+
99
+ run_loop() {
100
+ local max_iterations="$DEFAULT_MAX_ITERATIONS"
101
+ local specific_story=""
102
+ local fast_mode=false
103
+
104
+ # Parse arguments
105
+ while [[ $# -gt 0 ]]; do
106
+ case "$1" in
107
+ --max)
108
+ max_iterations="$2"
109
+ shift 2
110
+ ;;
111
+ --story)
112
+ specific_story="$2"
113
+ shift 2
114
+ ;;
115
+ --fast)
116
+ fast_mode=true
117
+ shift
118
+ ;;
119
+ *)
120
+ shift
121
+ ;;
122
+ esac
123
+ done
124
+
125
+ # Export for use in verification
126
+ export RALPH_FAST_MODE="$fast_mode"
127
+
128
+ # Validate prerequisites
129
+ check_dependencies
130
+
131
+ # Pre-flight checks to catch issues before wasting iterations
132
+ preflight_checks
133
+
134
+ if [[ ! -f "$RALPH_DIR/prd.json" ]]; then
135
+ # Check for misplaced PRD in subdirectories
136
+ local found_prd
137
+ found_prd=$(find . -path "./.ralph" -prune -o -name "prd.json" -path "*/.ralph/prd.json" -print 2>/dev/null | head -1)
138
+
139
+ if [[ -n "$found_prd" ]]; then
140
+ print_warning "PRD found in wrong location: $found_prd"
141
+ echo ""
142
+ echo "PRD should be at root: .ralph/prd.json"
143
+ echo ""
144
+ read -r -p "Move it to root? [Y/n] " response
145
+ if [[ "$response" =~ ^[Nn] ]]; then
146
+ echo "Aborted. Move it manually:"
147
+ echo " mv $found_prd .ralph/prd.json"
148
+ exit 1
149
+ fi
150
+ mkdir -p "$RALPH_DIR"
151
+ mv "$found_prd" "$RALPH_DIR/prd.json"
152
+ print_success "Moved PRD to .ralph/prd.json"
153
+ echo ""
154
+ else
155
+ print_error "No PRD found."
156
+ echo ""
157
+ echo "Create one with:"
158
+ echo " /idea 'your feature description' # thorough (recommended)"
159
+ echo " ralph prd 'your feature' # quick"
160
+ echo ""
161
+ exit 1
162
+ fi
163
+ fi
164
+
165
+ if [[ ! -f "$PROMPT_FILE" ]]; then
166
+ print_error "PROMPT.md not found."
167
+ echo ""
168
+ echo "Create it with: ralph init"
169
+ echo ""
170
+ exit 1
171
+ fi
172
+
173
+ # Validate PRD structure
174
+ if ! validate_prd "$RALPH_DIR/prd.json"; then
175
+ return 1
176
+ fi
177
+
178
+ local iteration=0
179
+ local last_story=""
180
+ local consecutive_failures=0
181
+ local max_story_retries=5
182
+ local total_attempts=0
183
+ local skipped_stories=()
184
+ local start_time
185
+ start_time=$(date +%s)
186
+
187
+ while [[ $iteration -lt $max_iterations ]]; do
188
+ # Check for stop signal
189
+ if [[ -f "$RALPH_DIR/.stop" ]]; then
190
+ rm -f "$RALPH_DIR/.stop"
191
+ print_warning "Stop signal received. Exiting gracefully."
192
+ local passed failed
193
+ passed=$(jq '[.stories[] | select(.passes==true)] | length' "$RALPH_DIR/prd.json" 2>/dev/null || echo "0")
194
+ failed=$(jq '[.stories[] | select(.passes==false)] | length' "$RALPH_DIR/prd.json" 2>/dev/null || echo "0")
195
+ send_notification "🛑 Ralph stopped: $passed passed, $failed remaining"
196
+ return 0
197
+ fi
198
+
199
+ ((iteration++))
200
+ echo ""
201
+ print_info "=== Iteration $iteration/$max_iterations ==="
202
+ echo ""
203
+
204
+ # 1. Get next incomplete story
205
+ local story
206
+ if [[ -n "$specific_story" ]]; then
207
+ story="$specific_story"
208
+ # Verify it exists
209
+ local exists
210
+ exists=$(jq -r --arg id "$story" '.stories[] | select(.id==$id) | .id' "$RALPH_DIR/prd.json" 2>/dev/null)
211
+ if [[ -z "$exists" ]]; then
212
+ print_error "Story $story not found in PRD"
213
+ return 1
214
+ fi
215
+ else
216
+ story=$(jq -r '.stories[] | select(.passes==false) | .id' "$RALPH_DIR/prd.json" 2>/dev/null | head -1)
217
+ fi
218
+
219
+ if [[ -z "$story" ]]; then
220
+ print_progress_summary "$start_time" "$total_attempts" "${#skipped_stories[@]}"
221
+ send_notification "✅ Ralph finished: All stories passed!"
222
+ archive_feature
223
+ return 0
224
+ fi
225
+
226
+ ((total_attempts++))
227
+
228
+ # Track repeated failures on same story
229
+ if [[ "$story" == "$last_story" ]]; then
230
+ ((consecutive_failures++))
231
+
232
+ # Circuit breaker: skip to next story after max retries
233
+ if [[ $consecutive_failures -gt $max_story_retries ]]; then
234
+ print_error "Circuit breaker: $story failed $max_story_retries times, skipping to next story"
235
+ echo ""
236
+ echo " Saved failure context to: $RALPH_DIR/failures/$story.txt"
237
+ mkdir -p "$RALPH_DIR/failures"
238
+ cp "$RALPH_DIR/last_failure.txt" "$RALPH_DIR/failures/$story.txt" 2>/dev/null || true
239
+ skipped_stories+=("$story")
240
+ # Mark as skipped (not passed, but move on)
241
+ jq --arg id "$story" '(.stories[] | select(.id==$id)) |= . + {skipped: true}' "$RALPH_DIR/prd.json" > "$RALPH_DIR/prd.json.tmp" && mv "$RALPH_DIR/prd.json.tmp" "$RALPH_DIR/prd.json"
242
+ last_story=""
243
+ consecutive_failures=0
244
+ continue
245
+ fi
246
+
247
+ # Quick retry - no delay needed (Claude API isn't rate-limited)
248
+ print_warning "Retry $consecutive_failures/$max_story_retries for $story"
249
+ else
250
+ consecutive_failures=1
251
+ last_story="$story"
252
+ fi
253
+
254
+ # 2. Session startup checklist (Anthropic best practice)
255
+ startup_checklist
256
+
257
+ # 3. Build prompt with current story context (including failure context if any)
258
+ print_info "Preparing prompt for $story..."
259
+ local prompt_file
260
+ prompt_file=$(create_temp_file ".md") || {
261
+ print_error "Failed to create temp file for prompt"
262
+ return 1
263
+ }
264
+
265
+ local failure_context=""
266
+ if [[ -f "$RALPH_DIR/last_failure.txt" ]]; then
267
+ failure_context=$(cat "$RALPH_DIR/last_failure.txt")
268
+ fi
269
+
270
+ # Temporarily disable errexit to capture build_prompt errors
271
+ set +e
272
+ build_prompt "$story" "$failure_context" > "$prompt_file" 2>&1
273
+ local build_status=$?
274
+ set -e
275
+
276
+ if [[ $build_status -ne 0 ]]; then
277
+ print_error "Failed to build prompt (see $prompt_file for errors)"
278
+ cat "$prompt_file" | head -20
279
+ rm -f "$prompt_file"
280
+ return 1
281
+ fi
282
+
283
+ # Save git state before Claude runs (for migration detection)
284
+ local pre_story_sha=""
285
+ if command -v git &>/dev/null && [[ -d ".git" ]]; then
286
+ pre_story_sha=$(git rev-parse HEAD 2>/dev/null || echo "")
287
+ fi
288
+
289
+ # 4. Spawn Claude (fresh context, with timeout)
290
+ # Get story details for banner (single jq call for performance)
291
+ local story_json story_title story_desc story_type story_emoji
292
+ story_json=$(jq --arg id "$story" '.stories[] | select(.id==$id)' "$RALPH_DIR/prd.json")
293
+ story_title=$(echo "$story_json" | jq -r '.title // "Untitled"')
294
+ story_desc=$(echo "$story_json" | jq -r '.description // ""' | head -c 50)
295
+ story_type=$(echo "$story_json" | jq -r '.type // "general"')
296
+ story_emoji=$(type_emoji "$story_type")
297
+
298
+ # Get progress
299
+ local total_stories passed_stories current_num
300
+ total_stories=$(jq '[.stories[]] | length' "$RALPH_DIR/prd.json")
301
+ passed_stories=$(jq '[.stories[] | select(.passes==true)] | length' "$RALPH_DIR/prd.json")
302
+ current_num=$((passed_stories + 1))
303
+
304
+ # Display dynamic banner
305
+ echo ""
306
+ echo "┌─────────────────────────────────────────────────────────┐"
307
+ printf "│ %s %-14s [%d/%d] %s │\n" "$story_emoji" "$story" "$current_num" "$total_stories" "$(progress_bar $current_num $total_stories)"
308
+ printf "│ %-55s │\n" "$story_title"
309
+ printf "│ %-55s │\n" "${story_desc}..."
310
+ printf "│ Type: %-49s │\n" "$story_type"
311
+ echo "└─────────────────────────────────────────────────────────┘"
312
+ echo ""
313
+
314
+ local timeout_seconds
315
+ timeout_seconds=$(get_config '.maxSessionSeconds' "$DEFAULT_TIMEOUT_SECONDS")
316
+
317
+ # Run Claude with output visible on terminal
318
+ if ! cat "$prompt_file" | run_with_timeout "$timeout_seconds" claude -p --dangerously-skip-permissions --verbose; then
319
+ print_warning "Claude session ended (timeout or error)"
320
+ log_progress "$story" "TIMEOUT" "Claude session ended after ${timeout_seconds}s"
321
+ rm -f "$prompt_file"
322
+
323
+ # If running specific story, exit on failure
324
+ [[ -n "$specific_story" ]] && return 1
325
+ continue
326
+ fi
327
+
328
+ rm -f "$prompt_file"
329
+
330
+ # 5. Run migrations BEFORE verification (tests need DB schema)
331
+ if ! run_migrations_if_needed "$pre_story_sha"; then
332
+ log_progress "$story" "FAILED" "Migration failed"
333
+ save_failure_context "$story" # Include migration error in next prompt
334
+ print_error "Migration failed for $story, will retry with error context..."
335
+ continue
336
+ fi
337
+
338
+ # 6. Run verification pipeline
339
+ echo ""
340
+ if run_verification "$story"; then
341
+ # Mark story as complete
342
+ update_json "$RALPH_DIR/prd.json" \
343
+ --arg id "$story" '(.stories[] | select(.id==$id) | .passes) = true'
344
+
345
+ # Clear failure context on success
346
+ rm -f "$RALPH_DIR/last_failure.txt"
347
+ rm -f "$RALPH_DIR/last_migration_failure.log"
348
+ rm -f "$RALPH_DIR/last_review_failure.json"
349
+ rm -f "$RALPH_DIR/last_test_failure.log"
350
+ rm -f "$RALPH_DIR/last_playwright_failure.log"
351
+ rm -f "$RALPH_DIR/last_browser_failure.json"
352
+ rm -f "$RALPH_DIR/last_precommit_failure.log"
353
+
354
+ # Auto-commit if git is available
355
+ if command -v git &>/dev/null && [[ -d ".git" ]]; then
356
+ local title commit_log commit_success
357
+ title=$(jq -r --arg id "$story" '.stories[] | select(.id==$id) | .title' "$RALPH_DIR/prd.json")
358
+ commit_log=$(mktemp)
359
+ commit_success=false
360
+
361
+ # Try up to 3 times to handle auto-fix chains (some hooks always modify files)
362
+ for attempt in 1 2 3; do
363
+ git add -A
364
+ if git commit -m "feat($story): $title" > "$commit_log" 2>&1; then
365
+ commit_success=true
366
+ break
367
+ fi
368
+
369
+ # Check if failure is due to file modifications (auto-fix)
370
+ if grep -q "files were modified by this hook" "$commit_log" 2>/dev/null; then
371
+ # Check for REAL errors (not just file modifications or warnings)
372
+ if grep -qE "^error:|: error:|Error:|SyntaxError" "$commit_log" 2>/dev/null; then
373
+ # Real errors - stop retrying
374
+ break
375
+ fi
376
+ # ESLint with actual errors (not just warnings)
377
+ if grep -qE "✖ [0-9]+ problems? \([1-9][0-9]* errors?" "$commit_log" 2>/dev/null; then
378
+ break
379
+ fi
380
+ # Only file modifications - retry
381
+ if [[ $attempt -lt 3 ]]; then
382
+ continue
383
+ fi
384
+ # Max attempts with only file mods - try one more commit
385
+ git add -A
386
+ if git commit -m "feat($story): $title" --no-verify > "$commit_log" 2>&1; then
387
+ commit_success=true
388
+ print_warning "(committed with --no-verify due to auto-fix loop)"
389
+ fi
390
+ break
391
+ else
392
+ # Failed for other reason - check if it's a real error
393
+ if ! grep -qE "^error:|: error:|Error:|SyntaxError|✖ [0-9]+ problems? \([1-9]" "$commit_log" 2>/dev/null; then
394
+ # No real errors found - might just be warnings
395
+ # Try committing with --no-verify
396
+ git add -A
397
+ if git commit -m "feat($story): $title" --no-verify > "$commit_log" 2>&1; then
398
+ commit_success=true
399
+ print_warning "(committed with --no-verify - only warnings detected)"
400
+ fi
401
+ fi
402
+ break
403
+ fi
404
+ done
405
+
406
+ if [[ "$commit_success" != "true" ]]; then
407
+ print_warning "Pre-commit hooks failed, needs fixes..."
408
+ cp "$commit_log" "$RALPH_DIR/last_precommit_failure.log"
409
+ rm -f "$commit_log"
410
+ save_failure_context "$story"
411
+ log_progress "$story" "FAILED" "Pre-commit hooks failed"
412
+ continue
413
+ fi
414
+ rm -f "$commit_log"
415
+ fi
416
+
417
+ log_progress "$story" "COMPLETED"
418
+ print_success "Story $story completed!"
419
+
420
+ # If running specific story, we're done
421
+ [[ -n "$specific_story" ]] && return 0
422
+ else
423
+ log_progress "$story" "FAILED" "Verification failed, will retry"
424
+ print_warning "Verification failed for $story, iterating..."
425
+
426
+ # If running specific story, exit on failure
427
+ [[ -n "$specific_story" ]] && return 1
428
+ fi
429
+
430
+ sleep "$ITERATION_DELAY_SECONDS" # Brief pause between iterations
431
+ done
432
+
433
+ print_warning "Max iterations ($max_iterations) reached"
434
+ print_progress_summary "$start_time" "$total_attempts" "${#skipped_stories[@]}"
435
+ local passed failed
436
+ passed=$(jq '[.stories[] | select(.passes==true)] | length' "$RALPH_DIR/prd.json" 2>/dev/null || echo "0")
437
+ failed=$(jq '[.stories[] | select(.passes==false and .skipped!=true)] | length' "$RALPH_DIR/prd.json" 2>/dev/null || echo "0")
438
+ send_notification "⚠️ Ralph stopped: $passed passed, $failed remaining (max iterations reached)"
439
+ return 1
440
+ }
441
+
442
+ # Display startup checklist (Anthropic best practice)
443
+ startup_checklist() {
444
+ echo "--- Startup Checklist ---"
445
+ echo "Working directory: $(pwd)"
446
+ echo ""
447
+
448
+ echo "Recent progress:"
449
+ if [[ -f "$RALPH_DIR/progress.txt" ]]; then
450
+ tail -"$MAX_PROGRESS_LINES" "$RALPH_DIR/progress.txt" | sed 's/^/ /'
451
+ else
452
+ echo " (no progress yet)"
453
+ fi
454
+ echo ""
455
+
456
+ echo "Stories:"
457
+ jq -r '.stories[] | " \(.id): \(.title) [\(if .passes then "DONE" else "TODO" end)]"' "$RALPH_DIR/prd.json" 2>/dev/null || echo " (none)"
458
+ echo ""
459
+
460
+ if command -v git &>/dev/null && [[ -d ".git" ]]; then
461
+ echo "Git status:"
462
+ git status --short | head -"$MAX_GIT_STATUS_LINES" | sed 's/^/ /'
463
+ echo ""
464
+ fi
465
+ }
466
+
467
+ # Helper: Inject story details into prompt
468
+ _inject_story_context() {
469
+ local story_json="$1"
470
+
471
+ echo ""
472
+ echo "---"
473
+ echo ""
474
+ echo "## Current Story"
475
+ echo ""
476
+ echo '```json'
477
+ echo "$story_json"
478
+ echo '```'
479
+ }
480
+
481
+ # Helper: Inject file guidance into prompt
482
+ _inject_file_guidance() {
483
+ local story_json="$1"
484
+
485
+ local has_files
486
+ has_files=$(echo "$story_json" | jq -r '.files // empty' 2>/dev/null)
487
+ [[ -z "$has_files" ]] && return
488
+
489
+ echo ""
490
+ echo "### File Guidance for This Story"
491
+ echo ""
492
+ echo "**Create these files:**"
493
+ echo "$story_json" | jq -r '.files.create[]? // empty' | sed 's/^/- /'
494
+ echo ""
495
+ echo "**Modify these files:**"
496
+ echo "$story_json" | jq -r '.files.modify[]? // empty' | sed 's/^/- /'
497
+ echo ""
498
+ echo "**Reuse/import from:**"
499
+ echo "$story_json" | jq -r '.files.reuse[]? // empty' | sed 's/^/- /'
500
+ }
501
+
502
+ # Helper: Inject scalability guidance for story
503
+ _inject_story_scale() {
504
+ local story_json="$1"
505
+
506
+ local has_scale
507
+ has_scale=$(echo "$story_json" | jq -r '.scale // empty' 2>/dev/null)
508
+ [[ -z "$has_scale" ]] && return
509
+
510
+ echo ""
511
+ echo "### Scalability Requirements for This Story"
512
+ echo ""
513
+ echo "$story_json" | jq -r '.scale | to_entries[] | "- **\(.key):** \(.value)"' 2>/dev/null
514
+ }
515
+
516
+ # Helper: Inject styleguide reference for frontend stories
517
+ _inject_styleguide() {
518
+ local story_json="$1"
519
+
520
+ local story_type
521
+ story_type=$(echo "$story_json" | jq -r '.type // "frontend"' 2>/dev/null)
522
+ local styleguide_path
523
+ styleguide_path=$(get_config '.styleguide' "")
524
+
525
+ if [[ "$story_type" == "frontend" && -n "$styleguide_path" && -f "$styleguide_path" ]]; then
526
+ echo ""
527
+ echo "### Styleguide"
528
+ echo ""
529
+ echo "**FIRST:** Read the project styleguide at \`$styleguide_path\` before implementing."
530
+ echo "Use existing components, colors, and patterns from the styleguide."
531
+ fi
532
+ }
533
+
534
+ # Helper: Inject feature-level context
535
+ _inject_feature_context() {
536
+ echo ""
537
+ echo "## Feature Context"
538
+ echo ""
539
+ echo '```json'
540
+ jq '{feature: .feature, metadata: .metadata}' "$RALPH_DIR/prd.json"
541
+ echo '```'
542
+ }
543
+
544
+ # Helper: Inject scalability requirements
545
+ _inject_scalability() {
546
+ local has_scalability
547
+ has_scalability=$(jq -r '.scalability // empty' "$RALPH_DIR/prd.json" 2>/dev/null)
548
+ [[ -z "$has_scalability" ]] && return
549
+
550
+ echo ""
551
+ echo "## Scalability Requirements"
552
+ echo ""
553
+ echo "**IMPORTANT:** Follow these scalability rules."
554
+ echo ""
555
+ echo '```json'
556
+ jq '.scalability' "$RALPH_DIR/prd.json"
557
+ echo '```'
558
+ echo ""
559
+ echo "### Key Rules:"
560
+ echo "- Always paginate list endpoints (never return unbounded arrays)"
561
+ echo "- Avoid N+1 queries - eager load relationships"
562
+ echo "- Add database indexes for frequently queried fields"
563
+ echo "- Implement caching strategy as specified"
564
+ echo "- Add rate limiting to public endpoints"
565
+ }
566
+
567
+ # Helper: Inject architecture guidelines
568
+ _inject_architecture() {
569
+ local has_architecture
570
+ has_architecture=$(jq -r '.architecture // empty' "$RALPH_DIR/prd.json" 2>/dev/null)
571
+ [[ -z "$has_architecture" ]] && return
572
+
573
+ echo ""
574
+ echo "## Architecture Guidelines"
575
+ echo ""
576
+ echo "**IMPORTANT:** Follow these architecture rules strictly."
577
+ echo ""
578
+ echo '```json'
579
+ jq '.architecture' "$RALPH_DIR/prd.json"
580
+ echo '```'
581
+ echo ""
582
+ echo "### Key Rules:"
583
+ echo "- Put files in the specified directories"
584
+ echo "- Reuse existing components listed in 'patterns.reuse'"
585
+ echo "- Do NOT create anything in 'doNotCreate'"
586
+ echo "- Keep files under $(jq -r '.architecture.principles.maxFileLines // 300' "$RALPH_DIR/prd.json") lines"
587
+ echo "- Scripts go in scripts/, docs go in docs/"
588
+ }
589
+
590
+ # Helper: Inject failure context from previous iteration
591
+ _inject_failure_context() {
592
+ local failure_context="$1"
593
+ [[ -z "$failure_context" ]] && return
594
+
595
+ echo ""
596
+ echo "## Previous Iteration Failed"
597
+ echo ""
598
+ echo "**IMPORTANT:** The previous attempt at this story failed verification. Review the errors below and fix them."
599
+ echo ""
600
+ echo '```'
601
+ echo "$failure_context"
602
+ echo '```'
603
+ echo ""
604
+ echo "### What to do:"
605
+ echo "1. Read the error messages carefully"
606
+ echo "2. Identify the root cause"
607
+ echo "3. Fix the issue (do not just retry the same approach)"
608
+ echo "4. Run verification again"
609
+ }
610
+
611
+ # Helper: Inject signs (learned patterns)
612
+ _inject_signs() {
613
+ echo ""
614
+ echo "## Signs (Learned Patterns)"
615
+ echo ""
616
+ echo "Apply these lessons from previous sessions:"
617
+ echo ""
618
+ if [[ -f "$RALPH_DIR/signs.json" ]]; then
619
+ jq -r '.signs[] | "- [\(.category)] \(.pattern)"' "$RALPH_DIR/signs.json" 2>/dev/null || echo "(none yet)"
620
+ else
621
+ echo "(none yet)"
622
+ fi
623
+ }
624
+
625
+ # Helper: Inject developer DNA
626
+ _inject_developer_dna() {
627
+ [[ ! -f "$HOME/.claude/DNA.md" ]] && return
628
+
629
+ echo ""
630
+ echo "## Developer DNA"
631
+ echo ""
632
+ echo "The developer has these working preferences:"
633
+ echo ""
634
+ cat "$HOME/.claude/DNA.md"
635
+ }
636
+
637
+ # Build the prompt with story context injected
638
+ build_prompt() {
639
+ local story="$1"
640
+ local failure_context="${2:-}"
641
+
642
+ # Read base PROMPT.md
643
+ cat "$PROMPT_FILE"
644
+
645
+ # Get story JSON once
646
+ local story_json
647
+ story_json=$(jq --arg id "$story" '.stories[] | select(.id==$id)' "$RALPH_DIR/prd.json")
648
+
649
+ # Inject all sections
650
+ _inject_story_context "$story_json"
651
+ _inject_file_guidance "$story_json"
652
+ _inject_story_scale "$story_json"
653
+ _inject_styleguide "$story_json"
654
+ _inject_feature_context
655
+ _inject_scalability
656
+ _inject_architecture
657
+ _inject_failure_context "$failure_context"
658
+ _inject_signs
659
+ _inject_developer_dna
660
+ }
661
+
662
+ # Print progress summary at end of run
663
+ print_progress_summary() {
664
+ local start_time="$1"
665
+ local total_attempts="$2"
666
+ local skipped_count="$3"
667
+
668
+ local end_time
669
+ end_time=$(date +%s)
670
+ local duration=$((end_time - start_time))
671
+ local hours=$((duration / 3600))
672
+ local minutes=$(((duration % 3600) / 60))
673
+
674
+ local passed failed total
675
+ passed=$(jq '[.stories[] | select(.passes==true)] | length' "$RALPH_DIR/prd.json" 2>/dev/null || echo "0")
676
+ failed=$(jq '[.stories[] | select(.passes==false and .skipped!=true)] | length' "$RALPH_DIR/prd.json" 2>/dev/null || echo "0")
677
+ total=$(jq '.stories | length' "$RALPH_DIR/prd.json" 2>/dev/null || echo "0")
678
+
679
+ echo ""
680
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
681
+ print_success "COMPLETE"
682
+ echo ""
683
+ echo " Stories: $passed/$total passed"
684
+ [[ "$skipped_count" -gt 0 ]] && echo " Skipped: $skipped_count (hit circuit breaker)"
685
+ echo " Attempts: $total_attempts total iterations"
686
+ if [[ $hours -gt 0 ]]; then
687
+ echo " Duration: ${hours}h ${minutes}m"
688
+ else
689
+ echo " Duration: ${minutes}m"
690
+ fi
691
+ echo ""
692
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
693
+ }
694
+
695
+ # Mark feature as complete (keep PRD for appending new stories)
696
+ archive_feature() {
697
+ local feature_name
698
+ feature_name=$(jq -r '.feature.name' "$RALPH_DIR/prd.json")
699
+
700
+ print_success "Feature '$feature_name' complete!"
701
+
702
+ # Update status to complete (don't archive - user may want to append stories)
703
+ update_json "$RALPH_DIR/prd.json" '.feature.status = "complete"'
704
+
705
+ # Final commit if git available
706
+ if command -v git &>/dev/null && [[ -d ".git" ]]; then
707
+ git add -A
708
+ if ! git commit -m "feat: complete $feature_name" 2>/dev/null; then
709
+ # Retry after pre-commit auto-fixes
710
+ git add -A
711
+ if ! git commit -m "feat: complete $feature_name" 2>/dev/null; then
712
+ # Check if it's "nothing to commit" vs real error
713
+ if git diff --cached --quiet 2>/dev/null; then
714
+ echo " (no changes to commit)"
715
+ else
716
+ print_warning "Final commit failed - check git status"
717
+ fi
718
+ fi
719
+ fi
720
+ fi
721
+
722
+ log_progress "FEATURE" "COMPLETE" "$feature_name"
723
+
724
+ echo ""
725
+ echo "All stories passed! PRD kept at: $RALPH_DIR/prd.json"
726
+ echo ""
727
+ echo "Next:"
728
+ echo " /idea 'new feature' # Add more stories (will append)"
729
+ echo " ralph status # See completed stories"
730
+ }