thrivekit 2.0.13 → 2.0.15

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thrivekit",
3
- "version": "2.0.13",
3
+ "version": "2.0.15",
4
4
  "description": "Tools to thrive with agentic coding - RALPH autonomous loop, Claude Code hooks, PRD-driven development",
5
5
  "author": "Allie Jones <allie@allthrive.ai>",
6
6
  "license": "MIT",
package/ralph/loop.sh CHANGED
@@ -2,6 +2,90 @@
2
2
  # shellcheck shell=bash
3
3
  # loop.sh - The autonomous development loop
4
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
+ # Try to verify DB connection via alembic or Django
48
+ if [[ -f "$backend_dir/alembic.ini" ]]; then
49
+ if (cd "$backend_dir" && alembic current >/dev/null 2>&1); then
50
+ print_success "ok"
51
+ else
52
+ print_warning "check DB connection"
53
+ echo " Run: cd $backend_dir && alembic current"
54
+ ((warnings++))
55
+ fi
56
+ fi
57
+ fi
58
+ fi
59
+
60
+ # Check Docker if docker-compose exists
61
+ for compose_file in "docker-compose.yml" "docker-compose.yaml" "compose.yml"; do
62
+ if [[ -f "$compose_file" ]]; then
63
+ printf " Docker services... "
64
+ if docker compose ps --quiet 2>/dev/null | grep -q .; then
65
+ print_success "running"
66
+ elif docker-compose ps --quiet 2>/dev/null | grep -q .; then
67
+ print_success "running"
68
+ else
69
+ print_warning "not running"
70
+ echo " Run: docker compose up -d"
71
+ ((warnings++))
72
+ fi
73
+ break
74
+ fi
75
+ done
76
+
77
+ echo ""
78
+ if [[ $warnings -gt 0 ]]; then
79
+ print_warning "$warnings pre-flight warning(s) - loop may fail on connectivity issues"
80
+ echo ""
81
+ read -r -p "Continue anyway? [Y/n] " response
82
+ if [[ "$response" =~ ^[Nn] ]]; then
83
+ echo "Aborted. Fix the issues and try again."
84
+ exit 1
85
+ fi
86
+ fi
87
+ }
88
+
5
89
  run_loop() {
6
90
  local max_iterations="$DEFAULT_MAX_ITERATIONS"
7
91
  local specific_story=""
@@ -26,6 +110,9 @@ run_loop() {
26
110
  # Validate prerequisites
27
111
  check_dependencies
28
112
 
113
+ # Pre-flight checks to catch issues before wasting iterations
114
+ preflight_checks
115
+
29
116
  if [[ ! -f "$RALPH_DIR/prd.json" ]]; then
30
117
  # Check for misplaced PRD in subdirectories
31
118
  local found_prd
@@ -73,7 +160,11 @@ run_loop() {
73
160
  local iteration=0
74
161
  local last_story=""
75
162
  local consecutive_failures=0
76
- local max_story_retries=3
163
+ local max_story_retries=5
164
+ local total_attempts=0
165
+ local skipped_stories=()
166
+ local start_time
167
+ start_time=$(date +%s)
77
168
 
78
169
  while [[ $iteration -lt $max_iterations ]]; do
79
170
  # Check for stop signal
@@ -108,19 +199,37 @@ run_loop() {
108
199
  fi
109
200
 
110
201
  if [[ -z "$story" ]]; then
202
+ print_progress_summary "$start_time" "$total_attempts" "${#skipped_stories[@]}"
111
203
  send_notification "✅ Ralph finished: All stories passed!"
112
204
  archive_feature
113
205
  return 0
114
206
  fi
115
207
 
208
+ ((total_attempts++))
209
+
116
210
  # Track repeated failures on same story
117
211
  if [[ "$story" == "$last_story" ]]; then
118
212
  ((consecutive_failures++))
119
- if [[ $consecutive_failures -ge $max_story_retries ]]; then
120
- print_warning "$story failed $consecutive_failures times - check failure context above"
121
- else
122
- print_warning "Retry $consecutive_failures/$max_story_retries for $story (failure context included)"
213
+
214
+ # Circuit breaker: skip to next story after max retries
215
+ if [[ $consecutive_failures -gt $max_story_retries ]]; then
216
+ print_error "Circuit breaker: $story failed $max_story_retries times, skipping to next story"
217
+ echo ""
218
+ echo " Saved failure context to: $RALPH_DIR/failures/$story.txt"
219
+ mkdir -p "$RALPH_DIR/failures"
220
+ cp "$RALPH_DIR/last_failure.txt" "$RALPH_DIR/failures/$story.txt" 2>/dev/null || true
221
+ skipped_stories+=("$story")
222
+ # Mark as skipped (not passed, but move on)
223
+ 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"
224
+ last_story=""
225
+ consecutive_failures=0
226
+ continue
123
227
  fi
228
+
229
+ # Exponential backoff before retry
230
+ local backoff=$((2 ** (consecutive_failures - 1)))
231
+ print_warning "Retry $consecutive_failures/$max_story_retries for $story (waiting ${backoff}s...)"
232
+ sleep "$backoff"
124
233
  else
125
234
  consecutive_failures=1
126
235
  last_story="$story"
@@ -305,9 +414,10 @@ run_loop() {
305
414
  done
306
415
 
307
416
  print_warning "Max iterations ($max_iterations) reached"
417
+ print_progress_summary "$start_time" "$total_attempts" "${#skipped_stories[@]}"
308
418
  local passed failed
309
419
  passed=$(jq '[.stories[] | select(.passes==true)] | length' "$RALPH_DIR/prd.json" 2>/dev/null || echo "0")
310
- failed=$(jq '[.stories[] | select(.passes==false)] | length' "$RALPH_DIR/prd.json" 2>/dev/null || echo "0")
420
+ failed=$(jq '[.stories[] | select(.passes==false and .skipped!=true)] | length' "$RALPH_DIR/prd.json" 2>/dev/null || echo "0")
311
421
  send_notification "⚠️ Ralph stopped: $passed passed, $failed remaining (max iterations reached)"
312
422
  return 1
313
423
  }
@@ -532,6 +642,39 @@ build_prompt() {
532
642
  _inject_developer_dna
533
643
  }
534
644
 
645
+ # Print progress summary at end of run
646
+ print_progress_summary() {
647
+ local start_time="$1"
648
+ local total_attempts="$2"
649
+ local skipped_count="$3"
650
+
651
+ local end_time
652
+ end_time=$(date +%s)
653
+ local duration=$((end_time - start_time))
654
+ local hours=$((duration / 3600))
655
+ local minutes=$(((duration % 3600) / 60))
656
+
657
+ local passed failed total
658
+ passed=$(jq '[.stories[] | select(.passes==true)] | length' "$RALPH_DIR/prd.json" 2>/dev/null || echo "0")
659
+ failed=$(jq '[.stories[] | select(.passes==false and .skipped!=true)] | length' "$RALPH_DIR/prd.json" 2>/dev/null || echo "0")
660
+ total=$(jq '.stories | length' "$RALPH_DIR/prd.json" 2>/dev/null || echo "0")
661
+
662
+ echo ""
663
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
664
+ print_success "COMPLETE"
665
+ echo ""
666
+ echo " Stories: $passed/$total passed"
667
+ [[ "$skipped_count" -gt 0 ]] && echo " Skipped: $skipped_count (hit circuit breaker)"
668
+ echo " Attempts: $total_attempts total iterations"
669
+ if [[ $hours -gt 0 ]]; then
670
+ echo " Duration: ${hours}h ${minutes}m"
671
+ else
672
+ echo " Duration: ${minutes}m"
673
+ fi
674
+ echo ""
675
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
676
+ }
677
+
535
678
  # Mark feature as complete (keep PRD for appending new stories)
536
679
  archive_feature() {
537
680
  local feature_name
@@ -78,21 +78,9 @@ Check for these issues:
78
78
 
79
79
  ## Response Format
80
80
 
81
- Respond with ONLY a JSON object:
82
- {
83
- "pass": true/false,
84
- "issues": [
85
- {
86
- "severity": "critical|warning|info",
87
- "category": "security|error-handling|edge-case|quality|performance|scalability|a11y|architecture|compliance",
88
- "file": "path/to/file",
89
- "line": 123,
90
- "message": "Description of the issue",
91
- "suggestion": "How to fix it"
92
- }
93
- ],
94
- "summary": "Brief overall assessment"
95
- }
81
+ IMPORTANT: Output ONLY raw JSON, no markdown formatting, no code blocks, no explanation.
82
+
83
+ {"pass": true/false, "issues": [{"severity": "critical|warning|info", "category": "security|error-handling|edge-case|quality|performance|scalability|a11y|architecture|compliance", "file": "path/to/file", "line": 123, "message": "Description", "suggestion": "Fix"}], "summary": "Brief assessment"}
96
84
 
97
85
  Only fail (pass: false) for critical or multiple warning-level issues.
98
86
  EOF